@tmagic/editor 1.7.14-beta.1 → 1.7.14-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.
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.7.14-beta.1",
2
+ "version": "1.7.14-beta.3",
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.14-beta.1",
62
- "@tmagic/form": "1.7.14-beta.1",
63
- "@tmagic/table": "1.7.14-beta.1",
64
- "@tmagic/stage": "1.7.14-beta.1",
65
- "@tmagic/utils": "1.7.14-beta.1"
61
+ "@tmagic/design": "1.7.14-beta.3",
62
+ "@tmagic/form": "1.7.14-beta.3",
63
+ "@tmagic/stage": "1.7.14-beta.3",
64
+ "@tmagic/table": "1.7.14-beta.3",
65
+ "@tmagic/utils": "1.7.14-beta.3"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/events": "^3.0.3",
@@ -75,8 +75,8 @@
75
75
  "monaco-editor": "^0.55.1 ",
76
76
  "type-fest": "^5.2.0",
77
77
  "typescript": "^6.0.3",
78
- "vue": "^3.5.33",
79
- "@tmagic/core": "1.7.14-beta.1"
78
+ "vue": "^3.5.34",
79
+ "@tmagic/core": "1.7.14-beta.3"
80
80
  },
81
81
  "peerDependenciesMeta": {
82
82
  "typescript": {
@@ -1,6 +1,7 @@
1
1
  <template>
2
2
  <div
3
3
  v-if="display"
4
+ ref="rootEl"
4
5
  class="menu-item"
5
6
  :class="`${data.type} ${data.className || ''}`"
6
7
  @click="clickHandler(data, $event)"
@@ -56,7 +57,7 @@
56
57
  </template>
57
58
 
58
59
  <script lang="ts" setup>
59
- import { computed } from 'vue';
60
+ import { computed, useTemplateRef } from 'vue';
60
61
  import { ArrowDown } from '@element-plus/icons-vue';
61
62
 
62
63
  import {
@@ -93,6 +94,9 @@ const props = withDefaults(
93
94
  );
94
95
  const services = useServices();
95
96
 
97
+ const rootElRef = useTemplateRef<HTMLDivElement>('rootEl');
98
+ const getElRef = () => rootElRef;
99
+
96
100
  const disabled = computed(() => {
97
101
  if (typeof props.data === 'string') return false;
98
102
  if (props.data.type === 'component') return false;
@@ -145,4 +149,6 @@ const mouseupHandler = (item: MenuButton | MenuComponent, event: MouseEvent) =>
145
149
  buttonHandler(item, event);
146
150
  }
147
151
  };
152
+
153
+ defineExpose({ getElRef });
148
154
  </script>
@@ -214,24 +214,32 @@ const setEditorValue = (v: string | any, m: string | any) => {
214
214
  if (props.type === 'diff') {
215
215
  const originalModel = monaco.editor.createModel(values.value, 'text/javascript');
216
216
  const modifiedModel = monaco.editor.createModel(toString(m, props.language), 'text/javascript');
217
- const position = vsDiffEditor?.getPosition();
217
+ // 保存视图状态(光标、选区、滚动、折叠等)
218
+ const viewState = vsDiffEditor?.saveViewState();
218
219
  const result = vsDiffEditor?.setModel({
219
220
  original: originalModel,
220
221
  modified: modifiedModel,
221
222
  });
222
- if (position) {
223
- vsDiffEditor?.setPosition(position);
224
- vsDiffEditor?.focus();
223
+ // setAutoHeight 内部会在 nextTick 中将 scrollTop 重置为 0,这里也放到 nextTick 中
224
+ // 利用 Vue nextTick 队列的 FIFO 特性,保证恢复在重置之后执行
225
+ if (viewState) {
226
+ nextTick(() => {
227
+ vsDiffEditor?.restoreViewState(viewState);
228
+ vsDiffEditor?.focus();
229
+ });
225
230
  }
226
231
  return result;
227
232
  }
228
- // 保存当前光标位置
229
- const position = vsEditor?.getPosition();
233
+ // 保存视图状态(光标、选区、滚动、折叠等)
234
+ const viewState = vsEditor?.saveViewState();
230
235
  const result = vsEditor?.setValue(values.value);
231
- // 恢复光标位置
232
- if (position) {
233
- vsEditor?.setPosition(position);
234
- vsEditor?.focus();
236
+ // setAutoHeight 内部会在 nextTick 中将 scrollTop 重置为 0,这里也放到 nextTick 中
237
+ // 利用 Vue nextTick 队列的 FIFO 特性,保证恢复在重置之后执行
238
+ if (viewState) {
239
+ nextTick(() => {
240
+ vsEditor?.restoreViewState(viewState);
241
+ vsEditor?.focus();
242
+ });
235
243
  }
236
244
  return result;
237
245
  };
@@ -1,8 +1,12 @@
1
1
  <template>
2
2
  <div class="m-editor-nav-menu" :style="{ height: `${height}px` }" ref="navMenu">
3
- <div v-for="key in keys" :class="`menu-${key}`" :key="key" :style="`width: ${columnWidth?.[key]}px`">
4
- <ToolButton :data="item" v-for="(item, index) in buttons[key]" :key="index"></ToolButton>
5
- </div>
3
+ <NavMenuColumn
4
+ v-for="key in keys"
5
+ :key="key"
6
+ :column-key="key"
7
+ :items="buttons[key]"
8
+ :width="columnWidth?.[key]"
9
+ ></NavMenuColumn>
6
10
  </div>
7
11
  </template>
8
12
 
@@ -12,10 +16,11 @@ import { Back, Delete, FullScreen, Grid, Memo, Right, ScaleToOriginal, ZoomIn, Z
12
16
 
13
17
  import { NodeType } from '@tmagic/core';
14
18
 
15
- import ToolButton from '@editor/components/ToolButton.vue';
16
19
  import { useServices } from '@editor/hooks/use-services';
17
20
  import { ColumnLayout, MenuBarData, MenuButton, MenuComponent, MenuItem } from '@editor/type';
18
21
 
22
+ import NavMenuColumn from './NavMenuColumn.vue';
23
+
19
24
  defineOptions({
20
25
  name: 'MEditorNavMenu',
21
26
  });
@@ -0,0 +1,252 @@
1
+ <template>
2
+ <div
3
+ :class="`menu-${columnKey} m-editor-nav-menu-column`"
4
+ :style="width != null ? `width: ${width}px` : ''"
5
+ ref="columnEl"
6
+ >
7
+ <ToolButton
8
+ v-for="(item, index) in items"
9
+ :data="item"
10
+ :key="`item-${index}`"
11
+ :class="{ 'm-editor-nav-menu-slot-hidden': hiddenIndexSet.has(index) }"
12
+ :ref="(comp: any) => setItemRef(comp, index)"
13
+ ></ToolButton>
14
+
15
+ <div
16
+ class="m-editor-nav-menu-more-wrapper"
17
+ :class="{ 'm-editor-nav-menu-more-wrapper-hidden': !hasOverflow }"
18
+ ref="moreWrapperEl"
19
+ >
20
+ <TMagicPopover
21
+ placement="bottom-end"
22
+ popper-class="m-editor-nav-menu-popover"
23
+ :width="popoverWidth"
24
+ :visible="popoverVisible"
25
+ >
26
+ <div class="m-editor-nav-menu-overflow-list">
27
+ <ToolButton v-for="(item, index) in overflowItems" :data="item" :key="`o-${index}`"></ToolButton>
28
+ </div>
29
+ <template #reference>
30
+ <div class="menu-item button m-editor-nav-menu-more" ref="referenceEl" @click="togglePopover">
31
+ <TMagicButton
32
+ size="small"
33
+ text
34
+ :icon="popoverVisible ? ArrowUp : ArrowDown"
35
+ :bg="popoverVisible"
36
+ :type="popoverVisible ? 'primary' : ''"
37
+ ></TMagicButton>
38
+ </div>
39
+ </template>
40
+ </TMagicPopover>
41
+ </div>
42
+ </div>
43
+ </template>
44
+
45
+ <script lang="ts" setup>
46
+ import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue';
47
+ import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
48
+
49
+ import { TMagicButton, TMagicPopover } from '@tmagic/design';
50
+
51
+ import ToolButton from '@editor/components/ToolButton.vue';
52
+ import { MenuButton, MenuComponent } from '@editor/type';
53
+
54
+ defineOptions({
55
+ name: 'MEditorNavMenuColumn',
56
+ });
57
+
58
+ const props = withDefaults(
59
+ defineProps<{
60
+ columnKey: string;
61
+ items: (MenuButton | MenuComponent)[];
62
+ width?: number;
63
+ /** 子元素之间的间距,需与 SCSS gap 保持一致 */
64
+ gap?: number;
65
+ /** Popover 内容宽度 */
66
+ popoverWidth?: number;
67
+ }>(),
68
+ {
69
+ items: () => [],
70
+ gap: 3,
71
+ popoverWidth: 180,
72
+ },
73
+ );
74
+
75
+ const columnEl = useTemplateRef<HTMLDivElement>('columnEl');
76
+ const moreWrapperEl = useTemplateRef<HTMLDivElement>('moreWrapperEl');
77
+
78
+ const popoverVisible = ref(false);
79
+ const togglePopover = () => {
80
+ popoverVisible.value = !popoverVisible.value;
81
+ };
82
+
83
+ const itemInstances = ref<(InstanceType<typeof ToolButton> | null)[]>([]);
84
+ let slotsRO: ResizeObserver | undefined;
85
+ const observedEls = new Set<HTMLElement>();
86
+
87
+ const setItemRef = (inst: InstanceType<typeof ToolButton> | null, index: number) => {
88
+ itemInstances.value[index] = inst ?? null;
89
+ };
90
+
91
+ const itemEls = computed<(HTMLElement | null)[]>(() =>
92
+ itemInstances.value.map((inst) => inst?.getElRef?.().value ?? null),
93
+ );
94
+
95
+ const reobserveSlots = () => {
96
+ if (!slotsRO) return;
97
+ for (const el of observedEls) slotsRO.unobserve(el);
98
+ observedEls.clear();
99
+ for (const el of itemEls.value) {
100
+ if (el) {
101
+ slotsRO.observe(el);
102
+ observedEls.add(el);
103
+ }
104
+ }
105
+ };
106
+
107
+ const cachedWidths = ref<Map<number, number>>(new Map());
108
+ const moreWidth = ref(0);
109
+ const containerWidth = ref(0);
110
+ const hiddenIndexSet = ref<Set<number>>(new Set());
111
+
112
+ const hasOverflow = computed(() => hiddenIndexSet.value.size > 0);
113
+ const overflowItems = computed(() => props.items.filter((_, index) => hiddenIndexSet.value.has(index)));
114
+
115
+ const measureAndCompute = () => {
116
+ if (!columnEl.value) return;
117
+
118
+ containerWidth.value = columnEl.value.clientWidth;
119
+
120
+ const els = itemEls.value;
121
+ for (let i = 0; i < props.items.length; i++) {
122
+ const el = els[i];
123
+ if (!el) {
124
+ cachedWidths.value.delete(i);
125
+ continue;
126
+ }
127
+ const w = el.getBoundingClientRect().width;
128
+ if (w > 0) {
129
+ cachedWidths.value.set(i, w);
130
+ } else {
131
+ cachedWidths.value.delete(i);
132
+ }
133
+ }
134
+
135
+ if (moreWrapperEl.value) {
136
+ const w = moreWrapperEl.value.getBoundingClientRect().width;
137
+ if (w > 0) moreWidth.value = w;
138
+ }
139
+
140
+ const total = props.items.length;
141
+ if (total === 0 || containerWidth.value <= 0) {
142
+ if (hiddenIndexSet.value.size > 0) hiddenIndexSet.value = new Set();
143
+ return;
144
+ }
145
+
146
+ let fullSum = 0;
147
+ let positive = 0;
148
+ for (let i = 0; i < total; i++) {
149
+ const w = cachedWidths.value.get(i) ?? 0;
150
+ if (w > 0) {
151
+ fullSum += w;
152
+ positive += 1;
153
+ }
154
+ }
155
+ fullSum += props.gap * Math.max(0, positive - 1);
156
+
157
+ // more 按钮位置始终保留,参与"是否放得下"的判断,避免出现时再次挤压
158
+ const effectiveMoreWidth = moreWidth.value > 0 ? moreWidth.value : 32;
159
+ const reservedMore = effectiveMoreWidth + (positive > 0 ? props.gap : 0);
160
+
161
+ let nextHidden: Set<number> | null = null;
162
+ if (fullSum + reservedMore <= containerWidth.value + 0.5) {
163
+ if (hiddenIndexSet.value.size > 0) nextHidden = new Set();
164
+ } else {
165
+ const newHidden = new Set<number>();
166
+ let used = effectiveMoreWidth;
167
+ let cutoff = -1;
168
+ for (let i = 0; i < total; i++) {
169
+ const w = cachedWidths.value.get(i) ?? 0;
170
+ if (w === 0) continue;
171
+ const need = props.gap + w;
172
+ if (used + need > containerWidth.value) {
173
+ cutoff = i;
174
+ break;
175
+ }
176
+ used += need;
177
+ }
178
+ if (cutoff >= 0) {
179
+ for (let j = cutoff; j < total; j++) newHidden.add(j);
180
+ }
181
+ nextHidden = newHidden;
182
+ }
183
+
184
+ if (nextHidden) {
185
+ const same =
186
+ nextHidden.size === hiddenIndexSet.value.size && [...nextHidden].every((v) => hiddenIndexSet.value.has(v));
187
+ if (!same) hiddenIndexSet.value = nextHidden;
188
+ }
189
+ };
190
+
191
+ let raf = 0;
192
+ const scheduleMeasure = () => {
193
+ if (raf) cancelAnimationFrame(raf);
194
+ raf = requestAnimationFrame(() => {
195
+ raf = 0;
196
+ measureAndCompute();
197
+ if (hasOverflow.value && moreWidth.value === 0) {
198
+ raf = requestAnimationFrame(() => {
199
+ raf = 0;
200
+ measureAndCompute();
201
+ });
202
+ }
203
+ });
204
+ };
205
+
206
+ watch(hasOverflow, (value) => {
207
+ if (!value) popoverVisible.value = false;
208
+ });
209
+
210
+ watch(
211
+ () => props.items,
212
+ () => {
213
+ cachedWidths.value = new Map();
214
+ hiddenIndexSet.value = new Set();
215
+ },
216
+ { deep: true },
217
+ );
218
+
219
+ watch(itemEls, () => {
220
+ cachedWidths.value = new Map();
221
+ reobserveSlots();
222
+ scheduleMeasure();
223
+ });
224
+
225
+ watch(
226
+ () => props.width,
227
+ () => scheduleMeasure(),
228
+ );
229
+
230
+ let ro: ResizeObserver | undefined;
231
+
232
+ onMounted(() => {
233
+ if (typeof ResizeObserver !== 'undefined') {
234
+ if (columnEl.value) {
235
+ ro = new ResizeObserver(() => scheduleMeasure());
236
+ ro.observe(columnEl.value);
237
+ }
238
+ slotsRO = new ResizeObserver(() => scheduleMeasure());
239
+ reobserveSlots();
240
+ }
241
+
242
+ scheduleMeasure();
243
+ });
244
+
245
+ onBeforeUnmount(() => {
246
+ if (raf) cancelAnimationFrame(raf);
247
+ ro?.disconnect();
248
+ slotsRO?.disconnect();
249
+ slotsRO = undefined;
250
+ observedEls.clear();
251
+ });
252
+ </script>