You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/06/25 01:30:52 UTC
(skywalking-horizon-ui) 01/01: fix(layer-dashboards): flow the widget editor into view so it opens complete
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 <wu...@foxmail.com>
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">