Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions packages/diagram-client/src/diagram-client.css
Original file line number Diff line number Diff line change
Expand Up @@ -3283,12 +3283,15 @@ details[open] > summary .agent-chat-expand-arrow {
z-index: 1002;
}

/* Opens beside its button in the floating stack (bottom right), growing
upward; hierarchy-outline.ts `panelPlacement` sets right/bottom/max-height
from the button's position. These are where it opens before it is measured. */
.workflow-hierarchy-outline {
position: fixed;
top: 78px;
left: 8px;
bottom: 48px;
z-index: 30;
right: 60px;
bottom: calc(var(--workflow-wf-nav-height, 0px) + 16px);
max-height: 70vh;
z-index: 1002;
width: 300px;
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -3339,6 +3342,7 @@ details[open] > summary .agent-chat-expand-arrow {

.workflow-hierarchy-outline-body {
flex: 1;
min-height: 0; /* scrolls inside the panel's max-height */
overflow: auto;
padding-bottom: 6px;
}
Expand Down
40 changes: 40 additions & 0 deletions packages/diagram-client/src/hierarchy-outline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export class HierarchyOutlinePanel {
const body = document.getElementById(BODY_ID);
body?.addEventListener('click', event => this.onRowClick(event, false));
body?.addEventListener('dblclick', event => this.onRowClick(event, true));
window.addEventListener('resize', () => this.placeNearButton());
document.addEventListener('keydown', event => {
const target = event.target as HTMLElement | null;
const typing = target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable);
Expand All @@ -176,6 +177,8 @@ export class HierarchyOutlinePanel {
if (panel) {
panel.hidden = !available || !this.open;
}
// The button may have moved (the stack grows and shrinks): follow it.
requestAnimationFrame(() => this.placeNearButton());
}

private setOpen(open: boolean): void {
Expand All @@ -187,10 +190,28 @@ export class HierarchyOutlinePanel {
}
document.getElementById(TOGGLE_ID)?.classList.toggle('active', open);
if (open) {
this.placeNearButton();
this.render();
}
}

/** Open the panel beside its button, wherever the button stack put it. */
private placeNearButton(): void {
const panel = document.getElementById(PANEL_ID);
const toggle = document.getElementById(TOGGLE_ID);
if (!panel || !toggle || panel.hidden || toggle.hidden) {
return;
}
const rect = toggle.getBoundingClientRect();
if (rect.width === 0 && rect.height === 0) {
return;
}
const place = panelPlacement(rect, { width: window.innerWidth, height: window.innerHeight });
panel.style.right = `${place.right}px`;
panel.style.bottom = `${place.bottom}px`;
panel.style.maxHeight = `${place.maxHeight}px`;
}

// ── Rows ────────────────────────────────────────────────────────────

private render(): void {
Expand Down Expand Up @@ -362,6 +383,25 @@ export class HierarchyOutlinePanel {
}
}

/** Space between the button and the panel, and kept from the window's top edge. */
const PANEL_GAP_PX = 8;

/**
* Where the panel opens: just left of its button, bottom-aligned with it, and
* growing upward -- as tall as the room above the button allows. In the
* coordinates `position: fixed` takes (`right`/`bottom` from the window edges).
*/
export function panelPlacement(
button: { left: number; bottom: number },
window: { width: number; height: number }
): { right: number; bottom: number; maxHeight: number } {
return {
right: Math.max(PANEL_GAP_PX, window.width - button.left + PANEL_GAP_PX),
bottom: Math.max(PANEL_GAP_PX, window.height - button.bottom),
maxHeight: Math.max(160, button.bottom - PANEL_GAP_PX * 2)
};
}

/**
* Put the hierarchy button in the floating button stack, directly under the
* chat button. The stack is a reversed column -- what comes first in it sits
Expand Down
18 changes: 17 additions & 1 deletion packages/diagram-client/test/outline-button-and-viewport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* instead of to the default scroll, which put the graph off-center.
*/
import { describe, expect, it } from 'vitest';
import { placeInButtonStack } from '../src/hierarchy-outline';
import { panelPlacement, placeInButtonStack } from '../src/hierarchy-outline';
import { modelViewportKey, settleViewport } from '../src/viewport-preserving-set-model-command';

/** A minimal element tree: enough for parent/sibling bookkeeping. */
Expand Down Expand Up @@ -112,3 +112,19 @@ describe('the viewport across a model change', () => {
expect({ zoom: refreshed.zoom, scroll: refreshed.scroll }).toEqual({ zoom: 1.2, scroll: { x: 5, y: 6 } });
});
});

describe('the hierarchy panel', () => {
it('opens just left of its button, bottom-aligned with it, growing upward', () => {
// A 1200x800 window; the button 32px wide, 14px from the right edge,
// its bottom 16px above the window's.
const place = panelPlacement({ left: 1154, bottom: 784 }, { width: 1200, height: 800 });

expect(place.right).toBe(1200 - 1154 + 8);
expect(place.bottom).toBe(16);
expect(place.maxHeight).toBe(784 - 16);
});

it('keeps a usable height when the button sits low in a short window', () => {
expect(panelPlacement({ left: 400, bottom: 100 }, { width: 500, height: 120 }).maxHeight).toBe(160);
});
});
Loading