This commit is contained in:
@@ -16,6 +16,7 @@ import { aggregateSelectedDaysIngredientNeed } from '../services/planIngredients
|
||||
import { loadPlans, dateKey } from '../services/planStore.js?v=2';
|
||||
import { addDays, startOfDay, startOfMonth } from '../services/dateUtils.js';
|
||||
import { createSwipePopoverCalendarHTML, initSwipePopoverCalendar } from '../ui/swipePopoverCalendar.js';
|
||||
import { createCalendarPopoverController, createCalendarPopoverHTML } from '../ui/calendarPopover.js';
|
||||
import { showAppToast } from '../ui/toast.js';
|
||||
|
||||
/* ── helpers ── */
|
||||
@@ -64,6 +65,7 @@ let expandedAmount = 0;
|
||||
let calendarOpen = false;
|
||||
let calendarMonth = startOfMonth(new Date());
|
||||
let shoppingCalendar = null;
|
||||
let shoppingCalendarPopover = null;
|
||||
|
||||
/* ── day helpers ── */
|
||||
|
||||
@@ -133,14 +135,13 @@ export function getShoppingListHTML() {
|
||||
</button>
|
||||
|
||||
<!-- popup calendar (absolute, overlays content below) -->
|
||||
<div id="sl-calendar-popup" style="position:absolute; top:calc(100% + 0.5rem); left:0; right:0; z-index:50; pointer-events:none; opacity:0; transform:translateY(-6px) scale(0.98); transition: opacity 0.2s ease, transform 0.2s ease;">
|
||||
<div class="rounded-[1.35rem] py-3" style="background:rgb(var(--sunken-rgb)); border:1px solid rgb(var(--border-input-rgb)); box-shadow:var(--shadow-shell);">
|
||||
${createSwipePopoverCalendarHTML({
|
||||
${createCalendarPopoverHTML({
|
||||
id: 'sl-calendar-popup',
|
||||
calendarHTML: createSwipePopoverCalendarHTML({
|
||||
idPrefix: 'sl-cal',
|
||||
weekdays: WEEKDAY_SHORT,
|
||||
}),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- popup bought (absolute, overlays content below) -->
|
||||
<div id="sl-bought-popup" style="position:absolute; top:calc(100% + 0.5rem); left:0; right:0; z-index:50; pointer-events:none; opacity:0; transform:translateY(-6px) scale(0.98); transition: opacity 0.2s ease, transform 0.2s ease;">
|
||||
@@ -201,6 +202,14 @@ function initShoppingCalendar() {
|
||||
dot: 'rgb(var(--text-faint-rgb))',
|
||||
},
|
||||
});
|
||||
shoppingCalendarPopover = createCalendarPopoverController({
|
||||
popupId: 'sl-calendar-popup',
|
||||
viewportId: 'sl-cal-viewport',
|
||||
triggerId: 'sl-range-pill',
|
||||
chevronId: 'sl-range-chevron',
|
||||
getCalendar: () => shoppingCalendar,
|
||||
hideViewportDuringLayout: true,
|
||||
});
|
||||
}
|
||||
|
||||
function updatePillLabel() {
|
||||
@@ -212,64 +221,12 @@ function openCalendar() {
|
||||
if (boughtPopupOpen) closeBoughtPopup();
|
||||
calendarOpen = true;
|
||||
calendarMonth = startOfMonth(new Date());
|
||||
const popup = document.getElementById('sl-calendar-popup');
|
||||
const viewport = document.getElementById('sl-cal-viewport');
|
||||
const chevron = document.getElementById('sl-range-chevron');
|
||||
const pill = document.getElementById('sl-range-pill');
|
||||
if (popup) {
|
||||
popup.style.pointerEvents = 'auto';
|
||||
popup.style.opacity = '1';
|
||||
popup.style.transform = 'translateY(0) scale(1)';
|
||||
}
|
||||
if (chevron) chevron.style.transform = 'rotate(180deg)';
|
||||
if (pill) {
|
||||
pill.style.background = 'rgb(var(--sunken-rgb))';
|
||||
pill.style.borderColor = 'rgb(var(--border-input-rgb))';
|
||||
}
|
||||
if (viewport) {
|
||||
viewport.style.opacity = '0';
|
||||
viewport.style.visibility = 'hidden';
|
||||
viewport.style.transition = 'opacity 120ms ease';
|
||||
}
|
||||
shoppingCalendar?.render();
|
||||
// Compute geometry while hidden; reveal only after stable layout.
|
||||
const ensureStableCalendarLayout = (attempt = 0) => {
|
||||
const vw = viewport ? (viewport.clientWidth || viewport.getBoundingClientRect().width) : 0;
|
||||
if (vw < 8 && attempt < 8) {
|
||||
requestAnimationFrame(() => ensureStableCalendarLayout(attempt + 1));
|
||||
return;
|
||||
}
|
||||
shoppingCalendar?.reapplyLayout();
|
||||
shoppingCalendar?.resetTrackPosition();
|
||||
requestAnimationFrame(() => {
|
||||
shoppingCalendar?.reapplyLayout();
|
||||
shoppingCalendar?.resetTrackPosition();
|
||||
if (viewport) {
|
||||
viewport.style.visibility = 'visible';
|
||||
viewport.style.opacity = '1';
|
||||
}
|
||||
});
|
||||
};
|
||||
requestAnimationFrame(() => ensureStableCalendarLayout());
|
||||
shoppingCalendarPopover?.open();
|
||||
}
|
||||
|
||||
function closeCalendar() {
|
||||
calendarOpen = false;
|
||||
const popup = document.getElementById('sl-calendar-popup');
|
||||
const chevron = document.getElementById('sl-range-chevron');
|
||||
const pill = document.getElementById('sl-range-pill');
|
||||
if (popup) {
|
||||
popup.style.pointerEvents = 'none';
|
||||
popup.style.opacity = '0';
|
||||
popup.style.transform = 'translateY(-6px) scale(0.98)';
|
||||
}
|
||||
if (chevron) chevron.style.transform = '';
|
||||
if (pill) {
|
||||
pill.style.background = 'rgb(var(--card-rgb))';
|
||||
pill.style.borderColor = 'rgb(var(--border-card-rgb))';
|
||||
}
|
||||
shoppingCalendar?.clearPendingRange?.();
|
||||
shoppingCalendar?.resetTrackPosition();
|
||||
shoppingCalendarPopover?.close({ clearPendingRange: true });
|
||||
}
|
||||
|
||||
function toggleCalendar() {
|
||||
@@ -327,11 +284,11 @@ function activeItemHtml(item) {
|
||||
const stepAmt = isExpanded ? expandedAmount : Math.max(step, Math.round(item.shortfall / step) * step);
|
||||
|
||||
return `
|
||||
<div class="sl-swipe-wrap relative rounded-xl mb-1.5 overflow-hidden" style="background:rgb(var(--success-rgb)); box-shadow:var(--shadow-card);">
|
||||
<div class="sl-swipe-bg-buy absolute inset-0 flex items-center pr-5 justify-end">
|
||||
<div class="sl-swipe-wrap relative rounded-xl mb-1.5 overflow-hidden" style="box-shadow:var(--shadow-card);">
|
||||
<div class="sl-swipe-bg-buy absolute inset-0 flex items-center pr-5 justify-end" style="background:rgb(var(--success-rgb)); opacity:0;">
|
||||
<i class="fas fa-check text-white text-lg"></i>
|
||||
</div>
|
||||
<div class="sl-swipe-inner rounded-xl" style="background:rgb(var(--card-rgb)); position:relative;"
|
||||
<div class="sl-swipe-inner" style="background:rgb(var(--card-rgb)); position:relative;"
|
||||
data-id="${esc(item.ingredientId)}" data-unit="${esc(item.unit)}" data-shortfall="${item.shortfall}">
|
||||
<div class="sl-item-main flex items-center gap-3 py-1.5 px-3 cursor-pointer select-none">
|
||||
${mediaHtml}
|
||||
@@ -371,11 +328,11 @@ function boughtItemHtml(entry) {
|
||||
: `<div class="w-8 h-8 rounded-lg flex items-center justify-center shrink-0" style="background:rgb(var(--card-soft-rgb));"><i class="fas ${icon} text-xs" style="color:rgb(var(--text-faint-rgb));"></i></div>`;
|
||||
|
||||
return `
|
||||
<div class="sl-swipe-wrap relative rounded-xl mb-1.5 overflow-hidden" style="background:rgb(var(--danger-rgb)); box-shadow:var(--shadow-card);">
|
||||
<div class="sl-swipe-bg-undo absolute inset-0 flex items-center pl-5 justify-start">
|
||||
<div class="sl-swipe-wrap relative rounded-xl mb-1.5 overflow-hidden" style="box-shadow:var(--shadow-card);">
|
||||
<div class="sl-swipe-bg-undo absolute inset-0 flex items-center pl-5 justify-start" style="background:rgb(var(--danger-rgb)); opacity:0;">
|
||||
<i class="fas fa-rotate-left text-white text-lg"></i>
|
||||
</div>
|
||||
<div class="sl-swipe-inner flex items-center gap-3 py-1.5 px-3 rounded-xl" style="background:rgb(var(--card-rgb)); position:relative;" data-entry-id="${esc(entry.id)}">
|
||||
<div class="sl-swipe-inner flex items-center gap-3 py-1.5 px-3" style="background:rgb(var(--card-rgb)); position:relative;" data-entry-id="${esc(entry.id)}">
|
||||
${mediaHtml}
|
||||
<div class="flex-1 min-w-0">
|
||||
<span class="block text-[13px] font-medium leading-tight truncate" style="color:rgb(var(--text-body-rgb));">${esc(entry.name)}</span>
|
||||
@@ -390,6 +347,10 @@ function boughtItemHtml(entry) {
|
||||
function attachSwipe(container, opts) {
|
||||
const inner = container.querySelector('.sl-swipe-inner');
|
||||
if (!inner) return;
|
||||
const bgBuy = container.querySelector('.sl-swipe-bg-buy');
|
||||
const bgUndo = container.querySelector('.sl-swipe-bg-undo');
|
||||
const showBg = (el) => { if (el) el.style.opacity = '1'; };
|
||||
const hideBgs = () => { if (bgBuy) bgBuy.style.opacity = '0'; if (bgUndo) bgUndo.style.opacity = '0'; };
|
||||
let startX = 0, startY = 0, dx = 0, tracking = false, decided = false, goingH = false;
|
||||
|
||||
container.addEventListener('pointerdown', (e) => {
|
||||
@@ -408,10 +369,10 @@ function attachSwipe(container, opts) {
|
||||
decided = true;
|
||||
goingH = Math.abs(ddx) > Math.abs(ddy);
|
||||
}
|
||||
if (!goingH) { tracking = false; inner.style.transform = ''; return; }
|
||||
if (!goingH) { tracking = false; inner.style.transform = ''; hideBgs(); return; }
|
||||
dx = ddx;
|
||||
if (dx > 0 && opts.onRight) inner.style.transform = `translateX(${Math.min(dx, 90)}px)`;
|
||||
else if (dx < 0 && opts.onLeft) inner.style.transform = `translateX(${Math.max(dx, -90)}px)`;
|
||||
if (dx > 0 && opts.onRight) { inner.style.transform = `translateX(${Math.min(dx, 90)}px)`; showBg(bgBuy); }
|
||||
else if (dx < 0 && opts.onLeft) { inner.style.transform = `translateX(${Math.max(dx, -90)}px)`; showBg(bgUndo); }
|
||||
});
|
||||
|
||||
const finish = () => {
|
||||
@@ -427,6 +388,7 @@ function attachSwipe(container, opts) {
|
||||
setTimeout(opts.onLeft, 180);
|
||||
} else {
|
||||
inner.style.transform = '';
|
||||
hideBgs();
|
||||
}
|
||||
dx = 0;
|
||||
};
|
||||
@@ -436,6 +398,7 @@ function attachSwipe(container, opts) {
|
||||
tracking = false;
|
||||
inner.style.transition = 'transform 0.2s ease';
|
||||
inner.style.transform = '';
|
||||
hideBgs();
|
||||
dx = 0;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user