swipe, zoeken op producten, beter toevoegen in lijsten
This commit is contained in:
+72
-4
@@ -4,6 +4,11 @@ body {
|
||||
|
||||
.navbar {
|
||||
margin-bottom: 25px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1030;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, .08);
|
||||
}
|
||||
|
||||
.card {
|
||||
@@ -47,15 +52,78 @@ body {
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
|
||||
gap: .5rem;
|
||||
margin-top: 1.5rem;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.list-check-button {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
width: 3.25rem;
|
||||
height: 3.25rem;
|
||||
padding: 0;
|
||||
font-size: 1.35rem;
|
||||
font-size: 1.7rem;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
align-items: stretch;
|
||||
|
||||
.shopping-list-item {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
transition: transform .2s ease;
|
||||
touch-action: pan-y;
|
||||
}
|
||||
|
||||
.shopping-list-item:hover,
|
||||
.shopping-list-item:focus-visible {
|
||||
background-color: rgba(32, 107, 196, .06);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.list-item-amount {
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.shopping-list-swipe {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.swipe-action {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 7rem;
|
||||
padding: 0 1rem;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
opacity: 0;
|
||||
transition: opacity .15s ease;
|
||||
gap: .4rem;
|
||||
}
|
||||
|
||||
.shopping-list-swipe.is-swiping.swipe-left .swipe-action-check,
|
||||
.shopping-list-swipe.is-swiping.swipe-right .swipe-action-delete {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.swipe-action-delete {
|
||||
left: 0;
|
||||
right: auto;
|
||||
justify-content: flex-start;
|
||||
background: #d63939;
|
||||
}
|
||||
|
||||
.swipe-action-check {
|
||||
right: 0;
|
||||
left: auto;
|
||||
justify-content: flex-end;
|
||||
background: #2fb344;
|
||||
}
|
||||
|
||||
.swipe-action-icon {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.product-actions form,
|
||||
|
||||
@@ -1,4 +1,141 @@
|
||||
(() => {
|
||||
document.querySelectorAll("[data-product-filter]").forEach((filter) => {
|
||||
const search = filter.querySelector("[data-product-search]");
|
||||
const category = filter.querySelector("[data-product-category]");
|
||||
const list = filter.closest(".card")?.querySelector("[data-product-list]");
|
||||
const rows = list ? [...list.querySelectorAll("[data-product-row]")] : [];
|
||||
const select = filter.querySelector("[data-product-select]");
|
||||
const options = select ? [...select.querySelectorAll("[data-product-option]")] : [];
|
||||
const empty = list?.parentElement.querySelector("[data-product-empty]");
|
||||
|
||||
const update = () => {
|
||||
const query = (search?.value || "").trim().toLowerCase();
|
||||
const selectedCategory = category?.value || "";
|
||||
const matches = (element) => {
|
||||
const nameMatches = !query || element.dataset.productName.includes(query);
|
||||
const categoryMatches = !selectedCategory || element.dataset.productCategory === selectedCategory;
|
||||
return nameMatches && categoryMatches;
|
||||
};
|
||||
|
||||
rows.forEach((row) => { row.classList.toggle("d-none", !matches(row)); });
|
||||
options.forEach((option) => { option.hidden = !matches(option); });
|
||||
if (empty) empty.classList.toggle("d-none", rows.some((row) => !row.classList.contains("d-none")));
|
||||
};
|
||||
|
||||
search?.addEventListener("input", update);
|
||||
category?.addEventListener("change", update);
|
||||
update();
|
||||
});
|
||||
|
||||
const amountModal = document.getElementById("amount-modal");
|
||||
const amountForm = document.getElementById("amount-form");
|
||||
const amountInput = document.getElementById("modal-amount");
|
||||
const amountProductName = document.getElementById("amount-product-name");
|
||||
|
||||
if (amountModal && amountForm && amountInput && amountProductName) {
|
||||
let backdrop;
|
||||
|
||||
const closeAmountModal = () => {
|
||||
amountModal.classList.remove("show");
|
||||
amountModal.style.display = "none";
|
||||
amountModal.setAttribute("aria-hidden", "true");
|
||||
document.body.classList.remove("modal-open");
|
||||
backdrop?.remove();
|
||||
};
|
||||
|
||||
const openAmountModal = (item) => {
|
||||
amountForm.action = `/lists/${window.location.pathname.split("/")[2]}/items/${item.dataset.itemId}/amount`;
|
||||
amountProductName.textContent = item.dataset.productName;
|
||||
amountInput.value = item.dataset.itemAmount;
|
||||
amountModal.classList.add("show");
|
||||
amountModal.style.display = "block";
|
||||
amountModal.setAttribute("aria-hidden", "false");
|
||||
document.body.classList.add("modal-open");
|
||||
backdrop = document.createElement("div");
|
||||
backdrop.className = "modal-backdrop fade show";
|
||||
backdrop.addEventListener("click", closeAmountModal);
|
||||
document.body.append(backdrop);
|
||||
amountInput.focus();
|
||||
};
|
||||
|
||||
document.querySelectorAll(".shopping-list-item").forEach((item) => {
|
||||
const swipeContainer = item.closest(".shopping-list-swipe");
|
||||
let touchStartX = 0;
|
||||
let touchStartY = 0;
|
||||
let swiped = false;
|
||||
let touchMoved = false;
|
||||
|
||||
item.addEventListener("click", (event) => {
|
||||
if (swiped) {
|
||||
swiped = false;
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (event.target.closest("form, button, a, input, select")) return;
|
||||
openAmountModal(item);
|
||||
});
|
||||
item.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
openAmountModal(item);
|
||||
}
|
||||
});
|
||||
|
||||
item.addEventListener("touchstart", (event) => {
|
||||
const touch = event.changedTouches[0];
|
||||
touchStartX = touch.clientX;
|
||||
touchStartY = touch.clientY;
|
||||
swiped = false;
|
||||
touchMoved = false;
|
||||
swipeContainer?.classList.remove("is-swiping");
|
||||
swipeContainer?.classList.remove("swipe-left", "swipe-right");
|
||||
}, { passive: true });
|
||||
|
||||
item.addEventListener("touchmove", (event) => {
|
||||
const touch = event.changedTouches[0];
|
||||
const deltaX = touch.clientX - touchStartX;
|
||||
const deltaY = touch.clientY - touchStartY;
|
||||
if (Math.abs(deltaX) <= Math.abs(deltaY) || Math.abs(deltaX) < 8) return;
|
||||
|
||||
touchMoved = true;
|
||||
event.preventDefault();
|
||||
swipeContainer?.classList.add("is-swiping");
|
||||
swipeContainer?.classList.toggle("swipe-left", deltaX < 0);
|
||||
swipeContainer?.classList.toggle("swipe-right", deltaX > 0);
|
||||
item.style.transform = `translateX(${Math.max(-120, Math.min(120, deltaX))}px)`;
|
||||
}, { passive: false });
|
||||
|
||||
item.addEventListener("touchend", (event) => {
|
||||
const touch = event.changedTouches[0];
|
||||
const deltaX = touch.clientX - touchStartX;
|
||||
const deltaY = touch.clientY - touchStartY;
|
||||
item.style.transform = "";
|
||||
swipeContainer?.classList.remove("is-swiping");
|
||||
swipeContainer?.classList.remove("swipe-left", "swipe-right");
|
||||
if (!touchMoved || Math.abs(deltaX) < 70 || Math.abs(deltaX) < Math.abs(deltaY)) return;
|
||||
|
||||
swiped = true;
|
||||
event.preventDefault();
|
||||
if (deltaX < 0) {
|
||||
fetch(item.dataset.toggleUrl, { method: "POST" }).then((response) => {
|
||||
if (response.ok) window.location.reload();
|
||||
});
|
||||
} else if (confirm("Product verwijderen?")) {
|
||||
fetch(item.dataset.deleteUrl, { method: "DELETE" }).then((response) => {
|
||||
if (response.ok) window.location.reload();
|
||||
});
|
||||
}
|
||||
}, { passive: false });
|
||||
});
|
||||
|
||||
amountModal.querySelectorAll("[data-bs-dismiss='modal']").forEach((button) => {
|
||||
button.addEventListener("click", closeAmountModal);
|
||||
});
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape" && amountModal.classList.contains("show")) closeAmountModal();
|
||||
});
|
||||
}
|
||||
|
||||
const logoutLink = document.querySelector('a[href="/logout"]');
|
||||
if (!logoutLink) return;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const CACHE_NAME = "pantryhub-static-v3";
|
||||
const CACHE_NAME = "pantryhub-static-v8";
|
||||
|
||||
self.addEventListener("install", () => {
|
||||
self.skipWaiting();
|
||||
|
||||
Reference in New Issue
Block a user