@oslokommune/punkt-elements 12.18.0 → 12.18.2

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 (126) hide show
  1. package/dist/alert-BbVWu2lm.cjs +27 -0
  2. package/dist/alert-Dh6A96vo.js +159 -0
  3. package/dist/{index-D2jSRMrn.js → calendar-BUqcNvfI.js} +41 -29
  4. package/dist/calendar-QSulz7im.cjs +108 -0
  5. package/dist/card-B0GPdG5M.cjs +23 -0
  6. package/dist/card-kWt0BA2a.js +170 -0
  7. package/dist/{class-map-a5HUzP83.cjs → class-map-Boa7BqCc.cjs} +2 -2
  8. package/dist/{class-map-CBvUV2N3.js → class-map-hz16xq5a.js} +9 -10
  9. package/dist/datepicker-BhavwiBZ.js +659 -0
  10. package/dist/datepicker-vX74tb3R.cjs +154 -0
  11. package/dist/helptext--4FLdAWi.js +194 -0
  12. package/dist/helptext-iZEgxz2U.cjs +23 -0
  13. package/dist/icon-CdMQ6zBT.cjs +250 -0
  14. package/dist/{index-CFDwiDTU.js → icon-wUXeHiBk.js} +7371 -7559
  15. package/dist/if-defined-DEDlGbAc.cjs +5 -0
  16. package/dist/if-defined-ZFE4ti2t.js +10 -0
  17. package/dist/index.d.ts +49 -51
  18. package/dist/input-element-BQTCZtNQ.js +185 -0
  19. package/dist/input-element-DNklGY_O.cjs +1 -0
  20. package/dist/{input-wrapper-DVXNuxVu.js → input-wrapper-BTQk3W8T.js} +10 -11
  21. package/dist/input-wrapper-D-PNRJB_.cjs +46 -0
  22. package/dist/link-BpqavGSD.cjs +8 -0
  23. package/dist/link-Bx9nVgZi.js +108 -0
  24. package/dist/linkcard-CUrbzjLK.js +53 -0
  25. package/dist/linkcard-DSu3A4Yx.cjs +13 -0
  26. package/dist/messagebox-C1aWoQbu.cjs +12 -0
  27. package/dist/messagebox-LpiVQIoM.js +107 -0
  28. package/dist/modal-Avai5eVz.cjs +30 -0
  29. package/dist/{modal-lQfiTbGh.js → modal-Co1YFmHi.js} +7 -8
  30. package/dist/pkt-alert.cjs +1 -27
  31. package/dist/pkt-alert.js +4 -158
  32. package/dist/pkt-calendar.cjs +1 -1
  33. package/dist/pkt-calendar.js +4 -7
  34. package/dist/pkt-card.cjs +1 -23
  35. package/dist/pkt-card.js +4 -169
  36. package/dist/pkt-datepicker.cjs +1 -154
  37. package/dist/pkt-datepicker.js +4 -657
  38. package/dist/pkt-helptext.cjs +1 -1
  39. package/dist/pkt-helptext.js +1 -1
  40. package/dist/pkt-icon.cjs +1 -1
  41. package/dist/pkt-icon.js +4 -4
  42. package/dist/pkt-index.cjs +29 -1
  43. package/dist/pkt-index.js +127 -32
  44. package/dist/pkt-input-wrapper.cjs +1 -1
  45. package/dist/pkt-input-wrapper.js +1 -1
  46. package/dist/pkt-link.cjs +1 -8
  47. package/dist/pkt-link.js +4 -108
  48. package/dist/pkt-linkcard.cjs +1 -1
  49. package/dist/pkt-linkcard.js +1 -1
  50. package/dist/pkt-messagebox.cjs +1 -12
  51. package/dist/pkt-messagebox.js +4 -106
  52. package/dist/pkt-modal.cjs +1 -1
  53. package/dist/pkt-modal.js +1 -1
  54. package/dist/pkt-progressbar.cjs +1 -1
  55. package/dist/pkt-progressbar.js +2 -2
  56. package/dist/pkt-tag.cjs +1 -17
  57. package/dist/pkt-tag.js +4 -149
  58. package/dist/pkt-textarea.cjs +1 -1
  59. package/dist/pkt-textarea.js +1 -1
  60. package/dist/pkt-textinput.cjs +1 -1
  61. package/dist/pkt-textinput.js +1 -1
  62. package/dist/{progressbar-1PEs_SMc.js → progressbar-BS_oawSB.js} +42 -47
  63. package/dist/{progressbar-BlzTFI9U.cjs → progressbar-CuXkbAhJ.cjs} +7 -11
  64. package/dist/ref-DCOsLZQg.cjs +13 -0
  65. package/dist/{ref-Dzme4zb6.js → ref-DuFGTLVX.js} +26 -27
  66. package/dist/state-BfyXV7EL.js +12 -0
  67. package/dist/state-SKYD8kRO.cjs +5 -0
  68. package/dist/stringutils-CkVRq4jP.cjs +1 -0
  69. package/dist/stringutils-DJjRa8dG.js +7 -0
  70. package/dist/tag-CGy2mSLE.cjs +17 -0
  71. package/dist/tag-DGFgUF3l.js +150 -0
  72. package/dist/textarea-BFWHQHLs.cjs +48 -0
  73. package/dist/{textarea-BXzQA_ub.js → textarea-BYtGXst8.js} +29 -29
  74. package/dist/{textinput-52G3CfwA.cjs → textinput-33wZwZ4O.cjs} +2 -2
  75. package/dist/{textinput-CTNWiIaP.js → textinput-IgHewJDJ.js} +23 -23
  76. package/package.json +2 -2
  77. package/src/components/alert/alert.ts +115 -0
  78. package/src/components/alert/index.ts +4 -113
  79. package/src/components/calendar/calendar.ts +711 -0
  80. package/src/components/calendar/index.ts +3 -711
  81. package/src/components/card/card.ts +78 -0
  82. package/src/components/card/index.ts +4 -77
  83. package/src/components/datepicker/datepicker.ts +619 -0
  84. package/src/components/datepicker/index.ts +3 -618
  85. package/src/components/helptext/helptext.ts +2 -2
  86. package/src/components/icon/icon.ts +99 -0
  87. package/src/components/icon/index.ts +3 -98
  88. package/src/components/index.ts +21 -17
  89. package/src/components/input-wrapper/input-wrapper.ts +2 -2
  90. package/src/components/link/index.ts +3 -56
  91. package/src/components/link/link.ts +57 -0
  92. package/src/components/linkcard/index.ts +1 -1
  93. package/src/components/linkcard/linkcard.ts +5 -6
  94. package/src/components/messagebox/index.ts +4 -69
  95. package/src/components/messagebox/messagebox.ts +69 -0
  96. package/src/components/modal/index.ts +0 -1
  97. package/src/components/modal/modal.ts +5 -7
  98. package/src/components/progressbar/progressbar.ts +2 -2
  99. package/src/components/tag/index.ts +4 -109
  100. package/src/components/tag/tag.ts +118 -0
  101. package/src/components/textarea/textarea.ts +5 -4
  102. package/src/components/textinput/textinput.ts +3 -3
  103. package/dist/component-template.d.ts +0 -8
  104. package/dist/converters-DNCwIFwr.js +0 -17
  105. package/dist/converters-DhM11VlY.cjs +0 -1
  106. package/dist/custom-element-B-TlBwRu.cjs +0 -9
  107. package/dist/custom-element-CWfU4dcr.js +0 -38
  108. package/dist/element.d.ts +0 -8
  109. package/dist/helptext-DBolvFI4.js +0 -72
  110. package/dist/helptext-_fMLOOCL.cjs +0 -23
  111. package/dist/index-CR7t1zY9.cjs +0 -238
  112. package/dist/index-CmTjXoAb.cjs +0 -9
  113. package/dist/index-RwtTBIhT.js +0 -88
  114. package/dist/index-tvpcg-ad.cjs +0 -108
  115. package/dist/input-wrapper-tJE-X4Ac.cjs +0 -46
  116. package/dist/linkcard-BHokvuVN.js +0 -55
  117. package/dist/linkcard-CUXMP6BH.cjs +0 -13
  118. package/dist/modal-CjsQgmmH.cjs +0 -30
  119. package/dist/pkt-component-template.cjs +0 -29
  120. package/dist/pkt-component-template.js +0 -100
  121. package/dist/pkt-element.cjs +0 -1
  122. package/dist/pkt-element.js +0 -5
  123. package/dist/ref-CA2-0S_W.cjs +0 -13
  124. package/dist/textarea-D_ud1Mpa.cjs +0 -48
  125. package/src/components/component-template/index.ts +0 -129
  126. package/src/components/element/index.ts +0 -353
