@tmagic/editor 1.3.0-alpha.9 → 1.3.0-beta.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 (139) hide show
  1. package/dist/style.css +106 -219
  2. package/dist/tmagic-editor.js +6253 -5042
  3. package/dist/tmagic-editor.js.map +1 -1
  4. package/dist/tmagic-editor.umd.cjs +6280 -5055
  5. package/dist/tmagic-editor.umd.cjs.map +1 -1
  6. package/package.json +15 -15
  7. package/src/Editor.vue +4 -5
  8. package/src/components/CodeBlockEditor.vue +266 -0
  9. package/src/components/CodeParams.vue +60 -0
  10. package/src/components/ContentMenu.vue +97 -43
  11. package/src/components/Resizer.vue +8 -30
  12. package/src/components/SplitView.vue +3 -2
  13. package/src/editorProps.ts +30 -5
  14. package/src/fields/Code.vue +23 -19
  15. package/src/fields/CodeLink.vue +6 -7
  16. package/src/fields/CodeSelect.vue +12 -9
  17. package/src/fields/CodeSelectCol.vue +70 -81
  18. package/src/fields/DataSourceFieldSelect.vue +47 -0
  19. package/src/fields/DataSourceFields.vue +38 -22
  20. package/src/fields/DataSourceInput.vue +21 -58
  21. package/src/fields/DataSourceMethodSelect.vue +136 -0
  22. package/src/fields/DataSourceMethods.vue +104 -0
  23. package/src/fields/DataSourceSelect.vue +5 -12
  24. package/src/fields/EventSelect.vue +53 -19
  25. package/src/fields/KeyValue.vue +9 -11
  26. package/src/fields/UISelect.vue +54 -14
  27. package/src/hooks/index.ts +21 -0
  28. package/src/hooks/use-code-block-edit.ts +84 -0
  29. package/src/hooks/use-data-source-method.ts +100 -0
  30. package/src/hooks/use-getso.ts +35 -0
  31. package/src/{utils/stage.ts → hooks/use-stage.ts} +4 -7
  32. package/src/icons/CenterIcon.vue +13 -0
  33. package/src/icons/CodeIcon.vue +0 -2
  34. package/src/icons/FolderMinusIcon.vue +22 -0
  35. package/src/icons/PinIcon.vue +25 -0
  36. package/src/icons/PinnedIcon.vue +25 -0
  37. package/src/index.ts +14 -0
  38. package/src/initService.ts +69 -16
  39. package/src/layouts/CodeEditor.vue +35 -3
  40. package/src/layouts/Framework.vue +7 -3
  41. package/src/layouts/PropsPanel.vue +8 -3
  42. package/src/layouts/sidebar/ComponentListPanel.vue +4 -6
  43. package/src/layouts/sidebar/LayerMenu.vue +20 -21
  44. package/src/layouts/sidebar/LayerPanel.vue +12 -2
  45. package/src/layouts/sidebar/Sidebar.vue +22 -36
  46. package/src/layouts/sidebar/code-block/CodeBlockList.vue +81 -119
  47. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +64 -0
  48. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +27 -47
  49. package/src/layouts/sidebar/data-source/DataSourceList.vue +145 -0
  50. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +58 -85
  51. package/src/layouts/workspace/Workspace.vue +1 -1
  52. package/src/layouts/workspace/viewer/NodeListMenu.vue +159 -0
  53. package/src/layouts/workspace/viewer/NodeListMenuTitle.vue +68 -0
  54. package/src/layouts/workspace/{Stage.vue → viewer/Stage.vue} +5 -2
  55. package/src/layouts/workspace/{ViewerMenu.vue → viewer/ViewerMenu.vue} +10 -35
  56. package/src/services/codeBlock.ts +18 -71
  57. package/src/services/dataSource.ts +16 -1
  58. package/src/services/dep.ts +22 -20
  59. package/src/services/editor.ts +46 -18
  60. package/src/services/props.ts +2 -2
  61. package/src/theme/code-block.scss +5 -117
  62. package/src/theme/code-editor.scss +27 -2
  63. package/src/theme/content-menu.scss +26 -2
  64. package/src/theme/data-source-methods.scss +13 -0
  65. package/src/theme/data-source.scss +11 -0
  66. package/src/theme/event.scss +15 -12
  67. package/src/theme/framework.scss +0 -3
  68. package/src/theme/index.scss +8 -0
  69. package/src/theme/layout.scss +4 -0
  70. package/src/theme/sidebar.scss +18 -60
  71. package/src/theme/theme.scss +1 -0
  72. package/src/type.ts +49 -7
  73. package/src/utils/content-menu.ts +92 -0
  74. package/src/utils/data-source/formConfigs/base.ts +28 -30
  75. package/src/utils/data-source/formConfigs/http.ts +9 -0
  76. package/src/utils/data-source/index.ts +65 -2
  77. package/src/utils/dep.ts +33 -7
  78. package/src/utils/index.ts +1 -1
  79. package/src/utils/props.ts +308 -189
  80. package/src/utils/undo-redo.ts +1 -1
  81. package/types/Editor.vue.d.ts +30 -6
  82. package/types/components/CodeBlockEditor.vue.d.ts +25 -0
  83. package/types/components/CodeParams.vue.d.ts +26 -0
  84. package/types/components/ContentMenu.vue.d.ts +27 -8
  85. package/types/components/Resizer.vue.d.ts +1 -3
  86. package/types/components/ToolButton.vue.d.ts +3 -3
  87. package/types/editorProps.d.ts +19 -5
  88. package/types/fields/Code.vue.d.ts +24 -19
  89. package/types/fields/CodeLink.vue.d.ts +12 -21
  90. package/types/fields/CodeSelect.vue.d.ts +6 -17
  91. package/types/fields/CodeSelectCol.vue.d.ts +10 -14
  92. package/types/fields/DataSourceFieldSelect.vue.d.ts +29 -0
  93. package/types/fields/DataSourceFields.vue.d.ts +7 -18
  94. package/types/fields/DataSourceInput.vue.d.ts +10 -30
  95. package/types/fields/DataSourceMethodSelect.vue.d.ts +29 -0
  96. package/types/fields/DataSourceMethods.vue.d.ts +32 -0
  97. package/types/fields/DataSourceSelect.vue.d.ts +13 -29
  98. package/types/fields/EventSelect.vue.d.ts +2 -13
  99. package/types/fields/KeyValue.vue.d.ts +9 -24
  100. package/types/fields/UISelect.vue.d.ts +2 -11
  101. package/types/hooks/index.d.ts +3 -0
  102. package/types/{components/FunctionEditor.vue.d.ts → hooks/use-code-block-edit.d.ts} +36 -119
  103. package/types/hooks/use-data-source-method.d.ts +130 -0
  104. package/types/hooks/use-getso.d.ts +5 -0
  105. package/types/icons/CenterIcon.vue.d.ts +2 -0
  106. package/types/icons/FolderMinusIcon.vue.d.ts +2 -0
  107. package/types/icons/PinIcon.vue.d.ts +2 -0
  108. package/types/icons/PinnedIcon.vue.d.ts +2 -0
  109. package/types/index.d.ts +7 -0
  110. package/types/initService.d.ts +2 -2
  111. package/types/layouts/CodeEditor.vue.d.ts +7 -4
  112. package/types/layouts/PropsPanel.vue.d.ts +274 -2
  113. package/types/layouts/sidebar/Sidebar.vue.d.ts +0 -3
  114. package/types/layouts/sidebar/code-block/CodeBlockList.vue.d.ts +4 -9
  115. package/types/layouts/sidebar/code-block/{CodeBlockEditor.vue.d.ts → CodeBlockListPanel.vue.d.ts} +9 -5
  116. package/types/layouts/sidebar/data-source/DataSourceConfigPanel.vue.d.ts +3 -1
  117. package/types/layouts/sidebar/data-source/DataSourceList.vue.d.ts +14 -0
  118. package/types/layouts/sidebar/data-source/DataSourceListPanel.vue.d.ts +1 -11
  119. package/types/layouts/workspace/viewer/NodeListMenu.vue.d.ts +15 -0
  120. package/types/layouts/workspace/viewer/NodeListMenuTitle.vue.d.ts +15 -0
  121. package/types/layouts/workspace/{Stage.vue.d.ts → viewer/Stage.vue.d.ts} +1 -1
  122. package/types/layouts/workspace/{ViewerMenu.vue.d.ts → viewer/ViewerMenu.vue.d.ts} +1 -1
  123. package/types/services/codeBlock.d.ts +6 -37
  124. package/types/services/dataSource.d.ts +7 -1
  125. package/types/services/dep.d.ts +12 -21
  126. package/types/services/editor.d.ts +1 -1
  127. package/types/services/props.d.ts +10 -115
  128. package/types/type.d.ts +44 -7
  129. package/types/utils/content-menu.d.ts +7 -0
  130. package/types/utils/data-source/formConfigs/base.d.ts +1 -2
  131. package/types/utils/data-source/index.d.ts +5 -0
  132. package/types/utils/dep.d.ts +4 -2
  133. package/types/utils/index.d.ts +1 -1
  134. package/types/utils/props.d.ts +13 -102
  135. package/src/components/CodeDraftEditor.vue +0 -148
  136. package/src/components/FunctionEditor.vue +0 -197
  137. package/src/layouts/sidebar/code-block/CodeBlockEditor.vue +0 -100
  138. package/types/components/CodeDraftEditor.vue.d.ts +0 -61
  139. /package/types/{utils/stage.d.ts → hooks/use-stage.d.ts} +0 -0
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.3.0-alpha.9",
2
+ "version": "1.3.0-beta.1",
3
3
  "name": "@tmagic/editor",
