@splicetree/plugin-keyboard 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,25 +1,22 @@
1
1
  # @splicetree/plugin-keyboard
2
2
 
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - fix: republish plugin-keyboard after partial release failure
8
+
9
+ ## 0.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - ### @splicetree/plugin-keyboard
14
+ - 只采集方向键并统一派发 `input:direction`
15
+ - 配置聚合到 `configuration.keyboard`:`autoListen/target/keymap`
16
+ - 自动为目标容器添加 `tabindex="0"` 并聚焦
17
+
3
18
  ## 0.0.1
4
19
 
5
20
  ### Patch Changes
6
21
 
7
22
  - chore: 搭建 SpliceTree 框架基础架构与工程体系
8
-
9
- 框架架构:
10
- - 建立无头树(Headless Tree)核心模型与数据结构
11
- - 定义节点操作 API、事件系统和状态管理机制
12
- - 设计并实现插件体系(生命周期、注册机制、能力扩展)
13
- - 引入模块化架构,明确 core / plugins / adapters 的边界
14
- - 预留扩展点与内部协议,构建可插拔式架构基础
15
-
16
- 适配层与生态:
17
- - 添加 Vue 3 适配层(渲染无关、纯接口绑定)
18
- - 设计独立的 UI 层解耦策略,确保跨框架可迁移
19
- - 预留未来 React/Svelte/WebComponents 的适配接口
20
-
21
- 工程化与工具链:
22
- - 初始化 monorepo 工程结构(packages + docs)
23
- - 配置构建工具链 tsdown(多包构建、类型输出)
24
- - 构建文档系统(VitePress)与基础导航结构
25
- - 设置开发环境、代码规范(ESLint)、格式化流程
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # @splicetree/plugin-keyboard
2
2
 
