+exemplate, +fields

This commit is contained in:
Simon Martens
2026-01-07 16:43:35 +01:00
parent 02d78388e7
commit f9fb077518
13 changed files with 897 additions and 272 deletions

View File

@@ -27,6 +27,7 @@ export class DivManager extends HTMLElement {
this._target = null;
this._button = null;
this._menu = null;
this._originalButtonText = null;
}
connectedCallback() {
@@ -41,6 +42,10 @@ export class DivManager extends HTMLElement {
const label = node.querySelector("label");
return label ? label.innerHTML : node.hasAttribute(DM_TITLE_ATTRIBUTE) ? node.getAttribute(DM_TITLE_ATTRIBUTE) : "";
},
nameText: () => {
const label = node.querySelector("label");
return label ? label.textContent.trim() : node.hasAttribute(DM_TITLE_ATTRIBUTE) ? node.getAttribute(DM_TITLE_ATTRIBUTE) : "";
},
};
});
@@ -54,6 +59,9 @@ export class DivManager extends HTMLElement {
console.error("DivManagerMenu needs a button element.");
return;
}
if (!this._originalButtonText) {
this._originalButtonText = this._button.innerHTML;
}
for (const child of this._cildren) {
this.removeChild(child.node);
@@ -70,56 +78,113 @@ export class DivManager extends HTMLElement {
});
}
this.renderMenu();
this.renderIntoTarget();
this.refresh();
this._observer = new MutationObserver(() => {
this.refresh();
});
this._cildren.forEach((child) => {
this._observer.observe(child.node, { attributes: true, attributeFilter: ["class"] });
});
}
disconnectedCallback() {
if (this._observer) {
this._observer.disconnect();
}
document.removeEventListener("click", this.boundHandleClickOutside);
}
refresh() {
this.renderButton();
this.renderMenu();
this.updateTargetVisibility();
}
_toggleMenu(event) {
event.preventDefault();
event.stopPropagation();
if (!this._menu) {
this._menu = this.querySelector(`.${DM_MENU_CLASS}`);
}
if (!this._menu) {
console.error("DivManagerMenu: Menu not found.");
const hiddenChildren = this._cildren.filter((c) => c.hidden());
if (hiddenChildren.length === 1) {
const index = this._cildren.indexOf(hiddenChildren[0]);
this.showDiv(event, index);
return;
}
if (hiddenChildren.length === 0) {
this.hideMenu();
return;
}
this.renderMenu();
if (this._menu.classList.contains(TAILWIND_HIDDEN_CLASS)) {
this._menu.classList.remove(TAILWIND_HIDDEN_CLASS);
document.addEventListener("click", this.handleClickOutside);
document.addEventListener("click", this.boundHandleClickOutside);
} else {
this._menu.classList.add(TAILWIND_HIDDEN_CLASS);
document.removeEventListener("click", this.handleClickOutside);
document.removeEventListener("click", this.boundHandleClickOutside);
}
}
handleClickOutside(event) {
if (!this._menu) return;
if (!this._menu.contains(event.target) && !this._button.contains(event.target)) {
this._menu.classList.add(TAILWIND_HIDDEN_CLASS);
this.hideMenu();
}
}
hideMenu() {
if (!this._menu) return;
this._menu.classList.add(TAILWIND_HIDDEN_CLASS);
document.removeEventListener("click", this.boundHandleClickOutside);
}
renderButton() {
if (!this._button) {
return;
}
for (const child of this._cildren) {
if (child.hidden()) {
if (this._button.parentElement !== this) {
this._button.classList.remove(TAILWIND_HIDDEN_CLASS);
this.appendChild(this._button);
}
return;
}
// Store original button text if not already stored (do this first)
if (!this._originalButtonText) {
this._originalButtonText = this._button.innerHTML;
}
this._button.classList.add(TAILWIND_HIDDEN_CLASS);
this.removeChild(this._button);
// Check if there are any hidden children
const hiddenChildren = this._cildren.filter((c) => c.hidden());
if (hiddenChildren.length === 0) {
// No hidden children, hide and remove the button completely
this._button.classList.add(TAILWIND_HIDDEN_CLASS);
if (this._button.parentElement) {
this._button.parentElement.removeChild(this._button);
}
this._menu = null;
this.hideMenu();
return;
}
// There are hidden children, ensure button is in the DOM and visible
if (!this._button.parentElement) {
this.appendChild(this._button);
}
this._button.classList.remove(TAILWIND_HIDDEN_CLASS);
// Update button text based on number of hidden children
if (hiddenChildren.length === 1) {
// Only one option left - show its name
const icon = this._button.querySelector("i");
const iconHTML = icon ? icon.outerHTML : '<i class="ri-add-line"></i>';
this._button.innerHTML = `${iconHTML}\n${hiddenChildren[0].nameText()} hinzufügen`;
this._menu = null;
this.hideMenu();
} else {
// Multiple options - restore original text
this._button.innerHTML = this._originalButtonText;
this._menu = null;
}
}
hideDiv(event, node) {
@@ -139,9 +204,8 @@ export class DivManager extends HTMLElement {
return;
}
if (!child.hidden()) {
child.node.classList.add(TAILWIND_HIDDEN_CLASS);
}
// Always ensure the hidden class is added
child.node.classList.add(TAILWIND_HIDDEN_CLASS);
this._target.removeChild(child.node);
// INFO: the order of these matter, dont fuck it up
@@ -161,37 +225,42 @@ export class DivManager extends HTMLElement {
}
const child = this._cildren[index];
if (child.hidden()) {
child.node.classList.remove(TAILWIND_HIDDEN_CLASS);
}
// Always ensure the hidden class is removed
child.node.classList.remove(TAILWIND_HIDDEN_CLASS);
this._target.appendChild(child.node);
this.insertChildInOrder(child);
// INFO: the order of these matter, dont fuck it up
this.renderMenu();
this.renderButton();
this.updateTargetVisibility();
}
renderMenu() {
if (!this._menu) {
this._button.innerHTML += `<div class="${DM_MENU_CLASS} absolute hidden"></div>`;
const hiddenChildren = this._cildren.filter((c) => c.hidden());
if (hiddenChildren.length <= 1) {
this.hideMenu();
return;
}
if (!this._menu || !this._button.contains(this._menu)) {
this._button.insertAdjacentHTML("beforeend", `<div class="${DM_MENU_CLASS} absolute hidden"></div>`);
this._menu = this._button.querySelector(`.${DM_MENU_CLASS}`);
}
this._menu.innerHTML = `${this._cildren
this._menu.innerHTML = `${hiddenChildren
.map((c, index) => {
if (!c.hidden()) return "";
return `
<button type="button" class="${DM_ITEM_CLASS}" dm-itemno="${index}">
<button type="button" class="${DM_ITEM_CLASS}" dm-itemno="${this._cildren.indexOf(c)}">
${c.name()}
</button>`;
})
.join("")}`;
this._menu = this.querySelector(`.${DM_MENU_CLASS}`);
const buttons = this._menu.querySelectorAll(`.${DM_ITEM_CLASS}`);
buttons.forEach((button) => {
button.addEventListener("click", (event) => {
this.showDiv(event, parseInt(button.getAttribute("dm-itemno")));
this._toggleMenu(event);
this.hideMenu();
this.renderButton();
});
});
}
@@ -199,8 +268,34 @@ export class DivManager extends HTMLElement {
renderIntoTarget() {
this._cildren.forEach((child) => {
if (!child.hidden()) {
this._target.appendChild(child.node);
this.insertChildInOrder(child);
}
});
this.updateTargetVisibility();
}
insertChildInOrder(child) {
const currentIndex = this._cildren.indexOf(child);
const nextVisibleSibling = this._cildren
.slice(currentIndex + 1)
.map((c) => c.node)
.find((node) => this._target.contains(node));
if (nextVisibleSibling) {
this._target.insertBefore(child.node, nextVisibleSibling);
} else {
this._target.appendChild(child.node);
}
}
updateTargetVisibility() {
if (!this._target || this._target === this) {
return;
}
const hasVisibleChild = Array.from(this._target.children).some(
(node) => !node.classList.contains(TAILWIND_HIDDEN_CLASS),
);
this._target.classList.toggle(TAILWIND_HIDDEN_CLASS, !hasVisibleChild);
}
}

View File

@@ -27,7 +27,7 @@
.dbform .inputwrapper .inputselect {
@apply mt-1 block w-full rounded-md focus:border-none focus:outline-none
disabled:opacity-50;
disabled:opacity-50 py-1 px-3;
}
.dbform .submitbutton {
@apply w-full inline-flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-slate-700 hover:bg-slate-800 cursor-pointer focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500 active:bg-slate-900 transition-all duration-75;
@@ -176,6 +176,7 @@
/* --- MultiSelectSimple Component Base Styles (using @apply) --- */
.mss-component-wrapper {
/* 'relative' is set inline for positioning dropdown */
@apply px-3 py-1;
}
.mss-selected-items-container {

View File

@@ -14,6 +14,7 @@ import { MultiSelectRole } from "./multi-select-role.js";
import { MultiSelectSimple } from "./multi-select-simple.js";
import { ResetButton } from "./reset-button.js";
import { DivManager } from "./div-menu.js";
import { ItemsEditor } from "./items-editor.js";
const FILTER_LIST_ELEMENT = "filter-list";
const SCROLL_BUTTON_ELEMENT = "scroll-button";
@@ -28,6 +29,7 @@ const MULTI_SELECT_ROLE_ELEMENT = "multi-select-places";
const MULTI_SELECT_SIMPLE_ELEMENT = "multi-select-simple";
const RESET_BUTTON_ELEMENT = "reset-button";
const DIV_MANAGER_ELEMENT = "div-manager";
const ITEMS_EDITOR_ELEMENT = "items-editor";
customElements.define(INT_LINK_ELEMENT, IntLink);
customElements.define(ABBREV_TOOLTIPS_ELEMENT, AbbreviationTooltips);
@@ -42,6 +44,7 @@ customElements.define(MULTI_SELECT_ROLE_ELEMENT, MultiSelectRole);
customElements.define(MULTI_SELECT_SIMPLE_ELEMENT, MultiSelectSimple);
customElements.define(RESET_BUTTON_ELEMENT, ResetButton);
customElements.define(DIV_MANAGER_ELEMENT, DivManager);
customElements.define(ITEMS_EDITOR_ELEMENT, ItemsEditor);
function PathPlusQuery() {
const path = window.location.pathname;
@@ -277,4 +280,4 @@ window.PathPlusQuery = PathPlusQuery;
window.HookupRBChange = HookupRBChange;
window.FormLoad = FormLoad;
export { FilterList, ScrollButton, AbbreviationTooltips, MultiSelectSimple, MultiSelectRole, ToolTip, PopupImage, TabList, FilterPill, ImageReel, IntLink };
export { FilterList, ScrollButton, AbbreviationTooltips, MultiSelectSimple, MultiSelectRole, ToolTip, PopupImage, TabList, FilterPill, ImageReel, IntLink, ItemsEditor };