Merge branch 'master' into fix/agent-preset-ui-flow

This commit is contained in:
imccyu
2026-08-11 10:54:13 +08:00
committed by GitHub
1947 changed files with 24165 additions and 14839 deletions

View File

@@ -1,8 +1,15 @@
{
"name": "@deepseek-ai/dsh-client-ui-primitives",
"description": "Pure React atoms for the dsh web UI: controls, icons, markdown, and JSON inspectors (zero cordis)",
"version": "0.0.1",
"private": true,
"version": "0.0.1-rc.1",
"publishConfig": {
"access": "restricted"
},
"repository": {
"type": "git",
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
"directory": "packages/client/ui-primitives"
},
"type": "module",
"main": "lib/index.js",
"types": "lib/types/index.d.ts",
@@ -45,7 +52,7 @@
"@deepseek-ai/dsh-invariants": "workspace:^",
"@types/react": "~18.3.1",
"@types/react-dom": "~18.3.0",
"cordis": "^4.0.0-rc.7"
"@deepseek-ai/cordis": "workspace:^"
},
"files": [
"lib/index.js",
@@ -53,7 +60,7 @@
"lib/types/**/*.d.ts"
],
"peerDependencies": {
"@deepseek-ai/dsh-invariants": "^0.0.1",
"cordis": "^4.0.0-rc.7"
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/cordis": "workspace:^"
}
}

View File

@@ -24,9 +24,11 @@ interface AnchorProps {
onBlur?: FocusEventHandler | undefined
}
type TooltipLabel = string | (() => string)
/**
* Attach a hover/focus tooltip to an anchor element.
* @param props.label - bubble text.
* @param props.label - bubble text, or a resolver evaluated only while the bubble is visible.
* @param props.side - placement relative to the anchor (default 'right').
* @param props.delayMs - hover delay in milliseconds; keyboard focus remains immediate.
* @param props.disabled - suppress the bubble while true; the anchor renders identically so
@@ -34,7 +36,7 @@ interface AnchorProps {
* @param props.children - a single anchor element; its own ref (callback or object) is forwarded alongside the tooltip's.
* @returns the cloned anchor plus a fixed-position bubble while hovered/focused.
*/
export function Tooltip({ label, side = 'right', delayMs = 0, disabled = false, children }: { label: string; side?: TooltipSide; delayMs?: number; disabled?: boolean; children: ReactElement<AnchorProps> }) {
export function Tooltip({ label, side = 'right', delayMs = 0, disabled = false, children }: { label: TooltipLabel; side?: TooltipSide; delayMs?: number; disabled?: boolean; children: ReactElement<AnchorProps> }) {
const anchor = useRef<HTMLElement | null>(null)
// React 18 keeps the element's ref outside props; forward it so wrapping an
// anchor in Tooltip never silently severs the owner's ref.
@@ -46,6 +48,9 @@ export function Tooltip({ label, side = 'right', delayMs = 0, disabled = false,
}, [childRef])
const [pos, setPos] = useState<{ x: number; y: number } | null>(null)
const bubble = useRef<HTMLSpanElement | null>(null)
const resolvedLabel = pos === null
? null
: typeof label === 'function' ? label() : label
// Horizontal viewport clamp: fixed positioning knows nothing about edges, so
// a centered bubble near the right edge would clip. Each measurement resets
// the base position before applying a direct style offset, allowing a shorter
@@ -67,7 +72,7 @@ export function Tooltip({ label, side = 'right', delayMs = 0, disabled = false,
clamp()
window.addEventListener('resize', clamp)
return () => { window.removeEventListener('resize', clamp) }
}, [label, pos])
}, [pos, resolvedLabel])
const showTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
// Hover and focus are independent triggers: the bubble hides only after
// BOTH clear (hovering away from a focused anchor must not drop it).
@@ -128,7 +133,7 @@ export function Tooltip({ label, side = 'right', delayMs = 0, disabled = false,
})}
{pos !== null && (
<span ref={bubble} className={css.bubble} data-side={side} style={{ left: pos.x, top: pos.y }} role="tooltip">
{label}
{resolvedLabel}
</span>
)}
</>

View File

@@ -4,7 +4,7 @@
*/
/* jscpd:ignore-start */
import type { Context } from 'cordis'
import type { Context } from '@deepseek-ai/cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
const PACKAGE_NAME = '@deepseek-ai/dsh-client-ui-primitives'

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import { Context } from '@deepseek-ai/cordis'
import * as PrimitivesInvariant from '@deepseek-ai/dsh-client-ui-primitives/invariant'
import InvariantService from '@deepseek-ai/dsh-invariants'

View File

@@ -6,6 +6,27 @@ import { Tooltip } from '@deepseek-ai/dsh-client-ui-primitives'
afterEach(cleanup)
describe('Tooltip', () => {
it('resolves lazy labels only after the bubble becomes visible', () => {
vi.useFakeTimers()
try {
const label = vi.fn(() => 'Timing details')
render(
<Tooltip label={label} delayMs={500}>
<button type="button">anchor</button>
</Tooltip>,
)
expect(label).not.toHaveBeenCalled()
fireEvent.mouseEnter(screen.getByText('anchor'))
act(() => { vi.advanceTimersByTime(499) })
expect(label).not.toHaveBeenCalled()
act(() => { vi.advanceTimersByTime(1) })
expect(screen.getByRole('tooltip').textContent).toBe('Timing details')
expect(label).toHaveBeenCalledOnce()
} finally {
vi.useRealTimers()
}
})
it('can delay pointer hover without delaying keyboard focus', () => {
vi.useFakeTimers()
try {