@oslokommune/punkt-elements
Version:
Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo
186 lines (162 loc) • 6.42 kB
text/typescript
import { html, nothing, PropertyValues } from 'lit'
import { PktElement } from '@/base-elements/element'
import { customElement, property, state } from 'lit/decorators.js'
import { classMap } from 'lit/directives/class-map.js'
import { ref, createRef, Ref } from 'lit/directives/ref.js'
import { formatISODate, newDate } from 'shared-utils/date-utils'
import { PktCalendar } from '../calendar/calendar'
import { calendarUtils } from './datepicker-utils'
export class PktDatepickerPopup extends PktElement {
open = false
multiple = false
range = false
weeknumbers = false
withcontrols = false
maxMultiple: number | null = null
selected: string[] = []
earliest: string | null = null
latest: string | null = null
excludedates: string[] = []
excludeweekdays: string[] = []
currentmonth: string | null = null
today: string | null = null
private _hasBeenOpened = false
popupRef: Ref<HTMLElement> = createRef()
calendarRef: Ref<HTMLElement> = createRef()
firstUpdated() {
// expose calendarRef for external use
this.calRef = this.calendarRef
}
updated(changedProperties: PropertyValues) {
super.updated(changedProperties)
if (changedProperties.has('open')) {
if (this.open) {
this._hasBeenOpened = true
document.addEventListener('keydown', this.handleDocumentKeydown)
document.addEventListener('click', this.handleDocumentClick)
} else {
document.removeEventListener('click', this.handleDocumentClick)
document.removeEventListener('keydown', this.handleDocumentKeydown)
}
}
}
disconnectedCallback() {
super.disconnectedCallback()
document.removeEventListener('click', this.handleDocumentClick)
document.removeEventListener('keydown', this.handleDocumentKeydown)
}
handleDocumentClick = (e: MouseEvent) => {
if (!this.open) return
const path = e.composedPath() as EventTarget[]
const host = this.parentElement as EventTarget | null
const popupNode = this.popupRef.value as EventTarget | null
if (
!path.includes(this) &&
!path.includes(popupNode as EventTarget) &&
!(host && path.includes(host))
) {
this.hide()
this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }))
}
}
handleDocumentKeydown = (e: KeyboardEvent) => {
if (!this.open) return
if (e.key === 'Escape') {
this.hide()
this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }))
}
}
show() {
this.open = true
;(this.calendarRef.value as HTMLElement | null)?.focus()
}
hide() {
this.open = false
}
toggle() {
this.open ? this.hide() : this.show()
}
contains(node: Node | null) {
return !!node && !!(this.popupRef.value as HTMLElement | null)?.contains(node as Node)
}
focusOnCurrentDate() {
const cal = this.calendarRef.value as PktCalendar
if (cal && typeof cal.focusOnCurrentDate === 'function') cal.focusOnCurrentDate()
}
addToSelected(e: Event, min?: string | null, max?: string | null) {
const cal = this.calendarRef.value as PktCalendar
if (cal && typeof calendarUtils.addToSelected === 'function') {
return calendarUtils.addToSelected(e, this.calendarRef as any, min, max)
}
// Calendar not rendered yet — handle add directly
const target = e.target as HTMLInputElement
if (!target.value) return
const minAsDate = min ? newDate(min) : null
const maxAsDate = max ? newDate(max) : null
const date = newDate(target.value.split(',')[0])
if (date && !isNaN(date.getTime()) && (!minAsDate || date >= minAsDate) && (!maxAsDate || date <= maxAsDate)) {
this.handleDateSelect(date)
}
target.value = ''
}
handleDateSelect(date: Date) {
const cal = this.calendarRef.value as PktCalendar
if (cal && typeof cal.handleDateSelect === 'function') return cal.handleDateSelect(date)
// Calendar not rendered yet — handle selection toggle directly
const dateStr = formatISODate(date)
const index = this.selected.indexOf(dateStr)
const newSelected = index >= 0
? this.selected.filter((d) => d !== dateStr)
: [...this.selected, dateStr]
this.selected = newSelected
this.dispatchEvent(
new CustomEvent('date-selected', { detail: newSelected, bubbles: true, composed: true }),
)
return undefined
}
render() {
const classes = { 'pkt-calendar-popup': true, show: this.open, hide: !this.open }
return html`
<div
class="${classMap(classes)}"
${ref(this.popupRef)}
id="date-popup"
?hidden=${!this.open}
aria-hidden="${!this.open}"
>
${this.open || this._hasBeenOpened
? html`<pkt-calendar
${ref(this.calendarRef)}
?multiple=${this.multiple}
?range=${this.range}
?weeknumbers=${this.weeknumbers}
?withcontrols=${this.withcontrols}
.maxMultiple=${this.maxMultiple}
.selected=${this.selected}
.earliest=${this.earliest}
.latest=${this.latest}
.excludedates=${this.excludedates}
.excludeweekdays=${this.excludeweekdays}
.currentmonth=${this.currentmonth}
.today=${this.today}
-selected=${(e: CustomEvent) => {
this.selected = e.detail
this.dispatchEvent(
new CustomEvent('date-selected', { detail: e.detail, bubbles: true, composed: true }),
)
}}
=${() => {
this.hide()
this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }))
}}
></pkt-calendar>`
: nothing}
</div>
`
}
}
try {
customElement('pkt-datepicker-popup')(PktDatepickerPopup)
} catch (e) {
console.warn('Forsøker å definere <pkt-datepicker-popup>, men den er allerede definert')
}