8383 </ul >
8484 <template v-if =" checkedItems .length > 0 " >
8585 <h3 class =" pantry-detail__section-title" >{{ strings.doneTitle }}</h3 >
86- <ul class =" pantry-detail__items pantry-detail__items--done" >
87- <ChecklistItemRow
88- v-for =" item in checkedItems"
89- :key =" item.id"
90- :item =" item"
91- :category =" categoryFor(item.categoryId)"
92- :house-id =" houseIdNum"
93- @toggle =" handleToggle"
94- @view =" openView"
95- @edit =" startEdit"
96- @remove =" handleRemove"
97- @preview =" openPreview"
98- />
86+ <ul ref =" checkedListRef" class =" pantry-detail__items pantry-detail__items--done" >
87+ <template v-for =" gi in checkedGridItems " :key =" gi .key " >
88+ <li
89+ v-if =" gi.type === 'placeholder'"
90+ class =" pantry-detail__placeholder"
91+ @dragover.prevent
92+ @drop.prevent.stop =" onPlaceholderDrop"
93+ />
94+ <ChecklistItemRow
95+ v-else
96+ :item =" gi.item"
97+ :category =" categoryFor(gi.item.categoryId)"
98+ :house-id =" houseIdNum"
99+ :reorder-enabled =" isCustomSort"
100+ @toggle =" handleToggle"
101+ @view =" openView"
102+ @edit =" startEdit"
103+ @remove =" handleRemove"
104+ @preview =" openPreview"
105+ @drag-start =" onItemDragStart"
106+ @reorder-over =" onReorderOver"
107+ />
108+ </template >
99109 </ul >
100110 </template >
101111 </template >
@@ -251,20 +261,33 @@ const isCustomSort = computed(() => currentSort.value === 'custom')
251261const uncheckedItems = computed (() => sortWithinPartition (items .value .filter ((i ) => ! i .done )))
252262const checkedItems = computed (() => sortWithinPartition (items .value .filter ((i ) => i .done )))
253263
254- // ----- Drag/drop reorder (unchecked partition only, custom sort ) -----
264+ // ----- Drag/drop reorder (custom sort, per partition ) -----
255265
256266type ListGridItem =
257267 | { type: ' item' ; key: string ; item: ChecklistItem }
258268 | { type: ' placeholder' ; key: string }
259269
270+ type Partition = ' unchecked' | ' checked'
271+
260272const draggingItemId = ref <number | null >(null )
273+ const draggingPartition = ref <Partition | null >(null )
261274const dropIndex = ref <number | null >(null )
262275const uncheckedListRef = ref <HTMLElement | null >(null )
276+ const checkedListRef = ref <HTMLElement | null >(null )
263277
264- const uncheckedGridItems = computed <ListGridItem []>(() => {
265- const source = uncheckedItems .value
278+ function partitionItems(p : Partition ): ChecklistItem [] {
279+ return p === ' unchecked' ? uncheckedItems .value : checkedItems .value
280+ }
281+
282+ function buildGridItems(p : Partition ): ListGridItem [] {
283+ const source = partitionItems (p )
266284 const dragId = draggingItemId .value
267- if (! isCustomSort .value || dragId === null || dropIndex .value === null ) {
285+ if (
286+ ! isCustomSort .value ||
287+ dragId === null ||
288+ dropIndex .value === null ||
289+ draggingPartition .value !== p
290+ ) {
268291 return source .map ((i ) => ({ type: ' item' as const , key: ' i-' + i .id , item: i }))
269292 }
270293 const without = source .filter ((i ) => i .id !== dragId )
@@ -276,18 +299,33 @@ const uncheckedGridItems = computed<ListGridItem[]>(() => {
276299 const clamped = Math .min (dropIndex .value , items .length )
277300 items .splice (clamped , 0 , { type: ' placeholder' , key: ' drop-placeholder' })
278301 return items
279- })
302+ }
303+
304+ const uncheckedGridItems = computed <ListGridItem []>(() => buildGridItems (' unchecked' ))
305+ const checkedGridItems = computed <ListGridItem []>(() => buildGridItems (' checked' ))
306+
307+ function findPartitionOf(itemId : number ): Partition | null {
308+ if (uncheckedItems .value .some ((i ) => i .id === itemId )) return ' unchecked'
309+ if (checkedItems .value .some ((i ) => i .id === itemId )) return ' checked'
310+ return null
311+ }
280312
281313function onItemDragStart(itemId : number ) {
282314 draggingItemId .value = itemId
315+ draggingPartition .value = findPartitionOf (itemId )
283316 dropIndex .value = null
284317}
285318
286319function computeItemDropIndex(hoveredItemId : number , clientY : number , target : HTMLElement | null ) {
287320 const dragId = draggingItemId .value
288321 if (! dragId || dragId === hoveredItemId ) return
289322
290- const without = uncheckedItems .value .filter ((i ) => i .id !== dragId )
323+ const partition = draggingPartition .value
324+ if (! partition ) return
325+
326+ // Only allow reordering within the same partition.
327+ const source = partitionItems (partition )
328+ const without = source .filter ((i ) => i .id !== dragId )
291329 const idx = without .findIndex ((i ) => i .id === hoveredItemId )
292330 if (idx === - 1 ) return
293331
@@ -311,12 +349,17 @@ function onPlaceholderDrop() {
311349async function commitReorder() {
312350 const dragId = draggingItemId .value
313351 const idx = dropIndex .value
352+ const partition = draggingPartition .value
314353 draggingItemId .value = null
354+ draggingPartition .value = null
315355 dropIndex .value = null
316356
317- if (dragId === null || idx === null ) return
357+ if (dragId === null || idx === null || ! partition ) return
318358
319- const source = uncheckedItems .value
359+ // Reorder within the dragged partition, then merge with the other partition
360+ // (preserving its relative order) so the API receives a complete sort order
361+ // for all items in the list.
362+ const source = partitionItems (partition )
320363 const dragged = source .find ((i ) => i .id === dragId )
321364 if (! dragged ) return
322365
@@ -325,28 +368,45 @@ async function commitReorder() {
325368 const reordered = [... without ]
326369 reordered .splice (clamped , 0 , dragged )
327370
328- const entries = reordered .map ((i , idx ) => ({ id: i .id , sortOrder: idx }))
371+ const otherPartition: Partition = partition === ' unchecked' ? ' checked' : ' unchecked'
372+ const other = partitionItems (otherPartition )
373+
374+ // Unchecked items always come first in the sortOrder sequence.
375+ const finalOrder = partition === ' unchecked' ? [... reordered , ... other ] : [... other , ... reordered ]
376+
377+ const entries = finalOrder .map ((i , n ) => ({ id: i .id , sortOrder: n }))
329378 await reorderItems (entries )
330379}
331380
332- // Capture-phase listeners
381+ // Capture-phase listeners — attached to both partition lists.
333382function onDropCapture() {
334383 commitReorder ()
335384}
336385function onDragEndCapture() {
337386 draggingItemId .value = null
387+ draggingPartition .value = null
338388 dropIndex .value = null
339389}
390+ function bindDragListeners(el : HTMLElement | null ) {
391+ if (! el ) return
392+ el .addEventListener (' drop' , onDropCapture , true )
393+ el .addEventListener (' dragend' , onDragEndCapture , true )
394+ }
395+ function unbindDragListeners(el : HTMLElement | null ) {
396+ if (! el ) return
397+ el .removeEventListener (' drop' , onDropCapture , true )
398+ el .removeEventListener (' dragend' , onDragEndCapture , true )
399+ }
340400onMounted (() => {
341- uncheckedListRef .value ?. addEventListener ( ' drop ' , onDropCapture , true )
342- uncheckedListRef .value ?. addEventListener ( ' dragend ' , onDragEndCapture , true )
401+ bindDragListeners ( uncheckedListRef .value )
402+ bindDragListeners ( checkedListRef .value )
343403})
344404onBeforeUnmount (() => {
345- uncheckedListRef .value ?. removeEventListener ( ' drop ' , onDropCapture , true )
346- uncheckedListRef .value ?. removeEventListener ( ' dragend ' , onDragEndCapture , true )
405+ unbindDragListeners ( uncheckedListRef .value )
406+ unbindDragListeners ( checkedListRef .value )
347407})
348408
349- // Touch reorder
409+ // Touch reorder — one composable instance per partition list.
350410useTouchReorder (
351411 uncheckedListRef ,
352412 {
@@ -358,6 +418,25 @@ useTouchReorder(
358418 onDrop: commitReorder ,
359419 onCancel() {
360420 draggingItemId .value = null
421+ draggingPartition .value = null
422+ dropIndex .value = null
423+ },
424+ },
425+ isCustomSort ,
426+ )
427+
428+ useTouchReorder (
429+ checkedListRef ,
430+ {
431+ onDragStart: onItemDragStart ,
432+ onReorderOver(hoveredId , _clientX , clientY ) {
433+ const el = checkedListRef .value ?.querySelector <HTMLElement >(` [data-drag-id="${hoveredId }"] ` )
434+ computeItemDropIndex (hoveredId , clientY , el )
435+ },
436+ onDrop: commitReorder ,
437+ onCancel() {
438+ draggingItemId .value = null
439+ draggingPartition .value = null
361440 dropIndex .value = null
362441 },
363442 },
0 commit comments