@oslokommune/punkt-elements 13.6.11 → 13.6.12

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 (36) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/{combobox-cK_746ek.cjs → combobox-BFOjlFIj.cjs} +1 -1
  3. package/dist/{combobox-DxNotM0u.js → combobox-DaiEdUKx.js} +1 -1
  4. package/dist/datepicker-C244h82t.cjs +190 -0
  5. package/dist/datepicker-DwOkktaP.js +859 -0
  6. package/dist/index.d.ts +30 -3
  7. package/dist/{input-wrapper-D_JdEqcO.js → input-wrapper-CQzXG44g.js} +22 -22
  8. package/dist/{input-wrapper-C9rZEgju.cjs → input-wrapper-DVjNwf8-.cjs} +11 -12
  9. package/dist/pkt-combobox.cjs +1 -1
  10. package/dist/pkt-combobox.js +1 -1
  11. package/dist/pkt-datepicker.cjs +1 -1
  12. package/dist/pkt-datepicker.js +1 -1
  13. package/dist/pkt-index.cjs +1 -1
  14. package/dist/pkt-index.js +6 -6
  15. package/dist/pkt-input-wrapper.cjs +1 -1
  16. package/dist/pkt-input-wrapper.js +1 -1
  17. package/dist/pkt-select.cjs +1 -1
  18. package/dist/pkt-select.js +1 -1
  19. package/dist/pkt-textarea.cjs +1 -1
  20. package/dist/pkt-textarea.js +1 -1
  21. package/dist/pkt-textinput.cjs +1 -1
  22. package/dist/pkt-textinput.js +1 -1
  23. package/dist/{select-D7OQaUrQ.js → select-DKkoxmgj.js} +1 -1
  24. package/dist/{select-Cf1RWSsI.cjs → select-DynzsPo0.cjs} +1 -1
  25. package/dist/{textarea-CXu8UUsY.cjs → textarea-BS1tgktz.cjs} +1 -1
  26. package/dist/{textarea-C0vTWTov.js → textarea-COG1CH_s.js} +1 -1
  27. package/dist/{textinput-C6wccDhZ.cjs → textinput-CCK8ti2y.cjs} +1 -1
  28. package/dist/{textinput-CmZrfH4A.js → textinput-CTOtfcTR.js} +1 -1
  29. package/package.json +2 -2
  30. package/src/components/datepicker/datepicker-popup.test.ts +77 -0
  31. package/src/components/datepicker/datepicker-popup.ts +137 -0
  32. package/src/components/datepicker/datepicker-utils.ts +13 -8
  33. package/src/components/datepicker/datepicker.ts +42 -47
  34. package/src/components/input-wrapper/input-wrapper.ts +7 -7
  35. package/dist/datepicker-BEMo4X9s.js +0 -770
  36. package/dist/datepicker-n49TAIAt.cjs +0 -169
