This is an automated email from the ASF dual-hosted git repository. wu-sheng pushed a commit to branch fix/dashboard-editor-controls in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git
commit 281bfe68980e2bb95e737591b5750d04bb9ed597 Author: Wu Sheng <[email protected]> AuthorDate: Thu Jun 25 09:30:41 2026 +0800 fix(layer-dashboards): flow the widget editor into view so it opens complete The widget canvas sits far below the scope config, so clicking a widget without first scrolling left the sticky editor drawer at its natural position — entirely below the fold, clamped to its min height with the form overflowing. On select the editor now scrolls fully into view (and re-fits), but only when it isn't already usable, so clicking through widgets with the editor pinned at the top doesn't yank the page around. --- CHANGELOG.md | 2 +- .../admin/layer-templates/LayerDashboardsAdmin.vue | 34 +++++++++++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb9234e..880cd01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ The version line is shared by every package in the monorepo (apps + shared packa ### Layer dashboard editor -- **The widget editor's move / delete controls stay reachable.** On the Layer dashboards admin, the per-widget editor's `Up` / `Down` / `Delete` row is now a pinned footer instead of the last thing in a scrolling panel, and the panel sizes itself to the visible area — so Delete no longer slips below the fold on a long form or a short board. Adding a widget also scrolls the new widget into view, next to the editor that opens for it, instead of leaving it off-screen at the bottom of the canvas. +- **The widget editor always opens complete and reachable.** On the Layer dashboards admin, the per-widget editor's `Up` / `Down` / `Delete` row is now a pinned footer instead of the last thing in a scrolling panel, and the panel sizes itself to the visible area — so Delete no longer slips below the fold on a long form or a short board. Selecting a widget flows the editor fully into view (the canvas sits well below the scope config, so it used to open off-screen), and adding a widget scr [...] ## 0.7.0 diff --git a/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue b/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue index 52288fe..1207419 100644 --- a/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue +++ b/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue @@ -609,11 +609,31 @@ function onWinResizeScope(): void { updateScopeScroll(); } // instead, so the footer is always visible. Scroll listens in the capture // phase so the inner content pane's scroll (not just window) re-syncs it. const drawerEl = ref<HTMLElement | null>(null); +const editorCardEl = ref<HTMLElement | null>(null); function syncDrawerHeight(): void { const el = drawerEl.value; if (!el) return; el.style.height = `${Math.max(220, window.innerHeight - el.getBoundingClientRect().top - 8)}px`; } +/** Flow the editor into view so it shows complete on select. The widget canvas + * sits far below the scope config, so clicking a widget without first + * scrolling leaves the sticky drawer at its natural (off-screen) position, + * clamped to its min height. Only scroll when it isn't already fully usable — + * so clicking through widgets while the editor is pinned at the top doesn't + * yank the page around. */ +function flowEditorIntoView(): void { + const el = drawerEl.value; + const card = editorCardEl.value; + if (!el || !card) return; + const body = el.querySelector('.drawer-body'); + const lowOnScreen = el.getBoundingClientRect().top > 120; + const cramped = body ? body.scrollHeight > body.clientHeight + 1 : false; + if (lowOnScreen || cramped) { + card.scrollIntoView({ behavior: 'smooth', block: 'start' }); + // The smooth scroll changes the drawer's top; re-fit once it settles. + window.setTimeout(syncDrawerHeight, 400); + } +} onMounted(() => { window.addEventListener('resize', onWinResizeScope); window.addEventListener('scroll', syncDrawerHeight, { capture: true, passive: true }); @@ -708,9 +728,15 @@ function moveWidget(i: number, dir: -1 | 1): void { * ------------------------------------------------------------------- */ const selectedIdx = ref<number | null>(null); -/* Re-fit the drawer to the viewport when it opens / the target widget changes - * (content height differs); the scroll + resize listeners keep it fitted after. */ -watch(selectedIdx, () => void nextTick(syncDrawerHeight)); +/* When a widget is selected: re-fit the drawer, then (if it isn't already fully + * usable) flow the editor into view so it shows complete. The scroll + resize + * listeners keep it fitted afterwards. */ +watch(selectedIdx, (idx) => { + void nextTick(() => { + syncDrawerHeight(); + if (idx !== null) void nextTick(flowEditorIntoView); + }); +}); /** When the user switches scope or layer we drop the selection so the * drawer doesn't refer to a widget that no longer exists. */ @@ -3675,7 +3701,7 @@ const namingTest = computed<NamingTestResult>(() => { </div> </section> - <section v-else class="sw-card editor-card"> + <section v-else ref="editorCardEl" class="sw-card editor-card"> <div class="card-head"> <h4>{{ scopeLabel(activeScope) }} widgets</h4> <span class="sub">
