@tmagic/editor 1.4.13 → 1.4.15

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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.4.13",
2
+ "version": "1.4.15",
3
3
  "name": "@tmagic/editor",
4
4
  "type": "module",
5
5
  "sideEffects": [
@@ -55,8 +55,8 @@
55
55
  "moveable": "^0.53.0",
56
56
  "serialize-javascript": "^6.0.0",
57
57
  "sortablejs": "^1.15.2",
58
- "@tmagic/dep": "1.4.13",
59
- "@tmagic/table": "1.4.13"
58
+ "@tmagic/dep": "1.4.15",
59
+ "@tmagic/table": "1.4.15"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/events": "^3.0.0",
@@ -78,12 +78,12 @@
78
78
  "monaco-editor": "^0.48.0",
79
79
  "typescript": "*",
80
80
  "vue": "^3.4.27",
81
- "@tmagic/design": "1.4.13",
82
- "@tmagic/form": "1.4.13",
83
- "@tmagic/core": "1.4.13",
84
- "@tmagic/schema": "1.4.13",
85
- "@tmagic/stage": "1.4.13",
86
- "@tmagic/utils": "1.4.13"
81
+ "@tmagic/core": "1.4.15",
82
+ "@tmagic/form": "1.4.15",
83
+ "@tmagic/schema": "1.4.15",
84
+ "@tmagic/stage": "1.4.15",
85
+ "@tmagic/design": "1.4.15",
86
+ "@tmagic/utils": "1.4.15"
87
87
  },
