@oslokommune/punkt-elements 12.1.0 → 12.3.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 (91) hide show
  1. package/README.md +30 -0
  2. package/dist/alert.d.ts +1 -0
  3. package/dist/calendar.d.ts +1 -0
  4. package/dist/card.d.ts +1 -0
  5. package/dist/class-map-CA8wadiN.cjs +5 -0
  6. package/dist/class-map-DiT0qP3E.js +34 -0
  7. package/dist/component-template.d.ts +1 -0
  8. package/dist/converters-DNCwIFwr.js +17 -0
  9. package/dist/converters-DhM11VlY.cjs +1 -0
  10. package/dist/datepicker.d.ts +1 -0
  11. package/dist/directive-19_ixLKS.cjs +9 -0
  12. package/dist/directive-DA0-wdk7.js +38 -0
  13. package/dist/element.d.ts +1 -0
  14. package/dist/icon.d.ts +1 -0
  15. package/dist/index-BHzxfdBK.js +807 -0
  16. package/dist/index-BJ_4AGO3.cjs +23 -0
  17. package/dist/index-BlHnvy7v.js +82 -0
  18. package/dist/index-CPvZ03uX.js +577 -0
  19. package/dist/index-DSplpVWQ.cjs +9 -0
  20. package/dist/index-DkGcZra2.cjs +90 -0
  21. package/dist/index.d.ts +393 -3
  22. package/dist/input-wrapper.d.ts +1 -0
  23. package/dist/link.d.ts +1 -0
  24. package/dist/messagebox.d.ts +1 -0
  25. package/dist/pkt-alert.cjs +27 -0
  26. package/dist/pkt-alert.js +86 -0
  27. package/dist/pkt-calendar.cjs +1 -0
  28. package/dist/pkt-calendar.js +10 -0
  29. package/dist/pkt-card.cjs +23 -0
  30. package/dist/pkt-card.js +177 -0
  31. package/dist/pkt-component-template.cjs +29 -0
  32. package/dist/{pkt-el-component-template.js → pkt-component-template.js} +25 -26
  33. package/dist/pkt-datepicker.cjs +140 -0
  34. package/dist/pkt-datepicker.js +1769 -0
  35. package/dist/pkt-element.cjs +1 -0
  36. package/dist/pkt-element.js +5 -0
  37. package/dist/pkt-icon.cjs +1 -0
  38. package/dist/pkt-icon.js +6 -0
  39. package/dist/pkt-index.cjs +1 -0
  40. package/dist/pkt-index.js +20 -0
  41. package/dist/pkt-input-wrapper.cjs +59 -0
  42. package/dist/pkt-input-wrapper.js +293 -0
  43. package/dist/pkt-link.cjs +3 -0
  44. package/dist/pkt-link.js +90 -0
  45. package/dist/pkt-messagebox.cjs +12 -0
  46. package/dist/pkt-messagebox.js +62 -0
  47. package/dist/pkt-tag.cjs +17 -0
  48. package/dist/pkt-tag.js +151 -0
  49. package/dist/ref-Bk590hog.cjs +13 -0
  50. package/dist/ref-Co_S0Cgj.js +173 -0
  51. package/dist/state-BRgFbJX9.js +12 -0
  52. package/dist/state-D2tUtTi6.cjs +5 -0
  53. package/dist/tag.d.ts +1 -0
  54. package/package.json +10 -7
  55. package/src/components/alert/index.ts +86 -0
  56. package/src/components/calendar/index.ts +391 -143
  57. package/src/components/card/index.ts +78 -0
  58. package/src/components/component-template/index.ts +25 -9
  59. package/src/components/datepicker/index.ts +546 -0
  60. package/src/components/element/index.ts +181 -8
  61. package/src/components/icon/index.ts +44 -49
  62. package/src/components/index.ts +7 -1
  63. package/src/components/input-wrapper/index.ts +260 -0
  64. package/src/components/link/index.ts +51 -0
  65. package/src/components/messagebox/index.ts +63 -0
  66. package/src/components/tag/index.ts +110 -0
  67. package/dist/components/calendar/index.d.ts +0 -59
  68. package/dist/components/component-template/index.d.ts +0 -34
  69. package/dist/components/element/index.d.ts +0 -24
  70. package/dist/components/icon/index.d.ts +0 -29
  71. package/dist/components/index.d.ts +0 -3
  72. package/dist/controllers/pkt-slot-controller.d.ts +0 -11
  73. package/dist/converters-Bi8tmNvQ.cjs +0 -5
  74. package/dist/converters-DMveycvc.js +0 -100
  75. package/dist/directive-C7mkmyiy.js +0 -573
  76. package/dist/directive-DfhMJ1ie.cjs +0 -23
  77. package/dist/helpers/converters.d.ts +0 -3
  78. package/dist/index-0PZgk9Oc.js +0 -159
  79. package/dist/index-xoVy6sfy.cjs +0 -13
  80. package/dist/pkt-el-calendar.cjs +0 -60
  81. package/dist/pkt-el-calendar.js +0 -1496
  82. package/dist/pkt-el-component-template.cjs +0 -29
  83. package/dist/pkt-el-element.cjs +0 -1
  84. package/dist/pkt-el-element.js +0 -5
  85. package/dist/pkt-el-icon.cjs +0 -9
  86. package/dist/pkt-el-icon.js +0 -98
  87. package/dist/pkt-el-index.cjs +0 -1
  88. package/dist/pkt-el-index.js +0 -8
  89. package/dist/property-BBVRv-DT.js +0 -47
  90. package/dist/property-CK6SFc5B.cjs +0 -9
  91. package/dist/src/translations/no.json.d.ts +0 -43
