@tmagic/editor 1.3.0-beta.1 → 1.3.0-beta.3

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 (81) hide show
  1. package/dist/style.css +67 -75
  2. package/dist/tmagic-editor.js +1667 -1062
  3. package/dist/tmagic-editor.js.map +1 -1
  4. package/dist/tmagic-editor.umd.cjs +1692 -1084
  5. package/dist/tmagic-editor.umd.cjs.map +1 -1
  6. package/package.json +10 -10
  7. package/src/Editor.vue +70 -65
  8. package/src/components/ContentMenu.vue +5 -1
  9. package/src/components/SearchInput.vue +5 -1
  10. package/src/components/ToolButton.vue +1 -1
  11. package/src/editorProps.ts +51 -129
  12. package/src/fields/Code.vue +9 -3
  13. package/src/fields/DataSourceFieldSelect.vue +28 -16
  14. package/src/fields/DataSourceFields.vue +95 -7
  15. package/src/fields/DataSourceMethodSelect.vue +23 -14
  16. package/src/fields/DataSourceMocks.vue +237 -0
  17. package/src/fields/EventSelect.vue +30 -11
  18. package/src/index.ts +4 -1
  19. package/src/initService.ts +50 -20
  20. package/src/layouts/CodeEditor.vue +20 -4
  21. package/src/layouts/Framework.vue +3 -1
  22. package/src/layouts/PropsPanel.vue +3 -1
  23. package/src/layouts/sidebar/ComponentListPanel.vue +18 -8
  24. package/src/layouts/sidebar/Sidebar.vue +7 -10
  25. package/src/layouts/sidebar/code-block/CodeBlockList.vue +3 -1
  26. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +3 -1
  27. package/src/layouts/sidebar/layer/LayerNode.vue +206 -0
  28. package/src/layouts/sidebar/layer/LayerNodeTool.vue +32 -0
  29. package/src/layouts/sidebar/layer/LayerPanel.vue +85 -0
  30. package/src/layouts/sidebar/layer/use-drag.ts +157 -0
  31. package/src/layouts/sidebar/layer/use-filter.ts +70 -0
  32. package/src/layouts/sidebar/layer/use-keybinding.ts +47 -0
  33. package/src/layouts/sidebar/layer/use-node-status.ts +103 -0
  34. package/src/layouts/workspace/Workspace.vue +3 -1
  35. package/src/layouts/workspace/viewer/NodeListMenu.vue +16 -9
  36. package/src/layouts/workspace/viewer/Stage.vue +19 -17
  37. package/src/layouts/workspace/viewer/ViewerMenu.vue +1 -1
  38. package/src/services/dataSource.ts +21 -0
  39. package/src/services/editor.ts +45 -5
  40. package/src/theme/common/var.scss +2 -2
  41. package/src/theme/content-menu.scss +22 -20
  42. package/src/theme/framework.scss +2 -0
  43. package/src/theme/layer-panel.scss +63 -63
  44. package/src/theme/props-panel.scss +8 -0
  45. package/src/theme/sidebar.scss +1 -5
  46. package/src/type.ts +61 -0
  47. package/src/utils/data-source/index.ts +23 -0
  48. package/src/utils/dep.ts +26 -11
  49. package/src/utils/editor.ts +14 -3
  50. package/src/utils/props.ts +4 -0
  51. package/types/editorProps.d.ts +61 -107
  52. package/types/fields/Code.vue.d.ts +10 -6
  53. package/types/fields/DataSourceMocks.vue.d.ts +32 -0
  54. package/types/index.d.ts +2 -1
  55. package/types/initService.d.ts +5 -4
  56. package/types/layouts/CodeEditor.vue.d.ts +5 -0
  57. package/types/layouts/Framework.vue.d.ts +2 -12
  58. package/types/layouts/PropsPanel.vue.d.ts +2 -3
  59. package/types/layouts/sidebar/ComponentListPanel.vue.d.ts +2 -7
  60. package/types/layouts/sidebar/Sidebar.vue.d.ts +2 -17
  61. package/types/layouts/sidebar/code-block/CodeBlockList.vue.d.ts +2 -7
  62. package/types/layouts/sidebar/code-block/CodeBlockListPanel.vue.d.ts +2 -9
  63. package/types/layouts/sidebar/{LayerMenu.vue.d.ts → layer/LayerMenu.vue.d.ts} +1 -1
  64. package/types/layouts/sidebar/layer/LayerNode.vue.d.ts +50 -0
  65. package/types/layouts/sidebar/{LayerNode.vue.d.ts → layer/LayerNodeTool.vue.d.ts} +1 -1
  66. package/types/layouts/sidebar/{LayerPanel.vue.d.ts → layer/LayerPanel.vue.d.ts} +2 -8
  67. package/types/layouts/sidebar/layer/use-drag.d.ts +14 -0
  68. package/types/layouts/sidebar/layer/use-filter.d.ts +8 -0
  69. package/types/layouts/sidebar/layer/use-keybinding.d.ts +4 -0
  70. package/types/layouts/sidebar/layer/use-node-status.d.ts +7 -0
  71. package/types/layouts/workspace/Workspace.vue.d.ts +2 -11
  72. package/types/layouts/workspace/viewer/Stage.vue.d.ts +1 -1
  73. package/types/services/dataSource.d.ts +7 -0
  74. package/types/services/editor.d.ts +1 -0
  75. package/types/type.d.ts +61 -0
  76. package/types/utils/dep.d.ts +1 -1
  77. package/types/utils/editor.d.ts +3 -2
  78. package/src/layouts/sidebar/LayerNode.vue +0 -40
  79. package/src/layouts/sidebar/LayerPanel.vue +0 -369
  80. package/types/Editor.vue.d.ts +0 -233
  81. /package/src/layouts/sidebar/{LayerMenu.vue → layer/LayerMenu.vue} +0 -0