4
4
  "type": "module",
5
5
  "sideEffects": [
@@ -46,27 +46,27 @@
46
46
  "dependencies": {
47
47
  "@babel/core": "^7.18.0",
48
48
  "@element-plus/icons-vue": "^2.0.9",
49
- "@tmagic/core": "1.3.0-alpha.9",
50
- "@tmagic/design": "1.3.0-alpha.9",
51
- "@tmagic/form": "1.3.0-alpha.9",
52
- "@tmagic/schema": "1.3.0-alpha.9",
53
- "@tmagic/stage": "1.3.0-alpha.9",
54
- "@tmagic/table": "1.3.0-alpha.9",
55
- "@tmagic/utils": "1.3.0-alpha.9",
49
+ "@tmagic/core": "1.3.0-beta.1",
50
+ "@tmagic/design": "1.3.0-beta.1",
51
+ "@tmagic/form": "1.3.0-beta.1",
52
+ "@tmagic/schema": "1.3.0-beta.1",
53
+ "@tmagic/stage": "1.3.0-beta.1",
54
+ "@tmagic/table": "1.3.0-beta.1",
55
+ "@tmagic/utils": "1.3.0-beta.1",
56
56
  "buffer": "^6.0.3",
57
57
  "color": "^3.1.3",
58
58
  "events": "^3.3.0",
59
- "gesto": "^1.7.0",
60
- "keycon": "^1.1.2",
59
+ "gesto": "^1.19.1",
60
+ "keycon": "^1.4.0",
61
61
  "lodash-es": "^4.17.21",
62
- "monaco-editor": "^0.39.0",
62
+ "monaco-editor": "^0.41.0",
63
63
  "serialize-javascript": "^6.0.0",
64
64
  "vue": "^3.3.4"
65
65
  },
66
66
  "peerDependencies": {
67
- "@tmagic/design": "1.3.0-alpha.9",
68
- "@tmagic/form": "1.3.0-alpha.9",
69
- "monaco-editor": "^0.39.0",
67
+ "@tmagic/design": "1.3.0-beta.1",
68
+ "@tmagic/form": "1.3.0-beta.1",
69
+ "monaco-editor": "^0.41.0",
70
70
  "vue": "^3.3.4"
71
71
  },
72
72
  "devDependencies": {
@@ -81,7 +81,7 @@
81
81
  "sass": "^1.35.1",
82
82
  "tsc-alias": "^1.8.5",
83
83
  "typescript": "^5.0.4",
84
- "vite": "^4.3.8",
84
+ "vite": "^4.4.4",
85
85
  "vue-tsc": "^1.6.5"
86
86
  }
87
87
  }
