@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.
Files changed (47) 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-uccD6Pnv.cjs → card-BUITGoqX.cjs} +10 -10
  5. package/dist/{card-BI1NZONj.js → card-Dtw26f7i.js} +96 -76
  6. package/dist/checkbox-Gn7Wtk9h.cjs +31 -0
  7. package/dist/checkbox-ym7z6cpt.js +142 -0
  8. package/dist/{combobox-BhcqC30d.cjs → combobox-DjO0RMUB.cjs} +1 -1
  9. package/dist/{combobox-D9dGKWuZ.js → combobox-yE4aYhTi.js} +1 -1
  10. package/dist/{datepicker-CYOn3tRm.js → datepicker-BJKJBoy_.js} +102 -59
  11. package/dist/datepicker-CmTrG5GE.cjs +164 -0
  12. package/dist/index.d.ts +9 -2
  13. package/dist/pkt-calendar.cjs +1 -1
  14. package/dist/pkt-calendar.js +1 -1
  15. package/dist/pkt-card.cjs +1 -1
  16. package/dist/pkt-card.js +1 -1
  17. package/dist/pkt-checkbox.cjs +1 -1
  18. package/dist/pkt-checkbox.js +1 -1
  19. package/dist/pkt-combobox.cjs +1 -1
  20. package/dist/pkt-combobox.js +1 -1
  21. package/dist/pkt-datepicker.cjs +1 -1
  22. package/dist/pkt-datepicker.js +1 -1
  23. package/dist/pkt-index.cjs +1 -1
  24. package/dist/pkt-index.js +6 -6
  25. package/package.json +3 -3
  26. package/src/components/calendar/calendar.accessibility.test.ts +111 -0
  27. package/src/components/calendar/calendar.constraints.test.ts +110 -0
  28. package/src/components/calendar/calendar.core.test.ts +367 -0
  29. package/src/components/calendar/calendar.interaction.test.ts +139 -0
  30. package/src/components/calendar/calendar.selection.test.ts +273 -0
  31. package/src/components/calendar/calendar.ts +74 -42
  32. package/src/components/card/card.test.ts +606 -0
  33. package/src/components/card/card.ts +24 -1
  34. package/src/components/checkbox/checkbox.test.ts +535 -0
  35. package/src/components/checkbox/checkbox.ts +44 -1
  36. package/src/components/combobox/combobox.test.ts +737 -0
  37. package/src/components/combobox/combobox.ts +1 -1
  38. package/src/components/datepicker/datepicker.accessibility.test.ts +193 -0
  39. package/src/components/datepicker/datepicker.core.test.ts +322 -0
  40. package/src/components/datepicker/datepicker.input.test.ts +268 -0
  41. package/src/components/datepicker/datepicker.selection.test.ts +286 -0
  42. package/src/components/datepicker/datepicker.ts +121 -19
  43. package/src/components/datepicker/datepicker.validation.test.ts +176 -0
  44. package/dist/calendar-BZe2D4Sr.cjs +0 -108
  45. package/dist/checkbox-CTRbpbye.js +0 -120
  46. package/dist/checkbox-wJ26voZd.cjs +0 -30
  47. package/dist/datepicker-B9rhz_AF.cjs +0 -154
@@ -184,7 +184,7 @@ export class PktCombobox extends PktInputElement implements IPktCombobox {
184
184
  this._options = [...this.options]
185
185
  }
186
186
 