@@ -0,0 +1,85 @@
1
+ <template>
2
+ <TMagicScrollbar class="magic-editor-layer-panel">
3
+ <slot name="layer-panel-header"></slot>
4
+
5
+ <SearchInput @search="filterTextChangeHandler"></SearchInput>
6
+
7
+ <div class="magic-editor-layer-tree" tabindex="-1" @dragover="handleDragOver">
8
+ <LayerNode
9
+ v-if="page && root"
10
+ :data="page"
11
+ :filter-text="filterText"
12
+ :is-ctrl-key-down="isCtrlKeyDown"
13
+ @node-contextmenu="contextmenu"
14
+ >
15
+ <template #layer-node-content="{ data: nodeData }">
16
+ <slot name="layer-node-content" :data="nodeData"> </slot>
17
+ </template>
18
+ </LayerNode>
19
+ </div>
20
+
21
+ <Teleport to="body">
22
+ <LayerMenu ref="menu" :layer-content-menu="layerContentMenu" @collapse-all="collapseAllHandler"></LayerMenu>
23
+ </Teleport>
24
+ </TMagicScrollbar>
25
+ </template>
26
+
27
+ <script setup lang="ts">
28
+ import { computed, inject, provide, ref } from 'vue';
29
+
30
+ import { TMagicScrollbar } from '@tmagic/design';
31
+
32
+ import SearchInput from '@editor/components/SearchInput.vue';
33
+ import type { LayerPanelSlots, MenuButton, MenuComponent, Services } from '@editor/type';
34
+
35
+ import LayerMenu from './LayerMenu.vue';
36
+ import LayerNode from './LayerNode.vue';
37
+ import { useDrag } from './use-drag';
38
+ import { useFilter } from './use-filter';
39
+ import { useKeybinding } from './use-keybinding';
40
+ import { useNodeStatus } from './use-node-status';
41
+
42
+ defineSlots<LayerPanelSlots>();
43
+
44
+ defineOptions({
45
+ name: 'MEditorLayerPanel',
46
+ });
47
+
48
+ defineProps<{
49
+ layerContentMenu: (MenuButton | MenuComponent)[];
50
+ }>();
51
+
52
+ const services = inject<Services>('services');
53
+ const editorService = services?.editorService;
54
+
55
+ const page = computed(() => editorService?.get('page'));
56
+ const root = computed(() => editorService?.get('root'));
57
+
58
+ const { nodeStatusMap } = useNodeStatus(services, page);
59
+ const { isCtrlKeyDown } = useKeybinding(services);
60
+
61
+ provide('nodeStatusMap', nodeStatusMap);
62
+
63
+ const { filterText, filterTextChangeHandler } = useFilter(nodeStatusMap, page);
64
+
65
+ const collapseAllHandler = () => {
66
+ if (!page.value || !nodeStatusMap.value) return;
67
+ const items = nodeStatusMap.value.entries();
68
+ for (const [id, status] of items) {
69
+ if (id === page.value.id) {
70
+ continue;
71
+ }
72
+ status.expand = false;
73
+ }
74
+ };
75
+
76
+ // 右键菜单
77
+ const menu = ref<InstanceType<typeof LayerMenu>>();
78
+ const contextmenu = (event: MouseEvent): void => {
79
+ event.preventDefault();
80
+
81
+ menu.value?.show(event);
82
+ };
83
+
84
+ const { handleDragOver } = useDrag(services);
85
+ </script>
@@ -0,0 +1,157 @@
1
+ import type { Id, MContainer, MNode } from '@tmagic/schema';
2
+ import { addClassName, removeClassName } from '@tmagic/utils';
3
+
4
+ import { DragType, type Services } from '@editor/type';
5
+ import { getNodeIndex } from '@editor/utils';
6
+
7
+ export declare type NodeDropType = 'before' | 'after' | 'inner' | 'none';
8
+
9
+ const dragState: {
10
+ dragOverNodeId: Id;
11
+ dropType: NodeDropType | '';
12
+ container: HTMLElement | null;
13
+ } = {
14
+ dragOverNodeId: '',
15
+ dropType: '',
16
+ container: null,
17
+ };
18
+
19
+ const getNodeEl = (el: HTMLElement): HTMLElement | void => {
20
+ if (el.dataset.nodeId) {
21
+ return el;
22
+ }
23
+
24
+ if (el.parentElement) {
25
+ return getNodeEl(el.parentElement);
26
+ }
27
+ };
28
+
29
+ const removeStatusClass = (el: HTMLElement | null) => {
30
+ if (!el) return;
31
+
32
+ ['drag-before', 'drag-after', 'drag-inner'].forEach((className) => {
33
+ el.querySelectorAll(`.${className}`).forEach((el) => {
34
+ removeClassName(el, className);
35
+ });
36
+ });
37
+ };
38
+
39
+ /**
40
+ * dragstart/dragleave/dragend 属于源节点
41
+ * dragover 属于目标节点
42
+ * 这些方法并不是同一个dom事件触发的
43
+ */
44
+ export const useDrag = (services: Services | undefined) => {
45
+ const handleDragStart = (event: DragEvent) => {
46
+ if (!event.dataTransfer || !event.target || !event.currentTarget) return;
47
+
48
+ const targetEl = getNodeEl(event.target as HTMLElement);
49
+
50
+ if (!targetEl || targetEl !== event.currentTarget) return;
51
+
52
+ event.dataTransfer.effectAllowed = 'move';
53
+
54
+ try {
55
+ event.dataTransfer.setData(
56
+ 'text/json',
57
+ JSON.stringify({
58
+ dragType: DragType.LAYER_TREE,
59
+ }),
60
+ );
61
+ } catch {}
62
+ };
63
+
64
+ const handleDragOver = (event: DragEvent) => {
65
+ if (!event.target) return;
66
+
67
+ const targetEl = getNodeEl(event.target as HTMLElement);
68
+ if (!targetEl) return;
69
+
70
+ const labelEl = targetEl.children[0];
71
+ if (!labelEl) return;
72
+
73
+ const { top: targetTop, height: targetHeight } = labelEl.getBoundingClientRect();
74
+
75
+ const distance = event.clientY - targetTop;
76
+ const isContainer = targetEl.dataset.isContainer === 'true';
77
+
78
+ if (distance < targetHeight / 3) {
79
+ dragState.dropType = 'before';
80
+ addClassName(labelEl, globalThis.document, 'drag-before');
81
+ removeClassName(labelEl, 'drag-after', 'drag-inner');
82
+ } else if (distance > (targetHeight * 2) / 3) {
83
+ dragState.dropType = 'after';
84
+ addClassName(labelEl, globalThis.document, 'drag-after');
85
+ removeClassName(labelEl, 'drag-before', 'drag-inner');
86
+ } else if (isContainer) {
87
+ dragState.dropType = 'inner';
88
+ addClassName(labelEl, globalThis.document, 'drag-inner');
89
+ removeClassName(labelEl, 'drag-before', 'drag-after');
90
+ }
91
+
92
+ if (!dragState.dropType) {
93
+ return;
94
+ }
95
+
96
+ dragState.dragOverNodeId = targetEl.dataset.nodeId || '';
97
+ dragState.container = event.currentTarget as HTMLElement;
98
+
99
+ event.preventDefault();
100
+ };
101
+
102
+ const handleDragLeave = (event: DragEvent) => {
103
+ if (!event.target || !event.currentTarget) return;
104
+
105
+ const targetEl = getNodeEl(event.target as HTMLElement);
106
+
107
+ if (!targetEl || targetEl !== event.currentTarget) return;
108
+
109
+ const labelEl = targetEl.children[0];
110
+
111
+ removeClassName(labelEl, 'drag-before', 'drag-after', 'drag-inner');
112
+ };
113
+
114
+ const handleDragEnd = (event: DragEvent, node: MNode) => {
115
+ if (!event.target || !event.currentTarget) return;
116
+
117
+ const targetEl = getNodeEl(event.target as HTMLElement);
118
+
119
+ if (!targetEl || targetEl !== event.currentTarget) return;
120
+
121
+ removeStatusClass(dragState.container);
122
+
123
+ if (node && dragState.dragOverNodeId && dragState.dropType && services) {
124
+ const targetInfo = services.editorService.getNodeInfo(dragState.dragOverNodeId, false);
125
+ const targetNode = targetInfo.node;
126
+ let targetParent = targetInfo.parent;
127
+
128
+ if (!targetParent || !targetNode) return;
129
+
130
+ let targetIndex = -1;
131
+
132
+ if (Array.isArray(targetNode.items) && dragState.dropType === 'inner') {
133
+ targetIndex = targetNode.items.length;
134
+ targetParent = targetNode as MContainer;
135
+ } else {
136
+ targetIndex = getNodeIndex(dragState.dragOverNodeId, targetParent);
137
+ }
138
+
139
+ if (dragState.dropType === 'after') {
140
+ targetIndex += 1;
141
+ }
142
+
143
+ services?.editorService.dragTo(node, targetParent, targetIndex);
144
+ }
145
+
146
+ dragState.dragOverNodeId = '';
147
+ dragState.dropType = '';
148
+ dragState.container = null;
149
+ };
150
+
151
+ return {
152
+ handleDragStart,
153
+ handleDragEnd,
154
+ handleDragLeave,
155
+ handleDragOver,
156
+ };
157
+ };
@@ -0,0 +1,70 @@
1
+ import { type ComputedRef, ref } from 'vue';
2
+
3
+ import { Id, MNode, MPage } from '@tmagic/schema';
4
+ import { getKeys } from '@tmagic/utils';
5
+
6
+ import { LayerNodeStatus } from '@editor/type';
7
+ import { traverseNode } from '@editor/utils';
8
+
9
+ export const updateStatus = (nodeStatusMap: Map<Id, LayerNodeStatus>, id: Id, status: Partial<LayerNodeStatus>) => {
10
+ const nodeStatus = nodeStatusMap.get(id);
11
+
12
+ if (!nodeStatus) return;
13
+ getKeys(status).forEach((key) => {
14
+ if (nodeStatus[key] !== undefined && status[key] !== undefined) {
15
+ nodeStatus[key] = Boolean(status[key]);
16
+ }
17
+ });
18
+ };
19
+
20
+ export const useFilter = (
21
+ nodeStatusMap: ComputedRef<Map<Id, LayerNodeStatus> | undefined>,
22
+ page: ComputedRef<MPage | null | undefined>,
23
+ ) => {
24
+ // tree方法:对树节点进行筛选时执行的方法
25
+ const filterIsMatch = (value: string, data: MNode): boolean => {
26
+ if (!value) {
27
+ return true;
28
+ }
29
+ let name = '';
30
+ if (data.name) {
31
+ name = data.name;
32
+ } else if (data.items) {
33
+ name = 'container';
34
+ }
35
+ return `${data.id}${name}${data.type}`.includes(value);
36
+ };
37
+
38
+ const filterNode = (text: string) => (node: MNode, parents: MNode[]) => {
39
+ if (!nodeStatusMap.value) return;
40
+
41
+ const visible = filterIsMatch(text, node);
42
+ if (visible && parents.length) {
43
+ parents.forEach((parent) => {
44
+ updateStatus(nodeStatusMap.value!, parent.id, {
45
+ visible,
46
+ expand: true,
47
+ });
48
+ });
49
+ }
50
+
51
+ updateStatus(nodeStatusMap.value, node.id, {
52
+ visible,
53
+ });
54
+ };
55
+
56
+ const filter = (text: string) => {
57
+ if (!page.value?.items?.length) return;
58
+
59
+ page.value.items.forEach((node) => {
60
+ traverseNode(node, filterNode(text));
61
+ });
62
+ };
63
+
64
+ return {
65
+ filterText: ref(''),
66
+ filterTextChangeHandler(text: string) {
67
+ filter(text);
68
+ },
69
+ };
70
+ };
@@ -0,0 +1,47 @@
1
+ import { onBeforeUnmount, onMounted, ref } from 'vue';
2
+
3
+ import type { Services } from '@editor/type';
4
+
5
+ export const useKeybinding = (services: Services | undefined) => {
6
+ const keybindingService = services?.keybindingService;
7
+
8
+ // 是否多选
9
+ const isCtrlKeyDown = ref(false);
10
+
11
+ const windowBlurHandler = () => {
12
+ isCtrlKeyDown.value = false;
13
+ };
14
+
15
+ keybindingService?.registeCommand('layer-panel-global-keyup', () => {
16
+ isCtrlKeyDown.value = false;
17
+ });
18
+
19
+ keybindingService?.registeCommand('layer-panel-global-keydwon', () => {
20
+ isCtrlKeyDown.value = true;
21
+ });
22
+
23
+ keybindingService?.registe([
24
+ {
25
+ command: 'layer-panel-global-keydwon',
26
+ keybinding: 'ctrl',
27
+ when: [['global', 'keydown']],
28
+ },
29
+ {
30
+ command: 'layer-panel-global-keyup',
31
+ keybinding: 'ctrl',
32
+ when: [['global', 'keyup']],
33
+ },
34
+ ]);
35
+
36
+ onMounted(() => {
37
+ globalThis.addEventListener('blur', windowBlurHandler);
38
+ });
39
+
40
+ onBeforeUnmount(() => {
41
+ globalThis.removeEventListener('blur', windowBlurHandler);
42
+ });
43
+
44
+ return {
45
+ isCtrlKeyDown,
46
+ };
47
+ };
@@ -0,0 +1,103 @@
1
+ import { computed, type ComputedRef, ref, watch } from 'vue';
2
+
3
+ import type { Id, MNode, MPage } from '@tmagic/schema';
4
+ import { getNodePath } from '@tmagic/utils';
5
+
6
+ import { LayerNodeStatus, Services } from '@editor/type';
7
+ import { traverseNode } from '@editor/utils';
8
+
9
+ import { updateStatus } from './use-filter';
10
+
11
+ const createPageNodeStatus = (services: Services | undefined, pageId: Id) => {
12
+ const map = new Map<Id, LayerNodeStatus>();
13
+
14
+ map.set(pageId, {
15
+ visible: true,
16
+ expand: true,
17
+ selected: true,
18
+ });
19
+
20
+ services?.editorService.getNodeById(pageId)?.items.forEach((node: MNode) =>
21
+ traverseNode(node, (node) => {
22
+ map.set(node.id, {
23
+ visible: true,
24
+ expand: false,
25
+ selected: false,
26
+ });
27
+ }),
28
+ );
29
+
30
+ return map;
31
+ };
32
+
33
+ export const useNodeStatus = (services: Services | undefined, page: ComputedRef<MPage | null | undefined>) => {
34
+ const nodes = computed(() => services?.editorService.get('nodes') || []);
35
+
36
+ /** 所有页面的节点状态 */
37
+ const nodeStatusMaps = ref(new Map<Id, Map<Id, LayerNodeStatus>>());
38
+
39
+ /** 当前页面的节点状态 */
40
+ const nodeStatusMap = computed(() =>
41
+ page.value ? nodeStatusMaps.value.get(page.value.id) : new Map<Id, LayerNodeStatus>(),
42
+ );
43
+
44
+ // 切换页面,重新生成节点状态
45
+ watch(
46
+ () => page.value?.id,
47
+ (pageId) => {
48
+ // 已经存在,不需要重新生成
49
+ if (!pageId || nodeStatusMaps.value.has(pageId)) {
50
+ return;
51
+ }
52
+
53
+ // 新增页面,生成节点状态
54
+ nodeStatusMaps.value.set(pageId, createPageNodeStatus(services, pageId));
55
+ },
56
+ {
57
+ immediate: true,
58
+ },
59
+ );
60
+
61
+ // 选中状态变化,更新节点状态
62
+ watch(
63
+ nodes,
64
+ (nodes) => {
65
+ if (!nodeStatusMap.value) return;
66
+
67
+ for (const [id, status] of nodeStatusMap.value.entries()) {
68
+ status.selected = nodes.some((node) => node.id === id);
69
+ if (status.selected) {
70
+ getNodePath(id, page.value?.items).forEach((node) => {
71
+ updateStatus(nodeStatusMap.value!, node.id, {
72
+ expand: true,
73
+ });
74
+ });
75
+ }
76
+ }
77
+ },
78
+ {
79
+ immediate: true,
80
+ },
81
+ );
82
+
83
+ services?.editorService.on('add', (newNodes: MNode[]) => {
84
+ newNodes.forEach((node) => {
85
+ nodeStatusMap.value?.set(node.id, {
86
+ visible: true,
87
+ expand: Array.isArray(node.items),
88
+ selected: true,
89
+ });
90
+ });
91
+ });
92
+
93
+ services?.editorService.on('remove', (nodes: MNode[]) => {
94
+ nodes.forEach((node) => {
95
+ nodeStatusMap.value?.delete(node.id);
96
+ });
97
+ });
98
+
99
+ return {
100
+ nodeStatusMaps,
101
+ nodeStatusMap,
102
+ };
103
+ };
@@ -18,12 +18,14 @@
18
18
  <script lang="ts" setup>