@@ -0,0 +1,137 @@
1
+ import { html } from 'lit'
2
+ import { PktElement } from '@/base-elements/element'
3
+ import { customElement, property } from 'lit/decorators.js'
4
+ import { classMap } from 'lit/directives/class-map.js'
5
+ import { ref, createRef, Ref } from 'lit/directives/ref.js'
6
+
7
+ import { PktCalendar } from '../calendar/calendar'
8
+ import { calendarUtils } from './datepicker-utils'
9
+
10
+ @customElement('pkt-datepicker-popup')
11
+ export class PktDatepickerPopup extends PktElement {
12
+ @property({ type: Boolean, reflect: true }) open = false
13
+ @property({ type: Boolean }) multiple = false
14
+ @property({ type: Boolean }) range = false
15
+ @property({ type: Boolean }) weeknumbers = false
16
+ @property({ type: Boolean }) withcontrols = false
17
+ @property({ type: Number }) maxMultiple: number | null = null
18
+ @property({ type: Array }) selected: string[] = []
19
+ @property({ type: String }) earliest: string | null = null
20
+ @property({ type: String }) latest: string | null = null
21
+ @property({ type: Array }) excludedates: string[] = []
22
+ @property({ type: Array }) excludeweekdays: string[] = []
23
+ @property({ type: String }) currentmonth: string | null = null
24
+
25
+ popupRef: Ref<HTMLElement> = createRef()
26
+ calendarRef: Ref<HTMLElement> = createRef()
27
+
28
+ firstUpdated() {
29
+ // expose calendarRef for external use
30
+ this.calRef = this.calendarRef
31
+
32
+ document.addEventListener('keydown', this.handleDocumentKeydown)
33
+ document.addEventListener('click', this.handleDocumentClick)
34
+ }
35
+
36
+ disconnectedCallback() {
37
+ super.disconnectedCallback()
38
+ document.removeEventListener('click', this.handleDocumentClick)
39
+ document.removeEventListener('keydown', this.handleDocumentKeydown)
40
+ }
41
+
42
+ handleDocumentClick = (e: MouseEvent) => {
43
+ if (!this.open) return
44
+ const path = e.composedPath() as EventTarget[]
45
+ const host = this.parentElement as EventTarget | null
46
+ const popupNode = this.popupRef.value as EventTarget | null
47
+ if (
48
+ !path.includes(this) &&
49
+ !path.includes(popupNode as EventTarget) &&
50
+ !(host && path.includes(host))
51
+ ) {
52
+ this.hide()
53
+ this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }))
54
+ }
55
+ }
56
+
57
+ handleDocumentKeydown = (e: KeyboardEvent) => {
58
+ if (!this.open) return
59
+ if (e.key === 'Escape') {
60
+ this.hide()
61
+ this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }))
62
+ }
63
+ }
64
+
65
+ show() {
66
+ this.open = true
67
+ ;(this.calendarRef.value as HTMLElement | null)?.focus()
68
+ }
69
+
70
+ hide() {
71
+ this.open = false
72
+ }
73
+
74
+ toggle() {
75
+ this.open ? this.hide() : this.show()
76
+ }
77
+
78
+ contains(node: Node | null) {
79
+ return !!node && !!(this.popupRef.value as HTMLElement | null)?.contains(node as Node)
80
+ }
81
+
82
+ focusOnCurrentDate() {
83
+ const cal = this.calendarRef.value as PktCalendar
84
+ if (cal && typeof cal.focusOnCurrentDate === 'function') cal.focusOnCurrentDate()
85
+ }
86
+
87
+ addToSelected(e: Event, min?: string | null, max?: string | null) {
88
+ if (typeof calendarUtils.addToSelected === 'function') {
89
+ return calendarUtils.addToSelected(e, this.calendarRef as any, min, max)
90
+ }
91
+ return undefined
92
+ }
93
+
94
+ handleDateSelect(date: Date) {
95
+ const cal = this.calendarRef.value as PktCalendar
96
+ if (cal && typeof cal.handleDateSelect === 'function') return cal.handleDateSelect(date)
97
+ return undefined
98
+ }
99
+
100
+ render() {
101
+ const classes = { 'pkt-calendar-popup': true, show: this.open, hide: !this.open }
102
+ return html`
103
+ <div
104
+ class="${classMap(classes)}"
105
+ ${ref(this.popupRef)}
106
+ id="date-popup"
107
+ ?hidden=${!this.open}
108
+ aria-hidden="${!this.open}"
109
+ >
110
+ <pkt-calendar
111
+ ${ref(this.calendarRef)}
112
+ ?multiple=${this.multiple}
113
+ ?range=${this.range}
114
+ ?weeknumbers=${this.weeknumbers}
115
+ ?withcontrols=${this.withcontrols}
116
+ .maxMultiple=${this.maxMultiple}
117
+ .selected=${this.selected}
118
+ .earliest=${this.earliest}
119
+ .latest=${this.latest}
120
+ .excludedates=${this.excludedates}
121
+ .excludeweekdays=${this.excludeweekdays}
122
+ .currentmonth=${this.currentmonth}
123
+ @date-selected=${(e: CustomEvent) => {
124
+ this.selected = e.detail
125
+ this.dispatchEvent(
126
+ new CustomEvent('date-selected', { detail: e.detail, bubbles: true, composed: true }),
127
+ )
128
+ }}
129
+ @close=${() => {
130
+ this.hide()
131
+ this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }))
132
+ }}
133
+ ></pkt-calendar>
134
+ </div>
135
+ `
136
+ }
137
+ }
@@ -24,18 +24,23 @@ export const sleep = (ms: number): Promise<void> =>
24
24
  export const deviceDetection = {
25
25
  /**
26
26
  * Detects if the current device is iOS (iPhone, iPad, iPod)
27
+ * Handles modern iPad Safari which uses desktop user agent since iOS 13
27
28
  */
28
29
  isIOS(): boolean {
29
30
  const ua = navigator.userAgent
30
- return /iP(hone|od|ad)/.test(ua)
31
- },
32
31
 
33
- /**
34
- * Detects if the current device is Mobile Safari
35
- */
36
- isMobileSafari(): boolean {
37
- const ua = navigator.userAgent
38
- return /iP(hone|od|ad)/.test(ua) && /Safari/.test(ua) && !/CriOS|FxiOS/.test(ua)
32
+ // Legacy iOS detection (iPhone, iPod, older iPads)
33
+ if (/iP(hone|od|ad)/.test(ua)) {
34
+ return true
35
+ }
36
+
37
+ // Modern iPad detection (iOS 13+ iPads identify as Mac)
38
+ // Check for Mac + touch support
39
+ if (/Macintosh/.test(ua) && 'ontouchend' in document) {
40
+ return true
41
+ }
42
+
43
+ return false
39
44
  },
40
45
  }