@@ -0,0 +1,78 @@
1
+ import { PktElement } from '../element'
2
+ import { PktSlotController } from '../../controllers/pkt-slot-controller'
3
+ import { ref, createRef, Ref } from 'lit/directives/ref.js'
4
+ import { html, nothing } from 'lit'
5
+ import { classMap } from 'lit/directives/class-map.js'
6
+ import { customElement, property } from 'lit/decorators.js'
7
+ import '../icon'
8
+ import '../tag'
9
+ import { IPktTag } from '../tag'
10
+ import specs from 'componentSpecs/card.json'
11
+
12
+ export type ICardSkin = 'outlined' | 'gray' | 'beige' | 'green' | 'blue'
13
+ export type IDirection = 'portrait' | 'landscape'
14
+
15
+ @customElement('pkt-card')
16
+ export class PktCard extends PktElement {
17
+ defaultSlot: Ref<HTMLElement> = createRef()
18
+
19
+ constructor() {
20
+ super()
21
+ this.slotController = new PktSlotController(this, this.defaultSlot)
22
+ }
23
+
24
+ @property({ type: String, reflect: true }) skin: ICardSkin = specs.props.skin.default as ICardSkin
25
+ @property({ type: String, reflect: true }) direction: IDirection = specs.props.direction
26
+ .default as IDirection
27
+ @property({ type: Object, reflect: true }) image: { src: string; alt: string } = {
28
+ src: '',
29
+ alt: '',
30
+ }
31
+ @property({ type: String, reflect: true }) heading: string = ''
32
+ @property({ type: String, reflect: true }) subheading: string = ''
33
+ @property({ type: Array, reflect: true }) tags: (Omit<IPktTag, 'closeTag'> & { text: string })[] =
34
+ []
35
+
36
+ connectedCallback() {
37
+ super.connectedCallback()
38
+ console.log(this.tags)
39
+ }
40
+
41
+ render() {
42
+ const classes = {
43
+ 'pkt-card': true,
44
+ [`pkt-card--${this.skin}`]: this.skin,
45
+ [`pkt-card--${this.direction}`]: this.direction,
46
+ }
47
+
48
+ return html`
49
+ <div class=${classMap(classes)}>
50
+ ${this.image.src &&
51
+ html`
52
+ <div class="pkt-card__image">
53
+ <img src=${this.image.src} alt=${this.image.alt || ''} />
54
+ </div>
55
+ `}
56
+ <div class="pkt-card__wrapper">
57
+ ${this.tags.length > 0
58
+ ? html`
59
+ <div class="pkt-card__tags">
60
+ ${this.tags.map(
61
+ (tag) => html`
62
+ <pkt-tag textStyle="normal-text" size="medium" skin=${tag.skin}>
63
+ ${tag.text}
64
+ </pkt-tag>
65
+ `,
66
+ )}
67
+ </div>
68
+ `
69
+ : nothing}
70
+ ${this.heading && html`<h3 class="pkt-txt-30-medium">${this.heading}</h3>`}
71
+ ${this.subheading && html`<p class="pkt-txt-20-light">${this.subheading}</p>`}
72
+ ${this.defaultSlot &&
73
+ html`<div class="pkt-card__content" ${ref(this.defaultSlot)}></div>`}
74
+ </div>
75
+ </div>
76
+ `
77
+ }
78
+ }
@@ -1,7 +1,8 @@
1
1
  import { html } from 'lit'
