UNPKG

@oslokommune/punkt-elements

Version:

Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo

136 lines (116 loc) 4.06 kB
import type { IPktComboboxOption } from 'shared-types/combobox' import type { TTagSkin } from '../tag' import { buildFulltext } from 'shared-utils/combobox/option-utils' /** * Lit-specific utility functions for PktCombobox component. * * Framework-agnostic functions live in shared-utils/combobox/. * Only Lit-specific functions (using Ref, in-place mutation) stay here. */ // Selection state helpers (mutating, Lit-specific) /** * In-place option mutation helpers that preserve referential identity * between this._options and this.options in the Lit component. */ export const selectionMutators = { markOptionSelected(options: IPktComboboxOption[], value: string | null): void { if (!value) return for (const option of options) { if (option.value === value) { option.selected = true break } } }, markOptionDeselected(options: IPktComboboxOption[], value: string | null): void { if (!value) return for (const option of options) { if (option.value === value) { option.selected = false break } } }, markAllSelected(options: IPktComboboxOption[]): void { for (const option of options) { option.selected = true } }, markAllDeselected(options: IPktComboboxOption[]): void { for (const option of options) { option.selected = false } }, removeUserAddedOptions(options: IPktComboboxOption[]): IPktComboboxOption[] { return options.filter((option) => !option.userAdded) }, } // Slot parsing (Lit-specific) /** * Parses option elements from slot controller nodes into IPktComboboxOption[]. */ export const slotUtils = { parseOptionsFromSlot(nodes: Element[]): IPktComboboxOption[] { const options: IPktComboboxOption[] = [] nodes.forEach((node: Element) => { if (!node.textContent && !node.getAttribute('value')) return const option: IPktComboboxOption = { value: node.getAttribute('value') || node.textContent || '', label: node.textContent || node.getAttribute('value') || '', } if (node.getAttribute('data-prefix')) { option.prefix = node.getAttribute('data-prefix') || undefined } if (node.getAttribute('tagskincolor')) { option.tagSkinColor = node.getAttribute('tagskincolor') as TTagSkin } if (node.getAttribute('description')) { option.description = node.getAttribute('description') || undefined } option.fulltext = buildFulltext(option) options.push(option) }) return options }, } // Options state synchronization (Lit-specific, in-place mutation) export const optionStateUtils = { /** * Ensures options have labels and fulltext set. * Also syncs selected state with current values. * * IMPORTANT: Mutates options in place to preserve referential identity * between this._options and this.options in the Lit component. */ syncOptionsWithValues( options: IPktComboboxOption[], values: string[], ): { options: IPktComboboxOption[]; newValues: string[] } { const newValues = [...values] options.forEach((option) => { if (option.value && !option.label) { option.label = option.value } if (option.selected && !newValues.includes(option.value)) { newValues.push(option.value) } option.fulltext = buildFulltext(option) option.selected = option.selected || newValues.includes(option.value) }) return { options, newValues } }, /** * Merges user-added options with new options, preserving user additions. */ mergeWithUserAdded( newOptions: IPktComboboxOption[], previousOptions: IPktComboboxOption[], ): IPktComboboxOption[] { const userAddedValues = previousOptions.filter((option) => option?.userAdded && option.selected) const filteredUserAdded = userAddedValues.filter( (userOpt) => !(Array.isArray(newOptions) ? newOptions : []).some((opt) => opt.value === userOpt.value), ) return [...filteredUserAdded, ...newOptions] }, }