@tmagic/editor 1.7.8 → 1.7.10

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 (47) hide show
  1. package/dist/es/Editor.vue_vue_type_script_setup_true_lang.js +3 -1
  2. package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +17 -7
  3. package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +38 -9
  4. package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +3 -1
  5. package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +38 -5
  6. package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +3 -2
  7. package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +5 -0
  8. package/dist/es/fields/StyleSetter/pro/Transform.js +5 -0
  9. package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +54 -0
  10. package/dist/es/hooks/use-code-block-edit.js +3 -2
  11. package/dist/es/index.js +3 -3
  12. package/dist/es/initService.js +4 -4
  13. package/dist/es/layouts/workspace/viewer/Stage.vue_vue_type_script_setup_true_lang.js +64 -1
  14. package/dist/es/layouts/workspace/viewer/StageOverlay.vue_vue_type_script_setup_true_lang.js +1 -5
  15. package/dist/es/services/editor.js +191 -174
  16. package/dist/es/services/history.js +1 -9
  17. package/dist/es/type.js +28 -1
  18. package/dist/es/utils/dep/worker.js +1 -1
  19. package/dist/es/utils/editor-history.js +100 -0
  20. package/dist/es/utils/editor.js +178 -4
  21. package/dist/es/utils/props.js +4 -1
  22. package/dist/es/utils/undo-redo.js +7 -4
  23. package/dist/tmagic-editor.umd.cjs +744 -234
  24. package/package.json +8 -8
  25. package/src/Editor.vue +1 -0
  26. package/src/components/CodeBlockEditor.vue +1 -1
  27. package/src/components/CodeParams.vue +23 -7
  28. package/src/editorProps.ts +2 -0
  29. package/src/fields/DataSourceFieldSelect/FieldSelect.vue +39 -6
  30. package/src/fields/DataSourceFieldSelect/Index.vue +1 -0
  31. package/src/fields/DataSourceMethodSelect.vue +54 -13
  32. package/src/fields/DataSourceMethods.vue +9 -5
  33. package/src/fields/StyleSetter/Index.vue +5 -1
  34. package/src/fields/StyleSetter/pro/Transform.vue +54 -0
  35. package/src/fields/StyleSetter/pro/index.ts +1 -0
  36. package/src/hooks/use-code-block-edit.ts +8 -4
  37. package/src/initService.ts +6 -6
  38. package/src/layouts/workspace/viewer/Stage.vue +89 -1
  39. package/src/layouts/workspace/viewer/StageOverlay.vue +1 -6
  40. package/src/services/editor.ts +231 -270
  41. package/src/services/history.ts +1 -9
  42. package/src/type.ts +65 -5
  43. package/src/utils/editor-history.ts +138 -0
  44. package/src/utils/editor.ts +249 -4
  45. package/src/utils/props.ts +4 -0
  46. package/src/utils/undo-redo.ts +7 -6
  47. package/types/index.d.ts +179 -53
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.7.8",
2
+ "version": "1.7.10",
3
3
  "name": "@tmagic/editor",
4
4
  "type": "module",
