This commit is contained in:
root
2026-09-13 15:50:44 +02:00
parent 46cc687f57
commit 249519e95f
+88 -10
View File
@@ -10,6 +10,9 @@ const copied = ref(false)
const items = ref([]) const items = ref([])
const newName = ref('') const newName = ref('')
const editingId = ref(null)
const editName = ref('')
const sublists = ref([]) const sublists = ref([])
const newSublistName = ref('') const newSublistName = ref('')
const newSublistColor = ref('#16a34a') const newSublistColor = ref('#16a34a')
@@ -180,24 +183,62 @@ async function removeItem(item) {
items.value = items.value.filter((i) => i.id !== item.id) items.value = items.value.filter((i) => i.id !== item.id)
} }
function startEdit(item) {
editingId.value = item.id
editName.value = item.name
}
function cancelEdit() {
editingId.value = null
}
function saveEdit(item) {
if (editingId.value !== item.id) return
editingId.value = null
const name = editName.value.trim()
if (!name || name === item.name) return
fetch(`/api/lists/${listId.value}/items/${item.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name }),
})
.then((res) => res.json())
.then((updated) => Object.assign(item, updated))
}
const SWIPE_THRESHOLD = 70 const SWIPE_THRESHOLD = 70
const SWIPE_MAX = 140 const SWIPE_MAX = 140
const LONG_PRESS_MS = 500
const LONG_PRESS_TOLERANCE = 10
let drag = null let drag = null
let longPressTimer = null
function onSwipeStart(e) { function onSwipeStart(e, item) {
drag = { startX: e.clientX, dx: 0, el: e.currentTarget } drag = { startX: e.clientX, startY: e.clientY, dx: 0, el: e.currentTarget }
drag.el.setPointerCapture(e.pointerId) drag.el.setPointerCapture(e.pointerId)
drag.el.style.transition = 'none' drag.el.style.transition = 'none'
clearTimeout(longPressTimer)
longPressTimer = setTimeout(() => {
if (!drag) return
startEdit(item)
drag.el.style.transform = ''
drag = null
}, LONG_PRESS_MS)
} }
function onSwipeMove(e) { function onSwipeMove(e) {
if (!drag) return if (!drag) return
if (Math.abs(e.clientX - drag.startX) > LONG_PRESS_TOLERANCE || Math.abs(e.clientY - drag.startY) > LONG_PRESS_TOLERANCE) {
clearTimeout(longPressTimer)
}
const dx = e.clientX - drag.startX const dx = e.clientX - drag.startX
drag.dx = Math.max(-SWIPE_MAX, Math.min(SWIPE_MAX, dx)) drag.dx = Math.max(-SWIPE_MAX, Math.min(SWIPE_MAX, dx))
drag.el.style.transform = `translateX(${drag.dx}px)` drag.el.style.transform = `translateX(${drag.dx}px)`
} }
function onSwipeEnd(e, item) { function onSwipeEnd(e, item) {
clearTimeout(longPressTimer)
if (!drag) return if (!drag) return
const { dx, el } = drag const { dx, el } = drag
el.style.transition = 'transform 0.2s ease' el.style.transition = 'transform 0.2s ease'
@@ -371,6 +412,19 @@ function onHandleEnd() {
item.position = position item.position = position
item.sublist_id = targetSublistId item.sublist_id = targetSublistId
// Reposition within the underlying array too — groups only filter it, they
// don't sort by position, so without this the item stays wherever it
// physically sat in the array until the next full refetch reorders it.
const globalIdx = items.value.findIndex((i) => i.id === item.id)
items.value.splice(globalIdx, 1)
const anchor = next ?? prev
if (anchor) {
const anchorIdx = items.value.findIndex((i) => i.id === anchor.id)
items.value.splice(next ? anchorIdx : anchorIdx + 1, 0, item)
} else {
items.value.push(item)
}
fetch(`/api/lists/${listId.value}/items/${item.id}`, { fetch(`/api/lists/${listId.value}/items/${item.id}`, {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@@ -412,7 +466,7 @@ onMounted(init)
<main v-else> <main v-else>
<h1>skeps</h1> <h1>skeps</h1>
<form @submit.prevent="addItem"> <form class="add-item-bar" @submit.prevent="addItem">
<input v-model="newName" type="text" placeholder="Add an item" /> <input v-model="newName" type="text" placeholder="Add an item" />
<span class="dot" :style="{ background: newItemSublistColor }"></span> <span class="dot" :style="{ background: newItemSublistColor }"></span>
<select class="item-sublist" v-model="newItemSublistId"> <select class="item-sublist" v-model="newItemSublistId">
@@ -444,7 +498,7 @@ onMounted(init)
<div <div
class="swipe-content" class="swipe-content"
:class="{ done: item.done }" :class="{ done: item.done }"
@pointerdown="onSwipeStart" @pointerdown="onSwipeStart($event, item)"
@pointermove="onSwipeMove" @pointermove="onSwipeMove"
@pointerup="onSwipeEnd($event, item)" @pointerup="onSwipeEnd($event, item)"
@pointercancel="onSwipeEnd($event, item)" @pointercancel="onSwipeEnd($event, item)"
@@ -457,10 +511,21 @@ onMounted(init)
@pointercancel.stop="onHandleEnd" @pointercancel.stop="onHandleEnd"
>⠿</span >⠿</span
> >
<label> <label v-if="editingId !== item.id">
<input type="checkbox" :checked="item.done" @change="toggleDone(item)" /> <input type="checkbox" :checked="item.done" @change="toggleDone(item)" />
{{ item.name }} <span class="qty">x{{ item.quantity }}</span> {{ item.name }}
</label> </label>
<input
v-else
class="edit-name"
v-model="editName"
type="text"
autofocus
@pointerdown.stop
@keydown.enter="saveEdit(item)"
@keydown.esc="cancelEdit"
@blur="saveEdit(item)"
/>
</div> </div>
</li> </li>
</ul> </ul>
@@ -635,9 +700,21 @@ body {
main { main {
max-width: 480px; max-width: 480px;
margin: 2rem auto; margin: 2rem auto;
padding: env(safe-area-inset-top) 1rem env(safe-area-inset-bottom); padding: calc(env(safe-area-inset-top) + 4.25rem) 1rem env(safe-area-inset-bottom);
font-family: sans-serif; font-family: sans-serif;
} }
.add-item-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 20;
max-width: 480px;
margin: 0 auto;
padding: calc(env(safe-area-inset-top) + 0.75rem) 1rem 0.75rem;
background: var(--bg);
border-bottom: 1px solid var(--border);
}
.theme-picker { .theme-picker {
display: block; display: block;
font-size: 16px; font-size: 16px;
@@ -754,6 +831,10 @@ ul {
text-decoration: line-through; text-decoration: line-through;
color: var(--muted); color: var(--muted);
} }
.edit-name {
flex: 1;
min-width: 0;
}
.handle { .handle {
touch-action: none; touch-action: none;
cursor: grab; cursor: grab;
@@ -791,9 +872,6 @@ ul {
.add-sublist input[type='color']::-moz-color-swatch { .add-sublist input[type='color']::-moz-color-swatch {
border: none; border: none;
} }
.qty {
color: var(--muted);
}
.settings { .settings {
margin-top: 2rem; margin-top: 2rem;
color: var(--muted); color: var(--muted);