import Tooltip from "./Tooltip"; const FieldArray = (): void => { const fieldArrays: NodeListOf = 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 = fieldArray.querySelectorAll( "[data-field-array-item]" ); const addButton = fieldArray.querySelector( "button[data-field-array-add]" ) as HTMLButtonElement; const deleteButtons: NodeListOf = 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 = 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 = 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 = 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 = 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;