UNPKG

@oslokommune/punkt-elements

Version:

Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo

183 lines (172 loc) 6.7 kB
import { html, nothing, PropertyValues } from 'lit' import { ifDefined } from 'lit/directives/if-defined.js' import { customElement, property, state } from 'lit/decorators.js' import { Ref, createRef, ref } from 'lit/directives/ref.js' import { ElementProps } from '@/types/typeUtils' import { classMap } from 'lit/directives/class-map.js' import { PktInputElement } from '@/base-elements/input-element' import { PktSlotController } from '@/controllers/pkt-slot-controller' import '@/components/input-wrapper' import '@/components/icon' type Props = ElementProps< PktTextinput, | 'value' | 'type' | 'size' | 'autocomplete' | 'iconNameRight' | 'prefix' | 'suffix' | 'omitSearchIcon' > @customElement('pkt-textinput') export class PktTextinput extends PktInputElement<Props> { private inputRef: Ref<HTMLTextAreaElement> = createRef() private helptextSlot: Ref<HTMLElement> = createRef() @property({ type: String, reflect: true }) value: string = '' @property({ type: String }) type: string = 'text' @property({ type: Number }) size: number | null = null @property({ type: String }) autocomplete: string | null = null @property({ type: String }) iconNameRight: string | null = null @property({ type: String }) prefix: string | null = null @property({ type: String }) suffix: string | null = null @property({ type: Boolean }) omitSearchIcon: boolean = false @state() counterCurrent = 0 constructor() { super() this.slotController = new PktSlotController(this, this.helptextSlot) } attributeChangedCallback(name: string, _old: string | null, value: string | null): void { if (name === 'value' && this.value !== _old) { this.counterCurrent = value ? value.length : 0 this.valueChanged(value, _old) } super.attributeChangedCallback(name, _old, value) } updated(changedProperties: PropertyValues) { super.updated(changedProperties) if (changedProperties.has('value')) { this.counterCurrent = this.value?.length || 0 this.valueChanged(this.value, changedProperties.get('value')) } if (changedProperties.has('id')) { !this.name && this.id && (this.name = this.id) } } render() { const shouldShowSearchIcon = this.type === 'search' && !this.iconNameRight && !this.omitSearchIcon const inputClasses = classMap({ 'pkt-input': true, 'pkt-input--fullwidth': this.fullwidth, 'pkt-input--counter-error': this.counter && this.counterMaxLength && this.value.length && this.value.length > this.counterMaxLength, }) const labelledBy = this.ariaLabelledby || `${this.id}-input-label` return html` <pkt-input-wrapper label="${this.label}" ?counter=${this.counter} ?disabled=${this.disabled} ?hasError=${this.hasError} ?hasFieldset=${this.hasFieldset} ?inline=${this.inline} ?optionalTag=${this.optionalTag} ?required=${this.required} ?requiredTag=${this.requiredTag} ?useWrapper=${this.useWrapper} .ariaDescribedBy=${this.ariaDescribedBy} .counterCurrent=${this.counterCurrent} .counterMaxLength=${this.counterMaxLength} .errorMessage=${this.errorMessage} .forId="${this.id + '-input'}" .helptext=${this.helptext} .helptextDropdown=${this.helptextDropdown} .helptextDropdownButton=${this.helptextDropdownButton} .optionalText=${this.optionalText} .requiredText=${this.requiredText} .tagText=${this.tagText} class="pkt-textinput" > <div class="pkt-contents" ${ref(this.helptextSlot)} name="helptext" slot="helptext"></div> <div class="pkt-input__container"> ${this.prefix ? html`<div class="pkt-input-prefix">${this.prefix}</div>` : nothing} <input ${ref(this.inputRef)} class=${inputClasses} type=${this.type} name=${(this.name || this.id) + '-input'} id=${this.id + '-input'} placeholder=${ifDefined(this.placeholder)} aria-labelledby=${labelledBy} autocomplete=${this.autocomplete || 'off'} minlength=${ifDefined(this.minlength || undefined)} maxlength=${ifDefined(this.maxlength || undefined)} min=${ifDefined(this.min || undefined)} max=${ifDefined(this.max || undefined)} step=${ifDefined(this.step || undefined)} ?readonly=${this.readonly} size=${this.size || nothing} .value=${this.value} ?disabled=${this.disabled} aria-invalid=${this.hasError} aria-errormessage=${`${this.id}-error`} @input=${(e: InputEvent) => { this.value = (e.target as HTMLInputElement).value this.onInput() e.stopImmediatePropagation() }} @change=${(e: Event) => { e.stopImmediatePropagation() }} @focus=${(e: FocusEvent) => { this.onFocus() e.stopImmediatePropagation() }} @blur=${(e: FocusEvent) => { this.value = (e.target as HTMLInputElement).value this.onBlur() e.stopImmediatePropagation() }} @keydown=${(e: KeyboardEvent) => { if (e.key === 'Enter') { const form = this.internals.form as HTMLFormElement if (form) { form.requestSubmit() } else { this.inputRef.value?.blur() } } }} /> ${this.suffix ? html`<div class="pkt-input-suffix"> ${this.suffix} ${this.iconNameRight ? html`<pkt-icon class="pkt-input-suffix-icon" name=${this.iconNameRight} ></pkt-icon>` : nothing} ${shouldShowSearchIcon ? html`<pkt-icon class="pkt-input-suffix-icon" name="magnifying-glass-big" ></pkt-icon>` : nothing} </div>` : nothing} ${!this.suffix && this.iconNameRight ? html`<pkt-icon class="pkt-input-icon" name=${this.iconNameRight}></pkt-icon>` : nothing} ${!this.suffix && shouldShowSearchIcon ? html`<pkt-icon class="pkt-input-icon" name="magnifying-glass-big"></pkt-icon>` : nothing} </div> </pkt-input-wrapper> ` } }