@oslokommune/punkt-elements
Version:
Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo
136 lines (116 loc) • 4.06 kB
text/typescript
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]
},
}