This commit is contained in:
yinsx
2026-02-05 11:41:47 +08:00
parent bf5a3bc343
commit cbfc2cf974
3 changed files with 89 additions and 60 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
/node_modules/
/public/
/dist/
nul
nul/

View File

@ -75,17 +75,21 @@ const initialText = (child: FolderChild) => {
}
.folder-preview {
--folder-chip-size: min(20px, calc(var(--icon-size, 72px) * 0.28));
width: 100%;
height: 100%;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
grid-template-columns: repeat(2, var(--folder-chip-size));
grid-template-rows: repeat(2, var(--folder-chip-size));
gap: 6px;
padding-top: 6px;
padding: 4px;
place-content: center;
box-sizing: border-box;
}
.folder-chip {
width: var(--folder-chip-size);
height: var(--folder-chip-size);
border-radius: 8px;
background: rgba(255, 255, 255, 0.85);
color: #222;

View File

@ -98,18 +98,21 @@ const mergeOrderSnapshot = ref<GridOrderEntry[] | null>(null);
const mergePendingFolderId = ref<string | null>(null);
const mergePendingInsertIndex = ref<number | null>(null);
const OVERLAP_MERGE_THRESHOLD = 0.6;
const OVERLAP_HOLD_MS = 100;
const OVERLAP_HOLD_MS = 0;
const OVERLAP_DROP_THRESHOLD = 0.35;
const MERGE_HIT_INSET_RATIO = 0.18;
const MERGE_HIT_INSET_PX = 6;
const MERGE_HIT_INSET_RATIO = 0;
const MERGE_HIT_INSET_PX = 0;
const POINTER_ROW_TOLERANCE_RATIO = 0.6;
const POINTER_EDGE_PADDING = 6;
const lastPointer = ref<{ x: number; y: number } | null>(null);
const pointerTargetEl = ref<HTMLElement | null>(null);
const isPressing = ref(false);
const pressTimer = ref<number | null>(null);
const layoutAnimationMs = 240;
const layoutEasing = 'cubic-bezier(0.4, 0, 0.2, 1)';
const DELETE_ANIM_MS = 240;
const DELETE_ANIM_EASING = 'cubic-bezier(0.4, 0, 0.2, 1)';
const PRESS_DELAY_MS = 100;
const normalizeGroupId = (groupId?: string | null) => groupId ?? DEFAULT_GROUP_ID;
const activeGroupId = computed(() => normalizeGroupId(props.activeGroupId));
@ -415,6 +418,32 @@ const handleClick = (event: MouseEvent) => {
}
};
const clearPressTimer = () => {
if (pressTimer.value !== null) {
window.clearTimeout(pressTimer.value);
pressTimer.value = null;
}
};
const stopPressing = () => {
clearPressTimer();
isPressing.value = false;
};
const handlePointerDown = (event: PointerEvent) => {
if (event.button !== 0) return;
const target = event.target as HTMLElement | null;
if (!target?.closest('.grid-item')) return;
clearPressTimer();
pressTimer.value = window.setTimeout(() => {
isPressing.value = true;
}, PRESS_DELAY_MS);
};
const handleGlobalPointerUp = () => {
stopPressing();
};
const handleClickCapture = (event: MouseEvent) => {
if (isDragging.value || suppressClick.value || Date.now() < clickBlockUntil.value) {
event.preventDefault();
@ -798,29 +827,11 @@ const handleMergeDrop = () => {
const dragId = draggingMeta.value.id;
const dragType = draggingMeta.value.type;
if (!dragId || !dragType) return;
let targetEl = mergeTargetEl.value;
if (!mergeActive.value || !targetEl) {
targetEl = hoverTargetEl.value;
const dragEl = draggingEl.value;
if (!targetEl && dragEl) {
const stats = findTargetStats(dragEl);
const best = stats?.best ?? null;
if (best && best.ratio >= OVERLAP_MERGE_THRESHOLD) {
targetEl = best.el;
}
}
}
let targetEl = mergeTargetEl.value ?? hoverTargetEl.value ?? pointerTargetEl.value;
if (!targetEl) {
clearHoverTarget();
return;
}
if (!mergeActive.value && draggingEl.value) {
const ratio = getOverlapRatio(draggingEl.value, targetEl);
if (ratio < OVERLAP_DROP_THRESHOLD) {
clearHoverTarget();
return;
}
}
const targetId = targetEl.dataset.id;
const targetType = targetEl.dataset.type as GridItemType | undefined;
if (!targetId || !targetType) return;
@ -1232,6 +1243,8 @@ onMounted(async () => {
ensureOrderConsistency();
previousIconIds.value = new Set(layoutStore.icons.map(icon => icon.id));
previousWidgetIds.value = new Set(widgetsStore.widgets.map(widget => widget.id));
window.addEventListener('pointerup', handleGlobalPointerUp);
window.addEventListener('pointercancel', handleGlobalPointerUp);
await nextTick();
if (!gridRef.value) return;
grid.value = new Muuri(gridRef.value, {
@ -1266,6 +1279,7 @@ onMounted(async () => {
grid.value.on('dragStart', (item: any) => {
isDragging.value = true;
suppressClick.value = true;
stopPressing();
const el = item.getElement() as HTMLElement;
draggingEl.value = el;
draggingItemRef.value = item;
@ -1305,39 +1319,32 @@ onMounted(async () => {
const now = performance.now();
const pointerTarget = pointerTargetEl.value;
if (mergeActive.value) {
const locked = mergeTargetEl.value;
if (!locked || !locked.isConnected) {
deactivateMergeTarget(true);
} else if (pointerTarget && pointerTarget !== locked) {
deactivateMergeTarget(true);
mergeCandidateEl.value = pointerTarget;
mergeCandidateAt.value = now;
setHoverTarget(pointerTarget);
} else if (!pointerTarget) {
deactivateMergeTarget(true);
} else {
setHoverTarget(locked);
}
} else {
if (pointerTarget && pointerTarget.isConnected) {
setHoverTarget(pointerTarget);
if (mergeCandidateEl.value === pointerTarget) {
if (now - mergeCandidateAt.value >= OVERLAP_HOLD_MS) {
activateMergeTarget(pointerTarget);
}
} else {
if (mergeCandidateEl.value !== pointerTarget) {
mergeCandidateEl.value = pointerTarget;
mergeCandidateAt.value = now;
if (mergeActive.value && mergeTargetEl.value !== pointerTarget) {
deactivateMergeTarget(false);
}
}
const holdReady = now - mergeCandidateAt.value >= OVERLAP_HOLD_MS;
if (holdReady) {
if (!mergeActive.value || mergeTargetEl.value !== pointerTarget) {
activateMergeTarget(pointerTarget);
} else {
setHoverTarget(pointerTarget);
}
} else {
clearHoverTarget();
}
} else {
mergeCandidateEl.value = null;
mergeCandidateAt.value = 0;
deactivateMergeTarget(true);
clearHoverTarget();
}
}
applySortLock(mergeActive.value);
applySortLock(!!pointerTarget);
});
}
});
@ -1497,6 +1504,9 @@ watch(
onUnmounted(() => {
pendingResizedKeys.clear();
stopPressing();
window.removeEventListener('pointerup', handleGlobalPointerUp);
window.removeEventListener('pointercancel', handleGlobalPointerUp);
grid.value?.destroy();
grid.value = null;
});
@ -1505,10 +1515,11 @@ onUnmounted(() => {
<template>
<div
ref="gridRef"
class="grid-canvas"
:class="['grid-canvas', { 'is-dragging': isDragging, 'is-pressing': isPressing }]"
:style="{ '--layout-anim-ms': `${layoutAnimationMs}ms` }"
@click.capture="handleClickCapture"
@click="handleClick"
@pointerdown="handlePointerDown"
@contextmenu.prevent="handleContextMenu"
>
<div
@ -1576,6 +1587,20 @@ onUnmounted(() => {
transition: opacity 160ms ease;
}
.grid-item * {
cursor: inherit;
}
.grid-canvas.is-dragging,
.grid-canvas.is-dragging * {
cursor: grabbing !important;
}
.grid-canvas.is-pressing,
.grid-canvas.is-pressing * {
cursor: grabbing !important;
}
.grid-item.is-resized {
overflow: visible;
}