This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch fix/dashboard-editor-flow
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git

commit 1526267a656f23168d8b4f9f0d68076f5951987b
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">

Reply via email to