UNPKG

@oslokommune/punkt-elements

Version:

Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo

214 lines (192 loc) 6.49 kB
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 { @property({ type: String }) appearance: TPktSearchInputAppearance = 'local' @property({ type: Boolean, reflect: true }) disabled: boolean = false @property({ type: Boolean, reflect: true }) fullwidth: boolean = false @property({ type: String, reflect: true }) id: string = '' @property({ type: String }) label: string | undefined @property({ type: String }) name: string | undefined @property({ type: String }) placeholder: string = 'Søk…' @property({ type: String }) value: string = '' @property({ type: Array }) suggestions: IPktSearchInputSuggestion[] | undefined @property({ type: String }) action: string | undefined @property({ 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') }