资源管理器补全:文件树右键菜单新增「重命名」(行内编辑:预填+全选、 Enter/失焦提交、Esc 取消、IME 组合键守卫)与「删除」(Modal 确认,文件/ 目录文案区分,永久删除——宿主无回收站)。根行(工作区根)不提供两项, 服务端 fs.rename/fs.remove 双保险拒绝;目标已存在 409;link-aware—— symlink 行只改/删链接本身(lstat 判定),fence 校验对解析后目标进行。 落定收尾:pruneTree 清旧前缀缓存 + 收起 stale expanded + 单层重载父目录; tab 调解走 service(rename→updateTab 改路径改标题、delete→closeTab 关 受影响 tab,覆盖两侧分栏/底栏/浮动窗口,经 tree-mutations.ts)。 二级菜单视觉关系(layout.css,仍限 data-dsh-sidebar-submenu 作用域): 父行在子菜单打开期间保持高亮(宿主已渲染 aria-expanded="true"), 卡片间距 10px→4px(悬停走廊同步收窄),翻转规则一并 4px。 i18n 新增 7 key × 20 语言文件(locales.spec 的 key-set-parity 门禁)。 测试:fs-operations 追加 12 例(rename/remove 全失败面);新增 file-tree-rename-delete.spec.tsx 8 例(菜单可见性/重命名流/删除流/错误条); menu-flip.spec.tsx 契约扩展覆盖 tether 规则。设计文档 docs/plans/2026-09-04-file-tree-rename-delete-design.md。
51 lines
2.4 KiB
TypeScript
51 lines
2.4 KiB
TypeScript
/**
|
|
* IME-composition key guard.
|
|
*
|
|
* While a Chinese/Japanese/Korean input method is composing (the user is
|
|
* picking a candidate from the IME window), every pressed key BELONGS to the
|
|
* input method: arrows move the candidate highlight, Enter/Space confirm the
|
|
* composition, Escape cancels it. Page code must not process those keys —
|
|
* a component that does (a number stepper calling preventDefault() on
|
|
* ArrowUp/ArrowDown, a submit handler reacting to Enter, ...) silently
|
|
* breaks the IME: candidates stop responding, the composition gets torn
|
|
* apart, and only bare letters come out.
|
|
*
|
|
* This guard enforces that rule at the document boundary: a capture-phase
|
|
* keydown/keyup listener that stops the event from propagating further
|
|
* whenever a composition is in progress. Because it runs in the capture
|
|
* phase on `document` — the outermost node — it fires BEFORE React's
|
|
* delegated handlers (attached at the root container) and before any native
|
|
* target/bubble listener, so an inlined third-party component (e.g. the
|
|
* Univer office UI bundled into this plugin) can never intercept
|
|
* composition keys. The browser's native IME processing is untouched:
|
|
* stopPropagation only silences page JS, not the default action.
|
|
*
|
|
* The composition signal follows the DSH core convention (InputBar's IME
|
|
* guard, issue #535): `isComposing` for modern engines, keyCode 229 as the
|
|
* legacy signal engines emit without isComposing.
|
|
*/
|
|
|
|
/** The pure decision: is this keyboard event part of an IME composition?
|
|
* `isComposing` is optional on the input: React's synthetic KeyboardEvent
|
|
* type does not declare it (the DOM event always carries it), and the
|
|
* keyCode 229 fallback covers exactly those callers. */
|
|
export function isImeComposition(event: { isComposing?: boolean; keyCode: number }): boolean {
|
|
return event.isComposing === true || event.keyCode === 229
|
|
}
|
|
|
|
/**
|
|
* Register the document-level capture guard. Returns the disposer
|
|
* (HMR-safe; call through `ctx.effect`).
|
|
*/
|
|
export function registerImeGuard(): () => void {
|
|
const onKey = (event: KeyboardEvent): void => {
|
|
if (isImeComposition(event)) event.stopPropagation()
|
|
}
|
|
document.addEventListener('keydown', onKey, true)
|
|
document.addEventListener('keyup', onKey, true)
|
|
return () => {
|
|
document.removeEventListener('keydown', onKey, true)
|
|
document.removeEventListener('keyup', onKey, true)
|
|
}
|
|
}
|