From f9ff5d09798ea1416d9efe620dc73198bcb2be5e Mon Sep 17 00:00:00 2001 From: lsdsjy <1356263+lsdsjy@users.noreply.github.com> Date: Thu, 22 Oct 2026 14:27:54 +0800 Subject: [PATCH] =?UTF-8?q?fix(sidebar):=20=E9=9A=90=E8=97=8F=E4=B8=8D?= =?UTF-8?q?=E6=94=B9=E5=8F=98=E6=A0=87=E7=AD=BE=E9=A1=BA=E5=BA=8F=E7=9A=84?= =?UTF-8?q?=E6=8B=96=E6=8B=BD=E5=AE=9A=E4=BD=8D=E6=9D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/ui-dockkit/README.i18n.yaml | 4 +-- packages/client/ui-dockkit/README.md | 2 ++ packages/client/ui-dockkit/README.zh.md | 2 ++ .../ui-dockkit/src/components/DockSurface.tsx | 13 +++++--- .../tests/components.client.spec.tsx | 31 ++++++++++++++++--- 5 files changed, 41 insertions(+), 11 deletions(-) diff --git a/packages/client/ui-dockkit/README.i18n.yaml b/packages/client/ui-dockkit/README.i18n.yaml index 2f06976029..16f40cb2d1 100644 --- a/packages/client/ui-dockkit/README.i18n.yaml +++ b/packages/client/ui-dockkit/README.i18n.yaml @@ -15,8 +15,8 @@ en: 42038fdc402b9c2a zh: 99890f8f908624ce /deepseek-ai-dsh-client-ui-dockkit/the-two-layers: - en: 99356ea8e3d1e7aa - zh: 870f746730d0e6cf + en: 712893d885798a0e + zh: 2bc8e1c388722acd /deepseek-ai-dsh-client-ui-dockkit/embedding-it: en: c4ba08038bfa6f30 zh: 95db30618b52babe diff --git a/packages/client/ui-dockkit/README.md b/packages/client/ui-dockkit/README.md index 0ba54c2c8e..1e6a98c980 100644 --- a/packages/client/ui-dockkit/README.md +++ b/packages/client/ui-dockkit/README.md @@ -41,6 +41,8 @@ A docking layout kit: a split tree of tabbed panes with invertible operations, a Middle-clicking anywhere on a closable tab chip, including its close control, closes it through the same intent as its close button, without selecting it first. The chip owns the middle click and suppresses nested title content’s default action. Closing a background tab preserves the selected tab. Only the primary button starts tab dragging; a middle click while another mouse button is held does not close the tab. +Strip slots that preserve the dragged tab’s position show no insertion caret and report no placement; the tab can still be dragged to split a pane or float. + Tab context menus show the close action name without a shortcut hint: they act on the clicked tab, while shortcuts can target another focused pane. An unmodified Escape dismisses the foreground tab menu without closing the tab and returns focus from a menu item to its tab without drawing a focus outline. Tab and arrow navigation retain the tab’s visible focus indicator. Composition and held-key repeats do not dismiss it. diff --git a/packages/client/ui-dockkit/README.zh.md b/packages/client/ui-dockkit/README.zh.md index 39acc02792..f822814d2b 100644 --- a/packages/client/ui-dockkit/README.zh.md +++ b/packages/client/ui-dockkit/README.zh.md @@ -41,6 +41,8 @@ kind: "package-reference" 中键点击可关闭标签页 chip 的任意位置(包括关闭控件)时,通过与关闭按钮相同的意图关闭,无需先选中。chip 接管中键点击,并阻止嵌套标题内容的默认动作。关闭后台标签页会保留当前选中的标签页。只有主键会开始拖动标签页;其它鼠标按键仍按住时,中键点击不会关闭标签页。 +不改变所拖标签位置的标签条落点不显示插入定位条,也不上报放置操作;标签仍可拖出分栏或浮动。 + 标签上下文菜单只显示关闭操作名,不显示快捷键:菜单操作针对被点击的标签,快捷键则可能作用于其他焦点分栏。无修饰键的 Esc 关闭前台标签菜单而不关闭标签,并将菜单项的焦点交还标签,不绘制焦点外轮廓线。Tab 和方向键导航保留标签页的可见焦点提示。输入法组合输入和长按重复事件不关闭菜单。 diff --git a/packages/client/ui-dockkit/src/components/DockSurface.tsx b/packages/client/ui-dockkit/src/components/DockSurface.tsx index eea2ea1724..170211d6fd 100644 --- a/packages/client/ui-dockkit/src/components/DockSurface.tsx +++ b/packages/client/ui-dockkit/src/components/DockSurface.tsx @@ -110,12 +110,14 @@ const ALWAYS = (): boolean => true /** * Resolve where a pointer sits inside the docked surface. An edge zone is only * offered where the split it would make is allowed: within the pane budget and - * with room for two halves; otherwise the release is not a move at all. + * with room for two halves. Strip slots that preserve the dragged tab’s position + * offer no caret or placement intent. */ function hitTest( root: HTMLElement, x: number, y: number, + tabId: TabId, canSplit: boolean, fits: ReadonlyMap, dropZones: 'edges' | 'horizontal', @@ -126,7 +128,10 @@ function hitTest( const strip = pane.querySelector('[data-dockkit-strip]') if (strip !== null && containsPoint(strip.getBoundingClientRect(), x, y)) { const tabs = [...strip.querySelectorAll('[data-dockkit-tab]')] - return { kind: 'strip', paneId, index: insertionIndex(tabs.map(tab => tab.getBoundingClientRect()), x) } + const index = insertionIndex(tabs.map(tab => tab.getBoundingClientRect()), x) + const sourceIndex = tabs.findIndex(tab => tab.dataset.dockkitTab === tabId) + if (sourceIndex >= 0 && (index === sourceIndex || index === sourceIndex + 1)) return undefined + return { kind: 'strip', paneId, index } } const zone = dropZones === 'horizontal' ? canSplit && fitOf(fits, paneId).row @@ -229,12 +234,12 @@ function Surface({ setPreview({ ...NO_PREVIEW, draggingTabId: tabId, - dropTarget: hitTest(root, moved.clientX, moved.clientY, canSplit, fits, dropZones), + dropTarget: hitTest(root, moved.clientX, moved.clientY, tabId, canSplit, fits, dropZones), }) }, up: (released) => { if (!dragging) return - const target = hitTest(root, released.clientX, released.clientY, canSplit, fits, dropZones) + const target = hitTest(root, released.clientX, released.clientY, tabId, canSplit, fits, dropZones) if (target === undefined) { if (containsPoint(root.getBoundingClientRect(), released.clientX, released.clientY)) return intents.floatTab(tabId, floatRectAt(released.clientX, released.clientY, FLOAT_DEFAULT_SIZE)) diff --git a/packages/client/ui-dockkit/tests/components.client.spec.tsx b/packages/client/ui-dockkit/tests/components.client.spec.tsx index 85640ed768..9a44d625f4 100644 --- a/packages/client/ui-dockkit/tests/components.client.spec.tsx +++ b/packages/client/ui-dockkit/tests/components.client.spec.tsx @@ -999,21 +999,42 @@ describe('tab drags', () => { expect(document.querySelector('[data-dockkit-caret]')).toBeNull() }) - it('previews a caret at the slot under the pointer and reports that slot on release', () => { + it('previews only strip slots that change the tab order', () => { const { intents, first, fileTabId, chip } = twoPanes() // Left of the first chip's midpoint: slot 0. drag(chip(fileTabId), FILE_CHIP, [30, 18], false) expect(document.querySelector('[data-dockkit-caret]')?.getAttribute('data-dockkit-caret')).toBe('0') - // Past both chips: the slot after the last one, drawn at the strip's end. fireEvent.pointerMove(window, { pointerId: 7, clientX: 300, clientY: 18 }) - expect(document.querySelector('[data-dockkit-caret]')?.getAttribute('data-dockkit-caret')).toBe('2') - fireEvent.pointerUp(window, { pointerId: 7, clientX: 300, clientY: 18 }) + expect(document.querySelector('[data-dockkit-caret]')).toBeNull() + fireEvent.pointerMove(window, { pointerId: 7, clientX: 120, clientY: 18 }) + expect(document.querySelector('[data-dockkit-caret]')).toBeNull() + fireEvent.pointerMove(window, { pointerId: 7, clientX: 30, clientY: 18 }) + fireEvent.pointerUp(window, { pointerId: 7, clientX: 30, clientY: 18 }) // The slot counts the dragged chip itself; the planner turns it into a reorder. - expect(intents.placeTab).toHaveBeenCalledWith(fileTabId, first, 2) + expect(intents.placeTab).toHaveBeenCalledWith(fileTabId, first, 0) expect(gestureIntents(intents)).toEqual(['placeTab']) expect(document.querySelector('[data-dockkit-caret]')).toBeNull() }) + it.each([30, 300])('does not offer a strip placement for the lone Start tab at x=%s', (x) => { + const controller = seededController() + controller.setExpanded(true) + const panes = dockPaneIds(controller.getSnapshot().state) + layOut(panes, 520) + const intents = spyIntents() + renderSurface(controller, intents) + const chip = screen.getByRole('tab') + drag(chip, [51, 18], [x, 18], false) + expect(document.querySelector('[data-dockkit-caret]')).toBeNull() + fireEvent.pointerUp(window, { pointerId: 7, clientX: x, clientY: 18 }) + expect(gestureIntents(intents)).toEqual([]) + + drag(chip, [51, 18], [260, 590], false) + expect(document.querySelector('[data-dockkit-drop-active]')?.getAttribute('data-dockkit-dock-zone')).toBe('bottom') + fireEvent.pointerUp(window, { pointerId: 7, clientX: 260, clientY: 590 }) + expect(intents.dropTab).toHaveBeenCalledExactlyOnceWith(chip.getAttribute('data-dockkit-tab'), panes[0], 'bottom') + }) + it('reports a slot in another pane\'s strip as a placement there', () => { const { intents, second, fileTabId, chip } = twoPanes() drag(chip(fileTabId), FILE_CHIP, [420 + 30, 18])