88
88
  "peerDependenciesMeta": {
89
89
  "typescript": {
package/src/Editor.vue CHANGED
@@ -33,6 +33,10 @@
33
33
  <slot name="layer-node-tool" :data="data"></slot>
34
34
  </template>
35
35
 
36
+ <template #component-list="{ componentGroupList }">
37
+ <slot name="component-list" :component-group-list="componentGroupList"></slot>
38
+ </template>
39
+
36
40
  <template #component-list-panel-header>
37
41
  <slot name="component-list-panel-header"></slot>
38
42
  </template>
@@ -38,6 +38,8 @@
38
38
  <script lang="ts" setup>
39
39
  import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
40
40
 
41
+ import { useZIndex } from '@tmagic/design';
42
+
41
43
  import { MenuButton, MenuComponent } from '@editor/type';
42
44
 
43
45
  import ToolButton from './ToolButton.vue';
@@ -71,6 +73,8 @@ const buttons = ref<InstanceType<typeof ToolButton>[]>();
71
73
  const subMenu = ref<any>();
72
74
  const visible = ref(false);
73
75
  const subMenuData = ref<(MenuButton | MenuComponent)[]>([]);
76
+ const zIndex = useZIndex();
77
+ const curZIndex = ref<number>(0);
74
78
 
75
79
  const menuPosition = ref({
76
80
  left: 0,
@@ -80,6 +84,7 @@ const menuPosition = ref({
80
84
  const menuStyle = computed(() => ({
81
85
  top: `${menuPosition.value.top}px`,
82
86
  left: `${menuPosition.value.left}px`,
87
+ zIndex: curZIndex.value,
83
88
  }));
84
89
 
85
90
  const contains = (el: HTMLElement) => menu.value?.contains(el) || subMenu.value?.contains(el);
@@ -127,13 +132,15 @@ const setPosition = (e: { clientY: number; clientX: number }) => {
127
132
  };
128
133
 
129
134
  const show = (e?: { clientY: number; clientX: number }) => {
130
- // 加settimeout是以为,如果菜单中的按钮监听的是mouseup,那么菜单显示出来时鼠标如果正好在菜单上就会马上触发按钮的mouseup
135
+ // 加setTimeout是以为,如果菜单中的按钮监听的是mouseup,那么菜单显示出来时鼠标如果正好在菜单上就会马上触发按钮的mouseup
131
136
  setTimeout(() => {
132
137
  visible.value = true;
133
138
 
134
139
  nextTick(() => {
135
140
  e && setPosition(e);
136
141
 
142
+ curZIndex.value = zIndex.nextZIndex();
143
+
137
144
  emit('show');
138
145
  });
139
146
  }, 300);
@@ -4,6 +4,8 @@
4
4
  class="m-editor-tree-node"
5
5
  :draggable="draggable"
6
6
  :data-node-id="data.id"
7
+ :data-parent-id="parent?.id"
8
+ :data-parents-id="parentsId"
7
9
  :data-is-container="Array.isArray(data.items)"
8
10
  @dragstart="handleDragStart"
9
11
  @dragleave="handleDragLeave"
@@ -40,6 +42,8 @@
40
42
  v-for="item in data.items"
41
43
  :key="item.id"
42
44
  :data="item"
45
+ :parent="data"
46
+ :parentsId="[...parentsId, data.id]"
43
47
  :node-status-map="nodeStatusMap"
44
48
  :indent="indent + 11"
45
49
  >
@@ -91,11 +95,14 @@ const treeEmit = inject<typeof emit>('treeEmit');
91
95
  const props = withDefaults(
92
96
  defineProps<{
93
97
  data: TreeNodeData;
98
+ parent?: TreeNodeData;
99
+ parentsId?: Id[];
94
100
  nodeStatusMap: Map<Id, LayerNodeStatus>;
95
101
  indent?: number;
96
102
  }>(),
97
103
  {
98
104
  indent: 0,
105
+ parentsId: () => [],
99
106
  },
100
107
  );
101
108
 
@@ -72,7 +72,7 @@ const codeConfig = computed(() => ({
72
72
  {
73
73
  type: 'select',
74
74
  name: 'codeType',
75
- span: 8,
75
+ span: 6,
76
76
  options: [
77
77
  { value: HookCodeType.CODE, text: '代码块' },
78
78
  { value: HookCodeType.DATA_SOURCE_METHOD, text: '数据源方法' },
@@ -91,7 +91,7 @@ const codeConfig = computed(() => ({
91
91
  {
92
92
  type: 'code-select-col',
93
93
  name: 'codeId',
94
- span: 16,
94
+ span: 18,
95
95
  labelWidth: 0,
96
96
  display: (mForm: FormState, { model }: any) => model.codeType !== HookCodeType.DATA_SOURCE_METHOD,
97
97
  notEditable: () => !services?.codeBlockService.getEditStatus(),
@@ -99,7 +99,7 @@ const codeConfig = computed(() => ({
99
99
  {
100
100
  type: 'data-source-method-select',
101
101
  name: 'codeId',
102
- span: 16,
102
+ span: 18,
103
103
  labelWidth: 0,
104
104
  display: (mForm: FormState, { model }: any) => model.codeType === HookCodeType.DATA_SOURCE_METHOD,
105
105
  notEditable: () => !services?.dataSourceService.get('editable'),
@@ -57,13 +57,11 @@
57
57
  @change="onChangeHandler"
58
58
  ></TMagicCascader>
59
59
 
60
- <TMagicButton
61
- v-if="selectDataSourceId && hasDataSourceSidePanel"
62
- class="m-fields-select-action-button"
63
- :size="size"
64
- @click="editHandler(selectDataSourceId)"
65
- ><MIcon :icon="!notEditable ? Edit : View"></MIcon
66
- ></TMagicButton>
60
+ <TMagicTooltip v-if="selectDataSourceId && hasDataSourceSidePanel" :content="notEditable ? '查看' : '编辑'">
61
+ <TMagicButton class="m-fields-select-action-button" :size="size" @click="editHandler(selectDataSourceId)"
62
+ ><MIcon :icon="!notEditable ? Edit : View"></MIcon
63
+ ></TMagicButton>
64
+ </TMagicTooltip>
67
65
  </div>
68
66
  </template>
69
67
 
@@ -71,7 +69,13 @@
71
69
  import { computed, inject, ref, watch } from 'vue';
72
70
  import { Edit, View } from '@element-plus/icons-vue';
73
71
 
74
- import { getConfig as getDesignConfig, TMagicButton, TMagicCascader, TMagicSelect } from '@tmagic/design';
72
+ import {
73
+ getConfig as getDesignConfig,
74
+ TMagicButton,
75
+ TMagicCascader,
76
+ TMagicSelect,
77
+ TMagicTooltip,
78
+ } from '@tmagic/design';
75
79
  import { type FilterFunction, filterFunction, type FormState, type SelectOption } from '@tmagic/form';
76
80
  import { DataSourceFieldType } from '@tmagic/schema';
77
81
  import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX } from '@tmagic/utils';
@@ -26,14 +26,15 @@
26
26
  @change="onChangeHandler"
27
27
  ></component>
28
28
 
29
- <TMagicButton
30
- v-if="config.fieldConfig"
31
- style="margin-left: 5px"
32
- :type="showDataSourceFieldSelect ? 'primary' : 'default'"
33
- :size="size"
34
- @click="showDataSourceFieldSelect = !showDataSourceFieldSelect"
35
- ><MIcon :icon="Coin"></MIcon
36
- ></TMagicButton>
29
+ <TMagicTooltip v-if="config.fieldConfig" :disabled="showDataSourceFieldSelect" content="选择数据源">
30
+ <TMagicButton
31
+ style="margin-left: 5px"
32
+ :type="showDataSourceFieldSelect ? 'primary' : 'default'"
33
+ :size="size"
34
+ @click="showDataSourceFieldSelect = !showDataSourceFieldSelect"
35
+ ><MIcon :icon="Coin"></MIcon
36
+ ></TMagicButton>
37
+ </TMagicTooltip>
37
38
  </div>
38
39
  </template>
39
40
 
@@ -41,7 +42,7 @@
41
42
  import { computed, inject, ref, resolveComponent, watch } from 'vue';
42
43
  import { Coin } from '@element-plus/icons-vue';
43
44
 
44
- import { TMagicButton, tMagicMessage } from '@tmagic/design';
45
+ import { TMagicButton, tMagicMessage, TMagicTooltip } from '@tmagic/design';
45
46
  import type { FieldProps, FormState } from '@tmagic/form';
46
47
  import { DataSchema } from '@tmagic/schema';
47
48
  import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX } from '@tmagic/utils';
@@ -9,14 +9,14 @@
9
9
  @change="onChangeHandler"
10
10
  ></MContainer>
11
11
 
12
- <TMagicButton
12
+ <TMagicTooltip
13
13
  v-if="model[name] && isCustomMethod && hasDataSourceSidePanel"
14
- class="m-fields-select-action-button"
15
- :size="size"
16
- @click="editCodeHandler"
14
+ :content="notEditable ? '查看' : '编辑'"
17
15
  >
18
- <MIcon :icon="!notEditable ? Edit : View"></MIcon>
19
- </TMagicButton>
16
+ <TMagicButton class="m-fields-select-action-button" :size="size" @click="editCodeHandler">
17
+ <MIcon :icon="!notEditable ? Edit : View"></MIcon>
18
+ </TMagicButton>
19
+ </TMagicTooltip>
20
20
  </div>
21
21
 
22
22
  <CodeParams
@@ -35,7 +35,7 @@
35
35
  import { computed, inject, ref } from 'vue';
36
36
  import { Edit, View } from '@element-plus/icons-vue';
37
37
 
38
- import { TMagicButton } from '@tmagic/design';
38
+ import { TMagicButton, TMagicTooltip } from '@tmagic/design';
39
39
  import { createValues, type FieldProps, filterFunction, type FormState, MContainer } from '@tmagic/form';
40
40
  import type { Id } from '@tmagic/schema';
41
41
 
@@ -91,7 +91,7 @@ const getParamItemsConfig = ([dataSourceId, methodName]: [Id, string] = ['', '']
91
91
  }));
92
92
  };
93
93
 
94
- const paramsConfig = ref<CodeParamStatement[]>(getParamItemsConfig(props.model.dataSourceMethod));
94
+ const paramsConfig = ref<CodeParamStatement[]>(getParamItemsConfig(props.model[props.name || 'dataSourceMethod']));
95
95
 
96
96
  const setParamsConfig = (dataSourceMethod: [Id, string], formState: any = {}) => {
97
97
  // 通过下拉框选择的codeId变化后修正model的值,避免写入其他codeId的params
@@ -11,13 +11,11 @@
11
11
  @change="changeHandler"
12
12
  ></MSelect>
13
13
 
14
- <TMagicButton
15
- v-if="model[name] && hasDataSourceSidePanel"
16
- class="m-fields-select-action-button"
17
- :size="size"
18
- @click="editHandler"
19
- ><MIcon :icon="!notEditable ? Edit : View"></MIcon
20
- ></TMagicButton>
14
+ <TMagicTooltip v-if="model[name] && hasDataSourceSidePanel" :content="notEditable ? '查看' : '编辑'">
15
+ <TMagicButton class="m-fields-select-action-button" :size="size" @click="editHandler"
16
+ ><MIcon :icon="!notEditable ? Edit : View"></MIcon
17
+ ></TMagicButton>
18
+ </TMagicTooltip>
21
19
  </div>
22
20
  </template>
23
21
 
@@ -25,7 +23,7 @@
25
23
  import { computed, inject } from 'vue';
26
24
  import { Edit, View } from '@element-plus/icons-vue';
27
25
 
28
- import { TMagicButton } from '@tmagic/design';
26
+ import { TMagicButton, TMagicTooltip } from '@tmagic/design';
29
27
  import { type FieldProps, filterFunction, type FormState, MSelect, type SelectConfig } from '@tmagic/form';
30
28
 
31
29
  import MIcon from '@editor/components/Icon.vue';
@@ -1,4 +1,4 @@
1
- import { computed, ComputedRef, inject, ref, watch } from 'vue';
1
+ import { computed, ComputedRef, inject, onBeforeUnmount, ref, watch } from 'vue';
2
2
 
3
3
  import type { Services } from '@editor/type';
4
4
 
@@ -30,22 +30,62 @@ export const useFloatBox = (slideKeys: ComputedRef<string[]>) => {
30
30
  const showingBoxKeys = computed(() =>
31
31
  Object.keys(floatBoxStates.value).filter((key) => floatBoxStates.value[key].status),
32
32
  );
33
- const isDragging = ref(false);
34
33
 
35
- const dragstartHandler = () => (isDragging.value = true);
34
+ const dragState = {
35
+ startOffset: {
36
+ x: 0,
37
+ y: 0,
38
+ },
39
+ isDragging: false,
40
+ };
41
+
42
+ const dragstartHandler = (e: DragEvent) => {
43
+ dragState.isDragging = true;
44
+
45
+ dragState.startOffset.x = e.clientX;
46
+ dragState.startOffset.y = e.clientY;
47
+ };
48
+
49
+ const effectiveDistance = 20;
50
+
36
51
  const dragendHandler = (key: string, e: DragEvent) => {
37
- const navMenuRect = services?.uiService?.get('navMenuRect');
38
- floatBoxStates.value[key] = {
39
- left: e.clientX,
40
- top: (navMenuRect?.top ?? 0) + (navMenuRect?.height ?? 0),
41
- status: true,
42
- };
43
- isDragging.value = false;
52
+ if (!dragState.isDragging) return;
53
+
54
+ const { startOffset } = dragState;
55
+
56
+ if (
57
+ Math.abs(startOffset.x - e.clientX) > effectiveDistance ||
58
+ Math.abs(startOffset.y - e.clientY) > effectiveDistance
59
+ ) {
60
+ const navMenuRect = services?.uiService?.get('navMenuRect');
61
+ floatBoxStates.value[key] = {
62
+ left: e.clientX,
63
+ top: (navMenuRect?.top ?? 0) + (navMenuRect?.height ?? 0),
64
+ status: true,
65
+ };
66
+ }
67
+
68
+ dragState.isDragging = false;
44
69
  };
45
70
 
46
- document.body.addEventListener('dragover', (e: DragEvent) => {
47
- if (!isDragging.value) return;
71
+ const dragoverHandler = (e: DragEvent) => {
72
+ if (!dragState.isDragging) return;
48
73
  e.preventDefault();
74
+ };
75
+
76
+ const blurHandler = () => {
77
+ dragState.startOffset.x = 0;
78
+ dragState.startOffset.y = 0;
79
+
80
+ dragState.isDragging = false;
81
+ };
82
+
83
+ globalThis.document.body.addEventListener('dragover', dragoverHandler);
84
+ globalThis.addEventListener('blur', blurHandler);
85
+
86
+ onBeforeUnmount(() => {
87
+ globalThis.document.body.removeEventListener('dragover', dragoverHandler);
88
+ globalThis.removeEventListener('blur', blurHandler);
49
89
  });
50
90
 
51
91
  watch(
@@ -2,31 +2,34 @@
2
2
  <TMagicScrollbar>
3
3
  <slot name="component-list-panel-header"></slot>
4
4
 
5
- <TMagicCollapse class="ui-component-panel" :model-value="collapseValue">
6
- <SearchInput @search="filterTextChangeHandler"></SearchInput>
7
- <template v-for="(group, index) in list">
8
- <TMagicCollapseItem v-if="group.items && group.items.length" :key="index" :name="`${index}`">
9
- <template #title><MIcon :icon="Grid"></MIcon>{{ group.title }}</template>
10
- <div
11
- v-for="item in group.items"
12
- class="component-item"
13
- draggable="true"
14
- :key="item.type"
15
- @click="appendComponent(item)"
16
- @dragstart="dragstartHandler(item, $event)"
17
- @dragend="dragendHandler"
18
- @drag="dragHandler"
19
- >
20
- <slot name="component-list-item" :component="item">
21
- <TMagicTooltip placement="right" :disabled="!Boolean(item.desc)" :content="item.desc">
22
- <MIcon :icon="item.icon"></MIcon>
23
- </TMagicTooltip>
24
- <span :title="item.text">{{ item.text }}</span>
25
- </slot>
26
- </div>
27
- </TMagicCollapseItem>
28
- </template>
29
- </TMagicCollapse>
5
+ <SearchInput @search="filterTextChangeHandler"></SearchInput>
6
+
7
+ <slot name="component-list" :component-group-list="list">
8
+ <TMagicCollapse class="ui-component-panel" :model-value="collapseValue">
9
+ <template v-for="(group, index) in list">
10
+ <TMagicCollapseItem v-if="group.items && group.items.length" :key="index" :name="`${index}`">
11
+ <template #title><MIcon :icon="Grid"></MIcon>{{ group.title }}</template>
12
+ <div
13
+ v-for="item in group.items"
14
+ class="component-item"
15
+ draggable="true"
16
+ :key="item.type"
17
+ @click="appendComponent(item)"
18
+ @dragstart="dragstartHandler(item, $event)"
19
+ @dragend="dragendHandler"
20
+ @drag="dragHandler"
21
+ >
22
+ <slot name="component-list-item" :component="item">
23
+ <TMagicTooltip placement="right" :disabled="!Boolean(item.desc)" :content="item.desc">
24
+ <MIcon :icon="item.icon"></MIcon>
25
+ </TMagicTooltip>
26
+ <span :title="item.text">{{ item.text }}</span>
27
+ </slot>
28
+ </div>
29
+ </TMagicCollapseItem>
30
+ </template>
31
+ </TMagicCollapse>
32
+ </slot>
30
33
  </TMagicScrollbar>
31
34
  </template>
32
35
 
@@ -65,8 +68,8 @@ const services = inject<Services>('services');
65
68
  const stageOptions = inject<StageOptions>('stageOptions');
66
69
 
67
70
  const stage = computed(() => services?.editorService.get('stage'));
68
- const list = computed(() =>
69
- services?.componentListService.getList().map((group: ComponentGroup) => ({
71
+ const list = computed<ComponentGroup[]>(() =>
72
+ (services?.componentListService.getList() || []).map((group: ComponentGroup) => ({
70
73
  ...group,
71
74
  items: group.items.filter((item: ComponentItem) => item.text.includes(searchText.value)),
72
75
  })),
@@ -5,11 +5,11 @@
5
5
  class="m-editor-sidebar-header-item"
6
6
  v-for="(config, index) in sideBarItems"
7
7
  v-show="!floatBoxStates[config.$key]?.status"
8
- draggable="true"
8
+ :draggable="config.draggable ?? true"
9
9
  :key="config.$key ?? index"
10
10
  :class="{ 'is-active': activeTabName === config.text }"
11
11
  :style="config.tabStyle || {}"
12
- @click="activeTabName = config.text || config.$key || `${index}`"
12
+ @click="headerItemClickHandler(config, index)"
13
13
  @dragstart="dragstartHandler"
14
14
  @dragend="dragendHandler(config.$key, $event)"
15
15
  >
@@ -24,11 +24,23 @@
24
24
  v-show="[config.text, config.$key, `${index}`].includes(activeTabName)"
25
25
  >
26
26
  <component
27
- v-if="config && !floatBoxStates[config.$key]?.status"
27
+ v-if="config?.component && !floatBoxStates[config.$key]?.status"
28
28
  :is="config.component"
29
29
  v-bind="config.props || {}"
30
30
  v-on="config?.listeners || {}"
31
31
  >
32
+ <template
33
+ #component-list="{ componentGroupList }"
34
+ v-if="config.$key === 'component-list' || config.slots?.componentList"
35
+ >
36
+ <slot
37
+ v-if="config.$key === 'component-list'"
38
+ name="component-list"
39
+ :component-group-list="componentGroupList"
40
+ ></slot>
41
+ <component v-else-if="config.slots?.componentList" :is="config.slots.componentList" />
42
+ </template>
43
+
32
44
  <template
33
45
  #component-list-panel-header
34
46
  v-if="config.$key === 'component-list' || config.slots?.componentListPanelHeader"
@@ -272,6 +284,15 @@ watch(
272
284
  },
273
285
  );
274
286
 
287
+ const headerItemClickHandler = async (config: SideComponent, index: number) => {
288
+ if (typeof config.beforeClick === 'function') {
289
+ if ((await config.beforeClick(config)) === false) {
290
+ return;
291
+ }
292
+ }
293
+ activeTabName.value = config.text || config.$key || `${index}`;
294
+ };
295
+
275
296
  defineExpose({
276
297
  activeTabName,
277
298
  });
@@ -53,7 +53,7 @@ import type { MNode } from '@tmagic/schema';
53
53
  import SearchInput from '@editor/components/SearchInput.vue';
54
54
  import Tree from '@editor/components/Tree.vue';
55
55
  import { useFilter } from '@editor/hooks/use-filter';
56
- import { LayerPanelSlots, MenuButton, MenuComponent, Services, TreeNodeData } from '@editor/type';
56
+ import type { LayerPanelSlots, MenuButton, MenuComponent, Services, TreeNodeData } from '@editor/type';
57
57
 
58
58
  import LayerMenu from './LayerMenu.vue';
59
59
  import LayerNodeTool from './LayerNodeTool.vue';
@@ -2,6 +2,7 @@ import { computed, type ComputedRef, nextTick, type Ref, ref } from 'vue';
2
2
  import { throttle } from 'lodash-es';
3
3
 
4
4
  import { Id, MNode } from '@tmagic/schema';
5
+ import { isPage, isPageFragment } from '@tmagic/utils';
5
6
 
6
7
  import { LayerNodeStatus, Services, TreeNodeData, UI_SELECT_MODE_EVENT_NAME } from '@editor/type';
7
8
  import { updateStatus } from '@editor/utils/tree';
@@ -31,6 +32,10 @@ export const useClick = (
31
32
  };
32
33
 
33
34
  const multiSelect = async (data: MNode) => {
35
+ if (isPage(data) || isPageFragment(data)) {
36
+ return;
37
+ }
38
+
34
39
  const nodes = services?.editorService.get('nodes') || [];
35
40
 
36
41
  const newNodes: Id[] = [];
@@ -41,6 +46,10 @@ export const useClick = (
41
46
  return;
42
47
  }
43
48
 
49
+ if (isPage(node) || isPageFragment(node)) {
50
+ return;
51
+ }
52
+
44
53
  newNodes.push(node.id);
45
54
  });
46
55
 
@@ -10,6 +10,7 @@ const dragState: {
10
10
  dragOverNodeId: Id;
11
11
  dropType: NodeDropType | '';
12
12
  container: HTMLElement | null;
13
+ nodeId?: Id;
13
14
  } = {
14
15
  dragOverNodeId: '',
15
16
  dropType: '',
@@ -50,6 +51,7 @@ export const useDrag = (services: Services | undefined) => {
50
51
  if (!targetEl || targetEl !== event.currentTarget) return;
51
52
 
52
53
  event.dataTransfer.effectAllowed = 'move';
54
+ dragState.nodeId = targetEl.dataset.nodeId;
53
55
 
54
56
  try {
55
57
  event.dataTransfer.setData(
@@ -70,30 +72,52 @@ export const useDrag = (services: Services | undefined) => {
70
72
  const labelEl = targetEl.children[0];
71
73
  if (!labelEl) return;
72
74
 
75
+ removeClassName(labelEl, 'drag-before', 'drag-after', 'drag-inner');
76
+
73
77
  const { top: targetTop, height: targetHeight } = labelEl.getBoundingClientRect();
74
78
 
75
79
  const distance = event.clientY - targetTop;
76
80
  const isContainer = targetEl.dataset.isContainer === 'true';
77
81
 
82
+ const targetNodeId = targetEl.dataset.nodeId;
83
+ const { nodeId } = dragState;
84
+ const parentsId = targetEl.dataset.parentsId?.split(',');
85
+
86
+ if (!targetNodeId) {
87
+ return;
88
+ }
89
+
90
+ // 如果是悬浮在拖动的节点上方,则不响应
91
+ if (parentsId) {
92
+ let targetIdIndex = -1;
93
+ for (let i = 0, l = parentsId.length; i < l; i++) {
94
+ const id = parentsId[i];
95
+ if (nodeId === id) {
96
+ targetIdIndex = i;
97
+ }
98
+
99
+ if (parentsId.includes(`${nodeId}`) && i >= targetIdIndex) {
100
+ return;
101
+ }
102
+ }
103
+ }
104
+
78
105
  if (distance < targetHeight / 3) {
79
106
  dragState.dropType = 'before';
80
107
  addClassName(labelEl, globalThis.document, 'drag-before');
81
- removeClassName(labelEl, 'drag-after', 'drag-inner');
82
108
  } else if (distance > (targetHeight * 2) / 3) {
83
109
  dragState.dropType = 'after';
84
110
  addClassName(labelEl, globalThis.document, 'drag-after');
85
- removeClassName(labelEl, 'drag-before', 'drag-inner');
86
111
  } else if (isContainer) {
87
112
  dragState.dropType = 'inner';
88
113
  addClassName(labelEl, globalThis.document, 'drag-inner');
89
- removeClassName(labelEl, 'drag-before', 'drag-after');
90
114
  }
91
115
 
92
116
  if (!dragState.dropType) {
93
117
  return;
94
118
  }
95
119
 
96
- dragState.dragOverNodeId = targetEl.dataset.nodeId || '';
120
+ dragState.dragOverNodeId = targetNodeId;
97
121
  dragState.container = event.currentTarget as HTMLElement;
98
122
 
99
123
  event.preventDefault();
@@ -899,9 +899,20 @@ class Editor extends BaseService {
899
899
 
900
900
  const newLayout = await this.getLayout(targetParent);
901
901
 
902
- for (const config of configs) {
902
+ // eslint-disable-next-line no-restricted-syntax
903
+ forConfigs: for (const config of configs) {
903
904
  const { parent, node: curNode } = this.getNodeInfo(config.id, false);
904
- if (!parent || !curNode) throw new Error('找不要删除的节点');
905
+ if (!parent || !curNode) {
906
+ continue;
907
+ }
908
+
909
+ const path = getNodePath(curNode.id, parent.items);
910
+
911
+ for (const node of path) {
912
+ if (targetParent.id === node.id) {
913
+ continue forConfigs;
914
+ }
915
+ }
905
916
 
906
917
  const index = getNodeIndex(curNode.id, parent);
907
918
 
package/src/type.ts CHANGED
@@ -81,6 +81,7 @@ export interface WorkspaceSlots {
81
81
 
82
82
  export interface ComponentListPanelSlots {
83
83
  'component-list-panel-header'(props: {}): any;
84
+ 'component-list'(props: { componentGroupList: ComponentGroup[] }): any;
84
85
  'component-list-item'(props: { component: ComponentItem }): any;
85
86
  }
86
87
 
@@ -384,6 +385,10 @@ export interface SideComponent extends MenuComponent {
384
385
  icon?: any;
385
386
  /** slide 唯一标识 key */
386
387
  $key: string;
388
+ /** 是否可以将面板拖出,默认为true */
389
+ draggable?: boolean;
390
+ /** 点击切换tab前调用,返回false阻止切换 */
391
+ beforeClick?: (config: SideComponent) => boolean | Promise<boolean>;
387
392
 
388
393
  /** 组件扩展参数 */
389
394
  boxComponentConfig?: {