layplux 2.0.0 → 2.0.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.
Files changed (91) hide show
  1. package/dist/cjs/components/center-view/index.cjs +18 -28
  2. package/dist/cjs/components/corner-glow/index.cjs +11 -28
  3. package/dist/cjs/components/dropdown/index.cjs +136 -130
  4. package/dist/cjs/components/icon/index.cjs +29 -51
  5. package/dist/cjs/components/index.cjs +24 -25
  6. package/dist/cjs/components/panel-view/index.cjs +111 -114
  7. package/dist/cjs/components/popup/index.cjs +166 -151
  8. package/dist/cjs/components/title/index.cjs +34 -47
  9. package/dist/cjs/components/tooltip/index.cjs +70 -61
  10. package/dist/cjs/components/widget/index.cjs +52 -72
  11. package/dist/cjs/index.cjs +13 -40
  12. package/dist/cjs/layout/glass-overlay.cjs +15 -28
  13. package/dist/cjs/layout/layered-manager.cjs +20 -29
  14. package/dist/cjs/layout/layplux.cjs +19 -32
  15. package/dist/cjs/layout/root-pane.cjs +20 -38
  16. package/dist/cjs/layout/skeleton/bottom-area.cjs +26 -43
  17. package/dist/cjs/layout/skeleton/bottom-left-area.cjs +12 -29
  18. package/dist/cjs/layout/skeleton/bottom-right-area.cjs +11 -28
  19. package/dist/cjs/layout/skeleton/center-area.cjs +278 -371
  20. package/dist/cjs/layout/skeleton/index.cjs +7 -24
  21. package/dist/cjs/layout/skeleton/left-bottom-area.cjs +12 -29
  22. package/dist/cjs/layout/skeleton/left-top-area.cjs +12 -29
  23. package/dist/cjs/layout/skeleton/right-bottom-area.cjs +11 -28
  24. package/dist/cjs/layout/skeleton/right-top-area.cjs +11 -28
  25. package/dist/cjs/layout/skeleton/skeleton.cjs +55 -60
  26. package/dist/cjs/layout/skeleton/top-area.cjs +26 -43
  27. package/dist/cjs/locales/en-US.cjs +11 -30
  28. package/dist/cjs/locales/index.cjs +12 -30
  29. package/dist/cjs/locales/zh-CN.cjs +11 -30
  30. package/dist/cjs/managers/area.cjs +12 -25
  31. package/dist/cjs/managers/index.cjs +12 -20
  32. package/dist/cjs/managers/pane.cjs +12 -26
  33. package/dist/cjs/managers/skeleton.cjs +112 -124
  34. package/dist/cjs/managers/theme.cjs +8 -29
  35. package/dist/cjs/managers/widget-container.cjs +31 -31
  36. package/dist/cjs/managers/widget.cjs +63 -50
  37. package/dist/cjs/types/config.cjs +2 -16
  38. package/dist/cjs/types/index.cjs +2 -18
  39. package/dist/cjs/types/locale.cjs +2 -16
  40. package/dist/cjs/utils/event-bus.cjs +53 -49
  41. package/dist/cjs/utils/focus-tracker.cjs +66 -42
  42. package/dist/cjs/utils/index.cjs +23 -31
  43. package/dist/cjs/utils/unique-id.cjs +5 -24
  44. package/dist/cjs/utils/vue.cjs +20 -30
  45. package/dist/esm/components/center-view/index.mjs +15 -7
  46. package/dist/esm/components/corner-glow/index.mjs +8 -7
  47. package/dist/esm/components/dropdown/index.mjs +117 -101
  48. package/dist/esm/components/icon/index.mjs +24 -30
  49. package/dist/esm/components/index.mjs +7 -8
  50. package/dist/esm/components/panel-view/index.mjs +107 -98
  51. package/dist/esm/components/popup/index.mjs +155 -130
  52. package/dist/esm/components/title/index.mjs +29 -24
  53. package/dist/esm/components/tooltip/index.mjs +67 -40
  54. package/dist/esm/components/widget/index.mjs +45 -48
  55. package/dist/esm/index.mjs +4 -10
  56. package/dist/esm/layout/glass-overlay.mjs +12 -7
  57. package/dist/esm/layout/layered-manager.mjs +17 -8
  58. package/dist/esm/layout/layplux.mjs +14 -11
  59. package/dist/esm/layout/root-pane.mjs +16 -16
  60. package/dist/esm/layout/skeleton/bottom-area.mjs +23 -22
  61. package/dist/esm/layout/skeleton/bottom-left-area.mjs +9 -8
  62. package/dist/esm/layout/skeleton/bottom-right-area.mjs +8 -7
  63. package/dist/esm/layout/skeleton/center-area.mjs +251 -333
  64. package/dist/esm/layout/skeleton/index.mjs +1 -4
  65. package/dist/esm/layout/skeleton/left-bottom-area.mjs +9 -8
  66. package/dist/esm/layout/skeleton/left-top-area.mjs +9 -8
  67. package/dist/esm/layout/skeleton/right-bottom-area.mjs +8 -7
  68. package/dist/esm/layout/skeleton/right-top-area.mjs +8 -7
  69. package/dist/esm/layout/skeleton/skeleton.mjs +52 -39
  70. package/dist/esm/layout/skeleton/top-area.mjs +23 -22
  71. package/dist/esm/locales/en-US.mjs +9 -10
  72. package/dist/esm/locales/index.mjs +7 -9
  73. package/dist/esm/locales/zh-CN.mjs +9 -10
  74. package/dist/esm/managers/area.mjs +10 -5
  75. package/dist/esm/managers/index.mjs +3 -3
  76. package/dist/esm/managers/pane.mjs +9 -5
  77. package/dist/esm/managers/skeleton.mjs +97 -95
  78. package/dist/esm/managers/theme.mjs +6 -9
  79. package/dist/esm/managers/widget-container.mjs +28 -10
  80. package/dist/esm/managers/widget.mjs +55 -25
  81. package/dist/esm/types/config.mjs +1 -0
  82. package/dist/esm/types/index.mjs +1 -1
  83. package/dist/esm/types/locale.mjs +1 -0
  84. package/dist/esm/utils/event-bus.mjs +46 -17
  85. package/dist/esm/utils/focus-tracker.mjs +63 -23
  86. package/dist/esm/utils/index.mjs +7 -10
  87. package/dist/esm/utils/unique-id.mjs +3 -4
  88. package/dist/esm/utils/vue.mjs +13 -5
  89. package/dist/types/managers/skeleton.d.ts.map +1 -1
  90. package/dist/umd/index.js +0 -2
  91. package/package.json +11 -8