2
2
  import { customElement, property } from 'lit/decorators.js'
3
- import { ref } from 'lit/directives/ref.js'
4
3
  import { PktElement } from '../element'
4
+ import { PktSlotController } from '@/controllers/pkt-slot-controller'
5
+ import { ref, Ref, createRef } from 'lit/directives/ref.js'
5
6
 
6
7
  // If you need to convert an object to a list of classes, use classMap
7
8
  import { classMap } from 'lit/directives/class-map.js'
@@ -13,19 +14,24 @@ import translations from '../../translations/no.json'
13
14
  // use helpers from converters.ts
14
15
  import { csvToArray } from '../../helpers/converters'
15
16
 
16
- @customElement('pkt-el-component')
17
+ @customElement('pkt-component')
17
18
  export class PktComponent extends PktElement {
18
19
  /**
19
20
  * Element attributes => props
20
21
  * Example:
21
- * <pkt-el-component string="hei" strings="hei,og,hallo" darkmode>
22
+ * <pkt-component string="hei" strings="hei,og,hallo" darkmode>
22
23
  * Hei!
23
- * </pkt-el-component>
24
+ * </pkt-component>
24
25
  */
25
26
 
26
- @property({ type: String }) string: string = ''
27
- @property({ converter: csvToArray }) strings: string[] = []
28
- @property({ type: Boolean }) darkmode: boolean = false
27
+ @property({ type: String })
28
+ string: string = ''
29
+
30
+ @property({ converter: csvToArray })
31
+ strings: string[] = []
32
+
33
+ @property({ type: Boolean })
34
+ darkmode: boolean = false
29
35
 
30
36
  /**
31
37
  * Private properties, for internal use only
@@ -44,6 +50,18 @@ export class PktComponent extends PktElement {
44
50
  super.connectedCallback()
45
51
  }
46
52
 
53
+ /**
54
+ * Set up slot support for Light DOM
55
+ */
56
+ slotController: PktSlotController
57
+ defaultSlot: Ref<HTMLElement> = createRef()
58
+ namedSlot: Ref<HTMLElement> = createRef()
59
+
60
+ constructor() {
61
+ super()
62
+ this.slotController = new PktSlotController(this, this.defaultSlot, this.namedSlot)
63
+ }
64
+
47
65
  /**
48
66
  * Render functions
49
67
  */
@@ -105,8 +123,6 @@ export class PktComponent extends PktElement {
105
123
  this.dispatchEvent(
106
124
  new CustomEvent('pkt-greeting', {
107
125
  detail: 'Hei på deg!',
108
- bubbles: true,
109
- composed: true,
110
126
  }),
111
127
  )
112
128
  }
@@ -0,0 +1,546 @@
1
+ import { html, nothing, PropertyValues } from 'lit'
2
+ import { customElement, property } from 'lit/decorators.js'
3
+ import { Ref, createRef, ref } from 'lit/directives/ref.js'
4
+ import { classMap } from 'lit/directives/class-map.js'
5
+ import { repeat } from 'lit/directives/repeat.js'
6
+ import { format } from 'date-fns'
7
+ import { PktInputElement } from '../element'
8
+ import { PktCalendar } from '../calendar'
9
+ import converters from '../../helpers/converters'
10
+ import specs from 'componentSpecs/datepicker.json'
11
+ import '../calendar'
12
+ import '../input-wrapper'
13
+ import '../icon'
14
+ import '../tag'
15
+
16
+ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
17
+ @customElement('pkt-datepicker')
18
+ export class PktDatepicker extends PktInputElement {
19
+ /**
20
+ * Element attributes and properties
21
+ */
22
+ @property({ type: String, reflect: true })
23
+ value: string | string[] = ''
24
+
25
+ @property({ type: Array, reflect: false })
26
+ _value: string[] = this.value
27
+ ? !Array.isArray(this.value)
28
+ ? this.value.split(',')
29
+ : this.value
30
+ : []
31
+
32
+ @property({ type: String, reflect: true })
33
+ label: string = 'Datovelger'
34
+
35
+ @property({ type: String, reflect: true })
36
+ helptext: string = ''
37
+
38
+ @property({ type: String, reflect: true })
39
+ helptextDropdown: string = ''
40
+
41
+ @property({ type: String, reflect: true })
42
+ helptextDropdownButton: string = specs.props.helptextDropdownButton.default
43
+
44
+ @property({ type: String, reflect: true })
45
+ dateformat: string = specs.props.dateformat.default
46
+
47
+ @property({ type: Boolean, reflect: true })
48
+ multiple: boolean = specs.props.multiple.default
49
+
50
+ @property({ type: Number })
51
+ maxlength: number | null = specs.props.maxlength.default
52
+
53
+ @property({ type: Boolean, reflect: true })
54
+ range: boolean = specs.props.range.default
55
+
56
+ @property({ type: Boolean, reflect: true })
57
+ weeknumbers: boolean = specs.props.weeknumbers.default
58
+
59
+ @property({ type: Boolean, reflect: true })
60
+ withcontrols: boolean = specs.props.withcontrols.default
61
+
62
+ @property({ type: Boolean, reflect: true })
63
+ fullwidth: boolean = false
64
+
65
+ @property({ converter: converters.csvToArray })
66
+ excludedates: string[] = []
67
+
68
+ @property({ converter: converters.csvToArray })
69
+ excludeweekdays: string[] = []
70
+
71
+ @property({ type: String, reflect: true })
72
+ currentmonth: string = this.formatISODate(new Date())
73
+
74
+ @property({ type: Boolean, reflect: true }) calendarOpen: boolean = false
75
+
76
+ /**
77
+ * Housekeeping / lifecycle methods
78
+ */
79
+
80
+ async connectedCallback() {
81
+ super.connectedCallback()
82
+ document &&
83
+ document.body.addEventListener('click', (e: MouseEvent) => {
84
+ if (
85
+ this.inputRef?.value &&
86
+ this.btnRef?.value &&
87
+ !this.inputRef.value.contains(e.target as Node) &&
88
+ !(this.inputRefTo.value && this.inputRefTo.value.contains(e.target as Node)) &&
89
+ !this.btnRef.value.contains(e.target as Node) &&
90
+ !(e.target as Element).closest('.pkt-calendar-popup')
91
+ ) {
92
+ this.hideCalendar()
93
+ }
94
+ })
95
+
96
+ if (this.value.length && this._value.length === 0) {
97
+ this._value = !Array.isArray(this.value) ? this.value.split(',') : this.value
98
+ }
99
+ this.min = this.min || specs.props.min.default
100
+ this.max = this.max || specs.props.max.default
101
+
102
+ if (typeof this.excludedates === 'string') {
103
+ this.excludedates = (this.excludedates as unknown as string).split(',')
104
+ }
105
+
106
+ if (typeof this.excludeweekdays === 'string') {
107
+ this.excludeweekdays = (this.excludeweekdays as unknown as string).split(',')
108
+ }
109
+
110
+ if ((this.multiple || this.range) && this.name && !this.name.endsWith('[]')) {
111
+ this.name = this.name + '[]'
112
+ }
113
+
114
+ if (this.calendarOpen) {
115
+ await sleep(20)
116
+ this.handleCalendarPosition()
117
+ }
118
+ }
119
+
120
+ disconnectedCallback(): void {
121
+ super.disconnectedCallback()
122
+ document &&
123
+ document.body.removeEventListener('click', (e: MouseEvent) => {
124
+ if (
125
+ this.inputRef?.value &&
126
+ this.btnRef?.value &&
127
+ !this.inputRef.value.contains(e.target as Node) &&
128
+ !this.btnRef.value.contains(e.target as Node)
129
+ ) {
130
+ this.hideCalendar()
131
+ }
132
+ })
133
+ }
134
+
135
+ attributeChangedCallback(name: string, _old: string | null, value: string | null): void {
136
+ super.attributeChangedCallback(name, _old, value)
137
+ if (name === 'value') {
138
+ if (this.value === '' || this.value.length === 0) {
139
+ this._value = []
140
+ } else if (this.value.length && this._value.length === 0) {
141
+ const dates = !Array.isArray(this.value) ? this.value.split(',') : this.value
142
+ for (const date of dates) {
143
+ if (date && !this._value.includes(date)) {
144
+ this.calRef?.value?.handleDateSelect(this.fromISOToDate(date))
145
+ }
146
+ }
147
+ }
148
+
149
+ this.requestUpdate('value')
150
+ }
151
+
152
+ if (name === 'excludedates' && typeof this.excludedates === 'string') {
153
+ this.excludedates = value?.split(',') ?? []
154
+ }
155
+
156
+ if (name === 'excludeweekdays' && typeof this.excludeweekdays === 'string') {
157
+ this.excludeweekdays = value?.split(',') ?? []
158
+ }
159
+ }
160
+
161
+ updated(changedProperties: PropertyValues): void {
162
+ super.updated(changedProperties)
163
+ if (changedProperties.has('value')) {
164
+ if (this.range && this._value.length === 1) {
165
+ return
166
+ }
167
+ this.onInput(this.value)
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Element references
173
+ */
174
+
175
+ // When using PktInputElement, we always need to define `inputRef`
176
+ inputRef: Ref<HTMLInputElement> = createRef()
177
+ inputRefTo: Ref<HTMLInputElement> = createRef()
178
+ btnRef: Ref<HTMLButtonElement> = createRef()
179
+ calRef: Ref<PktCalendar> = createRef()
180
+ popupRef: Ref<HTMLDivElement> = createRef()
181
+
182
+ /**
183
+ * CSS classes
184
+ */
185
+
186
+ inputClasses = {
187
+ 'pkt-input': true,
188
+ 'pkt-datepicker__input': true,
189
+ 'pkt-input--fullwidth': this.fullwidth,
190
+ }
191
+
192
+ buttonClasses = {
193
+ 'pkt-input-icon': true,
194
+ 'pkt-btn': true,
195
+ 'pkt-btn--icon-only': true,
196
+ 'pkt-btn--tertiary': true,
197
+ }
198
+
199
+ /**
200
+ * Date value manipulation
201
+ */
202
+
203
+ private formatISODate(date: Date) {
204
+ return date.toISOString().split('T')[0]
205
+ }
206
+
207
+ fromISOToDate = (date: string | null) => {
208
+ if (!date) {
209
+ return null
210
+ }
211
+ const dateObj = new Date(date)
212
+ dateObj.setHours(12, 0, 0, 0)
213
+ if (isNaN(dateObj.getTime())) {
214
+ return null
215
+ }
216
+ return dateObj
217
+ }
218
+
219
+ fromISOtoLocal = (date: string) => {
220
+ const dateObj = new Date(date)
221
+ if (isNaN(dateObj.getTime())) {
222
+ return ''
223
+ }
224
+ return format(dateObj, this.dateformat)
225
+ }
226
+
227
+ massageDates = (dates: (string | undefined)[]) => {
228
+ const dateObjects = dates.map((date) => {
229
+ return date ?? undefined
230
+ }) as string[]
231
+ return this.multiple ? dateObjects : this.range ? dateObjects.slice(0, 2) : [dateObjects[0]]
232
+ }
233
+
234
+ renderInput() {
235
+ return html`
236
+ <input
237
+ class="${classMap(this.inputClasses)}"
238
+ type="date"
239
+ id="${this.id}-input"
240
+ name="${this.name}"
241
+ .value=${this.value}
242
+ min=${this.min}
243
+ max=${this.max}
244
+ @focus=${this.showCalendar}
245
+ @click=${(e: MouseEvent) => {
246
+ e.preventDefault()
247
+ }}
248
+ ?disabled=${this.disabled}
249
+ @blur=${(e: Event) => {
250
+ this.manageValidity(e.target as HTMLInputElement)
251
+ this.value = (e.target as HTMLInputElement).value
252
+ this._value = this.massageDates((e.target as HTMLInputElement).value.split(','))
253
+ }}
254
+ @change=${(e: Event) => {
255
+ e.stopImmediatePropagation()
256
+ }}
257
+ ${ref(this.inputRef)}
258
+ />
259
+ `
260
+ }
261
+
262
+ renderRangeInput() {
263
+ return html`
264
+ <div class="pkt-input-prefix">Fra</div>
265
+ <input
266
+ class=${classMap(this.inputClasses)}
267
+ type="date"
268
+ id="${this.id}-input"
269
+ name="${this.name}"
270
+ .value=${this._value[0] ?? ''}
271
+ min=${this.min}
272
+ max=${this.max}
273
+ @focus=${this.showCalendar}
274
+ @click=${(e: MouseEvent) => {
275
+ e.preventDefault()
276
+ }}
277
+ ?disabled=${this.disabled}
278
+ @blur=${(e: Event) => {
279
+ if ((e.target as HTMLInputElement).value) {
280
+ this.manageValidity(e.target as HTMLInputElement)
281
+ const date = this.fromISOToDate((e.target as HTMLInputElement).value)
282
+ if (date) {
283
+ this.calRef?.value?.handleDateSelect(date)
284
+ }
285
+ }
286
+ }}
287
+ @change=${(e: Event) => {
288
+ e.stopImmediatePropagation()
289
+ }}
290
+ ${ref(this.inputRef)}
291
+ />
292
+ <div class="pkt-input-prefix">Til</div>
293
+ <input
294
+ class=${classMap(this.inputClasses)}
295
+ type="date"
296
+ id="${this.id}-to"
297
+ name="${this.name}-to"
298
+ .value=${this._value[1] ?? ''}
299
+ min=${this.min}
300
+ max=${this.max}
301
+ @focus=${this.showCalendar}
302
+ @click=${(e: MouseEvent) => {
303
+ e.preventDefault()
304
+ }}
305
+ ?disabled=${this.disabled}
306
+ @blur=${(e: Event) => {
307
+ if ((e.target as HTMLInputElement).value) {
308
+ this.manageValidity(e.target as HTMLInputElement)
309
+ const val = (e.target as HTMLInputElement).value
310
+ if (this.min && this.min > val) {
311
+ this.internals.setValidity(
312
+ { rangeUnderflow: true },
313
+ this.strings.forms.messages.rangeUnderflow,
314
+ e.target as HTMLInputElement,
315
+ )
316
+ } else if (this.max && this.max < val) {
317
+ this.internals.setValidity(
318
+ { rangeOverflow: true },
319
+ this.strings.forms.messages.rangeOverflow,
320
+ e.target as HTMLInputElement,
321
+ )
322
+ }
323
+ const date = this.fromISOToDate((e.target as HTMLInputElement).value)
324
+ if (date) {
325
+ if (this._value[1] !== this.formatISODate(date)) {
326
+ this.calRef?.value?.handleDateSelect(date)
327
+ }
328
+ }
329
+ }
330
+ }}
331
+ @change=${(e: Event) => {
332
+ e.stopImmediatePropagation()
333
+ }}
334
+ ${ref(this.inputRefTo)}
335
+ />
336
+ `
337
+ }
338
+
339
+ renderMultipleInput() {
340
+ return html`
341
+ <input
342
+ class=${classMap(this.inputClasses)}
343
+ type="date"
344
+ id="${this.id}-input"
345
+ name="${this.name}"
346
+ min=${this.min}
347
+ max=${this.max}
348
+ @focus=${this.showCalendar}
349
+ @click=${(e: MouseEvent) => {
350
+ e.preventDefault()
351
+ }}
352
+ @blur=${(e: Event) => {
353
+ this.addToSelected(e)
354
+ }}
355
+ @keydown=${(e: KeyboardEvent) => {
356
+ if (e.key === ',' || e.key === 'Enter') {
357
+ e.preventDefault()
358
+ this.addToSelected(e)
359
+ }
360
+ }}
361
+ ?disabled=${this.disabled || (this.maxlength && this._value.length >= this.maxlength)}
362
+ @change=${(e: Event) => {
363
+ e.stopImmediatePropagation()
364
+ }}
365
+ ${ref(this.inputRef)}
366
+ />
367
+ `
368
+ }
369
+
370
+ renderTags() {
371
+ return html`
372
+ <div class="pkt-datepicker__tags" aria-live="polite">
373
+ ${!!this._value[0]
374
+ ? repeat(
375
+ this._value ?? [],
376
+ (date) => date,
377
+ (date) => html`
378
+ <pkt-tag
379
+ .id="${this.id + date + '-tag'}"
380
+ closeTag
381
+ @onClose=${() => this.calRef.value?.handleDateSelect(this.fromISOToDate(date))}
382
+ >${this.fromISOtoLocal(date)}</pkt-tag
383
+ >
384
+ `,
385
+ )
386
+ : nothing}
387
+ </div>
388
+ `
389
+ }
390
+
391
+ renderCalendar() {
392
+ return html`<div
393
+ class="pkt-calendar-popup pkt-${this.calendarOpen ? 'show' : 'hide'}"
394
+ @focusout=${this.handleFocusOut}
395
+ id="${this.id}-popup"
396
+ ${ref(this.popupRef)}
397
+ >
398
+ <pkt-calendar
399
+ ?multiple=${this.multiple}
400
+ ?range=${this.range}
401
+ ?weeknumbers=${this.weeknumbers}
402
+ ?withcontrols=${this.withcontrols}
403
+ ?focused=${this.calendarOpen}
404
+ .maxMultiple=${this.maxlength}
405
+ .selected=${this._value}
406
+ .earliest=${this.min}
407
+ .latest=${this.max}
408
+ .excludedates=${Array.isArray(this.excludedates)
409
+ ? this.excludedates
410
+ : (this.excludedates as string).split(',')}
411
+ .excludeweekdays=${this.excludeweekdays}
412
+ .currentmonth=${new Date(this.currentmonth)}
413
+ @date-selected=${(e: CustomEvent) => {
414
+ this.value =
415
+ !this.multiple && !this.range
416
+ ? !Array.isArray(e.detail)
417
+ ? e.detail.split(',')[0]
418
+ : e.detail[0]
419
+ : e.detail
420
+ this._value = e.detail
421
+ if (this.inputRef.value) {
422
+ if (this.range && this.inputRefTo.value) {
423
+ this.inputRef.value.value = this._value[0] ?? ''
424
+ this.inputRefTo.value.value = this._value[1] ?? ''
425
+ } else if (!this.multiple) {
426
+ this.inputRef.value.value = this._value.length ? this._value[0] : ''
427
+ }
428
+ }
429
+ }}
430
+ @close=${this.hideCalendar}
431
+ ${ref(this.calRef)}
432
+ ></pkt-calendar>
433
+ </div>`
434
+ }
435
+
436
+ render() {
437
+ return html`
438
+ <pkt-input-wrapper
439
+ label="${this.label}"
440
+ forId="${this.id}-input"
441
+ ?counter=${this.multiple && !!this.maxlength}
442
+ .counterCurrent=${this.value ? this._value.length : 0}
443
+ .counterMaxLength=${this.maxlength}
444
+ ?disabled=${this.disabled}
445
+ ?hasError=${this.hasError}
446
+ ?required=${this.required}
447
+ ?optionalTag=${this.optionalTag}
448
+ ?requiredTag=${this.requiredTag}
449
+ .optionalText=${this.optionalText}
450
+ .requiredText=${this.requiredText}
451
+ .errorMessage=${this.errorMessage}
452
+ .helptext=${this.helptext}
453
+ .helptextDropdown=${this.helptextDropdown}
454
+ .helptextDropdownButton=${this.helptextDropdownButton}
455
+ class="pkt-datepicker"
456
+ >
457
+ ${this.multiple ? this.renderTags() : nothing}
458
+ <div class="pkt-datepicker__inputs ${this.range ? 'pkt-input__range-inputs' : ''}">
459
+ <div class="pkt-input__container">
460
+ ${this.range
461
+ ? this.renderRangeInput()
462
+ : this.multiple
463
+ ? this.renderMultipleInput()
464
+ : this.renderInput()}
465
+ <button
466
+ class="${classMap(this.buttonClasses)}"
467
+ @click=${this.toggleCalendar}
468
+ ${ref(this.btnRef)}
469
+ >
470
+ <pkt-icon name="calendar"></pkt-icon>
471
+ <span class="pkt-btn__text">${this.strings.calendar.buttonAltText}</span>
472
+ </button>
473
+ </div>
474
+ </div>
475
+ </pkt-input-wrapper>
476
+ ${this.renderCalendar()}
477
+ `
478
+ }
479
+
480
+ /**
481
+ * Handlers
482
+ */
483
+
484
+ handleCalendarPosition() {
485
+ if (this.popupRef.value && this.inputRef.value) {
486
+ const counter = this.multiple && !!this.maxlength
487
+
488
+ const inputRect =
489
+ this.inputRef.value.parentElement?.getBoundingClientRect() ||
490
+ this.inputRef.value.getBoundingClientRect()
491
+
492
+ const inputHeight = counter ? inputRect.height + 30 : inputRect.height
493
+ const popupHeight = this.popupRef.value.getBoundingClientRect().height
494
+
495
+ let top = counter ? 'calc(100% - 30px)' : '100%'
496
+ if (
497
+ inputRect &&
498
+ inputRect.top + popupHeight > window.innerHeight &&
499
+ inputRect.top - popupHeight > 0
500
+ ) {
501
+ top = `calc(100% - ${inputHeight}px - ${popupHeight}px)`
502
+ }
503
+ this.popupRef.value.style.top = top
504
+ }
505
+ }
506
+
507
+ addToSelected = (e: Event | KeyboardEvent) => {
508
+ const target = e.target as HTMLInputElement
509
+ const minAsDate = this.min ? new Date(this.min) : null
510
+ const maxAsDate = this.max ? new Date(this.max) : null
511
+ const date = new Date(target.value.split(',')[0])
512
+ date.setHours(12, 0, 0, 0)
513
+ if (
514
+ date &&
515
+ !isNaN(date.getTime()) &&
516
+ (!minAsDate || date >= minAsDate) &&
517
+ (!maxAsDate || date <= maxAsDate) &&
518
+ this.calRef.value
519
+ ) {
520
+ this.calRef.value.handleDateSelect(date)
521
+ }
522
+ target.value = ''
523
+ this.requestUpdate('value')
524
+ }
525
+
526
+ private handleFocusOut(e: FocusEvent) {
527
+ if (!this.contains(e.target as Node)) {
528
+ this.hideCalendar()
529
+ }
530
+ }
531
+
532
+ public async showCalendar() {
533
+ this.calendarOpen = true
534
+ await sleep(20)
535
+ this.handleCalendarPosition()
536
+ }
537
+
538
+ public hideCalendar() {
539
+ this.calendarOpen = false
540
+ }
541
+
542
+ public toggleCalendar(e: Event) {
543
+ e.preventDefault()
544
+ this.calendarOpen ? this.hideCalendar() : this.showCalendar()
545
+ }
546
+ }