@@ -0,0 +1,711 @@
1
+ import { classMap } from 'lit/directives/class-map.js'
2
+ import { customElement, property, state } from 'lit/decorators.js'
3
+ import { formatISODate, newDate, newDateYMD, formatReadableDate } from '@/utils/dateutils'
4
+ import { getWeek, eachDayOfInterval, getISODay, addDays, addHours } from 'date-fns'
5
+ import { html, nothing, PropertyValues } from 'lit'
6
+ import { PktElement } from '@/base-elements/element'
7
+ import { TZDate } from '@date-fns/tz'
8
+ import converters from '../../helpers/converters'
9
+ import specs from 'componentSpecs/calendar.json'
10
+ import '../icon/icon'
11
+
12
+ type DatesInRange = {
13
+ [key: string]: boolean
14
+ }
15
+
16
+ @customElement('pkt-calendar')
17
+ export class PktCalendar extends PktElement {
18
+ /**
19
+ * Element attributes
20
+ */
21
+ @property({ type: Boolean })
22
+ multiple: boolean = specs.props.multiple.default
23
+
24
+ @property({ type: Number })
25
+ maxMultiple: number = specs.props.maxMultiple.default
26
+
27
+ @property({ type: Boolean })
28
+ range: boolean = specs.props.range.default
29
+
30
+ @property({ type: Boolean })
31
+ weeknumbers: boolean = specs.props.weeknumbers.default
32
+
33
+ @property({ type: Boolean })
34
+ withcontrols: boolean = specs.props.withcontrols.default
35
+
36
+ @property({ converter: converters.csvToArray })
37
+ selected: string | string[] = []
38
+
39
+ @property({ type: String })
40
+ earliest: string | null = specs.props.earliest.default
41
+
42
+ @property({ type: String })
43
+ latest: string | null = specs.props.latest.default
44
+
45
+ @property({ converter: converters.stringsToDate })
46
+ excludedates: Date[] = []
47
+
48
+ @property({ converter: converters.csvToArray })
49
+ excludeweekdays: string[] = []
50
+
51
+ @property({ converter: converters.stringToDate })
52
+ currentmonth: Date | null = null
53
+
54
+ /**
55
+ * Strings
56
+ */
57
+ @property({ type: Array }) dayStrings: string[] = this.strings.dates.daysShort
58
+ @property({ type: Array }) dayStringsLong: string[] = this.strings.dates.days
59
+ @property({ type: Array }) monthStrings: string[] = this.strings.dates.months
60
+ @property({ type: String }) weekString: string = this.strings.dates.week
61
+ @property({ type: String }) prevMonthString: string = this.strings.dates.prevMonth
62
+ @property({ type: String }) nextMonthString: string = this.strings.dates.nextMonth
63
+
64
+ /**
65
+ * Private properties
66
+ */
67
+ @property({ type: Array }) private _selected: Date[] = []
68
+ @property({ type: Number }) private year: number = 0
69
+ @property({ type: Number }) private month: number = 0
70
+ @property({ type: Number }) private week: number = 0
71
+ @property({ type: Date }) private rangeHovered: Date | null = null
72
+
73
+ @state() private inRange: DatesInRange = {}
74
+ @state() private focusedDate: string | null = null
75
+ @state() private selectableDates: {
76
+ currentDateISO: string
77
+ isDisabled: boolean
78
+ tabindex: string
79
+ }[] = []
80
+ @state() private currentmonthtouched: boolean = false
81
+ @state() private tabIndexSet: number = 0
82
+ /**
83
+ * Runs on mount, used to set up various values and whatever you need to run
84
+ */
85
+ connectedCallback() {
86
+ super.connectedCallback()
87
+ }
88
+
89
+ disconnectedCallback(): void {
90
+ this.removeEventListener('keydown', this.handleKeydown)
91
+ super.disconnectedCallback()
92
+ }
93
+
94
+ attributeChangedCallback(name: string, _old: string | null, value: string | null): void {
95
+ if (name === 'selected' && value) {
96
+ this.convertSelected()
97
+ }
98
+ super.attributeChangedCallback(name, _old, value)
99
+ }
100
+
101
+ updated(changedProperties: PropertyValues): void {
102
+ super.updated(changedProperties)
103
+ if (changedProperties.has('selected')) {
104
+ this.convertSelected()
105
+ }
106
+ }
107
+
108
+ protected firstUpdated(_changedProperties: PropertyValues): void {
109
+ this.addEventListener('keydown', this.handleKeydown)
110
+ }
111
+
112
+ convertSelected() {
113
+ if (typeof this.selected === 'string') {
114
+ this.selected = this.selected.split(',')
115
+ }
116
+ if (this.selected.length === 1 && this.selected[0] === '') {
117
+ this.selected = []
118
+ }
119
+ this._selected = this.selected.map((d: string) => newDate(d))
120
+ if (this.range && this.selected.length === 2) {
121
+ const days = eachDayOfInterval({
122
+ start: newDate(this.selected[0]),
123
+ end: newDate(this.selected[1]),
124
+ })
125
+
126
+ this.inRange = {}
127
+ if (Array.isArray(days) && days.length) {
128
+ const inRange: DatesInRange = {}
129
+ for (let i = 0; i < days.length; i++) {
130
+ inRange[formatISODate(days[i])] = this.isInRange(days[i])
131
+ }
132
+ this.inRange = inRange
133
+ }
134
+ }
135
+ this.setCurrentMonth()
136
+ }
137
+
138
+ setCurrentMonth() {
139
+ if (this.currentmonth === null && !this.currentmonthtouched) {
140
+ this.currentmonthtouched = true
141
+ return
142
+ }
143
+ if (this.selected.length && this.selected[0] !== '') {
144
+ this.currentmonth = newDate(this.selected[this.selected.length - 1])
145
+ } else if (this.currentmonth === null) {
146
+ this.currentmonth = newDate()
147
+ }
148
+ this.year = this.currentmonth.getFullYear()
149
+ this.month = this.currentmonth.getMonth()
150
+ }
151
+
152
+ handleKeydown(e: KeyboardEvent) {
153
+ switch (e.key) {
154
+ case 'ArrowLeft':
155
+ this.handleArrowKey(e, -1)
156
+ break
157
+ case 'ArrowRight':
158
+ this.handleArrowKey(e, 1)
159
+ break
160
+ case 'ArrowUp':
161
+ this.handleArrowKey(e, -7)
162
+ break
163
+ case 'ArrowDown':
164
+ this.handleArrowKey(e, 7)
165
+ break
166
+ default:
167
+ break
168
+ }
169
+ }
170
+
171
+ handleArrowKey(e: KeyboardEvent, direction: number) {
172
+ if ((e.target as HTMLElement)?.nodeName === 'INPUT') return
173
+ if ((e.target as HTMLElement)?.nodeName === 'SELECT') return
174
+ if ((e.target as HTMLElement)?.nodeName === 'BUTTON') return
175
+ e.preventDefault()
176
+ if (!this.focusedDate) {
177
+ this.focusOnCurrentDate()
178
+ }
179
+ const date = this.focusedDate ? newDate(this.focusedDate) : newDateYMD(this.year, this.month, 1)
180
+ let nextDate = addDays(date, direction)
181
+ if (nextDate) {
182
+ let el = this.querySelector(`div[data-date="${formatISODate(nextDate)}"]`)
183
+ if (el instanceof HTMLDivElement) {
184
+ if (el.dataset.disabled) {
185
+ nextDate = addDays(nextDate, direction)
186
+ let nextElement = this.querySelector(`div[data-date="${formatISODate(nextDate)}"]`)
187
+ while (
188
+ nextElement &&
189
+ nextElement instanceof HTMLDivElement &&
190
+ nextElement.dataset.disabled
191
+ ) {
192
+ nextDate = addDays(nextDate, direction)
193
+ nextElement = this.querySelector(`div[data-date="${formatISODate(nextDate)}"]`)
194
+ }
195
+ el = nextElement
196
+ }
197
+ if (el instanceof HTMLDivElement && !el.dataset.disabled) {
198
+ this.focusedDate = formatISODate(nextDate)
199
+ el.focus()
200
+ }
201
+ }
202
+ }
203
+ }
204
+ /**
205
+ * Component functionality and render
206
+ */
207
+ render() {
208
+ return html`
209
+ <div
210
+ class="pkt-calendar ${this.weeknumbers ? 'pkt-cal-weeknumbers' : nothing}"
211
+ @focusout=${this.closeEvent}
212
+ @keydown=${(e: KeyboardEvent) => {
213
+ if (e.key === 'Escape') {
214
+ e.preventDefault()
215
+ this.close()
216
+ }
217
+ }}
218
+ >
219
+ <nav class="pkt-cal-month-nav">
220
+ <div>
221
+ <button
222
+ type="button"
223
+ @click=${this.isPrevMonthAllowed() && this.prevMonth}
224
+ @keydown=${(e: KeyboardEvent) => {
225
+ if (e.key === 'Enter' || e.key === ' ') {
226
+ e.preventDefault()
227
+ this.isNextMonthAllowed() && this.prevMonth()
228
+ }
229
+ }}
230
+ class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--icon-only ${this.isPrevMonthAllowed()
231
+ ? ''
232
+ : 'pkt-hide'}"
233
+ .data-disabled=${!this.isPrevMonthAllowed() ? 'disabled' : nothing}
234
+ ?aria-disabled=${!this.isPrevMonthAllowed()}
235
+ tabindex=${this.isPrevMonthAllowed() ? '0' : '-1'}
236
+ >
237
+ <pkt-icon class="pkt-btn__icon" name="chevron-thin-left"></pkt-icon>
238
+ <span class="pkt-btn__text">${this.prevMonthString}</span>
239
+ </button>
240
+ </div>
241
+ ${this.renderMonthNav()}
242
+ <div>
243
+ <button
244
+ type="button"
245
+ @click=${this.isNextMonthAllowed() && this.nextMonth}
246
+ @keydown=${(e: KeyboardEvent) => {
247
+ if (e.key === 'Enter' || e.key === ' ') {
248
+ e.preventDefault()
249
+ this.isNextMonthAllowed() && this.nextMonth()
250
+ }
251
+ }}
252
+ class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--icon-only ${this.isNextMonthAllowed()
253
+ ? ''
254
+ : 'pkt-hide'}"
255
+ .data-disabled=${!this.isNextMonthAllowed() ? 'disabled' : nothing}
256
+ ?aria-disabled=${!this.isNextMonthAllowed()}
257
+ tabindex=${this.isNextMonthAllowed() ? '0' : '-1'}
258
+ >
259
+ <pkt-icon class="pkt-btn__icon" name="chevron-thin-right"></pkt-icon>
260
+ <span class="pkt-btn__text">${this.nextMonthString}</span>
261
+ </button>
262
+ </div>
263
+ </nav>
264
+ <table
265
+ class="pkt-cal-days pkt-txt-12-medium"
266
+ role="grid"
267
+ ?aria-multiselectable=${this.range || this.multiple}
268
+ >
269
+ <thead>
270
+ ${this.renderDayNames()}
271
+ </thead>
272
+ <tbody>
273
+ ${this.renderCalendarBody()}
274
+ </tbody>
275
+ </table>
276
+ </div>
277
+ `
278
+ }
279
+
280
+ private renderDayNames() {
281
+ const days = []
282
+ if (this.weeknumbers) {
283
+ days.push(html`<th><div>${this.weekString}</div></th>`)
284
+ }
285
+ for (let i = 0; i < this.dayStrings.length; i++) {
286
+ days.push(
287
+ html`<th><div aria-label="${this.dayStringsLong[i]}">${this.dayStrings[i]}</div></th>`,
288
+ )
289
+ }
290
+ return html`<tr class="pkt-cal-week-row">
291
+ ${days}
292
+ </tr>`
293
+ }
294
+
295
+ private renderMonthNav() {
296
+ let monthView = []
297
+ if (this.withcontrols) {
298
+ monthView.push(
299
+ html`<div class="pkt-cal-month-picker">
300
+ <label for="${this.id}-monthnav" class="pkt-hide">${this.strings.dates.month}</label>
301
+ <select
302
+ aria-label="${this.strings.dates.month}"
303
+ class="pkt-input pkt-input-compact"
304
+ id="${this.id}-monthnav"
305
+ @change=${(e: any) => {
306
+ e.stopImmediatePropagation()
307
+ this.changeMonth(this.year, e.target.value)
308
+ }}
309
+ >
310
+ ${this.monthStrings.map(
311
+ (month, index) =>
312
+ html`<option value=${index} ?selected=${this.month === index}>${month}</option>`,
313
+ )}
314
+ </select>
315
+ <label for="${this.id}-yearnav" class="pkt-hide">${this.strings.dates.year}</label>
316
+ <input
317
+ aria-label="${this.strings.dates.year}"
318
+ class="pkt-input pkt-cal-input-year pkt-input-compact"
319
+ id="${this.id}-yearnav"
320
+ type="number"
321
+ size="4"
322
+ placeholder="0000"
323
+ @change=${(e: any) => {
324
+ e.stopImmediatePropagation()
325
+ this.changeMonth(e.target.value, this.month)
326
+ }}
327
+ .value=${this.year}
328
+ />
329
+ </div> `,
330
+ )
331
+ } else {
332
+ monthView.push(
333
+ html`<div class="pkt-txt-16-medium" aria-live="polite">
334
+ ${this.monthStrings[this.month]} ${this.year}
335
+ </div>`,
336
+ )
337
+ }
338
+ return monthView
339
+ }
340
+
341
+ private renderDayView(dayCounter: number, today: Date, j: number) {
342
+ const currentDate = newDateYMD(this.year, this.month, dayCounter)
343
+ const currentDateISO = formatISODate(currentDate)
344
+ const isToday = currentDateISO === formatISODate(today)
345
+ const isSelected = this.selected.includes(currentDateISO)
346
+ const ariaLabel = formatReadableDate(currentDate)
347
+ const isDisabled =
348
+ this.isExcluded(j, currentDate) ||
349
+ (!isSelected &&
350
+ this.multiple &&
351
+ this.maxMultiple > 0 &&
352
+ this.selected.length >= this.maxMultiple)
353
+ const tabindex = this.focusedDate
354
+ ? this.focusedDate === currentDateISO && !isDisabled
355
+ ? '0'
356
+ : '-1'
357
+ : !isDisabled && this.tabIndexSet === 0
358
+ ? '0'
359
+ : this.tabIndexSet === dayCounter
360
+ ? '0'
361
+ : '-1'
362
+
363
+ if (tabindex === '0') {
364
+ this.tabIndexSet = dayCounter
365
+ }
366
+
367
+ this.selectableDates.push({ currentDateISO, isDisabled, tabindex })
368
+
369
+ const classes = {
370
+ 'pkt-cal-today': isToday,
371
+ 'pkt-cal-selected': isSelected,
372
+ 'pkt-cal-in-range': this.inRange[currentDateISO],
373
+ 'pkt-cal-excluded': this.isExcluded(j, currentDate),
374
+ 'pkt-cal-in-range-first':
375
+ this.range &&
376
+ (this.selected.length === 2 || this.rangeHovered !== null) &&
377
+ currentDateISO === this.selected[0],
378
+ 'pkt-cal-in-range-last':
379
+ this.range && this.selected.length === 2 && currentDateISO === this.selected[1],
380
+ 'pkt-cal-range-hover':
381
+ this.rangeHovered !== null && currentDateISO === formatISODate(this.rangeHovered),
382
+ }
383
+ return html`<td class=${classMap(classes)}>
384
+ <div
385
+ ?aria-selected=${isSelected}
386
+ role="gridcell"
387
+ class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--label-only"
388
+ @mouseover=${() =>
389
+ this.range && !this.isExcluded(j, currentDate) && this.handleRangeHover(currentDate)}
390
+ @focus=${() => {
391
+ this.range && !this.isExcluded(j, currentDate) && this.handleRangeHover(currentDate)
392
+ this.focusedDate = currentDateISO
393
+ }}
394
+ aria-label="${ariaLabel}"
395
+ tabindex=${this.selectableDates.find((x) => x.currentDateISO === currentDateISO)?.tabindex}
396
+ data-disabled=${isDisabled ? 'disabled' : nothing}
397
+ data-date=${currentDateISO}
398
+ @keydown=${(e: KeyboardEvent) => {
399
+ if (e.key === 'Enter' || e.key === ' ') {
400
+ e.preventDefault()
401
+ this.handleDateSelect(currentDate)
402
+ }
403
+ }}
404
+ @click=${(e: MouseEvent) => {
405
+ if (!isDisabled) {
406
+ e.preventDefault()
407
+ this.handleDateSelect(currentDate)
408
+ }
409
+ }}
410
+ >
411
+ <span class="pkt-btn__text pkt-txt-14-light">${dayCounter}</span>
412
+ </div>
413
+ </td>`
414
+ }
415
+
416
+ private renderCalendarBody() {
417
+ const today = newDate()
418
+ const firstDayOfMonth = newDateYMD(this.year, this.month, 1)
419
+ const lastDayOfMonth = newDateYMD(this.year, this.month + 1, 0)
420
+ const startingDay = (firstDayOfMonth.getDay() + 6) % 7
421
+ const numDays = lastDayOfMonth.getDate()
422
+ const numRows = Math.ceil((numDays + startingDay) / 7)
423
+ const lastDayOfPrevMonth = newDateYMD(this.year, this.month, 0)
424
+ const numDaysPrevMonth = lastDayOfPrevMonth.getDate()
425
+
426
+ let dayCounter = 1
427
+ this.week = getWeek(newDateYMD(this.year, this.month, 1))
428
+
429
+ const rows = []
430
+
431
+ for (let i = 0; i < numRows; i++) {
432
+ const cells = []
433
+
434
+ this.weeknumbers && cells.push(html`<td class="pkt-cal-week">${this.week}</td>`)
435
+ this.week++
436
+
437
+ for (let j = 1; j < 8; j++) {
438
+ if (i === 0 && j < startingDay + 1) {
439
+ const dayFromPrevMonth = numDaysPrevMonth - (startingDay - j)
440
+ cells.push(
441
+ html`<td class="pkt-cal-other">
442
+ <div
443
+ class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--label-only"
444
+ data-disabled="disabled"
445
+ >
446
+ <span class="pkt-btn__text pkt-txt-14-light">${dayFromPrevMonth}</span>
447
+ </div>
448
+ </td>`,
449
+ )
450
+ } else if (dayCounter > numDays) {
451
+ cells.push(
452
+ html`<td class="pkt-cal-other">
453
+ <div
454
+ class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--label-only"
455
+ data-disabled="disabled"
456
+ >
457
+ <span class="pkt-btn__text pkt-txt-14-light">${dayCounter - numDays}</span>
458
+ </div>
459
+ </td>`,
460
+ )
461
+ dayCounter++
462
+ } else {
463
+ cells.push(this.renderDayView(dayCounter, today, j))
464
+ dayCounter++
465
+ }
466
+ }
467
+
468
+ rows.push(
469
+ html`<tr class="pkt-cal-week-row" role="row">
470
+ ${cells}
471
+ </tr>`,
472
+ )
473
+ }
474
+
475
+ return rows
476
+ }
477
+
478
+ private isExcluded(weekday: number, date: TZDate) {
479
+ if (this.excludeweekdays.includes(weekday.toString())) return true
480
+ if (this.earliest && addHours(date, 1) < newDate(this.earliest)) return true
481
+ if (this.latest && addHours(date, -1) > newDate(this.latest)) return true
482
+ return this.excludedates.some((x: TZDate | Date | string) => {
483
+ if (typeof x === 'string') {
484
+ return x === formatISODate(date)
485
+ } else {
486
+ return x.toDateString() === date.toDateString()
487
+ }
488
+ })
489
+ }
490
+
491
+ isPrevMonthAllowed() {
492
+ const prevMonth = newDateYMD(this.year, this.month, 0)
493
+ if (this.earliest && newDate(this.earliest) > prevMonth) return false
494
+ return true
495
+ }
496
+
497
+ private prevMonth() {
498
+ const newMonth = this.month === 0 ? 11 : this.month - 1
499
+ const newYear = this.month === 0 ? this.year - 1 : this.year
500
+ this.changeMonth(newYear, newMonth)
501
+ }
502
+
503
+ isNextMonthAllowed() {
504
+ const nextMonth = newDateYMD(
505
+ this.month === 11 ? this.year + 1 : this.year,
506
+ this.month === 11 ? 0 : this.month + 1,
507
+ 1,
508
+ )
509
+ if (this.latest && newDate(this.latest) < nextMonth) return false
510
+ return true
511
+ }
512
+
513
+ private nextMonth() {
514
+ const newMonth = this.month === 11 ? 0 : this.month + 1
515
+ const newYear = this.month === 11 ? this.year + 1 : this.year
516
+ this.changeMonth(newYear, newMonth)
517
+ }
518
+
519
+ private changeMonth(year: number, month: number) {
520
+ this.year = typeof year === 'string' ? parseInt(year) : year
521
+ this.month = typeof month === 'string' ? parseInt(month) : month
522
+ this.tabIndexSet = 0
523
+ this.focusedDate = null
524
+ this.selectableDates = []
525
+ }
526
+
527
+ private isInRange(date: TZDate | Date) {
528
+ if (this.range && this.selected.length === 2) {
529
+ if (date > newDate(this.selected[0]) && date < newDate(this.selected[1])) return true
530
+ } else if (this.range && this.selected.length === 1 && this.rangeHovered) {
531
+ if (date > newDate(this.selected[0]) && date < this.rangeHovered) return true
532
+ }
533
+ return false
534
+ }
535
+
536
+ private isRangeAllowed(date: TZDate) {
537
+ let allowed = true
538
+ if (this._selected.length === 1) {
539
+ const days = eachDayOfInterval({
540
+ start: this._selected[0],
541
+ end: date,
542
+ })
543
+
544
+ if (Array.isArray(days) && days.length) {
545
+ for (let i = 0; i < days.length; i++) {
546
+ this.excludedates.forEach((d: TZDate | Date) => {
547
+ if (d > this._selected[0] && d < date) {
548
+ allowed = false
549
+ }
550
+ })
551
+ if (this.excludeweekdays.includes(getISODay(days[i]).toString())) {
552
+ allowed = false
553
+ }
554
+ }
555
+ }
556
+ }
557
+ return allowed
558
+ }
559
+
560
+ private emptySelected() {
561
+ this.selected = []
562
+ this._selected = []
563
+ this.inRange = {}
564
+ }
565
+
566
+ public addToSelected(selectedDate: TZDate) {
567
+ if (this.selected.includes(formatISODate(selectedDate))) return
568
+ this.selected = [...this.selected, formatISODate(selectedDate)]
569
+ this._selected = [...this._selected, selectedDate]
570
+ if (this.range && this.selected.length === 2) {
571
+ this.close()
572
+ }
573
+ }
574
+
575
+ public removeFromSelected(selectedDate: TZDate) {
576
+ if (this.selected.length === 1) {
577
+ this.emptySelected()
578
+ } else {
579
+ const selectedDateIndex = this.selected.indexOf(formatISODate(selectedDate))
580
+ const selectedCopy = [...this.selected]
581
+ const _selectedCopy = [...this._selected]
582
+ selectedCopy.splice(selectedDateIndex, 1)
583
+ _selectedCopy.splice(selectedDateIndex, 1)
584
+ this.selected = selectedCopy
585
+ this._selected = _selectedCopy
586
+ }
587
+ }
588
+
589
+ public toggleSelected(selectedDate: TZDate) {
590
+ const selectedDateISO = formatISODate(selectedDate)
591
+ this.selected.includes(selectedDateISO)
592
+ ? this.removeFromSelected(selectedDate)
593
+ : !(this.maxMultiple && this.selected.length >= this.maxMultiple)
594
+ ? this.addToSelected(selectedDate)
595
+ : null
596
+ }
597
+
598
+ private handleRangeSelect(selectedDate: TZDate) {
599
+ const selectedDateISO = formatISODate(selectedDate)
600
+ if (this.selected.includes(selectedDateISO)) {
601
+ if (this.selected.indexOf(selectedDateISO) === 0) {
602
+ this.emptySelected()
603
+ } else {
604
+ this.removeFromSelected(selectedDate)
605
+ }
606
+ } else {
607
+ if (this.selected.length > 1) {
608
+ this.emptySelected()
609
+ this.addToSelected(selectedDate)
610
+ } else {
611
+ if (this.selected.length === 1 && !this.isRangeAllowed(selectedDate)) {
612
+ this.emptySelected()
613
+ }
614
+ if (this.selected.length === 1 && this._selected[0] > selectedDate) {
615
+ this.emptySelected()
616
+ }
617
+ this.addToSelected(selectedDate)
618
+ }
619
+ }
620
+ return Promise.resolve()
621
+ }
622
+
623
+ private handleRangeHover(date: TZDate) {
624
+ if (
625
+ this.range &&
626
+ this._selected.length === 1 &&
627
+ this.isRangeAllowed(date) &&
628
+ this._selected[0] < date
629
+ ) {
630
+ this.rangeHovered = date
631
+
632
+ this.inRange = {}
633
+ const days = eachDayOfInterval({
634
+ start: this._selected[0],
635
+ end: date,
636
+ })
637
+
638
+ if (Array.isArray(days) && days.length) {
639
+ for (let i = 0; i < days.length; i++) {
640
+ this.inRange[formatISODate(days[i])] = this.isInRange(days[i])
641
+ }
642
+ }
643
+ } else {
644
+ this.rangeHovered = null
645
+ }
646
+ }
647
+
648
+ public handleDateSelect(selectedDate: TZDate | null) {
649
+ if (!selectedDate) return
650
+ if (this.range) {
651
+ this.handleRangeSelect(selectedDate)
652
+ } else if (this.multiple) {
653
+ this.toggleSelected(selectedDate)
654
+ } else {
655
+ if (this.selected.includes(formatISODate(selectedDate))) {
656
+ this.emptySelected()
657
+ } else {
658
+ this.emptySelected()
659
+ this.addToSelected(selectedDate)
660
+ }
661
+ this.close()
662
+ }
663
+ this.dispatchEvent(
664
+ new CustomEvent('date-selected', {
665
+ detail: this.selected,
666
+ bubbles: true,
667
+ composed: true,
668
+ }),
669
+ )
670
+ return Promise.resolve()
671
+ }
672
+
673
+ public focusOnCurrentDate() {
674
+ const currentDateISO = formatISODate(newDate())
675
+ const el = this.querySelector(`div[data-date="${currentDateISO}"]`)
676
+ if (el instanceof HTMLDivElement) {
677
+ this.focusedDate = currentDateISO
678
+ el.focus()
679
+ } else {
680
+ const firstSelectable = this.selectableDates.find((x) => !x.isDisabled)
681
+ if (firstSelectable) {
682
+ const firstSelectableEl = this.querySelector(
683
+ `div[data-date="${firstSelectable.currentDateISO}"]`,
684
+ )
685
+ if (firstSelectableEl instanceof HTMLDivElement) {
686
+ this.focusedDate = firstSelectable.currentDateISO
687
+ firstSelectableEl.focus()
688
+ }
689
+ }
690
+ }
691
+ }
692
+
693
+ public closeEvent(e: FocusEvent) {
694
+ if (
695
+ !this.contains(e.relatedTarget as Node) &&
696
+ !(e.target as Element).classList.contains('pkt-hide')
697
+ ) {
698
+ this.close()
699
+ }
700
+ }
701
+
702
+ public close() {
703
+ this.dispatchEvent(
704
+ new CustomEvent('close', {
705
+ detail: true,
706
+ bubbles: true,
707
+ composed: true,
708
+ }),
709
+ )
710
+ }
711
+ }