41
46
 
@@ -12,6 +12,7 @@ import '@/components/calendar'
12
12
  import '@/components/icon'
13
13
  import '@/components/input-wrapper'
14
14
  import './date-tags'
15
+ import './datepicker-popup'
15
16
  import { PktSlotController } from '@/controllers/pkt-slot-controller'
16
17
  import { keyboardUtils } from './datepicker-utils'
17
18
  import {
@@ -20,11 +21,11 @@ import {
20
21
  valueUtils,
21
22
  inputTypeUtils,
22
23
  calendarUtils,
23
- eventUtils,
24
24
  cssUtils,
25
25
  dateProcessingUtils,
26
26
  formUtils,
27
27
  } from './datepicker-utils'
28
+ import { PktDatepickerPopup } from './datepicker-popup'
28
29
  import { ElementProps } from '@/types/typeUtils'
29
30
 
30
31
  type Props = ElementProps<
@@ -52,8 +53,7 @@ export class PktDatepicker extends PktInputElement<Props> {
52
53
  * Element attributes and properties
53
54
  */
54
55
  private _valueProperty: string = ''
55
- private documentClickListener?: (e: MouseEvent) => void
56
- private documentKeydownListener?: (e: KeyboardEvent) => void
56
+ datepickerPopupRef: Ref<PktDatepickerPopup> = createRef()
57
57
 
58
58
  @property({ type: String, reflect: true })
59
59
  get value(): string {
@@ -141,30 +141,10 @@ export class PktDatepicker extends PktInputElement<Props> {
141
141
  }
142
142
  this.name =
143
143
  valueUtils.normalizeNameForMultiple(this.name, this.multiple, this.range) || this.name
144
- this.documentClickListener = eventUtils.createDocumentClickListener(
145
- this.inputRef,
146
- this.inputRefTo,
147
- this.btnRef,
148
- () => this.calendarOpen,
149
- this.onBlur.bind(this),
150
- this.hideCalendar.bind(this),
151
- )
152
- this.documentKeydownListener = eventUtils.createDocumentKeydownListener(
153
- () => this.calendarOpen,
154
- this.hideCalendar.bind(this),
155
- )
156
- document.addEventListener('click', this.documentClickListener)
157
- document.addEventListener('keydown', this.documentKeydownListener)
158
144
  }
159
145
 
160
146
  disconnectedCallback(): void {
161
147
  super.disconnectedCallback()
162
- if (this.documentClickListener) {
163
- document.removeEventListener('click', this.documentClickListener)
164
- }
165
- if (this.documentKeydownListener) {
166
- document.removeEventListener('keydown', this.documentKeydownListener)
167
- }
168
148
  }
169
149
 
170
150
  onInput(): void {
@@ -292,7 +272,7 @@ export class PktDatepicker extends PktInputElement<Props> {
292
272
  }}
293
273
  @focus=${() => {
294
274
  this.onFocus()
295
- if (deviceDetection.isMobileSafari()) {
275
+ if (deviceDetection.isIOS()) {
296
276
  this.showCalendar()
297
277
  }
298
278
  }}
@@ -348,7 +328,7 @@ export class PktDatepicker extends PktInputElement<Props> {
348
328
  }}
349
329
  @focus=${() => {
350
330
  this.onFocus()
351
- if (deviceDetection.isMobileSafari()) {
331
+ if (deviceDetection.isIOS()) {
352
332
  this.showCalendar()
353
333
  }
354
334
  }}
@@ -401,7 +381,7 @@ export class PktDatepicker extends PktInputElement<Props> {
401
381
  }}
402
382
  @focus=${() => {
403
383
  this.onFocus()
404
- if (deviceDetection.isMobileSafari()) {
384
+ if (deviceDetection.isIOS()) {
405
385
  this.showCalendar()
406
386
  }
407
387
  }}
@@ -462,7 +442,7 @@ export class PktDatepicker extends PktInputElement<Props> {
462
442
  }}
463
443
  @focus=${() => {
464
444
  this.onFocus()
465
- if (deviceDetection.isMobileSafari()) {
445
+ if (deviceDetection.isIOS()) {
466
446
  this.showCalendar()
467
447
  }
468
448
  }}
@@ -486,16 +466,10 @@ export class PktDatepicker extends PktInputElement<Props> {
486
466
  }
487
467
 
488
468
  renderCalendar() {
489
- return html`<div
490
- class="pkt-calendar-popup pkt-${this.calendarOpen ? 'show' : 'hide'}"
491
- @focusout=${(e: FocusEvent) => {
492
- if (this.calendarOpen) this.handleFocusOut(e)
493
- }}
494
- id="${this.id}-popup"
495
- ${ref(this.popupRef)}
496
- >
497
- <pkt-calendar
498
- id="${this.id}-calendar"
469
+ return html`
470
+ <pkt-datepicker-popup
471
+ class="pkt-contents"
472
+ ?open=${this.calendarOpen}
499
473
  ?multiple=${this.multiple}
500
474
  ?range=${this.range}
501
475
  ?weeknumbers=${this.weeknumbers}
@@ -525,9 +499,9 @@ export class PktDatepicker extends PktInputElement<Props> {
525
499
  this.onBlur()
526
500
  this.hideCalendar()
527
501
  }}
528
- ${ref(this.calRef)}
529
- ></pkt-calendar>
530
- </div>`
502
+ ${ref(this.datepickerPopupRef)}
503
+ ></pkt-datepicker-popup>
504
+ `
531
505
  }
532
506
 
533
507
  render() {
@@ -572,7 +546,13 @@ export class PktDatepicker extends PktInputElement<Props> {
572
546
  strings=${this.strings}
573
547
  id-base=${this.id}
574
548
  @date-tag-removed=${(e: CustomEvent) => {
575
- this.calRef.value?.handleDateSelect(fromISOToDate(e.detail))
549
+ const popup = this.datepickerPopupRef.value
550
+ const date = fromISOToDate(e.detail)
551
+ if (popup && date && typeof popup.handleDateSelect === 'function') {
552
+ popup.handleDateSelect(date)
553
+ } else {
554
+ this.calRef.value?.handleDateSelect(date)
555
+ }
576
556
  }}
577
557
  ></pkt-date-tags>`
578
558
  : nothing}
@@ -616,28 +596,43 @@ export class PktDatepicker extends PktInputElement<Props> {
616
596
  }
617
597
 
618
598
  addToSelected = (e: Event | KeyboardEvent) => {
619
- calendarUtils.addToSelected(e, this.calRef, this.min, this.max)
620
- }
621
-
622
- private handleFocusOut(e: FocusEvent) {
623
- eventUtils.handleFocusOut(e, this, this.onBlur.bind(this), this.hideCalendar.bind(this))
599
+ const popup = this.datepickerPopupRef.value
600
+ if (popup && typeof popup.addToSelected === 'function') {
601
+ return popup.addToSelected(e, this.min, this.max)
602
+ }
603
+ return calendarUtils.addToSelected(e, this.calRef, this.min, this.max)
624
604
  }
625
605
 
626
606
  public async showCalendar() {
607
+ const popup = this.datepickerPopupRef.value
627
608
  this.calendarOpen = true
609
+ if (popup && typeof popup.show === 'function') {
610
+ popup.show()
611
+ if (deviceDetection.isIOS()) popup.focusOnCurrentDate()
612
+ return
613
+ }
628
614
  await sleep(20)
629
615
  this.handleCalendarPosition()
630
- if (deviceDetection.isMobileSafari()) {
616
+ if (deviceDetection.isIOS()) {
631
617
  this.calRef.value?.focusOnCurrentDate()
632
618
  }
633
619
  }
634
620
 
635
621
  public hideCalendar() {
622
+ const popup = this.datepickerPopupRef.value
636
623
  this.calendarOpen = false
624
+ if (popup && typeof popup.hide === 'function') return popup.hide()
637
625
  }
638
626
 
639
627
  public async toggleCalendar(e: Event) {
640
628
  e.preventDefault()
629
+ const popup = this.datepickerPopupRef.value
630
+ if (popup && typeof popup.toggle === 'function') {
631
+ const wasOpen = !!popup.open
632
+ popup.toggle()
633
+ this.calendarOpen = !wasOpen
634
+ return
635
+ }
641
636
  this.calendarOpen ? this.hideCalendar() : this.showCalendar()
642
637
  }
643
638
 
@@ -11,6 +11,7 @@ import { uuidish } from '@/utils/stringutils'
11
11
  import specs from 'componentSpecs/input-wrapper.json'
12
12
  import '@/components/helptext'
13
13
  import '@/components/icon'
14
+ import '@/components/alert'
14
15
 
15
16
  type TCounterPosition = 'top' | 'bottom'
16
17
  type Props = ElementProps<
@@ -190,16 +191,15 @@ export class PktInputWrapper extends PktElement<Props> {
190
191
 
191
192
  const errorElement = () => {
192
193
  if (this.hasError && this.errorMessage) {
193
- return html`<div
194
- role="alert"
195
- class="pkt-alert pkt-alert--error pkt-alert--compact"
194
+ return html`<pkt-alert
195
+ skin="error"
196
+ compact
197
+ id=${`${this.forId}-error`}
196
198
  aria-live="assertive"
197
199
  aria-atomic="true"
198
- id="${this.forId}-error"
199
200
  >
200
- <pkt-icon name="alert-error" class="pkt-alert__icon"></pkt-icon>
201
- <div class="pkt-alert__text">${unsafeHTML(this.errorMessage)}</div>
202
- </div>`
201
+ ${unsafeHTML(this.errorMessage)}
202
+ </pkt-alert>`
203
203
  } else {
204
204
  return nothing
205
205
  }