Fix: Resizable Panels — Drag-Overlay verhindert Text-Selektion
- Unsichtbares Fullscreen-Overlay während Drag fängt alle Maus-Events - document.body.style.userSelect blockiert sofort bei mousedown - Bestehende Selektion wird beim Drag-Start gelöscht - Handle 5px breit + unsichtbarer 15px Greifbereich (::before) - Visueller Grip-Indikator beim Hover Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
92353e2852
commit
a0ad11b66c
1 changed files with 80 additions and 28 deletions
|
|
@ -24,7 +24,6 @@
|
||||||
|
|
||||||
// ============ Resizable Panels ============
|
// ============ Resizable Panels ============
|
||||||
|
|
||||||
// Breiten in Pixel (werden zu fr umgerechnet)
|
|
||||||
let panelWidths = [220, 400, 400, 380];
|
let panelWidths = [220, 400, 400, 380];
|
||||||
let container: HTMLDivElement;
|
let container: HTMLDivElement;
|
||||||
let dragging: number | null = null;
|
let dragging: number | null = null;
|
||||||
|
|
@ -32,10 +31,9 @@
|
||||||
let startWidths: number[] = [];
|
let startWidths: number[] = [];
|
||||||
|
|
||||||
function getGridTemplate(): string {
|
function getGridTemplate(): string {
|
||||||
// 4 Panels + 3 Handles dazwischen: panel handle panel handle panel handle panel
|
|
||||||
return panelWidths.map((w, i) => {
|
return panelWidths.map((w, i) => {
|
||||||
if (i < panelWidths.length - 1) {
|
if (i < panelWidths.length - 1) {
|
||||||
return `${w}px 3px`;
|
return `${w}px 5px`;
|
||||||
}
|
}
|
||||||
return `${w}px`;
|
return `${w}px`;
|
||||||
}).join(' ');
|
}).join(' ');
|
||||||
|
|
@ -43,17 +41,30 @@
|
||||||
|
|
||||||
function onMouseDown(index: number, event: MouseEvent) {
|
function onMouseDown(index: number, event: MouseEvent) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
dragging = index;
|
dragging = index;
|
||||||
startX = event.clientX;
|
startX = event.clientX;
|
||||||
startWidths = [...panelWidths];
|
startWidths = [...panelWidths];
|
||||||
|
|
||||||
|
// Sofort Text-Selektion blockieren auf dem ganzen Dokument
|
||||||
|
document.body.style.userSelect = 'none';
|
||||||
|
document.body.style.webkitUserSelect = 'none';
|
||||||
|
document.body.style.cursor = 'col-resize';
|
||||||
|
|
||||||
|
// Bestehende Selektion löschen
|
||||||
|
window.getSelection()?.removeAllRanges();
|
||||||
|
|
||||||
window.addEventListener('mousemove', onMouseMove);
|
window.addEventListener('mousemove', onMouseMove);
|
||||||
window.addEventListener('mouseup', onMouseUp);
|
window.addEventListener('mouseup', onMouseUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseMove(event: MouseEvent) {
|
function onMouseMove(event: MouseEvent) {
|
||||||
if (dragging === null) return;
|
if (dragging === null) return;
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
const dx = event.clientX - startX;
|
const dx = event.clientX - startX;
|
||||||
const minWidth = 120;
|
const minWidth = 100;
|
||||||
|
|
||||||
const leftIdx = dragging;
|
const leftIdx = dragging;
|
||||||
const rightIdx = dragging + 1;
|
const rightIdx = dragging + 1;
|
||||||
|
|
@ -61,7 +72,6 @@
|
||||||
let newLeft = startWidths[leftIdx] + dx;
|
let newLeft = startWidths[leftIdx] + dx;
|
||||||
let newRight = startWidths[rightIdx] - dx;
|
let newRight = startWidths[rightIdx] - dx;
|
||||||
|
|
||||||
// Mindestbreiten einhalten
|
|
||||||
if (newLeft < minWidth) {
|
if (newLeft < minWidth) {
|
||||||
newRight -= (minWidth - newLeft);
|
newRight -= (minWidth - newLeft);
|
||||||
newLeft = minWidth;
|
newLeft = minWidth;
|
||||||
|
|
@ -73,22 +83,26 @@
|
||||||
|
|
||||||
panelWidths[leftIdx] = Math.max(minWidth, newLeft);
|
panelWidths[leftIdx] = Math.max(minWidth, newLeft);
|
||||||
panelWidths[rightIdx] = Math.max(minWidth, newRight);
|
panelWidths[rightIdx] = Math.max(minWidth, newRight);
|
||||||
panelWidths = panelWidths; // Svelte reactivity
|
panelWidths = panelWidths;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseUp() {
|
function onMouseUp() {
|
||||||
dragging = null;
|
dragging = null;
|
||||||
|
|
||||||
|
// Selektion wieder erlauben
|
||||||
|
document.body.style.userSelect = '';
|
||||||
|
document.body.style.webkitUserSelect = '';
|
||||||
|
document.body.style.cursor = '';
|
||||||
|
|
||||||
window.removeEventListener('mousemove', onMouseMove);
|
window.removeEventListener('mousemove', onMouseMove);
|
||||||
window.removeEventListener('mouseup', onMouseUp);
|
window.removeEventListener('mouseup', onMouseUp);
|
||||||
|
|
||||||
// Breiten in localStorage speichern
|
|
||||||
try {
|
try {
|
||||||
localStorage.setItem('panel-widths', JSON.stringify(panelWidths));
|
localStorage.setItem('panel-widths', JSON.stringify(panelWidths));
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Gespeicherte Breiten laden
|
|
||||||
try {
|
try {
|
||||||
const saved = localStorage.getItem('panel-widths');
|
const saved = localStorage.getItem('panel-widths');
|
||||||
if (saved) {
|
if (saved) {
|
||||||
|
|
@ -99,10 +113,10 @@
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
// Initiale Breiten an Fenster anpassen wenn zu breit
|
// Breiten an Fenster anpassen
|
||||||
if (container) {
|
if (container) {
|
||||||
const total = panelWidths.reduce((a, b) => a + b, 0);
|
const total = panelWidths.reduce((a, b) => a + b, 0);
|
||||||
const available = container.clientWidth - 9; // 3 Handles à 3px
|
const available = container.clientWidth - 15; // 3 Handles à 5px
|
||||||
if (total > available) {
|
if (total > available) {
|
||||||
const scale = available / total;
|
const scale = available / total;
|
||||||
panelWidths = panelWidths.map(w => Math.round(w * scale));
|
panelWidths = panelWidths.map(w => Math.round(w * scale));
|
||||||
|
|
@ -118,21 +132,23 @@
|
||||||
style="grid-template-columns: {getGridTemplate()}"
|
style="grid-template-columns: {getGridTemplate()}"
|
||||||
>
|
>
|
||||||
<!-- Session-Sidebar -->
|
<!-- Session-Sidebar -->
|
||||||
<aside class="panel panel-sessions">
|
<aside class="panel">
|
||||||
<SessionList />
|
<SessionList />
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div class="resize-handle" on:mousedown={(e) => onMouseDown(0, e)} role="separator" aria-orientation="vertical"></div>
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div class="handle" on:mousedown={(e) => onMouseDown(0, e)}><div class="handle-grip"></div></div>
|
||||||
|
|
||||||
<!-- Chat -->
|
<!-- Chat -->
|
||||||
<section class="panel panel-chat">
|
<section class="panel">
|
||||||
<ChatPanel />
|
<ChatPanel />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="resize-handle" on:mousedown={(e) => onMouseDown(1, e)} role="separator" aria-orientation="vertical"></div>
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div class="handle" on:mousedown={(e) => onMouseDown(1, e)}><div class="handle-grip"></div></div>
|
||||||
|
|
||||||
<!-- Aktivität / Memory / Audit -->
|
<!-- Aktivität / Memory / Audit -->
|
||||||
<section class="panel panel-activity">
|
<section class="panel">
|
||||||
<div class="panel-tabs">
|
<div class="panel-tabs">
|
||||||
{#each middleTabs as tab}
|
{#each middleTabs as tab}
|
||||||
<button
|
<button
|
||||||
|
|
@ -155,10 +171,11 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="resize-handle" on:mousedown={(e) => onMouseDown(2, e)} role="separator" aria-orientation="vertical"></div>
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div class="handle" on:mousedown={(e) => onMouseDown(2, e)}><div class="handle-grip"></div></div>
|
||||||
|
|
||||||
<!-- Agents / Guard-Rails -->
|
<!-- Agents / Guard-Rails -->
|
||||||
<section class="panel panel-details">
|
<section class="panel">
|
||||||
<div class="panel-tabs">
|
<div class="panel-tabs">
|
||||||
{#each rightTabs as tab}
|
{#each rightTabs as tab}
|
||||||
<button
|
<button
|
||||||
|
|
@ -180,17 +197,19 @@
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Unsichtbares Overlay während Drag — fängt alle Maus-Events ab -->
|
||||||
|
{#if dragging !== null}
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div class="drag-overlay" on:mousemove={onMouseMove} on:mouseup={onMouseUp}></div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.panels {
|
.panels {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
}
|
position: relative;
|
||||||
|
|
||||||
.panels.dragging {
|
|
||||||
cursor: col-resize;
|
|
||||||
user-select: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
|
|
@ -201,20 +220,53 @@
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Resize Handle */
|
/* Resize Handle — 5px breit, mit unsichtbarem 15px Greifbereich */
|
||||||
.resize-handle {
|
.handle {
|
||||||
width: 3px;
|
width: 5px;
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
background: var(--border);
|
background: var(--border);
|
||||||
transition: background 0.15s ease;
|
position: relative;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resize-handle:hover,
|
.handle::before {
|
||||||
.panels.dragging .resize-handle {
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: -5px;
|
||||||
|
right: -5px;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle:hover {
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.handle-grip {
|
||||||
|
width: 1px;
|
||||||
|
height: 30px;
|
||||||
|
background: var(--text-secondary);
|
||||||
|
border-radius: 1px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle:hover .handle-grip {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drag-Overlay — ganzer Bildschirm, unsichtbar, fängt Maus-Events */
|
||||||
|
.drag-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
cursor: col-resize;
|
||||||
|
}
|
||||||
|
|
||||||
/* Tabs */
|
/* Tabs */
|
||||||
.panel-tabs {
|
.panel-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue