From c43b3766cd1b4df9e0e78b7050252ac4ec916caf Mon Sep 17 00:00:00 2001 From: ulfrxdev Date: Mon, 20 Apr 2026 23:04:28 +0200 Subject: [PATCH] Fix calendar styling --- js/ui/swipePopoverCalendar.js | 181 ++++++++++++++-------------------- js/views/Pantry.js | 4 +- js/views/ShoppingList.js | 7 +- 3 files changed, 82 insertions(+), 110 deletions(-) diff --git a/js/ui/swipePopoverCalendar.js b/js/ui/swipePopoverCalendar.js index 74d1f6f..05cb83e 100644 --- a/js/ui/swipePopoverCalendar.js +++ b/js/ui/swipePopoverCalendar.js @@ -7,12 +7,12 @@ const DEFAULT_MONTHS_LONG = [ ]; const DEFAULT_THEME = { - selectedBg: 'rgb(var(--card-rgb))', - selectedBorder: 'rgb(var(--border-input-rgb))', + selectedBorder: 'rgba(var(--text-emphasis-rgb),0.34)', selectedText: 'rgb(var(--text-emphasis-rgb))', selectedDot: 'rgb(var(--text-emphasis-rgb))', + selectedShadow: '0 0 0 1px rgba(var(--text-emphasis-rgb),0.10)', bg: 'rgb(var(--app-bg-rgb))', - border: 'rgb(var(--card-raised-rgb))', + border: 'transparent', text: 'rgb(var(--text-body-soft-rgb))', dimmedBg: 'transparent', dimText: 'rgb(var(--text-faint-rgb))', @@ -58,20 +58,6 @@ export function createSwipePopoverCalendarHTML({ ${weekdays.map((d) => `
${d}
`).join('')} `; - const gripLeft = ` - - `; - const gripRight = ` - - `; return `
@@ -79,25 +65,19 @@ export function createSwipePopoverCalendarHTML({
-
+
${weekdayHeader}
- ${gripLeft} - ${gripRight}
${weekdayHeader}
- ${gripLeft} - ${gripRight}
${weekdayHeader}
- ${gripLeft} - ${gripRight}
@@ -138,50 +118,37 @@ export function initSwipePopoverCalendar({ const SWIPE_THRESHOLD = 40; const ANIMATION_MS = 260; - let viewportWidth = 0; let panelWidth = 0; - let panelHandle = 0; - let dragHandleWidth = 0; + let panelInset = 0; let restOffset = 0; let animatingNav = false; + let pendingRangeStart = null; + let suppressClickUntil = 0; const panels = Array.from(track.querySelectorAll('.swc-panel')); - const syncGripVisibility = (showAdjacent = false) => { - panels.forEach((panel) => { - const isCurrent = panel.dataset.panel === 'current'; - panel.querySelectorAll('[data-swc-grip]').forEach((grip) => { - grip.style.opacity = (isCurrent || showAdjacent) ? '0.66' : '0'; - }); - }); - }; - const applyLayout = () => { const vw = viewport.clientWidth || viewport.getBoundingClientRect().width; if (!vw) return; - viewportWidth = vw; - const computedHandle = panelHandlePx == null + const computedInset = panelHandlePx == null ? Math.round(vw * panelHandleRatio) : panelHandlePx; - panelHandle = Math.max(panelHandleMin, Math.min(panelHandleMax, computedHandle)); - dragHandleWidth = panelHandle; + panelInset = Math.max(panelHandleMin, Math.min(panelHandleMax, computedInset)); panelWidth = vw; restOffset = -panelWidth; panels.forEach((panel) => { panel.style.width = `${panelWidth}px`; panel.style.boxSizing = 'border-box'; - panel.style.paddingLeft = `${panelHandle}px`; - panel.style.paddingRight = `${panelHandle}px`; + panel.style.paddingLeft = `${panelInset}px`; + panel.style.paddingRight = `${panelInset}px`; }); track.style.transition = 'none'; track.style.transform = `translate3d(${restOffset}px, 0, 0)`; - syncGripVisibility(false); }; const resetTrackPosition = () => { track.style.transition = 'none'; track.style.transform = `translate3d(${restOffset}px, 0, 0)`; - syncGripVisibility(false); }; const setDragTranslate = (dx, ms) => { @@ -189,6 +156,10 @@ export function initSwipePopoverCalendar({ track.style.transform = `translate3d(${restOffset + dx}px, 0, 0)`; }; + const snapBack = () => { + setDragTranslate(0, ANIMATION_MS); + }; + const getNavigationTarget = (monthDelta) => { const anchor = normalizeMonth(getMonthAnchor()); return startOfMonth(new Date(anchor.getFullYear(), anchor.getMonth() + monthDelta, 1)); @@ -232,20 +203,22 @@ export function initSwipePopoverCalendar({ let bg; let borderColor; let text; - let borderClass = 'border'; - if (isSelected) { - bg = theme.selectedBg; - borderColor = theme.selectedBorder; - text = theme.selectedText; - } else if (dimmed) { - bg = theme.dimmedBg; + let shadow = 'none'; + let borderClass = 'border-0'; + if (dimmed) { + bg = theme.dimmedBg ?? DEFAULT_THEME.dimmedBg; borderColor = 'transparent'; - text = theme.dimText; - borderClass = 'border-0'; + text = theme.dimText || DEFAULT_THEME.dimText; } else { - bg = theme.bg; - borderColor = theme.border; - text = theme.text; + bg = theme.bg || DEFAULT_THEME.bg; + borderColor = theme.border || DEFAULT_THEME.border; + text = theme.text || DEFAULT_THEME.text; + } + if (isSelected) { + borderColor = theme.selectedBorder || DEFAULT_THEME.selectedBorder; + text = theme.selectedText || DEFAULT_THEME.selectedText; + shadow = theme.selectedShadow || DEFAULT_THEME.selectedShadow; + borderClass = 'border'; } const opacity = dimmed && !isSelected ? String(theme.dimOpacity ?? 0.58) : '1'; const dotColor = isSelected ? theme.selectedDot : theme.dot; @@ -256,7 +229,7 @@ export function initSwipePopoverCalendar({ return ` <${tag} ${attrs} class="mx-auto flex h-[2.05rem] w-full min-w-0 max-w-full items-center justify-center rounded-full ${borderClass}${dayClass} text-xs font-medium leading-tight overflow-hidden" - style="background:${bg}; border-color:${borderColor}; color:${text}; opacity:${opacity}; touch-action:none;"> + style="background:${bg}; border-color:${borderColor}; color:${text}; opacity:${opacity}; box-shadow:${shadow}; touch-action:pan-y;"> ${day.getDate()} ${showDot ? `` : ''} @@ -268,7 +241,8 @@ export function initSwipePopoverCalendar({ const render = (previewSelection = null) => { const anchor = normalizeMonth(getMonthAnchor()); - const selectedSet = getSelectedSet(previewSelection); + const rangePreview = selectionMode === 'range' && pendingRangeStart ? [pendingRangeStart] : null; + const selectedSet = getSelectedSet(previewSelection ?? rangePreview); if (monthLabelEl) monthLabelEl.textContent = monthLabel(anchor, monthsLong); renderMonthGrid(gridEl, anchor, selectedSet); renderMonthGrid(prevGridEl, new Date(anchor.getFullYear(), anchor.getMonth() - 1, 1), selectedSet); @@ -278,8 +252,7 @@ export function initSwipePopoverCalendar({ const commitNavigation = (monthDelta) => { if (!canNavigate(monthDelta)) { - setDragTranslate(0, ANIMATION_MS); - setTimeout(() => syncGripVisibility(false), ANIMATION_MS + 20); + snapBack(); return; } animatingNav = true; @@ -294,49 +267,28 @@ export function initSwipePopoverCalendar({ }; if (selectionMode === 'range') { - let dragStart = null; - let dragCurrent = null; - let dragging = false; - gridEl.addEventListener('pointerdown', (e) => { - if (animatingNav) return; + gridEl.addEventListener('click', (e) => { const btn = e.target.closest('.swc-day'); if (!btn) return; e.stopPropagation(); - dragStart = btn.dataset.dk; - dragCurrent = btn.dataset.dk; - dragging = true; - gridEl.setPointerCapture(e.pointerId); - render([dragStart]); - }); - gridEl.addEventListener('pointermove', (e) => { - if (!dragging || animatingNav) return; - e.preventDefault(); - const el = document.elementFromPoint(e.clientX, e.clientY); - const btn = el?.closest('.swc-day'); - if (btn?.dataset.dk && btn.dataset.dk !== dragCurrent) { - dragCurrent = btn.dataset.dk; - render(dayRange(dragStart, dragCurrent)); + const selectedKey = btn.dataset.dk; + if (!pendingRangeStart) { + pendingRangeStart = selectedKey; + if (typeof onSelectionCommit === 'function') onSelectionCommit([selectedKey]); + render([selectedKey]); + return; } - }); - gridEl.addEventListener('pointerup', () => { - if (!dragging) return; - dragging = false; - const range = dayRange(dragStart, dragCurrent); - dragStart = null; - dragCurrent = null; + + const range = dayRange(pendingRangeStart, selectedKey); + pendingRangeStart = null; if (typeof onSelectionCommit === 'function') onSelectionCommit(range); render(); }); - gridEl.addEventListener('pointercancel', () => { - dragging = false; - dragStart = null; - dragCurrent = null; - render(); - }); } else { gridEl.addEventListener('click', (e) => { const btn = e.target.closest('.swc-day'); if (!btn) return; + e.stopPropagation(); if (typeof onSelectionCommit === 'function') onSelectionCommit(btn.dataset.dk); render(); }); @@ -347,22 +299,18 @@ export function initSwipePopoverCalendar({ let startY = 0; let moved = false; let axis = null; + let hasPointerCapture = false; viewport.addEventListener('pointerdown', (e) => { if (animatingNav || ptrId !== null) return; if (e.pointerType === 'mouse' && e.button !== 0) return; if (!panelWidth) applyLayout(); - const rect = viewport.getBoundingClientRect(); - const localX = e.clientX - rect.left; - const inLeft = localX <= dragHandleWidth; - const inRight = localX >= (viewportWidth - dragHandleWidth); - if (!inLeft && !inRight) return; ptrId = e.pointerId; startX = e.clientX; startY = e.clientY; moved = false; axis = null; - try { viewport.setPointerCapture(e.pointerId); } catch (_) {} + hasPointerCapture = false; }); viewport.addEventListener('pointermove', (e) => { @@ -372,29 +320,52 @@ export function initSwipePopoverCalendar({ if (!moved && (Math.abs(dx) > MOVE_THRESHOLD || Math.abs(dy) > MOVE_THRESHOLD)) { moved = true; axis = Math.abs(dx) >= Math.abs(dy) ? 'x' : 'y'; - if (axis === 'x') syncGripVisibility(true); } - if (axis === 'x') setDragTranslate(getAllowedDragDx(dx), 0); + if (axis === 'x') { + e.preventDefault(); + suppressClickUntil = Date.now() + 450; + if (!hasPointerCapture) { + try { + viewport.setPointerCapture(e.pointerId); + hasPointerCapture = true; + } catch (_) {} + } + setDragTranslate(getAllowedDragDx(dx), 0); + } }); const endGesture = (e) => { if (e && e.pointerId !== ptrId) return; + if (e && hasPointerCapture) { + try { viewport.releasePointerCapture(e.pointerId); } catch (_) {} + } + hasPointerCapture = false; ptrId = null; if (!moved || axis !== 'x') return; const dx = e ? e.clientX - startX : 0; const monthDelta = dx > 0 ? -1 : 1; if (Math.abs(dx) >= SWIPE_THRESHOLD && canNavigate(monthDelta)) commitNavigation(monthDelta); else { - setDragTranslate(0, ANIMATION_MS); - setTimeout(() => syncGripVisibility(false), ANIMATION_MS + 20); + snapBack(); } moved = false; axis = null; }; - viewport.addEventListener('pointerup', endGesture); - viewport.addEventListener('pointercancel', endGesture); + viewport.addEventListener('click', (e) => { + if (Date.now() > suppressClickUntil) return; + suppressClickUntil = 0; + e.preventDefault(); + e.stopPropagation(); + }, true); + window.addEventListener('pointerup', endGesture); + window.addEventListener('pointercancel', endGesture); window.addEventListener('resize', applyLayout); - return { render, reapplyLayout: applyLayout, resetTrackPosition }; + const clearPendingRange = () => { + pendingRangeStart = null; + render(); + }; + + return { render, reapplyLayout: applyLayout, resetTrackPosition, clearPendingRange }; } diff --git a/js/views/Pantry.js b/js/views/Pantry.js index 56872eb..8400689 100644 --- a/js/views/Pantry.js +++ b/js/views/Pantry.js @@ -86,10 +86,10 @@ const PANTRY_CALENDAR_THEME = { dimmedBg: 'transparent', dimmedBorder: 'transparent', dot: 'rgb(var(--text-faint-rgb))', - selectedBg: 'rgb(var(--card-rgb))', - selectedBorder: 'rgb(var(--border-input-rgb))', + selectedBorder: 'rgba(var(--text-emphasis-rgb),0.34)', selectedText: 'rgb(var(--text-emphasis-rgb))', selectedDot: 'rgb(var(--text-emphasis-rgb))', + selectedShadow: '0 0 0 1px rgba(var(--text-emphasis-rgb),0.10)', }; /* ── state ── */ diff --git a/js/views/ShoppingList.js b/js/views/ShoppingList.js index cc41e46..b6ebce7 100644 --- a/js/views/ShoppingList.js +++ b/js/views/ShoppingList.js @@ -188,12 +188,12 @@ function initShoppingCalendar() { showDot: dateKey(day) === todayKey() && !isSelected, }), theme: { - selectedBg: 'rgb(var(--card-rgb))', - selectedBorder: 'rgb(var(--border-input-rgb))', + selectedBorder: 'rgba(var(--text-emphasis-rgb),0.34)', selectedText: 'rgb(var(--text-emphasis-rgb))', selectedDot: 'rgb(var(--text-emphasis-rgb))', + selectedShadow: '0 0 0 1px rgba(var(--text-emphasis-rgb),0.10)', bg: 'rgb(var(--app-bg-rgb))', - border: 'rgb(var(--card-raised-rgb))', + border: 'transparent', text: 'rgb(var(--text-body-soft-rgb))', dimmedBg: 'transparent', dimText: CALENDAR_DIM_TEXT, @@ -268,6 +268,7 @@ function closeCalendar() { pill.style.background = 'rgb(var(--card-rgb))'; pill.style.borderColor = 'rgb(var(--border-card-rgb))'; } + shoppingCalendar?.clearPendingRange?.(); shoppingCalendar?.resetTrackPosition(); }