mirror of
https://github.com/callumalpass/tasknotes.git
synced 2026-07-22 12:50:26 +00:00
fix kanban drag drop marker
This commit is contained in:
parent
2ead7d59a0
commit
7c9a9ce015
5 changed files with 119 additions and 0 deletions
|
|
@ -33,3 +33,5 @@ When a change has user-facing documentation, include a canonical tasknotes.dev l
|
|||
-->
|
||||
|
||||
## Fixed
|
||||
|
||||
- (#1996) Made Kanban card drag-and-drop show a clear drop slot marker while cards are being repositioned, so the landing position remains visible during sorting. Thanks to @phillipadsmith for reporting this.
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import { clearStaticStyleClasses } from "../utils/staticStyleClasses";
|
|||
import { setElementDragImage } from "../utils/dragImage";
|
||||
import {
|
||||
applyKanbanTaskDropFrontmatterPlan,
|
||||
clearKanbanDropMarkers,
|
||||
createKanbanDropTarget,
|
||||
getKanbanCardDropTargetFromClientY,
|
||||
getKanbanDraggedPaths,
|
||||
|
|
@ -43,6 +44,7 @@ import {
|
|||
reconstructKanbanDropTargetFromContainer,
|
||||
resolveKanbanContainerDropTarget,
|
||||
resolveNestedTaskCardDragSource,
|
||||
updateKanbanDropMarker,
|
||||
type KanbanDropTarget,
|
||||
type KanbanTaskDropUpdatePlan,
|
||||
type KanbanTaskDragSource,
|
||||
|
|
@ -2796,6 +2798,7 @@ export class KanbanView extends BasesViewBase {
|
|||
i >= insertionIndex
|
||||
);
|
||||
}
|
||||
updateKanbanDropMarker(container, siblings, insertionIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -3167,6 +3170,7 @@ export class KanbanView extends BasesViewBase {
|
|||
"tn-static-overflow-y-auto-03df744e",
|
||||
"tn-static-overflow-y-clip-c5043043"
|
||||
);
|
||||
clearKanbanDropMarkers(this.dragContainer);
|
||||
this.dragContainer.style.removeProperty("overflow-y");
|
||||
this.dragContainer.style.removeProperty("padding-bottom");
|
||||
const wrappers = this.dragContainer.querySelectorAll<HTMLElement>(
|
||||
|
|
@ -3192,6 +3196,9 @@ export class KanbanView extends BasesViewBase {
|
|||
}
|
||||
|
||||
// Also clean any wrappers on the entire board (safety net for cross-column)
|
||||
if (this.boardEl) {
|
||||
clearKanbanDropMarkers(this.boardEl);
|
||||
}
|
||||
this.boardEl
|
||||
?.querySelectorAll<HTMLElement>(
|
||||
".kanban-view__card-wrapper--drag-shift, .kanban-view__card-wrapper--shift-down"
|
||||
|
|
|
|||
|
|
@ -224,6 +224,47 @@ export function reconstructKanbanDropTargetFromContainer({
|
|||
return { taskPath: visibleCards[idx - 1].dataset.taskPath as string, above: false };
|
||||
}
|
||||
|
||||
export function updateKanbanDropMarker(
|
||||
cardsContainer: HTMLElement,
|
||||
visibleCards: readonly HTMLElement[],
|
||||
insertionIndex: number
|
||||
): void {
|
||||
cardsContainer.classList.remove("kanban-view__cards--drop-marker-end");
|
||||
for (const card of visibleCards) {
|
||||
card.classList.remove("kanban-view__card-wrapper--drop-marker");
|
||||
}
|
||||
|
||||
const markerCard = visibleCards[insertionIndex];
|
||||
if (markerCard) {
|
||||
markerCard.classList.add("kanban-view__card-wrapper--drop-marker");
|
||||
return;
|
||||
}
|
||||
|
||||
if (visibleCards.length > 0 && insertionIndex >= visibleCards.length) {
|
||||
cardsContainer.classList.add("kanban-view__cards--drop-marker-end");
|
||||
}
|
||||
}
|
||||
|
||||
export function clearKanbanDropMarkers(root: ParentNode): void {
|
||||
if ((root as Node).nodeType === Node.ELEMENT_NODE) {
|
||||
(root as HTMLElement).classList.remove(
|
||||
"kanban-view__card-wrapper--drop-marker",
|
||||
"kanban-view__cards--drop-marker-end"
|
||||
);
|
||||
}
|
||||
|
||||
root
|
||||
.querySelectorAll<HTMLElement>(".kanban-view__card-wrapper--drop-marker")
|
||||
.forEach((card) => {
|
||||
card.classList.remove("kanban-view__card-wrapper--drop-marker");
|
||||
});
|
||||
root
|
||||
.querySelectorAll<HTMLElement>(".kanban-view__cards--drop-marker-end")
|
||||
.forEach((container) => {
|
||||
container.classList.remove("kanban-view__cards--drop-marker-end");
|
||||
});
|
||||
}
|
||||
|
||||
export function performKanbanOptimisticReorder({
|
||||
draggedPaths,
|
||||
dropTarget,
|
||||
|
|
|
|||
|
|
@ -697,6 +697,32 @@ body.is-mobile .tasknotes-plugin .kanban-view__swimlane-column {
|
|||
transform: translateY(var(--tn-drag-gap, 60px));
|
||||
}
|
||||
|
||||
.tasknotes-plugin .kanban-view__card-wrapper--drop-marker::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset-inline: 0;
|
||||
top: calc(-1 * var(--tn-drag-gap, 60px) + var(--tn-spacing-xs));
|
||||
height: max(32px, calc(var(--tn-drag-gap, 60px) - (var(--tn-spacing-xs) * 2)));
|
||||
box-sizing: border-box;
|
||||
border: 2px dashed var(--tn-interactive-accent);
|
||||
border-radius: var(--tn-radius-md);
|
||||
background: color-mix(in srgb, var(--tn-interactive-accent) 12%, transparent);
|
||||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--tn-interactive-accent) 20%, transparent);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tasknotes-plugin .kanban-view__cards--drop-marker-end::after,
|
||||
.tasknotes-plugin .kanban-view__tasks-container.kanban-view__cards--drop-marker-end::after {
|
||||
content: "";
|
||||
display: block;
|
||||
height: max(32px, calc(var(--tn-drag-gap, 60px) - (var(--tn-spacing-xs) * 2)));
|
||||
box-sizing: border-box;
|
||||
border: 2px dashed var(--tn-interactive-accent);
|
||||
border-radius: var(--tn-radius-md);
|
||||
background: color-mix(in srgb, var(--tn-interactive-accent) 12%, transparent);
|
||||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--tn-interactive-accent) 20%, transparent);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.tasknotes-plugin .kanban-view__card-wrapper--drag-shift {
|
||||
transition: none;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
applyKanbanTaskDropFrontmatterPlan,
|
||||
clearKanbanDropMarkers,
|
||||
getKanbanCardDropTargetFromClientY,
|
||||
getKanbanDraggedPaths,
|
||||
kanbanDropPlanNeedsWrite,
|
||||
|
|
@ -9,6 +10,7 @@ import {
|
|||
planKanbanTaskDropUpdate,
|
||||
reconstructKanbanDropTargetFromContainer,
|
||||
resolveKanbanContainerDropTarget,
|
||||
updateKanbanDropMarker,
|
||||
type KanbanDropTarget,
|
||||
} from "../../../src/bases/kanbanDragUtils";
|
||||
import type { TaskInfo } from "../../../src/types";
|
||||
|
|
@ -166,6 +168,47 @@ describe("kanbanDragUtils", () => {
|
|||
).toEqual({ taskPath: "Tasks/c.md", above: false });
|
||||
});
|
||||
|
||||
it("marks the card where the active drop gap is shown", () => {
|
||||
const cards = document.createElement("div");
|
||||
const first = createCard("Tasks/a.md");
|
||||
const second = createCard("Tasks/b.md");
|
||||
const third = createCard("Tasks/c.md");
|
||||
cards.append(first, second, third);
|
||||
|
||||
updateKanbanDropMarker(cards, [first, second, third], 1);
|
||||
|
||||
expect(first.classList.contains("kanban-view__card-wrapper--drop-marker")).toBe(false);
|
||||
expect(second.classList.contains("kanban-view__card-wrapper--drop-marker")).toBe(true);
|
||||
expect(third.classList.contains("kanban-view__card-wrapper--drop-marker")).toBe(false);
|
||||
expect(cards.classList.contains("kanban-view__cards--drop-marker-end")).toBe(false);
|
||||
});
|
||||
|
||||
it("marks the end of a card container for append drops", () => {
|
||||
const cards = document.createElement("div");
|
||||
const first = createCard("Tasks/a.md");
|
||||
const second = createCard("Tasks/b.md");
|
||||
cards.append(first, second);
|
||||
|
||||
updateKanbanDropMarker(cards, [first, second], 2);
|
||||
|
||||
expect(first.classList.contains("kanban-view__card-wrapper--drop-marker")).toBe(false);
|
||||
expect(second.classList.contains("kanban-view__card-wrapper--drop-marker")).toBe(false);
|
||||
expect(cards.classList.contains("kanban-view__cards--drop-marker-end")).toBe(true);
|
||||
});
|
||||
|
||||
it("clears Kanban drop marker classes from a drag root", () => {
|
||||
const cards = document.createElement("div");
|
||||
const first = createCard("Tasks/a.md");
|
||||
cards.classList.add("kanban-view__cards--drop-marker-end");
|
||||
first.classList.add("kanban-view__card-wrapper--drop-marker");
|
||||
cards.appendChild(first);
|
||||
|
||||
clearKanbanDropMarkers(cards);
|
||||
|
||||
expect(first.classList.contains("kanban-view__card-wrapper--drop-marker")).toBe(false);
|
||||
expect(cards.classList.contains("kanban-view__cards--drop-marker-end")).toBe(false);
|
||||
});
|
||||
|
||||
it("appends dragged cards into an empty target container for optimistic cross-column drops", () => {
|
||||
const source = document.createElement("div");
|
||||
const target = document.createElement("div");
|
||||
|
|
|
|||
Loading…
Reference in a new issue