package/src/Editor.vue CHANGED
@@ -40,10 +40,6 @@
40
40
  <template #code-block-panel-tool="{ id, data }">
41
41
  <slot name="code-block-panel-tool" :id="id" :data="data"></slot>
42
42
  </template>
43
-
44
- <template #code-block-edit-panel-header="{ id }">
45
- <slot name="code-block-edit-panel-header" :id="id"></slot>
46
- </template>
47
43
  </Sidebar>
48
44
  </slot>
49
45
  </template>
@@ -61,7 +57,10 @@
61
57
 
62
58
  <template #props-panel>
63
59
  <slot name="props-panel">
64
- <PropsPanel @mounted="(instance: any) => $emit('props-panel-mounted', instance)">
60
+ <PropsPanel
61
+ :extend-state="extendFormState"
62
+ @mounted="(instance: any) => $emit('props-panel-mounted', instance)"
63
+ >
65
64
  <template #props-panel-header>
66
65
  <slot name="props-panel-header"></slot>
67
66
  </template>
@@ -0,0 +1,266 @@
1
+ <template>
2
+ <MFormDrawer
3
+ class="m-editor-code-block-editor"
4
+ ref="fomDrawer"
5
+ label-width="80px"
6
+ :close-on-press-escape="false"
7
+ :title="content.name"
8
+ :width="size"
9
+ :config="functionConfig"
10
+ :values="content"
11
+ :disabled="disabled"
12
+ :before-close="beforeClose"
13
+ @change="changeHandler"
14
+ @submit="submitForm"
15
+ @error="errorHandler"
16
+ @open="openHandler"
17
+ @closed="closedHandler"
18
+ >
19
+ <template #left>
20
+ <TMagicButton type="primary" text @click="difVisible = true">查看修改</TMagicButton>
21
+ </template>
22
+ </MFormDrawer>
23
+
24
+ <TMagicDialog v-model="difVisible" title="查看修改" fullscreen>
25
+ <div style="display: flex; margin-bottom: 10px">
26
+ <div style="flex: 1"><TMagicTag size="small" type="info">修改前</TMagicTag></div>
27
+ <div style="flex: 1"><TMagicTag size="small" type="success">修改后</TMagicTag></div>
28
+ </div>
29
+
30
+ <CodeEditor
31
+ v-if="difVisible"
32
+ ref="magicVsEditor"
33
+ type="diff"
34
+ language="json"
35
+ :initValues="content.content"
36
+ :modifiedValues="fomDrawer?.form?.values.content"
37
+ :style="`height: ${height - 200}px`"
38
+ ></CodeEditor>
39
+
40
+ <template #footer>
41
+ <span class="dialog-footer">
42
+ <TMagicButton size="small" @click="difVisible = false">取消</TMagicButton>
43
+ <TMagicButton size="small" type="primary" @click="diffChange">确定</TMagicButton>
44
+ </span>
45
+ </template>
46
+ </TMagicDialog>
47
+ </template>
48
+
49
+ <script lang="ts" setup>
50
+ import { computed, inject, onUnmounted, ref } from 'vue';
51
+
52
+ import { TMagicButton, TMagicDialog, tMagicMessage, tMagicMessageBox, TMagicTag } from '@tmagic/design';
53
+ import { ColumnConfig, FormConfig, FormState, MFormDrawer } from '@tmagic/form';
54
+ import type { CodeBlockContent } from '@tmagic/schema';
55
+
56
+ import CodeEditor from '@editor/layouts/CodeEditor.vue';
57
+ import type { Services } from '@editor/type';
58
+ import { getConfig } from '@editor/utils/config';
59
+
60
+ defineOptions({
61
+ name: 'MEditorCodeBlockEditor',
62
+ });
63
+
64
+ const props = defineProps<{
65
+ content: CodeBlockContent;
66
+ disabled?: boolean;
67
+ isDataSource?: boolean;
68
+ dataSourceType?: string;
69
+ }>();
70
+
71
+ const emit = defineEmits<{
72
+ submit: [values: CodeBlockContent];
73
+ }>();
74
+
75
+ const services = inject<Services>('services');
76
+
77
+ const difVisible = ref(false);
78
+ const height = ref(globalThis.innerHeight);
79
+
80
+ const windowReizehandler = () => {
81
+ height.value = globalThis.innerHeight;
82
+ };
83
+
84
+ globalThis.addEventListener('resize', windowReizehandler);
85
+
86
+ onUnmounted(() => {
87
+ globalThis.removeEventListener('resize', windowReizehandler);
88
+ });
89
+
90
+ const magicVsEditor = ref<InstanceType<typeof CodeEditor>>();
91
+
92
+ const diffChange = () => {
93
+ if (!magicVsEditor.value || !fomDrawer.value?.form) {
94
+ return;
95
+ }
96
+
97
+ fomDrawer.value.form.values.content = magicVsEditor.value.getEditorValue();
98
+
99
+ difVisible.value = false;
100
+ };
101
+
102
+ const columnWidth = computed(() => services?.uiService.get('columnWidth'));
103
+ const size = computed(() =>
104
+ columnWidth.value ? columnWidth.value.center + columnWidth.value.right - (props.isDataSource ? 100 : 0) : 600,
105
+ );
106
+
107
+ const codeEditorHeight = ref('600px');
108
+
109
+ const defaultParamColConfig: ColumnConfig = {
110
+ type: 'row',
111
+ label: '参数类型',
112
+ items: [
113
+ {
114
+ text: '参数类型',
115
+ labelWidth: '70px',
116
+ type: 'select',
117
+ name: 'type',
118
+ options: [
119
+ {
120
+ text: '数字',
121
+ label: '数字',
122
+ value: 'number',
123
+ },
124
+ {
125
+ text: '字符串',
126
+ label: '字符串',
127
+ value: 'text',
128
+ },
129
+ {
130
+ text: '组件',
131
+ label: '组件',
132
+ value: 'ui-select',
133
+ },
134
+ ],
135
+ },
136
+ ],
137
+ };
138
+
139
+ const functionConfig = computed<FormConfig>(() => [
140
+ {
141
+ text: '名称',
142
+ name: 'name',
143
+ rules: [{ required: true, message: '请输入名称', trigger: 'blur' }],
144
+ },
145
+ {
146
+ text: '描述',
147
+ name: 'desc',
148
+ },
149
+ {
150
+ text: '执行时机',
151
+ name: 'timing',
152
+ type: 'select',
153
+ options: () => {
154
+ const options = [
155
+ { text: '初始化前', value: 'beforeInit' },
156
+ { text: '初始化后', value: 'afterInit' },
157
+ ];
158
+ if (props.dataSourceType !== 'base') {
159
+ options.push({ text: '请求前', value: 'beforeRequest' });
160
+ options.push({ text: '请求后', value: 'afterRequest' });
161
+ }
162
+ return options;
163
+ },
164
+ display: () => props.isDataSource,
165
+ },
166
+ {
167
+ type: 'table',
168
+ border: true,
169
+ text: '参数',
170
+ enableFullscreen: false,
171
+ enableToggleMode: false,
172
+ name: 'params',
173
+ dropSort: false,
174
+ items: [
175
+ {
176
+ type: 'text',
177
+ label: '参数名',
178
+ name: 'name',
179
+ },
180
+ {
181
+ type: 'text',
182
+ label: '描述',
183
+ name: 'extra',
184
+ },
185
+ services?.codeBlockService.getParamsColConfig() || defaultParamColConfig,
186
+ ],
187
+ },
188
+ {
189
+ name: 'content',
190
+ type: 'vs-code',
191
+ options: inject('codeOptions', {}),
192
+ height: codeEditorHeight.value,
193
+ onChange: (formState: FormState | undefined, code: string) => {
194
+ try {
195
+ // 检测js代码是否存在语法错误
196
+ getConfig('parseDSL')(code);
197
+
198
+ return code;
199
+ } catch (error: any) {
200
+ tMagicMessage.error(error.message);
201
+
202
+ throw error;
203
+ }
204
+ },
205
+ },
206
+ ]);
207
+
208
+ const submitForm = (values: CodeBlockContent) => {
209
+ emit('submit', values);
210
+ };
211
+
212
+ const errorHandler = (error: any) => {
213
+ tMagicMessage.error(error.message);
214
+ };
215
+
216
+ const fomDrawer = ref<InstanceType<typeof MFormDrawer>>();
217
+
218
+ const openHandler = () => {
219
+ setTimeout(() => {
220
+ if (fomDrawer.value) {
221
+ const height = fomDrawer.value?.bodyHeight - 348 - (props.isDataSource ? 50 : 0);
222
+ codeEditorHeight.value = `${height > 100 ? height : 600}px`;
223
+ }
224
+ });
225
+ };
226
+
227
+ const changedValue = ref<CodeBlockContent>();
228
+ const changeHandler = (values: CodeBlockContent) => {
229
+ changedValue.value = values;
230
+ };
231
+
232
+ const beforeClose = (done: (cancel?: boolean) => void) => {
233
+ if (!changedValue.value) {
234
+ done();
235
+ return;
236
+ }
237
+ tMagicMessageBox
238
+ .confirm('当前代码块已修改,是否保存?', '提示', {
239
+ confirmButtonText: '保存并关闭',
240
+ cancelButtonText: '不保存并关闭',
241
+ type: 'warning',
242
+ distinguishCancelAndClose: true,
243
+ })
244
+ .then(() => {
245
+ changedValue.value && submitForm(changedValue.value);
246
+ done();
247
+ })
248
+ .catch((action: string) => {
249
+ done(action === 'close');
250
+ });
251
+ };
252
+
253
+ const closedHandler = () => {
254
+ changedValue.value = undefined;
255
+ };
256
+
257
+ defineExpose({
258
+ show() {
259
+ fomDrawer.value?.show();
260
+ },
261
+
262
+ hide() {
263
+ fomDrawer.value?.hide();
264
+ },
265
+ });
266
+ </script>
@@ -0,0 +1,60 @@
1
+ <template>
2
+ <MForm
3
+ ref="form"
4
+ :config="codeParamsConfig"
5
+ :init-values="model"
6
+ :disabled="disabled"
7
+ :size="size"
8
+ :watch-props="false"
9
+ @change="onParamsChangeHandler"
10
+ ></MForm>
11
+ </template>
12
+
13
+ <script lang="ts" setup>
14
+ import { computed, ref } from 'vue';
15
+
16
+ import { FormConfig, MForm } from '@tmagic/form';
17
+
18
+ import type { CodeParamStatement } from '@editor/type';
19
+ import { error } from '@editor/utils';
20
+
21
+ defineOptions({
22
+ name: 'MEditorCodeParams',
23
+ });
24
+
25
+ const props = defineProps<{
26
+ model: any;
27
+ size?: 'small' | 'default' | 'large';
28
+ disabled?: boolean;
29
+ name: string;
30
+ paramsConfig: CodeParamStatement[];
31
+ }>();
32
+
33
+ const emit = defineEmits(['change']);
34
+
35
+ const form = ref<InstanceType<typeof MForm>>();
36
+
37
+ const getFormConfig = (items: FormConfig = []) => [
38
+ {
39
+ type: 'fieldset',
40
+ items,
41
+ legend: '参数',
42
+ labelWidth: '70px',
43
+ name: props.name,
44
+ },
45
+ ];
46
+
47
+ const codeParamsConfig = computed(() => getFormConfig(props.paramsConfig));
48
+
49
+ /**
50
+ * 参数值修改更新
51
+ */
52
+ const onParamsChangeHandler = async () => {
53
+ try {
54
+ const value = await form.value?.submitForm(true);
55
+ emit('change', value);
56
+ } catch (e) {
57
+ error(e);
58
+ }
59
+ };
60
+ </script>
@@ -1,30 +1,42 @@
1
1
  <template>
