From b4807ec77c53756b9c36c9354f0f284242d19309 Mon Sep 17 00:00:00 2001 From: Clifford <33897029+cray-com@users.noreply.github.com> Date: Tue, 1 Sep 2026 09:24:40 +0200 Subject: [PATCH] fix: move draggable panel host visibly (#14) --- README.md | 2 +- fixture/smoke.html | 8 +++++--- src/element.ts | 15 ++++++++++----- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 1b058d3..f040dcf 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Website-CSS kann Tailwind über semantische Layer verwenden. Dynamische Reglerwe `DevtoolsConfig`, `Metadata`, `Target`, `Family`, `Variant`, `Range`, `Select`, `SelectOption`, `Toggle`, `DevtoolsState` sowie `defineDevtoolsConfig`, `validateConfig`, `initialState`, `validateState`, `encodeState`, `decodeState`, `stateUrl`, `applyEffects`, `changes`, `changesJson`, `agentBrief`, `resetBaseline`, `recipe` und `typescriptRecipe` sind serialisierbar bzw. strict typisiert. Nicht-kanonische Alias-Exporte werden nicht angeboten. Ungültige URL-Werte werden verworfen (fail-closed). -Das Panel startet platzsparend eingeklappt. Es bietet Reset, Permalink-, JSON- und TypeScript-Copy sowie JSON-Download. Es ist vollständig ausblendbar und per Cmd/Ctrl+Shift+D wiederherstellbar beziehungsweise umschaltbar. Tastaturfokus und Reduced Motion werden berücksichtigt. URL-State verwendet nur den Parameter `fd` und erhält vorhandene Parameter. +Das Panel startet beim ersten Aufruf geöffnet und merkt sich danach Position, aktive Ansicht und den eingeklappten Zustand. Es bietet Reset, Permalink-, JSON- und TypeScript-Copy sowie JSON-Download. Es ist vollständig ausblendbar und per Cmd/Ctrl+Shift+D wiederherstellbar beziehungsweise umschaltbar. Tastaturfokus und Reduced Motion werden berücksichtigt. URL-State verwendet nur den Parameter `fd` und erhält vorhandene Parameter. ## DOM-Inspector (V1) diff --git a/fixture/smoke.html b/fixture/smoke.html index a78db43..b9a34c3 100644 --- a/fixture/smoke.html +++ b/fixture/smoke.html @@ -181,7 +181,8 @@ panel.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, clientX: panelRect.left + 20, clientY: panelRect.top + 20, pointerId: 7 })); panel.dispatchEvent(new PointerEvent('pointermove', { bubbles: true, clientX: 1, clientY: panelRect.top + 80, pointerId: 7 })); panel.dispatchEvent(new PointerEvent('pointerup', { bubbles: true, clientX: 1, clientY: panelRect.top + 80, pointerId: 7 })); - const edgeSnap = panel.style.left === '0px' && panel.getBoundingClientRect().top >= 0; + const snappedRect = panel.getBoundingClientRect(); + const edgeSnap = snappedRect.left === 0 && snappedRect.top >= 0 && snappedRect.left !== panelRect.left; const running = document.createElement('video'); const paused = document.createElement('video'); document.body.append(running, paused); const runningBefore = { currentTime: running.currentTime, paused: running.paused }; const pausedBefore = { currentTime: paused.currentTime, paused: paused.paused }; @@ -193,8 +194,9 @@ const restoredMotion = getComputedStyle(motion).animationPlayState !== 'paused' && getComputedStyle(motion).transitionProperty !== 'none' && !shadow.querySelector('style[data-fd-freeze]') && !document.querySelector('style[data-fd-freeze]'); const videoState = frozenVideoState && running.paused === runningBefore.paused && running.currentTime === runningBefore.currentTime && paused.paused === pausedBefore.paused && paused.currentTime === pausedBefore.currentTime; running.remove(); paused.remove(); - panel.style.left = '9999px'; panel.style.top = '9999px'; window.dispatchEvent(new Event('resize')); - const resizeClamped = panel.getBoundingClientRect().right <= innerWidth && panel.getBoundingClientRect().bottom <= innerHeight; + element.style.left = '9999px'; element.style.top = '9999px'; window.dispatchEvent(new Event('resize')); + const resizedRect = panel.getBoundingClientRect(); + const resizeClamped = resizedRect.right <= innerWidth && resizedRect.bottom <= innerHeight && resizedRect.left >= 0 && resizedRect.top >= 0; const pickForReload = (target) => { root.querySelector('[data-action="pick"]').click(); target.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); diff --git a/src/element.ts b/src/element.ts index cec4f4e..8aa680b 100644 --- a/src/element.ts +++ b/src/element.ts @@ -24,7 +24,7 @@ import { } from './core.js'; const styles = ` -:host { all: initial; position: fixed; inset: auto 12px 12px auto; z-index: 2147483647; color: #e9edf2; font: 12px/1.3 ui-monospace, SFMono-Regular, monospace; } +:host { all: initial; display: block; position: fixed; inset: auto 12px 12px auto; z-index: 2147483647; color: #e9edf2; font: 12px/1.3 ui-monospace, SFMono-Regular, monospace; } * { box-sizing: border-box; } .panel { width: min(360px, calc(100vw - 24px)); max-height: min(760px, 70vh, calc(100vh - 24px)); overflow: auto; background: #17191c; border: 1px solid #454a51; border-radius: 6px; box-shadow: 0 5px 24px #0008; } .bar { display: flex; align-items: center; gap: 6px; padding: 5px 7px; background: #22262a; position: sticky; top: 0; } .tabs { display:flex; gap:2px; padding:4px 7px; border-bottom:1px solid #353a40; } .tabs button { flex:1; } @@ -119,7 +119,9 @@ export class FoundationDevtoolsElement extends HTMLElement { const proposedTop = endEvent ? rect.top + endEvent.clientY - start.clientY : current.top; const left = Math.abs(proposedLeft) <= edge ? 0 : Math.abs(innerWidth - (proposedLeft + current.width)) <= edge ? innerWidth - current.width : proposedLeft; const top = Math.abs(proposedTop) <= edge ? 0 : Math.abs(innerHeight - (proposedTop + current.height)) <= edge ? innerHeight - current.height : proposedTop; - this.setPanelPosition(left, top); this.savePanelPosition(left, top); + this.setPanelPosition(left, top); + const snapped = this.panel!.getBoundingClientRect(); + this.savePanelPosition(snapped.left, snapped.top); }; this.panel!.addEventListener('pointermove', move); this.panel!.addEventListener('pointerup', end); this.panel!.addEventListener('pointercancel', end); window.addEventListener('pointermove', move, true); window.addEventListener('pointerup', end, { once: true, capture: true }); window.addEventListener('pointercancel', end, { once: true, capture: true }); @@ -342,13 +344,16 @@ export class FoundationDevtoolsElement extends HTMLElement { if (!this.panel) return; const rect = this.panel.getBoundingClientRect(); const maxLeft = Math.max(0, innerWidth - rect.width); const maxTop = Math.max(0, innerHeight - rect.height); - this.panel.style.left = `${Math.max(0, Math.min(maxLeft, left))}px`; this.panel.style.top = `${Math.max(0, Math.min(maxTop, top))}px`; - this.panel.style.right = 'auto'; this.panel.style.bottom = 'auto'; + this.style.left = `${Math.max(0, Math.min(maxLeft, left))}px`; this.style.top = `${Math.max(0, Math.min(maxTop, top))}px`; + this.style.right = 'auto'; this.style.bottom = 'auto'; } private savePanelPosition(left: number, top: number): void { try { localStorage.setItem(`${this.storageKey}:position`, JSON.stringify({ left, top })); } catch {} } private clampPanel = (): void => { if (!this.panel) return; const rect = this.panel.getBoundingClientRect(); this.setPanelPosition(rect.left, rect.top); }; private restorePosition(): void { - try { const value = JSON.parse(localStorage.getItem(`${this.storageKey}:position`) ?? 'null'); if (value && Number.isFinite(value.left) && Number.isFinite(value.top)) { this.panel!.style.left = `${Math.max(0, Math.min(innerWidth - this.panel!.offsetWidth, value.left))}px`; this.panel!.style.top = `${Math.max(0, Math.min(innerHeight - this.panel!.offsetHeight, value.top))}px`; this.panel!.style.right = 'auto'; this.panel!.style.bottom = 'auto'; } } catch {} + try { + const value = JSON.parse(localStorage.getItem(`${this.storageKey}:position`) ?? 'null'); + if (value && Number.isFinite(value.left) && Number.isFinite(value.top)) this.setPanelPosition(value.left, value.top); + } catch {} } private restorePanelMode(): void { let mode: 'open' | 'collapsed' = 'open';