@@ -1,113 +1,104 @@
1
- import { ref } from "vue";
2
- import { useArea } from "./area.mjs";
3
- import { isWidget, useWidget } from "./widget.mjs";
4
- import { useWidgetContainer } from "./widget-container.mjs";
5
- import {
6
- FocusTracker,
7
- createPluginEventBus,
8
- getBuiltInLocale
9
- } from "../utils/index.mjs";
10
- import { injectThemeCSS } from "./theme.mjs";
1
+ import { ref } from 'vue';
2
+ import { useArea } from './area.mjs';
3
+ import { isWidget, useWidget } from './widget.mjs';
4
+ import { useWidgetContainer } from './widget-container.mjs';
5
+ import { injectThemeCSS } from './theme.mjs';
6
+ import { createPluginEventBus } from '../utils/event-bus.mjs';
7
+ import { getBuiltInLocale } from '../locales/index.mjs';
8
+ import { FocusTracker } from '../utils/focus-tracker.mjs';
9
+
11
10
  function useSkeleton() {
12
11
  const widgets = [];
13
12
  const self = {};
14
- const containers = /* @__PURE__ */ new Map();
13
+ const containers = new Map();
15
14
  const focusTracker = new FocusTracker();
16
- const event = createPluginEventBus("skeleton");
17
- const locale = ref(getBuiltInLocale("zh-CN"));
15
+ const event = createPluginEventBus('skeleton');
16
+ const locale = ref(getBuiltInLocale('zh-CN'));
18
17
  function setLocale(name) {
19
18
  locale.value = getBuiltInLocale(name);
20
19
  }
21
- const theme = ref("system");
22
- const systemDark = ref(
23
- typeof window !== "undefined" ? window.matchMedia("(prefers-color-scheme: dark)").matches : false
24
- );
20
+ const theme = ref('system');
21
+ const systemDark = ref(typeof window !== 'undefined' ? window.matchMedia('(prefers-color-scheme: dark)').matches : false);
25
22
  function resolveTheme() {
26
- if (theme.value === "system") {
27
- return systemDark.value ? "dark" : "light";
23
+ if (theme.value === 'system') {
24
+ return systemDark.value ? 'dark' : 'light';
28
25
  }
29
26
  return theme.value;
30
27
  }
31
28
  function isDark() {
32
- return resolveTheme() === "dark";
29
+ return resolveTheme() === 'dark';
33
30
  }
34
31
  function setTheme(t) {
35
32
  theme.value = t;
36
33
  }
37
- const themeName = ref("default");
34
+ const themeName = ref('default');
38
35
  function setThemeName(name) {
39
36
  themeName.value = name;
40
37
  }
41
38
  function registerTheme(name, vars) {
42
39
  injectThemeCSS(name, vars);
43
40
  }
44
- if (typeof window !== "undefined") {
45
- window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => {
41
+
42
+ // 监听系统主题变化
43
+ if (typeof window !== 'undefined') {
44
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
46
45
  systemDark.value = e.matches;
47
46
  });
48
47
  }
49
- const topArea = useArea(
50
- {
51
- createContainer
52
- },
53
- "topArea",
54
- (config, container) => createWidget(config, container)
55
- );
56
- const bottomArea = useArea(
57
- {
58
- createContainer
59
- },
60
- "bottomArea",
61
- (config, container) => createWidget(config, container)
62
- );
63
- const leftTopArea = useArea(
64
- {
65
- createContainer
66
- },
67
- "leftTopArea",
68
- (config, container) => createWidget(config, container)
69
- );
70
- const leftBottomArea = useArea(
71
- {
72
- createContainer
73
- },
74
- "leftBottomArea",
75
- (config, container) => createWidget(config, container)
76
- );
77
- const rightTopArea = useArea(
78
- { createContainer },
79
- "rightTopArea",
80
- (config, container) => createWidget(config, container)
81
- );
82
- const rightBottomArea = useArea(
83
- { createContainer },
84
- "rightBottomArea",
85
- (config, container) => createWidget(config, container)
86
- );
87
- const bottomRightArea = useArea(
88
- { createContainer },
89
- "bottomRightArea",
90
- (config, container) => createWidget(config, container)
91
- );
92
- const bottomLeftArea = useArea(
93
- {
94
- createContainer
95
- },
96
- "bottomLeftArea",
97
- (config, container) => createWidget(config, container)
98
- );
99
- const centerArea = useArea(
100
- { createContainer },
101
- "centerArea",
102
- (config, container) => createWidget(config, container)
103
- );
48
+
49
+ // 顶部工具栏
50
+ const topArea = useArea({
51
+ createContainer
52
+ }, 'topArea', (config, container) => createWidget(config, container));
53
+
54
+ // 底部状态栏
55
+ const bottomArea = useArea({
56
+ createContainer
57
+ }, 'bottomArea', (config, container) => createWidget(config, container));
58
+
59
+ // 左侧顶部主区域
60
+ const leftTopArea = useArea({
61
+ createContainer
62
+ }, 'leftTopArea', (config, container) => createWidget(config, container));
63
+
64
+ // 左侧底部快捷区域
65
+ const leftBottomArea = useArea({
66
+ createContainer
67
+ }, 'leftBottomArea', (config, container) => createWidget(config, container));
68
+
69
+ // 右侧顶部主区域
70
+ const rightTopArea = useArea({
71
+ createContainer
72
+ }, 'rightTopArea', (config, container) => createWidget(config, container));
73
+
74
+ // 右侧底部区域
75
+ const rightBottomArea = useArea({
76
+ createContainer
77
+ }, 'rightBottomArea', (config, container) => createWidget(config, container));
78
+
79
+ // 右侧最底部快捷操作
80
+ const bottomRightArea = useArea({
81
+ createContainer
82
+ }, 'bottomRightArea', (config, container) => createWidget(config, container));
83
+
84
+ // 左侧最底部快捷操作(交互型)
85
+ const bottomLeftArea = useArea({
86
+ createContainer
87
+ }, 'bottomLeftArea', (config, container) => createWidget(config, container));
88
+
89
+ // 中心区域
90
+ const centerArea = useArea({
91
+ createContainer
92
+ }, 'centerArea', (config, container) => createWidget(config, container));
104
93
  function createWidget(config, container) {
105
94
  if (isWidget(config)) {
106
95
  return config;
107
96
  }
108
97
  const widget = useWidget(config, container, self);
109
98
  widgets.push(widget);
110
- event.emitGlobal("skeleton:widget-added", { widget });
99
+ event.emitGlobal('skeleton:widget-added', {
100
+ widget
101
+ });
111
102
  return widget;
112
103
  }
113
104
  const focusedId = ref(null);
@@ -120,34 +111,44 @@ function useSkeleton() {
120
111
  }
121
112
  function focus(id) {
122
113
  focusedId.value = id;
123
- event.emitGlobal("skeleton:focus-changed", { focusedId: id });
114
+ event.emitGlobal('skeleton:focus-changed', {
115
+ focusedId: id
116
+ });
124
117
  }
125
118
  function blur() {
126
119
  focusedId.value = null;
127
- event.emitGlobal("skeleton:focus-changed", { focusedId: null });
120
+ event.emitGlobal('skeleton:focus-changed', {
121
+ focusedId: null
122
+ });
128
123
  }
129
124
  function add(config, extraConfig) {
125
+ // TODO: 处理extraConfig
130
126
  if (extraConfig) {
131
- config = { ...config, ...extraConfig };
127
+ config = {
128
+ ...config,
129
+ ...extraConfig
130
+ };
132
131
  }
133
- const { area } = config;
134
- if (area === "topArea") {
132
+ const {
133
+ area
134
+ } = config;
135
+ if (area === 'topArea') {
135
136
  topArea.add(config);
136
- } else if (area === "bottomArea") {
137
+ } else if (area === 'bottomArea') {
137
138
  bottomArea.add(config);
138
- } else if (area === "leftTopArea") {
139
+ } else if (area === 'leftTopArea') {
139
140
  leftTopArea.add(config);
140
- } else if (area === "leftBottomArea") {
141
+ } else if (area === 'leftBottomArea') {
141
142
  leftBottomArea.add(config);
142
- } else if (area === "bottomLeftArea") {
143
+ } else if (area === 'bottomLeftArea') {
143
144
  bottomLeftArea.add(config);
144
- } else if (area === "rightTopArea") {
145
+ } else if (area === 'rightTopArea') {
145
146
  rightTopArea.add(config);
146
- } else if (area === "rightBottomArea") {
147
+ } else if (area === 'rightBottomArea') {
147
148
  rightBottomArea.add(config);
148
- } else if (area === "bottomRightArea") {
149
+ } else if (area === 'bottomRightArea') {
149
150
  bottomRightArea.add(config);
150
- } else if (area === "centerArea") {
151
+ } else if (area === 'centerArea') {
151
152
  centerArea.add(config);
152
153
  }
153
154
  }
@@ -156,6 +157,8 @@ function useSkeleton() {
156
157
  containers.set(name, container);
157
158
  return container;
158
159
  }
160
+
161
+ // 4. 填充 self 的真正属性
159
162
  Object.assign(self, {
160
163
  widgets,
161
164
  topArea,
@@ -187,6 +190,5 @@ function useSkeleton() {
187
190
  });
188
191
  return self;
189
192
  }
190
- export {
191
- useSkeleton
192
- };
193
+
194
+ export { useSkeleton };
@@ -1,17 +1,14 @@
1
+ /** 为指定 theme 注入 CSS 变量样式,重复注册会替换 */
1
2
  function injectThemeCSS(name, vars) {
2
3
  const styleId = `layplux-theme-${name}`;
3
4
  let styleEl = document.getElementById(styleId);
4
5
  if (!styleEl) {
5
- styleEl = document.createElement("style");
6
+ styleEl = document.createElement('style');
6
7
  styleEl.id = styleId;
7
8
  document.head.appendChild(styleEl);
8
9
  }
9
- const varLines = Object.entries(vars).map(([key, value]) => ` ${key}: ${value};`).join("\n");
10
- styleEl.textContent = `.layplux-root[data-theme='${name}'] {
11
- ${varLines}
10
+ const varLines = Object.entries(vars).map(([key, value]) => ` ${key}: ${value};`).join('\n');
11
+ styleEl.textContent = `.layplux-root[data-theme='${name}'] {\n${varLines}\n}\n`;
12
12
  }
13
- `;
14
- }
15
- export {
16
- injectThemeCSS
17
- };
13
+
14
+ export { injectThemeCSS };
@@ -1,8 +1,20 @@
1
- import { ref } from "vue";
1
+ import { ref } from 'vue';
2
+
3
+ /**
4
+ * handle 函数签名:把原始 item(config 或 widget)转换成 widget 实例。
5
+ * 第二个参数是所属 container 的引用,方便在创建 widget 时回引。
6
+ */
7
+
8
+ /**
9
+ * widget container 用于管理 widget 的添加、删除、获取等操作
10
+ * T 为 widget 的类型
11
+ * G 为 widgetconfig 的配置类型
12
+ */
2
13
  function useWidgetContainer(handle, skeleton) {
3
14
  const maps = {};
4
15
  const items = ref([]);
5
- const activeId = ref(null);
16
+ const activeId = ref(null); // ✅ 单一数据源
17
+
6
18
  const self = {
7
19
  items,
8
20
  activeId,
@@ -16,6 +28,7 @@ function useWidgetContainer(handle, skeleton) {
16
28
  toggleActive
17
29
  };
18
30
  function add(item) {
31
+ // 将config转换为widget,将创建widget的能力交给外部,并把 container 自身传出去
19
32
  const nItem = handle(item, self);
20
33
  const origin = get(nItem.name);
21
34
  if (origin === nItem) return origin;
@@ -43,23 +56,29 @@ function useWidgetContainer(handle, skeleton) {
43
56
  const i = items.value.indexOf(item);
44
57
  if (i > -1) items.value.splice(i, 1);
45
58
  delete maps[name];
46
- skeleton.event.emitGlobal("skeleton:widget-removed", { name });
59
+ skeleton.event.emitGlobal('skeleton:widget-removed', {
60
+ name
61
+ });
47
62
  return item;
48
63
  }
49
64
  function activate(id) {
50
65
  if (!maps[id]) return;
51
66
  activeId.value = id;
52
67
  skeleton.focus(id);
53
- maps[id].focusable.active();
54
- skeleton.event.emitGlobal(`widget:${id}:activated`, { widget: maps[id] });
68
+ maps[id].focusable.active(); // 面板激活 → 同步焦点栈
69
+ skeleton.event.emitGlobal(`widget:${id}:activated`, {
70
+ widget: maps[id]
71
+ });
55
72
  }
56
73
  function deactivate() {
57
74
  const current = activeId.value;
58
75
  activeId.value = null;
59
76
  skeleton.blur();
60
77
  if (current && maps[current]) {
61
- maps[current].focusable.suspense();
62
- skeleton.event.emitGlobal(`widget:${current}:deactivated`, { widget: maps[current] });
78
+ maps[current].focusable.suspense(); // 面板收起 → 从焦点栈移除
79
+ skeleton.event.emitGlobal(`widget:${current}:deactivated`, {
80
+ widget: maps[current]
81
+ });
63
82
  }
64
83
  }
65
84
  function toggleActive(id) {
@@ -71,6 +90,5 @@ function useWidgetContainer(handle, skeleton) {
71
90
  }
72
91
  return self;
73
92
  }
74
- export {
75
- useWidgetContainer
76
- };
93
+
94
+ export { useWidgetContainer };
@@ -1,50 +1,80 @@
1
- import { computed, h, watch } from "vue";
2
- import { createContent, uniqueId } from "../utils/index.mjs";
3
- import { WidgetTitleView, WidgetView } from "../components/index.mjs";
4
- import { usePane } from "./pane.mjs";
1
+ import { computed, h, watch } from 'vue';
2
+ import { usePane } from './pane.mjs';
3
+ import { uniqueId } from '../utils/unique-id.mjs';
4
+ import { createContent } from '../utils/vue.mjs';
5
+ import { WidgetView, WidgetTitleView } from '../components/widget/index.mjs';
6
+
5
7
  function useWidget(config, container, skeleton) {
6
- const { name, props, type } = config;
8
+ const {
9
+ name,
10
+ props,
11
+ type
12
+ } = config;
7
13
  const active = computed(() => container?.activeId.value === name);
8
14
  const focused = computed(() => skeleton?.focusedId.value === name);
9
15
  const id = uniqueId(type);
10
- const align = props?.align ?? "left";
16
+ const align = props?.align ?? 'left';
11
17
  const pane = usePane();
18
+
19
+ // ─── Focusable 注册 ──────────────────────────────────────────────────────
20
+ // range 初始为 () => false,PanelView 挂载后通过 focusable.setRange(el) 注入真实 DOM
12
21
  const focusable = skeleton.focusTracker.create({
13
- range: (e) => {
22
+ range: e => {
14
23
  const target = e.target;
15
24
  if (!target) {
16
25
  return false;
17
26
  }
27
+ // 当点击的是panel时,激活
18
28
  const el = document.getElementById(id);
19
29
  if (el?.contains(target)) {
20
30
  return true;
21
31
  }
22
- if (target.classList.contains("layplux-resize-handle")) {
32
+ // 当class中包含layplux-resize-handle则不失去焦点
33
+ if (target.classList.contains('layplux-resize-handle')) {
23
34
  return true;
24
35
  }
25
36
  return false;
26
37
  },
27
38
  onActive: () => {
28
39
  widget.container?.activate(name);
29
- skeleton.event?.emitGlobal(`widget:${name}:focus`, { widget });
40
+ skeleton.event?.emitGlobal(`widget:${name}:focus`, {
41
+ widget
42
+ });
30
43
  },
31
44
  onBlur: () => {
45
+ // 焦点离开 → 清除 focusedId
32
46
  skeleton.blur();
33
- skeleton.event?.emitGlobal(`widget:${name}:blur`, { widget });
34
- if (pane.viewMode.value === "DockUnpinned" || pane.viewMode.value === "Undock") {
47
+ skeleton.event?.emitGlobal(`widget:${name}:blur`, {
48
+ widget
49
+ });
50
+ // DockUnpinned:失焦自动收起
51
+ if (pane.viewMode.value === 'DockUnpinned' || pane.viewMode.value === 'Undock') {
35
52
  container?.deactivate();
36
53
  }
37
54
  }
38
55
  });
39
56
  function renderBody() {
40
- const { content, contentProps } = config;
41
- return createContent(content, { ...contentProps, config, event: widget.event });
57
+ const {
58
+ content,
59
+ contentProps
60
+ } = config;
61
+ return createContent(content, {
62
+ ...contentProps,
63
+ config,
64
+ event: widget.event
65
+ });
42
66
  }
43
67
  function renderContent() {
44
- return h(WidgetView, { key: id, widget });
68
+ return h(WidgetView, {
69
+ key: id,
70
+ widget
71
+ });
45
72
  }
46
73
  function renderTitle() {
47
- return h(WidgetTitleView, { key: id, widget });
74
+ return h(WidgetTitleView, {
75
+ key: id,
76
+ widget
77
+ });
48
78
  }
49
79
  const widget = {
50
80
  id,
@@ -64,20 +94,20 @@ function useWidget(config, container, skeleton) {
64
94
  renderTitle
65
95
  };
66
96
  props?.onInit?.(widget);
97
+
98
+ // Emit view-mode-changed events
67
99
  if (skeleton?.event) {
68
- watch(
69
- () => pane.viewMode.value,
70
- (mode) => {
71
- skeleton.event.emitGlobal(`widget:${name}:view-mode-changed`, { widget, mode });
72
- }
73
- );
100
+ watch(() => pane.viewMode.value, mode => {
101
+ skeleton.event.emitGlobal(`widget:${name}:view-mode-changed`, {
102
+ widget,
103
+ mode
104
+ });
105
+ });
74
106
  }
75
107
  return widget;
76
108
  }
77
109
  function isWidget(obj) {
78
110
  return obj && obj.isWidget;
79
111
  }
80
- export {
81
- isWidget,
82
- useWidget
83
- };
112
+
113
+ export { isWidget, useWidget };
@@ -0,0 +1 @@
1
+
@@ -1 +1 @@
1
- export * from "./config.mjs";
1
+
@@ -0,0 +1 @@
1
+
@@ -1,27 +1,57 @@
1
- import EventEmitter2, {} from "eventemitter2";
1
+ import EventEmitter2 from 'eventemitter2';
2
+
3
+ // =================================================================
4
+ // event-bus.ts — 基于 EventEmitter2 的插件事件总线
5
+ //
6
+ // 利用 EventEmitter2 的三个核心特性:
7
+ // 1. wildcard: true → 支持 'terminal:*' 订阅整个命名空间
8
+ // 2. delimiter: ':' → 'namespace:event' 格式的原生支持
9
+ // 3. listener.off() → 订阅返回 Listener 对象,无需手动传 handler 引用
10
+ // =================================================================
11
+
12
+
13
+ // ── 全局总线单例(所有插件共享,跨插件通信用)──────────────────────
14
+ //
15
+ // wildcard + delimiter 让命名空间隔离变成原生能力:
16
+ // - 插件 A 订阅 'terminal:*' 可以收到所有 terminal 前缀的事件
17
+ // - 全局订阅 '**' 可以监听所有事件(调试用)
18
+ // - maxListeners 设大一点,避免大量插件时出现警告
19
+
2
20
  const globalEmitter = new EventEmitter2({
3
21
  wildcard: true,
4
22
  // 开启通配符
5
- delimiter: ":",
23
+ delimiter: ':',
6
24
  // 命名空间分隔符,与原版 createModuleEventBus 保持一致
7
25
  newListener: false,
8
26
  // 不触发 newListener 事件,减少不必要开销
9
27
  maxListeners: 200,
10
28
  // 对标原版 createModuleEventBus(pluginName, 200) 的容量参数
11
- verboseMemoryLeak: true
12
- // 超出 maxListeners 时打印详细的内存泄漏警告
29
+ verboseMemoryLeak: true // 超出 maxListeners 时打印详细的内存泄漏警告
13
30
  });
31
+ // ── 插件私有事件总线工厂函数 ──────────────────────────────────────
32
+ //
33
+ // 每个插件获得一个独立的 emitter 实例(私有事件不污染全局),
34
+ // 同时持有 globalEmitter 引用用于跨插件通信。
35
+ //
36
+ // 命名规则:
37
+ // ctx.event.emit('data-loaded') → 实际发出 'terminal:data-loaded'(自动加前缀)
38
+ // ctx.event.on('data-loaded', fn) → 订阅 'terminal:data-loaded'
39
+ // ctx.event.onGlobal('layout:*', fn) → 订阅全局总线上所有 layout 事件(wildcard)
40
+ // ctx.event.emitGlobal('layout:ready')→ 向全局总线发出事件,所有插件都能收到
41
+
14
42
  function createPluginEventBus(namespace) {
43
+ // 每个插件自己的私有 emitter(同样开启 wildcard)
15
44
  const privateEmitter = new EventEmitter2({
16
45
  wildcard: true,
17
- delimiter: ":",
46
+ delimiter: ':',
18
47
  newListener: false,
19
48
  maxListeners: 200,
20
49
  verboseMemoryLeak: true
21
50
  });
22
- const prefixed = (event) => `${namespace}:${event}`;
51
+ const prefixed = event => `${namespace}:${event}`;
23
52
  return {
24
53
  // ── 私有事件(自动加 namespace 前缀)──────────────────────────
54
+
25
55
  emit(event, payload) {
26
56
  privateEmitter.emit(prefixed(event), payload);
27
57
  },
@@ -31,11 +61,9 @@ function createPluginEventBus(namespace) {
31
61
  * 无需调用方保存 handler 引用。
32
62
  */
33
63
  on(event, handler) {
34
- const listener = privateEmitter.on(
35
- prefixed(event),
36
- handler,
37
- { objectify: true }
38
- // 返回 Listener 对象而非 emitter 本身
64
+ const listener = privateEmitter.on(prefixed(event), handler, {
65
+ objectify: true
66
+ } // 返回 Listener 对象而非 emitter 本身
39
67
  );
40
68
  return () => listener.off();
41
69
  },
@@ -52,6 +80,7 @@ function createPluginEventBus(namespace) {
52
80
  return () => listener.off();
53
81
  },
54
82
  // ── 全局事件(跨插件通信,走 globalEmitter)────────────────────
83
+
55
84
  emitGlobal(event, payload) {
56
85
  globalEmitter.emit(event, payload);
57
86
  },
@@ -95,6 +124,7 @@ function createPluginEventBus(namespace) {
95
124
  });
96
125
  },
97
126
  // ── 清理(插件 destroy 时调用)─────────────────────────────────
127
+
98
128
  /**
99
129
  * 移除该插件私有 emitter 上的所有监听器。
100
130
  * 全局监听器需要单独 offGlobal(插件应在 teardown 中手动清理)。
@@ -104,6 +134,9 @@ function createPluginEventBus(namespace) {
104
134
  }
105
135
  };
106
136
  }
137
+
138
+ // ── 系统级全局事件工具函数(PluginManager 使用)────────────────────
139
+
107
140
  function emitSystemEvent(event, payload) {
108
141
  globalEmitter.emit(event, payload);
109
142
  }
@@ -116,9 +149,5 @@ function onSystemEvent(event, handler) {
116
149
  function getGlobalEmitter() {
117
150
  return globalEmitter;
118
151
  }
119
- export {
120
- createPluginEventBus,
121
- emitSystemEvent,
122
- getGlobalEmitter,
123
- onSystemEvent
124
- };
152
+
153
+ export { createPluginEventBus, emitSystemEvent, getGlobalEmitter, onSystemEvent };