@oslokommune/punkt-elements
Version:
Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo
361 lines (360 loc) • 12.6 kB
JavaScript
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
=${() => {
this.toggleOption(e);
}}
aria-selected=${e.selected ? "true" : "false"}
=${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"
=${() => this.toggleOption({
value: this.customUserInput || ""
})}
=${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}
=${this.handleSearchInput}
=${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
};