@tmagic/editor 1.8.0-beta.14 → 1.8.0-beta.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.
@@ -66,15 +66,29 @@ var ContentMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defi
66
66
  if (contains(target)) return;
67
67
  hide();
68
68
  };
69
+ const fixPosition = () => {
70
+ const menu = menuEl.value;
71
+ if (!menu || !visible.value) return;
72
+ const menuHeight = menu.clientHeight;
73
+ const menuWidth = menu.clientWidth;
74
+ let { top, left } = menuPosition.value;
75
+ if (top + menuHeight > document.body.clientHeight) top = Math.max(0, document.body.clientHeight - menuHeight);
76
+ if (left + menuWidth > document.body.clientWidth) left = Math.max(0, document.body.clientWidth - menuWidth);
77
+ if (top !== menuPosition.value.top || left !== menuPosition.value.left) menuPosition.value = {
78
+ top,
79
+ left
80
+ };
81
+ };
69
82
  const setPosition = (e) => {
70
- const menuHeight = menuEl.value?.clientHeight || 0;
71
- let top = e.clientY;
72
- if (menuHeight + e.clientY > document.body.clientHeight) top = document.body.clientHeight - menuHeight;
73
83
  menuPosition.value = {
74
- top,
84
+ top: e.clientY,
75
85
  left: e.clientX
76
86
  };
87
+ fixPosition();
77
88
  };
