@oslokommune/punkt-elements
Version:
Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo
214 lines (192 loc) • 6.49 kB
text/typescript
import { PktElement } from '@/base-elements/element'
import { html, nothing } from 'lit'
import { customElement, property } from 'lit/decorators.js'
import { classMap } from 'lit/directives/class-map.js'
import { ifDefined } from 'lit/directives/if-defined.js'
import { FocusModalityController } from '@/controllers/focus-modality-controller'
import '@/components/icon'
import type {
IPktSearchInput,
IPktSearchInputSuggestion,
TPktSearchInputAppearance,
TPktSearchInputMethod,
} from 'shared-types/searchinput'
export type {
IPktSearchInput,
IPktSearchInputSuggestion,
TPktSearchInputAppearance,
TPktSearchInputMethod,
} from 'shared-types/searchinput'
export class PktSearchInput extends PktElement<IPktSearchInput> implements IPktSearchInput {
({ type: String }) appearance: TPktSearchInputAppearance = 'local'
({ type: Boolean, reflect: true }) disabled: boolean = false
({ type: Boolean, reflect: true }) fullwidth: boolean = false
({ type: String, reflect: true }) id: string = ''
({ type: String }) label: string | undefined
({ type: String }) name: string | undefined
({ type: String }) placeholder: string = 'Søk…'
({ type: String }) value: string = ''
({ type: Array }) suggestions: IPktSearchInputSuggestion[] | undefined
({ type: String }) action: string | undefined
({ type: String }) method: TPktSearchInputMethod | undefined
focusModality = new FocusModalityController(this, '.pkt-searchinput')
private get suggestionsId() {
return `${this.id}-suggestions`
}
private dispatchSearch(): void {
this.dispatchEvent(
new CustomEvent('pkt-search', {
detail: { value: this.value },
bubbles: true,
composed: true,
}),
)
}
private onInput(event: InputEvent): void {
const target = event.target as HTMLInputElement
this.value = target.value
}
private onInputKeydown(event: KeyboardEvent): void {
if (event.key !== 'Enter') return
event.preventDefault()
this.dispatchSearch()
}
private onSearchClick(event: Event): void {
event.preventDefault()
this.dispatchSearch()
}
private onSuggestionClick(
event: Event,
suggestion: IPktSearchInputSuggestion,
index: number,
): void {
const customEvent = new CustomEvent('pkt-suggestion-click', {
detail: {
index,
suggestion,
},
bubbles: true,
composed: true,
cancelable: true,
})
const allowed = this.dispatchEvent(customEvent)
if (!allowed) {
event.preventDefault()
}
}
private renderSuggestion(suggestion: IPktSearchInputSuggestion, index: number) {
const suggestionContent = html`
${suggestion.title
? html`<h3 class="pkt-searchinput__suggestion-title">${suggestion.title}</h3>`
: nothing}
${suggestion.text
? html`<p class="pkt-searchinput__suggestion-text">${suggestion.text}</p>`
: nothing}
`
if (suggestion.href) {
return html`<a
href=${suggestion.href}
class="pkt-searchinput__suggestion"
@click=${(event: Event) => this.onSuggestionClick(event, suggestion, index)}
>
${suggestionContent}
</a>`
}
if (suggestion.nonInteractive) {
return html`<div class="pkt-searchinput__suggestion">${suggestionContent}</div>`
}
return html`<button
type="button"
class="pkt-searchinput__suggestion"
@click=${(event: Event) => this.onSuggestionClick(event, suggestion, index)}
>
${suggestionContent}
</button>`
}
render() {
const wrapperClasses = classMap({
'pkt-searchinput': true,
[`pkt-searchinput--${this.appearance}`]: true,
'pkt-searchinput--fullwidth': this.fullwidth,
})
const buttonClasses = classMap({
'pkt-searchinput__button': true,
'pkt-input-icon': this.appearance === 'local',
'pkt-btn': true,
'pkt-btn--medium': true,
'pkt-btn--icon-only': true,
'pkt-btn--tertiary': this.appearance === 'local',
'pkt-btn--primary': this.appearance === 'global' || this.appearance === 'local-with-button',
'pkt-btn--yellow': this.appearance === 'global',
})
const inputClasses = classMap({
'pkt-input': true,
'pkt-input--fullwidth': this.fullwidth,
})
const fieldClass =
this.appearance === 'local' ? 'pkt-input__container' : 'pkt-searchinput__field'
const body = html`
${this.label
? html`<label for=${ifDefined(this.id || undefined)} class="pkt-inputwrapper__label"
>${this.label}</label
>`
: nothing}
<div class=${fieldClass}>
<input
class=${inputClasses}
type="search"
name=${this.name || this.id}
id=${this.id}
placeholder=${this.placeholder || 'Søk…'}
.value=${this.value}
?disabled=${this.disabled}
autocomplete="off"
aria-autocomplete="list"
aria-controls=${this.suggestionsId}
@input=${this.onInput}
@keydown=${this.onInputKeydown}
/>
<button
type="submit"
class=${buttonClasses}
?disabled=${this.disabled}
@click=${this.onSearchClick}
>
<pkt-icon class="pkt-btn__icon" name="magnifying-glass-big" />
<span class="pkt-btn__text">${this.label || this.placeholder || 'Søk…'}</span>
</button>
</div>
${this.suggestions
? html`<ul
id=${this.suggestionsId}
class="pkt-searchinput__suggestions"
aria-live="assertive"
>
${this.suggestions.map(
(suggestion, index) => html` <li>${this.renderSuggestion(suggestion, index)}</li> `,
)}
</ul>`
: nothing}
`
if (this.action) {
return html`<form
role="search"
class=${wrapperClasses}
action=${this.action}
method=${ifDefined(this.method)}
@submit=${(event: Event) => {
event.preventDefault()
}}
>
${body}
</form>`
}
return html`<div role="search" class=${wrapperClasses}>${body}</div>`
}
}
export default PktSearchInput
try {
customElement('pkt-searchinput')(PktSearchInput)
} catch (e) {
console.warn('Forsøker å definere <pkt-searchinput>, men den er allerede definert')
}