UNPKG

@oslokommune/punkt-elements

Version:

Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo

361 lines (360 loc) 12.6 kB
import { P as u, x as r, E as p, t as b, n as l, a as f } from "./element-CgEWt74-.js"; import { r as m } from "./state-Bo2bck5_.js"; import { o as y } from "./if-defined-CmuO4Vz9.js"; import { c as k } from "./repeat-C8BeHwYx.js"; import { e as d } from "./class-map-BpTj9gtz.js"; import { u as x } from "./stringutils-DJjRa8dG.js"; var O = Object.defineProperty, g = Object.getOwnPropertyDescriptor, a = (e, t, s, o) => { for (var n = o > 1 ? void 0 : o ? g(t, s) : t, c = e.length - 1, h; c >= 0; c--) (h = e[c]) && (n = (o ? h(t, s, n) : h(n)) || n); return o && n && O(t, s, n), n; }; let i = class extends u { constructor() { super(...arguments), this.id = x(), this.label = null, this.options = [], this.isOpen = !1, this.disabled = !1, this.includeSearch = !1, this.isMultiSelect = !1, this.allowUserInput = !1, this.maxIsReached = !1, this.customUserInput = null, this.searchPlaceholder = null, this.searchValue = null, this.maxLength = 0, this.userMessage = null, this._selectedOptions = 0, this._filteredOptions = []; } // Lifecycle methods connectedCallback() { super.connectedCallback(), this.includeSearch && !this.searchValue && (this.searchValue = ""), this.options.length > 0 && this.filterOptions(), this.setAttribute("tabindex", "-1"), this.addEventListener("focus", this.focusFirstOrSelectedOption); } updated(e) { (e.has("options") || e.has("searchValue")) && this.filterOptions(), super.updated(e); } attributeChangedCallback(e, t, s) { (e === "options" || e === "searchValue" || e === "search-value") && this.filterOptions(), super.attributeChangedCallback(e, t, s); } // Render methods render() { return r` <div class=${d({ "pkt-listbox": !0, "pkt-listbox__open": this.isOpen, "pkt-txt-16-light": !0 })} role="listbox" aria-label=${y(this.label)} > <div class="pkt-listbox__banners"> ${this.renderMaximumReachedBanner()} ${this.renderUserMessage()} ${this.renderNewOptionBanner()} ${this.renderSearch()} </div> <ul class="pkt-listbox__options" role="presentation"> ${this.renderList()} </ul> </div> `; } renderCheckboxOrCheckIcon(e, t) { return this.isMultiSelect ? r` <input class="pkt-input-check__input-checkbox" type="checkbox" role="presentation" tabindex="-1" value=${e.value} .checked=${e.selected} aria-labelledby=${this.id + "-option-label-" + t} ?disabled=${this.disabled || e.disabled || this.maxIsReached && !e.selected} /> ` : e.selected ? r`<pkt-icon name="check-big"></pkt-icon>` : p; } renderList() { return r` ${k( this._filteredOptions, (e) => e.value, (e, t) => r` <li @click=${() => { this.toggleOption(e); }} aria-selected=${e.selected ? "true" : "false"} @keydown=${this.handleOptionKeydown} class=${d({ "pkt-listbox__option": !0, "pkt-listbox__option--selected": !!(!this.isMultiSelect && e.selected), "pkt-listbox__option--checkBox": this.isMultiSelect })} tabindex="${this.disabled || e.disabled ? "-1" : "0"}" data-index=${t} data-value=${e.value} data-selected=${e.selected ? "true" : "false"} ?data-disabled=${this.disabled || e.disabled || this.maxIsReached && !e.selected} role="option" id=${`${this.id}-${t}`} > ${this.renderCheckboxOrCheckIcon(e, t)} <span class="pkt-listbox__option-label" id=${this.id + "-option-label-" + t}> ${e.prefix ? r`<span class="pkt-listbox__option-prefix">${e.prefix}</span>` : p} ${e.label || e.value} </span> ${e.description ? r`<span class="pkt-listbox__option-description pkt-txt-14-light" >${e.description}</span >` : p} </li> ` )} `; } renderNewOptionBanner() { return this.allowUserInput && this.customUserInput ? r` <div class="pkt-listbox__banner pkt-listbox__banner--new-option pkt-listbox__option" data-type="new-option" data-value=${this.customUserInput} data-selected="false" tabindex="0" @click=${() => this.toggleOption({ value: this.customUserInput || "" })} @keydown=${this.handleOptionKeydown} > <pkt-icon class="pkt-listbox__banner-icon" name="plus-sign" size="large"></pkt-icon> Legg til “${this.customUserInput}” </div> ` : p; } renderMaximumReachedBanner() { return this._selectedOptions = this.options.filter((e) => e.selected).length, this.isMultiSelect && this._selectedOptions > 0 && this.maxLength > 0 ? r` <div class="pkt-listbox__banner pkt-listbox__banner--maximum-reached"> ${this._selectedOptions} av maks ${this.maxLength} mulige er valgt. </div> ` : p; } renderUserMessage() { return this.userMessage ? r`<div class="pkt-listbox__banner pkt-listbox__banner--user-message"> <pkt-icon class="pkt-listbox__banner-icon" name="exclamation-mark-circle" size="large" ></pkt-icon> ${this.userMessage} </div>` : p; } renderSearch() { return this.includeSearch ? r` <div class="pkt-listbox__search"> <span class="pkt-listbox__search-icon"> <pkt-icon name="magnifying-glass-small" size="large"></pkt-icon> </span> <input class="pkt-txt-16-light" type="text" aria-label="Søk i listen" form="" placeholder=${this.searchPlaceholder || b.forms.search.placeholder} @input=${this.handleSearchInput} @keydown=${this.handleSearchKeydown} .value=${this.searchValue} data-type="searchbox" ?disabled=${this.disabled} ?readonly=${this.disabled} role="searchbox" /> </div> ` : p; } // Event handlers handleSearchInput(e) { this.searchValue = e.target.value, this.dispatchEvent( new CustomEvent("search", { detail: this.searchValue, bubbles: !1 }) ); } handleSearchKeydown(e) { switch (e.key) { case "Enter": e.preventDefault(); break; case "ArrowUp": case "Escape": this.closeOptions(), e.preventDefault(); break; case "ArrowDown": case "Tab": this.focusFirstOrSelectedOption(); break; } } handleOptionKeydown(e) { const t = e.currentTarget, s = t.dataset.value, o = t.dataset.type, n = t.dataset.selected === "true"; if (!(!this.getOptionElements().length && (!this.customUserInput || !this.allowUserInput && this.customUserInput) && o !== "new-option" && o !== "searchbox")) switch (e.key) { case " ": case "Enter": this.toggleOption(t), e.preventDefault(); break; case "Backspace": s && (n ? this.toggleOption(t) : this.closeOptions()), e.preventDefault(); break; case "Escape": case "Tab": this.closeOptions(); break; case "ArrowDown": e.altKey ? this.focusLastOption() : o === "searchbox" || o === "new-option" ? this.focusFirstOption() : this.focusNextOption(t), e.preventDefault(); break; case "ArrowUp": if (e.altKey) this.focusFirstOption(); else if (t.dataset.index === "0" && this.includeSearch) { const c = this.querySelector('[role="searchbox"]'); c && c.focus(); } else if (t.dataset.index === "0" && this.customUserInput) { const c = this.querySelector('[data-type="new-option"]'); c && c.focus(); } else this.focusPreviousOption(t); e.preventDefault(); break; case "Home": this.focusFirstOption(), e.preventDefault(); break; case "End": this.focusLastOption(), e.preventDefault(); break; default: (e.metaKey || e.ctrlKey) && e.key === "a" && (this.selectAll(), e.preventDefault()), this.isLetterOrSpace(e.key) && (this.handleTypeAhead(e.key), e.preventDefault()); break; } } // Focus management methods focusAndScrollIntoView(e) { e.scrollIntoView({ block: "nearest" }), window.setTimeout(() => e.focus(), 0); } focusNextOption(e) { const t = e.nextElementSibling; t && this.focusAndScrollIntoView(t); } focusPreviousOption(e) { const t = e.previousElementSibling; if (e.dataset.index === "0" && this.includeSearch) { const s = this.querySelector('[role="searchbox"]'); s && this.focusAndScrollIntoView(s); } else t && this.focusAndScrollIntoView(t); } focusFirstOption() { const e = this.getOptionElements()[0]; e && this.focusAndScrollIntoView(e); } focusLastOption() { const e = this.getOptionElements().pop(); e && this.focusAndScrollIntoView(e); } focusFirstOrSelectedOption() { if (this.disabled) return; const e = this.getOptionElements().find( (t) => t.dataset.selected === "true" ); if (this.allowUserInput && this.customUserInput) { const t = this.querySelector('[data-type="new-option"]'); this.focusAndScrollIntoView(t); } else if (e) this.focusAndScrollIntoView(e); else if (this.includeSearch && !(document.activeElement instanceof HTMLInputElement)) { const t = this.querySelector('[role="searchbox"]'); window.setTimeout(() => t.focus(), 0); } else this.focusFirstOption(); } // Event dispatching methods toggleOption(e) { const t = e instanceof HTMLElement ? e.dataset.disabled : e.disabled; if (this.disabled || t) return; const s = e instanceof HTMLElement ? e.dataset.value : e.value; this.dispatchEvent( new CustomEvent("option-toggle", { detail: s, bubbles: !1 }) ); } selectAll() { this.dispatchEvent(new CustomEvent("select-all", { bubbles: !1 })); } closeOptions() { this.dispatchEvent(new CustomEvent("close-options", { bubbles: !1 })); } // Filtering and typeahead methods filterOptions() { this.searchValue ? this._filteredOptions = this.options.filter((e) => { var s; return (e.label + e.value).toLowerCase().includes(((s = this.searchValue) == null ? void 0 : s.toLowerCase()) || ""); }) : this._filteredOptions = [...this.options]; } isLetterOrSpace(e) { return /^[\p{L} ]$/u.test(e); } handleTypeAhead(e) { this.typeAheadString += e.toLowerCase(), this.typeAheadTimeout && clearTimeout(this.typeAheadTimeout), this.typeAheadTimeout = window.setTimeout(() => { this.typeAheadString = ""; }, 500); const s = this.getOptionElements().find( (o) => { var n; return (n = o.textContent) == null ? void 0 : n.trim().toLowerCase().startsWith(this.typeAheadString); } ); s && this.focusAndScrollIntoView(s); } // DOM helper methods getOptionElements() { return this._filteredOptions.length ? Array.from( this.querySelectorAll('[role="option"]:not([data-disabled])') || [] ) : []; } }; a([ l({ type: String }) ], i.prototype, "id", 2); a([ l({ type: String }) ], i.prototype, "label", 2); a([ l({ type: Array }) ], i.prototype, "options", 2); a([ l({ type: Boolean, reflect: !0 }) ], i.prototype, "isOpen", 2); a([ l({ type: Boolean }) ], i.prototype, "disabled", 2); a([ l({ type: Boolean }) ], i.prototype, "includeSearch", 2); a([ l({ type: Boolean }) ], i.prototype, "isMultiSelect", 2); a([ l({ type: Boolean }) ], i.prototype, "allowUserInput", 2); a([ l({ type: Boolean }) ], i.prototype, "maxIsReached", 2); a([ l({ type: String }) ], i.prototype, "customUserInput", 2); a([ l({ type: String }) ], i.prototype, "searchPlaceholder", 2); a([ l({ type: String }) ], i.prototype, "searchValue", 2); a([ l({ type: Number }) ], i.prototype, "maxLength", 2); a([ l({ type: String }) ], i.prototype, "userMessage", 2); a([ m() ], i.prototype, "_filteredOptions", 2); i = a([ f("pkt-listbox") ], i); export { i as P };