3
+ 提供快捷键导航能力:上下移动、展开与收起。
4
+
3
5
  [![version](https://img.shields.io/npm/v/@splicetree/plugin-keyboard.svg?label=version)](https://www.npmjs.com/package/@splicetree/plugin-keyboard)
4
6
  [![downloads](https://img.shields.io/npm/dm/@splicetree/plugin-keyboard.svg)](https://npmcharts.com/compare/%40splicetree%2Fplugin-keyboard?minimal=true)
5
7
  [![license](https://img.shields.io/npm/l/@splicetree/plugin-keyboard.svg)](https://www.npmjs.com/package/@splicetree/plugin-keyboard)
6
8
  [![Website](https://img.shields.io/static/v1?label=Website&message=splicetree.dev&color=blue)](https://www.splicetree.dev)
7
9
  [![GitHub](https://img.shields.io/static/v1?label=GitHub&message=splicetree%2Fsplicetree&logo=github)](https://github.com/michaelcocova/splicetree)
8
10
 
9
- 提供快捷键导航能力:上下移动、展开与收起。
10
-
11
11
  ## 安装
12
12
 
13
13
  `pnpm add @splicetree/plugin-keyboard`
@@ -20,32 +20,28 @@ import keyboardNavigation from '@splicetree/plugin-keyboard'
20
20
 
21
21
  const tree = createSpliceTree(data, {
22
22
  plugins: [keyboardNavigation],
23
- defaultActive: 'a',
24
- autoListenKeyboard: true,
25
- keymap: { expand: 'ArrowRight', collapse: 'ArrowLeft', next: 'ArrowDown', prev: 'ArrowUp' },
23
+ configuration: {
24
+ keyboard: {
25
+ autoListen: true,
26
+ target: '.keyboard-wrap',
27
+ keymap: { up: 'ArrowUp', down: 'ArrowDown', left: 'ArrowLeft', right: 'ArrowRight' },
28
+ },
29
+ },
26
30
  })
27
31
  ```
28
32
 
29
33
  ## Api
30
34
 
31
- ### Options
35
+ ### Configuration
32
36
 
33
- | 选项 | 类型 | 默认值 | 说明 |
34
- | -------------------- | ---------------------------------------------------------------------- | --------------- | -------------------------------- |
35
- | `defaultActive` | `string` | `undefined` | 默认激活节点 |
36
- | `autoListenKeyboard` | `boolean` | `true` | 是否自动监听键盘事件 |
37
- | `keymap` | `{ expand?: string; collapse?: string; next?: string; prev?: string }` | `见下` | 快捷键映射(默认:左右上下箭头) |
38
- | `keyboardTarget` | `HTMLElement` | `document.body` | 键盘监听目标 |
37
+ - `configuration.keyboard.autoListen: boolean` 是否自动监听键盘(默认 `true`)
38
+ - `configuration.keyboard.target: HTMLElement | string | (() => HTMLElement | null)` 监听目标
39
+ - `configuration.keyboard.keymap: { up?: string; down?: string; left?: string; right?: string }` 快捷键映射
39
40
 
40
41
  ### 实例方法
41
42
 
42
- | 名称 | 参数 | 说明 |
43
- | ----------------------- | -------------------------- | ---------------------- |
44
- | `activeId` | `无` | 当前激活的节点 |
45
- | `toggleActive(id, on?)` | `id: string; on?: boolean` | 切换或显式设置激活状态 |
43
+ - `activeId?: string` 当前激活节点 id(与选择插件协同)
46
44
 
47
45
  ### 节点方法
48
46
 
49
- | 名称 | 参数 | 说明 |
50
- | ------------------- | -------------- | -------------------- |
51
- | `toggleActive(on?)` | `on?: boolean` | 切换或显式设置激活态 |
47
+ - 无(键盘插件不扩展节点方法,专注输入事件)
package/dist/index.d.ts CHANGED
@@ -1,72 +1,42 @@
1
1
  import { SpliceTreePlugin } from "@splicetree/core";
2
2
 
3
3
  //#region src/index.d.ts
4
+ interface KeyboardKeymap {
5
+ up?: string;
6
+ down?: string;
7
+ left?: string;
8
+ right?: string;
9
+ }
4
10
  type KeyboardTargetType = HTMLElement | string | null | undefined;
5
11
  type KeyboardTarget = KeyboardTargetType | (() => KeyboardTargetType);
12
+ interface Modifiers {
13
+ shift: boolean;
14
+ ctrl: boolean;
15
+ meta: boolean;
16
+ alt: boolean;
17
+ }
6
18
  declare module '@splicetree/core' {
7
- interface UseSpliceTreeOptions {
8
- /**
9
- * 默认激活节点 ID
10
- * @default undefined
11
- */
12
- defaultActive?: string;
13
- /**
14
- * 是否自动监听键盘事件
15
- * 开启后,插件会自动监听键盘事件
16
- * 关闭后,需要手动调用 `listenKeyboard` 方法监听键盘事件
17
- * @default true
18
- */
19
- autoListenKeyboard?: boolean;
20
- /**
21
- * 键盘导航快捷键
22
- * @default { expand: 'ArrowRight', collapse: 'ArrowLeft', next: 'ArrowDown', prev: 'ArrowUp' }
23
- */
24
- keymap?: {
25
- expand?: string;
26
- collapse?: string;
27
- next?: string;
28
- prev?: string;
19
+ interface SpliceTreeConfiguration {
20
+ keyboard?: {
21
+ autoListen?: boolean;
22
+ target?: KeyboardTarget;
23
+ keymap?: KeyboardKeymap;
29
24
  };
30
- /**
31
- * 键盘导航目标元素
32
- * @default document.body
33
- */
34
- keyboardTarget?: KeyboardTarget;
35
25
  }
36
- /**
37
- * 实例扩展(Keyboard)
38
- * - activeId:当前激活的节点 id
39
- * - toggleActive:切换或显式设置某节点的激活态
40
- */
41
- interface SpliceTreeInstance {
42
- /**
43
- * 当前激活的节点 id(未激活时为 undefined)
44
- */
45
- activeId?: string;
46
- /**
47
- * 切换或显式设置某节点的激活态
48
- * @param id 节点 id
49
- * @param active 不传表示切换;true/false 表示显式设置
50
- */
51
- toggleActive: (id: string, active?: boolean) => void;
26
+ interface SpliceTreeEventPayloadMap {
27
+ 'input:direction': {
28
+ direction: 'up' | 'down' | 'left' | 'right';
29
+ modifiers: Modifiers;
30
+ };
31
+ 'input:node-click': {
32
+ nodeId: string;
33
+ modifiers: Modifiers;
34
+ };
52
35
  }
53
- /**
54
- * 节点扩展(Keyboard)
55
- * - isActive:当前节点是否激活
56
- * - toggleActive:切换当前节点的激活态
57
- */
58
- interface SpliceTreeNode {
59
- /**
60
- * 当前节点是否为激活态
61
- */
62
- isActive: () => boolean;
63
- /**
64
- * 切换当前节点的激活态
65
- * @param active 不传表示切换;true/false 表示显式设置
66
- */
67
- toggleActive: (active?: boolean) => void;
36
+ interface SpliceTreeInstance {
37
+ emitNodeClick: (nodeId: string, e: MouseEvent) => void;
68
38
  }
69
39
  }
70
- declare const keyboardNavigation: SpliceTreePlugin;
40
+ declare const keyboardPlugin: SpliceTreePlugin;
71
41
  //#endregion
72
- export { keyboardNavigation as default, keyboardNavigation };
42
+ export { keyboardPlugin as default, keyboardPlugin };
package/dist/index.js CHANGED
@@ -1,147 +1,79 @@
1
1
  //#region src/index.ts
2
- /**
3
- * 解析键盘监听目标元素
4
- * 支持传入选择器、元素实例或函数
5
- */
6
- function resolveKeyboardTarget(targe) {
7
- if (typeof targe === "string") return document.querySelector(targe);
8
- if (targe instanceof HTMLElement) return targe;
9
- if (typeof targe === "function") return resolveKeyboardTarget(targe?.());
2
+ function resolveTarget(target) {
3
+ if (typeof target === "string") return document.querySelector(target);
4
+ if (target instanceof HTMLElement) return target;
5
+ if (typeof target === "function") return resolveTarget(target());
10
6
  return null;
11
7
  }
12
- const keyboardNavigation = {
8
+ function getModifiers(e) {
9
+ return {
10
+ shift: e.shiftKey,
11
+ ctrl: e.ctrlKey,
12
+ meta: e.metaKey,
13
+ alt: e.altKey
14
+ };
15
+ }
16
+ const keyboardPlugin = {
13
17
  name: "keyboard",
14
18
  setup(ctx) {
15
- const { defaultActive, autoListenKeyboard = true, keyboardTarget, keymap } = ctx.options;
16
- let activeId = defaultActive;
17
- Object.defineProperty(ctx.tree, "activeId", {
18
- get() {
19
- return activeId;
20
- },
21
- configurable: true,
22
- enumerable: true
23
- });
24
- /**
25
- * 切换或设置激活节点
26
- * 更新后派发 visibility 事件以刷新视图
27
- * @param id 节点 id
28
- * @param active 不传表示切换;true/false 表示显式设置
29
- */
30
- const toggleActive = (id, active) => {
31
- if (active === void 0) activeId = activeId === id ? void 0 : id;
32
- else if (active) activeId = id;
33
- else if (activeId === id) activeId = void 0;
34
- ctx.events.emit({
35
- name: "visibility",
36
- keys: ctx.tree.expandedKeys()
37
- });
19
+ const config = ctx.options?.configuration?.keyboard ?? {};
20
+ const autoListenKeyboard = config.autoListen ?? true;
21
+ const keyboardTarget = config.target;
22
+ const keymap = config.keymap;
23
+ const keys = {
24
+ up: keymap?.up ?? "ArrowUp",
25
+ down: keymap?.down ?? "ArrowDown",
26
+ left: keymap?.left ?? "ArrowLeft",
27
+ right: keymap?.right ?? "ArrowRight"
38
28
  };
39
- /**
40
- * 按可见序列移动激活项
41
- * @param delta 移动步长(-1 上一个,1 下一个)
42
- */
43
- const moveActive = (delta) => {
44
- const items = ctx.tree.items();
45
- if (!items.length) return;
46
- const idx = activeId ? items.findIndex((n) => n.id === activeId) : -1;
47
- activeId = items[Math.max(0, Math.min(items.length - 1, idx < 0 ? 0 : idx + delta))]?.id;
29
+ const emitDirection = (direction, e) => {
48
30
  ctx.events.emit({
49
- name: "visibility",
50
- keys: ctx.tree.expandedKeys()
31
+ name: "input:direction",
32
+ direction,
33
+ modifiers: getModifiers(e)
51
34
  });
52
35
  };
53
- /**
54
- * 激活上一个可见节点
55
- */
56
- const keyUp = () => moveActive(-1);
57
- /**
58
- * 激活下一个可见节点
59
- */
60
- const keyDown = () => moveActive(1);
61
- /**
62
- * 左方向键:优先收起;否则激活父节点
63
- */
64
- const keyLeft = () => {
65
- if (!activeId) return;
66
- const node = ctx.tree.getNode(activeId);
67
- if (!node) return;
68
- if (ctx.tree.isExpanded(node.id)) ctx.tree.collapse(node.id);
69
- else if (node.getParent()?.id) activeId = node.getParent().id;
70
- ctx.events.emit({
71
- name: "visibility",
72
- keys: ctx.tree.expandedKeys()
73
- });
74
- };
75
- /**
76
- * 右方向键:优先展开;已展开则进入第一个子节点
77
- */
78
- const keyRight = () => {
79
- if (!activeId) return;
80
- const node = ctx.tree.getNode(activeId);
81
- if (!node) return;
82
- if (!ctx.tree.isExpanded(node.id)) ctx.tree.expand(node.id);
83
- else {
84
- const first = node.getChildren()[0];
85
- if (first) activeId = first.id;
36
+ const onKeydown = (e) => {
37
+ switch (e.key) {
38
+ case keys.up:
39
+ e.preventDefault();
40
+ emitDirection("up", e);
41
+ break;
42
+ case keys.down:
43
+ e.preventDefault();
44
+ emitDirection("down", e);
45
+ break;
46
+ case keys.left:
47
+ e.preventDefault();
48
+ emitDirection("left", e);
49
+ break;
50
+ case keys.right:
51
+ e.preventDefault();
52
+ emitDirection("right", e);
53
+ break;
86
54
  }
55
+ };
56
+ const emitNodeClick = (nodeId, e) => {
87
57
  ctx.events.emit({
88
- name: "visibility",
89
- keys: ctx.tree.expandedKeys()
58
+ name: "input:node-click",
59
+ nodeId,
60
+ modifiers: getModifiers(e)
90
61
  });
91
62
  };
92
- /**
93
- * 键盘按下事件处理
94
- * - 根据 keymap 映射调用对应处理函数
95
- * - 自动阻止默认行为以避免滚动
96
- */
97
- const onKeydown = (e) => {
98
- const kExpand = keymap?.expand ?? "ArrowRight";
99
- const kCollapse = keymap?.collapse ?? "ArrowLeft";
100
- const kNext = keymap?.next ?? "ArrowDown";
101
- const kPrev = keymap?.prev ?? "ArrowUp";
102
- const key = e.key;
103
- if (key === kPrev) {
104
- e.preventDefault();
105
- keyUp();
106
- return;
107
- }
108
- if (key === kNext) {
109
- e.preventDefault();
110
- keyDown();
111
- return;
112
- }
113
- if (key === kCollapse) {
114
- e.preventDefault();
115
- keyLeft();
116
- return;
117
- }
118
- if (key === kExpand) {
119
- e.preventDefault();
120
- keyRight();
121
- }
122
- };
123
63
  if (autoListenKeyboard && typeof document !== "undefined") setTimeout(() => {
124
- const root = resolveKeyboardTarget(keyboardTarget);
125
- if (root) {
126
- root.setAttribute("tabindex", "0");
127
- root?.focus?.();
128
- }
64
+ const root = resolveTarget(keyboardTarget);
65
+ if (!root) return;
66
+ root.setAttribute("tabindex", "0");
129
67
  const handler = (e) => {
130
68
  const active = document.activeElement;
131
- if (!root || root.contains(active)) onKeydown(e);
69
+ if (root.contains(active)) onKeydown(e);
132
70
  };
133
71
  document.addEventListener("keydown", handler);
134
72
  });
135
- return { toggleActive };
136
- },
137
- extendNode(node, ctx) {
138
- node.isActive = () => ctx.tree?.activeId === node.id;
139
- node.toggleActive = (active) => {
140
- ctx.tree.toggleActive(node.id, active);
141
- };
73
+ return { emitNodeClick };
142
74
  }
143
75
  };
144
- var src_default = keyboardNavigation;
76
+ var src_default = keyboardPlugin;
145
77
 
146
78
  //#endregion
147
- export { src_default as default, keyboardNavigation };
79
+ export { src_default as default, keyboardPlugin };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@splicetree/plugin-keyboard",
3
3
  "type": "module",
4
- "version": "0.0.1",
4
+ "version": "0.1.1",
5
5
  "author": {
6
6
  "email": "michael.cocova@gmail.com",
7
7
  "name": "Michael Cocova"
@@ -23,7 +23,7 @@
23
23
  "access": "public"
24
24
  },
25
25
  "devDependencies": {
26
- "@splicetree/core": "0.0.1"
26
+ "@splicetree/core": "0.1.1"
27
27
  },
28
28
  "scripts": {
29
29
  "dev": "tsdown --watch",
package/src/index.ts CHANGED
@@ -1,252 +1,147 @@
1
1
  import type { SpliceTreePlugin, SpliceTreePluginContext } from '@splicetree/core'
2
2
  import '@splicetree/core'
3
3
 
4
+ interface KeyboardKeymap {
5
+ up?: string
6
+ down?: string
7
+ left?: string
8
+ right?: string
9
+ }
4
10
  type KeyboardTargetType = HTMLElement | string | null | undefined
5
11
  type KeyboardTarget = KeyboardTargetType | (() => KeyboardTargetType)
12
+
13
+ interface Modifiers {
14
+ shift: boolean
15
+ ctrl: boolean
16
+ meta: boolean
17
+ alt: boolean
18
+ }
19
+
6
20
  declare module '@splicetree/core' {
7
- export interface UseSpliceTreeOptions {
8
- /**
9
- * 默认激活节点 ID
10
- * @default undefined
11
- */
12
- defaultActive?: string
13
- /**
14
- * 是否自动监听键盘事件
15
- * 开启后,插件会自动监听键盘事件
16
- * 关闭后,需要手动调用 `listenKeyboard` 方法监听键盘事件
17
- * @default true
18
- */
19
- autoListenKeyboard?: boolean
20
- /**
21
- * 键盘导航快捷键
22
- * @default { expand: 'ArrowRight', collapse: 'ArrowLeft', next: 'ArrowDown', prev: 'ArrowUp' }
23
- */
24
- keymap?: {
25
- expand?: string
26
- collapse?: string
27
- next?: string
28
- prev?: string
21
+ export interface SpliceTreeConfiguration {
22
+ keyboard?: {
23
+ autoListen?: boolean
24
+ target?: KeyboardTarget
25
+ keymap?: KeyboardKeymap
29
26
  }
30
- /**
31
- * 键盘导航目标元素
32
- * @default document.body
33
- */
34
- keyboardTarget?: KeyboardTarget
35
27
  }
36
-
37
- /**
38
- * 实例扩展(Keyboard)
39
- * - activeId:当前激活的节点 id
40
- * - toggleActive:切换或显式设置某节点的激活态
41
- */
42
- interface SpliceTreeInstance {
43
- /**
44
- * 当前激活的节点 id(未激活时为 undefined)
45
- */
46
- activeId?: string
47
- /**
48
- * 切换或显式设置某节点的激活态
49
- * @param id 节点 id
50
- * @param active 不传表示切换;true/false 表示显式设置
51
- */
52
- toggleActive: (id: string, active?: boolean) => void
28
+ export interface SpliceTreeEventPayloadMap {
29
+ 'input:direction': {
30
+ direction: 'up' | 'down' | 'left' | 'right'
31
+ modifiers: Modifiers
32
+ }
33
+ 'input:node-click': {
34
+ nodeId: string
35
+ modifiers: Modifiers
36
+ }
53
37
  }
54
-
55
- /**
56
- * 节点扩展(Keyboard)
57
- * - isActive:当前节点是否激活
58
- * - toggleActive:切换当前节点的激活态
59
- */
60
- interface SpliceTreeNode {
61
- /**
62
- * 当前节点是否为激活态
63
- */
64
- isActive: () => boolean
65
- /**
66
- * 切换当前节点的激活态
67
- * @param active 不传表示切换;true/false 表示显式设置
68
- */
69
- toggleActive: (active?: boolean) => void
38
+ export interface SpliceTreeInstance {
39
+ emitNodeClick: (nodeId: string, e: MouseEvent) => void
70
40
  }
71
41
  }
72
-
73
- /**
74
- * 解析键盘监听目标元素
75
- * 支持传入选择器、元素实例或函数
76
- */
77
- function resolveKeyboardTarget(targe?: KeyboardTarget): HTMLElement | null {
78
- if (typeof targe === 'string') {
79
- return document.querySelector(targe)
42
+ function resolveTarget(target?: KeyboardTarget): HTMLElement | null {
43
+ if (typeof target === 'string') {
44
+ return document.querySelector(target)
80
45
  }
81
- if (targe instanceof HTMLElement) {
82
- return targe
46
+ if (target instanceof HTMLElement) {
47
+ return target
83
48
  }
84
- if (typeof targe === 'function') {
85
- return resolveKeyboardTarget(targe?.())
49
+ if (typeof target === 'function') {
50
+ return resolveTarget(target())
86
51
  }
87
52
  return null
88
53
  }
89
- export const keyboardNavigation: SpliceTreePlugin = {
54
+
55
+ function getModifiers(e: KeyboardEvent | MouseEvent) {
56
+ return {
57
+ shift: e.shiftKey,
58
+ ctrl: e.ctrlKey,
59
+ meta: e.metaKey,
60
+ alt: e.altKey,
61
+ }
62
+ }
63
+
64
+ export const keyboardPlugin: SpliceTreePlugin = {
90
65
  name: 'keyboard',
91
- /**
92
- * 键盘导航插件
93
- * - 提供 activeId 概念与切换 API
94
- * - 支持上下移动、展开/收起与进入子级
95
- * - 自动或自定义监听目标元素的键盘事件
96
- */
66
+
97
67
  setup(ctx: SpliceTreePluginContext) {
98
- const { defaultActive, autoListenKeyboard = true, keyboardTarget, keymap } = ctx.options
99
- let activeId: string | undefined = defaultActive
100
-
101
- Object.defineProperty(ctx.tree, 'activeId', {
102
- get() {
103
- return activeId
104
- },
105
- configurable: true,
106
- enumerable: true,
107
- })
108
-
109
- /**
110
- * 切换或设置激活节点
111
- * 更新后派发 visibility 事件以刷新视图
112
- * @param id 节点 id
113
- * @param active 不传表示切换;true/false 表示显式设置
114
- */
115
- const toggleActive = (id: string, active?: boolean) => {
116
- if (active === undefined) {
117
- activeId = activeId === id ? undefined : id
118
- } else {
119
- if (active)
120
- activeId = id
121
- else if (activeId === id)
122
- activeId = undefined
123
- }
124
- ctx.events.emit({ name: 'visibility', keys: ctx.tree.expandedKeys() })
68
+ const config = (ctx.options?.configuration?.keyboard ?? {}) as {
69
+ autoListen?: boolean
70
+ target?: KeyboardTarget
71
+ keymap?: KeyboardKeymap
125
72
  }
73
+ const autoListenKeyboard = config.autoListen ?? true
74
+ const keyboardTarget = config.target
75
+ const keymap = config.keymap
126
76
 
127
- /**
128
- * 按可见序列移动激活项
129
- * @param delta 移动步长(-1 上一个,1 下一个)
130
- */
131
- const moveActive = (delta: number) => {
132
- const items = ctx.tree.items()
133
- if (!items.length) {
134
- return
135
- }
136
- const idx = activeId ? items.findIndex(n => n.id === activeId) : -1
137
- const next = Math.max(0, Math.min(items.length - 1, (idx < 0 ? 0 : idx + delta)))
138
- activeId = items[next]?.id
139
- ctx.events.emit({ name: 'visibility', keys: ctx.tree.expandedKeys() })
77
+ const keys = {
78
+ up: keymap?.up ?? 'ArrowUp',
79
+ down: keymap?.down ?? 'ArrowDown',
80
+ left: keymap?.left ?? 'ArrowLeft',
81
+ right: keymap?.right ?? 'ArrowRight',
140
82
  }
141
83
 
142
- /**
143
- * 激活上一个可见节点
144
- */
145
- const keyUp = () => moveActive(-1)
146
- /**
147
- * 激活下一个可见节点
148
- */
149
- const keyDown = () => moveActive(1)
150
- /**
151
- * 左方向键:优先收起;否则激活父节点
152
- */
153
- const keyLeft = () => {
154
- if (!activeId) {
155
- return
156
- }
157
- const node = ctx.tree.getNode(activeId)
158
- if (!node) {
159
- return
160
- }
161
- if (ctx.tree.isExpanded(node.id)) {
162
- ctx.tree.collapse(node.id)
163
- } else if (node.getParent()?.id) {
164
- activeId = node.getParent()!.id
165
- }
166
- ctx.events.emit({ name: 'visibility', keys: ctx.tree.expandedKeys() })
167
- }
168
- /**
169
- * 右方向键:优先展开;已展开则进入第一个子节点
170
- */
171
- const keyRight = () => {
172
- if (!activeId) {
173
- return
174
- }
175
- const node = ctx.tree.getNode(activeId)
176
- if (!node) {
177
- return
178
- }
179
- if (!ctx.tree.isExpanded(node.id)) {
180
- ctx.tree.expand(node.id)
181
- } else {
182
- const first = node.getChildren()[0]
183
- if (first) {
184
- activeId = first.id
185
- }
186
- }
187
- ctx.events.emit({ name: 'visibility', keys: ctx.tree.expandedKeys() })
84
+ const emitDirection = (direction: 'up' | 'down' | 'left' | 'right', e: KeyboardEvent) => {
85
+ ctx.events.emit({
86
+ name: 'input:direction',
87
+ direction,
88
+ modifiers: getModifiers(e),
89
+ })
188
90
  }
189
- /**
190
- * 键盘按下事件处理
191
- * - 根据 keymap 映射调用对应处理函数
192
- * - 自动阻止默认行为以避免滚动
193
- */
91
+
194
92
  const onKeydown = (e: KeyboardEvent) => {
195
- const kExpand = keymap?.expand ?? 'ArrowRight'
196
- const kCollapse = keymap?.collapse ?? 'ArrowLeft'
197
- const kNext = keymap?.next ?? 'ArrowDown'
198
- const kPrev = keymap?.prev ?? 'ArrowUp'
199
- const key = e.key
200
- if (key === kPrev) {
201
- e.preventDefault()
202
- keyUp()
203
- return
204
- }
205
- if (key === kNext) {
206
- e.preventDefault()
207
- keyDown()
208
- return
209
- }
210
- if (key === kCollapse) {
211
- e.preventDefault()
212
- keyLeft()
213
- return
214
- }
215
- if (key === kExpand) {
216
- e.preventDefault()
217
- keyRight()
93
+ switch (e.key) {
94
+ case keys.up:
95
+ e.preventDefault()
96
+ emitDirection('up', e)
97
+ break
98
+ case keys.down:
99
+ e.preventDefault()
100
+ emitDirection('down', e)
101
+ break
102
+ case keys.left:
103
+ e.preventDefault()
104
+ emitDirection('left', e)
105
+ break
106
+ case keys.right:
107
+ e.preventDefault()
108
+ emitDirection('right', e)
109
+ break
218
110
  }
219
111
  }
112
+
113
+ const emitNodeClick = (nodeId: string, e: MouseEvent) => {
114
+ ctx.events.emit({
115
+ name: 'input:node-click',
116
+ nodeId,
117
+ modifiers: getModifiers(e),
118
+ })
119
+ }
120
+
220
121
  if (autoListenKeyboard && typeof document !== 'undefined') {
221
122
  setTimeout(() => {
222
- const root = resolveKeyboardTarget(keyboardTarget)
223
- if (root) {
224
- root.setAttribute('tabindex', '0')
225
- root?.focus?.()
123
+ const root = resolveTarget(keyboardTarget)
124
+ if (!root) {
125
+ return
226
126
  }
127
+
128
+ root.setAttribute('tabindex', '0')
129
+
227
130
  const handler = (e: KeyboardEvent) => {
228
131
  const active = document.activeElement
229
- // 仅当 activeElement 在树容器内时触发
230
- if (!root || root.contains(active)) {
132
+ if (root.contains(active)) {
231
133
  onKeydown(e)
232
134
  }
233
135
  }
136
+
234
137
  document.addEventListener('keydown', handler)
235
138
  })
236
139
  }
237
- return { toggleActive }
238
- },
239
- /**
240
- * 为节点扩展激活态判断方法
241
- * - isActive:是否为当前激活项
242
- * - toggleActive:切换当前节点的激活态
243
- */
244
- extendNode(node, ctx) {
245
- node.isActive = () => ctx.tree?.activeId === node.id
246
- node.toggleActive = (active?: boolean) => {
247
- ctx.tree.toggleActive(node.id, active)
140
+
141
+ return {
142
+ emitNodeClick,
248
143
  }
249
144
  },
250
145
  }
251
146
 
252
- export default keyboardNavigation
147
+ export default keyboardPlugin