187
- if (changedProperties.has('options') && this.options.length) {
187
+ if (changedProperties.has('options')) {
188
188
  // If options change, we need to update _options, but we need to preserve userAdded values
189
189
  const userAddedValues = this._options.filter((option) => option.userAdded)
190
190
  // Filter out userAddedValues that are overridden by this.options
@@ -0,0 +1,193 @@
1
+ import '@testing-library/jest-dom'
2
+ import { axe, toHaveNoViolations } from 'jest-axe'
3
+ import { fireEvent } from '@testing-library/dom'
4
+
5
+ expect.extend(toHaveNoViolations)
6
+
7
+ import './datepicker'
8
+ import '../calendar/calendar'
9
+ import { PktDatepicker } from './datepicker'
10
+
11
+ const waitForCustomElements = async () => {
12
+ await customElements.whenDefined('pkt-datepicker')
13
+ await customElements.whenDefined('pkt-calendar')
14
+ }
15
+
16
+ // Helper function to create datepicker markup
17
+ const createDatepicker = async (datepickerProps = '') => {
18
+ const container = document.createElement('div')
19
+ container.innerHTML = `
20
+ <pkt-datepicker ${datepickerProps}></pkt-datepicker>
21
+ `
22
+ document.body.appendChild(container)
23
+ await waitForCustomElements()
24
+ return container
25
+ }
26
+
27
+ // Cleanup after each test
28
+ afterEach(() => {
29
+ document.body.innerHTML = ''
30
+ })
31
+
32
+ describe('PktDatepicker', () => {
33
+ describe('Event handling', () => {
34
+ test('dispatches change event when value changes', async () => {
35
+ const container = await createDatepicker()
36
+
37
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
38
+ await datepicker.updateComplete
39
+
40
+ let changeEventFired = false
41
+ datepicker.addEventListener('change', () => {
42
+ changeEventFired = true
43
+ })
44
+
45
+ const input = datepicker.querySelector('input') as HTMLInputElement
46
+ fireEvent.change(input, { target: { value: '2024-06-15' } })
47
+ fireEvent.blur(input)
48
+ await datepicker.updateComplete
49
+
50
+ expect(changeEventFired).toBe(true)
51
+ })
52
+
53
+ test('dispatches input event during typing', async () => {
54
+ const container = await createDatepicker()
55
+
56
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
57
+ await datepicker.updateComplete
58
+
59
+ let inputEventFired = false
60
+ datepicker.addEventListener('input', () => {
61
+ inputEventFired = true
62
+ })
63
+
64
+ const input = datepicker.querySelector('input') as HTMLInputElement
65
+ fireEvent.input(input, { target: { value: '2024-06-15' } })
66
+ await datepicker.updateComplete
67
+
68
+ expect(inputEventFired).toBe(true)
69
+ })
70
+
71
+ test('dispatches focus and blur events', async () => {
72
+ const container = await createDatepicker()
73
+
74
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
75
+ await datepicker.updateComplete
76
+
77
+ let focusEventFired = false
78
+ let blurEventFired = false
79
+
80
+ datepicker.addEventListener('focus', () => {
81
+ focusEventFired = true
82
+ })
83
+ datepicker.addEventListener('blur', () => {
84
+ blurEventFired = true
85
+ })
86
+
87
+ const input = datepicker.querySelector('input') as HTMLInputElement
88
+ fireEvent.focus(input)
89
+ await datepicker.updateComplete
90
+ fireEvent.blur(input)
91
+ await datepicker.updateComplete
92
+
93
+ expect(focusEventFired).toBe(true)
94
+ expect(blurEventFired).toBe(true)
95
+ })
96
+ })
97
+
98
+ describe('Accessibility', () => {
99
+ test('has no accessibility violations', async () => {
100
+ const container = await createDatepicker(
101
+ 'label="Test Datepicker" helptext="Select your date"',
102
+ )
103
+
104
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
105
+ await datepicker.updateComplete
106
+
107
+ const results = await axe(datepicker)
108
+ expect(results).toHaveNoViolations()
109
+ })
110
+
111
+ test('associates label with input correctly', async () => {
112
+ const container = await createDatepicker('id="test-datepicker" label="Test Label"')
113
+
114
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
115
+ await datepicker.updateComplete
116
+
117
+ const label = datepicker.querySelector('label')
118
+ const input = datepicker.querySelector('input')
119
+
120
+ expect(label).toHaveAttribute('for')
121
+ expect(input).toHaveAttribute('id')
122
+ expect(label?.getAttribute('for')).toBe(input?.getAttribute('id'))
123
+ })
124
+
125
+ test('has proper ARIA attributes for calendar button', async () => {
126
+ const container = await createDatepicker('label="Test Datepicker"')
127
+
128
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
129
+ await datepicker.updateComplete
130
+
131
+ const calendarButton = datepicker.querySelector('button[type="button"]')
132
+ expect(calendarButton).toBeInTheDocument()
133
+
134
+ // Check initial state
135
+ expect(datepicker.calendarOpen).toBe(false)
136
+
137
+ // Open calendar
138
+ fireEvent.click(calendarButton!)
139
+ await datepicker.updateComplete
140
+
141
+ expect(datepicker.calendarOpen).toBe(true)
142
+ })
143
+
144
+ test('provides proper screen reader announcements', async () => {
145
+ const container = await createDatepicker('label="Test Datepicker"')
146
+
147
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
148
+ await datepicker.updateComplete
149
+
150
+ const input = datepicker.querySelector('input')
151
+ expect(input).toHaveAttribute('aria-describedby')
152
+ })
153
+
154
+ test('handles focus management correctly', async () => {
155
+ const container = await createDatepicker()
156
+
157
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
158
+ await datepicker.updateComplete
159
+
160
+ const calendarButton = datepicker.querySelector('button[type="button"]') as HTMLElement
161
+
162
+ // Focus should be manageable
163
+ if (calendarButton) {
164
+ calendarButton.focus()
165
+ expect(document.activeElement).toBe(calendarButton)
166
+ }
167
+ })
168
+
169
+ test('supports keyboard-only interaction', async () => {
170
+ const container = await createDatepicker()
171
+
172
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
173
+ await datepicker.updateComplete
174
+
175
+ const calendarButton = datepicker.querySelector('button[type="button"]') as HTMLElement
176
+
177
+ // Should open with keyboard
178
+ if (calendarButton) {
179
+ calendarButton.focus()
180
+ fireEvent.keyDown(calendarButton, { key: 'Enter' })
181
+ await datepicker.updateComplete
182
+
183
+ expect(datepicker.calendarOpen).toBe(true)
184
+ }
185
+
186
+ // Should close with keyboard
187
+ fireEvent.keyDown(datepicker, { key: 'Escape' })
188
+ await datepicker.updateComplete
189
+
190
+ expect(datepicker.calendarOpen).toBe(false)
191
+ })
192
+ })
193
+ })
@@ -0,0 +1,322 @@
1
+ import '@testing-library/jest-dom'
2
+ import { toHaveNoViolations } from 'jest-axe'
3
+ import { fireEvent } from '@testing-library/dom'
4
+ import { parseISODateString } from '@/utils/dateutils'
5
+
6
+ expect.extend(toHaveNoViolations)
7
+
8
+ import './datepicker'
9
+ import '../calendar/calendar'
10
+ import { PktDatepicker } from './datepicker'
11
+ import { PktCalendar } from '../calendar/calendar'
12
+
13
+ const waitForCustomElements = async () => {
14
+ await customElements.whenDefined('pkt-datepicker')
15
+ await customElements.whenDefined('pkt-calendar')
16
+ }
17
+
18
+ // Helper function to create datepicker markup
19
+ const createDatepicker = async (datepickerProps = '') => {
20
+ const container = document.createElement('div')
21
+ container.innerHTML = `
22
+ <pkt-datepicker ${datepickerProps}></pkt-datepicker>
23
+ `
24
+ document.body.appendChild(container)
25
+ await waitForCustomElements()
26
+ return container
27
+ }
28
+
29
+ // Cleanup after each test
30
+ afterEach(() => {
31
+ document.body.innerHTML = ''
32
+ })
33
+
34
+ describe('PktDatepicker', () => {
35
+ describe('Rendering and basic functionality', () => {
36
+ test('renders without errors', async () => {
37
+ const container = await createDatepicker()
38
+
39
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
40
+ expect(datepicker).toBeInTheDocument()
41
+
42
+ await datepicker.updateComplete
43
+ expect(datepicker).toBeTruthy()
44
+ })
45
+
46
+ test('renders with correct structure', async () => {
47
+ const container = await createDatepicker('label="Test Datepicker"')
48
+
49
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
50
+ await datepicker.updateComplete
51
+
52
+ const inputWrapper = datepicker.querySelector('pkt-input-wrapper')
53
+ const input = datepicker.querySelector('input')
54
+ const calendarButton = datepicker.querySelector('button[type="button"]')
55
+
56
+ expect(inputWrapper).toBeInTheDocument()
57
+ expect(input).toBeInTheDocument()
58
+ expect(calendarButton).toBeInTheDocument()
59
+ })
60
+
61
+ test('renders calendar when opened', async () => {
62
+ const container = await createDatepicker('label="Test Datepicker"')
63
+
64
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
65
+ await datepicker.updateComplete
66
+
67
+ const calendarButton = datepicker.querySelector('button[type="button"]')
68
+ fireEvent.click(calendarButton!)
69
+ await datepicker.updateComplete
70
+
71
+ const calendar = datepicker.querySelector('pkt-calendar')
72
+ expect(calendar).toBeInTheDocument()
73
+ })
74
+
75
+ test('closes calendar when clicking outside', async () => {
76
+ const container = await createDatepicker('label="Test Datepicker"')
77
+
78
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
79
+ await datepicker.updateComplete
80
+
81
+ // Open calendar
82
+ const calendarButton = datepicker.querySelector('button[type="button"]')
83
+ fireEvent.click(calendarButton!)
84
+ await datepicker.updateComplete
85
+
86
+ // Click outside
87
+ fireEvent.click(document.body)
88
+ await datepicker.updateComplete
89
+
90
+ expect(datepicker.calendarOpen).toBe(false)
91
+ })
92
+
93
+ test('closes calendar when clicking outside', async () => {
94
+ const container = await createDatepicker('label="Test Datepicker"')
95
+
96
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
97
+ await datepicker.updateComplete
98
+
99
+ // Open calendar
100
+ const calendarButton = datepicker.querySelector('button[type="button"]')
101
+ fireEvent.click(calendarButton!)
102
+ await datepicker.updateComplete
103
+
104
+ // Click outside
105
+ fireEvent.click(document.body)
106
+ await datepicker.updateComplete
107
+
108
+ expect(datepicker.calendarOpen).toBe(false)
109
+ })
110
+ })
111
+
112
+ describe('Properties and attributes', () => {
113
+ test('applies default properties correctly', async () => {
114
+ const container = await createDatepicker('name="test" id="test"')
115
+
116
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
117
+ await datepicker.updateComplete
118
+
119
+ expect(datepicker.value).toBe('')
120
+ expect(datepicker.label).toBe('Datovelger')
121
+ expect(datepicker.multiple).toBe(false)
122
+ expect(datepicker.range).toBe(false)
123
+ expect(datepicker.maxlength).toBe(null)
124
+ expect(datepicker.showRangeLabels).toBe(false)
125
+ })
126
+
127
+ test('handles value property correctly', async () => {
128
+ const testDate = '2024-06-15'
129
+ const container = await createDatepicker(`value="${testDate}"`)
130
+
131
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
132
+ await datepicker.updateComplete
133
+
134
+ expect(datepicker.value).toBe(testDate)
135
+
136
+ const input = datepicker.querySelector('input') as HTMLInputElement
137
+ expect(input.value).toBeTruthy()
138
+ })
139
+
140
+ test('handles multiple values correctly', async () => {
141
+ const testDates = '2024-06-15,2024-06-20,2024-06-25'
142
+ const container = await createDatepicker(`value="${testDates}" multiple`)
143
+
144
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
145
+ await datepicker.updateComplete
146
+
147
+ expect(datepicker.value).toBe(testDates)
148
+ expect(datepicker.multiple).toBe(true)
149
+
150
+ // Should show tags for multiple values
151
+ const tags = datepicker.querySelectorAll('pkt-tag')
152
+ expect(tags.length).toBe(3)
153
+ })
154
+
155
+ test('handles range values correctly', async () => {
156
+ const rangeValue = '2024-06-15,2024-06-20'
157
+ const container = await createDatepicker(`value="${rangeValue}" range`)
158
+
159
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
160
+ await datepicker.updateComplete
161
+
162
+ expect(datepicker.value).toBe(rangeValue)
163
+ expect(datepicker.range).toBe(true)
164
+ })
165
+
166
+ test('handles label property correctly', async () => {
167
+ const testLabel = 'Select your date'
168
+ const container = await createDatepicker(`label="${testLabel}"`)
169
+
170
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
171
+ await datepicker.updateComplete
172
+
173
+ expect(datepicker.label).toBe(testLabel)
174
+
175
+ const label = datepicker.querySelector('label')
176
+ expect(label?.textContent).toContain(testLabel)
177
+ })
178
+
179
+ test('handles dateformat property correctly', async () => {
180
+ const customFormat = 'MM/dd/yyyy'
181
+ const container = await createDatepicker(
182
+ `dateformat="${customFormat}" value="2024-06-15" multiple`,
183
+ )
184
+
185
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
186
+ await datepicker.updateComplete
187
+
188
+ expect(datepicker.dateformat).toBe(customFormat)
189
+
190
+ // Multiple mode input should be empty (for new input)
191
+ const input = datepicker.querySelector('input') as HTMLInputElement
192
+ expect(input.value).toBe('')
193
+
194
+ // Selected dates should show in tags with custom format
195
+ const tag = datepicker.querySelector('pkt-tag time')
196
+ expect(tag).toBeInTheDocument()
197
+ if (tag) {
198
+ expect(tag.textContent).toMatch(/\d{2}\/\d{2}\/\d{4}/)
199
+ }
200
+ })
201
+
202
+ test('handles maxlength property correctly', async () => {
203
+ const container = await createDatepicker('multiple maxlength="3"')
204
+
205
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
206
+ await datepicker.updateComplete
207
+
208
+ expect(datepicker.maxlength).toBe(3)
209
+ })
210
+
211
+ test('handles disabled property correctly', async () => {
212
+ const container = await createDatepicker('disabled')
213
+
214
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
215
+ await datepicker.updateComplete
216
+
217
+ expect(datepicker.disabled).toBe(true)
218
+
219
+ const input = datepicker.querySelector('input') as HTMLInputElement
220
+ const calendarButton = datepicker.querySelector('button[type="button"]') as HTMLButtonElement
221
+
222
+ expect(input.disabled).toBe(true)
223
+ expect(calendarButton.disabled).toBe(true)
224
+ })
225
+
226
+ test('handles currentmonth property correctly', async () => {
227
+ const testMonth = '2024-03-01'
228
+ const container = await createDatepicker(`currentmonth="${testMonth}"`)
229
+
230
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
231
+ await datepicker.updateComplete
232
+
233
+ // Open calendar to see the month
234
+ const calendarButton = datepicker.querySelector('button[type="button"]')
235
+ fireEvent.click(calendarButton!)
236
+ await datepicker.updateComplete
237
+
238
+ const calendar = datepicker.querySelector('pkt-calendar') as PktCalendar
239
+ expect(calendar.currentmonth).toEqual(parseISODateString(testMonth))
240
+ })
241
+ })
242
+
243
+ describe('Calendar integration', () => {
244
+ test('passes properties to calendar correctly', async () => {
245
+ const container = await createDatepicker(
246
+ 'min="2024-06-01" max="2024-06-30" excludeweekdays="0,6"',
247
+ )
248
+
249
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
250
+ await datepicker.updateComplete
251
+
252
+ // Open calendar
253
+ const calendarButton = datepicker.querySelector('button[type="button"]')
254
+ fireEvent.click(calendarButton!)
255
+ await datepicker.updateComplete
256
+
257
+ const calendar = datepicker.querySelector('pkt-calendar') as PktCalendar
258
+ expect(calendar.earliest).toBe('2024-06-01')
259
+ expect(calendar.latest).toBe('2024-06-30')
260
+ expect(calendar.excludeweekdays).toEqual(['0', '6'])
261
+ })
262
+
263
+ test('syncs selected dates between input and calendar', async () => {
264
+ const testDate = '2024-06-15'
265
+ const container = await createDatepicker(`value="${testDate}"`)
266
+
267
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
268
+ await datepicker.updateComplete
269
+
270
+ // Open calendar
271
+ const calendarButton = datepicker.querySelector('button[type="button"]')
272
+ fireEvent.click(calendarButton!)
273
+ await datepicker.updateComplete
274
+
275
+ const calendar = datepicker.querySelector('pkt-calendar') as PktCalendar
276
+ expect(calendar.selected).toContain(testDate)
277
+
278
+ const selectedDate = datepicker.querySelector('.pkt-calendar__date--selected')
279
+ expect(selectedDate).toBeInTheDocument()
280
+ })
281
+
282
+ test('updates input when date selected in calendar', async () => {
283
+ const container = await createDatepicker()
284
+
285
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
286
+ await datepicker.updateComplete
287
+
288
+ // Open calendar
289
+ const calendarButton = datepicker.querySelector('button[type="button"]')
290
+ fireEvent.click(calendarButton!)
291
+ await datepicker.updateComplete
292
+
293
+ // Select a date
294
+ const availableDate = datepicker.querySelector(
295
+ '.pkt-calendar__date:not(.pkt-calendar__date--disabled)',
296
+ )
297
+ fireEvent.click(availableDate!)
298
+ await datepicker.updateComplete
299
+
300
+ // Input should be updated
301
+ const input = datepicker.querySelector('input') as HTMLInputElement
302
+ expect(input.value).toBeTruthy()
303
+ expect(datepicker.value).toBeTruthy()
304
+ })
305
+
306
+ test('shows current month correctly in calendar', async () => {
307
+ const currentMonth = '2024-06-01'
308
+ const container = await createDatepicker(`currentmonth="${currentMonth}"`)
309
+
310
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
311
+ await datepicker.updateComplete
312
+
313
+ // Open calendar
314
+ const calendarButton = datepicker.querySelector('button[type="button"]')
315
+ fireEvent.click(calendarButton!)
316
+ await datepicker.updateComplete
317
+
318
+ const monthTitle = datepicker.querySelector('.pkt-calendar__month-title')
319
+ expect(monthTitle?.textContent).toContain('2024')
320
+ })
321
+ })
322
+ })