@oslokommune/punkt-elements 13.6.11 → 13.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/dist/{combobox-cK_746ek.cjs → combobox-BFOjlFIj.cjs} +1 -1
- package/dist/{combobox-DxNotM0u.js → combobox-DaiEdUKx.js} +1 -1
- package/dist/datepicker-BR1imflE.cjs +289 -0
- package/dist/datepicker-CbVSKaOY.js +1390 -0
- package/dist/index.d.ts +125 -7
- package/dist/{input-wrapper-D_JdEqcO.js → input-wrapper-CQzXG44g.js} +22 -22
- package/dist/{input-wrapper-C9rZEgju.cjs → input-wrapper-DVjNwf8-.cjs} +11 -12
- package/dist/pkt-combobox.cjs +1 -1
- package/dist/pkt-combobox.js +1 -1
- package/dist/pkt-datepicker.cjs +1 -1
- package/dist/pkt-datepicker.js +8 -4
- package/dist/pkt-index.cjs +1 -1
- package/dist/pkt-index.js +6 -6
- package/dist/pkt-input-wrapper.cjs +1 -1
- package/dist/pkt-input-wrapper.js +1 -1
- package/dist/pkt-select.cjs +1 -1
- package/dist/pkt-select.js +1 -1
- package/dist/pkt-textarea.cjs +1 -1
- package/dist/pkt-textarea.js +1 -1
- package/dist/pkt-textinput.cjs +1 -1
- package/dist/pkt-textinput.js +1 -1
- package/dist/{select-D7OQaUrQ.js → select-DKkoxmgj.js} +1 -1
- package/dist/{select-Cf1RWSsI.cjs → select-DynzsPo0.cjs} +1 -1
- package/dist/{textarea-CXu8UUsY.cjs → textarea-BS1tgktz.cjs} +1 -1
- package/dist/{textarea-C0vTWTov.js → textarea-COG1CH_s.js} +1 -1
- package/dist/{textinput-C6wccDhZ.cjs → textinput-CCK8ti2y.cjs} +1 -1
- package/dist/{textinput-CmZrfH4A.js → textinput-CTOtfcTR.js} +1 -1
- package/package.json +3 -3
- package/src/components/datepicker/datepicker-multiple.ts +202 -0
- package/src/components/datepicker/datepicker-popup.test.ts +77 -0
- package/src/components/datepicker/datepicker-popup.ts +137 -0
- package/src/components/datepicker/datepicker-range.ts +281 -0
- package/src/components/datepicker/datepicker-single.ts +198 -0
- package/src/components/datepicker/datepicker-utils.ts +22 -9
- package/src/components/datepicker/datepicker.ts +179 -256
- package/src/components/datepicker/index.ts +5 -1
- package/src/components/input-wrapper/input-wrapper.ts +7 -7
- package/dist/datepicker-BEMo4X9s.js +0 -770
- package/dist/datepicker-n49TAIAt.cjs +0 -169
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { html } from 'lit'
|
|
2
|
+
import { customElement, property } from 'lit/decorators.js'
|
|
3
|
+
import { classMap } from 'lit/directives/class-map.js'
|
|
4
|
+
import { ifDefined } from 'lit/directives/if-defined.js'
|
|
5
|
+
import { Ref, createRef, ref } from 'lit/directives/ref.js'
|
|
6
|
+
import { PktElement } from '@/base-elements/element'
|
|
7
|
+
import { keyboardUtils, formUtils, deviceDetection, cssUtils } from './datepicker-utils'
|
|
8
|
+
import '@/components/icon'
|
|
9
|
+
|
|
10
|
+
@customElement('pkt-datepicker-multiple')
|
|
11
|
+
export class PktDatepickerMultiple extends PktElement {
|
|
12
|
+
@property({ type: Array })
|
|
13
|
+
value: string[] = []
|
|
14
|
+
|
|
15
|
+
@property({ type: String })
|
|
16
|
+
inputType: string = 'date'
|
|
17
|
+
|
|
18
|
+
@property({ type: String })
|
|
19
|
+
id: string = ''
|
|
20
|
+
|
|
21
|
+
@property({ type: String })
|
|
22
|
+
min?: string
|
|
23
|
+
|
|
24
|
+
@property({ type: String })
|
|
25
|
+
max?: string
|
|
26
|
+
|
|
27
|
+
@property({ type: String })
|
|
28
|
+
placeholder?: string
|
|
29
|
+
|
|
30
|
+
@property({ type: Boolean })
|
|
31
|
+
readonly: boolean = false
|
|
32
|
+
|
|
33
|
+
@property({ type: Boolean })
|
|
34
|
+
disabled: boolean = false
|
|
35
|
+
|
|
36
|
+
@property({ type: Number })
|
|
37
|
+
maxlength?: number
|
|
38
|
+
|
|
39
|
+
@property({ type: Object })
|
|
40
|
+
inputClasses: Record<string, boolean> = {}
|
|
41
|
+
|
|
42
|
+
@property({ type: Object })
|
|
43
|
+
internals?: any
|
|
44
|
+
|
|
45
|
+
@property({ type: Object })
|
|
46
|
+
strings: any = { calendar: { buttonAltText: 'Åpne kalender' } }
|
|
47
|
+
|
|
48
|
+
inputRef: Ref<HTMLInputElement> = createRef()
|
|
49
|
+
btnRef: Ref<HTMLButtonElement> = createRef()
|
|
50
|
+
|
|
51
|
+
get inputElement(): HTMLInputElement | undefined {
|
|
52
|
+
return this.inputRef.value
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get buttonElement(): HTMLButtonElement | undefined {
|
|
56
|
+
return this.btnRef.value
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get isInputReadonly(): boolean {
|
|
60
|
+
return this.readonly || this.inputType === 'text'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get isInputDisabled(): boolean {
|
|
64
|
+
return (
|
|
65
|
+
this.disabled ||
|
|
66
|
+
(this.maxlength !== undefined &&
|
|
67
|
+
this.maxlength !== null &&
|
|
68
|
+
this.value.length >= this.maxlength)
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private dispatchToggleCalendar(e: Event) {
|
|
73
|
+
if (this.readonly) return
|
|
74
|
+
|
|
75
|
+
this.dispatchEvent(
|
|
76
|
+
new CustomEvent('toggle-calendar', {
|
|
77
|
+
detail: e,
|
|
78
|
+
bubbles: true,
|
|
79
|
+
composed: true,
|
|
80
|
+
}),
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private dispatchInput(e: Event) {
|
|
85
|
+
this.dispatchEvent(
|
|
86
|
+
new CustomEvent('input-change', {
|
|
87
|
+
detail: e,
|
|
88
|
+
bubbles: true,
|
|
89
|
+
composed: true,
|
|
90
|
+
}),
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private dispatchFocus() {
|
|
95
|
+
this.dispatchEvent(
|
|
96
|
+
new CustomEvent('input-focus', {
|
|
97
|
+
bubbles: true,
|
|
98
|
+
composed: true,
|
|
99
|
+
}),
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private dispatchBlur(e: FocusEvent) {
|
|
104
|
+
this.dispatchEvent(
|
|
105
|
+
new CustomEvent('input-blur', {
|
|
106
|
+
detail: e,
|
|
107
|
+
bubbles: true,
|
|
108
|
+
composed: true,
|
|
109
|
+
}),
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private dispatchChange(e: Event) {
|
|
114
|
+
this.dispatchEvent(
|
|
115
|
+
new CustomEvent('input-changed', {
|
|
116
|
+
detail: e,
|
|
117
|
+
bubbles: true,
|
|
118
|
+
composed: true,
|
|
119
|
+
}),
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private dispatchAddToSelected(e: Event | KeyboardEvent) {
|
|
124
|
+
this.dispatchEvent(
|
|
125
|
+
new CustomEvent('add-to-selected', {
|
|
126
|
+
detail: e,
|
|
127
|
+
bubbles: true,
|
|
128
|
+
composed: true,
|
|
129
|
+
}),
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
createRenderRoot() {
|
|
134
|
+
return this
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
render() {
|
|
138
|
+
return html`
|
|
139
|
+
<div class="pkt-input__container">
|
|
140
|
+
<input
|
|
141
|
+
class=${classMap(this.inputClasses)}
|
|
142
|
+
.type=${this.inputType}
|
|
143
|
+
id="${this.id}-input"
|
|
144
|
+
min=${ifDefined(this.min)}
|
|
145
|
+
max=${ifDefined(this.max)}
|
|
146
|
+
placeholder=${ifDefined(this.placeholder)}
|
|
147
|
+
?readonly=${this.isInputReadonly}
|
|
148
|
+
?disabled=${this.isInputDisabled}
|
|
149
|
+
@click=${(e: MouseEvent) => {
|
|
150
|
+
e.preventDefault()
|
|
151
|
+
this.dispatchToggleCalendar(e)
|
|
152
|
+
}}
|
|
153
|
+
@touchend=${(e: TouchEvent) => {
|
|
154
|
+
e.preventDefault()
|
|
155
|
+
this.dispatchToggleCalendar(e)
|
|
156
|
+
}}
|
|
157
|
+
@blur=${(e: FocusEvent) => {
|
|
158
|
+
this.dispatchBlur(e)
|
|
159
|
+
this.dispatchAddToSelected(e)
|
|
160
|
+
}}
|
|
161
|
+
@input=${(e: Event) => {
|
|
162
|
+
this.dispatchInput(e)
|
|
163
|
+
e.stopImmediatePropagation()
|
|
164
|
+
}}
|
|
165
|
+
@focus=${() => {
|
|
166
|
+
this.dispatchFocus()
|
|
167
|
+
if (deviceDetection.isIOS()) {
|
|
168
|
+
this.dispatchToggleCalendar(new Event('focus'))
|
|
169
|
+
}
|
|
170
|
+
}}
|
|
171
|
+
@keydown=${(e: KeyboardEvent) =>
|
|
172
|
+
keyboardUtils.handleInputKeydown(
|
|
173
|
+
e,
|
|
174
|
+
(event) => this.dispatchToggleCalendar(event),
|
|
175
|
+
() =>
|
|
176
|
+
formUtils.submitFormOrFallback(this.internals, () => this.inputRef.value?.blur()),
|
|
177
|
+
undefined,
|
|
178
|
+
undefined,
|
|
179
|
+
(event) => this.dispatchAddToSelected(event),
|
|
180
|
+
)}
|
|
181
|
+
@change=${(e: Event) => {
|
|
182
|
+
this.dispatchChange(e)
|
|
183
|
+
e.stopImmediatePropagation()
|
|
184
|
+
}}
|
|
185
|
+
${ref(this.inputRef)}
|
|
186
|
+
/>
|
|
187
|
+
<button
|
|
188
|
+
class="${classMap(cssUtils.getButtonClasses())}"
|
|
189
|
+
type="button"
|
|
190
|
+
@click=${(e: Event) => this.dispatchToggleCalendar(e)}
|
|
191
|
+
@keydown=${(e: KeyboardEvent) =>
|
|
192
|
+
keyboardUtils.handleButtonKeydown(e, (event) => this.dispatchToggleCalendar(event))}
|
|
193
|
+
?disabled=${this.disabled}
|
|
194
|
+
${ref(this.btnRef)}
|
|
195
|
+
>
|
|
196
|
+
<pkt-icon name="calendar"></pkt-icon>
|
|
197
|
+
<span class="pkt-btn__text">${this.strings.calendar.buttonAltText}</span>
|
|
198
|
+
</button>
|
|
199
|
+
</div>
|
|
200
|
+
`
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { fireEvent } from '@testing-library/dom'
|
|
3
|
+
import { vi } from 'vitest'
|
|
4
|
+
|
|
5
|
+
import { createElementTest, BaseTestConfig } from '../../tests/test-framework'
|
|
6
|
+
import './datepicker-popup'
|
|
7
|
+
import '../calendar/calendar'
|
|
8
|
+
|
|
9
|
+
import { PktDatepickerPopup } from './datepicker-popup'
|
|
10
|
+
import { PktCalendar } from '../calendar/calendar'
|
|
11
|
+
|
|
12
|
+
export interface IDatepickerPopupTest extends BaseTestConfig {
|
|
13
|
+
open?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const createPopupTest = async (config: IDatepickerPopupTest = {}) => {
|
|
17
|
+
const { container, element } = await createElementTest('pkt-datepicker-popup' as any, config)
|
|
18
|
+
return { container, popup: element as PktDatepickerPopup }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
document.body.innerHTML = ''
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe('PktDatepickerPopup', () => {
|
|
26
|
+
test('opens when show() is called', async () => {
|
|
27
|
+
const { popup } = await createPopupTest()
|
|
28
|
+
await popup.updateComplete
|
|
29
|
+
|
|
30
|
+
popup.show()
|
|
31
|
+
await popup.updateComplete
|
|
32
|
+
expect(popup.open).toBe(true)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('closes when clicking outside', async () => {
|
|
36
|
+
const { popup } = await createPopupTest()
|
|
37
|
+
await popup.updateComplete
|
|
38
|
+
|
|
39
|
+
popup.show()
|
|
40
|
+
await popup.updateComplete
|
|
41
|
+
expect(popup.open).toBe(true)
|
|
42
|
+
|
|
43
|
+
fireEvent.click(document.body)
|
|
44
|
+
await popup.updateComplete
|
|
45
|
+
expect(popup.open).toBe(false)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('closes on Escape key', async () => {
|
|
49
|
+
const { popup } = await createPopupTest()
|
|
50
|
+
await popup.updateComplete
|
|
51
|
+
|
|
52
|
+
popup.show()
|
|
53
|
+
await popup.updateComplete
|
|
54
|
+
expect(popup.open).toBe(true)
|
|
55
|
+
|
|
56
|
+
fireEvent.keyDown(popup, { key: 'Escape' })
|
|
57
|
+
await popup.updateComplete
|
|
58
|
+
expect(popup.open).toBe(false)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('re-dispatches date-selected from child calendar', async () => {
|
|
62
|
+
const { popup } = await createPopupTest()
|
|
63
|
+
await popup.updateComplete
|
|
64
|
+
|
|
65
|
+
const handler = vi.fn()
|
|
66
|
+
popup.addEventListener('date-selected', (e: any) => handler(e))
|
|
67
|
+
|
|
68
|
+
const cal = popup.querySelector('pkt-calendar') as PktCalendar | null
|
|
69
|
+
expect(cal).toBeTruthy()
|
|
70
|
+
const detail = ['2025-09-17']
|
|
71
|
+
cal?.dispatchEvent(new CustomEvent('date-selected', { detail, bubbles: true, composed: true }))
|
|
72
|
+
|
|
73
|
+
expect(handler).toHaveBeenCalled()
|
|
74
|
+
const eventArg = handler.mock.calls[0][0]
|
|
75
|
+
expect(eventArg.detail).toEqual(detail)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { html, nothing } from 'lit'
|
|
2
|
+
import { customElement, property } from 'lit/decorators.js'
|
|
3
|
+
import { classMap } from 'lit/directives/class-map.js'
|
|
4
|
+
import { ifDefined } from 'lit/directives/if-defined.js'
|
|
5
|
+
import { Ref, createRef, ref } from 'lit/directives/ref.js'
|
|
6
|
+
import { PktElement } from '@/base-elements/element'
|
|
7
|
+
import { keyboardUtils, formUtils, deviceDetection, cssUtils } from './datepicker-utils'
|
|
8
|
+
import '@/components/icon'
|
|
9
|
+
|
|
10
|
+
@customElement('pkt-datepicker-range')
|
|
11
|
+
export class PktDatepickerRange extends PktElement {
|
|
12
|
+
@property({ type: Array })
|
|
13
|
+
value: string[] = []
|
|
14
|
+
|
|
15
|
+
@property({ type: String })
|
|
16
|
+
inputType: string = 'date'
|
|
17
|
+
|
|
18
|
+
@property({ type: String })
|
|
19
|
+
id: string = ''
|
|
20
|
+
|
|
21
|
+
@property({ type: String })
|
|
22
|
+
min?: string
|
|
23
|
+
|
|
24
|
+
@property({ type: String })
|
|
25
|
+
max?: string
|
|
26
|
+
|
|
27
|
+
@property({ type: String })
|
|
28
|
+
placeholder?: string
|
|
29
|
+
|
|
30
|
+
@property({ type: Boolean })
|
|
31
|
+
readonly: boolean = false
|
|
32
|
+
|
|
33
|
+
@property({ type: Boolean })
|
|
34
|
+
disabled: boolean = false
|
|
35
|
+
|
|
36
|
+
@property({ type: Boolean })
|
|
37
|
+
showRangeLabels: boolean = false
|
|
38
|
+
|
|
39
|
+
@property({ type: Object })
|
|
40
|
+
inputClasses: Record<string, boolean> = {}
|
|
41
|
+
|
|
42
|
+
@property({ type: Object })
|
|
43
|
+
internals?: any
|
|
44
|
+
|
|
45
|
+
@property({ type: Object })
|
|
46
|
+
strings: any = { generic: { from: 'Fra', to: 'Til' } }
|
|
47
|
+
|
|
48
|
+
inputRef: Ref<HTMLInputElement> = createRef()
|
|
49
|
+
inputRefTo: Ref<HTMLInputElement> = createRef()
|
|
50
|
+
btnRef: Ref<HTMLButtonElement> = createRef()
|
|
51
|
+
|
|
52
|
+
get inputElement(): HTMLInputElement | undefined {
|
|
53
|
+
return this.inputRef.value
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get inputElementTo(): HTMLInputElement | undefined {
|
|
57
|
+
return this.inputRefTo.value
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get buttonElement(): HTMLButtonElement | undefined {
|
|
61
|
+
return this.btnRef.value
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get isInputReadonly(): boolean {
|
|
65
|
+
return this.readonly || this.inputType === 'text'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private dispatchToggleCalendar(e: Event) {
|
|
69
|
+
if (this.readonly) return
|
|
70
|
+
|
|
71
|
+
this.dispatchEvent(
|
|
72
|
+
new CustomEvent('toggle-calendar', {
|
|
73
|
+
detail: e,
|
|
74
|
+
bubbles: true,
|
|
75
|
+
composed: true,
|
|
76
|
+
}),
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private dispatchInput(e: Event) {
|
|
81
|
+
this.dispatchEvent(
|
|
82
|
+
new CustomEvent('input-change', {
|
|
83
|
+
detail: e,
|
|
84
|
+
bubbles: true,
|
|
85
|
+
composed: true,
|
|
86
|
+
}),
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private dispatchFocus() {
|
|
91
|
+
this.dispatchEvent(
|
|
92
|
+
new CustomEvent('input-focus', {
|
|
93
|
+
bubbles: true,
|
|
94
|
+
composed: true,
|
|
95
|
+
}),
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private dispatchBlur(e: FocusEvent) {
|
|
100
|
+
this.dispatchEvent(
|
|
101
|
+
new CustomEvent('input-blur', {
|
|
102
|
+
detail: e,
|
|
103
|
+
bubbles: true,
|
|
104
|
+
composed: true,
|
|
105
|
+
}),
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private dispatchChange(e: Event) {
|
|
110
|
+
this.dispatchEvent(
|
|
111
|
+
new CustomEvent('input-changed', {
|
|
112
|
+
detail: e,
|
|
113
|
+
bubbles: true,
|
|
114
|
+
composed: true,
|
|
115
|
+
}),
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private dispatchManageValidity(input: HTMLInputElement) {
|
|
120
|
+
this.dispatchEvent(
|
|
121
|
+
new CustomEvent('manage-validity', {
|
|
122
|
+
detail: input,
|
|
123
|
+
bubbles: true,
|
|
124
|
+
composed: true,
|
|
125
|
+
}),
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
createRenderRoot() {
|
|
130
|
+
return this
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
render() {
|
|
134
|
+
const rangeLabelClasses = cssUtils.getRangeLabelClasses(this.showRangeLabels)
|
|
135
|
+
|
|
136
|
+
return html`
|
|
137
|
+
<div class="pkt-input__container">
|
|
138
|
+
${this.showRangeLabels
|
|
139
|
+
? html` <div class="pkt-input-prefix">${this.strings.generic.from}</div> `
|
|
140
|
+
: nothing}
|
|
141
|
+
<input
|
|
142
|
+
class=${classMap(this.inputClasses)}
|
|
143
|
+
.type=${this.inputType}
|
|
144
|
+
id="${this.id}-input"
|
|
145
|
+
.value=${this.value[0] ?? ''}
|
|
146
|
+
min=${ifDefined(this.min)}
|
|
147
|
+
max=${ifDefined(this.max)}
|
|
148
|
+
placeholder=${ifDefined(this.placeholder)}
|
|
149
|
+
?readonly=${this.isInputReadonly}
|
|
150
|
+
?disabled=${this.disabled}
|
|
151
|
+
@click=${(e: MouseEvent) => {
|
|
152
|
+
e.preventDefault()
|
|
153
|
+
this.dispatchToggleCalendar(e)
|
|
154
|
+
}}
|
|
155
|
+
@touchend=${(e: TouchEvent) => {
|
|
156
|
+
e.preventDefault()
|
|
157
|
+
this.dispatchToggleCalendar(e)
|
|
158
|
+
}}
|
|
159
|
+
@keydown=${(e: KeyboardEvent) =>
|
|
160
|
+
keyboardUtils.handleInputKeydown(
|
|
161
|
+
e,
|
|
162
|
+
(event) => this.dispatchToggleCalendar(event),
|
|
163
|
+
() =>
|
|
164
|
+
formUtils.submitFormOrFallback(this.internals, () =>
|
|
165
|
+
this.inputRefTo.value?.focus(),
|
|
166
|
+
),
|
|
167
|
+
() => this.inputRefTo.value?.focus(),
|
|
168
|
+
() => this.inputRef.value?.blur(),
|
|
169
|
+
)}
|
|
170
|
+
@input=${(e: Event) => {
|
|
171
|
+
this.dispatchInput(e)
|
|
172
|
+
e.stopImmediatePropagation()
|
|
173
|
+
}}
|
|
174
|
+
@focus=${() => {
|
|
175
|
+
this.dispatchFocus()
|
|
176
|
+
if (deviceDetection.isIOS()) {
|
|
177
|
+
this.dispatchToggleCalendar(new Event('focus'))
|
|
178
|
+
}
|
|
179
|
+
}}
|
|
180
|
+
@blur=${(e: Event) => {
|
|
181
|
+
this.dispatchEvent(
|
|
182
|
+
new CustomEvent('range-blur', {
|
|
183
|
+
detail: {
|
|
184
|
+
event: e,
|
|
185
|
+
values: this.value,
|
|
186
|
+
inputType: 'from',
|
|
187
|
+
},
|
|
188
|
+
bubbles: true,
|
|
189
|
+
composed: true,
|
|
190
|
+
}),
|
|
191
|
+
)
|
|
192
|
+
}}
|
|
193
|
+
@change=${(e: Event) => {
|
|
194
|
+
e.stopImmediatePropagation()
|
|
195
|
+
}}
|
|
196
|
+
${ref(this.inputRef)}
|
|
197
|
+
/>
|
|
198
|
+
<div class="${classMap(rangeLabelClasses)}" id="${this.id}-to-label">
|
|
199
|
+
${this.strings.generic.to}
|
|
200
|
+
</div>
|
|
201
|
+
${!this.showRangeLabels ? html` <div class="pkt-input-separator">–</div> ` : nothing}
|
|
202
|
+
<input
|
|
203
|
+
class=${classMap(this.inputClasses)}
|
|
204
|
+
.type=${this.inputType}
|
|
205
|
+
id="${this.id}-to"
|
|
206
|
+
aria-labelledby="${this.id}-to-label"
|
|
207
|
+
.value=${this.value[1] ?? ''}
|
|
208
|
+
min=${ifDefined(this.min)}
|
|
209
|
+
max=${ifDefined(this.max)}
|
|
210
|
+
placeholder=${ifDefined(this.placeholder)}
|
|
211
|
+
?readonly=${this.isInputReadonly}
|
|
212
|
+
?disabled=${this.disabled}
|
|
213
|
+
@click=${(e: MouseEvent) => {
|
|
214
|
+
e.preventDefault()
|
|
215
|
+
this.dispatchToggleCalendar(e)
|
|
216
|
+
}}
|
|
217
|
+
@touchend=${(e: TouchEvent) => {
|
|
218
|
+
e.preventDefault()
|
|
219
|
+
this.dispatchToggleCalendar(e)
|
|
220
|
+
}}
|
|
221
|
+
@keydown=${(e: KeyboardEvent) =>
|
|
222
|
+
keyboardUtils.handleInputKeydown(
|
|
223
|
+
e,
|
|
224
|
+
(event) => this.dispatchToggleCalendar(event),
|
|
225
|
+
() =>
|
|
226
|
+
formUtils.submitFormOrFallback(this.internals, () => this.inputRefTo.value?.blur()),
|
|
227
|
+
undefined,
|
|
228
|
+
() => this.inputRefTo.value?.blur(),
|
|
229
|
+
)}
|
|
230
|
+
@input=${(e: Event) => {
|
|
231
|
+
this.dispatchInput(e)
|
|
232
|
+
e.stopImmediatePropagation()
|
|
233
|
+
}}
|
|
234
|
+
@focus=${() => {
|
|
235
|
+
this.dispatchFocus()
|
|
236
|
+
if (deviceDetection.isIOS()) {
|
|
237
|
+
this.dispatchToggleCalendar(new Event('focus'))
|
|
238
|
+
}
|
|
239
|
+
}}
|
|
240
|
+
@blur=${(e: FocusEvent) => {
|
|
241
|
+
this.dispatchBlur(e)
|
|
242
|
+
if ((e.target as HTMLInputElement).value) {
|
|
243
|
+
this.dispatchManageValidity(e.target as HTMLInputElement)
|
|
244
|
+
this.dispatchEvent(
|
|
245
|
+
new CustomEvent('validate-date-input', {
|
|
246
|
+
detail: e.target as HTMLInputElement,
|
|
247
|
+
bubbles: true,
|
|
248
|
+
composed: true,
|
|
249
|
+
}),
|
|
250
|
+
)
|
|
251
|
+
this.dispatchEvent(
|
|
252
|
+
new CustomEvent('handle-date-select', {
|
|
253
|
+
detail: (e.target as HTMLInputElement).value,
|
|
254
|
+
bubbles: true,
|
|
255
|
+
composed: true,
|
|
256
|
+
}),
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
}}
|
|
260
|
+
@change=${(e: Event) => {
|
|
261
|
+
this.dispatchChange(e)
|
|
262
|
+
e.stopImmediatePropagation()
|
|
263
|
+
}}
|
|
264
|
+
${ref(this.inputRefTo)}
|
|
265
|
+
/>
|
|
266
|
+
<button
|
|
267
|
+
class="${classMap(cssUtils.getButtonClasses())}"
|
|
268
|
+
type="button"
|
|
269
|
+
@click=${(e: Event) => this.dispatchToggleCalendar(e)}
|
|
270
|
+
@keydown=${(e: KeyboardEvent) =>
|
|
271
|
+
keyboardUtils.handleButtonKeydown(e, (event) => this.dispatchToggleCalendar(event))}
|
|
272
|
+
?disabled=${this.disabled}
|
|
273
|
+
${ref(this.btnRef)}
|
|
274
|
+
>
|
|
275
|
+
<pkt-icon name="calendar"></pkt-icon>
|
|
276
|
+
<span class="pkt-btn__text">${this.strings.calendar.buttonAltText}</span>
|
|
277
|
+
</button>
|
|
278
|
+
</div>
|
|
279
|
+
`
|
|
280
|
+
}
|
|
281
|
+
}
|