5
5
  "sideEffects": [
@@ -58,11 +58,11 @@
58
58
  "moveable": "^0.53.0",
59
59
  "serialize-javascript": "^7.0.0",
60
60
  "sortablejs": "^1.15.6",
61
- "@tmagic/design": "1.7.8",
62
- "@tmagic/table": "1.7.8",
63
- "@tmagic/stage": "1.7.8",
64
- "@tmagic/form": "1.7.8",
65
- "@tmagic/utils": "1.7.8"
61
+ "@tmagic/design": "1.7.10",
62
+ "@tmagic/form": "1.7.10",
63
+ "@tmagic/table": "1.7.10",
64
+ "@tmagic/stage": "1.7.10",
65
+ "@tmagic/utils": "1.7.10"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/events": "^3.0.3",
@@ -74,9 +74,9 @@
74
74
  "peerDependencies": {
75
75
  "monaco-editor": "^0.55.1 ",
76
76
  "type-fest": "^5.2.0",
77
- "typescript": "^5.9.3",
77
+ "typescript": "^6.0.2",
78
78
  "vue": "^3.5.24",
79
- "@tmagic/core": "1.7.8"
79
+ "@tmagic/core": "1.7.10"
80
80
  },
81
81
  "peerDependenciesMeta": {
82
82
  "typescript": {
package/src/Editor.vue CHANGED
@@ -207,6 +207,7 @@ const stageOptions: StageOptions = {
207
207
  renderType: props.renderType,
208
208
  guidesOptions: props.guidesOptions,
209
209
  disabledMultiSelect: props.disabledMultiSelect,
210
+ beforeDblclick: props.beforeDblclick,
210
211
  };
211
212
 
212
213
  stageOverlayService.set('stageOptions', stageOptions);
@@ -88,7 +88,7 @@ const width = defineModel<number>('width', { default: 670 });
88
88
  const boxVisible = defineModel<boolean>('visible', { default: false });
89
89
 
90
90
  const props = defineProps<{
91
- content: CodeBlockContent;
91
+ content: Omit<CodeBlockContent, 'content'> & { content: string };
92
92
  disabled?: boolean;
93
93
  isDataSource?: boolean;
94
94
  dataSourceType?: string;
@@ -46,13 +46,29 @@ const getFormConfig = (items: FormItemConfig[] = []) => [
46
46
 
47
47
  const codeParamsConfig = computed(() =>
48
48
  getFormConfig(
49
- props.paramsConfig.map(({ name, text, extra, ...config }) => ({
50
- type: 'data-source-field-select',
51
- name,
52
- text,
53
- extra,
54
- fieldConfig: config as FormItemConfig,
55
- })),
49
+ props.paramsConfig.map(({ name, text, extra, ...config }) => {
50
+ let { type } = config;
51
+ if (typeof type === 'function') {
52
+ type = type(undefined, {
53
+ model: props.model[props.name],
54
+ });
55
+ }
56
+ if (type && ['data-source-field-select', 'vs-code'].includes(type)) {
57
+ return {
58
+ ...config,
59
+ name,
60
+ text,
61
+ extra,
62
+ };
63
+ }
64
+ return {
65
+ type: 'data-source-field-select' as const,
66
+ name,
67
+ text,
68
+ extra,
69
+ fieldConfig: config,
70
+ };
71
+ }) as FormItemConfig[],
56
72
  ),
57
73
  );
58
74
 
@@ -98,6 +98,8 @@ export interface EditorProps {
98
98
  isContainer?: (el: HTMLElement) => boolean | Promise<boolean>;
99
99
  /** 用于自定义组件树与画布的右键菜单 */
100
100
  customContentMenu?: CustomContentMenuFunction;
101
+ /** 画布双击前的钩子函数,返回 false 则阻止默认的双击行为 */
102
+ beforeDblclick?: (event: MouseEvent) => Promise<boolean | void> | boolean | void;
101
103
  extendFormState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
102
104
  /** 页面顺序拖拽配置参数 */
103
105
  pageBarSortOptions?: PageBarSortOptions;
@@ -1,6 +1,21 @@
1
1
  <template>
2
2
  <div class="m-editor-data-source-field-select">
3
- <template v-if="checkStrictly">
3
+ <template v-if="dataSourceId">
4
+ <TMagicCascader
5
+ :model-value="selectFieldsId"
6
+ clearable
7
+ filterable
8
+ :size="size"
9
+ :disabled="disabled"
10
+ :options="fieldsOptions"
11
+ :props="{
12
+ checkStrictly,
13
+ }"
14
+ @change="fieldChangeHandler"
15
+ ></TMagicCascader>
16
+ </template>
17
+
18
+ <template v-else-if="checkStrictly">
4
19
  <TMagicSelect
5
20
  :model-value="selectDataSourceId"
6
21
  clearable
@@ -92,6 +107,8 @@ const props = defineProps<{
92
107
  dataSourceFieldType?: DataSourceFieldType[];
93
108
  /** 是否可以编辑数据源,disable表示的是是否可以选择数据源 */
94
109
  notEditable?: boolean | FilterFunction;
110
+ /** 指定数据源ID,限定只能选择该数据源的字段 */
111
+ dataSourceId?: string;
95
112
  }>();
96
113
 
97
114
  const emit = defineEmits<{
@@ -106,7 +123,12 @@ const { dataSourceService, uiService } = useServices();
106
123
  const mForm = inject<FormState | undefined>('mForm');
107
124
  const eventBus = inject<EventBus>('eventBus');
108
125
 
109
- const dataSources = computed(() => dataSourceService.get('dataSources') || []);
126
+ const allDataSources = computed(() => dataSourceService.get('dataSources') || []);
127
+
128
+ const dataSources = computed(() => {
129
+ if (!props.dataSourceId) return allDataSources.value;
130
+ return allDataSources.value.filter((ds) => ds.id === props.dataSourceId);
131
+ });
110
132
 
111
133
  const valueIsKey = computed(() => props.value === 'key');
112
134
  const notEditable = computed(() => filterFunction(mForm, props.notEditable, props));
@@ -125,7 +147,13 @@ const selectFieldsId = ref<string[]>([]);
125
147
  watch(
126
148
  modelValue,
127
149
  (value) => {
128
- if (Array.isArray(value)) {
150
+ if (props.dataSourceId) {
151
+ const dsIdValue = valueIsKey.value
152
+ ? props.dataSourceId
153
+ : `${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}${props.dataSourceId}`;
154
+ selectDataSourceId.value = dsIdValue;
155
+ selectFieldsId.value = Array.isArray(value) ? value : [];
156
+ } else if (Array.isArray(value) && value.length) {
129
157
  const [dsId, ...fields] = value;
130
158
  selectDataSourceId.value = dsId;
131
159
  selectFieldsId.value = fields;
@@ -140,7 +168,7 @@ watch(
140
168
  );
141
169
 
142
170
  const fieldsOptions = computed(() => {
143
- const ds = dataSources.value.find((ds) => ds.id === removeDataSourceFieldPrefix(selectDataSourceId.value));
171
+ const ds = allDataSources.value.find((ds) => ds.id === removeDataSourceFieldPrefix(selectDataSourceId.value));
144
172
 
145
173
  if (!ds) return [];
146
174
 
@@ -163,8 +191,13 @@ const dsChangeHandler = (v: string) => {
163
191
  };
164
192
 
165
193
  const fieldChangeHandler = (v: string[] = []) => {
166
- modelValue.value = [selectDataSourceId.value, ...v];
167
- emit('change', modelValue.value);
194
+ if (props.dataSourceId) {
195
+ modelValue.value = v;
196
+ emit('change', v);
197
+ } else {
198
+ modelValue.value = [selectDataSourceId.value, ...v];
199
+ emit('change', modelValue.value);
200
+ }
168
201
  };
169
202
 
170
203
  const onChangeHandler = (v: string[] = []) => {
@@ -8,6 +8,7 @@
8
8
  :value="config.value"
9
9
  :checkStrictly="checkStrictly"
10
10
  :dataSourceFieldType="config.dataSourceFieldType"
11
+ :dataSourceId="config.dataSourceId"
11
12
  @change="onChangeHandler"
12
13
  ></FieldSelect>
13
14
 
@@ -52,12 +52,14 @@ import {
52
52
  type FormState,
53
53
  MCascader,
54
54
  } from '@tmagic/form';
55
+ import { DATA_SOURCE_SET_DATA_METHOD_NAME } from '@tmagic/utils';
55
56
 
56
57
  import CodeParams from '@editor/components/CodeParams.vue';
57
58
  import MIcon from '@editor/components/Icon.vue';
58
59
  import { useServices } from '@editor/hooks/use-services';
59
60
  import type { CodeParamStatement, EventBus } from '@editor/type';
60
61
  import { SideItemKey } from '@editor/type';
62
+ import { getFieldType } from '@editor/utils';
61
63
 
62
64
  defineOptions({
63
65
  name: 'MFieldsDataSourceMethodSelect',
@@ -92,6 +94,43 @@ const isCustomMethod = computed(() => {
92
94
  const getParamItemsConfig = ([dataSourceId, methodName]: [Id, string] = ['', '']): CodeParamStatement[] => {
93
95
  if (!dataSourceId) return [];
94
96
 
97
+ if (methodName === DATA_SOURCE_SET_DATA_METHOD_NAME) {
98
+ return [
99
+ {
100
+ name: 'field',
101
+ text: '字段',
102
+ type: 'data-source-field-select',
103
+ dataSourceId,
104
+ checkStrictly: true,
105
+ },
106
+ {
107
+ name: 'data',
108
+ text: '数据',
109
+ type: (_formState, { model }) => {
110
+ const fieldType = getFieldType(dataSourceService.getDataSourceById(`${dataSourceId}`), model.field);
111
+
112
+ let type = 'vs-code';
113
+
114
+ if (fieldType === 'number') {
115
+ type = 'number';
116
+ } else if (fieldType === 'string') {
117
+ type = 'text';
118
+ } else if (fieldType === 'boolean') {
119
+ type = 'switch';
120
+ }
121
+
122
+ return type;
123
+ },
124
+ language: 'javascript',
125
+ options: inject('codeOptions', {}),
126
+ autosize: {
127
+ minRows: 1,
128
+ maxRows: 10,
129
+ },
130
+ },
131
+ ];
132
+ }
133
+
95
134
  const paramStatements = dataSources.value
96
135
  ?.find((item) => item.id === dataSourceId)
97
136
  ?.methods?.find((item) => item.name === methodName)?.params;
@@ -108,19 +147,21 @@ const paramsConfig = ref<CodeParamStatement[]>(getParamItemsConfig(props.model[p
108
147
 
109
148
  const methodsOptions = computed(
110
149
  () =>
111
- dataSources.value
112
- ?.filter((ds) => ds.methods?.length || dataSourceService.getFormMethod(ds.type).length)
113
- ?.map((ds) => ({
114
- label: ds.title || ds.id,
115
- value: ds.id,
116
- children: [
117
- ...(dataSourceService?.getFormMethod(ds.type) || []),
118
- ...(ds.methods || []).map((method) => ({
119
- label: method.name,
120
- value: method.name,
121
- })),
122
- ],
123
- })) || [],
150
+ dataSources.value?.map((ds) => ({
151
+ label: ds.title || ds.id,
152
+ value: ds.id,
153
+ children: [
154
+ {
155
+ label: '设置数据',
156
+ value: DATA_SOURCE_SET_DATA_METHOD_NAME,
157
+ },
158
+ ...(dataSourceService?.getFormMethod(ds.type) || []),
159
+ ...(ds.methods || []).map((method) => ({
160
+ label: method.name,
161
+ value: method.name,
162
+ })),
163
+ ],
164
+ })) || [],
124
165
  );
125
166
 
126
167
  const cascaderConfig = computed<CascaderConfig>(() => ({
@@ -42,7 +42,7 @@ const props = withDefaults(defineProps<FieldProps<DataSourceMethodsConfig>>(), {
42
42
 
43
43
  const emit = defineEmits(['change']);
44
44
 
45
- const codeConfig = ref<CodeBlockContent>();
45
+ const codeConfig = ref<Omit<CodeBlockContent, 'content'> & { content: string }>();
46
46
  const codeBlockEditorRef = useTemplateRef<InstanceType<typeof CodeBlockEditor>>('codeBlockEditor');
47
47
 
48
48
  let editIndex = -1;
@@ -72,10 +72,14 @@ const methodColumns: ColumnConfig[] = [
72
72
  {
73
73
  text: '编辑',
74
74
  handler: (method: CodeBlockContent, index: number) => {
75
- let codeContent = method.content || '({ params, dataSource, app }) => {\n // place your code here\n}';
76
-
77
- if (typeof codeContent !== 'string') {
78
- codeContent = codeContent.toString();
75
+ let codeContent: string = '({ params, dataSource, app }) => {\n // place your code here\n}';
76
+
77
+ if (method.content) {
78
+ if (typeof method.content !== 'string') {
79
+ codeContent = method.content.toString();
80
+ } else {
81
+ codeContent = method.content;
82
+ }
79
83
  }
80
84
 
81
85
  codeConfig.value = {
@@ -26,7 +26,7 @@ import type { StyleSchema } from '@tmagic/schema';
26
26
 
27
27
  import MIcon from '@editor/components/Icon.vue';
28
28
 
29
- import { Background, Border, Font, Layout, Position } from './pro/';
29
+ import { Background, Border, Font, Layout, Position, Transform } from './pro/';
30
30
 
31
31
  defineOptions({
32
32
  name: 'MFieldsStyleSetter',
@@ -60,6 +60,10 @@ const list = [
60
60
  title: '边框与圆角',
61
61
  component: Border,
62
62
  },
63
+ {
64
+ title: '变形',
65
+ component: Transform,
66
+ },
63
67
  ];
64
68
 
65
69
  const collapseValue = shallowRef(
@@ -0,0 +1,54 @@
1
+ <template>
2
+ <MContainer :config="config" :model="values" :size="size" :disabled="disabled" @change="change"></MContainer>
3
+ </template>
4
+
5
+ <script lang="ts" setup>
6
+ import { type ContainerChangeEventData, defineFormItem, MContainer } from '@tmagic/form';
7
+ import type { StyleSchema } from '@tmagic/schema';
8
+
9
+ defineProps<{
10
+ values: Partial<StyleSchema>;
11
+ disabled?: boolean;
12
+ size?: 'large' | 'default' | 'small';
13
+ }>();
14
+
15
+ const emit = defineEmits<{
16
+ change: [v: StyleSchema, eventData: ContainerChangeEventData];
17
+ }>();
18
+
19
+ const config = defineFormItem({
20
+ items: [
21
+ {
22
+ name: 'transform',
23
+ items: [
24
+ {
25
+ name: 'rotate',
26
+ text: '旋转角度',
27
+ labelWidth: '68px',
28
+ type: 'data-source-field-select',
29
+ checkStrictly: false,
30
+ dataSourceFieldType: ['string', 'number'],
31
+ fieldConfig: {
32
+ type: 'text',
33
+ },
34
+ },
35
+ {
36
+ name: 'scale',
37
+ text: '缩放',
38
+ labelWidth: '68px',
39
+ type: 'data-source-field-select',
40
+ checkStrictly: false,
41
+ dataSourceFieldType: ['string', 'number'],
42
+ fieldConfig: {
43
+ type: 'text',
44
+ },
45
+ },
46
+ ],
47
+ },
48
+ ],
49
+ });
50
+
51
+ const change = (value: StyleSchema, eventData: ContainerChangeEventData) => {
52
+ emit('change', value, eventData);
53
+ };
54
+ </script>
@@ -3,3 +3,4 @@ export { default as Font } from './Font.vue';
3
3
  export { default as Layout } from './Layout.vue';
4
4
  export { default as Position } from './Position.vue';
5
5
  export { default as Border } from './Border.vue';
6
+ export { default as Transform } from './Transform.vue';
@@ -8,7 +8,7 @@ import CodeBlockEditor from '@editor/components/CodeBlockEditor.vue';
8
8
  import type { Services } from '@editor/type';
9
9
 
10
10
  export const useCodeBlockEdit = (codeBlockService: Services['codeBlockService']) => {
11
- const codeConfig = ref<CodeBlockContent>();
11
+ const codeConfig = ref<Omit<CodeBlockContent, 'content'> & { content: string }>();
12
12
  const codeId = ref<string>();
13
13
  const codeBlockEditorRef = useTemplateRef<InstanceType<typeof CodeBlockEditor>>('codeBlockEditor');
14
14
 
@@ -36,10 +36,14 @@ export const useCodeBlockEdit = (codeBlockService: Services['codeBlockService'])
36
36
  return;
37
37
  }
38
38
 
39
- let codeContent = codeBlock.content;
39
+ let codeContent = '';
40
40
 
41
- if (typeof codeContent !== 'string') {
42
- codeContent = codeContent.toString();
41
+ if (codeBlock.content) {
42
+ if (typeof codeBlock.content !== 'string') {
43
+ codeContent = codeBlock.content.toString();
44
+ } else {
45
+ codeContent = codeBlock.content;
46
+ }
43
47
  }
44
48
 
45
49
  codeConfig.value = {
@@ -233,7 +233,7 @@ export const initServiceEvents = (
233
233
  ((event: 'update:modelValue', value: MApp | null) => void),
234
234
  { editorService, codeBlockService, dataSourceService, depService }: Services,
235
235
  ) => {
236
- let getTMagicAppPrimise: Promise<TMagicCore | undefined> | null = null;
236
+ let getTMagicAppPromise: Promise<TMagicCore | undefined> | null = null;
237
237
 
238
238
  const getTMagicApp = async (): Promise<TMagicCore | undefined> => {
239
239
  const stage = await getStage();
@@ -246,11 +246,11 @@ export const initServiceEvents = (
246
246
  return renderer.runtime.getApp?.();
247
247
  }
248
248
 
249
- if (getTMagicAppPrimise) {
250
- return getTMagicAppPrimise;
249
+ if (getTMagicAppPromise) {
250
+ return getTMagicAppPromise;
251
251
  }
252
252
 
253
- getTMagicAppPrimise = new Promise<TMagicCore | undefined>((resolve) => {
253
+ getTMagicAppPromise = new Promise<TMagicCore | undefined>((resolve) => {
254
254
  // 设置 10s 超时
255
255
  const timeout = globalThis.setTimeout(() => {
256
256
  resolve(void 0);
@@ -264,7 +264,7 @@ export const initServiceEvents = (
264
264
  });
265
265
  });
266
266
 
267
- return getTMagicAppPrimise;
267
+ return getTMagicAppPromise;
268
268
  };
269
269
 
270
270
  const updateStageNodes = (nodes: MComponent[]) => {
@@ -560,7 +560,7 @@ export const initServiceEvents = (
560
560
  depService.clear(nodes);
561
561
  };
562
562
 
563
- // 由于历史记录变化是更新整个page,所以历史记录变化时,需要重新收集依赖
563
+ // 历史记录变化时,需要重新收集依赖
564
564
  const historyChangeHandler = (page: MPage | MPageFragment) => {
565
565
  collectIdle([page], true).then(() => {
566
566
  updateStageNode(page);
@@ -80,7 +80,7 @@ const props = withDefaults(
80
80
  let stage: StageCore | null = null;
81
81
  let runtime: Runtime | null = null;
82
82
 
83
- const { editorService, uiService, keybindingService } = useServices();
83
+ const { editorService, uiService, keybindingService, stageOverlayService } = useServices();
84
84
 
85
85
  const stageLoading = computed(() => editorService.get('stageLoading'));
86
86
 
@@ -97,6 +97,60 @@ const page = computed(() => editorService.get('page'));
97
97
  const zoom = computed(() => uiService.get('zoom'));
98
98
  const node = computed(() => editorService.get('node'));
99
99
 
100
+ /**
101
+ * 判断元素是否被非页面级的滚动容器裁剪(未完整显示)
102
+ *
103
+ * 从元素向上遍历祖先节点,跳过页面/页面片容器,
104
+ * 检查是否存在设置了 overflow 的滚动容器将该元素裁剪,
105
+ * 只有元素未被完整显示时才需要打开 overlay 以展示完整内容
106
+ */
107
+ const isClippedByScrollContainer = (el: HTMLElement): boolean => {
108
+ const win = el.ownerDocument.defaultView;
109
+ if (!win) return false;
110
+
111
+ // 收集所有页面和页面片的 id
112
+ const root = editorService.get('root');
113
+ const pageIds = new Set(root?.items?.map((item) => `${item.id}`) ?? []);
114
+
115
+ // el 本身就是页面或页面片,无需判断
116
+ const elId = getIdFromEl()(el);
117
+ if (elId && pageIds.has(elId)) return false;
118
+
119
+ let parent = el.parentElement;
120
+
121
+ while (parent && parent !== el.ownerDocument.documentElement) {
122
+ const parentId = getIdFromEl()(parent);
123
+
124
+ // 到达页面或页面片层级,不再继续向上查找
125
+ if (parentId && pageIds.has(parentId)) {
126
+ return false;
127
+ }
128
+
129
+ const { overflowX, overflowY } = win.getComputedStyle(parent);
130
+
131
+ if (
132
+ ['auto', 'scroll', 'hidden'].includes(overflowX) ||
133
+ ['auto', 'scroll', 'hidden'].includes(overflowY) ||
134
+ parent.scrollWidth > parent.clientWidth ||
135
+ parent.scrollHeight > parent.clientHeight
136
+ ) {
137
+ // 比较元素与容器的可视区域,判断元素是否被裁剪
138
+ const elRect = el.getBoundingClientRect();
139
+ const containerRect = parent.getBoundingClientRect();
140
+ if (
141
+ elRect.top < containerRect.top ||
142
+ elRect.left < containerRect.left ||
143
+ elRect.bottom > containerRect.bottom ||
144
+ elRect.right > containerRect.right
145
+ ) {
146
+ return true;
147
+ }
148
+ }
149
+ parent = parent.parentElement;
150
+ }
151
+ return false;
152
+ };
153
+
100
154
  watchEffect(() => {
101
155
  if (stage || !page.value) return;
102
156
 
@@ -109,6 +163,40 @@ watchEffect(() => {
109
163
  stageWrapRef.value?.container?.focus();
110
164
  });
111
165
 
166
+ stage.on('dblclick', async (event: MouseEvent) => {
167
+ if (props.stageOptions.beforeDblclick) {
168
+ const result = await props.stageOptions.beforeDblclick(event);
169
+ if (result === false) return;
170
+ }
171
+
172
+ const el = (await stage?.actionManager?.getElementFromPoint(event)) || null;
173
+ if (!el) return;
174
+
175
+ const id = getIdFromEl()(el);
176
+
177
+ if (id) {
178
+ const node = editorService.getNodeById(id);
179
+ if (node?.type === 'page-fragment-container' && node.pageFragmentId) {
180
+ await editorService.select(node.pageFragmentId);
181
+ return;
182
+ }
183
+ }
184
+
185
+ if (!props.disabledStageOverlay && isClippedByScrollContainer(el)) {
186
+ stageOverlayService.openOverlay(el);
187
+ return;
188
+ }
189
+
190
+ const nextEl = (await stage?.actionManager?.getNextElementFromPoint(event)) || null;
191
+ if (nextEl) {
192
+ const nextId = getIdFromEl()(nextEl);
193
+ if (nextId) {
194
+ await editorService.select(nextId);
195
+ editorService.get('stage')?.select(nextId);
196
+ }
197
+ }
198
+ });
199
+
112
200
  editorService.set('stage', markRaw(stage));
113
201
 
114
202
  stage.mount(stageContainerEl.value);
@@ -46,12 +46,7 @@ const style = computed(() => ({
46
46
  }));
47
47
 
48
48
  watch(stage, (stage) => {
49
- if (stage) {
50
- stage.on('dblclick', async (event: MouseEvent) => {
51
- const el = (await stage.actionManager?.getElementFromPoint(event)) || null;
52
- stageOverlayService.openOverlay(el);
53
- });
54
- } else {
49
+ if (!stage) {
55
50
  stageOverlayService.closeOverlay();
56
51
  }
57
52
  });