Merge pull request #5760 from deepseek-harness/turtle/image-viewer-windows-e0e1e6a26a6b

fix(client): keep the image lightbox clear of the Windows caption
This commit is contained in:
Turtle authored and GitHub committed 2026-10-08 11:04:04 +08:00
commit d83cdfafd6
6 files changed
+35 -14

No files matched your search

+12 -1
View File
@@ -289,7 +289,18 @@ describe('web e2e: Markdown image rendering', () => {
await page.getByRole('button', { name: `View full image: ${WORKSPACE_ALT}`, exact: true }).click()
const lightbox = page.getByRole('dialog', { name: 'Image preview', exact: true })
await lightbox.waitFor()
await lightbox.getByRole('button', { name: 'Close image preview' }).click()
const lightboxMask = lightbox.locator(':scope > div > [aria-hidden="true"]')
const lightboxClose = lightbox.getByRole('button', { name: 'Close image preview' })
for (const windows of [true, false]) {
await page.evaluate((value) => {
document.documentElement.toggleAttribute('data-windows-titlebar', value)
document.documentElement.style.setProperty('--dsh-windows-titlebar-height', value ? '40px' : '')
}, windows)
// The Windows caption stays unpainted and the close control sits below it.
expect((await lightboxMask.boundingBox())?.y).toBe(windows ? 40 : 0)
expect((await lightboxClose.boundingBox())?.y).toBe(windows ? 60 : 20)
}
await lightboxClose.click()
await lightbox.waitFor({ state: 'detached' })
const link = page.getByRole('button', { name: 'View comparison', exact: true })
await link.hover()
@@ -24,8 +24,8 @@
en: f0e54915b896af5a
zh: 2047520fd321be90
/deepseek-ai-dsh-client-ui-primitives/use-this-package/rendering-agent-output:
en: d79e20306593c4bf
zh: 5c0f1100e2cd6fcc
en: 4a1a61bb2d64fe4c
zh: 1b0097fff440c2ef
/deepseek-ai-dsh-client-ui-primitives/use-this-package/localizing-copy:
en: 4aef65dea11de083
zh: abc22cd7c65cdf38
+1 -1
View File
@@ -106,7 +106,7 @@ The nearest `MarkdownDelegateProvider` supplies optional `openExternalLink` and
`JsonTree` clamps collapsed strings to `collapsedStringLines` (three by default). Expanded strings show raw text, retain sibling commas, and fit within the window and outer scrolling containers. Resize and ancestor-scroll events update that limit. Row copy feedback updates independently of JSON value rendering; pending clipboard writes cannot update a different row or an unmounted tree.
`ImageLightbox` is the shared original-image modal with focus restoration and Escape dismissal. The internal thumbnail renderer receives loading/failure labels from its owner. `HoverCard.inline` keeps a file link in the text flow and uses the shared menu material, keyboard-visible focus, and placement above or below the anchor without covering it. Escape closes an open thumbnail even when focus is elsewhere; subsequent Escape presses reach the owner. `MarkdownDelegateProvider.fileImages` supplies a decoded-path resolver and complete image labels: settled image links gain hover previews, and standalone images gain lightbox activation. Image-only anchors retain their single navigation target. The parser recovers only complete, unescaped, standalone local image references with bare spaces and an unambiguous supported image suffix; code and ambiguous destinations remain literal.
`ImageLightbox` is the shared original-image modal with focus restoration and Escape dismissal. Like `Modal`, its mask leaves the Windows caption unpainted, and its close control sits 20px below the caption. The internal thumbnail renderer receives loading/failure labels from its owner. `HoverCard.inline` keeps a file link in the text flow and uses the shared menu material, keyboard-visible focus, and placement above or below the anchor without covering it. Escape closes an open thumbnail even when focus is elsewhere; subsequent Escape presses reach the owner. `MarkdownDelegateProvider.fileImages` supplies a decoded-path resolver and complete image labels: settled image links gain hover previews, and standalone images gain lightbox activation. Image-only anchors retain their single navigation target. The parser recovers only complete, unescaped, standalone local image references with bare spaces and an unambiguous supported image suffix; code and ambiguous destinations remain literal.
### Localizing copy
+1 -1
View File
@@ -106,7 +106,7 @@ kind: "package-library"
`JsonTree` 把折叠字符串限制为 `collapsedStringLines` 行(默认三行)。展开后显示原始文本、保留同级逗号,并限制在窗口与外层滚动容器内;尺寸变化和祖先滚动事件会更新此限制。行复制反馈独立于 JSON 值渲染更新;尚未完成的剪贴板写入不会更新另一行或已卸载的树。
`ImageLightbox` 是共享原图浮层,支持焦点恢复与 Esc 关闭。包内缩略图渲染器由调用方提供加载及失败文案。`HoverCard.inline` 使文件链接保持在文本流内,并使用共享菜单材质、键盘可见焦点,并在锚点上方或下方定位而不遮挡锚点。即使焦点位于其他位置,Esc 也会关闭已打开的缩略图;后续 Esc 按键继续传给 owner。`MarkdownDelegateProvider.fileImages` 提供已解码路径解析器与完整图片文案:消息落定后的图片链接支持悬停预览,独立图片支持点击放大。仅包含图片的链接保留单一导航目标。解析器仅恢复完整、未转义、独立成段且带明确图片扩展名的含裸空格本地图片引用;代码与有歧义的目标保持原文。
`ImageLightbox` 是共享原图浮层,支持焦点恢复与 Esc 关闭。与 `Modal` 相同,其遮罩不覆盖 Windows 顶栏,关闭按钮位于顶栏下方 20px。包内缩略图渲染器由调用方提供加载及失败文案。`HoverCard.inline` 使文件链接保持在文本流内,并使用共享菜单材质、键盘可见焦点,并在锚点上方或下方定位而不遮挡锚点。即使焦点位于其他位置,Esc 也会关闭已打开的缩略图;后续 Esc 按键继续传给 owner。`MarkdownDelegateProvider.fileImages` 提供已解码路径解析器与完整图片文案:消息落定后的图片链接支持悬停预览,独立图片支持点击放大。仅包含图片的链接保留单一导航目标。解析器仅恢复完整、未转义、独立成段且带明确图片扩展名的含裸空格本地图片引用;代码与有歧义的目标保持原文。
### 本地化文案
@@ -1,9 +1,17 @@
/* Full-viewport layer that blocks background clicks, including on the Windows caption. */
.backdrop {
position: fixed;
inset: 0;
z-index: 1000;
display: grid;
place-items: center;
}
/* Painted area below the Windows caption; like Modal, the caption stays unpainted. */
.stage {
position: absolute;
inset: var(--dsh-frame-chrome-top, 0px) 0 0;
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
}
@@ -18,7 +26,7 @@
.image {
position: relative;
max-width: min(100%, 1600px);
max-height: calc(100vh - 80px);
max-height: 100%;
object-fit: contain;
border-radius: var(--dsw-radius-lg);
background: var(--dsw-specific-input-major);
@@ -27,7 +35,7 @@
}
.close {
position: fixed;
position: absolute;
top: 20px;
right: 20px;
z-index: 1;
@@ -54,11 +54,13 @@ export function ImageLightbox({ src, alt, labels, onClose }: {
aria-modal="true"
aria-label={labels.dialog}
>
<div className={css.mask} aria-hidden="true" onMouseDown={onClose} />
<img className={css.image} src={src} alt={alt} />
<button ref={closeRef} type="button" className={css.close} aria-label={labels.close} onClick={onClose}>
<IconCloseOutlineRegular size={16} />
</button>
<div className={css.stage}>
<div className={css.mask} aria-hidden="true" onMouseDown={onClose} />
<img className={css.image} src={src} alt={alt} />
<button ref={closeRef} type="button" className={css.close} aria-label={labels.close} onClick={onClose}>
<IconCloseOutlineRegular size={16} />
</button>
</div>
</div>,
document.body,
)