19
19
  import { computed, inject } from 'vue';
20
20
 
21
- import { MenuButton, MenuComponent, Services } from '@editor/type';
21
+ import type { MenuButton, MenuComponent, Services, WorkspaceSlots } from '@editor/type';
22
22
 
23
23
  import MagicStage from './viewer/Stage.vue';
24
24
  import Breadcrumb from './Breadcrumb.vue';
25
25
  import PageBar from './PageBar.vue';
26
26
 
27
+ defineSlots<WorkspaceSlots>();
28
+
27
29
  defineOptions({
28
30
  name: 'MEditorWorkspace',
29
31
  });
@@ -1,6 +1,8 @@
1
1
  <template>
2
- <content-menu
2
+ <ContentMenu
3
3
  ref="menu"
4
+ class="magic-editor-node-list-menu"
5
+ style="max-width: 280px"
4
6
  :menu-data="menuData"
5
7
  :active="node?.id"
6
8
  :auto-hide="!pinned"
@@ -9,7 +11,7 @@
9
11
  <template #title>
10
12
  <NodeListMenuTitle v-model:pinned="pinned" @change="dragMenuHandler" @close="closeHandler"></NodeListMenuTitle>
11
13
  </template>
12
- </content-menu>
14
+ </ContentMenu>
13
15
  </template>
