@innertia-solutions/innertia-nuxt 0.1.39 → 0.1.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -2
- package/components/Kanban/Standard.vue +93 -23
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,12 +4,13 @@ All notable changes to `@innertia-solutions/innertia-nuxt` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
-
###
|
|
7
|
+
### Added
|
|
8
8
|
|
|
9
|
-
- `
|
|
9
|
+
- `Kanban.Standard`: reordenamiento intra-columna con drag&drop (prop `reorderEndpoint`); orden por `position`, placeholder de inserción, y persistencia vía POST. Retrocompatible: sin la prop se comporta igual que antes (solo cross-column).
|
|
10
10
|
|
|
11
11
|
### Fixed
|
|
12
12
|
|
|
13
|
+
- `Forms.Select`: el dropdown ahora usa `dropdownScope:'window'` (Preline lo teletransporta al `<body>` y lo posiciona con floating-ui). Deja de recortarse dentro de contenedores con scroll —modales, paneles— y flota por encima (`z-[10000]`). Se expone `window.FloatingUIDOM` en el plugin de Preline y se agrega `@floating-ui/dom` como dependencia.
|
|
13
14
|
- `Kanban.Standard`: en modo `bordered=false`, el `ring` de highlight al arrastrar sobre una columna se recortaba por arriba (el contenedor `overflow-x-auto` sin padding clippeaba los 2px del ring). Se agrega `p-1` al scroll container en ese modo.
|
|
14
15
|
|
|
15
16
|
### BREAKING CHANGES
|
|
@@ -56,6 +56,9 @@ const props = defineProps({
|
|
|
56
56
|
states: { type: Array, required: true },
|
|
57
57
|
moveMutation: { type: Function, default: null },
|
|
58
58
|
|
|
59
|
+
// Endpoint para persistir reorden (POST). Si null → solo cross-column, sin placeholder.
|
|
60
|
+
reorderEndpoint: { type: String, default: null },
|
|
61
|
+
|
|
59
62
|
// Toolbar visibility
|
|
60
63
|
showSearch: { type: Boolean, default: true },
|
|
61
64
|
searchPlaceholder:{ type: String, default: 'Buscar...' },
|
|
@@ -355,6 +358,7 @@ const buildBody = () => ({
|
|
|
355
358
|
per_page: props.perPage,
|
|
356
359
|
search: search.value.trim(),
|
|
357
360
|
...(enrichedFilters.value.length ? { filters: enrichedFilters.value } : {}),
|
|
361
|
+
...(props.reorderEndpoint ? { board: true } : {}),
|
|
358
362
|
})
|
|
359
363
|
|
|
360
364
|
const fetchAll = async () => {
|
|
@@ -433,6 +437,11 @@ const columnRows = computed(() => {
|
|
|
433
437
|
const state = row[props.stateKey]
|
|
434
438
|
if (map[state]) map[state].push(row)
|
|
435
439
|
}
|
|
440
|
+
if (props.reorderEndpoint) {
|
|
441
|
+
for (const k of Object.keys(map)) {
|
|
442
|
+
map[k].sort((a, b) => (a.position ?? 0) - (b.position ?? 0))
|
|
443
|
+
}
|
|
444
|
+
}
|
|
436
445
|
return map
|
|
437
446
|
})
|
|
438
447
|
|
|
@@ -442,21 +451,73 @@ const totalRendered = computed(() => rows.value.length)
|
|
|
442
451
|
const draggedId = ref(null)
|
|
443
452
|
const draggedFrom = ref(null)
|
|
444
453
|
const dragOverState = ref(null)
|
|
454
|
+
const dragOverIndex = ref(null) // índice de inserción (placeholder) en la columna dragOverState
|
|
445
455
|
|
|
446
456
|
const onDragStart = (row, state) => {
|
|
447
457
|
draggedId.value = row.id
|
|
448
458
|
draggedFrom.value = state
|
|
449
459
|
}
|
|
450
|
-
const onDragOver
|
|
451
|
-
|
|
460
|
+
const onDragOver = (e, state) => {
|
|
461
|
+
e.preventDefault()
|
|
462
|
+
dragOverState.value = state
|
|
463
|
+
if (!props.reorderEndpoint) return
|
|
464
|
+
if (dragOverIndex.value === null) dragOverIndex.value = columnRows.value[state]?.length ?? 0
|
|
465
|
+
}
|
|
466
|
+
const onDragLeave = () => { dragOverState.value = null; dragOverIndex.value = null }
|
|
467
|
+
|
|
468
|
+
// Llamado desde cada tarjeta: calcula si el cursor está en la mitad superior o inferior.
|
|
469
|
+
const onCardDragOver = (e, state, index) => {
|
|
470
|
+
if (!props.reorderEndpoint) return
|
|
471
|
+
e.preventDefault()
|
|
472
|
+
e.stopPropagation()
|
|
473
|
+
dragOverState.value = state
|
|
474
|
+
const rect = e.currentTarget.getBoundingClientRect()
|
|
475
|
+
const after = (e.clientY - rect.top) > rect.height / 2
|
|
476
|
+
dragOverIndex.value = after ? index + 1 : index
|
|
477
|
+
}
|
|
452
478
|
|
|
453
479
|
const onDrop = async (targetState) => {
|
|
454
|
-
dragOverState.value = null
|
|
455
480
|
const id = draggedId.value
|
|
456
481
|
const fromState = draggedFrom.value
|
|
457
|
-
|
|
482
|
+
const dropIndex = dragOverIndex.value
|
|
483
|
+
dragOverState.value = null
|
|
484
|
+
dragOverIndex.value = null
|
|
485
|
+
draggedId.value = null
|
|
458
486
|
draggedFrom.value = null
|
|
459
|
-
if (!id
|
|
487
|
+
if (!id) return
|
|
488
|
+
|
|
489
|
+
// Modo reorden (prop seteada): calcula vecinos y persiste posición.
|
|
490
|
+
if (props.reorderEndpoint) {
|
|
491
|
+
const colList = (columnRows.value[targetState] ?? []).filter(r => r.id !== id)
|
|
492
|
+
const idx = dropIndex == null ? colList.length : Math.min(dropIndex, colList.length)
|
|
493
|
+
const beforeId = idx > 0 ? colList[idx - 1].id : null // tarjeta arriba
|
|
494
|
+
const afterId = idx < colList.length ? colList[idx].id : null // tarjeta abajo
|
|
495
|
+
|
|
496
|
+
if (fromState === targetState && beforeId == null && afterId == null) return
|
|
497
|
+
|
|
498
|
+
const snapshot = rows.value.map(r => ({ ...r }))
|
|
499
|
+
const ridx = rows.value.findIndex(r => r.id === id)
|
|
500
|
+
if (ridx >= 0) {
|
|
501
|
+
const before = beforeId ? rows.value.find(r => r.id === beforeId) : null
|
|
502
|
+
const after = afterId ? rows.value.find(r => r.id === afterId) : null
|
|
503
|
+
const newPos = before && after ? (before.position + after.position) / 2
|
|
504
|
+
: before ? before.position + 1
|
|
505
|
+
: after ? after.position - 1
|
|
506
|
+
: 1
|
|
507
|
+
rows.value[ridx] = { ...rows.value[ridx], [props.stateKey]: targetState, position: newPos }
|
|
508
|
+
}
|
|
509
|
+
emit('move', { id, from: fromState, to: targetState })
|
|
510
|
+
|
|
511
|
+
try {
|
|
512
|
+
await api.post(props.reorderEndpoint, { id, column: targetState, before_id: beforeId, after_id: afterId })
|
|
513
|
+
} catch (e) {
|
|
514
|
+
rows.value = snapshot
|
|
515
|
+
}
|
|
516
|
+
return
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Modo legacy (sin reorderEndpoint): solo cross-column, comportamiento actual.
|
|
520
|
+
if (fromState === targetState) return
|
|
460
521
|
|
|
461
522
|
// Optimistic update local (solo en endpoint mode).
|
|
462
523
|
// En items mode el optimistic lo gestiona el moveMutation externo
|
|
@@ -747,25 +808,34 @@ defineExpose({ reload: fetchAll, rows })
|
|
|
747
808
|
bordered ? 'bg-muted/40' : '',
|
|
748
809
|
]"
|
|
749
810
|
>
|
|
811
|
+
<template v-for="(row, ci) in columnRows[state.key]" :key="row.id">
|
|
812
|
+
<div
|
|
813
|
+
v-if="reorderEndpoint && dragOverState === state.key && dragOverIndex === ci"
|
|
814
|
+
class="rounded-card border-2 border-dashed border-primary/50 bg-primary/5 h-10"
|
|
815
|
+
/>
|
|
816
|
+
<div
|
|
817
|
+
draggable="true"
|
|
818
|
+
@dragstart="onDragStart(row, state.key)"
|
|
819
|
+
@dragover="onCardDragOver($event, state.key, ci)"
|
|
820
|
+
@click="handleCardClick(row)"
|
|
821
|
+
class="bg-card border border-card-line rounded-card p-3 cursor-grab active:cursor-grabbing hover:border-primary/40 hover:shadow-sm transition-all select-none"
|
|
822
|
+
:class="[
|
|
823
|
+
draggedId === row.id ? 'opacity-40' : '',
|
|
824
|
+
previewRow?.id === row.id ? 'ring-2 ring-primary/30' : '',
|
|
825
|
+
]"
|
|
826
|
+
>
|
|
827
|
+
<slot name="card" :row="row" :state="state">
|
|
828
|
+
<div class="space-y-1">
|
|
829
|
+
<p class="text-sm font-medium text-foreground truncate">{{ row.name ?? row.title ?? row.id }}</p>
|
|
830
|
+
<p v-if="row.description" class="text-xs text-muted-foreground line-clamp-2">{{ row.description }}</p>
|
|
831
|
+
</div>
|
|
832
|
+
</slot>
|
|
833
|
+
</div>
|
|
834
|
+
</template>
|
|
750
835
|
<div
|
|
751
|
-
v-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
@dragstart="onDragStart(row, state.key)"
|
|
755
|
-
@click="handleCardClick(row)"
|
|
756
|
-
class="bg-card border border-card-line rounded-card p-3 cursor-grab active:cursor-grabbing hover:border-primary/40 hover:shadow-sm transition-all select-none"
|
|
757
|
-
:class="[
|
|
758
|
-
draggedId === row.id ? 'opacity-40' : '',
|
|
759
|
-
previewRow?.id === row.id ? 'ring-2 ring-primary/30' : '',
|
|
760
|
-
]"
|
|
761
|
-
>
|
|
762
|
-
<slot name="card" :row="row" :state="state">
|
|
763
|
-
<div class="space-y-1">
|
|
764
|
-
<p class="text-sm font-medium text-foreground truncate">{{ row.name ?? row.title ?? row.id }}</p>
|
|
765
|
-
<p v-if="row.description" class="text-xs text-muted-foreground line-clamp-2">{{ row.description }}</p>
|
|
766
|
-
</div>
|
|
767
|
-
</slot>
|
|
768
|
-
</div>
|
|
836
|
+
v-if="reorderEndpoint && dragOverState === state.key && dragOverIndex === (columnRows[state.key]?.length ?? 0) && columnRows[state.key]?.length"
|
|
837
|
+
class="rounded-card border-2 border-dashed border-primary/50 bg-primary/5 h-10"
|
|
838
|
+
/>
|
|
769
839
|
|
|
770
840
|
<div
|
|
771
841
|
v-if="!columnRows[state.key]?.length"
|