mirror of
https://code.castopod.org/adaures/castopod
synced 2025-04-23 01:01:20 +00:00
160 lines
4.7 KiB
TypeScript
160 lines
4.7 KiB
TypeScript
![]() |
import Tooltip from "./Tooltip";
|
||
|
|
||
|
const FieldArray = (): void => {
|
||
|
const fieldArrays: NodeListOf<HTMLElement> =
|
||
|
document.querySelectorAll("[data-field-array]");
|
||
|
|
||
|
for (let i = 0; i < fieldArrays.length; i++) {
|
||
|
const fieldArray = fieldArrays[i];
|
||
|
const fieldArrayContainer = fieldArray.querySelector(
|
||
|
"[data-field-array-container]"
|
||
|
);
|
||
|
const items: NodeListOf<HTMLElement> = fieldArray.querySelectorAll(
|
||
|
"[data-field-array-item]"
|
||
|
);
|
||
|
const addButton = fieldArray.querySelector(
|
||
|
"button[data-field-array-add]"
|
||
|
) as HTMLButtonElement;
|
||
|
|
||
|
const deleteButtons: NodeListOf<HTMLButtonElement> =
|
||
|
fieldArray.querySelectorAll("[data-field-array-delete]");
|
||
|
|
||
|
deleteButtons.forEach((deleteBtn) => {
|
||
|
deleteBtn.addEventListener("click", (e) => {
|
||
|
e.preventDefault();
|
||
|
deleteBtn.blur();
|
||
|
fieldArrayContainer
|
||
|
?.querySelector(
|
||
|
`[data-field-array-item="${deleteBtn.dataset.fieldArrayDelete}"]`
|
||
|
)
|
||
|
?.remove();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
// create base element to clone
|
||
|
const baseItem = items[0].cloneNode(true) as HTMLElement;
|
||
|
|
||
|
const elements: NodeListOf<HTMLFormElement> = baseItem.querySelectorAll(
|
||
|
"input, select, textarea"
|
||
|
);
|
||
|
|
||
|
elements.forEach((element) => {
|
||
|
element.value = "";
|
||
|
});
|
||
|
|
||
|
if (fieldArrayContainer && addButton) {
|
||
|
addButton.addEventListener("click", (event) => {
|
||
|
event.preventDefault();
|
||
|
|
||
|
const newItem = baseItem.cloneNode(true) as HTMLElement;
|
||
|
|
||
|
const deleteBtn: HTMLButtonElement | null = newItem.querySelector(
|
||
|
"button[data-field-array-delete]"
|
||
|
);
|
||
|
|
||
|
if (deleteBtn) {
|
||
|
deleteBtn.addEventListener("click", () => {
|
||
|
deleteBtn.blur();
|
||
|
newItem.remove();
|
||
|
});
|
||
|
|
||
|
fieldArrayContainer.appendChild(newItem);
|
||
|
newItem.scrollIntoView({
|
||
|
behavior: "auto",
|
||
|
block: "center",
|
||
|
inline: "center",
|
||
|
});
|
||
|
|
||
|
// reload tooltip module for showing remove button label
|
||
|
Tooltip();
|
||
|
|
||
|
// focus to first form element if mouse click
|
||
|
if (event.screenX !== 0 && event.screenY !== 0) {
|
||
|
const elements: NodeListOf<HTMLFormElement> =
|
||
|
newItem.querySelectorAll("input, select, textarea");
|
||
|
|
||
|
if (elements.length > 0) {
|
||
|
elements[0].focus();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
const updateIndexes = () => {
|
||
|
// get last child item to set item count
|
||
|
const items: NodeListOf<HTMLElement> =
|
||
|
fieldArrayContainer.querySelectorAll("[data-field-array-item]");
|
||
|
|
||
|
let itemIndex = 0;
|
||
|
items.forEach((item) => {
|
||
|
const itemNumber: HTMLElement | null = item.querySelector(
|
||
|
"[data-field-array-number]"
|
||
|
);
|
||
|
|
||
|
if (itemNumber) {
|
||
|
itemNumber.innerHTML = "#";
|
||
|
const indexNum = itemIndex + 1;
|
||
|
if (item.dataset.fieldArrayItem !== itemIndex.toString()) {
|
||
|
item.classList.add("motion-safe:animate-single-pulse");
|
||
|
setTimeout(() => {
|
||
|
item.classList.remove("motion-safe:animate-single-pulse");
|
||
|
itemNumber.innerHTML = indexNum.toString();
|
||
|
}, 300);
|
||
|
} else {
|
||
|
itemNumber.innerHTML = indexNum.toString();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
item.dataset.fieldArrayItem = itemIndex.toString();
|
||
|
const deleteBtn = item.querySelector(
|
||
|
"button[data-field-array-delete]"
|
||
|
) as HTMLButtonElement | null;
|
||
|
|
||
|
if (deleteBtn) {
|
||
|
deleteBtn.dataset.fieldArrayDelete = itemIndex.toString();
|
||
|
}
|
||
|
|
||
|
const itemElements: NodeListOf<HTMLFormElement> =
|
||
|
item.querySelectorAll("input, select, textarea");
|
||
|
|
||
|
itemElements.forEach((element) => {
|
||
|
const label: HTMLLabelElement | null = item.querySelector(
|
||
|
`label[for="${element.id}"]`
|
||
|
);
|
||
|
|
||
|
const elementID = element.name.replace(
|
||
|
/(.*\[)\d+?(\].*)/g,
|
||
|
`$1${itemIndex}$2`
|
||
|
);
|
||
|
|
||
|
if (label) {
|
||
|
label.htmlFor = elementID;
|
||
|
}
|
||
|
|
||
|
element.id = elementID;
|
||
|
element.name = elementID;
|
||
|
});
|
||
|
|
||
|
itemIndex++;
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// add mutation observer to run index updates when field array
|
||
|
// items are added or removed
|
||
|
const callback = function (mutationList: MutationRecord[]) {
|
||
|
for (const mutation of mutationList) {
|
||
|
if (mutation.type === "childList") {
|
||
|
updateIndexes();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const observer = new MutationObserver(callback);
|
||
|
|
||
|
observer.observe(fieldArrayContainer, { childList: true });
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
export default FieldArray;
|