2
- <div v-if="menuData.length" v-show="visible" class="magic-editor-content-menu" ref="menu" :style="menuStyle">
3
- <div>
4
- <ToolButton
5
- v-for="(item, index) in menuData"
6
- event-type="mouseup"
7
- :data="item"
8
- :key="index"
9
- @mouseup="hide"
10
- @mouseenter="showSubMenu(item)"
11
- ></ToolButton>
2
+ <transition name="fade">
3
+ <div
4
+ v-show="visible"
5
+ class="magic-editor-content-menu"
6
+ ref="menu"
7
+ :style="menuStyle"
8
+ @mouseenter="mouseenterHandler()"
9
+ >
10
+ <slot name="title"></slot>
11
+ <div>
12
+ <ToolButton
13
+ v-for="(item, index) in menuData"
14
+ event-type="mouseup"
15
+ ref="buttons"
16
+ :class="{ active: active && item.id === active }"
17
+ :data="item"
18
+ :key="index"
19
+ @mouseup="clickHandler"
20
+ @mouseenter="showSubMenu(item, index)"
21
+ ></ToolButton>
22
+ </div>
23
+ <teleport to="body">
24
+ <content-menu
25
+ v-if="subMenuData.length"
26
+ class="sub-menu"
27
+ ref="subMenu"
28
+ :active="active"
29
+ :menu-data="subMenuData"
30
+ :is-sub-menu="true"
31
+ @hide="hide"
32
+ ></content-menu>
33
+ </teleport>
12
34
  </div>
