mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
fix: update drag-n-drop tools - overlay, page scrim
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
import Button from "$lib/components/ui/Button.svelte";
|
import Button from "$lib/components/ui/Button.svelte";
|
||||||
import { ButtonVariantDefine } from "$lib/components/ui/define";
|
import { ButtonVariantDefine } from "$lib/components/ui/define";
|
||||||
import { isFav, toggleFav } from "$lib/favorites.svelte";
|
import { isFav, toggleFav } from "$lib/favorites.svelte";
|
||||||
import { GripVertical, Star } from "@lucide/svelte";
|
import { Star } from "@lucide/svelte";
|
||||||
import { DragDropProvider } from "@dnd-kit/svelte";
|
import { DragDropProvider } from "@dnd-kit/svelte";
|
||||||
import { hasTransparency } from "$lib/core/analyze";
|
import { hasTransparency } from "$lib/core/analyze";
|
||||||
import { debounce } from "$lib/core/debounce";
|
import { debounce } from "$lib/core/debounce";
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
import SchemaResultTile from "./SchemaResultTile.svelte";
|
import SchemaResultTile from "./SchemaResultTile.svelte";
|
||||||
import SchemaSourceTile from "./SchemaSourceTile.svelte";
|
import SchemaSourceTile from "./SchemaSourceTile.svelte";
|
||||||
import SortableStepCard from "./SortableStepCard.svelte";
|
import SortableStepCard from "./SortableStepCard.svelte";
|
||||||
import StepDropIndicator from "./StepDropIndicator.svelte";
|
import StepReorderOverlay from "./StepReorderOverlay.svelte";
|
||||||
import ToolPickerButton from "./ToolPickerButton.svelte";
|
import ToolPickerButton from "./ToolPickerButton.svelte";
|
||||||
import { hasPreviewResult } from "./schema-preview-model";
|
import { hasPreviewResult } from "./schema-preview-model";
|
||||||
import { createSchemaToolRunner } from "./schema-tool-runner.svelte";
|
import { createSchemaToolRunner } from "./schema-tool-runner.svelte";
|
||||||
@@ -613,7 +613,7 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
{#snippet addRow(index: number)}
|
{#snippet addRow(index: number)}
|
||||||
{#if canExtend && !stepDnd.active}
|
{#if canExtend}
|
||||||
<div class="add-row">
|
<div class="add-row">
|
||||||
<AddStepButton onadd={(id) => addStepAt(index, id)} />
|
<AddStepButton onadd={(id) => addStepAt(index, id)} />
|
||||||
{#if alignedMode}
|
{#if alignedMode}
|
||||||
@@ -623,17 +623,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
{#snippet indicator(slot: number)}
|
|
||||||
{#if stepDnd.isIndicatorAt(slot)}
|
|
||||||
<div class="indicator-row">
|
|
||||||
<StepDropIndicator height={stepDnd.dragHeight} />
|
|
||||||
{#if alignedMode}
|
|
||||||
<div class="row-bridge" aria-hidden="true"></div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/snippet}
|
|
||||||
|
|
||||||
<DragDropProvider
|
<DragDropProvider
|
||||||
onDragStart={stepDnd.onDragStart}
|
onDragStart={stepDnd.onDragStart}
|
||||||
onDragMove={stepDnd.onDragOver}
|
onDragMove={stepDnd.onDragOver}
|
||||||
@@ -641,12 +630,11 @@
|
|||||||
onDragEnd={stepDnd.onDragEnd}
|
onDragEnd={stepDnd.onDragEnd}
|
||||||
>
|
>
|
||||||
{#if alignedMode}
|
{#if alignedMode}
|
||||||
<div class="workspace aligned" class:dnd-active={stepDnd.active}>
|
<div class="workspace aligned">
|
||||||
<div class="head-spacer" aria-hidden="true"></div>
|
<div class="head-spacer" aria-hidden="true"></div>
|
||||||
{@render panelHead()}
|
{@render panelHead()}
|
||||||
{@render addRow(0)}
|
{@render addRow(0)}
|
||||||
{#each steps as step, i (step.key)}
|
{#each steps as step, i (step.key)}
|
||||||
{@render indicator(i)}
|
|
||||||
{@render stepCard(step, i)}
|
{@render stepCard(step, i)}
|
||||||
<div class="pair">
|
<div class="pair">
|
||||||
{@render inputTile(i)}
|
{@render inputTile(i)}
|
||||||
@@ -654,19 +642,16 @@
|
|||||||
</div>
|
</div>
|
||||||
{@render addRow(i + 1)}
|
{@render addRow(i + 1)}
|
||||||
{/each}
|
{/each}
|
||||||
{@render indicator(steps.length)}
|
|
||||||
<div class="panel-foot" aria-hidden="true"></div>
|
<div class="panel-foot" aria-hidden="true"></div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="workspace" class:dnd-active={stepDnd.active}>
|
<div class="workspace">
|
||||||
<section class="settings">
|
<section class="settings">
|
||||||
{@render addRow(0)}
|
{@render addRow(0)}
|
||||||
{#each steps as step, i (step.key)}
|
{#each steps as step, i (step.key)}
|
||||||
{@render indicator(i)}
|
|
||||||
{@render stepCard(step, i)}
|
{@render stepCard(step, i)}
|
||||||
{@render addRow(i + 1)}
|
{@render addRow(i + 1)}
|
||||||
{/each}
|
{/each}
|
||||||
{@render indicator(steps.length)}
|
|
||||||
</section>
|
</section>
|
||||||
<section class="panel">
|
<section class="panel">
|
||||||
{@render panelHead()}
|
{@render panelHead()}
|
||||||
@@ -685,17 +670,7 @@
|
|||||||
{#each allWarnings as w (warningText(w))}
|
{#each allWarnings as w (warningText(w))}
|
||||||
<p class="warn" role="status">{warningText(w)}</p>
|
<p class="warn" role="status">{warningText(w)}</p>
|
||||||
{/each}
|
{/each}
|
||||||
{#if stepDnd.activeData && stepDnd.pointer}
|
<StepReorderOverlay {steps} dnd={stepDnd} />
|
||||||
<div
|
|
||||||
class="step-ghost"
|
|
||||||
style:width="{stepDnd.ghostWidth}px"
|
|
||||||
style:left="{stepDnd.pointer.x - stepDnd.grabOffset.x}px"
|
|
||||||
style:top="{stepDnd.pointer.y - stepDnd.grabOffset.y}px"
|
|
||||||
>
|
|
||||||
<GripVertical size={16} aria-hidden="true" />
|
|
||||||
<span class="step-ghost-title">{stepDnd.activeData.title}</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</DragDropProvider>
|
</DragDropProvider>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -708,23 +683,6 @@
|
|||||||
clamp(var(--space-m), 4vw, var(--space-xxxl));
|
clamp(var(--space-m), 4vw, var(--space-xxxl));
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
.step-ghost {
|
|
||||||
position: fixed;
|
|
||||||
z-index: var(--z-drag);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-m);
|
|
||||||
padding: var(--space-m) var(--space-xl);
|
|
||||||
border: var(--size-border-thick) dashed var(--color-main);
|
|
||||||
background: var(--color-panel);
|
|
||||||
color: var(--color-border);
|
|
||||||
opacity: 0.85;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.step-ghost-title {
|
|
||||||
font: 600 var(--font-size-m) var(--font-mono);
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -859,8 +817,7 @@
|
|||||||
.aligned .panel-foot {
|
.aligned .panel-foot {
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
}
|
}
|
||||||
.aligned .add-row,
|
.aligned .add-row {
|
||||||
.aligned .indicator-row {
|
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: subgrid;
|
grid-template-columns: subgrid;
|
||||||
@@ -879,19 +836,6 @@
|
|||||||
border-radius: 0 0 var(--radius-m) var(--radius-m);
|
border-radius: 0 0 var(--radius-m) var(--radius-m);
|
||||||
background: var(--color-panel);
|
background: var(--color-panel);
|
||||||
}
|
}
|
||||||
/* Compact drag: while a step is dragged, cards collapse to headers so the
|
|
||||||
reorder happens in a tight list; aligned additionally drops the preview
|
|
||||||
panel (its rows are too tall to drag across). */
|
|
||||||
.dnd-active :global(.step-body) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.aligned.dnd-active .panel-head,
|
|
||||||
.aligned.dnd-active .pair,
|
|
||||||
.aligned.dnd-active .row-bridge,
|
|
||||||
.aligned.dnd-active .panel-foot,
|
|
||||||
.aligned.dnd-active .head-spacer {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.error {
|
.error {
|
||||||
margin: var(--space-l) 0 0;
|
margin: var(--space-l) 0 0;
|
||||||
color: var(--color-danger);
|
color: var(--color-danger);
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
interface Props {
|
|
||||||
height?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { height = 0 }: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="step-drop-indicator"
|
|
||||||
style:height={height > 0 ? `${height}px` : null}
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.step-drop-indicator {
|
|
||||||
border: var(--size-border-thick) dashed var(--color-main);
|
|
||||||
background: var(--color-panel);
|
|
||||||
opacity: 0.6;
|
|
||||||
min-height: var(--space-xxl);
|
|
||||||
pointer-events: none;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { chainStepTitle } from "$lib/i18n/schema-tool-strings";
|
||||||
|
import type { ChainStep } from "$lib/pipeline.svelte";
|
||||||
|
import { GripVertical } from "@lucide/svelte";
|
||||||
|
import type { StepDnd } from "./step-dnd.svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
steps: ChainStep[];
|
||||||
|
dnd: StepDnd;
|
||||||
|
}
|
||||||
|
let { steps, dnd }: Props = $props();
|
||||||
|
|
||||||
|
let listEl = $state<HTMLElement | null>(null);
|
||||||
|
$effect(() => {
|
||||||
|
dnd.registerList(listEl);
|
||||||
|
return () => dnd.registerList(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Align the overlay so its source row sits exactly under the cursor (the
|
||||||
|
// dragged card's grab point); the page behind never moves.
|
||||||
|
const top = $derived.by(() => {
|
||||||
|
const anchor = dnd.anchor;
|
||||||
|
const el = listEl;
|
||||||
|
if (!anchor || !el || steps.length === 0) return null;
|
||||||
|
const rowH = el.scrollHeight / steps.length;
|
||||||
|
const offsetInSource = anchor.startY - anchor.sourceTop;
|
||||||
|
const raw = anchor.startY - (dnd.sourceIndex ?? 0) * rowH - offsetInSource;
|
||||||
|
const margin = 16;
|
||||||
|
const maxTop = window.innerHeight - el.offsetHeight - margin;
|
||||||
|
return Math.min(Math.max(raw, margin), Math.max(margin, maxTop));
|
||||||
|
});
|
||||||
|
|
||||||
|
const lineTop = $derived.by(() => {
|
||||||
|
const slot = dnd.indicatorSlot;
|
||||||
|
const el = listEl;
|
||||||
|
if (slot === null || !el || steps.length === 0) return null;
|
||||||
|
const rowH = el.scrollHeight / steps.length;
|
||||||
|
return slot * rowH - el.scrollTop;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if dnd.activeData && dnd.anchor}
|
||||||
|
<div class="reorder-scrim" aria-hidden="true"></div>
|
||||||
|
<div
|
||||||
|
class="reorder-overlay"
|
||||||
|
style:left="{dnd.anchor.left}px"
|
||||||
|
style:top="{top ?? dnd.anchor.sourceTop}px"
|
||||||
|
style:width="{dnd.anchor.width}px"
|
||||||
|
style:visibility={top === null ? "hidden" : "visible"}
|
||||||
|
>
|
||||||
|
<ol class="reorder-list" bind:this={listEl}>
|
||||||
|
{#each steps as step, i (step.key)}
|
||||||
|
<li class:dimmed={i === dnd.sourceIndex}>
|
||||||
|
<GripVertical size={14} aria-hidden="true" />
|
||||||
|
<span class="reorder-index">{String(i + 1).padStart(2, "0")}</span>
|
||||||
|
<span class="reorder-title">{chainStepTitle(step.id)}</span>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ol>
|
||||||
|
{#if lineTop !== null}
|
||||||
|
<div class="reorder-line" style:top="{lineTop}px"></div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* The scrim dims the page so the floating list does not blend into the
|
||||||
|
step cards underneath; it never intercepts the drag. */
|
||||||
|
.reorder-scrim {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: var(--z-dropdown);
|
||||||
|
background: var(--color-scrim);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.reorder-overlay {
|
||||||
|
position: fixed;
|
||||||
|
z-index: var(--z-drag);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 60vh;
|
||||||
|
border: var(--size-border-thick) solid var(--color-main);
|
||||||
|
background: var(--color-panel);
|
||||||
|
}
|
||||||
|
.reorder-list {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.reorder-list li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-m);
|
||||||
|
padding: var(--space-s) var(--space-l);
|
||||||
|
border-bottom: var(--size-border) solid var(--color-border);
|
||||||
|
color: var(--color-border);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.reorder-list li:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.reorder-list li.dimmed {
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
.reorder-index {
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
color: var(--color-main);
|
||||||
|
}
|
||||||
|
.reorder-title {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font: 600 var(--font-size-m) var(--font-mono);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
.reorder-line {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
height: var(--size-border-thick);
|
||||||
|
background: var(--color-main);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Draggable, Droppable } from "@dnd-kit/dom";
|
import type { Draggable } from "@dnd-kit/dom";
|
||||||
import { isSortable, type SortableDraggable } from "@dnd-kit/dom/sortable";
|
import { isSortable } from "@dnd-kit/dom/sortable";
|
||||||
|
|
||||||
export interface StepDragData {
|
export interface StepDragData {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -19,139 +19,103 @@ interface DndEvent {
|
|||||||
canceled?: boolean;
|
canceled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count the slot the pointer is over: how many non-source cards have their
|
export interface StepDragAnchor {
|
||||||
// vertical midpoint above the pointer. Also reports the height of the first
|
left: number;
|
||||||
// visible card: during drag all cards collapse to headers, so the indicator
|
width: number;
|
||||||
// takes that uniform height rather than the hidden source's stale height.
|
// Pointer Y and the source card's top at drag start: the overlay aligns
|
||||||
function indexAtPosition(
|
// its source row under the cursor.
|
||||||
source: SortableDraggable<StepDragData>,
|
startY: number;
|
||||||
pointerY: number,
|
sourceTop: number;
|
||||||
): { index: number; cardHeight: number } {
|
|
||||||
const manager = source.sortable.manager;
|
|
||||||
let index = 0;
|
|
||||||
let cardHeight = 0;
|
|
||||||
if (!manager) return { index, cardHeight };
|
|
||||||
for (const droppable of manager.registry.droppables) {
|
|
||||||
if (!isSortable(droppable)) continue;
|
|
||||||
if (droppable.sortable.id === source.id) continue;
|
|
||||||
const el = (droppable as Droppable).element;
|
|
||||||
if (!el) continue;
|
|
||||||
const rect = el.getBoundingClientRect();
|
|
||||||
if (rect.height === 0) continue;
|
|
||||||
if (!cardHeight) cardHeight = rect.height;
|
|
||||||
if (pointerY > rect.top + rect.height / 2) index++;
|
|
||||||
}
|
|
||||||
return { index, cardHeight };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step-reorder state for SchemaToolView, single list. The dragged card is
|
// Step-reorder state for SchemaToolView. Dragging a card handle opens a
|
||||||
// hidden (DragOverlay renders the ghost) and a dashed indicator of the
|
// compact overlay list (StepReorderOverlay) instead of reshaping the page:
|
||||||
// dragged height marks the insertion slot between the remaining cards.
|
// the page keeps its layout and scroll position, drop targeting is computed
|
||||||
|
// against the overlay rows.
|
||||||
export function createStepDnd({ onMove }: StepDndOptions) {
|
export function createStepDnd({ onMove }: StepDndOptions) {
|
||||||
let activeData = $state<StepDragData | null>(null);
|
let activeData = $state<StepDragData | null>(null);
|
||||||
let indicatorIndex = $state<number | null>(null);
|
let indicatorSlot = $state<number | null>(null);
|
||||||
let dragHeight = $state(0);
|
|
||||||
let sourceIndex = $state<number | null>(null);
|
let sourceIndex = $state<number | null>(null);
|
||||||
let sourceElement: HTMLElement | null = null;
|
let anchor = $state<StepDragAnchor | null>(null);
|
||||||
// Ghost positioning: current pointer, grab offset inside the card and the
|
let listEl: HTMLElement | null = null;
|
||||||
// card width. DragOverlay is not used -- the Feedback plugin skips
|
|
||||||
// draggables with feedback: "none", so the ghost follows the pointer here.
|
|
||||||
let pointer = $state<{ x: number; y: number } | null>(null);
|
|
||||||
let grabOffset = $state<{ x: number; y: number }>({ x: 0, y: 0 });
|
|
||||||
let ghostWidth = $state(0);
|
|
||||||
// X-range of the source card: dragging sideways over the preview panel
|
|
||||||
// must not light up the indicator.
|
|
||||||
let sourceX: { left: number; right: number } | null = null;
|
|
||||||
|
|
||||||
function computeIndicator(event: DndEvent): number | null {
|
// Slot in full-list coordinates: how many overlay rows have their midpoint
|
||||||
const source = event.operation?.source;
|
// above the pointer. Rows are uniform single-line height.
|
||||||
if (!source || !isSortable(source)) return null;
|
function slotAt(position: { x: number; y: number }): number | null {
|
||||||
const position = event.operation?.position?.current;
|
const el = listEl;
|
||||||
if (!position || !sourceX) return null;
|
if (!el || el.childElementCount === 0) return null;
|
||||||
if (position.x < sourceX.left || position.x > sourceX.right) return null;
|
const rect = el.getBoundingClientRect();
|
||||||
const { index, cardHeight } = indexAtPosition(
|
if (position.x < rect.left || position.x > rect.right) return null;
|
||||||
source as SortableDraggable<StepDragData>,
|
const count = el.childElementCount;
|
||||||
position.y,
|
const rowH = el.scrollHeight / count;
|
||||||
);
|
if (rowH <= 0) return null;
|
||||||
if (cardHeight) dragHeight = cardHeight;
|
const offsetY = position.y - rect.top + el.scrollTop;
|
||||||
return index;
|
const slot = Math.floor(offsetY / rowH + 0.5);
|
||||||
|
return Math.min(Math.max(slot, 0), count);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDragStart(event: DndEvent) {
|
function onDragStart(event: DndEvent) {
|
||||||
const source = event.operation?.source;
|
const source = event.operation?.source;
|
||||||
if (!source) return;
|
if (!source) return;
|
||||||
activeData = (source.data as StepDragData | undefined) ?? null;
|
activeData = (source.data as StepDragData | undefined) ?? null;
|
||||||
sourceElement = (source.element as HTMLElement | undefined) ?? null;
|
|
||||||
const rect = sourceElement?.getBoundingClientRect();
|
|
||||||
dragHeight = rect?.height ?? 0;
|
|
||||||
ghostWidth = rect?.width ?? 0;
|
|
||||||
sourceX = rect ? { left: rect.left, right: rect.right } : null;
|
|
||||||
const start = event.operation?.position?.current;
|
|
||||||
grabOffset =
|
|
||||||
start && rect
|
|
||||||
? { x: start.x - rect.left, y: start.y - rect.top }
|
|
||||||
: { x: 0, y: 0 };
|
|
||||||
pointer = start ?? null;
|
|
||||||
if (isSortable(source)) sourceIndex = source.sortable.initialIndex;
|
if (isSortable(source)) sourceIndex = source.sortable.initialIndex;
|
||||||
if (sourceElement) sourceElement.style.display = "none";
|
const el = source.element as HTMLElement | undefined;
|
||||||
|
const rect = el?.getBoundingClientRect();
|
||||||
|
const position = event.operation?.position?.current;
|
||||||
|
if (rect) {
|
||||||
|
anchor = {
|
||||||
|
left: rect.left,
|
||||||
|
width: rect.width,
|
||||||
|
startY: position?.y ?? rect.top + rect.height / 2,
|
||||||
|
sourceTop: rect.top,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDragOver(event: DndEvent) {
|
function onDragOver(event: DndEvent) {
|
||||||
pointer = event.operation?.position?.current ?? pointer;
|
const position = event.operation?.position?.current;
|
||||||
indicatorIndex = computeIndicator(event);
|
indicatorSlot = position ? slotAt(position) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDragEnd(event: DndEvent) {
|
function onDragEnd(event: DndEvent) {
|
||||||
const drop = event.canceled ? null : computeIndicator(event);
|
const position = event.operation?.position?.current;
|
||||||
if (sourceElement) {
|
const slot = event.canceled || !position ? null : slotAt(position);
|
||||||
sourceElement.style.display = "";
|
|
||||||
sourceElement = null;
|
|
||||||
}
|
|
||||||
const from = sourceIndex;
|
const from = sourceIndex;
|
||||||
activeData = null;
|
activeData = null;
|
||||||
indicatorIndex = null;
|
indicatorSlot = null;
|
||||||
dragHeight = 0;
|
|
||||||
sourceIndex = null;
|
sourceIndex = null;
|
||||||
sourceX = null;
|
anchor = null;
|
||||||
pointer = null;
|
if (from === null || slot === null) return;
|
||||||
if (from === null || drop === null || drop === from) return;
|
// Slots include the source row; moveStep wants the compressed index.
|
||||||
onMove(from, drop);
|
const to = slot > from ? slot - 1 : slot;
|
||||||
|
if (to === from) return;
|
||||||
|
onMove(from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slots render in the full list where the hidden source still occupies
|
function registerList(el: HTMLElement | null) {
|
||||||
// its DOM position: a compressed index at/past the source shifts by 1.
|
listEl = el;
|
||||||
function isIndicatorAt(slot: number): boolean {
|
|
||||||
if (indicatorIndex === null) return false;
|
|
||||||
let renderIndex = indicatorIndex;
|
|
||||||
if (sourceIndex !== null && indicatorIndex >= sourceIndex) {
|
|
||||||
renderIndex += 1;
|
|
||||||
}
|
|
||||||
return renderIndex === slot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
get activeData() {
|
get activeData() {
|
||||||
return activeData;
|
return activeData;
|
||||||
},
|
},
|
||||||
get dragHeight() {
|
|
||||||
return dragHeight;
|
|
||||||
},
|
|
||||||
get pointer() {
|
|
||||||
return pointer;
|
|
||||||
},
|
|
||||||
get grabOffset() {
|
|
||||||
return grabOffset;
|
|
||||||
},
|
|
||||||
get ghostWidth() {
|
|
||||||
return ghostWidth;
|
|
||||||
},
|
|
||||||
get active() {
|
get active() {
|
||||||
return activeData !== null;
|
return activeData !== null;
|
||||||
},
|
},
|
||||||
|
get indicatorSlot() {
|
||||||
|
return indicatorSlot;
|
||||||
|
},
|
||||||
|
get sourceIndex() {
|
||||||
|
return sourceIndex;
|
||||||
|
},
|
||||||
|
get anchor() {
|
||||||
|
return anchor;
|
||||||
|
},
|
||||||
onDragStart,
|
onDragStart,
|
||||||
onDragOver,
|
onDragOver,
|
||||||
onDragEnd,
|
onDragEnd,
|
||||||
isIndicatorAt,
|
registerList,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user