+personen

This commit is contained in:
Simon Martens
2026-01-08 11:00:47 +01:00
parent 1275027296
commit 59f35263d5
7 changed files with 926 additions and 177 deletions

View File

@@ -52,6 +52,7 @@ func (p *AlmanachEditPage) GET(engine *templating.Engine, app core.App) HandleFu
data["filters"] = filters data["filters"] = filters
data["csrf_token"] = req.Session().Token data["csrf_token"] = req.Session().Token
data["item_types"] = dbmodels.ITEM_TYPE_VALUES data["item_types"] = dbmodels.ITEM_TYPE_VALUES
data["agent_relations"] = dbmodels.AGENT_RELATIONS
abbrs, err := pagemodels.GetAbks(app) abbrs, err := pagemodels.GetAbks(app)
if err == nil { if err == nil {

99
controllers/api_agents.go Normal file
View File

@@ -0,0 +1,99 @@
package controllers
import (
"net/http"
"strconv"
"strings"
"github.com/Theodor-Springmann-Stiftung/musenalm/app"
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
"github.com/Theodor-Springmann-Stiftung/musenalm/middleware"
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/router"
)
const (
URL_API_AGENTS = "/api/agents"
URL_API_AGENTS_SEARCH = "/search"
)
func init() {
app.Register(&AgentsAPI{})
}
type AgentsAPI struct{}
func (p *AgentsAPI) Up(app core.App, engine *templating.Engine) error {
return nil
}
func (p *AgentsAPI) Down(app core.App, engine *templating.Engine) error {
return nil
}
func (p *AgentsAPI) Setup(router *router.Router[*core.RequestEvent], app core.App, engine *templating.Engine) error {
rg := router.Group(URL_API_AGENTS)
rg.BindFunc(middleware.Authenticated(app))
rg.BindFunc(middleware.IsAdminOrEditor())
rg.GET(URL_API_AGENTS_SEARCH, p.searchHandler(app))
return nil
}
func (p *AgentsAPI) searchHandler(app core.App) HandleFunc {
return func(e *core.RequestEvent) error {
query := strings.TrimSpace(e.Request.URL.Query().Get("q"))
limit := parseAgentsLimit(e.Request.URL.Query().Get("limit"))
results, err := dbmodels.FTS5SearchAgents(app, query)
if err != nil {
primary, alt, err := dbmodels.BasicSearchAgents(app, query)
if err != nil {
app.Logger().Error("agent search failed", "query", query, "limit", limit, "error", err)
return e.JSON(http.StatusInternalServerError, map[string]any{
"error": "failed to search agents",
})
}
results = append(primary, alt...)
}
seen := map[string]bool{}
response := make([]map[string]string, 0, len(results))
for _, agent := range results {
if agent == nil || seen[agent.Id] {
continue
}
seen[agent.Id] = true
response = append(response, map[string]string{
"id": agent.Id,
"name": agent.Name(),
"detail": agent.Pseudonyms(),
"bio": agent.BiographicalData(),
})
if limit > 0 && len(response) >= limit {
break
}
}
return e.JSON(http.StatusOK, map[string]any{
"agents": response,
})
}
}
func parseAgentsLimit(value string) int {
if value == "" {
return 0
}
parsed, err := strconv.Atoi(value)
if err != nil {
return 0
}
if parsed <= 0 {
return 0
}
return parsed
}

View File

@@ -1,11 +1,11 @@
var gt = Object.defineProperty; var At = Object.defineProperty;
var $ = (a) => { var D = (a) => {
throw TypeError(a); throw TypeError(a);
}; };
var bt = (a, i, t) => i in a ? gt(a, i, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[i] = t; var Tt = (a, i, t) => i in a ? At(a, i, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[i] = t;
var p = (a, i, t) => bt(a, typeof i != "symbol" ? i + "" : i, t), y = (a, i, t) => i.has(a) || $("Cannot " + t); var p = (a, i, t) => Tt(a, typeof i != "symbol" ? i + "" : i, t), C = (a, i, t) => i.has(a) || D("Cannot " + t);
var C = (a, i, t) => (y(a, i, "read from private field"), t ? t.call(a) : i.get(a)), _ = (a, i, t) => i.has(a) ? $("Cannot add the same private member more than once") : i instanceof WeakSet ? i.add(a) : i.set(a, t), b = (a, i, t, e) => (y(a, i, "write to private field"), e ? e.call(a, t) : i.set(a, t), t), E = (a, i, t) => (y(a, i, "access private method"), t); var y = (a, i, t) => (C(a, i, "read from private field"), t ? t.call(a) : i.get(a)), _ = (a, i, t) => i.has(a) ? D("Cannot add the same private member more than once") : i instanceof WeakSet ? i.add(a) : i.set(a, t), b = (a, i, t, e) => (C(a, i, "write to private field"), e ? e.call(a, t) : i.set(a, t), t), E = (a, i, t) => (C(a, i, "access private method"), t);
class Et extends HTMLElement { class It extends HTMLElement {
constructor() { constructor() {
super(), this._value = "", this.render(); super(), this._value = "", this.render();
} }
@@ -74,9 +74,9 @@ class Et extends HTMLElement {
`; `;
} }
} }
const S = "filter-list-list", St = "filter-list-item", Lt = "filter-list-input", N = "filter-list-searchable"; const S = "filter-list-list", Ct = "filter-list-item", yt = "filter-list-input", P = "filter-list-searchable";
var m, g, B; var m, g, $;
class vt extends HTMLElement { class wt extends HTMLElement {
constructor() { constructor() {
super(); super();
_(this, g); _(this, g);
@@ -125,7 +125,7 @@ class vt extends HTMLElement {
let t = this.querySelector("#" + S); let t = this.querySelector("#" + S);
if (!t) if (!t)
return; return;
let e = new Mark(t.querySelectorAll("." + N)); let e = new Mark(t.querySelectorAll("." + P));
this._filter && e.mark(this._filter, { this._filter && e.mark(this._filter, {
separateWordSearch: !0 separateWordSearch: !0
}); });
@@ -165,7 +165,7 @@ class vt extends HTMLElement {
} }
getLinkText(t) { getLinkText(t) {
let e = this.getSearchText(t); let e = this.getSearchText(t);
return e === "" ? "" : `<span class="${N}">${e}</span>`; return e === "" ? "" : `<span class="${P}">${e}</span>`;
} }
getURL(t) { getURL(t) {
if (this._queryparam) { if (this._queryparam) {
@@ -187,7 +187,7 @@ class vt extends HTMLElement {
`, htmx && htmx.process(this); `, htmx && htmx.process(this);
} }
ActiveDot(t) { ActiveDot(t) {
return E(this, g, B).call(this, t), ""; return E(this, g, $).call(this, t), "";
} }
NoItems(t) { NoItems(t) {
return t.length === 0 ? '<div class="px-2 py-0.5 italic text-gray-500">Keine Einträge gefunden</div>' : ""; return t.length === 0 ? '<div class="px-2 py-0.5 italic text-gray-500">Keine Einträge gefunden</div>' : "";
@@ -200,7 +200,7 @@ class vt extends HTMLElement {
<input <input
type="text" type="text"
placeholder="${this._placeholder}" placeholder="${this._placeholder}"
class="${Lt} w-full placeholder:italic px-2 py-0.5" /> class="${yt} w-full placeholder:italic px-2 py-0.5" />
</div> </div>
</div> </div>
`; `;
@@ -215,7 +215,7 @@ class vt extends HTMLElement {
t = this._items.filter((s) => e.every((n) => this.getSearchText(s).toLowerCase().includes(n.toLowerCase()))); t = this._items.filter((s) => e.every((n) => this.getSearchText(s).toLowerCase().includes(n.toLowerCase())));
} }
return ` return `
<div id="${S}" class="${S} pt-1 max-h-60 overflow-auto bg-stone-50 ${C(this, m) ? "hidden" : ""}"> <div id="${S}" class="${S} pt-1 max-h-60 overflow-auto bg-stone-50 ${y(this, m) ? "hidden" : ""}">
${t.map( ${t.map(
(e, s) => ` (e, s) => `
<a <a
@@ -224,8 +224,8 @@ class vt extends HTMLElement {
hx-swap="outerHTML show:none" hx-swap="outerHTML show:none"
hx-select="main" hx-select="main"
hx-target="main" hx-target="main"
class="${St} block px-2.5 py-0.5 hover:bg-slate-200 no-underline ${s % 2 === 0 ? "bg-stone-100" : "bg-stone-50"}" class="${Ct} block px-2.5 py-0.5 hover:bg-slate-200 no-underline ${s % 2 === 0 ? "bg-stone-100" : "bg-stone-50"}"
${E(this, g, B).call(this, e) ? 'aria-current="page"' : ""}> ${E(this, g, $).call(this, e) ? 'aria-current="page"' : ""}>
${this.ActiveDot(e)} ${this.ActiveDot(e)}
${this.getLinkText(e)} ${this.getLinkText(e)}
</a> </a>
@@ -236,13 +236,13 @@ class vt extends HTMLElement {
`; `;
} }
} }
m = new WeakMap(), g = new WeakSet(), B = function(t) { m = new WeakMap(), g = new WeakSet(), $ = function(t) {
if (!t) if (!t)
return !1; return !1;
let e = this.getHREF(t); let e = this.getHREF(t);
return e === "" ? !1 : this._queryparam && (new URLSearchParams(window.location.search).get(this._queryparam) || "") === e ? !0 : !!window.location.href.endsWith(e); return e === "" ? !1 : this._queryparam && (new URLSearchParams(window.location.search).get(this._queryparam) || "") === e ? !0 : !!window.location.href.endsWith(e);
}; };
class At extends HTMLElement { class xt extends HTMLElement {
constructor() { constructor() {
super(), this.handleScroll = this.handleScroll.bind(this), this.scrollToTop = this.scrollToTop.bind(this); super(), this.handleScroll = this.handleScroll.bind(this), this.scrollToTop = this.scrollToTop.bind(this);
} }
@@ -278,7 +278,7 @@ class At extends HTMLElement {
window.scrollTo({ top: 0, behavior: "smooth" }); window.scrollTo({ top: 0, behavior: "smooth" });
} }
} }
class Tt extends HTMLElement { class Mt extends HTMLElement {
static get observedAttributes() { static get observedAttributes() {
return ["position", "timeout"]; return ["position", "timeout"];
} }
@@ -379,7 +379,7 @@ class Tt extends HTMLElement {
} }
} }
} }
class It extends HTMLElement { class Ot extends HTMLElement {
constructor() { constructor() {
super(), this.overlay = null, this._others = null, this._thisindex = -1, this._preview = null, this._description = null, this._imageURL = "", this._hideDLButton = !1; super(), this.overlay = null, this._others = null, this._thisindex = -1, this._preview = null, this._description = null, this._imageURL = "", this._hideDLButton = !1;
} }
@@ -487,7 +487,7 @@ class It extends HTMLElement {
this.overlay.parentNode.removeChild(this.overlay), this.overlay = null; this.overlay.parentNode.removeChild(this.overlay), this.overlay = null;
} }
} }
class yt extends HTMLElement { class kt extends HTMLElement {
static get observedAttributes() { static get observedAttributes() {
} }
constructor() { constructor() {
@@ -704,7 +704,7 @@ class f extends HTMLElement {
return /\s|[.,;:!?]/.test(i); return /\s|[.,;:!?]/.test(i);
} }
} }
class Ct extends HTMLElement { class Rt extends HTMLElement {
constructor() { constructor() {
super(); super();
} }
@@ -723,7 +723,7 @@ class Ct extends HTMLElement {
} }
} }
var A; var A;
class wt extends HTMLElement { class Bt extends HTMLElement {
constructor() { constructor() {
super(); super();
_(this, A, 176); _(this, A, 176);
@@ -742,14 +742,14 @@ class wt extends HTMLElement {
calculateShownImages() { calculateShownImages() {
const t = this.getBoundingClientRect(); const t = this.getBoundingClientRect();
console.log(t); console.log(t);
const e = Math.floor(t.width / (C(this, A) + 10)); const e = Math.floor(t.width / (y(this, A) + 10));
for (let s = 0; s < this._images.length; s++) for (let s = 0; s < this._images.length; s++)
s < e - 1 ? this._images[s].classList.remove("hidden") : this._images[s].classList.add("hidden"); s < e - 1 ? this._images[s].classList.remove("hidden") : this._images[s].classList.add("hidden");
} }
} }
A = new WeakMap(); A = new WeakMap();
const xt = "msr-component-wrapper", D = "msr-selected-items-container", P = "msr-placeholder-no-selection-text", Mt = "msr-selected-item-pill", Ot = "msr-selected-item-text", kt = "msr-item-name", Rt = "msr-item-additional-data", Bt = "msr-selected-item-role", H = "msr-selected-item-delete-btn", $t = "msr-controls-area", q = "msr-pre-add-button", U = "msr-input-area-wrapper", L = "msr-input-area-default-border", w = "msr-input-area-staged", F = "msr-staging-area-container", Nt = "msr-staged-item-pill", Dt = "msr-staged-item-text", x = "msr-staged-role-select", z = "msr-staged-cancel-btn", V = "msr-text-input", K = "msr-add-button", G = "msr-options-list", W = "msr-option-item", Pt = "msr-option-item-name", Ht = "msr-option-item-detail", j = "msr-option-item-highlighted", M = "msr-hidden-select", qt = "msr-state-no-selection", Ut = "msr-state-has-selection", Ft = "msr-state-list-open", zt = "msr-state-item-staged"; const Nt = "msr-component-wrapper", H = "msr-selected-items-container", q = "msr-placeholder-no-selection-text", $t = "msr-selected-item-pill", Dt = "msr-selected-item-text", Pt = "msr-item-name", Ht = "msr-item-additional-data", qt = "msr-selected-item-role", F = "msr-selected-item-delete-btn", Ft = "msr-controls-area", U = "msr-pre-add-button", K = "msr-input-area-wrapper", v = "msr-input-area-default-border", w = "msr-input-area-staged", z = "msr-staging-area-container", Ut = "msr-staged-item-pill", Kt = "msr-staged-item-text", x = "msr-staged-role-select", V = "msr-staged-cancel-btn", G = "msr-text-input", W = "msr-add-button", j = "msr-options-list", Q = "msr-option-item", zt = "msr-option-item-name", Vt = "msr-option-item-detail", J = "msr-option-item-highlighted", M = "msr-hidden-select", Gt = "msr-state-no-selection", Wt = "msr-state-has-selection", jt = "msr-state-list-open", Qt = "msr-state-item-staged";
class dt extends HTMLElement { class ft extends HTMLElement {
constructor() { constructor() {
super(); super();
p(this, "_blurTimeout", null); p(this, "_blurTimeout", null);
@@ -828,21 +828,21 @@ class dt extends HTMLElement {
} }
_setupTemplates() { _setupTemplates() {
this.optionTemplate = document.createElement("template"), this.optionTemplate.innerHTML = ` this.optionTemplate = document.createElement("template"), this.optionTemplate.innerHTML = `
<li role="option" class="${W} group"> <li role="option" class="${Q} group">
<span data-ref="nameEl" class="${Pt}"></span> <span data-ref="nameEl" class="${zt}"></span>
<span data-ref="detailEl" class="${Ht}"></span> <span data-ref="detailEl" class="${Vt}"></span>
</li> </li>
`, this.selectedItemTemplate = document.createElement("template"), this.selectedItemTemplate.innerHTML = ` `, this.selectedItemTemplate = document.createElement("template"), this.selectedItemTemplate.innerHTML = `
<span class="${Mt} group"> <span class="${$t} group">
<span data-ref="textEl" class="${Ot}"></span> <span data-ref="textEl" class="${Dt}"></span>
<button type="button" data-ref="deleteBtn" class="${H} ml-2">&times;</button> <button type="button" data-ref="deleteBtn" class="${F} ml-2">&times;</button>
</span> </span>
`, this.stagedPlacePillTemplate = document.createElement("template"), this.stagedPlacePillTemplate.innerHTML = ` `, this.stagedPlacePillTemplate = document.createElement("template"), this.stagedPlacePillTemplate.innerHTML = `
<span class="${Nt} flex items-center"> <span class="${Ut} flex items-center">
<span data-ref="nameEl" class="${Dt}"></span> <span data-ref="nameEl" class="${Kt}"></span>
</span> </span>
`, this.stagedCancelBtnTemplate = document.createElement("template"), this.stagedCancelBtnTemplate.innerHTML = ` `, this.stagedCancelBtnTemplate = document.createElement("template"), this.stagedCancelBtnTemplate.innerHTML = `
<button type="button" class="${z} flex items-center justify-center">&times;</button> <button type="button" class="${V} flex items-center justify-center">&times;</button>
`, this.stagedRoleSelectTemplate = document.createElement("template"), this.stagedRoleSelectTemplate.innerHTML = ` `, this.stagedRoleSelectTemplate = document.createElement("template"), this.stagedRoleSelectTemplate.innerHTML = `
<select class="${x}"> <select class="${x}">
</select> </select>
@@ -906,7 +906,7 @@ class dt extends HTMLElement {
this.setAttribute("name", t), this.hiddenSelect && (this.hiddenSelect.name = t); this.setAttribute("name", t), this.hiddenSelect && (this.hiddenSelect.name = t);
} }
connectedCallback() { connectedCallback() {
if (this.placeholderNoSelection = this.getAttribute("placeholder-no-selection") || this._placeholderNoSelection, this.placeholderSearch = this.getAttribute("placeholder-search") || this._placeholderSearch, this.placeholderRoleSelect = this.getAttribute("placeholder-role-select") || this._placeholderRoleSelect, this._render(), this.inputAreaWrapper = this.querySelector(`.${U}`), this.inputElement = this.querySelector(`.${V}`), this.stagedItemPillContainer = this.querySelector(`.${F}`), this.optionsListElement = this.querySelector(`.${G}`), this.selectedItemsContainer = this.querySelector(`.${D}`), this.addButtonElement = this.querySelector(`.${K}`), this.preAddButtonElement = this.querySelector(`.${q}`), this.hiddenSelect = this.querySelector(`.${M}`), this.name && this.hiddenSelect && (this.hiddenSelect.name = this.name), this.hasAttribute("show-add-button") ? this.showAddButton = this.getAttribute("show-add-button") : this.setAttribute("show-add-button", String(this._showAddButton)), this.inputElement && (this.inputElement.placeholder = this.placeholderSearch), this.inputElement.addEventListener("input", this._handleInput), this.inputElement.addEventListener("keydown", this._handleInputKeyDown), this.inputElement.addEventListener("focus", this._handleFocus), this.inputElement.addEventListener("blur", this._handleBlur), this.optionsListElement.addEventListener("mousedown", this._handleOptionMouseDown), this.optionsListElement.addEventListener("click", this._handleOptionClick), this.addButtonElement.addEventListener("click", this._handleAddButtonClick), this.addEventListener("keydown", this._handleKeyDown), this._renderStagedPillOrInput(), this._updateAddButtonState(), this._updatePreAddButtonVisibility(), this._updateRootElementStateClasses(), this.hasAttribute("value")) { if (this.placeholderNoSelection = this.getAttribute("placeholder-no-selection") || this._placeholderNoSelection, this.placeholderSearch = this.getAttribute("placeholder-search") || this._placeholderSearch, this.placeholderRoleSelect = this.getAttribute("placeholder-role-select") || this._placeholderRoleSelect, this._render(), this.inputAreaWrapper = this.querySelector(`.${K}`), this.inputElement = this.querySelector(`.${G}`), this.stagedItemPillContainer = this.querySelector(`.${z}`), this.optionsListElement = this.querySelector(`.${j}`), this.selectedItemsContainer = this.querySelector(`.${H}`), this.addButtonElement = this.querySelector(`.${W}`), this.preAddButtonElement = this.querySelector(`.${U}`), this.hiddenSelect = this.querySelector(`.${M}`), this.name && this.hiddenSelect && (this.hiddenSelect.name = this.name), this.hasAttribute("show-add-button") ? this.showAddButton = this.getAttribute("show-add-button") : this.setAttribute("show-add-button", String(this._showAddButton)), this.inputElement && (this.inputElement.placeholder = this.placeholderSearch), this.inputElement.addEventListener("input", this._handleInput), this.inputElement.addEventListener("keydown", this._handleInputKeyDown), this.inputElement.addEventListener("focus", this._handleFocus), this.inputElement.addEventListener("blur", this._handleBlur), this.optionsListElement.addEventListener("mousedown", this._handleOptionMouseDown), this.optionsListElement.addEventListener("click", this._handleOptionClick), this.addButtonElement.addEventListener("click", this._handleAddButtonClick), this.addEventListener("keydown", this._handleKeyDown), this._renderStagedPillOrInput(), this._updateAddButtonState(), this._updatePreAddButtonVisibility(), this._updateRootElementStateClasses(), this.hasAttribute("value")) {
const t = this.getAttribute("value"); const t = this.getAttribute("value");
try { try {
const e = JSON.parse(t); const e = JSON.parse(t);
@@ -934,7 +934,7 @@ class dt extends HTMLElement {
this.disabledCallback(t); this.disabledCallback(t);
} }
disabledCallback(t) { disabledCallback(t) {
this.inputElement && (this.inputElement.disabled = t), this.classList.toggle("pointer-events-none", t), this.querySelectorAll(`.${H}`).forEach( this.inputElement && (this.inputElement.disabled = t), this.classList.toggle("pointer-events-none", t), this.querySelectorAll(`.${F}`).forEach(
(s) => s.disabled = t (s) => s.disabled = t
); );
const e = this.querySelector(`.${x}`); const e = this.querySelector(`.${x}`);
@@ -957,7 +957,7 @@ class dt extends HTMLElement {
this.internals_.setFormValue(null), this._synchronizeHiddenSelect(); this.internals_.setFormValue(null), this._synchronizeHiddenSelect();
} }
_updateRootElementStateClasses() { _updateRootElementStateClasses() {
this.classList.toggle(qt, this._value.length === 0), this.classList.toggle(Ut, this._value.length > 0), this.classList.toggle(Ft, this._isOptionsListVisible), this.classList.toggle(zt, !!this._stagedItem); this.classList.toggle(Gt, this._value.length === 0), this.classList.toggle(Wt, this._value.length > 0), this.classList.toggle(jt, this._isOptionsListVisible), this.classList.toggle(Qt, !!this._stagedItem);
} }
_render() { _render() {
const t = this.id || `msr-${crypto.randomUUID().slice(0, 8)}`; const t = this.id || `msr-${crypto.randomUUID().slice(0, 8)}`;
@@ -968,27 +968,27 @@ class dt extends HTMLElement {
width: 0 !important; height: 0 !important; opacity: 0 !important; pointer-events: none !important; width: 0 !important; height: 0 !important; opacity: 0 !important; pointer-events: none !important;
} }
</style> </style>
<div class="${xt} relative"> <div class="${Nt} relative">
<div class="${D} flex flex-wrap gap-1 mb-2 min-h-[2.625rem] rounded-md" aria-live="polite"> <div class="${H} flex flex-wrap gap-1 mb-2 min-h-[2.625rem] rounded-md" aria-live="polite">
${this._value.length === 0 ? `<span class="${P}">${this.placeholderNoSelection}</span>` : ""} ${this._value.length === 0 ? `<span class="${q}">${this.placeholderNoSelection}</span>` : ""}
</div> </div>
<div class="${$t} flex items-center"> <div class="${Ft} flex items-center">
<div class="${U} ${L} flex-grow min-h-[42px] flex items-center flex-wrap gap-1" tabindex="-1"> <div class="${K} ${v} flex-grow min-h-[42px] flex items-center flex-wrap gap-1" tabindex="-1">
<span class="${F} flex items-center gap-2"></span> <span class="${z} flex items-center gap-2"></span>
<input type="text" <input type="text"
class="${V} flex-1 min-w-[100px] outline-none" class="${G} flex-1 min-w-[100px] outline-none"
placeholder="${this.placeholderSearch}" placeholder="${this.placeholderSearch}"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-expanded="false"> aria-expanded="false">
</div> </div>
<button type="button" <button type="button"
class="${q} hidden flex items-center justify-center ml-2" class="${U} hidden flex items-center justify-center ml-2"
aria-label="Element schnell hinzufügen"> aria-label="Element schnell hinzufügen">
+ +
</button> </button>
<button type="button" class="${K} hidden ml-2">Hinzufügen</button> <button type="button" class="${W} hidden ml-2">Hinzufügen</button>
</div> </div>
<ul role="listbox" id="${t}-options-list" class="${G} absolute z-20 w-full max-h-60 overflow-y-auto mt-1 hidden"></ul> <ul role="listbox" id="${t}-options-list" class="${j} absolute z-20 w-full max-h-60 overflow-y-auto mt-1 hidden"></ul>
<select multiple name="${this.getAttribute("name") || "items_with_roles_default"}" id="hidden-select-${t}" class="${M}" aria-hidden="true"></select> <select multiple name="${this.getAttribute("name") || "items_with_roles_default"}" id="hidden-select-${t}" class="${M}" aria-hidden="true"></select>
</div> </div>
`; `;
@@ -1011,7 +1011,7 @@ class dt extends HTMLElement {
_renderStagedPillOrInput() { _renderStagedPillOrInput() {
if (!(!this.stagedItemPillContainer || !this.inputElement || !this.inputAreaWrapper)) { if (!(!this.stagedItemPillContainer || !this.inputElement || !this.inputAreaWrapper)) {
if (this.stagedItemPillContainer.innerHTML = "", this._stagedItem && this._stagedItem.item) { if (this.stagedItemPillContainer.innerHTML = "", this._stagedItem && this._stagedItem.item) {
this.inputAreaWrapper.classList.remove(L), this.inputAreaWrapper.classList.add(w); this.inputAreaWrapper.classList.remove(v), this.inputAreaWrapper.classList.add(w);
const t = this._createStagedItemPillElement(this._stagedItem.item); const t = this._createStagedItemPillElement(this._stagedItem.item);
this.stagedItemPillContainer.appendChild(t); this.stagedItemPillContainer.appendChild(t);
const e = this._getAvailableRolesForItem(this._stagedItem.item.id), s = this._createStagedRoleSelectElement( const e = this._getAvailableRolesForItem(this._stagedItem.item.id), s = this._createStagedRoleSelectElement(
@@ -1022,7 +1022,7 @@ class dt extends HTMLElement {
const n = this._createStagedCancelButtonElement(this._stagedItem.item.name); const n = this._createStagedCancelButtonElement(this._stagedItem.item.name);
this.stagedItemPillContainer.appendChild(n), this.inputElement.classList.add("hidden"), this.inputElement.value = "", this.inputElement.removeAttribute("aria-activedescendant"), this.inputElement.setAttribute("aria-expanded", "false"); this.stagedItemPillContainer.appendChild(n), this.inputElement.classList.add("hidden"), this.inputElement.value = "", this.inputElement.removeAttribute("aria-activedescendant"), this.inputElement.setAttribute("aria-expanded", "false");
} else } else
this.inputAreaWrapper.classList.add(L), this.inputAreaWrapper.classList.remove(w), this.inputElement.classList.remove("hidden"); this.inputAreaWrapper.classList.add(v), this.inputAreaWrapper.classList.remove(w), this.inputElement.classList.remove("hidden");
this._updateAddButtonState(), this._updatePreAddButtonVisibility(), this._updateRootElementStateClasses(); this._updateAddButtonState(), this._updatePreAddButtonVisibility(), this._updateRootElementStateClasses();
} }
} }
@@ -1041,15 +1041,15 @@ class dt extends HTMLElement {
const e = this._getItemById(t.itemId); const e = this._getItemById(t.itemId);
if (!e) return null; if (!e) return null;
const n = this.selectedItemTemplate.content.cloneNode(!0).firstElementChild, r = n.querySelector('[data-ref="textEl"]'); const n = this.selectedItemTemplate.content.cloneNode(!0).firstElementChild, r = n.querySelector('[data-ref="textEl"]');
let l = `<span class="${kt}">${e.name}</span>`, o = e.additional_data ? ` <span class="${Rt}">(${e.additional_data})</span>` : "", h = ` <span class="${Bt}">${t.role}</span>`; let l = `<span class="${Pt}">${e.name}</span>`, o = e.additional_data ? ` <span class="${Ht}">(${e.additional_data})</span>` : "", h = ` <span class="${qt}">${t.role}</span>`;
r.innerHTML = `${l}${o}${h}`; r.innerHTML = `${l}${o}${h}`;
const u = n.querySelector('[data-ref="deleteBtn"]'); const u = n.querySelector('[data-ref="deleteBtn"]');
return u.setAttribute("aria-label", `Entferne ${e.name} als ${t.role}`), u.dataset.instanceId = t.instanceId, u.disabled = this.hasAttribute("disabled"), u.addEventListener("click", (ft) => { return u.setAttribute("aria-label", `Entferne ${e.name} als ${t.role}`), u.dataset.instanceId = t.instanceId, u.disabled = this.hasAttribute("disabled"), u.addEventListener("click", (Lt) => {
ft.stopPropagation(), this._handleDeleteSelectedItem(t.instanceId); Lt.stopPropagation(), this._handleDeleteSelectedItem(t.instanceId);
}), n; }), n;
} }
_renderSelectedItems() { _renderSelectedItems() {
this.selectedItemsContainer && (this.selectedItemsContainer.innerHTML = "", this._value.length === 0 ? this.selectedItemsContainer.innerHTML = `<span class="${P}">${this.placeholderNoSelection}</span>` : this._value.forEach((t) => { this.selectedItemsContainer && (this.selectedItemsContainer.innerHTML = "", this._value.length === 0 ? this.selectedItemsContainer.innerHTML = `<span class="${q}">${this.placeholderNoSelection}</span>` : this._value.forEach((t) => {
const e = this._createSelectedItemElement(t); const e = this._createSelectedItemElement(t);
e && this.selectedItemsContainer.appendChild(e); e && this.selectedItemsContainer.appendChild(e);
}), this._updateRootElementStateClasses()); }), this._updateRootElementStateClasses());
@@ -1062,7 +1062,7 @@ class dt extends HTMLElement {
} }
_createOptionElement(t, e) { _createOptionElement(t, e) {
const n = this.optionTemplate.content.cloneNode(!0).firstElementChild; const n = this.optionTemplate.content.cloneNode(!0).firstElementChild;
return n.querySelector('[data-ref="nameEl"]').textContent = t.name, n.querySelector('[data-ref="detailEl"]').textContent = t.additional_data ? `(${t.additional_data})` : "", n.dataset.id = t.id, n.setAttribute("aria-selected", String(e === this._highlightedIndex)), n.id = `${this.id || "msr"}-option-${t.id}`, e === this._highlightedIndex && n.classList.add(j), n; return n.querySelector('[data-ref="nameEl"]').textContent = t.name, n.querySelector('[data-ref="detailEl"]').textContent = t.additional_data ? `(${t.additional_data})` : "", n.dataset.id = t.id, n.setAttribute("aria-selected", String(e === this._highlightedIndex)), n.id = `${this.id || "msr"}-option-${t.id}`, e === this._highlightedIndex && n.classList.add(J), n;
} }
_renderOptionsList() { _renderOptionsList() {
if (!(!this.optionsListElement || !this.inputElement)) { if (!(!this.optionsListElement || !this.inputElement)) {
@@ -1074,7 +1074,7 @@ class dt extends HTMLElement {
this.optionsListElement.appendChild(n); this.optionsListElement.appendChild(n);
}); });
const t = this.optionsListElement.querySelector( const t = this.optionsListElement.querySelector(
`.${j}` `.${J}`
); );
t ? (t.scrollIntoView({ block: "nearest" }), this.inputElement.setAttribute("aria-activedescendant", t.id)) : this.inputElement.removeAttribute("aria-activedescendant"); t ? (t.scrollIntoView({ block: "nearest" }), this.inputElement.setAttribute("aria-activedescendant", t.id)) : this.inputElement.removeAttribute("aria-activedescendant");
} }
@@ -1123,7 +1123,7 @@ class dt extends HTMLElement {
if (!this.hasAttribute("disabled")) { if (!this.hasAttribute("disabled")) {
if (t.key === "Enter" && this._stagedItem && this._stagedItem.item) { if (t.key === "Enter" && this._stagedItem && this._stagedItem.item) {
const s = document.activeElement, n = (e = this.stagedItemPillContainer) == null ? void 0 : e.querySelector( const s = document.activeElement, n = (e = this.stagedItemPillContainer) == null ? void 0 : e.querySelector(
`.${z}` `.${V}`
); );
if (s === n) { if (s === n) {
t.preventDefault(), this._handleCancelStagedItem(t); t.preventDefault(), this._handleCancelStagedItem(t);
@@ -1161,7 +1161,7 @@ class dt extends HTMLElement {
} }
_handleFocus() { _handleFocus() {
if (!(this.hasAttribute("disabled") || this.inputElement && this.inputElement.disabled || this._stagedItem)) { if (!(this.hasAttribute("disabled") || this.inputElement && this.inputElement.disabled || this._stagedItem)) {
if (!this._stagedItem && this.inputAreaWrapper && (this.inputAreaWrapper.classList.add(L), this.inputAreaWrapper.classList.remove(w)), this.inputElement && this.inputElement.value.length > 0) { if (!this._stagedItem && this.inputAreaWrapper && (this.inputAreaWrapper.classList.add(v), this.inputAreaWrapper.classList.remove(w)), this.inputElement && this.inputElement.value.length > 0) {
const t = this.inputElement.value.toLowerCase(); const t = this.inputElement.value.toLowerCase();
this._filteredOptions = this._options.filter((e) => this._getAvailableRolesForItem(e.id).length === 0 ? !1 : e.name.toLowerCase().includes(t) || e.additional_data && e.additional_data.toLowerCase().includes(t)), this._filteredOptions.length > 0 ? (this._isOptionsListVisible = !0, this._highlightedIndex = 0, this._renderOptionsList()) : this._hideOptionsList(); this._filteredOptions = this._options.filter((e) => this._getAvailableRolesForItem(e.id).length === 0 ? !1 : e.name.toLowerCase().includes(t) || e.additional_data && e.additional_data.toLowerCase().includes(t)), this._filteredOptions.length > 0 ? (this._isOptionsListVisible = !0, this._highlightedIndex = 0, this._renderOptionsList()) : this._hideOptionsList();
} else } else
@@ -1180,7 +1180,7 @@ class dt extends HTMLElement {
} }
_handleOptionClick(t) { _handleOptionClick(t) {
if (this.hasAttribute("disabled")) return; if (this.hasAttribute("disabled")) return;
const e = t.target.closest(`li[data-id].${W}`); const e = t.target.closest(`li[data-id].${Q}`);
if (e) { if (e) {
const s = e.dataset.id, n = this._filteredOptions.find((r) => r.id === s); const s = e.dataset.id, n = this._filteredOptions.find((r) => r.id === s);
n && this._stageItem(n); n && this._stageItem(n);
@@ -1190,9 +1190,9 @@ class dt extends HTMLElement {
this.hasAttribute("disabled") || (this._value = this._value.filter((e) => e.instanceId !== t), this._updateFormValue(), this._renderSelectedItems(), this._stagedItem && this._stagedItem.item && this._renderStagedPillOrInput(), this.inputElement && this.inputElement.focus(), this._updatePreAddButtonVisibility()); this.hasAttribute("disabled") || (this._value = this._value.filter((e) => e.instanceId !== t), this._updateFormValue(), this._renderSelectedItems(), this._stagedItem && this._stagedItem.item && this._renderStagedPillOrInput(), this.inputElement && this.inputElement.focus(), this._updatePreAddButtonVisibility());
} }
} }
p(dt, "formAssociated", !0); p(ft, "formAssociated", !0);
const Vt = "mss-component-wrapper", Q = "mss-selected-items-container", Kt = "mss-selected-item-pill", Gt = "mss-selected-item-text", Wt = "mss-selected-item-pill-detail", J = "mss-selected-item-delete-btn", X = "mss-input-controls-container", Y = "mss-input-wrapper", Z = "mss-input-wrapper-focused", tt = "mss-text-input", et = "mss-create-new-button", it = "mss-options-list", jt = "mss-option-item", Qt = "mss-option-item-name", Jt = "mss-option-item-detail", st = "mss-option-item-highlighted", O = "mss-hidden-select", Xt = "mss-no-items-text", nt = "mss-loading", k = 1, R = 10, Yt = 250, Zt = "mss-state-no-selection", te = "mss-state-has-selection", ee = "mss-state-list-open"; const Jt = "mss-component-wrapper", X = "mss-selected-items-container", Xt = "mss-selected-item-pill", Yt = "mss-selected-item-text", Zt = "mss-selected-item-pill-detail", Y = "mss-selected-item-delete-btn", Z = "mss-input-controls-container", tt = "mss-input-wrapper", et = "mss-input-wrapper-focused", it = "mss-text-input", st = "mss-create-new-button", nt = "mss-options-list", te = "mss-option-item", ee = "mss-option-item-name", ie = "mss-option-item-detail", at = "mss-option-item-highlighted", O = "mss-hidden-select", se = "mss-no-items-text", rt = "mss-loading", k = 1, R = 10, ne = 250, ae = "mss-state-no-selection", re = "mss-state-has-selection", le = "mss-state-list-open";
class ct extends HTMLElement { class gt extends HTMLElement {
constructor() { constructor() {
super(); super();
p(this, "_blurTimeout", null); p(this, "_blurTimeout", null);
@@ -1384,15 +1384,15 @@ class ct extends HTMLElement {
} }
_setupTemplates() { _setupTemplates() {
this.optionTemplate = document.createElement("template"), this.optionTemplate.innerHTML = ` this.optionTemplate = document.createElement("template"), this.optionTemplate.innerHTML = `
<li role="option" class="${jt}"> <li role="option" class="${te}">
<span data-ref="nameEl" class="${Qt}"></span> <span data-ref="nameEl" class="${ee}"></span>
<span data-ref="detailEl" class="${Jt}"></span> <span data-ref="detailEl" class="${ie}"></span>
</li> </li>
`, this.selectedItemTemplate = document.createElement("template"), this.selectedItemTemplate.innerHTML = ` `, this.selectedItemTemplate = document.createElement("template"), this.selectedItemTemplate.innerHTML = `
<span class="${Kt} flex items-center"> <span class="${Xt} flex items-center">
<span data-ref="textEl" class="${Gt}"></span> <span data-ref="textEl" class="${Yt}"></span>
<span data-ref="detailEl" class="${Wt} hidden"></span> <span data-ref="detailEl" class="${Zt} hidden"></span>
<button type="button" data-ref="deleteBtn" class="${J}">&times;</button> <button type="button" data-ref="deleteBtn" class="${Y}">&times;</button>
</span> </span>
`; `;
} }
@@ -1446,7 +1446,7 @@ class ct extends HTMLElement {
this.setAttribute("name", t), this.hiddenSelect && (this.hiddenSelect.name = t); this.setAttribute("name", t), this.hiddenSelect && (this.hiddenSelect.name = t);
} }
connectedCallback() { connectedCallback() {
if (this._render(), this.inputControlsContainer = this.querySelector(`.${X}`), this.inputWrapper = this.querySelector(`.${Y}`), this.inputElement = this.querySelector(`.${tt}`), this.createNewButton = this.querySelector(`.${et}`), this.optionsListElement = this.querySelector(`.${it}`), this.selectedItemsContainer = this.querySelector(`.${Q}`), this.hiddenSelect = this.querySelector(`.${O}`), this.placeholder = this.getAttribute("placeholder") || "Search items...", this.showCreateButton = this.getAttribute("show-create-button") !== "false", this._remoteEndpoint = this.getAttribute("data-endpoint") || null, this._remoteResultKey = this.getAttribute("data-result-key") || "items", this._remoteMinChars = this._parsePositiveInt(this.getAttribute("data-minchars"), k), this._remoteLimit = this._parsePositiveInt(this.getAttribute("data-limit"), R), this.name && this.hiddenSelect && (this.hiddenSelect.name = this.name), this.inputElement.addEventListener("input", this._handleInput), this.inputElement.addEventListener("keydown", this._handleKeyDown), this.inputElement.addEventListener("focus", this._handleFocus), this.inputElement.addEventListener("blur", this._handleBlur), this.optionsListElement.addEventListener("mousedown", this._handleOptionMouseDown), this.optionsListElement.addEventListener("click", this._handleOptionClick), this.createNewButton.addEventListener("click", this._handleCreateNewButtonClick), this.selectedItemsContainer.addEventListener("click", this._handleSelectedItemsContainerClick), this._updateRootElementStateClasses(), this.hasAttribute("value")) { if (this._render(), this.inputControlsContainer = this.querySelector(`.${Z}`), this.inputWrapper = this.querySelector(`.${tt}`), this.inputElement = this.querySelector(`.${it}`), this.createNewButton = this.querySelector(`.${st}`), this.optionsListElement = this.querySelector(`.${nt}`), this.selectedItemsContainer = this.querySelector(`.${X}`), this.hiddenSelect = this.querySelector(`.${O}`), this.placeholder = this.getAttribute("placeholder") || "Search items...", this.showCreateButton = this.getAttribute("show-create-button") !== "false", this._remoteEndpoint = this.getAttribute("data-endpoint") || null, this._remoteResultKey = this.getAttribute("data-result-key") || "items", this._remoteMinChars = this._parsePositiveInt(this.getAttribute("data-minchars"), k), this._remoteLimit = this._parsePositiveInt(this.getAttribute("data-limit"), R), this.name && this.hiddenSelect && (this.hiddenSelect.name = this.name), this.inputElement.addEventListener("input", this._handleInput), this.inputElement.addEventListener("keydown", this._handleKeyDown), this.inputElement.addEventListener("focus", this._handleFocus), this.inputElement.addEventListener("blur", this._handleBlur), this.optionsListElement.addEventListener("mousedown", this._handleOptionMouseDown), this.optionsListElement.addEventListener("click", this._handleOptionClick), this.createNewButton.addEventListener("click", this._handleCreateNewButtonClick), this.selectedItemsContainer.addEventListener("click", this._handleSelectedItemsContainerClick), this._updateRootElementStateClasses(), this.hasAttribute("value")) {
const t = this.getAttribute("value"); const t = this.getAttribute("value");
try { try {
this.value = JSON.parse(t); this.value = JSON.parse(t);
@@ -1498,10 +1498,10 @@ class ct extends HTMLElement {
this.internals_.setFormValue(null), this._synchronizeHiddenSelect(); this.internals_.setFormValue(null), this._synchronizeHiddenSelect();
} }
disabledCallback(t) { disabledCallback(t) {
this.inputElement && (this.inputElement.disabled = t), this.createNewButton && (this.createNewButton.disabled = t), this.toggleAttribute("disabled", t), this.querySelectorAll(`.${J}`).forEach((e) => e.disabled = t), this.hiddenSelect && (this.hiddenSelect.disabled = t), t && this._hideOptionsList(); this.inputElement && (this.inputElement.disabled = t), this.createNewButton && (this.createNewButton.disabled = t), this.toggleAttribute("disabled", t), this.querySelectorAll(`.${Y}`).forEach((e) => e.disabled = t), this.hiddenSelect && (this.hiddenSelect.disabled = t), t && this._hideOptionsList();
} }
_updateRootElementStateClasses() { _updateRootElementStateClasses() {
this.classList.toggle(Zt, this._value.length === 0), this.classList.toggle(te, this._value.length > 0), this.classList.toggle(ee, this._isOptionsListVisible); this.classList.toggle(ae, this._value.length === 0), this.classList.toggle(re, this._value.length > 0), this.classList.toggle(le, this._isOptionsListVisible);
} }
_render() { _render() {
const t = this.id || `mss-${crypto.randomUUID().slice(0, 8)}`; const t = this.id || `mss-${crypto.randomUUID().slice(0, 8)}`;
@@ -1509,21 +1509,21 @@ class ct extends HTMLElement {
<style> <style>
.${O} { display: block !important; visibility: hidden !important; position: absolute !important; width: 0px !important; height: 0px !important; opacity: 0 !important; pointer-events: none !important; margin: -1px !important; padding: 0 !important; border: 0 !important; overflow: hidden !important; clip: rect(0, 0, 0, 0) !important; white-space: nowrap !important; } .${O} { display: block !important; visibility: hidden !important; position: absolute !important; width: 0px !important; height: 0px !important; opacity: 0 !important; pointer-events: none !important; margin: -1px !important; padding: 0 !important; border: 0 !important; overflow: hidden !important; clip: rect(0, 0, 0, 0) !important; white-space: nowrap !important; }
</style> </style>
<div class="${Vt} relative"> <div class="${Jt} relative">
<div class="${Q} flex flex-wrap gap-1 mb-1 min-h-[38px]" aria-live="polite" tabindex="-1"></div> <div class="${X} flex flex-wrap gap-1 mb-1 min-h-[38px]" aria-live="polite" tabindex="-1"></div>
<div class="${X} flex items-center space-x-4"> <div class="${Z} flex items-center space-x-4">
<div class="${Y} relative rounded-md flex items-center flex-grow"> <div class="${tt} relative rounded-md flex items-center flex-grow">
<input type="text" <input type="text"
class="${tt} w-full outline-none bg-transparent" class="${it} w-full outline-none bg-transparent"
placeholder="${this.placeholder}" placeholder="${this.placeholder}"
aria-autocomplete="list" aria-autocomplete="list"
aria-expanded="${this._isOptionsListVisible}" aria-expanded="${this._isOptionsListVisible}"
aria-controls="options-list-${t}" aria-controls="options-list-${t}"
autocomplete="off" autocorrect="off" autocapitalize="none" spellcheck="false" role="combobox" /> autocomplete="off" autocorrect="off" autocapitalize="none" spellcheck="false" role="combobox" />
</div> </div>
<button type="button" class="${et} ${this.showCreateButton ? "" : "hidden"}" title="Create new item from input">+</button> <button type="button" class="${st} ${this.showCreateButton ? "" : "hidden"}" title="Create new item from input">+</button>
</div> </div>
<ul id="options-list-${t}" role="listbox" class="${it} absolute z-20 w-full max-h-60 overflow-y-auto mt-1 hidden"></ul> <ul id="options-list-${t}" role="listbox" class="${nt} absolute z-20 w-full max-h-60 overflow-y-auto mt-1 hidden"></ul>
<select multiple name="${this.getAttribute("name") || "mss_default_name"}" id="hidden-select-${t}" class="${O}" aria-hidden="true"></select> <select multiple name="${this.getAttribute("name") || "mss_default_name"}" id="hidden-select-${t}" class="${O}" aria-hidden="true"></select>
</div> </div>
`; `;
@@ -1539,7 +1539,7 @@ class ct extends HTMLElement {
}), n; }), n;
} }
_renderSelectedItems() { _renderSelectedItems() {
this.selectedItemsContainer && (this.selectedItemsContainer.innerHTML = "", this._value.length === 0 ? this.selectedItemsContainer.innerHTML = `<span class="${Xt}">Keine Sprachen ausgewählt...</span>` : this._value.forEach((t) => { this.selectedItemsContainer && (this.selectedItemsContainer.innerHTML = "", this._value.length === 0 ? this.selectedItemsContainer.innerHTML = `<span class="${se}">Keine Sprachen ausgewählt...</span>` : this._value.forEach((t) => {
const e = this._createSelectedItemElement(t); const e = this._createSelectedItemElement(t);
e && this.selectedItemsContainer.appendChild(e); e && this.selectedItemsContainer.appendChild(e);
}), this._updateRootElementStateClasses()); }), this._updateRootElementStateClasses());
@@ -1550,7 +1550,7 @@ class ct extends HTMLElement {
const o = this._normalizeText(t.additional_data); const o = this._normalizeText(t.additional_data);
l.textContent = o ? `(${o})` : "", n.dataset.id = t.id, n.setAttribute("aria-selected", String(e === this._highlightedIndex)); l.textContent = o ? `(${o})` : "", n.dataset.id = t.id, n.setAttribute("aria-selected", String(e === this._highlightedIndex));
const h = `option-${this.id || "mss"}-${t.id}`; const h = `option-${this.id || "mss"}-${t.id}`;
return n.id = h, e === this._highlightedIndex && (n.classList.add(st), this.inputElement && this.inputElement.setAttribute("aria-activedescendant", h)), n; return n.id = h, e === this._highlightedIndex && (n.classList.add(at), this.inputElement && this.inputElement.setAttribute("aria-activedescendant", h)), n;
} }
_renderOptionsList() { _renderOptionsList() {
if (!(!this.optionsListElement || !this.inputElement)) { if (!(!this.optionsListElement || !this.inputElement)) {
@@ -1561,7 +1561,7 @@ class ct extends HTMLElement {
const n = this._createOptionElement(e, s); const n = this._createOptionElement(e, s);
this.optionsListElement.appendChild(n); this.optionsListElement.appendChild(n);
}); });
const t = this.optionsListElement.querySelector(`.${st}`); const t = this.optionsListElement.querySelector(`.${at}`);
t && (t.scrollIntoView({ block: "nearest" }), this.inputElement.setAttribute("aria-activedescendant", t.id)); t && (t.scrollIntoView({ block: "nearest" }), this.inputElement.setAttribute("aria-activedescendant", t.id));
} }
this._updateRootElementStateClasses(); this._updateRootElementStateClasses();
@@ -1628,10 +1628,10 @@ class ct extends HTMLElement {
this._isOptionsListVisible = !1, this._highlightedIndex = -1, this.optionsListElement && this._renderOptionsList(); this._isOptionsListVisible = !1, this._highlightedIndex = -1, this.optionsListElement && this._renderOptionsList();
} }
_handleFocus() { _handleFocus() {
this.inputElement.disabled || (this.inputWrapper && this.inputWrapper.classList.add(Z), this.inputElement.value.length > 0 && this._handleInput({ target: this.inputElement }), this._updateRootElementStateClasses()); this.inputElement.disabled || (this.inputWrapper && this.inputWrapper.classList.add(et), this.inputElement.value.length > 0 && this._handleInput({ target: this.inputElement }), this._updateRootElementStateClasses());
} }
_handleBlur() { _handleBlur() {
this.inputWrapper && this.inputWrapper.classList.remove(Z), this._blurTimeout = setTimeout(() => { this.inputWrapper && this.inputWrapper.classList.remove(et), this._blurTimeout = setTimeout(() => {
this.contains(document.activeElement) || this._hideOptionsList(); this.contains(document.activeElement) || this._hideOptionsList();
}, 150); }, 150);
} }
@@ -1660,14 +1660,14 @@ class ct extends HTMLElement {
} }
this._remoteFetchTimeout = setTimeout(() => { this._remoteFetchTimeout = setTimeout(() => {
this._fetchRemoteOptions(t); this._fetchRemoteOptions(t);
}, Yt); }, ne);
} }
_cancelRemoteFetch() { _cancelRemoteFetch() {
this._remoteFetchController && (this._remoteFetchController.abort(), this._remoteFetchController = null); this._remoteFetchController && (this._remoteFetchController.abort(), this._remoteFetchController = null);
} }
async _fetchRemoteOptions(t) { async _fetchRemoteOptions(t) {
if (!this._remoteEndpoint) return; if (!this._remoteEndpoint) return;
this._cancelRemoteFetch(), this.classList.add(nt); this._cancelRemoteFetch(), this.classList.add(rt);
const e = new AbortController(); const e = new AbortController();
this._remoteFetchController = e; this._remoteFetchController = e;
try { try {
@@ -1690,7 +1690,7 @@ class ct extends HTMLElement {
return; return;
console.error("MultiSelectSimple remote fetch error:", s), this._filteredOptions = [], this._isOptionsListVisible = !1, this._renderOptionsList(); console.error("MultiSelectSimple remote fetch error:", s), this._filteredOptions = [], this._isOptionsListVisible = !1, this._renderOptionsList();
} finally { } finally {
this._remoteFetchController === e && (this._remoteFetchController = null), this.classList.remove(nt); this._remoteFetchController === e && (this._remoteFetchController = null), this.classList.remove(rt);
} }
} }
_extractRemoteOptions(t) { _extractRemoteOptions(t) {
@@ -1724,9 +1724,9 @@ class ct extends HTMLElement {
return (s === '"' && n === '"' || s === "'" && n === "'") && (e = e.slice(1, -1).trim(), !e) ? "" : e; return (s === '"' && n === '"' || s === "'" && n === "'") && (e = e.slice(1, -1).trim(), !e) ? "" : e;
} }
} }
p(ct, "formAssociated", !0); p(gt, "formAssociated", !0);
const ie = "rbi-button", se = "rbi-icon"; const oe = "rbi-button", he = "rbi-icon";
class ne extends HTMLElement { class de extends HTMLElement {
constructor() { constructor() {
super(), this.initialStates = /* @__PURE__ */ new Map(), this._controlledElements = [], this.button = null, this.lastOverallModifiedState = null, this.handleInputChange = this.handleInputChange.bind(this), this.handleReset = this.handleReset.bind(this); super(), this.initialStates = /* @__PURE__ */ new Map(), this._controlledElements = [], this.button = null, this.lastOverallModifiedState = null, this.handleInputChange = this.handleInputChange.bind(this), this.handleReset = this.handleReset.bind(this);
} }
@@ -1735,10 +1735,10 @@ class ne extends HTMLElement {
} }
connectedCallback() { connectedCallback() {
const i = ` const i = `
<button type="button" class="${ie} cursor-pointer disabled:cursor-default" aria-label="Reset field"> <button type="button" class="${oe} cursor-pointer disabled:cursor-default" aria-label="Reset field">
<tool-tip position="right"> <tool-tip position="right">
<div class="data-tip">Feld zurücksetzen</div> <div class="data-tip">Feld zurücksetzen</div>
<span class="${se} ri-arrow-go-back-fill"></span> <span class="${he} ri-arrow-go-back-fill"></span>
</tool-tip> </tool-tip>
</button> </button>
`; `;
@@ -1882,32 +1882,32 @@ class ne extends HTMLElement {
this.button.setAttribute("aria-label", i); this.button.setAttribute("aria-label", i);
} }
} }
const d = "hidden", at = "dm-stay", v = "dm-title", rt = "dm-menu-button", ae = "dm-target", re = "data-dm-target", lt = "dm-menu", ot = "dm-menu-item", le = "dm-close-button"; const d = "hidden", lt = "dm-stay", L = "dm-title", ot = "dm-menu-button", ce = "dm-target", ue = "data-dm-target", ht = "dm-menu", dt = "dm-menu-item", me = "dm-close-button";
var T, ut; var T, bt;
class oe extends HTMLElement { class pe extends HTMLElement {
constructor() { constructor() {
super(); super();
_(this, T); _(this, T);
E(this, T, ut).call(this), this.boundHandleClickOutside = this.handleClickOutside.bind(this); E(this, T, bt).call(this), this.boundHandleClickOutside = this.handleClickOutside.bind(this);
} }
connectedCallback() { connectedCallback() {
if (this._target = document.getElementById(this.getAttribute(ae)), this._target || (this._target = this), this._cildren = Array.from(this.children).filter((t) => t.nodeType === Node.ELEMENT_NODE && !t.classList.contains(rt)).map((t) => ({ if (this._target = document.getElementById(this.getAttribute(ce)), this._target || (this._target = this), this._cildren = Array.from(this.children).filter((t) => t.nodeType === Node.ELEMENT_NODE && !t.classList.contains(ot)).map((t) => ({
node: t, node: t,
target: () => { target: () => {
const e = t.getAttribute(re); const e = t.getAttribute(ue);
return e ? document.getElementById(e) || this._target : this._target; return e ? document.getElementById(e) || this._target : this._target;
}, },
stay: () => t.hasAttribute(at) && t.getAttribute(at) == "true", stay: () => t.hasAttribute(lt) && t.getAttribute(lt) == "true",
hidden: () => t.classList.contains(d), hidden: () => t.classList.contains(d),
name: () => { name: () => {
const e = t.querySelector("label"); const e = t.querySelector("label");
return e ? e.innerHTML : t.hasAttribute(v) ? t.getAttribute(v) : ""; return e ? e.innerHTML : t.hasAttribute(L) ? t.getAttribute(L) : "";
}, },
nameText: () => { nameText: () => {
const e = t.querySelector("label"); const e = t.querySelector("label");
return e ? e.textContent.trim() : t.hasAttribute(v) ? t.getAttribute(v) : ""; return e ? e.textContent.trim() : t.hasAttribute(L) ? t.getAttribute(L) : "";
} }
})), this._button = this.querySelector(`.${rt}`), !this._button) { })), this._button = this.querySelector(`.${ot}`), !this._button) {
console.error("DivManagerMenu needs a button element."); console.error("DivManagerMenu needs a button element.");
return; return;
} }
@@ -1916,7 +1916,7 @@ class oe extends HTMLElement {
this.removeChild(t.node); this.removeChild(t.node);
this._button.addEventListener("click", this._toggleMenu.bind(this)), this._button.classList.add("relative"); this._button.addEventListener("click", this._toggleMenu.bind(this)), this._button.classList.add("relative");
for (const t of this._cildren) for (const t of this._cildren)
t.node.querySelectorAll(`.${le}`).forEach((s) => { t.node.querySelectorAll(`.${me}`).forEach((s) => {
s.addEventListener("click", (n) => { s.addEventListener("click", (n) => {
this.hideDiv(n, t.node); this.hideDiv(n, t.node);
}); });
@@ -1997,10 +1997,10 @@ ${t[0].nameText()} hinzufügen`, this._menu = null, this.hideMenu();
this.hideMenu(); this.hideMenu();
return; return;
} }
(!this._menu || !this._button.contains(this._menu)) && (this._button.insertAdjacentHTML("beforeend", `<div class="${lt} absolute hidden"></div>`), this._menu = this._button.querySelector(`.${lt}`)), this._menu.innerHTML = `${t.map((s, n) => ` (!this._menu || !this._button.contains(this._menu)) && (this._button.insertAdjacentHTML("beforeend", `<div class="${ht} absolute hidden"></div>`), this._menu = this._button.querySelector(`.${ht}`)), this._menu.innerHTML = `${t.map((s, n) => `
<button type="button" class="${ot}" dm-itemno="${this._cildren.indexOf(s)}"> <button type="button" class="${dt}" dm-itemno="${this._cildren.indexOf(s)}">
${s.name()} ${s.name()}
</button>`).join("")}`, this._menu.querySelectorAll(`.${ot}`).forEach((s) => { </button>`).join("")}`, this._menu.querySelectorAll(`.${dt}`).forEach((s) => {
s.addEventListener("click", (n) => { s.addEventListener("click", (n) => {
this.showDiv(n, parseInt(s.getAttribute("dm-itemno"))), this.hideMenu(), this.renderButton(); this.showDiv(n, parseInt(s.getAttribute("dm-itemno"))), this.hideMenu(), this.renderButton();
}); });
@@ -2026,16 +2026,16 @@ ${t[0].nameText()} hinzufügen`, this._menu = null, this.hideMenu();
}); });
} }
} }
T = new WeakSet(), ut = function() { T = new WeakSet(), bt = function() {
this._cildren = [], this._rendered = [], this._target = null, this._button = null, this._menu = null, this._originalButtonText = null; this._cildren = [], this._rendered = [], this._target = null, this._button = null, this._menu = null, this._originalButtonText = null;
}; };
const c = "items-row", he = "items-list", de = "items-template", ce = "items-add-button", ue = "items-cancel-button", me = "items-remove-button", pe = "items-edit-button", _e = "items-close-button", fe = "items-summary", ge = "items-edit-panel", ht = "items_removed[]"; const c = "items-row", _e = "items-list", fe = "items-template", ge = "items-add-button", be = "items-cancel-button", Ee = "items-remove-button", Se = "items-edit-button", ve = "items-close-button", Le = "items-summary", Ae = "items-edit-panel", ct = "items_removed[]";
class be extends HTMLElement { class Te extends HTMLElement {
constructor() { constructor() {
super(), this._list = null, this._template = null, this._addButton = null, this._idPrefix = `items-editor-${crypto.randomUUID().slice(0, 8)}`, this._handleAdd = this._onAddClick.bind(this); super(), this._list = null, this._template = null, this._addButton = null, this._idPrefix = `items-editor-${crypto.randomUUID().slice(0, 8)}`, this._handleAdd = this._onAddClick.bind(this);
} }
connectedCallback() { connectedCallback() {
if (this._list = this.querySelector(`.${he}`), this._template = this.querySelector(`template.${de}`), this._addButton = this.querySelector(`.${ce}`), !this._list || !this._template || !this._addButton) { if (this._list = this.querySelector(`.${_e}`), this._template = this.querySelector(`template.${fe}`), this._addButton = this.querySelector(`.${ge}`), !this._list || !this._template || !this._addButton) {
console.error("ItemsEditor: Missing list, template, or add button."); console.error("ItemsEditor: Missing list, template, or add button.");
return; return;
} }
@@ -2063,14 +2063,14 @@ class be extends HTMLElement {
s && this._ensureRemovalInput(s), t.remove(), this._refreshRowIds(); s && this._ensureRemovalInput(s), t.remove(), this._refreshRowIds();
} }
_wireRemoveButtons(i = this) { _wireRemoveButtons(i = this) {
i.querySelectorAll(`.${me}`).forEach((t) => { i.querySelectorAll(`.${Ee}`).forEach((t) => {
t.dataset.itemsBound !== "true" && (t.dataset.itemsBound = "true", t.addEventListener("click", (e) => { t.dataset.itemsBound !== "true" && (t.dataset.itemsBound = "true", t.addEventListener("click", (e) => {
e.preventDefault(), this.removeItem(t); e.preventDefault(), this.removeItem(t);
})); }));
}); });
} }
_wireCancelButtons(i = this) { _wireCancelButtons(i = this) {
i.querySelectorAll(`.${ue}`).forEach((t) => { i.querySelectorAll(`.${be}`).forEach((t) => {
t.dataset.itemsBound !== "true" && (t.dataset.itemsBound = "true", t.addEventListener("click", (e) => { t.dataset.itemsBound !== "true" && (t.dataset.itemsBound = "true", t.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
const s = t.closest(`.${c}`); const s = t.closest(`.${c}`);
@@ -2079,13 +2079,13 @@ class be extends HTMLElement {
}); });
} }
_wireEditButtons(i = this) { _wireEditButtons(i = this) {
i.querySelectorAll(`.${pe}`).forEach((t) => { i.querySelectorAll(`.${Se}`).forEach((t) => {
t.dataset.itemsBound !== "true" && (t.dataset.itemsBound = "true", t.addEventListener("click", (e) => { t.dataset.itemsBound !== "true" && (t.dataset.itemsBound = "true", t.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
const s = t.closest(`.${c}`); const s = t.closest(`.${c}`);
s && this._setRowMode(s, "edit"); s && this._setRowMode(s, "edit");
})); }));
}), i.querySelectorAll(`.${_e}`).forEach((t) => { }), i.querySelectorAll(`.${ve}`).forEach((t) => {
t.dataset.itemsBound !== "true" && (t.dataset.itemsBound = "true", t.addEventListener("click", (e) => { t.dataset.itemsBound !== "true" && (t.dataset.itemsBound = "true", t.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
const s = t.closest(`.${c}`); const s = t.closest(`.${c}`);
@@ -2102,7 +2102,7 @@ class be extends HTMLElement {
this._resetToOriginal(i), this._setRowMode(i, "summary"); this._resetToOriginal(i), this._setRowMode(i, "summary");
} }
_setRowMode(i, t) { _setRowMode(i, t) {
const e = i.querySelector(`.${fe}`), s = i.querySelector(`.${ge}`); const e = i.querySelector(`.${Le}`), s = i.querySelector(`.${Ae}`);
!e || !s || (t === "edit" ? (e.classList.add("hidden"), s.classList.remove("hidden")) : (e.classList.remove("hidden"), s.classList.add("hidden"), this._syncSummary(i))); !e || !s || (t === "edit" ? (e.classList.add("hidden"), s.classList.remove("hidden")) : (e.classList.remove("hidden"), s.classList.add("hidden"), this._syncSummary(i)));
} }
_captureAllOriginals() { _captureAllOriginals() {
@@ -2176,34 +2176,156 @@ class be extends HTMLElement {
return i instanceof HTMLInputElement || i instanceof HTMLTextAreaElement ? i.value.trim() : ""; return i instanceof HTMLInputElement || i instanceof HTMLTextAreaElement ? i.value.trim() : "";
} }
_ensureRemovalInput(i) { _ensureRemovalInput(i) {
if (Array.from(this.querySelectorAll(`input[name="${ht}"]`)).some( if (Array.from(this.querySelectorAll(`input[name="${ct}"]`)).some(
(s) => s.value === i (s) => s.value === i
)) ))
return; return;
const e = document.createElement("input"); const e = document.createElement("input");
e.type = "hidden", e.name = ht, e.value = i, this.appendChild(e); e.type = "hidden", e.name = ct, e.value = i, this.appendChild(e);
} }
} }
const Ee = "filter-list", Se = "scroll-button", Le = "tool-tip", ve = "abbrev-tooltips", Ae = "int-link", Te = "popup-image", Ie = "tab-list", ye = "filter-pill", Ce = "image-reel", we = "multi-select-places", xe = "multi-select-simple", mt = "reset-button", Me = "div-manager", Oe = "items-editor"; const Ie = "ssr-wrapper", ut = "ssr-input", mt = "ssr-list", Ce = "ssr-option", ye = "ssr-option-name", we = "ssr-option-detail", xe = "ssr-option-bio", pt = "ssr-hidden-input", _t = "ssr-clear-button", B = 1, N = 10, Me = 250;
customElements.define(Ae, Ct); class Oe extends HTMLElement {
customElements.define(ve, f); constructor() {
customElements.define(Ee, vt); super(), this._endpoint = "", this._resultKey = "items", this._minChars = B, this._limit = N, this._placeholder = "Search...", this._options = [], this._selected = null, this._fetchTimeout = null, this._fetchController = null, this._listVisible = !1, this._boundHandleInput = this._handleInput.bind(this), this._boundHandleFocus = this._handleFocus.bind(this), this._boundHandleKeyDown = this._handleKeyDown.bind(this), this._boundHandleClear = this._handleClear.bind(this), this._boundHandleClickOutside = this._handleClickOutside.bind(this);
customElements.define(Se, At); }
customElements.define(Le, Tt); static get observedAttributes() {
customElements.define(Te, It); return ["data-endpoint", "data-result-key", "data-minchars", "data-limit", "placeholder", "name"];
customElements.define(Ie, yt); }
customElements.define(ye, Et); connectedCallback() {
customElements.define(Ce, wt); this._render(), this._input = this.querySelector(`.${ut}`), this._list = this.querySelector(`.${mt}`), this._hiddenInput = this.querySelector(`.${pt}`), this._clearButton = this.querySelector(`.${_t}`), this._endpoint = this.getAttribute("data-endpoint") || "", this._resultKey = this.getAttribute("data-result-key") || "items", this._minChars = this._parsePositiveInt(this.getAttribute("data-minchars"), B), this._limit = this._parsePositiveInt(this.getAttribute("data-limit"), N), this._placeholder = this.getAttribute("placeholder") || "Search...", this._input && (this._input.placeholder = this._placeholder, this._input.addEventListener("input", this._boundHandleInput), this._input.addEventListener("focus", this._boundHandleFocus), this._input.addEventListener("keydown", this._boundHandleKeyDown)), this._clearButton && this._clearButton.addEventListener("click", this._boundHandleClear), document.addEventListener("click", this._boundHandleClickOutside);
customElements.define(we, dt); }
customElements.define(xe, ct); disconnectedCallback() {
customElements.define(mt, ne); document.removeEventListener("click", this._boundHandleClickOutside), this._input && (this._input.removeEventListener("input", this._boundHandleInput), this._input.removeEventListener("focus", this._boundHandleFocus), this._input.removeEventListener("keydown", this._boundHandleKeyDown)), this._clearButton && this._clearButton.removeEventListener("click", this._boundHandleClear);
customElements.define(Me, oe); }
customElements.define(Oe, be); attributeChangedCallback(i, t, e) {
function ke() { t !== e && (i === "data-endpoint" && (this._endpoint = e || ""), i === "data-result-key" && (this._resultKey = e || "items"), i === "data-minchars" && (this._minChars = this._parsePositiveInt(e, B)), i === "data-limit" && (this._limit = this._parsePositiveInt(e, N)), i === "placeholder" && (this._placeholder = e || "Search...", this._input && (this._input.placeholder = this._placeholder)), i === "name" && this._hiddenInput && (this._hiddenInput.name = e || ""));
}
_handleInput(i) {
const t = i.target.value.trim();
if (this._selected = null, this._syncHiddenInput(), t.length < this._minChars) {
this._options = [], this._renderOptions(), this._hideList();
return;
}
this._debouncedFetch(t);
}
_handleFocus() {
this._options.length > 0 && this._showList();
}
_handleKeyDown(i) {
i.key === "Escape" && this._hideList();
}
_handleClear(i) {
i.preventDefault(), this._selected = null, this._options = [], this._input && (this._input.value = ""), this._syncHiddenInput(), this._renderOptions(), this._hideList(), this.dispatchEvent(new CustomEvent("ssrchange", { bubbles: !0, detail: { item: null } }));
}
_handleClickOutside(i) {
this.contains(i.target) || this._hideList();
}
_debouncedFetch(i) {
this._fetchTimeout && clearTimeout(this._fetchTimeout), this._fetchTimeout = setTimeout(() => {
this._fetchOptions(i);
}, Me);
}
async _fetchOptions(i) {
if (!this._endpoint)
return;
this._fetchController && this._fetchController.abort(), this._fetchController = new AbortController();
const t = new URL(this._endpoint, window.location.origin);
t.searchParams.set("q", i), this._limit > 0 && t.searchParams.set("limit", String(this._limit));
try {
const e = await fetch(t.toString(), { signal: this._fetchController.signal });
if (!e.ok)
return;
const s = await e.json(), n = Array.isArray(s == null ? void 0 : s[this._resultKey]) ? s[this._resultKey] : [];
this._options = n.filter((r) => r && r.id && r.name), this._renderOptions(), this._options.length > 0 ? this._showList() : this._hideList();
} catch (e) {
if ((e == null ? void 0 : e.name) === "AbortError")
return;
}
}
_renderOptions() {
this._list && (this._list.innerHTML = "", this._options.forEach((i) => {
const t = document.createElement("button");
t.type = "button", t.className = [
Ce,
"w-full text-left px-3 py-2 hover:bg-slate-100 transition-colors"
].join(" ");
const e = document.createElement("div");
if (e.className = [ye, "text-sm font-semibold text-gray-800"].join(" "), e.textContent = i.name, t.appendChild(e), i.detail) {
const s = document.createElement("div");
s.className = [we, "text-xs text-gray-600"].join(" "), s.textContent = i.detail, t.appendChild(s);
}
if (i.bio) {
const s = document.createElement("div");
s.className = [xe, "text-xs text-gray-500"].join(" "), s.textContent = i.bio, t.appendChild(s);
}
t.addEventListener("click", () => {
this._selectOption(i);
}), this._list.appendChild(t);
}));
}
_selectOption(i) {
this._selected = i, this._input && (this._input.value = i.name || ""), this._syncHiddenInput(), this._hideList(), this.dispatchEvent(new CustomEvent("ssrchange", { bubbles: !0, detail: { item: i } })), this.dispatchEvent(new Event("change", { bubbles: !0 }));
}
_syncHiddenInput() {
var i;
this._hiddenInput && (this._hiddenInput.value = ((i = this._selected) == null ? void 0 : i.id) || "");
}
_showList() {
!this._list || this._listVisible || (this._list.classList.remove("hidden"), this._listVisible = !0);
}
_hideList() {
!this._list || !this._listVisible || (this._list.classList.add("hidden"), this._listVisible = !1);
}
_parsePositiveInt(i, t) {
const e = parseInt(i || "", 10);
return Number.isNaN(e) || e <= 0 ? t : e;
}
_render() {
const i = this.getAttribute("name") || "";
this.innerHTML = `
<div class="${Ie} relative">
<div class="flex items-center gap-2">
<input
type="text"
class="${ut} inputinput w-full"
autocomplete="off"
autocorrect="off"
autocapitalize="none"
spellcheck="false"
placeholder="${this._placeholder}"
/>
<button type="button" class="${_t} text-sm text-gray-600 hover:text-gray-900">
<i class="ri-close-line"></i>
</button>
</div>
<input type="hidden" class="${pt}" name="${i}" value="" />
<div class="${mt} absolute left-0 right-0 mt-1 border border-stone-200 rounded-xs bg-white shadow-sm z-10 hidden max-h-64 overflow-auto"></div>
</div>
`;
}
}
const ke = "filter-list", Re = "scroll-button", Be = "tool-tip", Ne = "abbrev-tooltips", $e = "int-link", De = "popup-image", Pe = "tab-list", He = "filter-pill", qe = "image-reel", Fe = "multi-select-places", Ue = "multi-select-simple", Ke = "single-select-remote", Et = "reset-button", ze = "div-manager", Ve = "items-editor";
customElements.define($e, Rt);
customElements.define(Ne, f);
customElements.define(ke, wt);
customElements.define(Re, xt);
customElements.define(Be, Mt);
customElements.define(De, Ot);
customElements.define(Pe, kt);
customElements.define(He, It);
customElements.define(qe, Bt);
customElements.define(Fe, ft);
customElements.define(Ue, gt);
customElements.define(Ke, Oe);
customElements.define(Et, de);
customElements.define(ze, pe);
customElements.define(Ve, Te);
function Ge() {
const a = window.location.pathname, i = window.location.search, t = a + i; const a = window.location.pathname, i = window.location.search, t = a + i;
return encodeURIComponent(t); return encodeURIComponent(t);
} }
function Re(a = 5e3, i = 100) { function We(a = 5e3, i = 100) {
return new Promise((t, e) => { return new Promise((t, e) => {
let s = 0; let s = 0;
const n = setInterval(() => { const n = setInterval(() => {
@@ -2211,8 +2333,8 @@ function Re(a = 5e3, i = 100) {
}, i); }, i);
}); });
} }
async function Be(a) { async function je(a) {
const i = await Re(), t = document.getElementById("qr"); const i = await We(), t = document.getElementById("qr");
t && (t.innerHTML = "", t.classList.add("hidden"), new i(t, { t && (t.innerHTML = "", t.classList.add("hidden"), new i(t, {
text: a, text: a,
width: 1280, width: 1280,
@@ -2224,7 +2346,7 @@ async function Be(a) {
t.classList.remove("hidden"); t.classList.remove("hidden");
}, 20)); }, 20));
} }
function $e(a) { function Qe(a) {
a && (a.addEventListener("focus", (i) => { a && (a.addEventListener("focus", (i) => {
i.preventDefault(), a.select(); i.preventDefault(), a.select();
}), a.addEventListener("mousedown", (i) => { }), a.addEventListener("mousedown", (i) => {
@@ -2237,7 +2359,7 @@ function $e(a) {
a.select(); a.select();
})); }));
} }
function Ne() { function Je() {
document.body.addEventListener("htmx:responseError", function(a) { document.body.addEventListener("htmx:responseError", function(a) {
const i = a.detail.requestConfig; const i = a.detail.requestConfig;
if (i.boosted) { if (i.boosted) {
@@ -2247,7 +2369,7 @@ function Ne() {
} }
}); });
} }
function De(a, i) { function Xe(a, i) {
if (!(a instanceof HTMLElement)) { if (!(a instanceof HTMLElement)) {
console.warn("Target must be an HTMLElement."); console.warn("Target must be an HTMLElement.");
return; return;
@@ -2256,7 +2378,7 @@ function De(a, i) {
console.warn("Action must be a function."); console.warn("Action must be a function.");
return; return;
} }
const t = a.querySelectorAll(mt); const t = a.querySelectorAll(Et);
a.addEventListener("rbichange", (e) => { a.addEventListener("rbichange", (e) => {
for (const s of t) for (const s of t)
if (s.isCurrentlyModified()) { if (s.isCurrentlyModified()) {
@@ -2273,10 +2395,10 @@ function I(a) {
} }
a.style.height = "auto", a.style.height = `${a.scrollHeight}px`; a.style.height = "auto", a.style.height = `${a.scrollHeight}px`;
} }
function pt(a) { function St(a) {
a.key === "Enter" && a.preventDefault(); a.key === "Enter" && a.preventDefault();
} }
function _t(a) { function vt(a) {
if (!(a instanceof HTMLTextAreaElement)) { if (!(a instanceof HTMLTextAreaElement)) {
console.warn("HookupTextareaAutoResize: Provided element is not a textarea."); console.warn("HookupTextareaAutoResize: Provided element is not a textarea.");
return; return;
@@ -2285,7 +2407,7 @@ function _t(a) {
I(a); I(a);
}); });
} }
function Pe(a) { function Ye(a) {
if (!(a instanceof HTMLTextAreaElement)) { if (!(a instanceof HTMLTextAreaElement)) {
console.warn("DisconnectTextareaAutoResize: Provided element is not a textarea."); console.warn("DisconnectTextareaAutoResize: Provided element is not a textarea.");
return; return;
@@ -2294,54 +2416,55 @@ function Pe(a) {
I(a); I(a);
}); });
} }
function He(a) { function Ze(a) {
!(a instanceof HTMLTextAreaElement) && a.classList.contains("no-enter") || a.addEventListener("keydown", pt); !(a instanceof HTMLTextAreaElement) && a.classList.contains("no-enter") || a.addEventListener("keydown", St);
} }
function qe(a) { function ti(a) {
!(a instanceof HTMLTextAreaElement) && a.classList.contains("no-enter") || a.removeEventListener("keydown", pt); !(a instanceof HTMLTextAreaElement) && a.classList.contains("no-enter") || a.removeEventListener("keydown", St);
} }
function Ue(a, i) { function ei(a, i) {
for (const t of a) for (const t of a)
if (t.type === "childList") { if (t.type === "childList") {
for (const e of t.addedNodes) for (const e of t.addedNodes)
e.nodeType === Node.ELEMENT_NODE && e.matches("textarea") && (_t(e), I(e)); e.nodeType === Node.ELEMENT_NODE && e.matches("textarea") && (vt(e), I(e));
for (const e of t.removedNodes) for (const e of t.removedNodes)
e.nodeType === Node.ELEMENT_NODE && e.matches("textarea") && (qe(e), Pe(e)); e.nodeType === Node.ELEMENT_NODE && e.matches("textarea") && (ti(e), Ye(e));
} }
} }
function Fe(a) { function ii(a) {
if (!(a instanceof HTMLFormElement)) { if (!(a instanceof HTMLFormElement)) {
console.warn("FormLoad: Provided element is not a form."); console.warn("FormLoad: Provided element is not a form.");
return; return;
} }
const i = document.querySelectorAll("textarea"); const i = document.querySelectorAll("textarea");
for (const s of i) for (const s of i)
_t(s), I(s); vt(s), I(s);
const t = document.querySelectorAll("textarea.no-enter"); const t = document.querySelectorAll("textarea.no-enter");
for (const s of t) for (const s of t)
He(s); Ze(s);
new MutationObserver(Ue).observe(a, { new MutationObserver(ei).observe(a, {
childList: !0, childList: !0,
subtree: !0 subtree: !0
}); });
} }
window.ShowBoostedErrors = Ne; window.ShowBoostedErrors = Je;
window.GenQRCode = Be; window.GenQRCode = je;
window.SelectableInput = $e; window.SelectableInput = Qe;
window.PathPlusQuery = ke; window.PathPlusQuery = Ge;
window.HookupRBChange = De; window.HookupRBChange = Xe;
window.FormLoad = Fe; window.FormLoad = ii;
export { export {
f as AbbreviationTooltips, f as AbbreviationTooltips,
vt as FilterList, wt as FilterList,
Et as FilterPill, It as FilterPill,
wt as ImageReel, Bt as ImageReel,
Ct as IntLink, Rt as IntLink,
be as ItemsEditor, Te as ItemsEditor,
dt as MultiSelectRole, ft as MultiSelectRole,
ct as MultiSelectSimple, gt as MultiSelectSimple,
It as PopupImage, Ot as PopupImage,
At as ScrollButton, xt as ScrollButton,
yt as TabList, Oe as SingleSelectRemote,
Tt as ToolTip kt as TabList,
Mt as ToolTip
}; };

File diff suppressed because one or more lines are too long

View File

@@ -274,10 +274,15 @@ type AlmanachResult struct {
</div> </div>
<div class="mt-3"> <div class="mt-3">
<div class="flex items-center justify-between gap-4">
<div class="flex items-center gap-2 text-lg font-bold text-gray-700"> <div class="flex items-center gap-2 text-lg font-bold text-gray-700">
<i class="ri-links-line"></i> <i class="ri-links-line"></i>
<span>Normdaten &amp; Verknüpfungen</span> <span>Normdaten &amp; Verknüpfungen</span>
</div> </div>
<button type="button" id="agents-add-toggle" class="text-gray-700 hover:text-gray-900">
<i class="ri-add-line"></i> Akteur hinzufügen
</button>
</div>
<hr class="border-slate-400 mt-2 mb-3" /> <hr class="border-slate-400 mt-2 mb-3" />
<div class="flex flex-col gap-4 mt-4"> <div class="flex flex-col gap-4 mt-4">
<div class="inputwrapper"> <div class="inputwrapper">
@@ -316,6 +321,238 @@ type AlmanachResult struct {
} }
</script> </script>
<div class="mt-3">
<div class="text-base font-semibold text-gray-700">
<span>Personen &amp; Körperschaften</span>
</div>
<div class="flex flex-col gap-2 mt-4">
{{- if $model.result.EntriesAgents -}}
{{- range $i, $r := $model.result.EntriesAgents -}}
{{- $a := index $model.result.Agents $r.Agent -}}
<div class="border border-stone-200 rounded-xs bg-stone-50 px-3 py-2">
<div class="grid grid-cols-[1fr_14rem_8rem_7rem] gap-3 items-center">
<div class="flex flex-col min-w-0">
{{- if $a -}}
<a href="/person/{{ $a.Id }}" class="text-base font-bold text-gray-800 no-underline hover:text-slate-900 truncate">
{{- $a.Name -}}
</a>
{{- if $a.BiographicalData -}}
<div class="text-xs text-gray-600 truncate">{{- $a.BiographicalData -}}</div>
{{- end -}}
{{- else -}}
<div class="text-base font-bold text-gray-800">Unbekannte Person</div>
{{- end -}}
</div>
<div>
<select name="entries_agents_type[{{ $r.Id }}]" id="entries_agents_type_{{ $r.Id }}" autocomplete="off" class="inputselect font-bold w-full">
{{- range $t := $model.agent_relations -}}
<option value="{{- $t -}}" {{ if eq $r.Type $t }}selected{{ end }}>{{- $t -}}</option>
{{- end -}}
</select>
</div>
<div class="flex items-center gap-2">
<input
type="checkbox"
name="entries_agents_uncertain[{{ $r.Id }}]"
id="entries_agents_uncertain_{{ $r.Id }}"
{{ if $r.Uncertain }}checked{{ end }} />
<label for="entries_agents_uncertain_{{ $r.Id }}" class="text-sm text-gray-700">Unsicher</label>
</div>
<div class="flex justify-end">
<button
type="button"
class="text-sm text-red-700 hover:text-red-900"
data-delete-toggle="entries_agents_delete_{{ $r.Id }}">
<i class="ri-delete-bin-line mr-1"></i> Entfernen
</button>
<input type="checkbox" class="hidden" name="entries_agents_delete[{{ $r.Id }}]" id="entries_agents_delete_{{ $r.Id }}" />
</div>
</div>
<input type="hidden" name="entries_agents_id[{{ $r.Id }}]" value="{{ $r.Id }}" />
<input type="hidden" name="entries_agents_agent[{{ $r.Id }}]" value="{{ $r.Agent }}" />
</div>
{{- end -}}
{{- else -}}
<div class="text-sm text-gray-600">Keine Personen verknüpft.</div>
{{- end -}}
</div>
<div id="agents-add-row" class="mt-2"></div>
<div id="agents-add-panel" class="mt-2 hidden">
<div class="border border-stone-200 rounded-xs bg-stone-50 px-3 py-2">
<div class="grid grid-cols-[1fr_14rem_8rem_7rem] gap-3 items-center">
<div class="min-w-0">
<label for="agents-add-select" class="sr-only">Akteur suchen</label>
<single-select-remote
id="agents-add-select"
name="entries_agents_new_id"
placeholder="Akteur suchen..."
data-endpoint="/api/agents/search"
data-result-key="agents"
data-minchars="1"
data-limit="15">
</single-select-remote>
</div>
<div>
<label for="entries_agents_new_type" class="sr-only">Beziehung</label>
<select name="entries_agents_new_type" id="entries_agents_new_type" autocomplete="off" class="inputselect font-bold w-full">
{{- range $t := $model.agent_relations -}}
<option value="{{- $t -}}">{{- $t -}}</option>
{{- end -}}
</select>
</div>
<div class="flex items-center gap-2">
<input type="checkbox" name="entries_agents_new_uncertain" id="entries_agents_new_uncertain" />
<label for="entries_agents_new_uncertain" class="text-sm text-gray-700">Unsicher</label>
</div>
<div class="flex justify-end">
<div class="flex items-center gap-3 text-lg">
<button type="button" class="text-gray-700 hover:text-gray-900" id="agents-add-apply" aria-label="Akteur hinzufügen">
<i class="ri-check-line"></i>
</button>
<button type="button" class="text-gray-700 hover:text-gray-900" id="agents-add-close" aria-label="Ausblenden">
<i class="ri-close-line"></i>
</button>
</div>
</div>
</div>
<div id="agents-add-error" class="text-xs text-red-700 mt-2 hidden">Bitte Akteur auswählen.</div>
</div>
</div>
<script type="module">
const agentsAddToggle = document.getElementById("agents-add-toggle");
const agentsAddPanel = document.getElementById("agents-add-panel");
const agentsAddClose = document.getElementById("agents-add-close");
const agentsAddApply = document.getElementById("agents-add-apply");
const agentsAddError = document.getElementById("agents-add-error");
const agentsAddRow = document.getElementById("agents-add-row");
const agentsAddSelect = document.getElementById("agents-add-select");
const relationSelect = document.getElementById("entries_agents_new_type");
const uncertainCheckbox = document.getElementById("entries_agents_new_uncertain");
let pendingAgent = null;
if (agentsAddToggle && agentsAddPanel) {
agentsAddToggle.addEventListener("click", () => {
agentsAddPanel.classList.toggle("hidden");
});
}
if (agentsAddClose && agentsAddPanel) {
agentsAddClose.addEventListener("click", () => {
agentsAddPanel.classList.add("hidden");
});
}
const clearAddPanel = () => {
if (agentsAddSelect) {
const clearButton = agentsAddSelect.querySelector(".ssr-clear-button");
if (clearButton) {
clearButton.click();
}
}
if (relationSelect) {
relationSelect.selectedIndex = 0;
}
if (uncertainCheckbox) {
uncertainCheckbox.checked = false;
}
};
const removeNewRow = () => {
if (agentsAddRow) {
agentsAddRow.innerHTML = "";
}
pendingAgent = null;
clearAddPanel();
};
if (agentsAddApply && agentsAddPanel) {
agentsAddApply.addEventListener("click", () => {
const idInput = agentsAddPanel.querySelector("input[name='entries_agents_new_id']");
const hasSelection = idInput && idInput.value.trim().length > 0;
if (!hasSelection) {
if (agentsAddError) {
agentsAddError.classList.remove("hidden");
}
return;
}
if (agentsAddError) {
agentsAddError.classList.add("hidden");
}
if (!pendingAgent || !agentsAddRow) {
return;
}
const row = document.createElement("div");
row.className = "border border-stone-200 rounded-xs bg-stone-50 px-3 py-2";
row.innerHTML = `
<div class="grid grid-cols-[1fr_14rem_8rem_7rem] gap-3 items-center">
<div class="flex flex-col min-w-0">
<div class="text-base font-bold text-gray-800 truncate">
<a href="/person/${pendingAgent.id}" class="no-underline hover:text-slate-900">
${pendingAgent.name || ""}
</a>
<em class="text-sm text-gray-600 ml-1">(Neu)</em>
</div>
${pendingAgent.bio ? `<div class="text-xs text-gray-600 truncate">${pendingAgent.bio}</div>` : ""}
</div>
<div>
<select name="entries_agents_new_type" class="inputselect font-bold w-full"></select>
</div>
<div class="flex items-center gap-2">
<input type="checkbox" name="entries_agents_new_uncertain" id="entries_agents_new_uncertain_row" />
<label for="entries_agents_new_uncertain_row" class="text-sm text-gray-700">Unsicher</label>
</div>
<div class="flex justify-end">
<button type="button" class="text-sm text-red-700 hover:text-red-900" data-new-delete>
<i class="ri-delete-bin-line mr-1"></i> Entfernen
</button>
</div>
</div>
<input type="hidden" name="entries_agents_new_id" value="${pendingAgent.id}" />
`;
const rowSelect = row.querySelector("select[name='entries_agents_new_type']");
if (rowSelect && relationSelect) {
rowSelect.innerHTML = relationSelect.innerHTML;
rowSelect.value = relationSelect.value;
}
const rowUncertain = row.querySelector("input[name='entries_agents_new_uncertain']");
if (rowUncertain && uncertainCheckbox) {
rowUncertain.checked = uncertainCheckbox.checked;
}
const deleteButton = row.querySelector("[data-new-delete]");
if (deleteButton) {
deleteButton.addEventListener("click", () => {
removeNewRow();
if (agentsAddPanel) {
agentsAddPanel.classList.remove("hidden");
}
});
}
agentsAddRow.innerHTML = "";
agentsAddRow.appendChild(row);
agentsAddPanel.classList.add("hidden");
clearAddPanel();
});
}
if (agentsAddSelect) {
agentsAddSelect.addEventListener("ssrchange", (event) => {
pendingAgent = event.detail?.item || null;
if (pendingAgent && agentsAddError) {
agentsAddError.classList.add("hidden");
}
});
}
document.querySelectorAll("[data-delete-toggle]").forEach((button) => {
button.addEventListener("click", () => {
const targetId = button.getAttribute("data-delete-toggle");
const checkbox = document.getElementById(targetId);
if (!checkbox) {
return;
}
checkbox.checked = !checkbox.checked;
button.classList.toggle("opacity-60", checkbox.checked);
});
});
</script>
</div>
</div> </div>
<!-- End Left Column --> <!-- End Left Column -->

View File

@@ -15,6 +15,7 @@ import { MultiSelectSimple } from "./multi-select-simple.js";
import { ResetButton } from "./reset-button.js"; import { ResetButton } from "./reset-button.js";
import { DivManager } from "./div-menu.js"; import { DivManager } from "./div-menu.js";
import { ItemsEditor } from "./items-editor.js"; import { ItemsEditor } from "./items-editor.js";
import { SingleSelectRemote } from "./single-select-remote.js";
const FILTER_LIST_ELEMENT = "filter-list"; const FILTER_LIST_ELEMENT = "filter-list";
const SCROLL_BUTTON_ELEMENT = "scroll-button"; const SCROLL_BUTTON_ELEMENT = "scroll-button";
@@ -27,6 +28,7 @@ const FILTER_PILL_ELEMENT = "filter-pill";
const IMAGE_REEL_ELEMENT = "image-reel"; const IMAGE_REEL_ELEMENT = "image-reel";
const MULTI_SELECT_ROLE_ELEMENT = "multi-select-places"; const MULTI_SELECT_ROLE_ELEMENT = "multi-select-places";
const MULTI_SELECT_SIMPLE_ELEMENT = "multi-select-simple"; const MULTI_SELECT_SIMPLE_ELEMENT = "multi-select-simple";
const SINGLE_SELECT_REMOTE_ELEMENT = "single-select-remote";
const RESET_BUTTON_ELEMENT = "reset-button"; const RESET_BUTTON_ELEMENT = "reset-button";
const DIV_MANAGER_ELEMENT = "div-manager"; const DIV_MANAGER_ELEMENT = "div-manager";
const ITEMS_EDITOR_ELEMENT = "items-editor"; const ITEMS_EDITOR_ELEMENT = "items-editor";
@@ -42,6 +44,7 @@ customElements.define(FILTER_PILL_ELEMENT, FilterPill);
customElements.define(IMAGE_REEL_ELEMENT, ImageReel); customElements.define(IMAGE_REEL_ELEMENT, ImageReel);
customElements.define(MULTI_SELECT_ROLE_ELEMENT, MultiSelectRole); customElements.define(MULTI_SELECT_ROLE_ELEMENT, MultiSelectRole);
customElements.define(MULTI_SELECT_SIMPLE_ELEMENT, MultiSelectSimple); customElements.define(MULTI_SELECT_SIMPLE_ELEMENT, MultiSelectSimple);
customElements.define(SINGLE_SELECT_REMOTE_ELEMENT, SingleSelectRemote);
customElements.define(RESET_BUTTON_ELEMENT, ResetButton); customElements.define(RESET_BUTTON_ELEMENT, ResetButton);
customElements.define(DIV_MANAGER_ELEMENT, DivManager); customElements.define(DIV_MANAGER_ELEMENT, DivManager);
customElements.define(ITEMS_EDITOR_ELEMENT, ItemsEditor); customElements.define(ITEMS_EDITOR_ELEMENT, ItemsEditor);
@@ -280,4 +283,4 @@ window.PathPlusQuery = PathPlusQuery;
window.HookupRBChange = HookupRBChange; window.HookupRBChange = HookupRBChange;
window.FormLoad = FormLoad; window.FormLoad = FormLoad;
export { FilterList, ScrollButton, AbbreviationTooltips, MultiSelectSimple, MultiSelectRole, ToolTip, PopupImage, TabList, FilterPill, ImageReel, IntLink, ItemsEditor }; export { FilterList, ScrollButton, AbbreviationTooltips, MultiSelectSimple, MultiSelectRole, ToolTip, PopupImage, TabList, FilterPill, ImageReel, IntLink, ItemsEditor, SingleSelectRemote };

View File

@@ -0,0 +1,286 @@
const SSR_WRAPPER_CLASS = "ssr-wrapper";
const SSR_INPUT_CLASS = "ssr-input";
const SSR_LIST_CLASS = "ssr-list";
const SSR_OPTION_CLASS = "ssr-option";
const SSR_OPTION_NAME_CLASS = "ssr-option-name";
const SSR_OPTION_DETAIL_CLASS = "ssr-option-detail";
const SSR_OPTION_BIO_CLASS = "ssr-option-bio";
const SSR_HIDDEN_INPUT_CLASS = "ssr-hidden-input";
const SSR_CLEAR_BUTTON_CLASS = "ssr-clear-button";
const SSR_DEFAULT_MIN_CHARS = 1;
const SSR_DEFAULT_LIMIT = 10;
const SSR_FETCH_DEBOUNCE_MS = 250;
export class SingleSelectRemote extends HTMLElement {
constructor() {
super();
this._endpoint = "";
this._resultKey = "items";
this._minChars = SSR_DEFAULT_MIN_CHARS;
this._limit = SSR_DEFAULT_LIMIT;
this._placeholder = "Search...";
this._options = [];
this._selected = null;
this._fetchTimeout = null;
this._fetchController = null;
this._listVisible = false;
this._boundHandleInput = this._handleInput.bind(this);
this._boundHandleFocus = this._handleFocus.bind(this);
this._boundHandleKeyDown = this._handleKeyDown.bind(this);
this._boundHandleClear = this._handleClear.bind(this);
this._boundHandleClickOutside = this._handleClickOutside.bind(this);
}
static get observedAttributes() {
return ["data-endpoint", "data-result-key", "data-minchars", "data-limit", "placeholder", "name"];
}
connectedCallback() {
this._render();
this._input = this.querySelector(`.${SSR_INPUT_CLASS}`);
this._list = this.querySelector(`.${SSR_LIST_CLASS}`);
this._hiddenInput = this.querySelector(`.${SSR_HIDDEN_INPUT_CLASS}`);
this._clearButton = this.querySelector(`.${SSR_CLEAR_BUTTON_CLASS}`);
this._endpoint = this.getAttribute("data-endpoint") || "";
this._resultKey = this.getAttribute("data-result-key") || "items";
this._minChars = this._parsePositiveInt(this.getAttribute("data-minchars"), SSR_DEFAULT_MIN_CHARS);
this._limit = this._parsePositiveInt(this.getAttribute("data-limit"), SSR_DEFAULT_LIMIT);
this._placeholder = this.getAttribute("placeholder") || "Search...";
if (this._input) {
this._input.placeholder = this._placeholder;
this._input.addEventListener("input", this._boundHandleInput);
this._input.addEventListener("focus", this._boundHandleFocus);
this._input.addEventListener("keydown", this._boundHandleKeyDown);
}
if (this._clearButton) {
this._clearButton.addEventListener("click", this._boundHandleClear);
}
document.addEventListener("click", this._boundHandleClickOutside);
}
disconnectedCallback() {
document.removeEventListener("click", this._boundHandleClickOutside);
if (this._input) {
this._input.removeEventListener("input", this._boundHandleInput);
this._input.removeEventListener("focus", this._boundHandleFocus);
this._input.removeEventListener("keydown", this._boundHandleKeyDown);
}
if (this._clearButton) {
this._clearButton.removeEventListener("click", this._boundHandleClear);
}
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue === newValue) {
return;
}
if (name === "data-endpoint") this._endpoint = newValue || "";
if (name === "data-result-key") this._resultKey = newValue || "items";
if (name === "data-minchars") this._minChars = this._parsePositiveInt(newValue, SSR_DEFAULT_MIN_CHARS);
if (name === "data-limit") this._limit = this._parsePositiveInt(newValue, SSR_DEFAULT_LIMIT);
if (name === "placeholder") {
this._placeholder = newValue || "Search...";
if (this._input) this._input.placeholder = this._placeholder;
}
if (name === "name" && this._hiddenInput) this._hiddenInput.name = newValue || "";
}
_handleInput(event) {
const value = event.target.value.trim();
this._selected = null;
this._syncHiddenInput();
if (value.length < this._minChars) {
this._options = [];
this._renderOptions();
this._hideList();
return;
}
this._debouncedFetch(value);
}
_handleFocus() {
if (this._options.length > 0) {
this._showList();
}
}
_handleKeyDown(event) {
if (event.key === "Escape") {
this._hideList();
}
}
_handleClear(event) {
event.preventDefault();
this._selected = null;
this._options = [];
if (this._input) this._input.value = "";
this._syncHiddenInput();
this._renderOptions();
this._hideList();
this.dispatchEvent(new CustomEvent("ssrchange", { bubbles: true, detail: { item: null } }));
}
_handleClickOutside(event) {
if (!this.contains(event.target)) {
this._hideList();
}
}
_debouncedFetch(query) {
if (this._fetchTimeout) {
clearTimeout(this._fetchTimeout);
}
this._fetchTimeout = setTimeout(() => {
this._fetchOptions(query);
}, SSR_FETCH_DEBOUNCE_MS);
}
async _fetchOptions(query) {
if (!this._endpoint) {
return;
}
if (this._fetchController) {
this._fetchController.abort();
}
this._fetchController = new AbortController();
const url = new URL(this._endpoint, window.location.origin);
url.searchParams.set("q", query);
if (this._limit > 0) {
url.searchParams.set("limit", String(this._limit));
}
try {
const resp = await fetch(url.toString(), { signal: this._fetchController.signal });
if (!resp.ok) {
return;
}
const data = await resp.json();
const items = Array.isArray(data?.[this._resultKey]) ? data[this._resultKey] : [];
this._options = items.filter((item) => item && item.id && item.name);
this._renderOptions();
if (this._options.length > 0) {
this._showList();
} else {
this._hideList();
}
} catch (err) {
if (err?.name === "AbortError") {
return;
}
}
}
_renderOptions() {
if (!this._list) {
return;
}
this._list.innerHTML = "";
this._options.forEach((item) => {
const option = document.createElement("button");
option.type = "button";
option.className = [
SSR_OPTION_CLASS,
"w-full text-left px-3 py-2 hover:bg-slate-100 transition-colors",
].join(" ");
const nameEl = document.createElement("div");
nameEl.className = [SSR_OPTION_NAME_CLASS, "text-sm font-semibold text-gray-800"].join(" ");
nameEl.textContent = item.name;
option.appendChild(nameEl);
if (item.detail) {
const detailEl = document.createElement("div");
detailEl.className = [SSR_OPTION_DETAIL_CLASS, "text-xs text-gray-600"].join(" ");
detailEl.textContent = item.detail;
option.appendChild(detailEl);
}
if (item.bio) {
const bioEl = document.createElement("div");
bioEl.className = [SSR_OPTION_BIO_CLASS, "text-xs text-gray-500"].join(" ");
bioEl.textContent = item.bio;
option.appendChild(bioEl);
}
option.addEventListener("click", () => {
this._selectOption(item);
});
this._list.appendChild(option);
});
}
_selectOption(item) {
this._selected = item;
if (this._input) {
this._input.value = item.name || "";
}
this._syncHiddenInput();
this._hideList();
this.dispatchEvent(new CustomEvent("ssrchange", { bubbles: true, detail: { item } }));
this.dispatchEvent(new Event("change", { bubbles: true }));
}
_syncHiddenInput() {
if (!this._hiddenInput) {
return;
}
this._hiddenInput.value = this._selected?.id || "";
}
_showList() {
if (!this._list || this._listVisible) {
return;
}
this._list.classList.remove("hidden");
this._listVisible = true;
}
_hideList() {
if (!this._list || !this._listVisible) {
return;
}
this._list.classList.add("hidden");
this._listVisible = false;
}
_parsePositiveInt(value, fallback) {
const parsed = parseInt(value || "", 10);
if (Number.isNaN(parsed) || parsed <= 0) {
return fallback;
}
return parsed;
}
_render() {
const inputName = this.getAttribute("name") || "";
this.innerHTML = `
<div class="${SSR_WRAPPER_CLASS} relative">
<div class="flex items-center gap-2">
<input
type="text"
class="${SSR_INPUT_CLASS} inputinput w-full"
autocomplete="off"
autocorrect="off"
autocapitalize="none"
spellcheck="false"
placeholder="${this._placeholder}"
/>
<button type="button" class="${SSR_CLEAR_BUTTON_CLASS} text-sm text-gray-600 hover:text-gray-900">
<i class="ri-close-line"></i>
</button>
</div>
<input type="hidden" class="${SSR_HIDDEN_INPUT_CLASS}" name="${inputName}" value="" />
<div class="${SSR_LIST_CLASS} absolute left-0 right-0 mt-1 border border-stone-200 rounded-xs bg-white shadow-sm z-10 hidden max-h-64 overflow-auto"></div>
</div>
`;
}
}