14
16
 
15
17
  <script lang="ts" setup>
@@ -53,6 +55,14 @@ const cancel = () => {
53
55
  menu.value?.hide();
54
56
  };
55
57
 
58
+ const clearTimeoutLazy = () => {
59
+ globalThis.setTimeout(() => {
60
+ if (timeout) {
61
+ globalThis.clearTimeout(timeout);
62
+ }
63
+ }, 300);
64
+ };
65
+
56
66
  const unWatch = watch(
57
67
  stage,
58
68
  (stage) => {
@@ -70,7 +80,7 @@ const unWatch = watch(
70
80
  }
71
81
 
72
82
  timeout = globalThis.setTimeout(() => {
73
- const els = stage?.renderer.getElementsFromPoint(event);
83
+ const els = stage.renderer.getElementsFromPoint(event) || [];
74
84
 
75
85
  const nodes = getNodes(
76
86
  els.map((el) => el.id),
@@ -90,7 +100,8 @@ const unWatch = watch(
90
100
  });
91
101
 
92
102
  stage.on('mouseleave', () => {
93
- cancel();
103
+ // mouseleave后,大概率还有最后一个mousemove事件,这里延迟清除
104
+ clearTimeoutLazy();
94
105
  });
95
106
 
96
107
  unWatch();
@@ -135,11 +146,7 @@ const menuData = computed<MenuButton[]>(() =>
135
146
 
136
147
  const mouseenterHandler = () => {
137
148
  // menu的mouseenter后,大概率还有最后一个mousemove事件,这里延迟清除
138
- globalThis.setTimeout(() => {
139
- if (timeout) {
140
- globalThis.clearTimeout(timeout);
141
- }
142
- }, 300);
149
+ clearTimeoutLazy();
143
150
  };
144
151
 
145
152
  const dragMenuHandler = ({ deltaY, deltaX }: OnDrag) => {
@@ -38,7 +38,7 @@ import StageCore, { calcValueByFontsize, getOffset, Runtime } from '@tmagic/stag
38
38
 
39
39
  import ScrollViewer from '@editor/components/ScrollViewer.vue';
40
40
  import { useStage } from '@editor/hooks/use-stage';
41
- import { Layout, MenuButton, MenuComponent, Services, StageOptions } from '@editor/type';
41
+ import { DragType, Layout, type MenuButton, type MenuComponent, type Services, type StageOptions } from '@editor/type';
42
42
  import { getConfig } from '@editor/utils/config';
43
43
 
44
44
  import NodeListMenu from './NodeListMenu.vue';
@@ -142,18 +142,30 @@ onUnmounted(() => {
142
142
  services?.keybindingService.unregisteEl('stage');
143
143
  });
144
144
 
145
+ const parseDSL = getConfig('parseDSL');
146
+
145
147
  const contextmenuHandler = (e: MouseEvent) => {
146
148
  e.preventDefault();
147
149
  menu.value?.show(e);
148
150
  };
149
151
 
150
152
  const dragoverHandler = (e: DragEvent) => {
151
- e.preventDefault();
152
153
  if (!e.dataTransfer) return;
154
+ e.preventDefault();
153
155
  e.dataTransfer.dropEffect = 'move';
154
156
  };
155
157
 
156
158
  const dropHandler = async (e: DragEvent) => {
159
+ if (!e.dataTransfer) return;
160
+
161
+ const data = e.dataTransfer.getData('text/json');
162
+
163
+ if (!data) return;
164
+
165
+ const config = parseDSL(`(${data})`);
166
+
167
+ if (!config || config.dragType !== DragType.COMPONENT_LIST) return;
168
+
157
169
  e.preventDefault();
158
170
 
159
171
  const doc = stage?.renderer.contentWindow?.document;
@@ -164,22 +176,12 @@ const dropHandler = async (e: DragEvent) => {
164
176
  parent = services?.editorService.getNodeById(parentEl.id, false) as MContainer;
165
177
  }
166
178
 
167
- if (e.dataTransfer && parent && stageContainer.value && stage) {
168
- const parseDSL = getConfig('parseDSL');
169
-
170
- const data = e.dataTransfer.getData('text/json');
171
-
172
- if (!data) return;
173
-
174
- const config = parseDSL(`(${data})`);
175
-
176
- if (!config) return;
177
-
179
+ if (parent && stageContainer.value && stage) {
178
180
  const layout = await services?.editorService.getLayout(parent);
179
181
 
180
182
  const containerRect = stageContainer.value.getBoundingClientRect();
181
183
  const { scrollTop, scrollLeft } = stage.mask;
182
- const { style = {} } = config;
184
+ const { style = {} } = config.data;
183
185
 
184
186
  let top = 0;
185
187
  let left = 0;
@@ -197,16 +199,16 @@ const dropHandler = async (e: DragEvent) => {
197
199
  }
198
200
  }
199
201
 
200
- config.style = {
202
+ config.data.style = {
201
203
  ...style,
202
204
  position,
203
205
  top: top / zoom.value,
204
206
  left: left / zoom.value,
205
207
  };
206
208
 
207
- config.inputEvent = e;
209
+ config.data.inputEvent = e;
208
210
 
209
- services?.editorService.add(config, parent);
211
+ services?.editorService.add(config.data, parent);
210
212
  }
211
213
  };
212
214
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <content-menu :menu-data="menuData" ref="menu"></content-menu>
2
+ <ContentMenu :menu-data="menuData" ref="menu"></ContentMenu>
3
3
  </template>
4
4
 
5
5
  <script lang="ts" setup>
@@ -1,6 +1,7 @@
1
1
  import { reactive } from 'vue';
2
2
  import { cloneDeep } from 'lodash-es';
3
3
 
4
+ import type { EventOption } from '@tmagic/core';
4
5
  import type { FormConfig } from '@tmagic/form';
5
6
  import type { DataSourceSchema } from '@tmagic/schema';
6
7
  import { guid } from '@tmagic/utils';
@@ -16,6 +17,8 @@ interface State {
16
17
  editable: boolean;
17
18
  configs: Record<string, FormConfig>;
18
19
  values: Record<string, Partial<DataSourceSchema>>;
20
+ events: Record<string, EventOption[]>;
21
+ methods: Record<string, EventOption[]>;
19
22
  }
20
23
 
21
24
  type StateKey = keyof State;
@@ -26,6 +29,8 @@ class DataSource extends BaseService {
26
29
  editable: true,
27
30
  configs: {},
28
31
  values: {},
32
+ events: {},
33
+ methods: {},
29
34
  });
30
35
 
31
36
  public set<K extends StateKey, T extends State[K]>(name: K, value: T) {
@@ -52,6 +57,22 @@ class DataSource extends BaseService {
52
57
  this.get('values')[type] = value;
53
58
  }
54
59
 
60
+ public getFormEvent(type = 'base') {
61
+ return this.get('events')[type] || [];
62
+ }
63
+
64
+ public setFormEvent(type: string, value: EventOption[] = []) {
65
+ this.get('events')[type] = value;
66
+ }
67
+
68
+ public getFormMethod(type = 'base') {
69
+ return this.get('methods')[type] || [];
70
+ }
71
+
72
+ public setFormMethod(type: string, value: EventOption[] = []) {
73
+ this.get('methods')[type] = value;
74
+ }
75
+
55
76
  public add(config: DataSourceSchema) {
56
77
  const newConfig = {
57
78
  ...config,