UNPKG

@oslokommune/punkt-elements

Version:

Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo

249 lines (228 loc) 6.51 kB
import { fromISOToDate, newDate } from 'shared-utils/date-utils' import { Ref } from 'lit/directives/ref.js' import { PktCalendar } from '@/components/calendar/calendar' import { validateRangeOrder, sortDates, filterDates, getDatepickerInputType, getDatepickerInputClasses, getDatepickerButtonClasses, getRangeLabelClasses, processDateSelection as sharedProcessDateSelection, handleCalendarPosition as sharedHandleCalendarPosition, handleDatepickerKeydown, handleDatepickerButtonKeydown, } from 'shared-utils/datepicker-utils' /** * Utility functions for PktDatepicker component * * This module provides helper functions organized by concern. * Framework-agnostic functions are delegated to shared-utils/datepicker-utils. * Lit-specific functions (using Ref, ElementInternals, PktCalendar) stay here. */ /** * Value parsing and validation utilities * Delegates to shared-utils/datepicker-utils */ export const valueUtils = { validateRangeOrder, sortDates, filterSelectableDates: filterDates, } /** * Input type detection utilities * Delegates to shared-utils/datepicker-utils */ export const inputTypeUtils = { getInputType: getDatepickerInputType, } /** * Form and validation utilities (Lit-specific — uses ElementInternals) */ export const formUtils = { submitForm(element: HTMLElement): void { const form = (element as any).internals?.form as HTMLFormElement if (form) { form.requestSubmit() } }, submitFormOrFallback(internals: any, fallbackAction: () => void): void { const form = internals?.form as HTMLFormElement if (form) { form.requestSubmit() } else { fallbackAction() } }, validateDateInput( input: HTMLInputElement, internals: any, min?: string | null, max?: string | null, strings?: any, ): void { const value = input.value if (!value) return if (min && min > value) { internals.setValidity( { rangeUnderflow: true }, strings?.forms?.messages?.rangeUnderflow || 'Value is below minimum', input, ) } else if (max && max < value) { internals.setValidity( { rangeOverflow: true }, strings?.forms?.messages?.rangeOverflow || 'Value is above maximum', input, ) } }, } /** * Calendar interaction utilities * handleCalendarPosition delegates to shared-utils (with Lit ref unwrapping). * addToSelected stays Lit-specific (uses Ref<PktCalendar>). */ export const calendarUtils = { addToSelected( event: Event | KeyboardEvent, calendarRef: Ref<PktCalendar>, min?: string | null, max?: string | null, ): void { const target = event.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) && calendarRef.value ) { calendarRef.value.handleDateSelect(date) } target.value = '' }, handleCalendarPosition( popupRef: Ref<HTMLDivElement>, inputRef: Ref<HTMLInputElement>, hasCounter: boolean = false, ): void { sharedHandleCalendarPosition(popupRef.value ?? null, inputRef.value ?? null, hasCounter) }, } /** * Event handling utilities (Lit-specific — uses Lit Ref types) */ export const eventUtils = { createDocumentClickListener( inputRef: Ref<HTMLInputElement>, inputRefTo: Ref<HTMLInputElement> | null, btnRef: Ref<HTMLButtonElement>, getCalendarOpen: () => boolean, onBlur: () => void, hideCalendar: () => void, ): (e: MouseEvent) => void { return (e: MouseEvent) => { if ( inputRef?.value && btnRef?.value && !inputRef.value.contains(e.target as Node) && !(inputRefTo?.value && inputRefTo.value.contains(e.target as Node)) && !btnRef.value.contains(e.target as Node) && !(e.target as Element).closest('.pkt-calendar-popup') && getCalendarOpen() ) { onBlur() hideCalendar() } } }, createDocumentKeydownListener( getCalendarOpen: () => boolean, hideCalendar: () => void, ): (e: KeyboardEvent) => void { return (e: KeyboardEvent) => { if (e.key === 'Escape' && getCalendarOpen()) { hideCalendar() } } }, handleFocusOut( event: FocusEvent, element: HTMLElement, onBlur: () => void, hideCalendar: () => void, ): void { if (!element.contains(event.target as Node)) { onBlur() hideCalendar() } }, } /** * CSS class utilities * Delegates to shared-utils/datepicker-utils */ export const cssUtils = { getInputClasses: getDatepickerInputClasses, getButtonClasses: getDatepickerButtonClasses, getRangeLabelClasses, } /** * Date value processing utilities * processDateSelection delegates to shared-utils. * updateInputValues and processRangeBlur stay Lit-specific (use Lit Ref). */ export const dateProcessingUtils = { processDateSelection: sharedProcessDateSelection, updateInputValues( inputRef: Ref<HTMLInputElement>, inputRefTo: Ref<HTMLInputElement> | null, values: string[], range: boolean, multiple: boolean, manageValidity: (input: HTMLInputElement) => void, ): void { if (!inputRef.value) return if (range && inputRefTo?.value) { inputRef.value.value = values[0] ?? '' inputRefTo.value.value = values[1] ?? '' manageValidity(inputRef.value) manageValidity(inputRefTo.value) } else if (!multiple) { inputRef.value.value = values.length ? values[0] : '' manageValidity(inputRef.value) } }, processRangeBlur( event: Event, values: string[], calendarRef: Ref<PktCalendar>, clearInputValue: () => void, manageValidity: (input: HTMLInputElement) => void, ): void { const target = event.target as HTMLInputElement if (target.value) { manageValidity(target) const date = fromISOToDate(target.value) if (date) { calendarRef?.value?.handleDateSelect(date) } } else if (values[0]) { clearInputValue() } }, } /** * Keyboard navigation utilities * Delegates to shared-utils/datepicker-utils */ export const keyboardUtils = { handleInputKeydown: handleDatepickerKeydown, handleButtonKeydown: handleDatepickerButtonKeydown, }