Frontend annoyances

This commit is contained in:
Simon Martens
2026-01-09 08:28:16 +01:00
parent 492d398d27
commit a08a7e5710
9 changed files with 717 additions and 473 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -288,7 +288,7 @@ type AlmanachResult struct {
<i class="ri-add-line"></i> Reihe hinzufügen
</button>
<button type="button" id="agents-add-toggle" class="text-gray-700 hover:text-gray-900">
<i class="ri-add-line"></i> Akteur hinzufügen
<i class="ri-add-line"></i> Person hinzufügen
</button>
</div>
</div>
@@ -297,19 +297,19 @@ type AlmanachResult struct {
<relations-editor data-prefix="entries_series" data-link-base="/reihe/" data-new-label="(Neu)" data-add-toggle-id="series-add-toggle">
<div class="inputwrapper">
<label class="inputlabel" for="series-section">Reihen</label>
<div id="series-section" class="flex flex-col gap-2 mt-2">
<div id="series-section" class="rel-section-container">
{{- if $model.result.Series -}}
{{- range $i, $s := $model.result.Series -}}
{{- $rel := index $model.result.EntriesSeries $s.Id -}}
{{- if $rel -}}
<div data-rel-row class="entries-series-row border border-stone-200 rounded-xs bg-stone-50 px-3 py-2">
<div class="grid grid-cols-[1fr_14rem_5.5rem_7rem] gap-3 items-center">
<div data-rel-strike class="relation-strike flex flex-col min-w-0">
<a data-rel-link href="/reihe/{{ $s.MusenalmID }}" class="text-base text-gray-800 no-underline hover:text-slate-900 truncate">
<div data-rel-row class="entries-series-row rel-row">
<div class="rel-grid">
<div data-rel-strike class="relation-strike rel-name-col">
<a data-rel-link href="/reihe/{{ $s.MusenalmID }}" class="rel-link">
<span data-rel-name>{{- $s.Title -}}</span>
</a>
{{- if $s.Pseudonyms -}}
<div data-rel-detail-container class="text-xs text-gray-600 truncate"><span data-rel-detail>{{- $s.Pseudonyms -}}</span></div>
<div data-rel-detail-container class="rel-detail"><span data-rel-detail>{{- $s.Pseudonyms -}}</span></div>
{{- end -}}
</div>
<div data-rel-strike class="relation-strike">
@@ -319,15 +319,15 @@ type AlmanachResult struct {
{{- end -}}
</select>
</div>
<div data-rel-strike class="relation-strike flex items-center gap-2">
<div data-rel-strike class="relation-strike rel-uncertain-container">
<input
type="checkbox"
name="entries_series_uncertain[{{ $rel.Id }}]"
id="entries_series_uncertain_{{ $rel.Id }}"
{{ if $rel.Uncertain }}checked{{ end }} />
<label for="entries_series_uncertain_{{ $rel.Id }}" class="text-sm text-gray-700">Unsicher</label>
<label for="entries_series_uncertain_{{ $rel.Id }}" class="rel-uncertain-label">Unsicher</label>
</div>
<div class="flex justify-end">
<div class="rel-button-container">
<button
type="button"
class="text-sm"
@@ -344,13 +344,13 @@ type AlmanachResult struct {
{{- end -}}
{{- end -}}
{{- else -}}
<div class="text-sm text-gray-600">Keine Reihen verknüpft.</div>
<div class="rel-empty-text">Keine Reihen verknüpft.</div>
{{- end -}}
</div>
<div data-role="relation-add-row" class="mt-2"></div>
<div data-role="relation-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_5.5rem_7rem] gap-3 items-center">
<div data-role="relation-add-row" class="mt-2 px-2"></div>
<div data-role="relation-add-panel" class="mt-2 px-2 hidden">
<div class="rel-row">
<div class="rel-grid">
<div class="min-w-0">
<label for="series-add-select" class="sr-only">Reihe suchen</label>
<single-select-remote
@@ -372,11 +372,11 @@ type AlmanachResult struct {
{{- end -}}
</select>
</div>
<div class="flex items-center gap-2">
<div class="rel-uncertain-container">
<input data-role="relation-uncertain" type="checkbox" name="entries_series_new_uncertain" id="entries_series_new_uncertain" />
<label for="entries_series_new_uncertain" class="text-sm text-gray-700">Unsicher</label>
<label for="entries_series_new_uncertain" class="rel-uncertain-label">Unsicher</label>
</div>
<div class="flex justify-end">
<div class="rel-button-container">
<div class="flex items-center gap-3 text-lg">
<button type="button" data-role="relation-add-apply" class="text-gray-700 hover:text-gray-900" id="series-add-apply" aria-label="Reihe hinzufügen">
<i class="ri-check-line"></i>
@@ -387,29 +387,29 @@ type AlmanachResult struct {
</div>
</div>
</div>
<div data-role="relation-add-error" class="text-xs text-red-700 mt-2 hidden">Bitte Reihe auswählen.</div>
<div data-role="relation-add-error" class="text-xs text-red-700 mt-2 hidden" data-error-empty="Bitte Reihe auswählen." data-error-duplicate="Diese Reihe ist bereits verknüpft.">Bitte Reihe auswählen.</div>
</div>
</div>
<template data-role="relation-new-template">
<div data-rel-row class="border border-stone-200 rounded-xs bg-stone-50 px-3 py-2">
<div class="grid grid-cols-[1fr_14rem_5.5rem_7rem] gap-3 items-center">
<div data-rel-strike class="relation-strike flex flex-col min-w-0">
<div data-rel-row class="rel-row">
<div class="rel-grid">
<div data-rel-strike class="relation-strike rel-name-col">
<div class="text-base text-gray-800 truncate">
<a data-rel-link class="no-underline hover:text-slate-900">
<span data-rel-name></span>
</a>
<em data-rel-new class="text-sm text-gray-600 ml-1"></em>
<em data-rel-new class="rel-new-badge"></em>
</div>
<div data-rel-detail-container class="text-xs text-gray-600 truncate"><span data-rel-detail></span></div>
<div data-rel-detail-container class="rel-detail"><span data-rel-detail></span></div>
</div>
<div data-rel-strike class="relation-strike">
<select data-rel-input="type" class="inputselect font-bold w-full"></select>
</div>
<div data-rel-strike class="relation-strike flex items-center gap-2">
<div data-rel-strike class="relation-strike rel-uncertain-container">
<input data-rel-input="uncertain" type="checkbox" />
<label data-rel-uncertain-label class="text-sm text-gray-700">Unsicher</label>
<label data-rel-uncertain-label class="rel-uncertain-label">Unsicher</label>
</div>
<div class="flex justify-end">
<div class="rel-button-container">
<button type="button" class="text-sm text-red-700 hover:text-red-900" data-role="relation-new-delete">
<i class="ri-delete-bin-line mr-1"></i> Entfernen
</button>
@@ -426,19 +426,19 @@ type AlmanachResult struct {
<relations-editor data-prefix="entries_agents" data-link-base="/person/" data-new-label="(Neu)" data-add-toggle-id="agents-add-toggle">
<div class="inputwrapper">
<label class="inputlabel" for="agents-section">Personen &amp; Körperschaften</label>
<div id="agents-section" class="flex flex-col gap-2 mt-2">
<div id="agents-section" class="rel-section-container">
{{- if $model.result.EntriesAgents -}}
{{- range $i, $r := $model.result.EntriesAgents -}}
{{- $a := index $model.result.Agents $r.Agent -}}
<div data-rel-row class="entries-agent-row border border-stone-200 rounded-xs bg-stone-50 px-3 py-2">
<div class="grid grid-cols-[1fr_14rem_5.5rem_7rem] gap-3 items-center">
<div data-rel-strike class="relation-strike flex flex-col min-w-0">
<div data-rel-row class="entries-agent-row rel-row">
<div class="rel-grid">
<div data-rel-strike class="relation-strike rel-name-col">
{{- if $a -}}
<a data-rel-link href="/person/{{ $a.Id }}" class="text-base text-gray-800 no-underline hover:text-slate-900 truncate">
<a data-rel-link href="/person/{{ $a.Id }}" class="rel-link">
<span data-rel-name>{{- $a.Name -}}</span>
</a>
{{- if $a.BiographicalData -}}
<div data-rel-detail-container class="text-xs text-gray-600 truncate"><span data-rel-detail>{{- $a.BiographicalData -}}</span></div>
<div data-rel-detail-container class="rel-detail"><span data-rel-detail>{{- $a.BiographicalData -}}</span></div>
{{- end -}}
{{- else -}}
<div class="text-base text-gray-800">Unbekannte Person</div>
@@ -451,15 +451,15 @@ type AlmanachResult struct {
{{- end -}}
</select>
</div>
<div data-rel-strike class="relation-strike flex items-center gap-2">
<div data-rel-strike class="relation-strike rel-uncertain-container">
<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>
<label for="entries_agents_uncertain_{{ $r.Id }}" class="rel-uncertain-label">Unsicher</label>
</div>
<div class="flex justify-end">
<div class="rel-button-container">
<button
type="button"
class="text-sm"
@@ -475,13 +475,13 @@ type AlmanachResult struct {
</div>
{{- end -}}
{{- else -}}
<div class="text-sm text-gray-600">Keine Personen verknüpft.</div>
<div class="rel-empty-text">Keine Personen verknüpft.</div>
{{- end -}}
</div>
<div data-role="relation-add-row" class="mt-2"></div>
<div data-role="relation-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_5.5rem_7rem] gap-3 items-center">
<div data-role="relation-add-row" class="mt-2 px-2"></div>
<div data-role="relation-add-panel" class="mt-2 px-2 hidden">
<div class="rel-row">
<div class="rel-grid">
<div class="min-w-0">
<label for="agents-add-select" class="sr-only">Akteur suchen</label>
<single-select-remote
@@ -503,13 +503,14 @@ type AlmanachResult struct {
{{- end -}}
</select>
</div>
<div class="flex items-center gap-2">
<div class="rel-uncertain-container">
<input data-role="relation-uncertain" 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>
<label for="entries_agents_new_uncertain" class="rel-uncertain-label">Unsicher</label>
</div>
<div class="flex justify-end">
<div class="rel-button-container">
<div class="flex items-center gap-3 text-lg">
<button type="button" data-role="relation-add-apply" class="text-gray-700 hover:text-gray-900" id="agents-add-apply" aria-label="Akteur hinzufügen">
<button type="button" data-role="relation-add-apply" class="text-gray-700
hover:text-gray-900" id="agents-add-apply" aria-label="Person hinzufügen">
<i class="ri-check-line"></i>
</button>
<button type="button" data-role="relation-add-close" class="text-gray-700 hover:text-gray-900" id="agents-add-close" aria-label="Ausblenden">
@@ -518,29 +519,29 @@ type AlmanachResult struct {
</div>
</div>
</div>
<div data-role="relation-add-error" class="text-xs text-red-700 mt-2 hidden">Bitte Akteur auswählen.</div>
<div data-role="relation-add-error" class="text-xs text-red-700 mt-2 hidden" data-error-empty="Bitte Akteur auswählen.">Bitte Akteur auswählen.</div>
</div>
</div>
<template data-role="relation-new-template">
<div data-rel-row class="border border-stone-200 rounded-xs bg-stone-50 px-3 py-2">
<div class="grid grid-cols-[1fr_14rem_5.5rem_7rem] gap-3 items-center">
<div data-rel-strike class="relation-strike flex flex-col min-w-0">
<div data-rel-row class="rel-row">
<div class="rel-grid">
<div data-rel-strike class="relation-strike rel-name-col">
<div class="text-base text-gray-800 truncate">
<a data-rel-link class="no-underline hover:text-slate-900">
<span data-rel-name></span>
</a>
<em data-rel-new class="text-sm text-gray-600 ml-1"></em>
<em data-rel-new class="rel-new-badge"></em>
</div>
<div data-rel-detail-container class="text-xs text-gray-600 truncate"><span data-rel-detail></span></div>
<div data-rel-detail-container class="rel-detail"><span data-rel-detail></span></div>
</div>
<div data-rel-strike class="relation-strike">
<select data-rel-input="type" class="inputselect font-bold w-full"></select>
</div>
<div data-rel-strike class="relation-strike flex items-center gap-2">
<div data-rel-strike class="relation-strike rel-uncertain-container">
<input data-rel-input="uncertain" type="checkbox" />
<label data-rel-uncertain-label class="text-sm text-gray-700">Unsicher</label>
<label data-rel-uncertain-label class="rel-uncertain-label">Unsicher</label>
</div>
<div class="flex justify-end">
<div class="rel-button-container">
<button type="button" class="text-sm text-red-700 hover:text-red-900" data-role="relation-new-delete">
<i class="ri-delete-bin-line mr-1"></i> Entfernen
</button>

View File

@@ -215,6 +215,14 @@ export class AlmanachEditPage extends HTMLElement {
});
const newAgentRelations = this._collectNewRelations("entries_agents");
// Validate no duplicate series relations
const allSeriesRelations = [...seriesRelations, ...newSeriesRelations];
const seriesTargetIds = allSeriesRelations.map((r) => r.target_id);
const duplicateSeries = seriesTargetIds.filter((id, index) => seriesTargetIds.indexOf(id) !== index);
if (duplicateSeries.length > 0) {
throw new Error("Doppelte Reihenverknüpfungen sind nicht erlaubt.");
}
return {
csrf_token: this._readValue(formData, "csrf_token"),
last_edited: this._readValue(formData, "last_edited"),
@@ -281,30 +289,37 @@ export class AlmanachEditPage extends HTMLElement {
_collectRelations(formData, { prefix, targetField }) {
const relations = [];
const deleted = [];
// Iterate over ID fields instead of type fields (IDs are always submitted even when disabled)
for (const [key, value] of formData.entries()) {
if (!key.startsWith(`${prefix}_type[`)) {
if (!key.startsWith(`${prefix}_id[`)) {
continue;
}
const relationKey = key.slice(key.indexOf("[") + 1, -1);
const targetKey = `${prefix}_${targetField}[${relationKey}]`;
const relationIdKey = `${prefix}_id[${relationKey}]`;
const typeKey = `${prefix}_type[${relationKey}]`;
const deleteKey = `${prefix}_delete[${relationKey}]`;
const uncertainKey = `${prefix}_uncertain[${relationKey}]`;
const relationId = (value || "").trim();
const targetId = (formData.get(targetKey) || "").trim();
if (!targetId) {
if (!targetId || !relationId) {
continue;
}
const relationId = (formData.get(relationIdKey) || relationKey).trim();
// Check if marked for deletion
if (formData.has(deleteKey)) {
if (relationId) {
deleted.push(relationId);
}
deleted.push(relationId);
continue;
}
// Not deleted, add to relations
const type = (formData.get(typeKey) || "").trim();
relations.push({
id: relationId,
target_id: targetId,
type: (value || "").trim(),
type: type,
uncertain: formData.has(uncertainKey),
});
}

View File

@@ -46,6 +46,57 @@
position: relative;
}
/* Relations editor abstracted classes */
.rel-section-container {
@apply flex flex-col gap-2 mt-1 px-2;
}
.rel-row {
@apply border border-stone-200 rounded-xs bg-stone-50 px-3 py-2;
}
.rel-grid {
@apply grid grid-cols-[1fr_14rem_5.5rem_7rem] gap-3 items-center;
}
.rel-name-col {
@apply flex flex-col min-w-0;
}
.rel-link {
@apply text-base text-gray-800 no-underline hover:text-slate-900 truncate;
}
.rel-detail {
@apply text-xs text-gray-600 truncate;
}
.rel-new-badge {
@apply text-sm text-gray-600 ml-1;
}
.rel-uncertain-container {
@apply flex items-center gap-2;
}
.rel-uncertain-label {
@apply text-sm text-gray-700;
}
.rel-button-container {
@apply flex justify-end;
}
.rel-empty-text {
@apply px-1.5 italic text-gray-600;
}
/* Disabled form controls in deleted relations */
[data-rel-row].bg-red-50 select:disabled,
[data-rel-row].bg-red-50 input[type="checkbox"]:disabled:not([data-delete-toggle]) {
@apply opacity-50 cursor-not-allowed;
}
/* Diagonal hatching pattern for deleted relations, items, and pills */
[data-rel-row],
.items-row,
@@ -62,13 +113,7 @@
left: 0;
right: 0;
bottom: 0;
background: repeating-linear-gradient(
-45deg,
transparent,
transparent 6px,
rgba(220, 38, 38, 0.2) 6px,
rgba(220, 38, 38, 0.2) 10px
);
background: repeating-linear-gradient(-45deg, transparent, transparent 6px, rgba(220, 38, 38, 0.2) 6px, rgba(220, 38, 38, 0.2) 10px);
border-radius: 0.25rem;
pointer-events: none;
z-index: 1;
@@ -94,13 +139,7 @@
left: 0;
right: 0;
bottom: 0;
background: repeating-linear-gradient(
-45deg,
#f5f5f4,
#f5f5f4 6px,
rgba(220, 38, 38, 0.25) 6px,
rgba(220, 38, 38, 0.25) 10px
);
background: repeating-linear-gradient(-45deg, #f5f5f4, #f5f5f4 6px, rgba(220, 38, 38, 0.25) 6px, rgba(220, 38, 38, 0.25) 10px);
border-radius: 0.25rem;
pointer-events: none;
z-index: 0;
@@ -345,7 +384,7 @@
}
.mss-toggle-button {
@apply text-gray-700 hover:text-gray-900 font-semibold text-lg px-2 py-0.5 rounded-xs border border-transparent bg-transparent whitespace-nowrap leading-none;
@apply text-gray-700 hover:text-gray-900 font-semibold text-lg py-0.5 rounded-xs border border-transparent bg-transparent whitespace-nowrap leading-none;
}
.mss-options-list {

View File

@@ -196,16 +196,42 @@ export class ItemsEditor extends HTMLElement {
row.setAttribute(REMOVED_ROW_STATE, removed ? "true" : "false");
row.classList.toggle("bg-red-50", removed);
const editButton = row.querySelector(".items-edit-button");
if (editButton) {
if (removed) {
editButton.classList.add("hidden");
} else {
editButton.classList.remove("hidden");
}
}
row.querySelectorAll("[data-delete-label]").forEach((label) => {
const nextLabel = removed
? label.getAttribute("data-delete-active") || "Wird entfernt"
: label.getAttribute("data-delete-default") || "Entfernen";
const button = label.closest(`.${REMOVE_BUTTON_CLASS}`);
const isHovered = button && button.matches(":hover");
let nextLabel;
if (removed && isHovered) {
nextLabel = label.getAttribute("data-delete-hover") || "Rückgängig";
} else if (removed) {
nextLabel = label.getAttribute("data-delete-active") || "Wird entfernt";
} else {
nextLabel = label.getAttribute("data-delete-default") || "Entfernen";
}
label.textContent = nextLabel;
});
row.querySelectorAll(`.${REMOVE_BUTTON_CLASS} i`).forEach((icon) => {
const button = icon.closest(`.${REMOVE_BUTTON_CLASS}`);
const isHovered = button && button.matches(":hover");
if (removed) {
icon.classList.add("hidden");
icon.classList.remove("ri-delete-bin-line", "ri-arrow-go-back-line");
if (isHovered) {
icon.classList.remove("hidden");
icon.classList.add("ri-arrow-go-back-line");
icon.classList.remove("ri-delete-bin-line");
} else {
icon.classList.add("hidden");
icon.classList.remove("ri-delete-bin-line", "ri-arrow-go-back-line");
}
} else {
icon.classList.remove("hidden");
icon.classList.add("ri-delete-bin-line");

View File

@@ -24,10 +24,61 @@ export class RelationsEditor extends HTMLElement {
this._linkBase = this.getAttribute("data-link-base") || "";
this._newLabel = this.getAttribute("data-new-label") || "(Neu)";
this._addToggleId = this.getAttribute("data-add-toggle-id") || "";
this._emptyText = this.querySelector(".rel-empty-text");
this._setupAddPanel();
this._setupDeleteToggles();
}
_getExistingIds() {
const ids = new Set();
// For series: entries_series_series[...]
// For agents: entries_agents_agent[...]
const targetField = this._prefix === "entries_series" ? "series" : "agent";
// Get existing relation target IDs (from server-rendered rows)
this.querySelectorAll(`input[name^="${this._prefix}_${targetField}["]`).forEach((input) => {
const value = input.value.trim();
if (value) {
ids.add(value);
}
});
// Get new relation target IDs (from dynamically added rows, but not from the add panel)
if (this._addRow) {
this._addRow.querySelectorAll(`input[name="${this._prefix}_new_id"]`).forEach((input) => {
const value = input.value.trim();
if (value) {
ids.add(value);
}
});
}
return ids;
}
_updateEmptyTextVisibility() {
if (!this._emptyText) {
return;
}
// Check if there are any existing relations (server-rendered rows)
const targetField = this._prefix === "entries_series" ? "series" : "agent";
const hasExisting = this.querySelectorAll(`input[name^="${this._prefix}_${targetField}["]`).length > 0;
// Check if there are any new relations in the add row
const hasNew = this._addRow && this._addRow.querySelectorAll(`input[name="${this._prefix}_new_id"]`).length > 0;
// Check if add panel is visible
const isPanelVisible = this._addPanel && !this._addPanel.classList.contains("hidden");
// Hide empty text if: panel is visible OR there are any relations (existing or new)
if (isPanelVisible || hasExisting || hasNew) {
this._emptyText.classList.add("hidden");
} else {
this._emptyText.classList.remove("hidden");
}
}
_setupAddPanel() {
this._addToggle = this.querySelector(ROLE_ADD_TOGGLE);
if (this._addToggleId) {
@@ -51,15 +102,24 @@ export class RelationsEditor extends HTMLElement {
return;
}
// Set up filtering for single-select-remote (only for series, not agents)
if (this._addSelect && this._prefix === "entries_series") {
this._addSelect.addEventListener("ssrbeforefetch", () => {
this._addSelect._excludeIds = Array.from(this._getExistingIds());
});
}
if (this._addToggle) {
this._addToggle.addEventListener("click", () => {
this._addPanel.classList.toggle("hidden");
this._updateEmptyTextVisibility();
});
}
if (this._addClose) {
this._addClose.addEventListener("click", () => {
this._addPanel.classList.add("hidden");
this._updateEmptyTextVisibility();
});
}
@@ -78,16 +138,28 @@ export class RelationsEditor extends HTMLElement {
const hasSelection = idInput && idInput.value.trim().length > 0;
if (!hasSelection) {
if (this._addError) {
this._addError.textContent = this._addError.getAttribute("data-error-empty") || "Bitte Reihe auswählen.";
this._addError.classList.remove("hidden");
}
return;
}
if (this._addError) {
this._addError.classList.add("hidden");
}
if (!this._pendingItem) {
return;
}
// Check for duplicates (only for series, not agents)
if (this._prefix === "entries_series") {
const existingIds = this._getExistingIds();
if (existingIds.has(this._pendingItem.id)) {
if (this._addError) {
this._addError.textContent = this._addError.getAttribute("data-error-duplicate") || "Diese Verknüpfung existiert bereits.";
this._addError.classList.remove("hidden");
}
return;
}
}
if (this._addError) {
this._addError.classList.add("hidden");
}
this._insertNewRow();
});
}
@@ -187,6 +259,7 @@ export class RelationsEditor extends HTMLElement {
if (this._addPanel) {
this._addPanel.classList.add("hidden");
}
this._updateEmptyTextVisibility();
});
}
@@ -197,6 +270,7 @@ export class RelationsEditor extends HTMLElement {
if (this._addPanel) {
this._addPanel.classList.add("hidden");
}
this._updateEmptyTextVisibility();
}
_setupDeleteToggles() {
@@ -212,20 +286,42 @@ export class RelationsEditor extends HTMLElement {
const row = button.closest(ROLE_REL_ROW);
if (row) {
row.classList.toggle("bg-red-50", checkbox.checked);
// Disable/enable form controls (but not the delete checkbox itself)
row.querySelectorAll("select, input[type='checkbox']").forEach((control) => {
// Skip the delete checkbox itself
if (control === checkbox) {
return;
}
control.disabled = checkbox.checked;
});
}
const isHovered = button.matches(":hover");
const label = button.querySelector("[data-delete-label]");
if (label) {
label.textContent = checkbox.checked
? label.getAttribute("data-delete-active") || "Wird entfernt"
: label.getAttribute("data-delete-default") || "Entfernen";
let nextLabel;
if (checkbox.checked && isHovered) {
nextLabel = label.getAttribute("data-delete-hover") || "Rückgängig";
} else if (checkbox.checked) {
nextLabel = label.getAttribute("data-delete-active") || "Wird entfernt";
} else {
nextLabel = label.getAttribute("data-delete-default") || "Entfernen";
}
label.textContent = nextLabel;
}
const icon = button.querySelector("i");
if (icon) {
if (checkbox.checked) {
icon.classList.add("hidden");
icon.classList.remove("ri-delete-bin-line", "ri-arrow-go-back-line");
if (isHovered) {
icon.classList.remove("hidden");
icon.classList.add("ri-arrow-go-back-line");
icon.classList.remove("ri-delete-bin-line");
} else {
icon.classList.add("hidden");
icon.classList.remove("ri-delete-bin-line", "ri-arrow-go-back-line");
}
} else {
icon.classList.remove("hidden");
icon.classList.add("ri-delete-bin-line");

View File

@@ -181,6 +181,10 @@ export class SingleSelectRemote extends HTMLElement {
if (this._fetchController) {
this._fetchController.abort();
}
// Dispatch event before fetch to allow filtering
this.dispatchEvent(new CustomEvent("ssrbeforefetch", { bubbles: true }));
this._fetchController = new AbortController();
const url = new URL(this._endpoint, window.location.origin);
url.searchParams.set("q", query);
@@ -194,7 +198,15 @@ export class SingleSelectRemote extends HTMLElement {
}
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);
let filteredItems = items.filter((item) => item && item.id && item.name);
// Filter out excluded IDs if provided
if (this._excludeIds && Array.isArray(this._excludeIds)) {
const excludeSet = new Set(this._excludeIds);
filteredItems = filteredItems.filter((item) => !excludeSet.has(item.id));
}
this._options = filteredItems;
this._highlightedIndex = this._options.length > 0 ? 0 : -1;
this._renderOptions();
if (this._options.length > 0) {