Files
deepseek-harness/packages/client/ui-primitives/src/input-modality.ts
T
mektpoy bca9cecc7d fix(client): keep pointer focus silent through descendant and editable rings
Pointer suppression previously cleared `outline-color` on the focused element
alone, so a ring painted by a descendant, a pseudo-element, or a `box-shadow`
stayed visible, and editable text controls lost the focus feedback they need on
click.

Ring colour now flows through `--dsw-focus-ring-color`, which component outlines
and focus-ring shadows read alongside the brand token. Every ring that inherits
the variable is suppressed together, and `body :focus-visible:not(:read-write)`
limits the suppression to rings while editable controls keep their own feedback.

Keyboard modality returns on Tab, Home, End, PageUp, or PageDown, and on focus
reaching a different control after a non-composing key. IME composition keys,
refocusing the same control, and window blur leave rings silent. The desktop
preload menubar suppresses its own ring inside its shadow root, where the
document-level attribute does not reach.
2026-09-22 20:28:10 +08:00

60 lines
2.0 KiB
TypeScript

/**
* Document-wide input tracking shared by tooltips and focus-ring styles.
* Tooltips follow the last input; rings follow navigation or a key followed by
* focus on a different control. Modifiers and refocusing alone keep rings silent.
* Module-level listeners live for the document lifetime; Node imports are inert.
*/
/** Modality values published on the document element. */
export const INPUT_MODALITY = { pointer: 'pointer', keyboard: 'keyboard' } as const
/** Attribute carrying whether focus navigation last owned focus. */
export const INPUT_MODALITY_ATTRIBUTE = 'data-input-modality'
const FOCUS_NAVIGATION = new Set(['Tab', 'Home', 'End', 'PageUp', 'PageDown'])
let pointer = false
let pointerOwnsFocus = false
let keyFocusOrigin: EventTarget | undefined
function publish(): void {
document.documentElement.setAttribute(INPUT_MODALITY_ATTRIBUTE, pointerOwnsFocus ? INPUT_MODALITY.pointer : INPUT_MODALITY.keyboard)
}
/**
* Whether the last input came from a pointer, independently of ring visibility.
* @returns True after pointer input; false after any key.
*/
export function pointerModality(): boolean {
return pointer
}
if (typeof window !== 'undefined') {
window.addEventListener('pointerdown', () => {
pointer = true
pointerOwnsFocus = true
keyFocusOrigin = undefined
publish()
}, true)
window.addEventListener('keydown', (event) => {
pointer = false
if (event.isComposing) {
keyFocusOrigin = undefined
return
}
// composedPath preserves the real control inside an open shadow root.
keyFocusOrigin = event.composedPath()[0]
if (!FOCUS_NAVIGATION.has(event.key) && !event.key.startsWith('Arrow')) return
pointerOwnsFocus = false
publish()
}, true)
window.addEventListener('focusin', (event) => {
if (keyFocusOrigin === undefined || event.composedPath()[0] === keyFocusOrigin) return
keyFocusOrigin = undefined
if (!pointerOwnsFocus) return
pointerOwnsFocus = false
publish()
}, true)
window.addEventListener('blur', () => { keyFocusOrigin = undefined })
}