89
+ const resizeObserver = new ResizeObserver(() => {
90
+ fixPosition();
91
+ });
78
92
  const show = (e) => {
79
93
  visible.value = true;
80
94
  nextTick(() => {
@@ -103,10 +117,12 @@ var ContentMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defi
103
117
  emit("mouseenter");
104
118
  };
105
119
  onMounted(() => {
120
+ if (menuEl.value) resizeObserver.observe(menuEl.value);
106
121
  if (props.isSubMenu) return;
107
122
  globalThis.addEventListener("mousedown", outsideClickHideHandler, true);
108
123
  });
109
124
  onBeforeUnmount(() => {
125
+ resizeObserver.disconnect();
110
126
  if (props.isSubMenu) return;
111
127
  globalThis.removeEventListener("mousedown", outsideClickHideHandler, true);
112
128
  });
@@ -1,5 +1,6 @@
1
1
  import CodeEditor_default from "../layouts/CodeEditor.js";
2
- import { computed, createBlock, defineComponent, openBlock } from "vue";
2
+ import { FORM_SILENT_MODE_KEY } from "@tmagic/form";
3
+ import { computed, createBlock, createCommentVNode, defineComponent, inject, openBlock, unref } from "vue";
3
4
  //#region packages/editor/src/fields/Code.vue?vue&type=script&setup=true&lang.ts
4
5
  var Code_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
5
6
  name: "MFieldsVsCode",
@@ -24,6 +25,12 @@ var Code_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
24
25
  },
25
26
  emits: ["change"],
26
27
  setup(__props, { emit: __emit }) {
28
+ /**
29
+ * 静默模式(submitForm/validateForm 隐藏挂载)下跳过 monaco 渲染:
30
+ * 校验由 FormItem 针对 model 中的值完成,与编辑器实例无关;本组件挂载无值副作用
31
+ * (save 仅由用户操作/编辑器内容变更触发),跳过可省去 monaco worker/model 的无谓实例化。
32
+ */
33
+ const silentMode = inject(FORM_SILENT_MODE_KEY, false);
27
34
  const emit = __emit;
28
35
  const props = __props;
29
36
  /**
@@ -41,7 +48,8 @@ var Code_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
41
48
  emit("change", v);
42
49
  };
43
50
  return (_ctx, _cache) => {
44
- return openBlock(), createBlock(CodeEditor_default, {
51
+ return !unref(silentMode) ? (openBlock(), createBlock(CodeEditor_default, {
52
+ key: 0,
45
53
  height: __props.config.height,
46
54
  type: diffMode.value ? "diff" : void 0,
47
55
  "init-values": diffMode.value ? (__props.lastValues || {})[__props.name] : __props.model[__props.name],
@@ -65,7 +73,7 @@ var Code_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
65
73
  "autosize",
66
74
  "parse",
67
75
  "editor-custom-type"
68
- ]);
76
+ ])) : createCommentVNode("v-if", true);
69
77
  };
70
78
  }
71
79
  });
@@ -72,14 +72,14 @@ var PropsPanel_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
72
72
  ...v,
73
73
  style: {}
74
74
  };
75
- if (v.style) {
75
+ if (v.style) if (eventData) {
76
76
  Object.entries(v.style).forEach(([key, value]) => {
77
77
  if (value !== "" && newValue.style) newValue.style[key] = value;
78
78
  });
79
- eventData?.changeRecords?.forEach((record) => {
79
+ eventData.changeRecords?.forEach((record) => {
80
80
  if (record.propPath?.startsWith("style") && record.value === "") setValueByKeyPath(record.propPath, record.value, newValue);
81
81
  });
82
- }
82
+ } else newValue.style = { ...v.style };
83
83
  const historySource = eventData ? "props" : "code";
84
84
  editorService.update(newValue, {
85
85
  changeRecords: eventData?.changeRecords,
@@ -351,6 +351,11 @@ var Editor = class extends BaseService {
351
351
  historySource,
352
352
  doNotPushHistory
353
353
  });
354
+ const addedPages = newNodes.filter((node) => isPage(node) || isPageFragment(node));
355
+ if (addedPages.length) history_default.notifyPageStructureChange({
356
+ add: addedPages,
357
+ remove: []
358
+ });
354
359
  return Array.isArray(addNode) ? newNodes : newNodes[0];
355
360
  }
356
361
  async doRemove(node, { doNotSelect = false, doNotSwitchPage = false } = {}) {
@@ -454,6 +459,11 @@ var Editor = class extends BaseService {
454
459
  historySource,
455
460
  doNotPushHistory
456
461
  });
462
+ const removedPages = nodes.filter((node) => isPage(node) || isPageFragment(node));
463
+ if (removedPages.length) history_default.notifyPageStructureChange({
464
+ add: [],
465
+ remove: removedPages
466
+ });
457
467
  }
458
468
  async doUpdate(config, { changeRecords = [], historySource } = {}) {
459
469
  if (!this.get("root")) throw new Error("root为空");
@@ -1238,24 +1248,35 @@ var Editor = class extends BaseService {
1238
1248
  const prevMap = new Map(prevPages.map((p) => [`${p.id}`, p]));
1239
1249
  const nextMap = new Map(nextPages.map((p) => [`${p.id}`, p]));
1240
1250
  const indexInItems = (root, id) => (root.items ?? []).findIndex((item) => `${item.id}` === `${id}`);
1251
+ const addedPages = [];
1252
+ const removedPages = [];
1241
1253
  nextPages.forEach((nextPage) => {
1242
1254
  const prevPage = prevMap.get(`${nextPage.id}`);
1243
- if (!prevPage) this.pushPageDiffStep("add", nextPage, {
1244
- newSchema: cloneDeep$1(toRaw(nextPage)),
1245
- parentId: nextRoot.id,
1246
- index: indexInItems(nextRoot, nextPage.id)
1247
- }, source);
1248
- else if (!isEqual(toRaw(prevPage), toRaw(nextPage))) this.pushPageDiffStep("update", nextPage, {
1255
+ if (!prevPage) {
1256
+ this.pushPageDiffStep("add", nextPage, {
1257
+ newSchema: cloneDeep$1(toRaw(nextPage)),
1258
+ parentId: nextRoot.id,
1259
+ index: indexInItems(nextRoot, nextPage.id)
1260
+ }, source);
1261
+ addedPages.push(nextPage);
1262
+ } else if (!isEqual(toRaw(prevPage), toRaw(nextPage))) this.pushPageDiffStep("update", nextPage, {
1249
1263
  oldSchema: cloneDeep$1(toRaw(prevPage)),
1250
1264
  newSchema: cloneDeep$1(toRaw(nextPage))
1251
1265
  }, source);
1252
1266
  });
1253
1267
  prevPages.forEach((prevPage) => {
1254
- if (!nextMap.has(`${prevPage.id}`)) this.pushPageDiffStep("remove", prevPage, {
1255
- oldSchema: cloneDeep$1(toRaw(prevPage)),
1256
- parentId: preRoot.id,
1257
- index: indexInItems(preRoot, prevPage.id)
1258
- }, source);
1268
+ if (!nextMap.has(`${prevPage.id}`)) {
1269
+ this.pushPageDiffStep("remove", prevPage, {
1270
+ oldSchema: cloneDeep$1(toRaw(prevPage)),
1271
+ parentId: preRoot.id,
1272
+ index: indexInItems(preRoot, prevPage.id)
1273
+ }, source);
1274
+ removedPages.push(prevPage);
1275
+ }
1276
+ });
1277
+ if (addedPages.length || removedPages.length) history_default.notifyPageStructureChange({
1278
+ add: addedPages,
1279
+ remove: removedPages
1259
1280
  });
1260
1281
  }
1261
1282
  /**
@@ -244,6 +244,22 @@ var History = class extends BaseService {
244
244
  });
245
245
  }
246
246
  /**
247
+ * 派发「页面 / 页面片结构变更」事件(`page-structure-change`)。
248
+ *
249
+ * 常规 `editorService.add` / `remove` 页面节点不会写入 `page` 历史栈(见 editor.add / remove 中
250
+ * 对 isPage / isPageFragment 的分支),因此不会产生任何 historyService 事件。该方法用于在这些
251
+ * 场景(以及 setRoot 整体替换 DSL 增删页面)下,向外统一通知页面结构的增删变化,供业务方感知。
252
+ *
253
+ * 一次操作涉及多个页面时,调用方应把本次增删的页面**合并为一个 change 一次性传入**,
254
+ * 使一次操作只派发一个事件;`add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。
255
+ *
256
+ * @param change 本次结构变更:{ add: 新增的页面列表, remove: 删除的页面列表 }
257
+ */
258
+ notifyPageStructureChange(change) {
259
+ if (!change.add.length && !change.remove.length) return;
260
+ this.emit("page-structure-change", change);
261
+ }
262
+ /**
247
263
  * 把当前内存中的全部历史栈(页面 / 代码块 / 数据源 / 扩展类型)序列化后写入本地 IndexedDB。
248
264
  *
249
265
  * - 每个 UndoRedo 栈连同其游标、容量一并保存,恢复后可继续 undo/redo;
package/dist/es/style.css CHANGED
@@ -103,6 +103,10 @@
103
103
  width: calc(var(--el-input-width, 100%) - 40px);
104
104
  }
105
105
 
106
+ .tmagic-design-color-picker {
107
+ flex: 0 0 auto;
108
+ }
109
+
106
110
  .m-table .el-button.action-btn + .el-button.action-btn {
107
111
  margin-left: 0;
108
112
  }
@@ -199,12 +199,14 @@ var resolveFieldByPath = (fields, fieldNames, options = {}) => {
199
199
  if (options.skipNumberIndices && isNumber(name)) continue;
200
200
  if (!currentFields.length) return {
201
201
  ok: false,
202
- fields: currentFields
202
+ fields: currentFields,
203
+ failedName: name
203
204
  };
204
205
  field = currentFields.find((item) => item.name === name);
205
206
  if (!field) return {
206
207
  ok: false,
207
- fields: currentFields
208
+ fields: currentFields,
209
+ failedName: name
208
210
  };
209
211
  currentFields = field.fields || [];
210
212
  }
@@ -3,6 +3,7 @@ import editor_default from "../services/editor.js";
3
3
  import { getFieldType, resolveFieldByPath } from "./data-source/index.js";
4
4
  import codeBlock_default from "../services/codeBlock.js";
5
5
  import dataSource_default from "../services/dataSource.js";
6
+ import { validateTypeMatch } from "@tmagic/form";
6
7
  import { appendValidateSuggestion } from "@tmagic/design";
7
8
  import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, DATA_SOURCE_SET_DATA_METHOD_NAME, dataSourceTemplateRegExp, getKeysArray, removeDataSourceFieldPrefix } from "@tmagic/utils";
8
9
  import { NodeType } from "@tmagic/core";
@@ -181,10 +182,10 @@ var validateDataSourceFieldPath = (path, options = {}) => {
181
182
  fieldNames = path.slice(1);
182
183
  }
183
184
  const ds = findDataSource(`${dsId}`);
184
- if (!ds) return defaultMessage(options.message, "值不在可选项中", dataSourceIdSuggestion());
185
+ if (!ds) return defaultMessage(options.message, `数据源(${dsId})不存在`, dataSourceIdSuggestion());
185
186
  if (!fieldNames.length) return;
186
- const { field, ok } = resolveFieldByPath(ds.fields, fieldNames);
187
- if (!ok) return defaultMessage(options.message, "值不在可选项中", dataSourceIdSuggestion());
187
+ const { field, ok, fields, failedName } = resolveFieldByPath(ds.fields, fieldNames);
188
+ if (!ok) return defaultMessage(options.message, `数据源字段(${failedName})不存在`, listSuggestion(fields.map((item) => item.name)));
188
189
  const allowedTypes = options.dataSourceFieldType || ["any"];
189
190
  if (!allowedTypes.length || allowedTypes.includes("any")) return;
190
191
  const leafType = field?.type || "any";
@@ -267,9 +268,15 @@ var isDataSourceFieldPathValue = (value, config) => {
267
268
  if (config.value === "key") return true;
268
269
  return `${value[0]}`.startsWith(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);
269
270
  };
270
- var validateDataSourceFieldSelect = (value, { message, props }) => {
271
+ var validateDataSourceFieldSelect = (value, { mForm, message, props }) => {
271
272
  const config = props.config || {};
272
- if (config.fieldConfig && !isDataSourceFieldPathValue(value, config)) return;
273
+ if (config.fieldConfig && !isDataSourceFieldPathValue(value, config)) return validateTypeMatch(value, mForm, {
274
+ ...props,
275
+ config: {
276
+ name: config.name,
277
+ ...config.fieldConfig
278
+ }
279
+ }, message);
273
280
  if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) return defaultMessage(message, `${value}类型应为字符串数组`, dataSourceFieldPathSuggestion());
274
281
  return validateDataSourceFieldPath(value, {
275
282
  dataSourceId: config.dataSourceId,
package/dist/style.css CHANGED
@@ -103,6 +103,10 @@
103
103
  width: calc(var(--el-input-width, 100%) - 40px);
104
104
  }
105
105
 
106
+ .tmagic-design-color-picker {
107
+ flex: 0 0 auto;
108
+ }
109
+
106
110
  .m-table .el-button.action-btn + .el-button.action-btn {
107
111
  margin-left: 0;
108
112
  }
@@ -103,6 +103,10 @@
103
103
  width: calc(var(--el-input-width, 100%) - 40px);
104
104
  }
105
105
 
106
+ .tmagic-design-color-picker {
107
+ flex: 0 0 auto;
108
+ }
109
+
106
110
  .m-form.m-form--magic-admin .el-collapse-item__header {
107
111
  background-color: transparent;
108
112
  padding: 0;
@@ -6605,6 +6605,22 @@
6605
6605
  });
6606
6606
  }
6607
6607
  /**
6608
+ * 派发「页面 / 页面片结构变更」事件(`page-structure-change`)。
6609
+ *
6610
+ * 常规 `editorService.add` / `remove` 页面节点不会写入 `page` 历史栈(见 editor.add / remove 中
6611
+ * 对 isPage / isPageFragment 的分支),因此不会产生任何 historyService 事件。该方法用于在这些
6612
+ * 场景(以及 setRoot 整体替换 DSL 增删页面)下,向外统一通知页面结构的增删变化,供业务方感知。
6613
+ *
6614
+ * 一次操作涉及多个页面时,调用方应把本次增删的页面**合并为一个 change 一次性传入**,
6615
+ * 使一次操作只派发一个事件;`add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。
6616
+ *
6617
+ * @param change 本次结构变更:{ add: 新增的页面列表, remove: 删除的页面列表 }
6618
+ */
6619
+ notifyPageStructureChange(change) {
6620
+ if (!change.add.length && !change.remove.length) return;
6621
+ this.emit("page-structure-change", change);
6622
+ }
6623
+ /**
6608
6624
  * 把当前内存中的全部历史栈(页面 / 代码块 / 数据源 / 扩展类型)序列化后写入本地 IndexedDB。
6609
6625
  *
6610
6626
  * - 每个 UndoRedo 栈连同其游标、容量一并保存,恢复后可继续 undo/redo;
@@ -7701,6 +7717,11 @@
7701
7717
  historySource,
7702
7718
  doNotPushHistory
7703
7719
  });
7720
+ const addedPages = newNodes.filter((node) => (0, _tmagic_utils.isPage)(node) || (0, _tmagic_utils.isPageFragment)(node));
7721
+ if (addedPages.length) history_default.notifyPageStructureChange({
7722
+ add: addedPages,
7723
+ remove: []
7724
+ });
7704
7725
  return Array.isArray(addNode) ? newNodes : newNodes[0];
7705
7726
  }
7706
7727
  async doRemove(node, { doNotSelect = false, doNotSwitchPage = false } = {}) {
@@ -7804,6 +7825,11 @@
7804
7825
  historySource,
7805
7826
  doNotPushHistory
7806
7827
  });
7828
+ const removedPages = nodes.filter((node) => (0, _tmagic_utils.isPage)(node) || (0, _tmagic_utils.isPageFragment)(node));
7829
+ if (removedPages.length) history_default.notifyPageStructureChange({
7830
+ add: [],
7831
+ remove: removedPages
7832
+ });
7807
7833
  }
7808
7834
  async doUpdate(config, { changeRecords = [], historySource } = {}) {
7809
7835
  if (!this.get("root")) throw new Error("root为空");
@@ -8588,24 +8614,35 @@
8588
8614
  const prevMap = new Map(prevPages.map((p) => [`${p.id}`, p]));
8589
8615
  const nextMap = new Map(nextPages.map((p) => [`${p.id}`, p]));
8590
8616
  const indexInItems = (root, id) => (root.items ?? []).findIndex((item) => `${item.id}` === `${id}`);
8617
+ const addedPages = [];
8618
+ const removedPages = [];
8591
8619
  nextPages.forEach((nextPage) => {
8592
8620
  const prevPage = prevMap.get(`${nextPage.id}`);
8593
- if (!prevPage) this.pushPageDiffStep("add", nextPage, {
8594
- newSchema: cloneDeep$1((0, vue.toRaw)(nextPage)),
8595
- parentId: nextRoot.id,
8596
- index: indexInItems(nextRoot, nextPage.id)
8597
- }, source);
8598
- else if (!isEqual((0, vue.toRaw)(prevPage), (0, vue.toRaw)(nextPage))) this.pushPageDiffStep("update", nextPage, {
8621
+ if (!prevPage) {
8622
+ this.pushPageDiffStep("add", nextPage, {
8623
+ newSchema: cloneDeep$1((0, vue.toRaw)(nextPage)),
8624
+ parentId: nextRoot.id,
8625
+ index: indexInItems(nextRoot, nextPage.id)
8626
+ }, source);
8627
+ addedPages.push(nextPage);
8628
+ } else if (!isEqual((0, vue.toRaw)(prevPage), (0, vue.toRaw)(nextPage))) this.pushPageDiffStep("update", nextPage, {
8599
8629
  oldSchema: cloneDeep$1((0, vue.toRaw)(prevPage)),
8600
8630
  newSchema: cloneDeep$1((0, vue.toRaw)(nextPage))
8601
8631
  }, source);
8602
8632
  });
8603
8633
  prevPages.forEach((prevPage) => {
8604
- if (!nextMap.has(`${prevPage.id}`)) this.pushPageDiffStep("remove", prevPage, {
8605
- oldSchema: cloneDeep$1((0, vue.toRaw)(prevPage)),
8606
- parentId: preRoot.id,
8607
- index: indexInItems(preRoot, prevPage.id)
8608
- }, source);
8634
+ if (!nextMap.has(`${prevPage.id}`)) {
8635
+ this.pushPageDiffStep("remove", prevPage, {
8636
+ oldSchema: cloneDeep$1((0, vue.toRaw)(prevPage)),
8637
+ parentId: preRoot.id,
8638
+ index: indexInItems(preRoot, prevPage.id)
8639
+ }, source);
8640
+ removedPages.push(prevPage);
8641
+ }
8642
+ });
8643
+ if (addedPages.length || removedPages.length) history_default.notifyPageStructureChange({
8644
+ add: addedPages,
8645
+ remove: removedPages
8609
8646
  });
8610
8647
  }
8611
8648
  /**
@@ -9598,12 +9635,14 @@
9598
9635
  if (options.skipNumberIndices && (0, _tmagic_utils.isNumber)(name)) continue;
9599
9636
  if (!currentFields.length) return {
9600
9637
  ok: false,
9601
- fields: currentFields
9638
+ fields: currentFields,
9639
+ failedName: name
9602
9640
  };
9603
9641
  field = currentFields.find((item) => item.name === name);
9604
9642
  if (!field) return {
9605
9643
  ok: false,
9606
- fields: currentFields
9644
+ fields: currentFields,
9645
+ failedName: name
9607
9646
  };
9608
9647
  currentFields = field.fields || [];
9609
9648
  }
@@ -10929,10 +10968,10 @@
10929
10968
  fieldNames = path.slice(1);
10930
10969
  }
10931
10970
  const ds = findDataSource(`${dsId}`);
10932
- if (!ds) return defaultMessage(options.message, "值不在可选项中", dataSourceIdSuggestion());
10971
+ if (!ds) return defaultMessage(options.message, `数据源(${dsId})不存在`, dataSourceIdSuggestion());
10933
10972
  if (!fieldNames.length) return;
10934
- const { field, ok } = resolveFieldByPath(ds.fields, fieldNames);
10935
- if (!ok) return defaultMessage(options.message, "值不在可选项中", dataSourceIdSuggestion());
10973
+ const { field, ok, fields, failedName } = resolveFieldByPath(ds.fields, fieldNames);
10974
+ if (!ok) return defaultMessage(options.message, `数据源字段(${failedName})不存在`, listSuggestion(fields.map((item) => item.name)));
10936
10975
  const allowedTypes = options.dataSourceFieldType || ["any"];
10937
10976
  if (!allowedTypes.length || allowedTypes.includes("any")) return;
10938
10977
  const leafType = field?.type || "any";
@@ -11015,9 +11054,15 @@
11015
11054
  if (config.value === "key") return true;
11016
11055
  return `${value[0]}`.startsWith(_tmagic_utils.DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);
11017
11056
  };
11018
- var validateDataSourceFieldSelect = (value, { message, props }) => {
11057
+ var validateDataSourceFieldSelect = (value, { mForm, message, props }) => {
11019
11058
  const config = props.config || {};
11020
- if (config.fieldConfig && !isDataSourceFieldPathValue(value, config)) return;
11059
+ if (config.fieldConfig && !isDataSourceFieldPathValue(value, config)) return (0, _tmagic_form.validateTypeMatch)(value, mForm, {
11060
+ ...props,
11061
+ config: {
11062
+ name: config.name,
11063
+ ...config.fieldConfig
11064
+ }
11065
+ }, message);
11021
11066
  if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) return defaultMessage(message, `${value}类型应为字符串数组`, dataSourceFieldPathSuggestion());
11022
11067
  return validateDataSourceFieldPath(value, {
11023
11068
  dataSourceId: config.dataSourceId,
@@ -15055,14 +15100,14 @@
15055
15100
  ...v,
15056
15101
  style: {}
15057
15102
  };
15058
- if (v.style) {
15103
+ if (v.style) if (eventData) {
15059
15104
  Object.entries(v.style).forEach(([key, value]) => {
15060
15105
  if (value !== "" && newValue.style) newValue.style[key] = value;
15061
15106
  });
15062
- eventData?.changeRecords?.forEach((record) => {
15107
+ eventData.changeRecords?.forEach((record) => {
15063
15108
  if (record.propPath?.startsWith("style") && record.value === "") (0, _tmagic_utils.setValueByKeyPath)(record.propPath, record.value, newValue);
15064
15109
  });
15065
- }
15110
+ } else newValue.style = { ...v.style };
15066
15111
  const historySource = eventData ? "props" : "code";
15067
15112
  editorService.update(newValue, {
15068
15113
  changeRecords: eventData?.changeRecords,
@@ -15700,15 +15745,29 @@
15700
15745
  if (contains(target)) return;
15701
15746
  hide();
15702
15747
  };
15748
+ const fixPosition = () => {
15749
+ const menu = menuEl.value;
15750
+ if (!menu || !visible.value) return;
15751
+ const menuHeight = menu.clientHeight;
15752
+ const menuWidth = menu.clientWidth;
15753
+ let { top, left } = menuPosition.value;
15754
+ if (top + menuHeight > document.body.clientHeight) top = Math.max(0, document.body.clientHeight - menuHeight);
15755
+ if (left + menuWidth > document.body.clientWidth) left = Math.max(0, document.body.clientWidth - menuWidth);
15756
+ if (top !== menuPosition.value.top || left !== menuPosition.value.left) menuPosition.value = {
15757
+ top,
15758
+ left
15759
+ };
15760
+ };
15703
15761
  const setPosition = (e) => {
15704
- const menuHeight = menuEl.value?.clientHeight || 0;
15705
- let top = e.clientY;
15706
- if (menuHeight + e.clientY > document.body.clientHeight) top = document.body.clientHeight - menuHeight;
15707
15762
  menuPosition.value = {
15708
- top,
15763
+ top: e.clientY,
15709
15764
  left: e.clientX
15710
15765
  };
15766
+ fixPosition();
15711
15767
  };
15768
+ const resizeObserver = new ResizeObserver(() => {
15769
+ fixPosition();
15770
+ });
15712
15771
  const show = (e) => {
15713
15772
  visible.value = true;
15714
15773
  (0, vue.nextTick)(() => {
@@ -15737,10 +15796,12 @@
15737
15796
  emit("mouseenter");
15738
15797
  };
15739
15798
  (0, vue.onMounted)(() => {
15799
+ if (menuEl.value) resizeObserver.observe(menuEl.value);
15740
15800
  if (props.isSubMenu) return;
15741
15801
  globalThis.addEventListener("mousedown", outsideClickHideHandler, true);
15742
15802
  });
15743
15803
  (0, vue.onBeforeUnmount)(() => {
15804
+ resizeObserver.disconnect();
15744
15805
  if (props.isSubMenu) return;
15745
15806
  globalThis.removeEventListener("mousedown", outsideClickHideHandler, true);
15746
15807
  });
@@ -25323,6 +25384,12 @@
25323
25384
  },
25324
25385
  emits: ["change"],
25325
25386
  setup(__props, { emit: __emit }) {
25387
+ /**
25388
+ * 静默模式(submitForm/validateForm 隐藏挂载)下跳过 monaco 渲染:
25389
+ * 校验由 FormItem 针对 model 中的值完成,与编辑器实例无关;本组件挂载无值副作用
25390
+ * (save 仅由用户操作/编辑器内容变更触发),跳过可省去 monaco worker/model 的无谓实例化。
25391
+ */
25392
+ const silentMode = (0, vue.inject)(_tmagic_form.FORM_SILENT_MODE_KEY, false);
25326
25393
  const emit = __emit;
25327
25394
  const props = __props;
25328
25395
  /**
@@ -25340,7 +25407,8 @@
25340
25407
  emit("change", v);
25341
25408
  };
25342
25409
  return (_ctx, _cache) => {
25343
- return (0, vue.openBlock)(), (0, vue.createBlock)(CodeEditor_default, {
25410
+ return !(0, vue.unref)(silentMode) ? ((0, vue.openBlock)(), (0, vue.createBlock)(CodeEditor_default, {
25411
+ key: 0,
25344
25412
  height: __props.config.height,
25345
25413
  type: diffMode.value ? "diff" : void 0,
25346
25414
  "init-values": diffMode.value ? (__props.lastValues || {})[__props.name] : __props.model[__props.name],
@@ -25364,7 +25432,7 @@
25364
25432
  "autosize",
25365
25433
  "parse",
25366
25434
  "editor-custom-type"
25367
- ]);
25435
+ ])) : (0, vue.createCommentVNode)("v-if", true);
25368
25436
  };
25369
25437
  }
25370
25438
  });
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.8.0-beta.14",
2
+ "version": "1.8.0-beta.15",
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.8.0-beta.14",
62
- "@tmagic/form": "1.8.0-beta.14",
63
- "@tmagic/stage": "1.8.0-beta.14",
64
- "@tmagic/table": "1.8.0-beta.14",
65
- "@tmagic/utils": "1.8.0-beta.14"
61
+ "@tmagic/design": "1.8.0-beta.15",
62
+ "@tmagic/form": "1.8.0-beta.15",
63
+ "@tmagic/table": "1.8.0-beta.15",
64
+ "@tmagic/utils": "1.8.0-beta.15",
65
+ "@tmagic/stage": "1.8.0-beta.15"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/events": "^3.0.3",
@@ -76,7 +76,7 @@
76
76
  "type-fest": "^5.2.0",
77
77
  "typescript": "^6.0.3",
78
78
  "vue": "^3.5.40",
79
- "@tmagic/core": "1.8.0-beta.14"
79
+ "@tmagic/core": "1.8.0-beta.15"
80
80
  },
81
81
  "peerDependenciesMeta": {
82
82
  "typescript": {
@@ -128,20 +128,42 @@ const outsideClickHideHandler = (e: MouseEvent) => {
128
128
  hide();
129
129
  };
130
130
 
131
- const setPosition = (e: { clientY: number; clientX: number }) => {
132
- const menuHeight = menuEl.value?.clientHeight || 0;
131
+ // 根据菜单实际尺寸修正位置,避免超出可视范围
132
+ const fixPosition = () => {
133
+ const menu = menuEl.value;
134
+ if (!menu || !visible.value) return;
135
+
136
+ const menuHeight = menu.clientHeight;
137
+ const menuWidth = menu.clientWidth;
138
+
139
+ let { top, left } = menuPosition.value;
140
+
141
+ if (top + menuHeight > document.body.clientHeight) {
142
+ top = Math.max(0, document.body.clientHeight - menuHeight);
143
+ }
144
+
145
+ if (left + menuWidth > document.body.clientWidth) {
146
+ left = Math.max(0, document.body.clientWidth - menuWidth);
147
+ }
133
148
 
134
- let top = e.clientY;
135
- if (menuHeight + e.clientY > document.body.clientHeight) {
136
- top = document.body.clientHeight - menuHeight;
149
+ if (top !== menuPosition.value.top || left !== menuPosition.value.left) {
150
+ menuPosition.value = { top, left };
137
151
  }
152
+ };
138
153
 
154
+ const setPosition = (e: { clientY: number; clientX: number }) => {
139
155
  menuPosition.value = {
140
- top,
156
+ top: e.clientY,
141
157
  left: e.clientX,
142
158
  };
159
+ fixPosition();
143
160
  };
144
161
 
162
+ // 菜单大小动态变化(如菜单项更新)后重新修正位置
163
+ const resizeObserver = new ResizeObserver(() => {
164
+ fixPosition();
165
+ });
166
+
145
167
  const show = (e?: { clientY: number; clientX: number }) => {
146
168
  visible.value = true;
147
169
 
@@ -186,12 +208,18 @@ const mouseenterHandler = () => {
186
208
  };
187
209
 
188
210
  onMounted(() => {
211
+ if (menuEl.value) {
212
+ resizeObserver.observe(menuEl.value);
213
+ }
214
+
189
215
  if (props.isSubMenu) return;
190
216
 
191
217
  globalThis.addEventListener('mousedown', outsideClickHideHandler, true);
192
218
  });
193
219
 
194
220
  onBeforeUnmount(() => {
221
+ resizeObserver.disconnect();
222
+
195
223
  if (props.isSubMenu) return;
196
224
 
197
225
  globalThis.removeEventListener('mousedown', outsideClickHideHandler, true);
@@ -1,5 +1,6 @@
1
1
  <template>
2
2
  <MagicCodeEditor
3
+ v-if="!silentMode"
3
4
  :height="config.height"
4
5
  :type="diffMode ? 'diff' : undefined"
5
6
  :init-values="diffMode ? (lastValues || {})[name] : model[name]"
@@ -17,9 +18,9 @@
17
18
  </template>
18
19
 
19
20
  <script lang="ts" setup>
20
- import { computed } from 'vue';
21
+ import { computed, inject } from 'vue';
21
22
 
22
- import type { CodeConfig, FieldProps } from '@tmagic/form';
23
+ import { type CodeConfig, type FieldProps, FORM_SILENT_MODE_KEY } from '@tmagic/form';
23
24
 
24
25
  import MagicCodeEditor from '@editor/layouts/CodeEditor.vue';
25
26
 
@@ -27,6 +28,13 @@ defineOptions({
27
28
  name: 'MFieldsVsCode',
28
29
  });
29
30
 
31
+ /**
32
+ * 静默模式(submitForm/validateForm 隐藏挂载)下跳过 monaco 渲染:
33
+ * 校验由 FormItem 针对 model 中的值完成,与编辑器实例无关;本组件挂载无值副作用
34
+ * (save 仅由用户操作/编辑器内容变更触发),跳过可省去 monaco worker/model 的无谓实例化。
35
+ */
36
+ const silentMode = inject(FORM_SILENT_MODE_KEY, false);
37
+
30
38
  const emit = defineEmits<{
31
39
  change: [value: string | any];
32
40
  }>();
@@ -164,17 +164,25 @@ const submit = async (
164
164
  };
165
165
 
166
166
  if (v.style) {
167
- Object.entries(v.style).forEach(([key, value]) => {
168
- if (value !== '' && newValue.style) {
169
- newValue.style[key] = value;
170
- }
171
- });
172
-
173
- eventData?.changeRecords?.forEach((record) => {
174
- if (record.propPath?.startsWith('style') && record.value === '') {
175
- setValueByKeyPath(record.propPath, record.value, newValue);
176
- }
177
- });
167
+ // 空字符串样式值表示「清除该样式」,需保留才能在 doUpdate 的 mergeWith 中覆盖旧值。
168
+ if (eventData) {
169
+ // 表单编辑:先过滤掉空字符串(避免表单默认空值污染 DSL),
170
+ // 再按 changeRecords 恢复被主动清空的字段。
171
+ Object.entries(v.style).forEach(([key, value]) => {
172
+ if (value !== '' && newValue.style) {
173
+ newValue.style[key] = value;
174
+ }
175
+ });
176
+
177
+ eventData.changeRecords?.forEach((record) => {
178
+ if (record.propPath?.startsWith('style') && record.value === '') {
179
+ setValueByKeyPath(record.propPath, record.value, newValue);
180
+ }
181
+ });
182
+ } else {
183
+ // 源码编辑器保存(无 eventData):style 原样保留,其中的空字符串视为用户主动清除该样式。
184
+ newValue.style = { ...v.style };
185
+ }
178
186
  }
179
187
 
180
188
  // 区分操作途径:表单字段编辑(MForm @change)会带上 eventData(含 changeRecords);
@@ -554,6 +554,12 @@ class Editor extends BaseService {
554
554
  doNotPushHistory,
555
555
  });
556
556
 
557
+ // 页面 / 页面片新增不入历史栈(见上方 isPage / isPageFragment 分支),这里合并补发一次结构变更通知
558
+ const addedPages = newNodes.filter((node) => isPage(node) || isPageFragment(node)) as (MPage | MPageFragment)[];
559
+ if (addedPages.length) {
560
+ historyService.notifyPageStructureChange({ add: addedPages, remove: [] });
561
+ }
562
+
557
563
  return Array.isArray(addNode) ? newNodes : newNodes[0];
558
564
  }
