From d7e1ad1c0c1a8d0f052a67bbc41fd562cad461d4 Mon Sep 17 00:00:00 2001 From: Jarvis Jr Hermes Date: Mon, 5 Oct 2026 12:50:12 +0000 Subject: [PATCH] fix: right-align inline diagram controls --- scripts/browser/inline-controls.cjs | 19 ++++++++++++++++++- scripts/inline-viewer.css | 15 ++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/scripts/browser/inline-controls.cjs b/scripts/browser/inline-controls.cjs index 6fd7237..d70a804 100644 --- a/scripts/browser/inline-controls.cjs +++ b/scripts/browser/inline-controls.cjs @@ -25,6 +25,21 @@ const fs = require('node:fs'); const box = await frame.locator(`#${id}`).boundingBox(); assert(box && box.width >= 40 && box.height >= 44, id); } + const toolbar = await frame.locator('.toolbar').boundingBox(); + const exportButton = await frame.locator('#btn-export').boundingBox(); + const diagram = await frame.locator('.diagram-container > svg').boundingBox(); + const rightGap = toolbar.x + toolbar.width - exportButton.x - exportButton.width; + assert(Math.abs(rightGap - 8) < 1, `toolbar right padding at ${width}: ${rightGap}`); + assert(diagram.y >= toolbar.y + toolbar.height, 'toolbar must stay above nodes'); + const checkMenu = async selector => { + await frame.locator(selector).evaluate(n => Promise.all(n.getAnimations().map(a => a.finished))); + const menu = await frame.locator(selector).boundingBox(); + const viewport = await iframe.boundingBox(); + const currentToolbar = await frame.locator('.toolbar').boundingBox(); + assert(menu && menu.x >= viewport.x - 1 && menu.y >= currentToolbar.y + currentToolbar.height - 1, selector); + assert(menu.x + menu.width <= viewport.x + viewport.width + 1, `${selector} clips horizontally`); + assert(menu.y + menu.height <= viewport.y + viewport.height + 1, `${selector} clips vertically`); + }; const light = await frame.locator('body').evaluate(n => getComputedStyle(n).backgroundColor); await frame.locator('#btn-theme').click(); await theme('dark'); assert.notEqual(await frame.locator('body').evaluate(n => getComputedStyle(n).backgroundColor), light); @@ -42,6 +57,7 @@ const fs = require('node:fs'); await portalTheme('light'); await theme('light'); for (const preset of ['signal-flow', 'blueprint', 'editorial', 'classic']) { await frame.locator('#btn-preset').click(); + await checkMenu('.preset-menu'); await frame.locator(`[data-preset-value="${preset}"]`).click(); assert.equal(await frame.locator('html').getAttribute('data-preset'), preset); } @@ -53,6 +69,7 @@ const fs = require('node:fs'); const exports = []; for (const format of ['png', 'jpeg', 'webp', 'svg', 'svg-light', 'svg-dark', 'webm']) { await frame.locator('#btn-export').click(); + await checkMenu('.export-menu'); if (format === 'webm' && await frame.locator('[data-format="webm"]').isDisabled()) { exports.push({format, unsupported: true}); await frame.locator('#btn-export').click(); @@ -108,7 +125,7 @@ const fs = require('node:fs'); await next.scrollIntoViewIfNeeded(); await (await (await next.elementHandle()).contentFrame()).waitForFunction(() => document.documentElement.dataset.theme === 'dark'); assert(!await page.evaluate(() => document.documentElement.scrollWidth > innerWidth)); - results.push({width, themeSync:true, presets:4, exports, fullscreen:true, routeTheme:true}); + results.push({width, toolbar, rightGap, dropdownsUnclipped:true, themeSync:true, presets:4, exports, fullscreen:true, routeTheme:true}); fs.writeFileSync(`${output}/results.json`, JSON.stringify(results,null,2)); await context.close(); } diff --git a/scripts/inline-viewer.css b/scripts/inline-viewer.css index 3641724..c6615f1 100644 --- a/scripts/inline-viewer.css +++ b/scripts/inline-viewer.css @@ -3,9 +3,9 @@ html[data-embed="true"] .toolbar { display: flex !important; position: relative; inset: auto; - justify-content: center; + justify-content: flex-end; height: 52px; - padding: 4px; + padding: 4px 8px; box-sizing: border-box; background: var(--bg); } @@ -16,17 +16,18 @@ html[data-embed="true"] .diagram-container > svg { } html[data-embed="true"] .toolbar .preset-menu, html[data-embed="true"] .toolbar .export-menu { - position: absolute; - top: 100%; - left: 0; - right: auto; + position: fixed; + top: 52px; + left: auto; + right: 0; width: min(320px, 100vw); max-width: 100vw; max-height: calc(100vh - 52px); overflow-y: auto; box-sizing: border-box; } +/* Keep the preset menu in the iframe viewport without moving its capsule. */ html[data-embed="true"] .toolbar .export-wrap { position: static; } -html[data-embed="true"] .toolbar .export-menu { left: auto; right: 0; } +html[data-embed="true"] .toolbar .export-menu { position: absolute; top: 100%; } html[data-embed="true"] .toolbar .preset-menu button, html[data-embed="true"] .toolbar .export-menu button { min-height: 44px; }