13
- <teleport to="body">
14
- <content-menu
15
- v-if="subMenuData.length"
16
- class="sub-menu"
17
- ref="subMenu"
18
- :menu-data="subMenuData"
19
- :is-sub-menu="true"
20
- @hide="hide"
21
- ></content-menu>
22
- </teleport>
23
- </div>
35
+ </transition>
24
36
  </template>
25
37
 
26
38
  <script lang="ts" setup>
27
- import { nextTick, onMounted, onUnmounted, ref } from 'vue';
39
+ import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
28
40
 
29
41
  import { MenuButton, MenuComponent } from '@editor/type';
30
42
 
@@ -38,24 +50,38 @@ const props = withDefaults(
38
50
  defineProps<{
39
51
  menuData?: (MenuButton | MenuComponent)[];
40
52
  isSubMenu?: boolean;
53
+ active?: string | number;
54
+ autoHide?: boolean;
41
55
  }>(),
42
56
  {
43
57
  menuData: () => [],
44
58
  isSubMenu: false,
59
+ autoHide: true,
45
60
  },
46
61
  );
47
62
 
48
- const emit = defineEmits(['hide', 'show']);
63
+ const emit = defineEmits<{
64
+ hide: [];
65
+ show: [];
66
+ mouseenter: [];
67
+ }>();
49
68
 
