From 3a738a246a4fbdef26a2478161630d865c3403c2 Mon Sep 17 00:00:00 2001 From: Krzysztof Rosinski Date: Wed, 14 Oct 2026 05:24:24 +0200 Subject: [PATCH 1/3] feat: refine demo themes and dismiss settings from backdrop --- demo/main.ts | 59 +++++++++- demo/style.css | 122 ++++++++++++++------ docs/guide/configuration.md | 2 +- index.html | 30 +++-- tests/browser/appearance.spec.ts | 15 ++- tests/browser/grid-detail.spec.ts | 2 +- tests/browser/grid-label-visibility.spec.ts | 2 +- tests/browser/settings-dialog.spec.ts | 43 ++++++- 8 files changed, 208 insertions(+), 67 deletions(-) diff --git a/demo/main.ts b/demo/main.ts index 566f84a..ba08e7f 100644 --- a/demo/main.ts +++ b/demo/main.ts @@ -41,21 +41,72 @@ settingsButton.addEventListener('click', () => { }); window.addEventListener('resize', positionSettingsDialog); window.addEventListener('scroll', positionSettingsDialog, { passive: true }); -settingsDialog.addEventListener('close', () => settingsButton.focus({ preventScroll: true })); +let settingsPressOutside = false; +function outsideSettings(event: MouseEvent): boolean { + const bounds = settingsDialog.getBoundingClientRect(); + return ( + event.target === settingsDialog && + (event.clientX < bounds.left || + event.clientX > bounds.right || + event.clientY < bounds.top || + event.clientY > bounds.bottom) + ); +} +settingsDialog.addEventListener('pointerdown', (event) => { + settingsPressOutside = event.button === 0 && outsideSettings(event); +}); +settingsDialog.addEventListener('pointercancel', () => { + settingsPressOutside = false; +}); +settingsDialog.addEventListener('click', (event) => { + if (settingsPressOutside && outsideSettings(event)) { + settingsDialog.close(); + } + settingsPressOutside = false; +}); +settingsDialog.addEventListener('close', () => { + settingsPressOutside = false; + settingsButton.focus({ preventScroll: true }); +}); + +function demoAppearance(theme: SmithAppearance['theme']): SmithAppearance { + return { + theme, + ...(theme === 'dark' + ? { + overrides: { + background: '#191c20', + fontFamily: 'Arial, sans-serif', + grid: { stroke: '#727984', textColor: '#c7cbd1' }, + boundary: { stroke: '#9da3ad' }, + scales: { + stroke: '#727984', + boundaryColor: '#9da3ad', + textColor: '#b5bcc5', + valueColor: '#e4e7eb', + indicatorColor: '#e9ac59', + indicatorOutline: '#191c20', + }, + cursor: { pointColor: '#a0bce5', impedanceColor: '#a0bce5' }, + }, + } + : {}), + }; +} const smith = new Smith({ - appearance: { theme: 'dark' }, + appearance: demoAppearance('dark'), interaction: { zoom: true, cursor: true }, peripheralScales: { visible: true }, grid: { detail: 'detailed' }, }); smith.draw('#smith'); -const scales = new SmithScales({ theme: 'dark' }); +const scales = new SmithScales(demoAppearance('dark')); scales.draw('#smith-scales'); element('theme').addEventListener('change', (event) => { const theme = (event.target as HTMLSelectElement).value as SmithAppearance['theme']; - const appearance: SmithAppearance = { theme }; + const appearance = demoAppearance(theme); smith.setOptions({ appearance }); scales.setAppearance(appearance); document.documentElement.dataset.theme = theme; diff --git a/demo/style.css b/demo/style.css index 1190d90..0a8cc76 100644 --- a/demo/style.css +++ b/demo/style.css @@ -1,16 +1,21 @@ :root { - --text: #24364b; - --page: #f3f6f9; + --text: #25292f; + --page: #f4f5f6; --surface: white; - --muted: #617286; - --accent: #287e76; - --border: #dce4eb; - --input-border: #bbcbd4; - --hover: #edf5f4; + --muted: #626a75; + --accent: #315f94; + --border: #dfe2e6; + --input-border: #b8bec7; + --hover: #e9edf2; --error: #b42318; color-scheme: light; - font-family: Inter, system-ui, sans-serif; + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + sans-serif; color: var(--text); background: var(--page); font-synthesis: none; @@ -29,15 +34,9 @@ main { header { margin-bottom: 28px; } -.eyebrow { - color: var(--accent); - font-size: 11px; - font-weight: 750; - letter-spacing: 0.16em; -} h1 { font-size: 36px; - letter-spacing: -0.04em; + letter-spacing: -0.025em; margin: 6px 0 8px; } p { @@ -62,10 +61,8 @@ nav[aria-label='Resources'] a { align-items: center; justify-content: flex-start; gap: 20px; - background: var(--surface); - border: 1px solid var(--border); - border-radius: 12px; - padding: 16px 20px; + border-bottom: 1px solid var(--border); + padding: 0 0 12px; margin-bottom: 20px; } fieldset { @@ -121,7 +118,7 @@ select:focus-visible { .panel { background: var(--surface); border: 1px solid var(--border); - border-radius: 12px; + border-radius: 6px; } .chart-column { min-width: 0; @@ -389,7 +386,7 @@ footer { } .toolbar { gap: 12px; - padding: 8px 16px; + padding: 0 0 12px; margin-bottom: 12px; } .workspace { @@ -447,15 +444,15 @@ footer { } :root[data-theme='dark'] { - --text: #e2e8f0; - --page: #020617; - --surface: #0f172a; - --muted: #94a3b8; - --accent: #5eead4; - --border: #334155; - --input-border: #475569; - --hover: #1e293b; - --error: #fca5a5; + --text: #e4e7eb; + --page: #111315; + --surface: #191c20; + --muted: #a0a7b2; + --accent: #a0bce5; + --border: #30353c; + --input-border: #4d5560; + --hover: #272c33; + --error: #efa19b; color-scheme: dark; } #chart-settings, @@ -464,14 +461,14 @@ footer { max-height: calc(100dvh - 32px); padding: 24px; border: 1px solid var(--border); - border-radius: 16px; + border-radius: 8px; background: var(--surface); color: var(--text); - box-shadow: 0 16px 64px rgb(0 0 0 / 25%); + box-shadow: 0 12px 32px rgb(0 0 0 / 22%); } #chart-settings::backdrop, #image-export::backdrop { - background: rgb(2 6 23 / 45%); + background: rgb(0 0 0 / 42%); } @media (min-width: 1000px) { #chart-settings { @@ -481,7 +478,7 @@ footer { max-height: calc(100dvh - var(--settings-top, 16px) - 16px); } #chart-settings::backdrop { - background: rgb(2 6 23 / 25%); + background: rgb(0 0 0 / 28%); } } .settings-heading { @@ -667,3 +664,60 @@ select, width: 100%; margin: 6px 0 12px; } + +.header-controls { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 12px 24px; + font-size: 13px; +} +.theme-control { + display: flex; + align-items: center; + gap: 8px; +} +.theme-control label { + color: var(--muted); +} +.theme-control select { + min-height: 36px; + border: 1px solid var(--input-border); + border-radius: 4px; + padding-block: 6px; + padding-left: 10px; + font: inherit; +} +button:disabled, +select:disabled { + opacity: 0.5; + cursor: default; +} +input[type='file']::file-selector-button { + margin-right: 12px; + padding: 7px 10px; + border: 1px solid var(--input-border); + border-radius: 4px; + background: var(--surface); + color: var(--text); + cursor: pointer; +} +input[type='file']::file-selector-button:hover { + background: var(--hover); +} +@media (max-width: 600px) { + .header-heading { + flex-wrap: wrap; + gap: 8px; + margin-bottom: 12px; + } + .header-controls { + width: 100%; + justify-content: space-between; + gap: 12px; + } + nav[aria-label='Resources'] { + gap: 12px; + } +} diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 30685d8..f296e7e 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -162,7 +162,7 @@ those individual overrides. Invalid appearance settings throw before making chan SVG exports resolve the appearance without requiring theme CSS. A configured background is included in the exported image; `transparent` preserves transparency. -The **Theme** selector in the demo's **Chart settings** dialog updates both library components and the demo UI. +The **Theme** selector in the demo's top-right header updates both library components and the demo UI. ## Grid layers diff --git a/index.html b/index.html index da959ed..5334d80 100644 --- a/index.html +++ b/index.html @@ -12,13 +12,21 @@
- RF WORKBENCH

Smith chart

- +
+ +
+ + +
+

Explore impedance, admittance and reflection. Reference impedance: @@ -184,18 +192,8 @@

Chart settings

- Customize the grid, labels, appearance, and reference impedance. + Customize the grid, labels, and reference impedance.

-
- Appearance -
- - -
-
Chart layers diff --git a/tests/browser/appearance.spec.ts b/tests/browser/appearance.spec.ts index fefe8f0..d03f694 100644 --- a/tests/browser/appearance.spec.ts +++ b/tests/browser/appearance.spec.ts @@ -231,21 +231,24 @@ test('demo theme switch updates chart and scales without losing marker selection await page.getByRole('button', { name: 'Load sample trace', exact: true }).click(); await page.getByRole('tab', { name: 'Marker', exact: true }).click(); await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); + await expect(page.locator('header #theme')).toBeVisible(); + await expect(page.getByRole('dialog', { name: 'Chart settings', exact: true })).not.toBeVisible(); const selected = await page.locator('#marker-select').inputValue(); - await page.getByRole('button', { name: 'Chart settings', exact: true }).click(); await page.getByLabel('Theme', { exact: true }).selectOption('dark'); - await page.getByRole('button', { name: 'Close', exact: true }).click(); - await expect(page.locator('#smith svg')).toHaveCSS('background-color', 'rgb(15, 23, 42)'); + await expect(page.locator('#smith svg')).toHaveCSS('background-color', 'rgb(25, 28, 32)'); await expect(page.locator('#smith-scales .radial-scales')).toHaveCSS( 'background-color', - 'rgb(15, 23, 42)', + 'rgb(25, 28, 32)', ); await expect(page.locator('#marker-select')).toHaveValue(selected); await page.getByRole('button', { name: 'Focus on chart' }).click(); await page.keyboard.press('End'); await expect(page.locator('#marker-readout')).toContainText('2 GHz'); - await page.getByRole('button', { name: 'Chart settings', exact: true }).click(); await page.getByLabel('Theme', { exact: true }).selectOption('light'); - await page.getByRole('button', { name: 'Close', exact: true }).click(); await expect(page.locator('#smith svg')).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); + await expect(page.locator('html')).toHaveAttribute('data-theme', 'light'); + await expect(page.locator('#marker-select')).toHaveValue(selected); + await page.getByLabel('Theme', { exact: true }).selectOption('dark'); + await expect(page.locator('#smith svg')).toHaveCSS('background-color', 'rgb(25, 28, 32)'); + await expect(page.locator('#marker-readout')).toContainText('2 GHz'); }); diff --git a/tests/browser/grid-detail.spec.ts b/tests/browser/grid-detail.spec.ts index 74babf9..0ec6dc1 100644 --- a/tests/browser/grid-detail.spec.ts +++ b/tests/browser/grid-detail.spec.ts @@ -139,8 +139,8 @@ test('demo selector applies all three levels to both grids and exports the basic await page.getByRole('button', { name: 'Chart settings', exact: true }).click(); await page.locator('#impedance').uncheck(); await page.locator('#admittance').check(); - await page.locator('#theme').selectOption('dark'); await page.getByRole('button', { name: 'Close', exact: true }).click(); + await page.locator('#theme').selectOption('dark'); await page.screenshot({ path: 'test-results/basic-admittance-dark.png', fullPage: true }); const download = page.waitForEvent('download'); await page.getByRole('button', { name: 'Export', exact: true }).click(); diff --git a/tests/browser/grid-label-visibility.spec.ts b/tests/browser/grid-label-visibility.spec.ts index 3cdf8a6..7fa9428 100644 --- a/tests/browser/grid-label-visibility.spec.ts +++ b/tests/browser/grid-label-visibility.spec.ts @@ -111,8 +111,8 @@ test('demo checkbox controls all grid labels across detail, visibility, zoom, an await page.getByLabel('Grid detail').selectOption('basic'); await page.getByLabel('Impedance', { exact: true }).uncheck(); await page.getByLabel('Admittance', { exact: true }).check(); - await page.locator('#theme').selectOption('dark'); await page.getByRole('button', { name: 'Close', exact: true }).click(); + await page.locator('#theme').selectOption('dark'); await page.locator('#smith').hover(); await page.mouse.wheel(0, -100); await page.setViewportSize({ width: 1100, height: 800 }); diff --git a/tests/browser/settings-dialog.spec.ts b/tests/browser/settings-dialog.spec.ts index 3387d9c..0ce8dd7 100644 --- a/tests/browser/settings-dialog.spec.ts +++ b/tests/browser/settings-dialog.spec.ts @@ -1,5 +1,42 @@ import { expect, test } from '@playwright/test'; +test.describe('settings backdrop', () => { + test.use({ hasTouch: true }); + + for (const width of [1280, 360]) { + test(`outside clicks and taps dismiss settings at ${width}px without losing changes`, async ({ + page, + }) => { + await page.setViewportSize({ width, height: 800 }); + await page.goto('./'); + const opener = page.getByRole('button', { name: 'Chart settings', exact: true }); + const dialog = page.getByRole('dialog', { name: 'Chart settings', exact: true }); + await opener.click(); + await dialog.getByLabel('Grid labels', { exact: true }).uncheck(); + const bounds = (await dialog.boundingBox())!; + // Dialog padding is part of the panel, even though the dialog is the event target. + await page.mouse.click(bounds.x + 8, bounds.y + 8); + await expect(dialog).toBeVisible(); + // Ending a drag outside must not act like clicking the backdrop. + await page.mouse.move(bounds.x + 8, bounds.y + 8); + await page.mouse.down(); + await page.mouse.move(4, 4); + await page.mouse.up(); + await expect(dialog).toBeVisible(); + await page.mouse.click(4, 4); + await expect(dialog).not.toBeVisible(); + await expect(opener).toBeFocused(); + await opener.click(); + await expect(dialog.getByLabel('Grid labels', { exact: true })).not.toBeChecked(); + await page.touchscreen.tap(4, 4); + await expect(dialog).not.toBeVisible(); + await expect(opener).toBeFocused(); + await expect(page.locator('header #theme')).toBeVisible(); + expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBe(width); + }); + } +}); + test('chart settings group all preferences, prevent background keyboard interaction, and retain changes on close', async ({ page, }) => { @@ -12,7 +49,6 @@ test('chart settings group all preferences, prevent background keyboard interact await expect(dialog).toBeVisible(); await expect(dialog.getByRole('button', { name: 'Close', exact: true })).toBeFocused(); for (const id of [ - 'theme', 'impedance', 'admittance', 'constantQ', @@ -38,8 +74,7 @@ test('chart settings group all preferences, prevent background keyboard interact } await dialog.getByLabel('Grid labels', { exact: true }).uncheck(); await dialog.getByLabel('Grid detail').selectOption('basic'); - await dialog.getByLabel('Theme', { exact: true }).selectOption('dark'); - await expect(dialog).toHaveCSS('background-color', 'rgb(15, 23, 42)'); + await expect(dialog).toHaveCSS('background-color', 'rgb(25, 28, 32)'); await page.keyboard.press('Escape'); await expect(dialog).not.toBeVisible(); await expect(opener).toBeFocused(); @@ -47,7 +82,7 @@ test('chart settings group all preferences, prevent background keyboard interact await opener.click(); await expect(dialog.getByLabel('Grid labels', { exact: true })).not.toBeChecked(); await expect(dialog.getByLabel('Grid detail')).toHaveValue('basic'); - await expect(dialog.getByLabel('Theme', { exact: true })).toHaveValue('dark'); + await expect(dialog.locator('#theme')).toHaveCount(0); await page.screenshot({ path: 'test-results/settings-dialog-dark.png' }); await dialog.getByRole('button', { name: 'Close', exact: true }).click(); await expect(opener).toBeFocused(); From aea3fd2e817d23017cc8759aaa58c322da0b8422 Mon Sep 17 00:00:00 2001 From: Krzysztof Rosinski Date: Wed, 14 Oct 2026 05:29:54 +0200 Subject: [PATCH 2/3] feat: keep chart interactive beside desktop settings --- demo/main.ts | 78 +++++++++++++++++++++------ demo/style.css | 20 +++++-- docs/guide/configuration.md | 4 ++ index.html | 1 + tests/browser/settings-dialog.spec.ts | 61 ++++++++++++++++++++- 5 files changed, 144 insertions(+), 20 deletions(-) diff --git a/demo/main.ts b/demo/main.ts index ba08e7f..2ed231f 100644 --- a/demo/main.ts +++ b/demo/main.ts @@ -23,26 +23,63 @@ function element(id: string): T { const settingsDialog = element('chart-settings'); const settingsButton = element('open-settings'); +const desktopSettings = window.matchMedia('(min-width: 1000px)'); +const chartPanel = element('smith').closest('.chart-panel')!; +const scalesPanel = element('parameter-panel').closest('.scales-panel')!; +const settingsBackdrop = element('settings-backdrop'); +const settingsBackground = document.querySelectorAll( + 'main > header, main > .toolbar, .scales-panel, .workspace > aside, main > footer', +); + function positionSettingsDialog(): void { - if (!settingsDialog.open || !window.matchMedia('(min-width: 1000px)').matches) { + if (!settingsDialog.open || !desktopSettings.matches) { return; } - const anchor = element('parameter-panel').closest('.scales-panel')!; - const bounds = anchor.getBoundingClientRect(); - // Align with the readout panel while reserving at least 320 px for settings on short screens. - const top = Math.max(16, Math.min(bounds.top, window.innerHeight - 336)); - settingsDialog.style.setProperty('--settings-top', `${top}px`); - settingsDialog.style.setProperty('--settings-left', `${bounds.left}px`); - settingsDialog.style.setProperty('--settings-width', `${bounds.width}px`); + const chartBounds = chartPanel.getBoundingClientRect(); + const scalesBounds = scalesPanel.getBoundingClientRect(); + settingsDialog.style.setProperty('--settings-top', `${chartBounds.top}px`); + settingsDialog.style.setProperty('--settings-left', `${scalesBounds.left}px`); + settingsDialog.style.setProperty('--settings-width', `${scalesBounds.width}px`); + settingsDialog.style.setProperty('--settings-height', `${chartBounds.height}px`); +} + +function setDesktopSettingsActive(active: boolean): void { + document.body.classList.toggle('settings-active', active); + settingsBackdrop.hidden = !active; + for (const region of settingsBackground) { + region.inert = active; + } } -settingsButton.addEventListener('click', () => { - settingsDialog.showModal(); + +function showSettings(): void { + setDesktopSettingsActive(desktopSettings.matches); + if (desktopSettings.matches) { + // A non-modal dialog leaves the chart available for zoom and marker interaction. + settingsDialog.show(); + } else { + settingsDialog.showModal(); + } positionSettingsDialog(); -}); +} + +settingsButton.addEventListener('click', showSettings); window.addEventListener('resize', positionSettingsDialog); window.addEventListener('scroll', positionSettingsDialog, { passive: true }); +const settingsResizeObserver = new ResizeObserver(positionSettingsDialog); +settingsResizeObserver.observe(chartPanel); +settingsResizeObserver.observe(scalesPanel); +desktopSettings.addEventListener('change', () => { + if (settingsDialog.open) { + settingsDialog.close(); + showSettings(); + } +}); + let settingsPressOutside = false; function outsideSettings(event: MouseEvent): boolean { + if (event.target === settingsBackdrop) { + return true; + } const bounds = settingsDialog.getBoundingClientRect(); return ( event.target === settingsDialog && @@ -52,20 +89,31 @@ function outsideSettings(event: MouseEvent): boolean { event.clientY > bounds.bottom) ); } -settingsDialog.addEventListener('pointerdown', (event) => { - settingsPressOutside = event.button === 0 && outsideSettings(event); +document.addEventListener('pointerdown', (event) => { + settingsPressOutside = settingsDialog.open && event.button === 0 && outsideSettings(event); }); -settingsDialog.addEventListener('pointercancel', () => { +document.addEventListener('pointercancel', () => { settingsPressOutside = false; }); -settingsDialog.addEventListener('click', (event) => { +document.addEventListener('click', (event) => { if (settingsPressOutside && outsideSettings(event)) { settingsDialog.close(); } settingsPressOutside = false; }); +document.addEventListener('keydown', (event) => { + if (event.key === 'Escape' && settingsDialog.open && desktopSettings.matches) { + event.preventDefault(); + settingsDialog.close(); + } +}); settingsDialog.addEventListener('close', () => { + // A breakpoint change closes and immediately reopens the dialog in its new mode. + if (settingsDialog.open) { + return; + } settingsPressOutside = false; + setDesktopSettingsActive(false); settingsButton.focus({ preventScroll: true }); }); diff --git a/demo/style.css b/demo/style.css index 0a8cc76..dbe0dd4 100644 --- a/demo/style.css +++ b/demo/style.css @@ -472,15 +472,29 @@ footer { } @media (min-width: 1000px) { #chart-settings { + position: fixed; + z-index: 21; inset: var(--settings-top, 16px) auto auto var(--settings-left, calc(50% + 8px)); margin: 0; width: var(--settings-width, calc(50vw - 32px)); - max-height: calc(100dvh - var(--settings-top, 16px) - 16px); + height: var(--settings-height); + max-height: none; + overflow-y: auto; + overscroll-behavior: contain; } - #chart-settings::backdrop { - background: rgb(0 0 0 / 28%); + .settings-active .chart-panel { + position: relative; + z-index: 21; } } +#settings-backdrop:not([hidden]) { + position: fixed; + inset: 0; + z-index: 20; + background: rgb(10 12 15 / 72%); + backdrop-filter: grayscale(1); + -webkit-backdrop-filter: grayscale(1); +} .settings-heading { display: flex; align-items: center; diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index f296e7e..bae6ac2 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -163,6 +163,10 @@ those individual overrides. Invalid appearance settings throw before making chan SVG exports resolve the appearance without requiring theme CSS. A configured background is included in the exported image; `transparent` preserves transparency. The **Theme** selector in the demo's top-right header updates both library components and the demo UI. +On desktop, **Chart settings** opens beside the chart at the same height. The chart +remains interactive, including zoom and markers, while the rest of the page is +dimmed and inactive. Click the dimmed area, press Escape, or use Close to dismiss +settings. On smaller screens, settings use a modal dialog. ## Grid layers diff --git a/index.html b/index.html index 5334d80..c4a12ae 100644 --- a/index.html +++ b/index.html @@ -182,6 +182,7 @@

Compare markers

Smithkit
+ { } }); -test('chart settings group all preferences, prevent background keyboard interaction, and retain changes on close', async ({ +test('chart settings group all preferences, prevent dimmed controls from receiving keyboard focus, and retain changes on close', async ({ page, }) => { await page.goto('./'); @@ -68,7 +68,10 @@ test('chart settings group all preferences, prevent background keyboard interact // but must never focus an underlying page control. expect( await dialog.evaluate( - (node) => node.contains(document.activeElement) || document.activeElement === document.body, + (node) => + node.contains(document.activeElement) || + document.querySelector('.chart-panel')!.contains(document.activeElement) || + document.activeElement === document.body, ), ).toBe(true); } @@ -109,3 +112,57 @@ test('settings remain usable without horizontal overflow on a small viewport', a await expect(dialog).not.toBeVisible(); await expect(page.locator('#reference-value')).toHaveText('Z₀ = 75 Ω'); }); + +test('desktop settings match the chart height and leave zoom and markers active', async ({ + page, +}) => { + await page.setViewportSize({ width: 1512, height: 982 }); + await page.goto('./'); + await page.getByRole('button', { name: 'Load sample trace' }).click(); + await page.getByRole('button', { name: 'Chart settings', exact: true }).click(); + const dialog = page.getByRole('dialog', { name: 'Chart settings', exact: true }); + const chart = page.locator('.chart-panel'); + const chartBounds = (await chart.boundingBox())!; + const settingsBounds = (await dialog.boundingBox())!; + expect(settingsBounds.y).toBeCloseTo(chartBounds.y, 0); + expect(settingsBounds.height).toBeCloseTo(chartBounds.height, 0); + expect(settingsBounds.x).toBeGreaterThan(chartBounds.x + chartBounds.width); + expect(await dialog.evaluate((node) => node.matches(':modal'))).toBe(false); + await expect(page.locator('header')).toHaveJSProperty('inert', true); + await expect(page.locator('.scales-panel')).toHaveJSProperty('inert', true); + const marker = page.getByRole('slider').first(); + await marker.focus(); + const originalSample = await marker.getAttribute('aria-valuenow'); + await page.keyboard.press('End'); + await expect(marker).not.toHaveAttribute('aria-valuenow', originalSample!); + await expect(dialog).toBeVisible(); + const view = page.locator('#smith svg > g').first(); + const originalTransform = await view.getAttribute('transform'); + await page.mouse.click( + chartBounds.x + chartBounds.width / 2, + chartBounds.y + chartBounds.height / 2, + ); + await page.mouse.wheel(0, -200); + await expect(view).not.toHaveAttribute('transform', originalTransform!); + await expect(dialog).toBeVisible(); + await dialog.getByLabel('Admittance', { exact: true }).check(); + await expect(page.locator('[data-layer=conductance]')).not.toHaveCSS('display', 'none'); + await page.screenshot({ path: 'test-results/settings-active-chart.png' }); + + // Switching layouts must release manual inert state and restore native modality. + await page.setViewportSize({ width: 390, height: 844 }); + await expect.poll(() => dialog.evaluate((node) => node.matches(':modal'))).toBe(true); + await expect(page.locator('header')).toHaveJSProperty('inert', false); + await expect(dialog.getByLabel('Admittance', { exact: true })).toBeChecked(); + await page.setViewportSize({ width: 1280, height: 720 }); + await expect.poll(() => dialog.evaluate((node) => node.matches(':modal'))).toBe(false); + const resizedChart = (await chart.boundingBox())!; + await expect + .poll(async () => (await dialog.boundingBox())!.height) + .toBeCloseTo(resizedChart.height, 0); + await page.keyboard.press('Escape'); + await expect(dialog).not.toBeVisible(); + await expect(page.locator('#settings-backdrop')).toBeHidden(); + await expect(page.locator('header')).toHaveJSProperty('inert', false); + await page.locator('#theme').selectOption('light'); +}); From e0b1f2250b76699c23ac46848a1a697e9751e28f Mon Sep 17 00:00:00 2001 From: Krzysztof Rosinski Date: Wed, 14 Oct 2026 05:32:33 +0200 Subject: [PATCH 3/3] style: align demo panels and unify example appearance --- demo/style.css | 4 +- examples/basic/index.html | 35 ++++++----- examples/basic/style.css | 123 +++++++++++++++++++++++++++++++++----- examples/style.css | 88 ++++++++++++++++++--------- 4 files changed, 193 insertions(+), 57 deletions(-) diff --git a/demo/style.css b/demo/style.css index dbe0dd4..239e840 100644 --- a/demo/style.css +++ b/demo/style.css @@ -395,7 +395,7 @@ footer { } .chart-column { grid-template-columns: repeat(2, minmax(0, 1fr)); - align-items: start; + align-items: stretch; gap: 16px; } #smith { @@ -413,7 +413,7 @@ footer { } aside { grid-template-columns: repeat(2, minmax(0, 1fr)); - align-items: start; + align-items: stretch; } } @media (max-width: 800px) { diff --git a/examples/basic/index.html b/examples/basic/index.html index 8ce5066..5a9c21a 100644 --- a/examples/basic/index.html +++ b/examples/basic/index.html @@ -3,28 +3,35 @@ - Smithkit integration example + Mount, update, and destroy · Smithkit examples
-