mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2025-10-29 17:25:32 +00:00
resetbutton & almanach edit start
This commit is contained in:
276
views/transform/form.css
Normal file
276
views/transform/form.css
Normal file
@@ -0,0 +1,276 @@
|
||||
@layer components {
|
||||
.dbform .inputwrapper {
|
||||
@apply rounded-xs border-2 border-transparent px-3
|
||||
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
||||
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100;
|
||||
}
|
||||
|
||||
.dbform .inputwrapper .inputlabel {
|
||||
@apply text-sm text-gray-700 font-bold;
|
||||
}
|
||||
|
||||
.inputlabeltext {
|
||||
@apply text-sm text-gray-700 font-bold;
|
||||
}
|
||||
|
||||
.dbform .inputwrapper .inputselect {
|
||||
@apply mt-1 block w-full rounded-md focus:border-none focus:outline-none
|
||||
disabled:opacity-50;
|
||||
}
|
||||
|
||||
.dbform .inputwrapper .inputinput {
|
||||
@apply mt-1 block w-full focus:border-none focus:outline-none;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.dbform .resetbutton {
|
||||
@apply w-full inline-flex justify-center py-2 px-4 border border-transparent rounded-md text-sm font-medium text-gray-800 bg-stone-200 hover:bg-stone-300 cursor-pointer focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500 no-underline;
|
||||
}
|
||||
|
||||
/* Multi-Select-Role example styles */
|
||||
.msr-selected-items-container {
|
||||
@apply rounded-md;
|
||||
}
|
||||
.msr-placeholder-no-selection-text {
|
||||
@apply text-sm text-gray-500 italic px-2 py-1;
|
||||
}
|
||||
|
||||
.msr-input-area-wrapper {
|
||||
@apply p-2 rounded-md;
|
||||
}
|
||||
.msr-input-area-wrapper.msr-input-area-default-border {
|
||||
@apply border border-gray-300;
|
||||
}
|
||||
.msr-input-area-wrapper.msr-input-area-default-border:focus-within {
|
||||
@apply focus-within:border-gray-500 focus-within:ring-1 focus-within:ring-gray-400;
|
||||
}
|
||||
.msr-input-area-wrapper.msr-input-area-staged {
|
||||
@apply border border-transparent;
|
||||
}
|
||||
|
||||
.msr-text-input {
|
||||
@apply bg-transparent text-sm placeholder-gray-400;
|
||||
}
|
||||
|
||||
.msr-selected-item-pill {
|
||||
@apply bg-gray-200 text-gray-700 px-3 py-[0.3rem] rounded-md text-sm inline-flex items-center m-0.5;
|
||||
}
|
||||
.msr-item-name {
|
||||
@apply font-medium;
|
||||
}
|
||||
.msr-item-additional-data {
|
||||
@apply text-xs ml-1 text-gray-600;
|
||||
}
|
||||
.msr-selected-item-role {
|
||||
@apply font-semibold text-xs ml-1 text-gray-800;
|
||||
}
|
||||
.msr-selected-item-delete-btn {
|
||||
@apply bg-transparent border-none text-gray-500 text-lg leading-none px-1 cursor-pointer opacity-60 transition-opacity duration-200;
|
||||
}
|
||||
.msr-selected-item-delete-btn:hover {
|
||||
@apply hover:opacity-100 hover:text-gray-900;
|
||||
}
|
||||
|
||||
.msr-staged-item-pill {
|
||||
@apply bg-gray-100 text-gray-800 px-2 py-1 rounded-md text-sm font-medium;
|
||||
}
|
||||
.msr-staged-item-text {
|
||||
@apply mr-2;
|
||||
}
|
||||
|
||||
.msr-staged-role-select {
|
||||
@apply px-2 py-1 text-sm rounded-md border border-gray-300 bg-white outline-none text-gray-700;
|
||||
}
|
||||
.msr-staged-role-select:focus {
|
||||
@apply focus:border-gray-500 focus:ring-1 focus:ring-gray-400;
|
||||
}
|
||||
|
||||
.msr-staged-cancel-btn {
|
||||
@apply w-5 h-5 bg-gray-200 text-gray-600 rounded-full text-sm leading-none cursor-pointer;
|
||||
}
|
||||
.msr-staged-cancel-btn:hover {
|
||||
@apply hover:bg-gray-300 hover:text-gray-800;
|
||||
}
|
||||
|
||||
.msr-pre-add-button {
|
||||
@apply w-10 h-[42px] text-xl rounded-md bg-gray-50 text-gray-700 border border-gray-300 font-semibold outline-none;
|
||||
}
|
||||
.msr-pre-add-button:focus {
|
||||
@apply focus:border-gray-500 focus:ring-1 focus:ring-gray-400;
|
||||
}
|
||||
.msr-pre-add-button:hover {
|
||||
@apply hover:bg-gray-100;
|
||||
}
|
||||
.msr-pre-add-button:disabled {
|
||||
@apply disabled:bg-gray-200 disabled:text-gray-400 disabled:cursor-not-allowed disabled:border-gray-200;
|
||||
}
|
||||
.msr-pre-add-button.hidden {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.msr-add-button {
|
||||
@apply px-4 py-2 text-sm rounded-md bg-gray-600 text-white font-medium;
|
||||
}
|
||||
.msr-add-button:hover {
|
||||
@apply hover:bg-gray-700;
|
||||
}
|
||||
.msr-add-button:disabled {
|
||||
@apply disabled:bg-gray-300 disabled:cursor-not-allowed;
|
||||
}
|
||||
.msr-add-button.hidden {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.msr-options-list {
|
||||
@apply bg-white border border-gray-300 rounded-md shadow-md;
|
||||
}
|
||||
.msr-options-list.hidden {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.msr-option-item {
|
||||
@apply px-3 py-2 text-sm cursor-pointer transition-colors duration-75;
|
||||
}
|
||||
.msr-option-item:hover {
|
||||
@apply bg-gray-100 text-gray-800;
|
||||
}
|
||||
.msr-option-item-highlighted {
|
||||
@apply bg-gray-100 text-gray-800;
|
||||
}
|
||||
.msr-option-item-name {
|
||||
@apply font-medium;
|
||||
}
|
||||
.msr-option-item-detail {
|
||||
@apply text-xs ml-2 text-gray-500;
|
||||
}
|
||||
.msr-option-item-highlighted .msr-option-item-detail,
|
||||
.msr-option-item:hover .msr-option-item-detail {
|
||||
/* Ensure detail text color changes on hover too */
|
||||
@apply text-gray-600;
|
||||
}
|
||||
|
||||
multi-select-role[disabled] {
|
||||
/* This remains standard CSS as Tailwind's disabled: variant is for native elements */
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.msr-hidden-select {
|
||||
/* No specific styling needed as it's visually hidden by JS/inline style */
|
||||
}
|
||||
|
||||
/* --- MultiSelectSimple Component Base Styles (using @apply) --- */
|
||||
.mss-component-wrapper {
|
||||
/* 'relative' is set inline for positioning dropdown */
|
||||
}
|
||||
|
||||
.mss-selected-items-container {
|
||||
@apply border border-gray-300 p-1.5 rounded;
|
||||
/* Tailwind classes from component: flex flex-wrap gap-1 mb-1 min-h-[38px] */
|
||||
}
|
||||
|
||||
.mss-no-items-text {
|
||||
@apply italic text-xs text-gray-500 p-1 w-full; /* Adjusted font size slightly to match 'xs' */
|
||||
}
|
||||
|
||||
.mss-selected-item-pill {
|
||||
@apply bg-gray-200 text-gray-800 py-0.5 px-2 rounded text-xs leading-5; /* Adjusted font size and padding */
|
||||
/* Tailwind classes from component: flex items-center */
|
||||
}
|
||||
|
||||
.mss-selected-item-text {
|
||||
/* Base styles for text part of the pill */
|
||||
}
|
||||
.mss-selected-item-pill-detail {
|
||||
@apply ml-1 opacity-75 text-xs text-gray-600;
|
||||
}
|
||||
.mss-selected-item-pill-detail.hidden {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.mss-selected-item-delete-btn {
|
||||
@apply bg-transparent border-none text-gray-600 opacity-70 cursor-pointer ml-1 text-base leading-none align-middle hover:opacity-100 hover:text-gray-900 disabled:opacity-40 disabled:cursor-not-allowed;
|
||||
}
|
||||
|
||||
.mss-input-controls-container {
|
||||
/* Tailwind classes from component: flex items-center space-x-2 */
|
||||
}
|
||||
|
||||
.mss-input-wrapper {
|
||||
@apply border border-gray-300 rounded;
|
||||
/* Tailwind classes from component: relative flex items-center flex-grow */
|
||||
}
|
||||
.mss-input-wrapper-focused {
|
||||
@apply border-indigo-600 ring-1 ring-indigo-600; /* Using ring for focus shadow */
|
||||
}
|
||||
|
||||
.mss-text-input {
|
||||
@apply py-1.5 px-2 text-sm;
|
||||
/* Tailwind classes from component: w-full outline-none bg-transparent */
|
||||
}
|
||||
.mss-text-input::placeholder {
|
||||
@apply text-gray-400 italic;
|
||||
}
|
||||
|
||||
.mss-create-new-button {
|
||||
@apply bg-gray-100 text-gray-700 border border-gray-300 py-1 px-1.5 text-sm rounded hover:bg-gray-200 hover:border-gray-400 disabled:bg-gray-50 disabled:text-gray-400 disabled:border-gray-200 disabled:opacity-70 disabled:cursor-not-allowed;
|
||||
}
|
||||
.mss-create-new-button.hidden {
|
||||
@apply !hidden; /* Ensure it hides */
|
||||
}
|
||||
|
||||
.mss-options-list {
|
||||
@apply bg-white border border-gray-300 rounded shadow-md; /* Using shadow-md as a softer default */
|
||||
/* Tailwind classes from component: absolute z-20 w-full max-h-60 overflow-y-auto mt-1 hidden */
|
||||
}
|
||||
|
||||
.mss-option-item {
|
||||
@apply text-gray-700 py-1.5 px-2.5 text-sm cursor-pointer transition-colors duration-75 hover:bg-gray-100;
|
||||
}
|
||||
|
||||
.mss-option-item-name {
|
||||
@apply font-medium;
|
||||
}
|
||||
|
||||
.mss-option-item-detail {
|
||||
@apply text-gray-500 text-xs ml-1.5;
|
||||
}
|
||||
|
||||
.mss-option-item-highlighted {
|
||||
@apply bg-indigo-100 text-indigo-800;
|
||||
}
|
||||
.mss-option-item-highlighted .mss-option-item-name {
|
||||
/* @apply font-medium; */ /* Already set by .mss-option-item-name, inherit color from parent */
|
||||
}
|
||||
.mss-option-item-highlighted .mss-option-item-detail {
|
||||
@apply text-indigo-700;
|
||||
}
|
||||
|
||||
.mss-hidden-select {
|
||||
/* Styles are inline in _render for !important, no change needed here */
|
||||
}
|
||||
|
||||
multi-select-simple[disabled] {
|
||||
@apply opacity-60; /* Adjusted opacity */
|
||||
}
|
||||
multi-select-simple[disabled] .mss-selected-items-container {
|
||||
@apply bg-gray-100;
|
||||
}
|
||||
multi-select-simple[disabled] .mss-selected-item-pill {
|
||||
@apply bg-gray-300 text-gray-500;
|
||||
}
|
||||
multi-select-simple[disabled] .mss-selected-item-delete-btn {
|
||||
@apply text-gray-400;
|
||||
}
|
||||
|
||||
.rbi-button {
|
||||
@apply disabled:hidden;
|
||||
}
|
||||
|
||||
select + reset-button .rbi-button {
|
||||
@apply ml-3;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import { IntLink } from "./int-link.js";
|
||||
import { ImageReel } from "./image-reel.js";
|
||||
import { MultiSelectRole } from "./multi-select-role.js";
|
||||
import { MultiSelectSimple } from "./multi-select-simple.js";
|
||||
import { ResetButton } from "./reset-button.js";
|
||||
|
||||
const FILTER_LIST_ELEMENT = "filter-list";
|
||||
const SCROLL_BUTTON_ELEMENT = "scroll-button";
|
||||
@@ -24,6 +25,7 @@ const FILTER_PILL_ELEMENT = "filter-pill";
|
||||
const IMAGE_REEL_ELEMENT = "image-reel";
|
||||
const MULTI_SELECT_ROLE_ELEMENT = "multi-select-places";
|
||||
const MULTI_SELECT_SIMPLE_ELEMENT = "multi-select-simple";
|
||||
const RESET_BUTTON_ELEMENT = "reset-button";
|
||||
|
||||
customElements.define(INT_LINK_ELEMENT, IntLink);
|
||||
customElements.define(ABBREV_TOOLTIPS_ELEMENT, AbbreviationTooltips);
|
||||
@@ -36,5 +38,118 @@ customElements.define(FILTER_PILL_ELEMENT, FilterPill);
|
||||
customElements.define(IMAGE_REEL_ELEMENT, ImageReel);
|
||||
customElements.define(MULTI_SELECT_ROLE_ELEMENT, MultiSelectRole);
|
||||
customElements.define(MULTI_SELECT_SIMPLE_ELEMENT, MultiSelectSimple);
|
||||
customElements.define(RESET_BUTTON_ELEMENT, ResetButton);
|
||||
|
||||
export { FilterList, ScrollButton, AbbreviationTooltips };
|
||||
function PathPlusQuery() {
|
||||
const path = window.location.pathname;
|
||||
const query = window.location.search;
|
||||
const fullPath = path + query;
|
||||
return encodeURIComponent(fullPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} timeout - Maximum time to wait in milliseconds.
|
||||
* @param {number} interval - How often to check in milliseconds.
|
||||
* @returns {Promise<Function>} Resolves with the QRCode constructor when available.
|
||||
*/
|
||||
function getQRCodeWhenAvailable(timeout = 5000, interval = 100) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let elapsedTime = 0;
|
||||
const checkInterval = setInterval(() => {
|
||||
if (typeof window.QRCode === "function") {
|
||||
clearInterval(checkInterval);
|
||||
resolve(window.QRCode); // Resolve with the QRCode object/function
|
||||
} else {
|
||||
elapsedTime += interval;
|
||||
if (elapsedTime >= timeout) {
|
||||
clearInterval(checkInterval);
|
||||
console.error("Timed out waiting for QRCode to become available.");
|
||||
reject(new Error("QRCode not available after " + timeout + "ms. Check if qrcode.min.js is loaded correctly and sets window.QRCode."));
|
||||
}
|
||||
}
|
||||
}, interval);
|
||||
});
|
||||
}
|
||||
|
||||
// INFO: We have to wait for the QRCode object to be available. It's messy.
|
||||
async function GenQRCode(value) {
|
||||
const QRCode = await getQRCodeWhenAvailable();
|
||||
const qrElement = document.getElementById("qr");
|
||||
if (qrElement) {
|
||||
// INFO: Clear previous QR code if any
|
||||
// Also hide it initially to prevent flickering
|
||||
qrElement.innerHTML = "";
|
||||
qrElement.classList.add("hidden");
|
||||
new QRCode(qrElement, {
|
||||
text: value,
|
||||
width: 1280,
|
||||
height: 1280,
|
||||
colorDark: "#000000",
|
||||
colorLight: "#ffffff",
|
||||
correctLevel: QRCode.CorrectLevel.H,
|
||||
});
|
||||
setTimeout(() => {
|
||||
qrElement.classList.remove("hidden");
|
||||
}, 20);
|
||||
}
|
||||
|
||||
// Add event listeners to the token input field to select its content on focus or click
|
||||
}
|
||||
|
||||
function SelectableInput(tokenElement) {
|
||||
if (tokenElement) {
|
||||
tokenElement.addEventListener("focus", (ev) => {
|
||||
ev.preventDefault();
|
||||
tokenElement.select();
|
||||
});
|
||||
tokenElement.addEventListener("mousedown", (ev) => {
|
||||
ev.preventDefault();
|
||||
tokenElement.select();
|
||||
});
|
||||
tokenElement.addEventListener("mouseup", (ev) => {
|
||||
ev.preventDefault();
|
||||
tokenElement.select();
|
||||
});
|
||||
}
|
||||
if (tokenElement) {
|
||||
tokenElement.addEventListener("focus", () => {
|
||||
tokenElement.select();
|
||||
});
|
||||
tokenElement.addEventListener("click", () => {
|
||||
tokenElement.select();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Doesn't work properly.
|
||||
// Intended to make sure errors from boosted links are shown.
|
||||
function ShowBoostedErrors() {
|
||||
document.body.addEventListener("htmx:responseError", function (event) {
|
||||
const config = event.detail.requestConfig;
|
||||
if (config.boosted) {
|
||||
document.body.innerHTML = event.detail.xhr.responseText;
|
||||
const newUrl = event.detail.xhr.responseURL || config.url;
|
||||
window.history.pushState(null, "", newUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function FormHasChanged(form) {
|
||||
if (!form || !(form instanceof HTMLFormElement)) {
|
||||
return false;
|
||||
}
|
||||
const resetButton = form.querySelector("reset-button");
|
||||
if (resetButton && resetButton.changed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
window.ShowBoostedErrors = ShowBoostedErrors;
|
||||
window.GenQRCode = GenQRCode;
|
||||
window.SelectableInput = SelectableInput;
|
||||
window.PathPlusQuery = PathPlusQuery;
|
||||
window.FormHasChanged = FormHasChanged;
|
||||
|
||||
export { FilterList, ScrollButton, AbbreviationTooltips, MultiSelectSimple, MultiSelectRole, ToolTip, PopupImage, TabList, FilterPill, ImageReel, IntLink };
|
||||
|
||||
263
views/transform/reset-button.js
Normal file
263
views/transform/reset-button.js
Normal file
@@ -0,0 +1,263 @@
|
||||
const RBI_BUTTON_BASE_CLASS = "rbi-button";
|
||||
const RBI_ICON_CLASS = "rbi-icon";
|
||||
|
||||
export class ResetButton extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.initialStates = new Map();
|
||||
this._controlledElements = [];
|
||||
this.button = null;
|
||||
this.changed = false;
|
||||
|
||||
this.handleInputChange = this.handleInputChange.bind(this);
|
||||
this.handleReset = this.handleReset.bind(this);
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return ["controls", "wrapper-class", "modified-class-suffix", "button-aria-label"];
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
// Use an HTML template literal string to define the button's structure
|
||||
const buttonHTML = `
|
||||
<button type="button" class="${RBI_BUTTON_BASE_CLASS} cursor-pointer disabled:cursor-default" aria-label="Reset field">
|
||||
<tool-tip position="right">
|
||||
<div class="data-tip">Feld zurücksetzen</div>
|
||||
<span class="${RBI_ICON_CLASS} ri-arrow-go-back-fill"></span>
|
||||
</tool-tip>
|
||||
</button>
|
||||
`;
|
||||
this.innerHTML = buttonHTML; // Set the inner HTML of the custom element
|
||||
this.button = this.querySelector("button"); // Get the button element
|
||||
|
||||
if (this.button) {
|
||||
this.button.addEventListener("click", this.handleReset);
|
||||
} else {
|
||||
// This case should ideally not be reached if the HTML string is correct
|
||||
console.error("ResetButtonIndividual: Button element not found after setting innerHTML.");
|
||||
}
|
||||
|
||||
this.updateControlledElements();
|
||||
this.updateButtonAriaLabel();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this.button) {
|
||||
this.button.removeEventListener("click", this.handleReset);
|
||||
}
|
||||
this._controlledElements.forEach((el) => {
|
||||
el.removeEventListener("input", this.handleInputChange);
|
||||
el.removeEventListener("change", this.handleInputChange);
|
||||
});
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
if (oldValue === newValue) return;
|
||||
|
||||
if (name === "controls") {
|
||||
this.updateControlledElements();
|
||||
}
|
||||
if (name === "controls" || name === "button-aria-label") {
|
||||
this.updateButtonAriaLabel();
|
||||
}
|
||||
}
|
||||
|
||||
updateControlledElements() {
|
||||
this._controlledElements.forEach((el) => {
|
||||
el.removeEventListener("input", this.handleInputChange);
|
||||
el.removeEventListener("change", this.handleInputChange);
|
||||
});
|
||||
this._controlledElements = [];
|
||||
this.initialStates.clear();
|
||||
|
||||
const controlIds = (this.getAttribute("controls") || "")
|
||||
.split(",")
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id);
|
||||
|
||||
if (!controlIds.length && this.button) {
|
||||
this.button.disabled = true;
|
||||
this.button.setAttribute("aria-disabled", "true");
|
||||
return;
|
||||
}
|
||||
|
||||
const foundElements = [];
|
||||
controlIds.forEach((id) => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
foundElements.push(element);
|
||||
this.storeInitialState(element);
|
||||
element.addEventListener("input", this.handleInputChange);
|
||||
element.addEventListener("change", this.handleInputChange);
|
||||
} else {
|
||||
console.warn(`ResetButtonIndividual: Element with ID "${id}" not found.`);
|
||||
}
|
||||
});
|
||||
this._controlledElements = foundElements;
|
||||
|
||||
if (this.button) {
|
||||
this.button.disabled = this._controlledElements.length === 0;
|
||||
this.button.setAttribute("aria-controls", this._controlledElements.map((el) => el.id).join(" "));
|
||||
if (this.button.disabled) {
|
||||
this.button.setAttribute("aria-disabled", "true");
|
||||
} else {
|
||||
this.button.removeAttribute("aria-disabled");
|
||||
}
|
||||
}
|
||||
this.checkIfModified();
|
||||
}
|
||||
|
||||
storeInitialState(element) {
|
||||
let state;
|
||||
switch (element.type) {
|
||||
case "checkbox":
|
||||
case "radio":
|
||||
state = { checked: element.checked };
|
||||
break;
|
||||
case "select-multiple":
|
||||
state = {
|
||||
selectedOptions: Array.from(element.options)
|
||||
.filter((o) => o.selected)
|
||||
.map((o) => o.value),
|
||||
};
|
||||
break;
|
||||
case "select-one":
|
||||
default:
|
||||
state = { value: element.value };
|
||||
break;
|
||||
}
|
||||
this.initialStates.set(element.id, state);
|
||||
}
|
||||
|
||||
resetElement(element) {
|
||||
const initialState = this.initialStates.get(element.id);
|
||||
if (!initialState) return;
|
||||
|
||||
switch (element.type) {
|
||||
case "checkbox":
|
||||
case "radio":
|
||||
element.checked = initialState.checked;
|
||||
break;
|
||||
case "select-multiple":
|
||||
Array.from(element.options).forEach((option) => {
|
||||
option.selected = initialState.selectedOptions.includes(option.value);
|
||||
});
|
||||
break;
|
||||
case "select-one":
|
||||
default:
|
||||
element.value = initialState.value;
|
||||
break;
|
||||
}
|
||||
element.dispatchEvent(new Event("input", { bubbles: true, cancelable: true }));
|
||||
element.dispatchEvent(new Event("change", { bubbles: true, cancelable: true }));
|
||||
}
|
||||
|
||||
handleReset() {
|
||||
this._controlledElements.forEach((el) => {
|
||||
this.resetElement(el);
|
||||
});
|
||||
this.checkIfModified();
|
||||
}
|
||||
|
||||
handleInputChange(event) {
|
||||
if (this._controlledElements.includes(event.target)) {
|
||||
this.checkIfModified();
|
||||
}
|
||||
}
|
||||
|
||||
isElementModified(element) {
|
||||
const initialState = this.initialStates.get(element.id);
|
||||
if (!initialState) return false;
|
||||
|
||||
switch (element.type) {
|
||||
case "checkbox":
|
||||
case "radio":
|
||||
return element.checked !== initialState.checked;
|
||||
case "select-multiple":
|
||||
const currentSelected = Array.from(element.options)
|
||||
.filter((o) => o.selected)
|
||||
.map((o) => o.value);
|
||||
const initialSelected = initialState.selectedOptions;
|
||||
return currentSelected.length !== initialSelected.length || currentSelected.some((val) => !initialSelected.includes(val)) || initialSelected.some((val) => !currentSelected.includes(val));
|
||||
case "select-one":
|
||||
default:
|
||||
return element.value !== initialState.value;
|
||||
}
|
||||
}
|
||||
|
||||
checkIfModified() {
|
||||
let overallModified = false;
|
||||
this._controlledElements.forEach((el) => {
|
||||
const modified = this.isElementModified(el);
|
||||
if (modified) {
|
||||
overallModified = true;
|
||||
el.classList.add("modified-element");
|
||||
} else {
|
||||
el.classList.remove("modified-element");
|
||||
}
|
||||
});
|
||||
|
||||
const wrapperClass = this.getAttribute("wrapper-class");
|
||||
if (wrapperClass) {
|
||||
const wrapperElement = this.closest(`.${wrapperClass}`);
|
||||
if (wrapperElement) {
|
||||
const modifiedSuffix = this.getAttribute("modified-class-suffix") || "modified";
|
||||
const modifiedWrapperClassName = `${wrapperClass}-${modifiedSuffix}`;
|
||||
if (overallModified) {
|
||||
wrapperElement.classList.add(modifiedWrapperClassName);
|
||||
} else {
|
||||
wrapperElement.classList.remove(modifiedWrapperClassName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.button) {
|
||||
this.button.disabled = !overallModified || this._controlledElements.length === 0;
|
||||
if (this.button.disabled) {
|
||||
this.button.setAttribute("aria-disabled", "true");
|
||||
} else {
|
||||
this.button.removeAttribute("aria-disabled");
|
||||
}
|
||||
}
|
||||
|
||||
if (this.changed !== overallModified) {
|
||||
const event = new CustomEvent("rbichange", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
modified: overallModified,
|
||||
controlledElementIds: this._controlledElements.map((el) => el.id),
|
||||
instance: this,
|
||||
},
|
||||
});
|
||||
this.dispatchEvent(event);
|
||||
this.changed = overallModified;
|
||||
}
|
||||
}
|
||||
|
||||
updateButtonAriaLabel() {
|
||||
if (!this.button) return;
|
||||
|
||||
let labelText = this.getAttribute("button-aria-label");
|
||||
|
||||
if (!labelText) {
|
||||
const controlIds = this._controlledElements.map((el) => el.id);
|
||||
if (controlIds.length === 1 && this._controlledElements[0]) {
|
||||
const controlledEl = this._controlledElements[0];
|
||||
const labelEl = document.querySelector(`label[for="${controlledEl.id}"]`);
|
||||
let fieldName = controlledEl.name || controlledEl.id;
|
||||
if (labelEl && labelEl.textContent) {
|
||||
fieldName = labelEl.textContent.trim().replace(/[:*]$/, "").trim();
|
||||
} else if (controlledEl.getAttribute("aria-label")) {
|
||||
fieldName = controlledEl.getAttribute("aria-label");
|
||||
}
|
||||
labelText = `Reset ${fieldName}`;
|
||||
} else if (controlIds.length > 1) {
|
||||
labelText = "Reset selected fields";
|
||||
} else {
|
||||
labelText = "Reset field";
|
||||
}
|
||||
}
|
||||
this.button.setAttribute("aria-label", labelText);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
@import "tailwindcss";
|
||||
@import "./form.css";
|
||||
@import "./usermgmt.css";
|
||||
|
||||
@theme {
|
||||
--font-script: Rancho, ui-serif;
|
||||
--font-sans: "Source Sans 3", "Merriweather Sans", ui-sans-serif;
|
||||
@@ -516,55 +519,6 @@
|
||||
@apply !text-slate-900 bg-stone-50;
|
||||
}
|
||||
|
||||
.user-chooser a {
|
||||
@apply px-4 py-2 no-underline text-gray-500 hover:text-slate-900 font-serif font-bold border-l-4 border-transparent hover:bg-slate-100 transition-all duration-75 rounded-xs;
|
||||
}
|
||||
|
||||
.user-chooser a[aria-current="page"] {
|
||||
@apply text-slate-900 font-bold bg-slate-100 border-slate-900 shadow-sm;
|
||||
}
|
||||
|
||||
.user-chooser a[aria-current="page"]:nth-child(1) {
|
||||
@apply border-blue-500;
|
||||
}
|
||||
|
||||
.user-chooser a[aria-current="page"]:nth-child(2) {
|
||||
@apply border-orange-600;
|
||||
}
|
||||
|
||||
.user-chooser a[aria-current="page"]:nth-child(3) {
|
||||
@apply border-red-600;
|
||||
}
|
||||
|
||||
.user-mgmt thead th {
|
||||
@apply text-left font-bold border-b-2 border-slate-800 py-2 px-3;
|
||||
}
|
||||
|
||||
.user-mgmt tbody tr td {
|
||||
@apply px-3 py-1.5;
|
||||
}
|
||||
|
||||
.user-mgmt tbody tr:nth-child(odd) td {
|
||||
@apply bg-slate-100;
|
||||
}
|
||||
|
||||
.user-mgmt tbody tr:nth-child(even) td {
|
||||
@apply bg-slate-50;
|
||||
}
|
||||
|
||||
.user-mgmt tbody tr td:last-of-type {
|
||||
@apply align-middle text-right pr-4;
|
||||
}
|
||||
|
||||
.user-mgmt tbody tr.deactivated td:not(:last-of-type) {
|
||||
@apply text-gray-400 line-through;
|
||||
}
|
||||
|
||||
.user-mgmt form button,
|
||||
.user-mgmt .edit-button {
|
||||
@apply bg-slate-700 text-gray-200 text-base rounded-xs font-sans transition-all duration-75 px-3 py-1.5 hover:bg-slate-800 hover:text-white cursor-pointer;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
@@ -576,240 +530,4 @@
|
||||
transform: rotate(360deg);
|
||||
} /* Pause at the final position */
|
||||
}
|
||||
|
||||
/* Multi-Select-Role example styles */
|
||||
.msr-selected-items-container {
|
||||
@apply rounded-md;
|
||||
}
|
||||
.msr-placeholder-no-selection-text {
|
||||
@apply text-sm text-gray-500 italic px-2 py-1;
|
||||
}
|
||||
|
||||
.msr-input-area-wrapper {
|
||||
@apply p-2 rounded-md;
|
||||
}
|
||||
.msr-input-area-wrapper.msr-input-area-default-border {
|
||||
@apply border border-gray-300;
|
||||
}
|
||||
.msr-input-area-wrapper.msr-input-area-default-border:focus-within {
|
||||
@apply focus-within:border-gray-500 focus-within:ring-1 focus-within:ring-gray-400;
|
||||
}
|
||||
.msr-input-area-wrapper.msr-input-area-staged {
|
||||
@apply border border-transparent;
|
||||
}
|
||||
|
||||
.msr-text-input {
|
||||
@apply bg-transparent text-sm placeholder-gray-400;
|
||||
}
|
||||
|
||||
.msr-selected-item-pill {
|
||||
@apply bg-gray-200 text-gray-700 px-3 py-[0.3rem] rounded-md text-sm inline-flex items-center m-0.5;
|
||||
}
|
||||
.msr-item-name {
|
||||
@apply font-medium;
|
||||
}
|
||||
.msr-item-additional-data {
|
||||
@apply text-xs ml-1 text-gray-600;
|
||||
}
|
||||
.msr-selected-item-role {
|
||||
@apply font-semibold text-xs ml-1 text-gray-800;
|
||||
}
|
||||
.msr-selected-item-delete-btn {
|
||||
@apply bg-transparent border-none text-gray-500 text-lg leading-none px-1 cursor-pointer opacity-60 transition-opacity duration-200;
|
||||
}
|
||||
.msr-selected-item-delete-btn:hover {
|
||||
@apply hover:opacity-100 hover:text-gray-900;
|
||||
}
|
||||
|
||||
.msr-staged-item-pill {
|
||||
@apply bg-gray-100 text-gray-800 px-2 py-1 rounded-md text-sm font-medium;
|
||||
}
|
||||
.msr-staged-item-text {
|
||||
@apply mr-2;
|
||||
}
|
||||
|
||||
.msr-staged-role-select {
|
||||
@apply px-2 py-1 text-sm rounded-md border border-gray-300 bg-white outline-none text-gray-700;
|
||||
}
|
||||
.msr-staged-role-select:focus {
|
||||
@apply focus:border-gray-500 focus:ring-1 focus:ring-gray-400;
|
||||
}
|
||||
|
||||
.msr-staged-cancel-btn {
|
||||
@apply w-5 h-5 bg-gray-200 text-gray-600 rounded-full text-sm leading-none cursor-pointer;
|
||||
}
|
||||
.msr-staged-cancel-btn:hover {
|
||||
@apply hover:bg-gray-300 hover:text-gray-800;
|
||||
}
|
||||
|
||||
.msr-pre-add-button {
|
||||
@apply w-10 h-[42px] text-xl rounded-md bg-gray-50 text-gray-700 border border-gray-300 font-semibold outline-none;
|
||||
}
|
||||
.msr-pre-add-button:focus {
|
||||
@apply focus:border-gray-500 focus:ring-1 focus:ring-gray-400;
|
||||
}
|
||||
.msr-pre-add-button:hover {
|
||||
@apply hover:bg-gray-100;
|
||||
}
|
||||
.msr-pre-add-button:disabled {
|
||||
@apply disabled:bg-gray-200 disabled:text-gray-400 disabled:cursor-not-allowed disabled:border-gray-200;
|
||||
}
|
||||
.msr-pre-add-button.hidden {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.msr-add-button {
|
||||
@apply px-4 py-2 text-sm rounded-md bg-gray-600 text-white font-medium;
|
||||
}
|
||||
.msr-add-button:hover {
|
||||
@apply hover:bg-gray-700;
|
||||
}
|
||||
.msr-add-button:disabled {
|
||||
@apply disabled:bg-gray-300 disabled:cursor-not-allowed;
|
||||
}
|
||||
.msr-add-button.hidden {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.msr-options-list {
|
||||
@apply bg-white border border-gray-300 rounded-md shadow-md;
|
||||
}
|
||||
.msr-options-list.hidden {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.msr-option-item {
|
||||
@apply px-3 py-2 text-sm cursor-pointer transition-colors duration-75;
|
||||
}
|
||||
.msr-option-item:hover {
|
||||
@apply bg-gray-100 text-gray-800;
|
||||
}
|
||||
.msr-option-item-highlighted {
|
||||
@apply bg-gray-100 text-gray-800;
|
||||
}
|
||||
.msr-option-item-name {
|
||||
@apply font-medium;
|
||||
}
|
||||
.msr-option-item-detail {
|
||||
@apply text-xs ml-2 text-gray-500;
|
||||
}
|
||||
.msr-option-item-highlighted .msr-option-item-detail,
|
||||
.msr-option-item:hover .msr-option-item-detail {
|
||||
/* Ensure detail text color changes on hover too */
|
||||
@apply text-gray-600;
|
||||
}
|
||||
|
||||
multi-select-role[disabled] {
|
||||
/* This remains standard CSS as Tailwind's disabled: variant is for native elements */
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.msr-hidden-select {
|
||||
/* No specific styling needed as it's visually hidden by JS/inline style */
|
||||
}
|
||||
|
||||
/* --- MultiSelectSimple Component Base Styles (using @apply) --- */
|
||||
.mss-component-wrapper {
|
||||
/* 'relative' is set inline for positioning dropdown */
|
||||
}
|
||||
|
||||
.mss-selected-items-container {
|
||||
@apply border border-gray-300 p-1.5 rounded;
|
||||
/* Tailwind classes from component: flex flex-wrap gap-1 mb-1 min-h-[38px] */
|
||||
}
|
||||
|
||||
.mss-no-items-text {
|
||||
@apply italic text-xs text-gray-500 p-1 w-full; /* Adjusted font size slightly to match 'xs' */
|
||||
}
|
||||
|
||||
.mss-selected-item-pill {
|
||||
@apply bg-gray-200 text-gray-800 py-0.5 px-2 rounded text-xs leading-5; /* Adjusted font size and padding */
|
||||
/* Tailwind classes from component: flex items-center */
|
||||
}
|
||||
|
||||
.mss-selected-item-text {
|
||||
/* Base styles for text part of the pill */
|
||||
}
|
||||
.mss-selected-item-pill-detail {
|
||||
@apply ml-1 opacity-75 text-xs text-gray-600;
|
||||
}
|
||||
.mss-selected-item-pill-detail.hidden {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.mss-selected-item-delete-btn {
|
||||
@apply bg-transparent border-none text-gray-600 opacity-70 cursor-pointer ml-1 text-base leading-none align-middle hover:opacity-100 hover:text-gray-900 disabled:opacity-40 disabled:cursor-not-allowed;
|
||||
}
|
||||
|
||||
.mss-input-controls-container {
|
||||
/* Tailwind classes from component: flex items-center space-x-2 */
|
||||
}
|
||||
|
||||
.mss-input-wrapper {
|
||||
@apply border border-gray-300 rounded;
|
||||
/* Tailwind classes from component: relative flex items-center flex-grow */
|
||||
}
|
||||
.mss-input-wrapper-focused {
|
||||
@apply border-indigo-600 ring-1 ring-indigo-600; /* Using ring for focus shadow */
|
||||
}
|
||||
|
||||
.mss-text-input {
|
||||
@apply py-1.5 px-2 text-sm;
|
||||
/* Tailwind classes from component: w-full outline-none bg-transparent */
|
||||
}
|
||||
.mss-text-input::placeholder {
|
||||
@apply text-gray-400 italic;
|
||||
}
|
||||
|
||||
.mss-create-new-button {
|
||||
@apply bg-gray-100 text-gray-700 border border-gray-300 py-1 px-1.5 text-sm rounded hover:bg-gray-200 hover:border-gray-400 disabled:bg-gray-50 disabled:text-gray-400 disabled:border-gray-200 disabled:opacity-70 disabled:cursor-not-allowed;
|
||||
}
|
||||
.mss-create-new-button.hidden {
|
||||
@apply !hidden; /* Ensure it hides */
|
||||
}
|
||||
|
||||
.mss-options-list {
|
||||
@apply bg-white border border-gray-300 rounded shadow-md; /* Using shadow-md as a softer default */
|
||||
/* Tailwind classes from component: absolute z-20 w-full max-h-60 overflow-y-auto mt-1 hidden */
|
||||
}
|
||||
|
||||
.mss-option-item {
|
||||
@apply text-gray-700 py-1.5 px-2.5 text-sm cursor-pointer transition-colors duration-75 hover:bg-gray-100;
|
||||
}
|
||||
|
||||
.mss-option-item-name {
|
||||
@apply font-medium;
|
||||
}
|
||||
|
||||
.mss-option-item-detail {
|
||||
@apply text-gray-500 text-xs ml-1.5;
|
||||
}
|
||||
|
||||
.mss-option-item-highlighted {
|
||||
@apply bg-indigo-100 text-indigo-800;
|
||||
}
|
||||
.mss-option-item-highlighted .mss-option-item-name {
|
||||
/* @apply font-medium; */ /* Already set by .mss-option-item-name, inherit color from parent */
|
||||
}
|
||||
.mss-option-item-highlighted .mss-option-item-detail {
|
||||
@apply text-indigo-700;
|
||||
}
|
||||
|
||||
.mss-hidden-select {
|
||||
/* Styles are inline in _render for !important, no change needed here */
|
||||
}
|
||||
|
||||
multi-select-simple[disabled] {
|
||||
@apply opacity-60; /* Adjusted opacity */
|
||||
}
|
||||
multi-select-simple[disabled] .mss-selected-items-container {
|
||||
@apply bg-gray-100;
|
||||
}
|
||||
multi-select-simple[disabled] .mss-selected-item-pill {
|
||||
@apply bg-gray-300 text-gray-500;
|
||||
}
|
||||
multi-select-simple[disabled] .mss-selected-item-delete-btn {
|
||||
@apply text-gray-400;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export class TabList extends HTMLElement {
|
||||
this.shown = -1;
|
||||
this._headings = [];
|
||||
this._contents = [];
|
||||
this._checkbox = null;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
@@ -38,6 +39,19 @@ export class TabList extends HTMLElement {
|
||||
});
|
||||
}
|
||||
|
||||
hookupShowAll(checkbox) {
|
||||
if (checkbox) {
|
||||
this._checkbox = checkbox;
|
||||
checkbox.addEventListener("change", (event) => {
|
||||
if (event.target.checked) {
|
||||
this.showAll();
|
||||
} else {
|
||||
this.default();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
hookupEvtHandlers() {
|
||||
for (let heading of this._headings) {
|
||||
heading.addEventListener("click", this.handleTabClick.bind(this));
|
||||
|
||||
50
views/transform/usermgmt.css
Normal file
50
views/transform/usermgmt.css
Normal file
@@ -0,0 +1,50 @@
|
||||
@layer components {
|
||||
.user-chooser a {
|
||||
@apply px-4 py-2 no-underline text-gray-500 hover:text-slate-900 font-serif font-bold border-l-4 border-transparent hover:bg-slate-100 transition-all duration-75 rounded-xs;
|
||||
}
|
||||
|
||||
.user-chooser a[aria-current="page"] {
|
||||
@apply text-slate-900 font-bold bg-slate-100 border-slate-900 shadow-sm;
|
||||
}
|
||||
|
||||
.user-chooser a[aria-current="page"]:nth-child(1) {
|
||||
@apply border-blue-500;
|
||||
}
|
||||
|
||||
.user-chooser a[aria-current="page"]:nth-child(2) {
|
||||
@apply border-orange-600;
|
||||
}
|
||||
|
||||
.user-chooser a[aria-current="page"]:nth-child(3) {
|
||||
@apply border-red-600;
|
||||
}
|
||||
|
||||
.user-mgmt thead th {
|
||||
@apply text-left font-bold border-b-2 border-slate-800 py-2 px-3;
|
||||
}
|
||||
|
||||
.user-mgmt tbody tr td {
|
||||
@apply px-3 py-1.5;
|
||||
}
|
||||
|
||||
.user-mgmt tbody tr:nth-child(odd) td {
|
||||
@apply bg-slate-100;
|
||||
}
|
||||
|
||||
.user-mgmt tbody tr:nth-child(even) td {
|
||||
@apply bg-slate-50;
|
||||
}
|
||||
|
||||
.user-mgmt tbody tr td:last-of-type {
|
||||
@apply align-middle text-right pr-4;
|
||||
}
|
||||
|
||||
.user-mgmt tbody tr.deactivated td:not(:last-of-type) {
|
||||
@apply text-gray-400 line-through;
|
||||
}
|
||||
|
||||
.user-mgmt form button,
|
||||
.user-mgmt .edit-button {
|
||||
@apply bg-slate-700 text-gray-200 text-base rounded-xs font-sans transition-all duration-75 px-3 py-1.5 hover:bg-slate-800 hover:text-white cursor-pointer;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user