50
69
  const menu = ref<HTMLDivElement>();
70
+ const buttons = ref<InstanceType<typeof ToolButton>[]>();
51
71
  const subMenu = ref<any>();
52
72
  const visible = ref(false);
53
73
  const subMenuData = ref<(MenuButton | MenuComponent)[]>([]);
54
- const menuStyle = ref({
55
- left: '0',
56
- top: '0',
74
+
75
+ const menuPosition = ref({
76
+ left: 0,
77
+ top: 0,
57
78
  });
58
79
 
80
+ const menuStyle = computed(() => ({
81
+ top: `${menuPosition.value.top}px`,
82
+ left: `${menuPosition.value.left}px`,
83
+ }));
84
+
59
85
  const contains = (el: HTMLElement) => menu.value?.contains(el) || subMenu.value?.contains(el);
60
86
 
61
87
  const hide = () => {
@@ -67,7 +93,15 @@ const hide = () => {
67
93
  emit('hide');
68
94
  };
69
95
 
70
- const hideHandler = (e: MouseEvent) => {
96
+ const clickHandler = () => {
97
+ if (!props.autoHide) return;
98
+
99
+ hide();
100
+ };
101
+
102
+ const outsideClickhideHandler = (e: MouseEvent) => {
103
+ if (!props.autoHide) return;
104
+
71
105
  const target = e.target as HTMLElement | undefined;
72
106
  if (!visible.value || !target) {
73
107
  return;
@@ -78,30 +112,38 @@ const hideHandler = (e: MouseEvent) => {
78
112
  hide();
79
113
  };
80
114
 
81
- const show = (e: MouseEvent) => {
115
+ const setPosition = (e: { clientY: number; clientX: number }) => {
116
+ const menuHeight = menu.value?.clientHeight || 0;
117
+
118
+ let top = e.clientY;
119
+ if (menuHeight + e.clientY > document.body.clientHeight) {
120
+ top = document.body.clientHeight - menuHeight;
121
+ }
122
+
123
+ menuPosition.value = {
124
+ top,
125
+ left: e.clientX,
126
+ };
127
+ };
128
+
129
+ const show = (e?: MouseEvent) => {
82
130
  // 加settimeout是以为,如果菜单中的按钮监听的是mouseup,那么菜单显示出来时鼠标如果正好在菜单上就会马上触发按钮的mouseup
83
131
  setTimeout(() => {
84
132
  visible.value = true;
85
133
 
86
- nextTick(() => {
87
- const menuHeight = menu.value?.clientHeight || 0;
88
-
89
- let top = e.clientY;
90
- if (menuHeight + e.clientY > document.body.clientHeight) {
91
- top = document.body.clientHeight - menuHeight;
92
- }
134
+ if (!e) {
135
+ return;
136
+ }
93
137
 
94
- menuStyle.value = {
95
- top: `${top}px`,
96
- left: `${e.clientX}px`,
97
- };
138
+ nextTick(() => {
139
+ setPosition(e);
98
140
 
99
141
  emit('show');
100
142
  });
101
143
  }, 300);
102
144
  };
103
145
 
104
- const showSubMenu = (item: MenuButton | MenuComponent) => {
146
+ const showSubMenu = (item: MenuButton | MenuComponent, index: number) => {
105
147
  const menuItem = item as MenuButton;
106
148
  if (typeof item !== 'object' || !menuItem.items?.length) {
107
149
  return;
@@ -110,30 +152,42 @@ const showSubMenu = (item: MenuButton | MenuComponent) => {
110
152
  subMenuData.value = menuItem.items || [];
111
153
  setTimeout(() => {
112
154
  if (menu.value) {
155
+ // 将子菜单放置在按钮右侧,与按钮齐平
156
+ let y = menu.value.offsetTop;
157
+ if (buttons.value?.[index].$el) {
158
+ const rect = buttons.value?.[index].$el.getBoundingClientRect();
159
+ y = rect.top;
160
+ }
113
161
  subMenu.value?.show({
114
162
  clientX: menu.value.offsetLeft + menu.value.clientWidth,
115
- clientY: menu.value.offsetTop,
163
+ clientY: y,
116
164
  });
117
165
  }
118
166
  }, 0);
119
167
  };
120
168
 
169
+ const mouseenterHandler = () => {
170
+ emit('mouseenter');
171
+ };
172
+
121
173
  onMounted(() => {
122
174
  if (props.isSubMenu) return;
123
175
 
124
- globalThis.addEventListener('mousedown', hideHandler, true);
176
+ globalThis.addEventListener('mousedown', outsideClickhideHandler, true);
125
177
  });
126
178
 
127
179
  onUnmounted(() => {
128
180
  if (props.isSubMenu) return;
129
181
 
130
- globalThis.removeEventListener('mousedown', hideHandler, true);
182
+ globalThis.removeEventListener('mousedown', outsideClickhideHandler, true);
131
183
  });
132
184
 
133
185
  defineExpose({
134
186
  menu,
187
+ menuPosition,
135
188
  hide,
136
189
  show,
137
190
  contains,
191
+ setPosition,
138
192
  });
139
193
  </script>
@@ -5,41 +5,19 @@
5
5
  </template>
6
6
 
7
7
  <script lang="ts" setup>
8
- import { onMounted, onUnmounted, ref } from 'vue';
9
- import Gesto from 'gesto';
8
+ import { ref } from 'vue';
9
+ import type { OnDrag } from 'gesto';
10
+
11
+ import { useGetSo } from '@editor/hooks/use-getso';
10
12
 
11
13
  defineOptions({
12
14
  name: 'MEditorResizer',
13
15
  });
14
16
 
15
- const emit = defineEmits(['change']);
17
+ const emit = defineEmits<{
18
+ change: [e: OnDrag];
19
+ }>();
16
20
 
17
21
  const target = ref<HTMLSpanElement>();
18
- const isDraging = ref(false);
19
-
20
- let getso: Gesto;
21
-
22
- onMounted(() => {
23
- if (!target.value) return;
24
- getso = new Gesto(target.value, {
25
- container: window,
26
- pinchOutside: true,
27
- })
28
- .on('drag', (e) => {
29
- if (!target.value) return;
30
-
31
- emit('change', e.deltaX);
32
- })
33
- .on('dragStart', () => {
34
- isDraging.value = true;
35
- })
36
- .on('dragEnd', () => {
37
- isDraging.value = false;
38
- });
39
- });
40
-
41
- onUnmounted(() => {
42
- getso?.unset();
43
- isDraging.value = false;
44
- });
22
+ const { isDraging } = useGetSo(target, emit);
45
23
  </script>