refactor(client): share the outside-pointer dismissal hook
The jobs list and the Cordis panel carried identical outside-pointerdown close effects, which the duplication gate rejects; both now use useDismissOnOutsidePointer from ui-primitives. Refs #2526
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo, useRef, useState, type KeyboardEvent } from 'react'
|
import { useEffect, useMemo, useRef, useState, type KeyboardEvent } from 'react'
|
||||||
import type { JobView } from '@deepseek-ai/dsh-client-runtime/client'
|
import type { JobView } from '@deepseek-ai/dsh-client-runtime/client'
|
||||||
import { IconChevronDownOutline14, StateDot, type StateDotState } from '@deepseek-ai/dsh-client-ui-primitives'
|
import { IconChevronDownOutline14, StateDot, useDismissOnOutsidePointer, type StateDotState } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||||
import type { PropsLocale, PropsRuntime, TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'
|
import type { PropsLocale, PropsRuntime, TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'
|
||||||
import { NS } from './locales.ts'
|
import { NS } from './locales.ts'
|
||||||
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||||
@@ -101,16 +101,7 @@ export function JobListAction({ sessionId, useSessions, t }: JobListActionProps)
|
|||||||
const rows = useMemo(() => ordered(jobs), [jobs])
|
const rows = useMemo(() => ordered(jobs), [jobs])
|
||||||
const liveCount = useMemo(() => jobs.filter(isLive).length, [jobs])
|
const liveCount = useMemo(() => jobs.filter(isLive).length, [jobs])
|
||||||
|
|
||||||
useEffect(() => {
|
useDismissOnOutsidePointer(rootRef, open, setOpen)
|
||||||
if (!open) return
|
|
||||||
const closeOutside = (event: PointerEvent): void => {
|
|
||||||
if (event.target instanceof Node && !rootRef.current?.contains(event.target)) {
|
|
||||||
setOpen(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.addEventListener('pointerdown', closeOutside)
|
|
||||||
return () => { document.removeEventListener('pointerdown', closeOutside) }
|
|
||||||
}, [open])
|
|
||||||
|
|
||||||
// The clock only runs while an open list is showing something that moves.
|
// The clock only runs while an open list is showing something that moves.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export { Input } from './Input.tsx'
|
|||||||
export { Menu } from './Menu.tsx'
|
export { Menu } from './Menu.tsx'
|
||||||
export type { MenuEntry, MenuItem, MenuSeparator, MenuLabel } from './Menu.tsx'
|
export type { MenuEntry, MenuItem, MenuSeparator, MenuLabel } from './Menu.tsx'
|
||||||
export { useAnchoredMaxHeight } from './useAnchoredMaxHeight.ts'
|
export { useAnchoredMaxHeight } from './useAnchoredMaxHeight.ts'
|
||||||
|
export { useDismissOnOutsidePointer } from './useDismissOnOutsidePointer.ts'
|
||||||
export { HoverCard } from './HoverCard.tsx'
|
export { HoverCard } from './HoverCard.tsx'
|
||||||
export { Modal } from './Modal.tsx'
|
export { Modal } from './Modal.tsx'
|
||||||
export { OnboardingSurface } from './OnboardingSurface.tsx'
|
export { OnboardingSurface } from './OnboardingSurface.tsx'
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Outside-pointer dismissal for trigger-owned popovers (jobs list, Cordis
|
||||||
|
* panel): while the surface is open, a pointerdown outside the root closes it.
|
||||||
|
*/
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import type { RefObject } from 'react'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close an open popover when a pointerdown lands outside its root element.
|
||||||
|
* @param root - element containing both the trigger and the open surface.
|
||||||
|
* @param open - whether the surface is showing; false detaches the listener.
|
||||||
|
* @param setOpen - state setter invoked with false on an outside pointerdown.
|
||||||
|
*/
|
||||||
|
export function useDismissOnOutsidePointer(
|
||||||
|
root: RefObject<HTMLElement | null>,
|
||||||
|
open: boolean,
|
||||||
|
setOpen: (open: boolean) => void,
|
||||||
|
): void {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return
|
||||||
|
const closeOutside = (event: PointerEvent): void => {
|
||||||
|
if (event.target instanceof Node && !root.current?.contains(event.target)) {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('pointerdown', closeOutside)
|
||||||
|
return () => { document.removeEventListener('pointerdown', closeOutside) }
|
||||||
|
}, [root, open, setOpen])
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
|||||||
import type { ButtonHTMLAttributes, ReactNode } from 'react'
|
import type { ButtonHTMLAttributes, ReactNode } from 'react'
|
||||||
import {
|
import {
|
||||||
IconCheckOutline16, IconCloseOutline16, IconCordisPluginOutline14, IconPlayOutline16,
|
IconCheckOutline16, IconCloseOutline16, IconCordisPluginOutline14, IconPlayOutline16,
|
||||||
IconStopFill16, IconTrashOutline16, Tooltip,
|
IconStopFill16, IconTrashOutline16, Tooltip, useDismissOnOutsidePointer,
|
||||||
} from '@deepseek-ai/dsh-client-ui-primitives'
|
} from '@deepseek-ai/dsh-client-ui-primitives'
|
||||||
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
||||||
import type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'
|
import type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'
|
||||||
@@ -137,16 +137,7 @@ export function CordisPanel({
|
|||||||
return () => { window.removeEventListener('resize', place) }
|
return () => { window.removeEventListener('resize', place) }
|
||||||
}, [open])
|
}, [open])
|
||||||
|
|
||||||
useEffect(() => {
|
useDismissOnOutsidePointer(rootRef, open, setOpen)
|
||||||
if (!open) return
|
|
||||||
const closeOutside = (event: PointerEvent): void => {
|
|
||||||
if (event.target instanceof Node && !rootRef.current?.contains(event.target)) {
|
|
||||||
setOpen(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.addEventListener('pointerdown', closeOutside)
|
|
||||||
return () => { document.removeEventListener('pointerdown', closeOutside) }
|
|
||||||
}, [open])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const now = new Set<ApprovalRequestId>()
|
const now = new Set<ApprovalRequestId>()
|
||||||
|
|||||||
Reference in New Issue
Block a user