fix sizing issues

This commit is contained in:
Boof2015
2026-03-27 23:54:56 -04:00
parent 85c0cecfd2
commit f07d22cb33
3 changed files with 213 additions and 179 deletions
+112 -89
View File
@@ -13,7 +13,6 @@ import type { ProfileMenuRequest } from '../types/profileMenu'
import { SCOPE_KINDS, type ScopeKind } from '../types/scope' import { SCOPE_KINDS, type ScopeKind } from '../types/scope'
let mainWindow: BrowserWindow | null = null let mainWindow: BrowserWindow | null = null
let currentSettingsHeight = 0
let moveInterval: ReturnType<typeof setInterval> | null = null let moveInterval: ReturnType<typeof setInterval> | null = null
let moveStartCursor: { x: number; y: number } | null = null let moveStartCursor: { x: number; y: number } | null = null
let moveStartPosition: number[] | null = null let moveStartPosition: number[] | null = null
@@ -21,6 +20,8 @@ let moveStartPosition: number[] | null = null
const scopePopoutWindows = new Map<ScopeKind, BrowserWindow>() const scopePopoutWindows = new Map<ScopeKind, BrowserWindow>()
const scopePopoutCloseAllowed = new Set<ScopeKind>() const scopePopoutCloseAllowed = new Set<ScopeKind>()
const popoutBoundsTimers = new Map<ScopeKind, ReturnType<typeof setTimeout>>() const popoutBoundsTimers = new Map<ScopeKind, ReturnType<typeof setTimeout>>()
const windowSettingsHeights = new Map<number, number>()
const windowSettingsBottomAnchors = new Map<number, number>()
const WINDOW_DEFAULTS = { const WINDOW_DEFAULTS = {
width: 900, width: 900,
@@ -48,6 +49,99 @@ function isMainRendererWindow(window: BrowserWindow | null): boolean {
return window !== null && window === mainWindow return window !== null && window === mainWindow
} }
function getBaseMinHeight(window: BrowserWindow): number {
return isMainRendererWindow(window) ? WINDOW_DEFAULTS.minHeight : POPOUT_DEFAULTS.minHeight
}
function getSettingsHeight(window: BrowserWindow | null): number {
if (!window) return 0
return windowSettingsHeights.get(window.id) ?? 0
}
function setSettingsHeightForWindow(window: BrowserWindow, height: number): void {
const nextHeight = Math.max(0, Math.round(height))
if (nextHeight === 0) {
windowSettingsHeights.delete(window.id)
windowSettingsBottomAnchors.delete(window.id)
return
}
windowSettingsHeights.set(window.id, nextHeight)
}
function toLogicalBounds(window: BrowserWindow, bounds = window.getBounds()): WindowBounds {
return {
...bounds,
height: Math.max(getBaseMinHeight(window), bounds.height - getSettingsHeight(window)),
}
}
function applyLogicalBounds(window: BrowserWindow, bounds: WindowBounds): void {
window.setBounds({
...bounds,
height: bounds.height + getSettingsHeight(window),
})
}
function setWindowHeight(window: BrowserWindow, bounds: WindowBounds, height: number, y = bounds.y): void {
window.setBounds({
x: bounds.x,
y,
width: bounds.width,
height,
})
}
function applySettingsHeight(window: BrowserWindow, rawNextHeight: number): void {
const currentSettingsHeight = getSettingsHeight(window)
const nextSettingsHeight = Math.max(0, Math.round(rawNextHeight))
const delta = nextSettingsHeight - currentSettingsHeight
const baseMinHeight = getBaseMinHeight(window)
const [minW] = window.getMinimumSize()
window.setMinimumSize(minW, baseMinHeight + nextSettingsHeight)
if (delta !== 0) {
const bounds = window.getBounds()
const newHeight = Math.max(baseMinHeight + nextSettingsHeight, bounds.height + delta)
const display = screen.getDisplayMatching(bounds)
const workArea = display.workArea
const workAreaBottom = workArea.y + workArea.height
const bottomEdge = bounds.y + newHeight
if (delta > 0 && bottomEdge > workAreaBottom) {
if (!windowSettingsBottomAnchors.has(window.id)) {
windowSettingsBottomAnchors.set(window.id, bounds.y + bounds.height)
}
const newY = Math.max(workArea.y, workAreaBottom - newHeight)
setWindowHeight(window, bounds, newHeight, newY)
} else if (delta < 0) {
const anchoredBottom = windowSettingsBottomAnchors.get(window.id)
if (anchoredBottom !== undefined) {
const targetY = Math.max(workArea.y, Math.min(anchoredBottom - newHeight, workAreaBottom - newHeight))
setWindowHeight(window, bounds, newHeight, targetY)
} else {
const baseHeight = newHeight - nextSettingsHeight
const naturalBottom = bounds.y + baseHeight
if (naturalBottom < workAreaBottom) {
const maxY = workAreaBottom - newHeight
if (bounds.y < maxY) {
setWindowHeight(window, bounds, newHeight)
} else {
setWindowHeight(window, bounds, newHeight, maxY)
}
} else {
setWindowHeight(window, bounds, newHeight)
}
}
} else {
setWindowHeight(window, bounds, newHeight)
}
}
setSettingsHeightForWindow(window, nextSettingsHeight)
}
function sendToRenderer(sender: WebContents, channel: string, ...args: unknown[]): void { function sendToRenderer(sender: WebContents, channel: string, ...args: unknown[]): void {
if (!sender.isDestroyed()) { if (!sender.isDestroyed()) {
sender.send(channel, ...args) sender.send(channel, ...args)
@@ -189,8 +283,11 @@ function createMainWindow(): void {
}) })
mainWindow.on('closed', () => { mainWindow.on('closed', () => {
if (mainWindow) {
windowSettingsHeights.delete(mainWindow.id)
windowSettingsBottomAnchors.delete(mainWindow.id)
}
mainWindow = null mainWindow = null
currentSettingsHeight = 0
for (const kind of SCOPE_KINDS) { for (const kind of SCOPE_KINDS) {
destroyScopePopoutWindow(kind) destroyScopePopoutWindow(kind)
@@ -219,7 +316,7 @@ function emitPopoutBoundsChanged(kind: ScopeKind, window: BrowserWindow): void {
popoutBoundsTimers.delete(kind) popoutBoundsTimers.delete(kind)
if (!mainWindow || mainWindow.isDestroyed() || window.isDestroyed()) return if (!mainWindow || mainWindow.isDestroyed() || window.isDestroyed()) return
const bounds = window.getBounds() const bounds = toLogicalBounds(window)
mainWindow.webContents.send('scope-popout:bounds-changed', kind, bounds) mainWindow.webContents.send('scope-popout:bounds-changed', kind, bounds)
}, 80) }, 80)
@@ -293,6 +390,7 @@ function createScopePopoutWindow(kind: ScopeKind, rawBounds?: WindowBounds): Bro
} }
const popoutWindow = new BrowserWindow(options) const popoutWindow = new BrowserWindow(options)
setSettingsHeightForWindow(popoutWindow, 0)
scopePopoutWindows.set(kind, popoutWindow) scopePopoutWindows.set(kind, popoutWindow)
popoutWindow.once('ready-to-show', () => { popoutWindow.once('ready-to-show', () => {
@@ -311,6 +409,8 @@ function createScopePopoutWindow(kind: ScopeKind, rawBounds?: WindowBounds): Bro
}) })
popoutWindow.on('closed', () => { popoutWindow.on('closed', () => {
windowSettingsHeights.delete(popoutWindow.id)
windowSettingsBottomAnchors.delete(popoutWindow.id)
scopePopoutWindows.delete(kind) scopePopoutWindows.delete(kind)
scopePopoutCloseAllowed.delete(kind) scopePopoutCloseAllowed.delete(kind)
@@ -348,7 +448,7 @@ function syncScopePopouts(nextState: ScopePopoutSyncStateMap): void {
|| currentBounds.height !== nextBounds.height || currentBounds.height !== nextBounds.height
if (hasBoundsDelta) { if (hasBoundsDelta) {
popoutWindow.setBounds(nextBounds) applyLogicalBounds(popoutWindow, nextBounds)
} }
} }
} }
@@ -486,30 +586,14 @@ function setupIPC(): void {
const targetWindow = getWindowFromSender(event.sender) const targetWindow = getWindowFromSender(event.sender)
if (!targetWindow) return if (!targetWindow) return
if (isMainRendererWindow(targetWindow)) { applyLogicalBounds(targetWindow, bounds)
targetWindow.setBounds({
...bounds,
height: bounds.height + currentSettingsHeight,
})
return
}
targetWindow.setBounds(bounds)
}) })
ipcMain.handle('window:get-bounds', (event) => { ipcMain.handle('window:get-bounds', (event) => {
const targetWindow = getWindowFromSender(event.sender) const targetWindow = getWindowFromSender(event.sender)
if (!targetWindow) return null if (!targetWindow) return null
const bounds = targetWindow.getBounds() return toLogicalBounds(targetWindow)
if (!isMainRendererWindow(targetWindow)) {
return bounds
}
return {
...bounds,
height: bounds.height - currentSettingsHeight,
}
}) })
ipcMain.on('window:reposition', (event, position: 'top' | 'bottom') => { ipcMain.on('window:reposition', (event, position: 'top' | 'bottom') => {
@@ -530,84 +614,23 @@ function setupIPC(): void {
ipcMain.on('window:expand-settings', (event, panelHeight: number) => { ipcMain.on('window:expand-settings', (event, panelHeight: number) => {
const targetWindow = getWindowFromSender(event.sender) const targetWindow = getWindowFromSender(event.sender)
if (!targetWindow || !isMainRendererWindow(targetWindow)) return if (!targetWindow) return
const bounds = targetWindow.getBounds() applySettingsHeight(targetWindow, getSettingsHeight(targetWindow) + panelHeight)
const [minW] = targetWindow.getMinimumSize()
const newHeight = bounds.height + panelHeight
targetWindow.setMinimumSize(minW, WINDOW_DEFAULTS.minHeight + panelHeight)
const display = screen.getDisplayMatching(bounds)
const workArea = display.workArea
const bottomEdge = bounds.y + newHeight
if (bottomEdge > workArea.y + workArea.height) {
const newY = Math.max(workArea.y, workArea.y + workArea.height - newHeight)
targetWindow.setBounds({ x: bounds.x, y: newY, width: bounds.width, height: newHeight })
} else {
targetWindow.setSize(bounds.width, newHeight, true)
}
currentSettingsHeight = Math.max(0, currentSettingsHeight + Math.round(panelHeight))
}) })
ipcMain.on('window:collapse-settings', (event, panelHeight: number) => { ipcMain.on('window:collapse-settings', (event, panelHeight: number) => {
const targetWindow = getWindowFromSender(event.sender) const targetWindow = getWindowFromSender(event.sender)
if (!targetWindow || !isMainRendererWindow(targetWindow)) return if (!targetWindow) return
const bounds = targetWindow.getBounds() applySettingsHeight(targetWindow, getSettingsHeight(targetWindow) - panelHeight)
const [minW] = targetWindow.getMinimumSize()
const newHeight = Math.max(WINDOW_DEFAULTS.minHeight, bounds.height - panelHeight)
targetWindow.setMinimumSize(minW, WINDOW_DEFAULTS.minHeight)
const display = screen.getDisplayMatching(bounds)
const workArea = display.workArea
const wasAtBottom = bounds.y + bounds.height >= workArea.y + workArea.height - 10
if (wasAtBottom) {
const newY = Math.min(bounds.y + panelHeight, workArea.y + workArea.height - newHeight)
targetWindow.setBounds({ x: bounds.x, y: newY, width: bounds.width, height: newHeight })
} else {
targetWindow.setSize(bounds.width, newHeight, true)
}
currentSettingsHeight = Math.max(0, currentSettingsHeight - Math.round(panelHeight))
}) })
ipcMain.on('window:set-settings-height', (event, panelHeight: number) => { ipcMain.on('window:set-settings-height', (event, panelHeight: number) => {
const targetWindow = getWindowFromSender(event.sender) const targetWindow = getWindowFromSender(event.sender)
if (!targetWindow || !isMainRendererWindow(targetWindow)) return if (!targetWindow) return
const nextHeight = Math.max(0, Math.round(panelHeight)) applySettingsHeight(targetWindow, panelHeight)
const delta = nextHeight - currentSettingsHeight
const [width, height] = targetWindow.getSize()
const [minW] = targetWindow.getMinimumSize()
targetWindow.setMinimumSize(minW, WINDOW_DEFAULTS.minHeight + nextHeight)
if (delta !== 0) {
const newHeight = Math.max(WINDOW_DEFAULTS.minHeight, height + delta)
const bounds = targetWindow.getBounds()
const display = screen.getDisplayMatching(bounds)
const workArea = display.workArea
const bottomEdge = bounds.y + newHeight
if (delta > 0 && bottomEdge > workArea.y + workArea.height) {
const newY = Math.max(workArea.y, workArea.y + workArea.height - newHeight)
targetWindow.setBounds({ x: bounds.x, y: newY, width, height: newHeight })
} else if (delta < 0) {
const baseHeight = newHeight - nextHeight
const naturalBottom = bounds.y + baseHeight
if (naturalBottom < workArea.y + workArea.height) {
const maxY = workArea.y + workArea.height - newHeight
if (bounds.y < maxY) {
targetWindow.setSize(width, newHeight, true)
} else {
targetWindow.setBounds({ x: bounds.x, y: maxY, width, height: newHeight })
}
} else {
targetWindow.setSize(width, newHeight, true)
}
} else {
targetWindow.setSize(width, newHeight, true)
}
}
currentSettingsHeight = nextHeight
}) })
ipcMain.on('scope-popout:sync', (event, state: ScopePopoutSyncStateMap) => { ipcMain.on('scope-popout:sync', (event, state: ScopePopoutSyncStateMap) => {
+83 -80
View File
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useRef, useState, type JSX, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent } from 'react' import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type JSX, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent } from 'react'
import type { ScopePopoutSnapshot } from '../../types/popout' import type { ScopePopoutSnapshot } from '../../types/popout'
import { SCOPE_LABELS, type ScopeKind } from '../../types/scope' import { SCOPE_LABELS, type ScopeKind } from '../../types/scope'
import { DEFAULT_SCOPE_SETTINGS, type ScopeSettings } from '../../types/settings' import { DEFAULT_SCOPE_SETTINGS, type ScopeSettings } from '../../types/settings'
@@ -41,11 +41,12 @@ interface ScopePopoutWindowProps {
scopeKind: ScopeKind scopeKind: ScopeKind
} }
const POPOUT_SETTINGS_EXPAND_HEIGHT = 260
export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps): JSX.Element { export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps): JSX.Element {
const [snapshot, setSnapshot] = useState<ScopePopoutSnapshot<ScopeKind> | null>(null) const [snapshot, setSnapshot] = useState<ScopePopoutSnapshot<ScopeKind> | null>(null)
const [miniSettingsOpen, setMiniSettingsOpen] = useState(false) const [miniSettingsOpen, setMiniSettingsOpen] = useState(false)
const [chromeHeight, setChromeHeight] = useState(0) const prevMiniSettingsOpenRef = useRef(false)
const chromeRef = useRef<HTMLDivElement>(null)
const dataSource = useMemo(() => new ScopePopoutDataSource(scopeKind), [scopeKind]) const dataSource = useMemo(() => new ScopePopoutDataSource(scopeKind), [scopeKind])
useEffect(() => { useEffect(() => {
@@ -74,6 +75,7 @@ export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps)
const effectiveAccent = snapshot?.accent ?? '#38bdf8' const effectiveAccent = snapshot?.accent ?? '#38bdf8'
const effectiveSettings = (snapshot?.settings ?? DEFAULT_SCOPE_SETTINGS[scopeKind]) as ScopeSettings[ScopeKind] const effectiveSettings = (snapshot?.settings ?? DEFAULT_SCOPE_SETTINGS[scopeKind]) as ScopeSettings[ScopeKind]
const settingsHeight = miniSettingsOpen ? POPOUT_SETTINGS_EXPAND_HEIGHT : 0
const handleUpdateScopeSettings = <K extends ScopeKind>(kind: K, partial: Partial<ScopeSettings[K]>): void => { const handleUpdateScopeSettings = <K extends ScopeKind>(kind: K, partial: Partial<ScopeSettings[K]>): void => {
if (kind !== scopeKind) return if (kind !== scopeKind) return
@@ -121,26 +123,21 @@ export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps)
window.electronAPI.stopWindowMove() window.electronAPI.stopWindowMove()
}, []) }, [])
useEffect(() => { useLayoutEffect(() => {
const chrome = chromeRef.current if (miniSettingsOpen && !prevMiniSettingsOpenRef.current) {
if (!chrome) return window.electronAPI.expandSettings(POPOUT_SETTINGS_EXPAND_HEIGHT)
} else if (!miniSettingsOpen && prevMiniSettingsOpenRef.current) {
const updateHeight = (): void => { window.electronAPI.collapseSettings(POPOUT_SETTINGS_EXPAND_HEIGHT)
setChromeHeight(chrome.scrollHeight)
} }
updateHeight() prevMiniSettingsOpenRef.current = miniSettingsOpen
}, [miniSettingsOpen])
const observer = typeof ResizeObserver === 'undefined'
? null
: new ResizeObserver(() => updateHeight())
observer?.observe(chrome)
return () => observer?.disconnect()
}, [miniSettingsOpen, snapshot])
useEffect(() => { useEffect(() => {
return () => { return () => {
if (prevMiniSettingsOpenRef.current) {
window.electronAPI.setSettingsHeight(0)
}
window.electronAPI.stopWindowMove() window.electronAPI.stopWindowMove()
} }
}, []) }, [])
@@ -152,57 +149,77 @@ export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps)
onMouseUp={handleAltDragEnd} onMouseUp={handleAltDragEnd}
> >
<div <div
ref={chromeRef} className="scope-popout__viewport"
className={[ style={{ height: `calc(100vh - ${settingsHeight}px)` }}
'scope-popout__chrome',
miniSettingsOpen ? 'is-expanded' : '',
].join(' ').trim()}
> >
<header className="scope-popout__header"> <div
<div className="scope-popout__drag"> className={[
<button 'scope-popout__chrome',
type="button" miniSettingsOpen ? 'is-expanded' : '',
className="scope-popout__drag-handle" ].join(' ').trim()}
onPointerDown={handleDragStart} >
onPointerUp={handleDragEnd} <header className="scope-popout__header">
onPointerCancel={handleDragEnd} <div className="scope-popout__drag">
onLostPointerCapture={handleDragEnd} <button
aria-label="Drag window" type="button"
title="Drag window" className="scope-popout__drag-handle"
> onPointerDown={handleDragStart}
<span className="scope-popout__drag-icon" aria-hidden="true"> onPointerUp={handleDragEnd}
<GripIcon /> onPointerCancel={handleDragEnd}
</span> onLostPointerCapture={handleDragEnd}
</button> aria-label="Drag window"
<div className="scope-popout__title-group"> title="Drag window"
<span className="scope-popout__title">{snapshot?.label ?? SCOPE_LABELS[scopeKind]}</span> >
<span className="scope-popout__subtitle">Detached Scope</span> <span className="scope-popout__drag-icon" aria-hidden="true">
<GripIcon />
</span>
</button>
<div className="scope-popout__title-group">
<span className="scope-popout__title">{snapshot?.label ?? SCOPE_LABELS[scopeKind]}</span>
<span className="scope-popout__subtitle">Detached Scope</span>
</div>
</div> </div>
</div>
<div className="scope-popout__actions"> <div className="scope-popout__actions">
<button <button
type="button" type="button"
className={`scope-popout__button ${miniSettingsOpen ? 'is-active' : ''}`.trim()} className={`scope-popout__button ${miniSettingsOpen ? 'is-active' : ''}`.trim()}
onClick={() => setMiniSettingsOpen((prev) => !prev)} onClick={() => setMiniSettingsOpen((prev) => !prev)}
aria-label="Toggle mini settings" aria-label="Toggle mini settings"
title="Mini settings" title="Mini settings"
> >
<SettingsIcon /> <SettingsIcon />
</button> </button>
<button <button
type="button" type="button"
className="scope-popout__button" className="scope-popout__button"
onClick={() => window.electronAPI.requestScopePopIn(scopeKind)} onClick={() => window.electronAPI.requestScopePopIn(scopeKind)}
aria-label={`Pop in ${SCOPE_LABELS[scopeKind]}`} aria-label={`Pop in ${SCOPE_LABELS[scopeKind]}`}
title={`Pop in ${SCOPE_LABELS[scopeKind]}`} title={`Pop in ${SCOPE_LABELS[scopeKind]}`}
> >
<PopInIcon /> <PopInIcon />
</button> </button>
</div> </div>
</header> </header>
</div>
{miniSettingsOpen && ( <div className="scope-popout__content">
<div className="scope-popout__canvas-region">
<ScopeModule
scopeKind={scopeKind}
lineColor={effectiveAccent}
settings={effectiveSettings}
dataSource={dataSource}
/>
</div>
</div>
</div>
{miniSettingsOpen && (
<div
className="scope-popout__settings-region"
style={{ height: `${settingsHeight}px` }}
>
<div className="scope-popout__settings-panel"> <div className="scope-popout__settings-panel">
<ScopeSettingsSection <ScopeSettingsSection
kind={scopeKind} kind={scopeKind}
@@ -210,22 +227,8 @@ export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps)
onUpdate={handleUpdateScopeSettings} onUpdate={handleUpdateScopeSettings}
/> />
</div> </div>
)}
</div>
<div
className="scope-popout__content"
style={miniSettingsOpen ? { paddingTop: `${chromeHeight}px` } : undefined}
>
<div className="scope-popout__canvas-region">
<ScopeModule
scopeKind={scopeKind}
lineColor={effectiveAccent}
settings={effectiveSettings}
dataSource={dataSource}
/>
</div> </div>
</div> )}
</div> </div>
) )
} }
+18 -10
View File
@@ -817,6 +817,13 @@ select {
color: var(--text-primary); color: var(--text-primary);
} }
.scope-popout__viewport {
position: relative;
flex: 0 0 auto;
min-height: 0;
overflow: hidden;
}
.scope-popout__chrome { .scope-popout__chrome {
position: absolute; position: absolute;
top: 0; top: 0;
@@ -831,7 +838,7 @@ select {
transition: max-height 160ms ease, opacity 120ms ease, transform 160ms ease; transition: max-height 160ms ease, opacity 120ms ease, transform 160ms ease;
} }
.scope-popout:hover .scope-popout__chrome, .scope-popout__viewport:hover .scope-popout__chrome,
.scope-popout__chrome:hover, .scope-popout__chrome:hover,
.scope-popout__chrome.is-expanded { .scope-popout__chrome.is-expanded {
max-height: 58px; max-height: 58px;
@@ -840,10 +847,6 @@ select {
transform: translateY(0); transform: translateY(0);
} }
.scope-popout__chrome.is-expanded {
max-height: 420px;
}
.scope-popout__header { .scope-popout__header {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -951,12 +954,12 @@ select {
} }
.scope-popout__settings-panel { .scope-popout__settings-panel {
flex-shrink: 0; height: 100%;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
background: linear-gradient(180deg, rgba(6, 8, 11, 0.98), rgba(4, 5, 7, 0.98)); background: linear-gradient(180deg, rgba(6, 8, 11, 0.98), rgba(4, 5, 7, 0.98));
border-bottom: 1px solid rgba(255, 255, 255, 0.06); border-top: 1px solid rgba(255, 255, 255, 0.06);
padding: 12px 0 8px; padding: 12px 0 10px;
} }
.scope-popout__settings-panel .settings-scope-section { .scope-popout__settings-panel .settings-scope-section {
@@ -964,12 +967,17 @@ select {
padding: 0 14px 12px; padding: 0 14px 12px;
} }
.scope-popout__settings-region {
flex: 0 0 auto;
min-height: 0;
overflow: hidden;
}
.scope-popout__content { .scope-popout__content {
flex: 1; height: 100%;
min-height: 0; min-height: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
transition: padding-top 160ms ease;
} }
.scope-popout__canvas-region { .scope-popout__canvas-region {