castopod/resources/js/_modules/FieldArray.ts

160 lines
4.7 KiB
TypeScript
Raw Normal View History

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;