559
565
 
@@ -695,6 +701,12 @@ class Editor extends BaseService {
695
701
 
696
702
  this.emit('remove', nodes);
697
703
  this.emit('change', { type: 'remove', data: changeItems, historySource, doNotPushHistory });
704
+
705
+ // 页面 / 页面片删除不入历史栈(见上方 isPage / isPageFragment 分支),这里合并补发一次结构变更通知
706
+ const removedPages = nodes.filter((node) => isPage(node) || isPageFragment(node)) as (MPage | MPageFragment)[];
707
+ if (removedPages.length) {
708
+ historyService.notifyPageStructureChange({ add: [], remove: removedPages });
709
+ }
698
710
  }
699
711
 
700
712
  public async doUpdate(
@@ -1776,6 +1788,10 @@ class Editor extends BaseService {
1776
1788
  const nextMap = new Map(nextPages.map((p) => [`${p.id}`, p]));
1777
1789
  const indexInItems = (root: MApp, id: Id) => (root.items ?? []).findIndex((item) => `${item.id}` === `${id}`);
1778
1790
 
1791
+ // 收集本次整体替换中增删的页面,循环结束后合并为一次结构变更通知(避免逐页派发多个事件)
1792
+ const addedPages: (MPage | MPageFragment)[] = [];
1793
+ const removedPages: (MPage | MPageFragment)[] = [];
1794
+
1779
1795
  nextPages.forEach((nextPage) => {
1780
1796
  const prevPage = prevMap.get(`${nextPage.id}`);
1781
1797
  if (!prevPage) {
@@ -1785,6 +1801,7 @@ class Editor extends BaseService {
1785
1801
  { newSchema: cloneDeep(toRaw(nextPage)), parentId: nextRoot.id, index: indexInItems(nextRoot, nextPage.id) },
1786
1802
  source,
1787
1803
  );
1804
+ addedPages.push(nextPage);
1788
1805
  } else if (!isEqual(toRaw(prevPage), toRaw(nextPage))) {
1789
1806
  this.pushPageDiffStep(
1790
1807
  'update',
@@ -1803,8 +1820,13 @@ class Editor extends BaseService {
1803
1820
  { oldSchema: cloneDeep(toRaw(prevPage)), parentId: preRoot.id, index: indexInItems(preRoot, prevPage.id) },
1804
1821
  source,
1805
1822
  );
1823
+ removedPages.push(prevPage);
1806
1824
  }
1807
1825
  });
1826
+
1827
+ if (addedPages.length || removedPages.length) {
1828
+ historyService.notifyPageStructureChange({ add: addedPages, remove: removedPages });
1829
+ }
1808
1830
  }
1809
1831
 
1810
1832
  /**
@@ -19,7 +19,7 @@
19
19
  import { reactive } from 'vue';
20
20
  import type { Writable } from 'type-fest';
21
21
 
22
- import type { Id } from '@tmagic/core';
22
+ import type { Id, MPage, MPageFragment } from '@tmagic/core';
23
23
  import { guid } from '@tmagic/utils';
24
24
 
25
25
  import type {
@@ -353,6 +353,27 @@ class History extends BaseService {
353
353
  this.emit('mark-saved', { kind: stepType, id });
354
354
  }
355
355
 
356
+ /**
357
+ * 派发「页面 / 页面片结构变更」事件(`page-structure-change`)。
358
+ *
359
+ * 常规 `editorService.add` / `remove` 页面节点不会写入 `page` 历史栈(见 editor.add / remove 中
360
+ * 对 isPage / isPageFragment 的分支),因此不会产生任何 historyService 事件。该方法用于在这些
361
+ * 场景(以及 setRoot 整体替换 DSL 增删页面)下,向外统一通知页面结构的增删变化,供业务方感知。
362
+ *
363
+ * 一次操作涉及多个页面时,调用方应把本次增删的页面**合并为一个 change 一次性传入**,
364
+ * 使一次操作只派发一个事件;`add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。
365
+ *
366
+ * @param change 本次结构变更:{ add: 新增的页面列表, remove: 删除的页面列表 }
367
+ */
368
+ public notifyPageStructureChange(change: {
369
+ add: (MPage | MPageFragment)[];
370
+ remove: (MPage | MPageFragment)[];
371
+ }): void {
372
+ // 增删均为空时不派发,避免无意义通知
373
+ if (!change.add.length && !change.remove.length) return;
374
+ this.emit('page-structure-change', change);
375
+ }
376
+
356
377
  /**
357
378
  * 把当前内存中的全部历史栈(页面 / 代码块 / 数据源 / 扩展类型)序列化后写入本地 IndexedDB。
358
379
  *
package/src/type.ts CHANGED
@@ -1385,6 +1385,12 @@ export interface HistoryEvents {
1385
1385
  'mark-saved': [{ kind: HistoryStepType; id?: Id }];
1386
1386
  clear: [{ id: Id; stepType: HistoryStepType }];
1387
1387
  'marker-change': [{ id: Id; marker: StepValue; stepType: HistoryStepType }];
1388
+ /**
1389
+ * 页面 / 页面片结构变更(新增 / 删除)时派发,见 {@link HistoryService.notifyPageStructureChange}。
1390
+ * 一次操作(add / remove / setRoot 整体替换)涉及多个页面时合并为**一个**事件;
1391
+ * `add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。
1392
+ */
1393
+ 'page-structure-change': [change: { add: (MPage | MPageFragment)[]; remove: (MPage | MPageFragment)[] }];
1388
1394
  }
1389
1395
 
1390
1396
  export const canUsePluginMethods = {
@@ -243,7 +243,7 @@ export const resolveFieldByPath = (
243
243
  fields: DataSchema[] | undefined,
244
244
  fieldNames: string[],
245
245
  options: { skipNumberIndices?: boolean } = {},
246
- ): { ok: boolean; field?: DataSchema; fields: DataSchema[] } => {
246
+ ): { ok: boolean; field?: DataSchema; fields: DataSchema[]; failedName?: string } => {
247
247
  let currentFields = fields || [];
248
248
  let field: DataSchema | undefined;
249
249
 
@@ -252,11 +252,11 @@ export const resolveFieldByPath = (
252
252
  continue;
253
253
  }
254
254
  if (!currentFields.length) {
255
- return { ok: false, fields: currentFields };
255
+ return { ok: false, fields: currentFields, failedName: name };
256
256
  }
257
257
  field = currentFields.find((item) => item.name === name);
258
258
  if (!field) {
259
- return { ok: false, fields: currentFields };
259
+ return { ok: false, fields: currentFields, failedName: name };
260
260
  }
261
261
  currentFields = field.fields || [];
262
262
  }
@@ -20,6 +20,7 @@ import type { DataSourceFieldType, DataSourceSchema, Id } from '@tmagic/core';
20
20
  import { NodeType } from '@tmagic/core';
21
21
  import { appendValidateSuggestion } from '@tmagic/design';
22
22
  import type { TypeMatchValidateContext, TypeMatchValidator } from '@tmagic/form';
23
+ import { validateTypeMatch } from '@tmagic/form';
23
24
  import {
24
25
  DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX,
25
26
  DATA_SOURCE_SET_DATA_METHOD_NAME,
@@ -261,16 +262,20 @@ const validateDataSourceFieldPath = (
261
262
 
262
263
  const ds = findDataSource(`${dsId}`);
263
264
  if (!ds) {
264
- return defaultMessage(options.message, '值不在可选项中', dataSourceIdSuggestion());
265
+ return defaultMessage(options.message, `数据源(${dsId})不存在`, dataSourceIdSuggestion());
265
266
  }
266
267
 
267
268
  if (!fieldNames.length) {
268
269
  return undefined;
269
270
  }
270
271
 
271
- const { field, ok } = resolveFieldByPath(ds.fields, fieldNames);
272
+ const { field, ok, fields, failedName } = resolveFieldByPath(ds.fields, fieldNames);
272
273
  if (!ok) {
273
- return defaultMessage(options.message, '值不在可选项中', dataSourceIdSuggestion());
274
+ return defaultMessage(
275
+ options.message,
276
+ `数据源字段(${failedName})不存在`,
277
+ listSuggestion(fields.map((item) => item.name)),
278
+ );
274
279
  }
275
280
 
276
281
  const allowedTypes = options.dataSourceFieldType || ['any'];
@@ -471,11 +476,12 @@ const isDataSourceFieldPathValue = (value: any, config: any): value is string[]
471
476
  return `${value[0]}`.startsWith(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);
472
477
  };
473
478
 
474
- const validateDataSourceFieldSelect: TypeMatchValidator = (value, { message, props }) => {
479
+ const validateDataSourceFieldSelect: TypeMatchValidator = (value, { mForm, message, props }) => {
475
480
  const config = props.config || {};
476
481
 
477
482
  if (config.fieldConfig && !isDataSourceFieldPathValue(value, config)) {
478
- return undefined;
483
+ // 值不是数据源字段路径时,按 fieldConfig 的类型校验(与表单项自身 typeMatch 行为一致)
484
+ return validateTypeMatch(value, mForm, { ...props, config: { name: config.name, ...config.fieldConfig } }, message);
479
485
  }
480
486
 
481
487
  if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
package/types/index.d.ts CHANGED
@@ -1214,6 +1214,22 @@ declare class History extends BaseService {
1214
1214
  * @param id 目标栈 id;缺省表示标记全部类型 / 全部栈
1215
1215
  */
1216
1216
  markSaved(stepType: HistoryStepType, id?: Id): void;
1217
+ /**
1218
+ * 派发「页面 / 页面片结构变更」事件(`page-structure-change`)。
1219
+ *
1220
+ * 常规 `editorService.add` / `remove` 页面节点不会写入 `page` 历史栈(见 editor.add / remove 中
1221
+ * 对 isPage / isPageFragment 的分支),因此不会产生任何 historyService 事件。该方法用于在这些
1222
+ * 场景(以及 setRoot 整体替换 DSL 增删页面)下,向外统一通知页面结构的增删变化,供业务方感知。
1223
+ *
1224
+ * 一次操作涉及多个页面时,调用方应把本次增删的页面**合并为一个 change 一次性传入**,
1225
+ * 使一次操作只派发一个事件;`add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。
1226
+ *
1227
+ * @param change 本次结构变更:{ add: 新增的页面列表, remove: 删除的页面列表 }
1228
+ */
1229
+ notifyPageStructureChange(change: {
1230
+ add: (MPage | MPageFragment)[];
1231
+ remove: (MPage | MPageFragment)[];
1232
+ }): void;
1217
1233
  /**
1218
1234
  * 把当前内存中的全部历史栈(页面 / 代码块 / 数据源 / 扩展类型)序列化后写入本地 IndexedDB。
1219
1235
  *
@@ -2694,6 +2710,15 @@ interface HistoryEvents {
2694
2710
  marker: StepValue;
2695
2711
  stepType: HistoryStepType;
2696
2712
  }];
2713
+ /**
2714
+ * 页面 / 页面片结构变更(新增 / 删除)时派发,见 {@link HistoryService.notifyPageStructureChange}。
2715
+ * 一次操作(add / remove / setRoot 整体替换)涉及多个页面时合并为**一个**事件;
2716
+ * `add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。
2717
+ */
2718
+ 'page-structure-change': [change: {
2719
+ add: (MPage | MPageFragment)[];
2720
+ remove: (MPage | MPageFragment)[];
2721
+ }];
2697
2722
  }
2698
2723
  declare const canUsePluginMethods: {
2699
2724
  async: readonly ["getLayout", "highlight", "select", "multiSelect", "doAdd", "add", "doRemove", "remove", "doUpdate", "update", "sort", "copy", "paste", "doPaste", "doAlignCenter", "alignCenter", "moveLayer", "moveToContainer", "dragTo", "undo", "redo", "move"];
@@ -3370,6 +3395,7 @@ declare const resolveFieldByPath: (fields: DataSchema[] | undefined, fieldNames:
3370
3395
  ok: boolean;
3371
3396
  field?: DataSchema;
3372
3397
  fields: DataSchema[];
3398
+ failedName?: string;
3373
3399
  };
3374
3400
  declare const getFieldType: (ds: DataSourceSchema | undefined, fieldNames: string[]) => "" | DataSourceFieldType;
3375
3401
  //#endregion