mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-07-26 16:08:10 +00:00
Compare commits
19 Commits
v1.8.0-bet
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b564c9e4aa | ||
|
|
b5abf31066 | ||
|
|
bf8df74864 | ||
|
|
a37e0487c6 | ||
|
|
329cd947e1 | ||
|
|
0e1986c819 | ||
|
|
d0a22315c0 | ||
|
|
fe7815cf3d | ||
|
|
bf9078a458 | ||
|
|
3b9d5da0bc | ||
|
|
2e031cc134 | ||
|
|
1f389ff9fd | ||
|
|
fe9e754b50 | ||
|
|
745f449396 | ||
|
|
48abab7051 | ||
|
|
11bf037054 | ||
|
|
97e0cac44a | ||
|
|
cde0805292 | ||
|
|
6919a0a0b1 |
25
CHANGELOG.md
25
CHANGELOG.md
@ -1,3 +1,28 @@
|
|||||||
|
# [1.8.0-beta.16](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.15...v1.8.0-beta.16) (2026-07-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 参考建议可选值最多展示个数由 5 调整为 20 ([0e1986c](https://github.com/Tencent/tmagic-editor/commit/0e1986c81998e50461c294c1a27a8139e8ae9a13))
|
||||||
|
# [1.8.0-beta.15](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.14...v1.8.0-beta.15) (2026-07-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **core:** 为背景图片 URL 添加引号 ([97e0cac](https://github.com/Tencent/tmagic-editor/commit/97e0cac44a2d6ceafaf95fbc4ae716bfe5d47f70))
|
||||||
|
* **editor:** 使用属性面板中的源码删除样式无效 ([cde0805](https://github.com/Tencent/tmagic-editor/commit/cde08052924ddef1c51faeb68ce15d3195827adb))
|
||||||
|
* **editor:** 右键菜单动态变化后修复菜单位置防止截断 ([6919a0a](https://github.com/Tencent/tmagic-editor/commit/6919a0a0b19efff92821fc8d52b61b2f7382a817))
|
||||||
|
* 跳过无字段名称配置的类型校验 ([11bf037](https://github.com/Tencent/tmagic-editor/commit/11bf037054774937132743a40006364e49ee8fd5))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **design:** 优化 FormItem 和 colorPicker 样式 ([2e031cc](https://github.com/Tencent/tmagic-editor/commit/2e031cc1346fb6ea739f0d45a89213b6577dc7ee))
|
||||||
|
* **editor:** 数据源字段选择器校验提示细化并支持 fieldConfig 类型校验 ([fe9e754](https://github.com/Tencent/tmagic-editor/commit/fe9e754b50f14e6034170b2284553ec1c4536150))
|
||||||
|
* **form,design:** 一行多个表单默认右靠齐label ([48abab7](https://github.com/Tencent/tmagic-editor/commit/48abab7051b93b7ed336172c21ab3b908733f2e7))
|
||||||
|
* **form,editor:** submitForm/validateForm 静默挂载时注入静默标记,vs-code 字段跳过 monaco 渲染 ([3b9d5da](https://github.com/Tencent/tmagic-editor/commit/3b9d5da0bc9f323e944c0e2adb4fc88642ce1915))
|
||||||
|
* **form:** text 类型校验允许数字值,兼容数据源字段配置 ([1f389ff](https://github.com/Tencent/tmagic-editor/commit/1f389ff9fd77fb93ad749f3fdbbc8aa0fe8725ac))
|
||||||
|
* 新增 page-structure-change 事件,支持页面增删统一通知 ([745f449](https://github.com/Tencent/tmagic-editor/commit/745f44939605528e8e2ccd0639478db98293a441))
|
||||||
# [1.8.0-beta.14](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.13...v1.8.0-beta.14) (2026-07-23)
|
# [1.8.0-beta.14](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.13...v1.8.0-beta.14) (2026-07-23)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"name": "tmagic",
|
"name": "tmagic",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"name": "@tmagic/cli",
|
"name": "@tmagic/cli",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"name": "@tmagic/core",
|
"name": "@tmagic/core",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -51,7 +51,7 @@ export const style2Obj = (style: string) => {
|
|||||||
|
|
||||||
export const fillBackgroundImage = (value: string) => {
|
export const fillBackgroundImage = (value: string) => {
|
||||||
if (value && !/^url/.test(value) && !/^linear-gradient/.test(value)) {
|
if (value && !/^url/.test(value) && !/^linear-gradient/.test(value)) {
|
||||||
return `url(${value})`;
|
return `url("${value}")`;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -50,7 +50,7 @@ describe('style2Obj', () => {
|
|||||||
|
|
||||||
describe('fillBackgroundImage', () => {
|
describe('fillBackgroundImage', () => {
|
||||||
test('裸路径会包裹 url()', () => {
|
test('裸路径会包裹 url()', () => {
|
||||||
expect(fillBackgroundImage('a.png')).toBe('url(a.png)');
|
expect(fillBackgroundImage('a.png')).toBe('url("a.png")');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('已经是 url() 不重复包裹', () => {
|
test('已经是 url() 不重复包裹', () => {
|
||||||
@ -127,7 +127,7 @@ describe('transformStyle', () => {
|
|||||||
|
|
||||||
test('backgroundImage: browser 下补全 url()', () => {
|
test('backgroundImage: browser 下补全 url()', () => {
|
||||||
expect(transformStyle({ backgroundImage: 'a.png' }, 'browser')).toEqual({
|
expect(transformStyle({ backgroundImage: 'a.png' }, 'browser')).toEqual({
|
||||||
backgroundImage: 'url(a.png)',
|
backgroundImage: 'url("a.png")',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"name": "@tmagic/data-source",
|
"name": "@tmagic/data-source",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"name": "@tmagic/dep",
|
"name": "@tmagic/dep",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"name": "@tmagic/design",
|
"name": "@tmagic/design",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": [
|
"sideEffects": [
|
||||||
|
|||||||
@ -47,12 +47,12 @@ const uiComponent = ui?.component || 'el-form-item';
|
|||||||
|
|
||||||
const adapterType = getDesignConfig('adapterType');
|
const adapterType = getDesignConfig('adapterType');
|
||||||
|
|
||||||
const formInline = inject<boolean>('formInline');
|
const formInline = inject<boolean>('formInline', false);
|
||||||
|
const formInRow = inject<boolean>('isInRow', false);
|
||||||
const uiProps = computed<FormItemProps>(() => {
|
const uiProps = computed<FormItemProps>(() => {
|
||||||
const { extra, extraTips, ...rest } = ui?.props(props) || props;
|
const { extra, extraTips, ...rest } = ui?.props(props) || props;
|
||||||
if (isGlobalFlat.value && rest.labelPosition === undefined) {
|
if (isGlobalFlat.value && rest.labelPosition === undefined) {
|
||||||
return { ...rest, labelPosition: formInline ? 'right' : 'left' };
|
return { ...rest, labelPosition: formInline || formInRow ? 'right' : 'left' };
|
||||||
}
|
}
|
||||||
return rest;
|
return rest;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,3 +2,7 @@
|
|||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
width: calc(var(--el-input-width, 100%) - 40px);
|
width: calc(var(--el-input-width, 100%) - 40px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tmagic-design-color-picker {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"name": "@tmagic/editor",
|
"name": "@tmagic/editor",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": [
|
"sideEffects": [
|
||||||
|
|||||||
@ -128,20 +128,42 @@ const outsideClickHideHandler = (e: MouseEvent) => {
|
|||||||
hide();
|
hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
const setPosition = (e: { clientY: number; clientX: number }) => {
|
// 根据菜单实际尺寸修正位置,避免超出可视范围
|
||||||
const menuHeight = menuEl.value?.clientHeight || 0;
|
const fixPosition = () => {
|
||||||
|
const menu = menuEl.value;
|
||||||
|
if (!menu || !visible.value) return;
|
||||||
|
|
||||||
let top = e.clientY;
|
const menuHeight = menu.clientHeight;
|
||||||
if (menuHeight + e.clientY > document.body.clientHeight) {
|
const menuWidth = menu.clientWidth;
|
||||||
top = document.body.clientHeight - menuHeight;
|
|
||||||
|
let { top, left } = menuPosition.value;
|
||||||
|
|
||||||
|
if (top + menuHeight > document.body.clientHeight) {
|
||||||
|
top = Math.max(0, document.body.clientHeight - menuHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (left + menuWidth > document.body.clientWidth) {
|
||||||
|
left = Math.max(0, document.body.clientWidth - menuWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top !== menuPosition.value.top || left !== menuPosition.value.left) {
|
||||||
|
menuPosition.value = { top, left };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setPosition = (e: { clientY: number; clientX: number }) => {
|
||||||
menuPosition.value = {
|
menuPosition.value = {
|
||||||
top,
|
top: e.clientY,
|
||||||
left: e.clientX,
|
left: e.clientX,
|
||||||
};
|
};
|
||||||
|
fixPosition();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 菜单大小动态变化(如菜单项更新)后重新修正位置
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
fixPosition();
|
||||||
|
});
|
||||||
|
|
||||||
const show = (e?: { clientY: number; clientX: number }) => {
|
const show = (e?: { clientY: number; clientX: number }) => {
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
|
|
||||||
@ -186,12 +208,18 @@ const mouseenterHandler = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
if (menuEl.value) {
|
||||||
|
resizeObserver.observe(menuEl.value);
|
||||||
|
}
|
||||||
|
|
||||||
if (props.isSubMenu) return;
|
if (props.isSubMenu) return;
|
||||||
|
|
||||||
globalThis.addEventListener('mousedown', outsideClickHideHandler, true);
|
globalThis.addEventListener('mousedown', outsideClickHideHandler, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
|
||||||
if (props.isSubMenu) return;
|
if (props.isSubMenu) return;
|
||||||
|
|
||||||
globalThis.removeEventListener('mousedown', outsideClickHideHandler, true);
|
globalThis.removeEventListener('mousedown', outsideClickHideHandler, true);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<MagicCodeEditor
|
<MagicCodeEditor
|
||||||
|
v-if="!silentMode"
|
||||||
:height="config.height"
|
:height="config.height"
|
||||||
:type="diffMode ? 'diff' : undefined"
|
:type="diffMode ? 'diff' : undefined"
|
||||||
:init-values="diffMode ? (lastValues || {})[name] : model[name]"
|
:init-values="diffMode ? (lastValues || {})[name] : model[name]"
|
||||||
@ -17,9 +18,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed, inject } from 'vue';
|
||||||
|
|
||||||
import type { CodeConfig, FieldProps } from '@tmagic/form';
|
import { type CodeConfig, type FieldProps, FORM_SILENT_MODE_KEY } from '@tmagic/form';
|
||||||
|
|
||||||
import MagicCodeEditor from '@editor/layouts/CodeEditor.vue';
|
import MagicCodeEditor from '@editor/layouts/CodeEditor.vue';
|
||||||
|
|
||||||
@ -27,6 +28,13 @@ defineOptions({
|
|||||||
name: 'MFieldsVsCode',
|
name: 'MFieldsVsCode',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 静默模式(submitForm/validateForm 隐藏挂载)下跳过 monaco 渲染:
|
||||||
|
* 校验由 FormItem 针对 model 中的值完成,与编辑器实例无关;本组件挂载无值副作用
|
||||||
|
* (save 仅由用户操作/编辑器内容变更触发),跳过可省去 monaco worker/model 的无谓实例化。
|
||||||
|
*/
|
||||||
|
const silentMode = inject(FORM_SILENT_MODE_KEY, false);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
change: [value: string | any];
|
change: [value: string | any];
|
||||||
}>();
|
}>();
|
||||||
|
|||||||
@ -210,6 +210,12 @@ const actionTypeConfig = computed(() => {
|
|||||||
trigger: 'blur',
|
trigger: 'blur',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
onChange: (_mForm: FormState, _v: string, { setModel }: any) => {
|
||||||
|
setModel('to', '');
|
||||||
|
setModel('method', '');
|
||||||
|
setModel('codeId', '');
|
||||||
|
setModel('dataSourceMethod', []);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
return { ...defaultActionTypeConfig, ...props.config.actionTypeConfig };
|
return { ...defaultActionTypeConfig, ...props.config.actionTypeConfig };
|
||||||
});
|
});
|
||||||
@ -222,7 +228,7 @@ const targetCompConfig = computed(() => {
|
|||||||
type: 'ui-select',
|
type: 'ui-select',
|
||||||
labelPosition: 'left',
|
labelPosition: 'left',
|
||||||
display: (_mForm, { model }) => model.actionType === ActionType.COMP,
|
display: (_mForm, { model }) => model.actionType === ActionType.COMP,
|
||||||
onChange: (_MForm, _v, { setModel }) => {
|
onChange: (_mForm, _v, { setModel }) => {
|
||||||
setModel('method', '');
|
setModel('method', '');
|
||||||
},
|
},
|
||||||
rules: [
|
rules: [
|
||||||
|
|||||||
@ -333,11 +333,11 @@ export const initServiceEvents = (
|
|||||||
Promise.all(
|
Promise.all(
|
||||||
nodes.map((node) => {
|
nodes.map((node) => {
|
||||||
if (node.type === NodeType.ROOT) {
|
if (node.type === NodeType.ROOT) {
|
||||||
return Promise.resolve();
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
return depService.collectIdle([node], { pageId: getPageIdByNode(node) }, deep, type);
|
return depService.collectIdle([node], { pageId: getPageIdByNode(node) }, deep, type);
|
||||||
}),
|
}),
|
||||||
);
|
).then((results) => results.every(Boolean));
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => editorService.get('stage'),
|
() => editorService.get('stage'),
|
||||||
@ -351,7 +351,7 @@ export const initServiceEvents = (
|
|||||||
|
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
|
|
||||||
await collectIdle([node], true, DepTargetType.DATA_SOURCE);
|
if (!(await collectIdle([node], true, DepTargetType.DATA_SOURCE))) return;
|
||||||
updateStageNode(node);
|
updateStageNode(node);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -494,8 +494,10 @@ export const initServiceEvents = (
|
|||||||
|
|
||||||
// 新增节点,收集依赖
|
// 新增节点,收集依赖
|
||||||
const nodeAddHandler = (nodes: MComponent[]) => {
|
const nodeAddHandler = (nodes: MComponent[]) => {
|
||||||
collectIdle(nodes, true).then(() => {
|
collectIdle(nodes, true).then((completed) => {
|
||||||
updateStageNodes(nodes);
|
if (completed) {
|
||||||
|
updateStageNodes(nodes);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -549,8 +551,8 @@ export const initServiceEvents = (
|
|||||||
if (needRecollectNodes.length) {
|
if (needRecollectNodes.length) {
|
||||||
// 有数据源依赖,需要等依赖重新收集完才更新stage
|
// 有数据源依赖,需要等依赖重新收集完才更新stage
|
||||||
const handler = async () => {
|
const handler = async () => {
|
||||||
await collectIdle(needRecollectNodes, true, DepTargetType.DATA_SOURCE);
|
if (!(await collectIdle(needRecollectNodes, true, DepTargetType.DATA_SOURCE))) return;
|
||||||
await collectIdle(needRecollectNodes, true, DepTargetType.DATA_SOURCE_COND);
|
if (!(await collectIdle(needRecollectNodes, true, DepTargetType.DATA_SOURCE_COND))) return;
|
||||||
updateStageNodes(needRecollectNodes);
|
updateStageNodes(needRecollectNodes);
|
||||||
};
|
};
|
||||||
handler();
|
handler();
|
||||||
@ -572,8 +574,10 @@ export const initServiceEvents = (
|
|||||||
|
|
||||||
// 历史记录变化时,需要重新收集依赖
|
// 历史记录变化时,需要重新收集依赖
|
||||||
const historyChangeHandler = (page: MPage | MPageFragment) => {
|
const historyChangeHandler = (page: MPage | MPageFragment) => {
|
||||||
collectIdle([page], true).then(() => {
|
collectIdle([page], true).then((completed) => {
|
||||||
updateStageNode(page);
|
if (completed) {
|
||||||
|
updateStageNode(page);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -654,7 +658,7 @@ export const initServiceEvents = (
|
|||||||
if (Array.isArray(root?.items)) {
|
if (Array.isArray(root?.items)) {
|
||||||
depService.clearIdleTasks();
|
depService.clearIdleTasks();
|
||||||
|
|
||||||
let collectIdlePromises: Promise<void[]>[] = [];
|
let collectIdlePromises: Promise<boolean>[] = [];
|
||||||
if (isModifyField) {
|
if (isModifyField) {
|
||||||
depService.removeTarget(config.id, DepTargetType.DATA_SOURCE);
|
depService.removeTarget(config.id, DepTargetType.DATA_SOURCE);
|
||||||
depService.removeTarget(config.id, DepTargetType.DATA_SOURCE_COND);
|
depService.removeTarget(config.id, DepTargetType.DATA_SOURCE_COND);
|
||||||
@ -679,10 +683,15 @@ export const initServiceEvents = (
|
|||||||
|
|
||||||
collectIdlePromises = [collectIdle(root.items, true, DepTargetType.DATA_SOURCE_METHOD)];
|
collectIdlePromises = [collectIdle(root.items, true, DepTargetType.DATA_SOURCE_METHOD)];
|
||||||
}
|
}
|
||||||
Promise.all(collectIdlePromises)
|
const handler = async () => {
|
||||||
.then(() => updateDataSourceSchema())
|
const results = await Promise.all(collectIdlePromises);
|
||||||
.then(() => updateDsData())
|
if (!results.every(Boolean)) return;
|
||||||
.then(() => updateStageNodes(root.items));
|
|
||||||
|
updateDataSourceSchema();
|
||||||
|
await updateDsData();
|
||||||
|
updateStageNodes(root.items);
|
||||||
|
};
|
||||||
|
handler();
|
||||||
}
|
}
|
||||||
} else if (root?.dataSources) {
|
} else if (root?.dataSources) {
|
||||||
updateDsData();
|
updateDsData();
|
||||||
@ -706,11 +715,12 @@ export const initServiceEvents = (
|
|||||||
const nodeIds = Object.keys(root.dataSourceDeps?.[id] || {});
|
const nodeIds = Object.keys(root.dataSourceDeps?.[id] || {});
|
||||||
const nodes = getNodes(nodeIds, root.items);
|
const nodes = getNodes(nodeIds, root.items);
|
||||||
|
|
||||||
await Promise.all([
|
const results = await Promise.all([
|
||||||
collectIdle(nodes, false, DepTargetType.DATA_SOURCE),
|
collectIdle(nodes, false, DepTargetType.DATA_SOURCE),
|
||||||
collectIdle(nodes, false, DepTargetType.DATA_SOURCE_COND),
|
collectIdle(nodes, false, DepTargetType.DATA_SOURCE_COND),
|
||||||
collectIdle(nodes, false, DepTargetType.DATA_SOURCE_METHOD),
|
collectIdle(nodes, false, DepTargetType.DATA_SOURCE_METHOD),
|
||||||
]);
|
]);
|
||||||
|
if (!results.every(Boolean)) return;
|
||||||
|
|
||||||
updateDataSourceSchema();
|
updateDataSourceSchema();
|
||||||
|
|
||||||
|
|||||||
@ -49,14 +49,13 @@ import { Document as DocumentIcon } from '@element-plus/icons-vue';
|
|||||||
|
|
||||||
import { TMagicButton, tMagicMessage, TMagicScrollbar } from '@tmagic/design';
|
import { TMagicButton, tMagicMessage, TMagicScrollbar } from '@tmagic/design';
|
||||||
import type { ContainerChangeEventData, FormConfig, FormState, FormValue } from '@tmagic/form';
|
import type { ContainerChangeEventData, FormConfig, FormState, FormValue } from '@tmagic/form';
|
||||||
import { MForm } from '@tmagic/form';
|
import { MForm, validateForm } from '@tmagic/form';
|
||||||
import { filterXSS } from '@tmagic/utils';
|
import { filterXSS } from '@tmagic/utils';
|
||||||
|
|
||||||
import MIcon from '@editor/components/Icon.vue';
|
import MIcon from '@editor/components/Icon.vue';
|
||||||
import { ENABLE_PROPS_FORM_VALIDATE } from '@editor/editorProps';
|
import { ENABLE_PROPS_FORM_VALIDATE } from '@editor/editorProps';
|
||||||
import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height';
|
import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height';
|
||||||
import { useServices } from '@editor/hooks/use-services';
|
import { useServices } from '@editor/hooks/use-services';
|
||||||
import { validatePropsForm } from '@editor/utils/props';
|
|
||||||
|
|
||||||
import CodeEditor from '../CodeEditor.vue';
|
import CodeEditor from '../CodeEditor.vue';
|
||||||
|
|
||||||
@ -162,14 +161,19 @@ const saveCode = async (values: any) => {
|
|||||||
// 做一次静默校验(不复用、也不污染页面上正在展示的表单),并将校验结果(错误信息)随提交
|
// 做一次静默校验(不复用、也不污染页面上正在展示的表单),并将校验结果(错误信息)随提交
|
||||||
// 一并抛给上层记录,使源码保存的错误状态与表单编辑保持一致。
|
// 一并抛给上层记录,使源码保存的错误状态与表单编辑保持一致。
|
||||||
try {
|
try {
|
||||||
const error = await validatePropsForm({
|
const error = await validateForm({
|
||||||
config: props.config,
|
config: props.config,
|
||||||
values: newValues,
|
|
||||||
appContext: internalInstance?.appContext ?? null,
|
|
||||||
services,
|
|
||||||
stage: stage.value,
|
|
||||||
typeMatchValid: true,
|
typeMatchValid: true,
|
||||||
extendState: props.extendState,
|
initValues: newValues,
|
||||||
|
// 将当前组件实例的 provides(含 Editor 顶层的 services / codeOptions 等组件级 provide)
|
||||||
|
// 合入 appContext,使临时 MForm 中的编辑器字段组件(DataSourceInput 等)能正常 inject
|
||||||
|
appContext: internalInstance?.appContext ? { ...internalInstance?.appContext, provides: { services } } : null,
|
||||||
|
extendState: (state) => {
|
||||||
|
if (configFormRef.value?.formState) {
|
||||||
|
return { ...(configFormRef.value?.formState || {}) };
|
||||||
|
}
|
||||||
|
return props.extendState?.(state) || {};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|||||||
@ -164,17 +164,25 @@ const submit = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (v.style) {
|
if (v.style) {
|
||||||
Object.entries(v.style).forEach(([key, value]) => {
|
// 空字符串样式值表示「清除该样式」,需保留才能在 doUpdate 的 mergeWith 中覆盖旧值。
|
||||||
if (value !== '' && newValue.style) {
|
if (eventData) {
|
||||||
newValue.style[key] = value;
|
// 表单编辑:先过滤掉空字符串(避免表单默认空值污染 DSL),
|
||||||
}
|
// 再按 changeRecords 恢复被主动清空的字段。
|
||||||
});
|
Object.entries(v.style).forEach(([key, value]) => {
|
||||||
|
if (value !== '' && newValue.style) {
|
||||||
|
newValue.style[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
eventData?.changeRecords?.forEach((record) => {
|
eventData.changeRecords?.forEach((record) => {
|
||||||
if (record.propPath?.startsWith('style') && record.value === '') {
|
if (record.propPath?.startsWith('style') && record.value === '') {
|
||||||
setValueByKeyPath(record.propPath, record.value, newValue);
|
setValueByKeyPath(record.propPath, record.value, newValue);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// 源码编辑器保存(无 eventData):style 原样保留,其中的空字符串视为用户主动清除该样式。
|
||||||
|
newValue.style = { ...v.style };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 区分操作途径:表单字段编辑(MForm @change)会带上 eventData(含 changeRecords);
|
// 区分操作途径:表单字段编辑(MForm @change)会带上 eventData(含 changeRecords);
|
||||||
|
|||||||
@ -42,6 +42,21 @@ interface State {
|
|||||||
|
|
||||||
type StateKey = keyof State;
|
type StateKey = keyof State;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一次 collectIdle 调用对应一个批次。批次自行统计入队/完成的任务数,在自身任务全部完成或被中断时
|
||||||
|
* 结算对应的 Promise,避免多次快速触发时共用 idleTask 的 finish 事件造成的串扰、提前 resolve,
|
||||||
|
* 以及 clearTasks 后 Promise 永不 resolve(collecting 卡死、监听器泄漏)。
|
||||||
|
*/
|
||||||
|
interface CollectBatch {
|
||||||
|
nodes: MNode[];
|
||||||
|
deep: boolean;
|
||||||
|
pending: number;
|
||||||
|
dsPending: number;
|
||||||
|
collectedEmitted: boolean;
|
||||||
|
dsSettled: boolean;
|
||||||
|
resolve: (completed: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
class Dep extends BaseService {
|
class Dep extends BaseService {
|
||||||
private state = shallowReactive<State>({
|
private state = shallowReactive<State>({
|
||||||
collecting: false,
|
collecting: false,
|
||||||
@ -54,6 +69,12 @@ class Dep extends BaseService {
|
|||||||
|
|
||||||
private waitingWorker?: Promise<void>;
|
private waitingWorker?: Promise<void>;
|
||||||
|
|
||||||
|
private resolveWaitingWorker?: () => void;
|
||||||
|
|
||||||
|
private workerGeneration = 0;
|
||||||
|
|
||||||
|
private activeBatches = new Set<CollectBatch>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@ -137,38 +158,44 @@ class Dep extends BaseService {
|
|||||||
await this.waitingWorker;
|
await this.waitingWorker;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.set('collecting', true);
|
const batch: CollectBatch = {
|
||||||
let startTask = false;
|
nodes,
|
||||||
this.watcher.collectByCallback(nodes, type, ({ node, target }) => {
|
deep,
|
||||||
startTask = true;
|
pending: 0,
|
||||||
|
dsPending: 0,
|
||||||
|
collectedEmitted: false,
|
||||||
|
dsSettled: false,
|
||||||
|
resolve: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
this.enqueueTask(node, target, depExtendedData, deep);
|
this.watcher.collectByCallback(nodes, type, ({ node, target }) => {
|
||||||
|
this.enqueueTask(node, target, depExtendedData, deep, batch);
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise<void>((resolve) => {
|
// 没有命中任何 target,无需收集,直接完成
|
||||||
if (!startTask) {
|
if (batch.pending === 0) {
|
||||||
this.emit('collected', nodes, deep);
|
this.emit('collected', nodes, deep);
|
||||||
this.set('collecting', false);
|
this.updateCollectingState();
|
||||||
resolve();
|
return true;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
this.idleTask.once('finish', () => {
|
this.activeBatches.add(batch);
|
||||||
this.emit('collected', nodes, deep);
|
this.set('collecting', true);
|
||||||
this.set('collecting', false);
|
|
||||||
});
|
return new Promise<boolean>((resolve) => {
|
||||||
this.idleTask.once('hight-level-finish', () => {
|
batch.resolve = resolve;
|
||||||
this.emit('ds-collected', nodes, deep);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public collectByWorker(dsl: MApp) {
|
public collectByWorker(dsl: MApp) {
|
||||||
this.set('collecting', true);
|
this.set('collecting', true);
|
||||||
|
this.workerGeneration += 1;
|
||||||
|
const generation = this.workerGeneration;
|
||||||
|
|
||||||
const { promise, resolve: waitingResolve } = Promise.withResolvers<void>();
|
const { promise, resolve: waitingResolve } = Promise.withResolvers<void>();
|
||||||
|
|
||||||
this.waitingWorker = promise;
|
this.waitingWorker = promise;
|
||||||
|
this.resolveWaitingWorker = waitingResolve;
|
||||||
|
|
||||||
return new Promise<Record<string, Record<string, DepData>>>((resolve) => {
|
return new Promise<Record<string, Record<string, DepData>>>((resolve) => {
|
||||||
const worker = new Work();
|
const worker = new Work();
|
||||||
@ -182,6 +209,11 @@ class Dep extends BaseService {
|
|||||||
resolve({});
|
resolve({});
|
||||||
};
|
};
|
||||||
}).then((depsData) => {
|
}).then((depsData) => {
|
||||||
|
if (generation !== this.workerGeneration) {
|
||||||
|
waitingResolve();
|
||||||
|
return depsData;
|
||||||
|
}
|
||||||
|
|
||||||
traverseTarget(this.watcher.getTargetsList(), (target) => {
|
traverseTarget(this.watcher.getTargetsList(), (target) => {
|
||||||
if (depsData[target.type]?.[target.id]) {
|
if (depsData[target.type]?.[target.id]) {
|
||||||
target.deps = reactive(depsData[target.type][target.id]);
|
target.deps = reactive(depsData[target.type][target.id]);
|
||||||
@ -200,6 +232,10 @@ class Dep extends BaseService {
|
|||||||
this.emit('collected', dsl.items, true);
|
this.emit('collected', dsl.items, true);
|
||||||
this.emit('ds-collected', dsl.items, true);
|
this.emit('ds-collected', dsl.items, true);
|
||||||
waitingResolve();
|
waitingResolve();
|
||||||
|
if (this.waitingWorker === promise) {
|
||||||
|
this.waitingWorker = undefined;
|
||||||
|
this.resolveWaitingWorker = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
return depsData;
|
return depsData;
|
||||||
});
|
});
|
||||||
@ -233,6 +269,7 @@ class Dep extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public clearIdleTasks() {
|
public clearIdleTasks() {
|
||||||
|
this.abortActiveBatches();
|
||||||
this.idleTask.clearTasks();
|
this.idleTask.clearTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,6 +288,11 @@ class Dep extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public reset() {
|
public reset() {
|
||||||
|
this.abortActiveBatches();
|
||||||
|
this.workerGeneration += 1;
|
||||||
|
this.resolveWaitingWorker?.();
|
||||||
|
this.resolveWaitingWorker = undefined;
|
||||||
|
this.waitingWorker = undefined;
|
||||||
this.idleTask.clearTasks();
|
this.idleTask.clearTasks();
|
||||||
|
|
||||||
for (const type of Object.keys(this.watcher.getTargetsList())) {
|
for (const type of Object.keys(this.watcher.getTargetsList())) {
|
||||||
@ -286,25 +328,99 @@ class Dep extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enqueueTask(node: MNode, target: Target, depExtendedData: DepExtendedData, deep: boolean) {
|
private enqueueTask(
|
||||||
|
node: MNode,
|
||||||
|
target: Target,
|
||||||
|
depExtendedData: DepExtendedData,
|
||||||
|
deep: boolean,
|
||||||
|
batch: CollectBatch,
|
||||||
|
) {
|
||||||
|
const isDataSource = target.type === DepTargetType.DATA_SOURCE;
|
||||||
|
|
||||||
|
batch.pending += 1;
|
||||||
|
if (isDataSource) {
|
||||||
|
batch.dsPending += 1;
|
||||||
|
}
|
||||||
|
|
||||||
this.idleTask.enqueueTask(
|
this.idleTask.enqueueTask(
|
||||||
({ node, deep, target }) => {
|
({ node, deep, target }) => {
|
||||||
this.collectNode(node, target, depExtendedData, deep);
|
try {
|
||||||
|
this.collectNode(node, target, depExtendedData, deep);
|
||||||
|
} finally {
|
||||||
|
this.onBatchTaskDone(batch, isDataSource);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
node,
|
node,
|
||||||
deep: false,
|
deep: false,
|
||||||
target,
|
target,
|
||||||
},
|
},
|
||||||
target.type === DepTargetType.DATA_SOURCE,
|
isDataSource,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (deep && Array.isArray(node.items) && node.items.length) {
|
if (deep && Array.isArray(node.items) && node.items.length) {
|
||||||
node.items.forEach((item) => {
|
node.items.forEach((item) => {
|
||||||
this.enqueueTask(item, target, depExtendedData, deep);
|
this.enqueueTask(item, target, depExtendedData, deep, batch);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onBatchTaskDone(batch: CollectBatch, isDataSource: boolean) {
|
||||||
|
if (isDataSource) {
|
||||||
|
batch.dsPending -= 1;
|
||||||
|
// 数据源依赖收集完先 resolve,让 stage 尽快更新,其余依赖继续在后台收集
|
||||||
|
if (batch.dsPending === 0) {
|
||||||
|
this.settleBatchDs(batch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
batch.pending -= 1;
|
||||||
|
if (batch.pending === 0) {
|
||||||
|
this.finishBatch(batch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private settleBatchDs(batch: CollectBatch) {
|
||||||
|
if (batch.dsSettled) return;
|
||||||
|
batch.dsSettled = true;
|
||||||
|
this.emit('ds-collected', batch.nodes, batch.deep);
|
||||||
|
batch.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private finishBatch(batch: CollectBatch) {
|
||||||
|
if (!batch.collectedEmitted) {
|
||||||
|
batch.collectedEmitted = true;
|
||||||
|
this.emit('collected', batch.nodes, batch.deep);
|
||||||
|
}
|
||||||
|
// 没有数据源任务的批次在此结算 Promise
|
||||||
|
this.settleBatchDs(batch);
|
||||||
|
this.activeBatches.delete(batch);
|
||||||
|
this.updateCollectingState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* idleTask 被清空时,在途批次的任务不会再执行,必须主动结算,
|
||||||
|
* 否则对应的 collectIdle Promise 永远不会 resolve(collecting 卡在 true、once 监听器泄漏)。
|
||||||
|
* 收集被中断(通常紧跟一次全量重新收集),因此不再 emit collected/ds-collected,仅结算 Promise。
|
||||||
|
*/
|
||||||
|
private abortActiveBatches() {
|
||||||
|
if (!this.activeBatches.size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const batches = [...this.activeBatches];
|
||||||
|
this.activeBatches.clear();
|
||||||
|
|
||||||
|
for (const batch of batches) {
|
||||||
|
batch.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateCollectingState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateCollectingState() {
|
||||||
|
this.set('collecting', this.activeBatches.size > 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DepService = Dep;
|
export type DepService = Dep;
|
||||||
|
|||||||
@ -554,6 +554,12 @@ class Editor extends BaseService {
|
|||||||
doNotPushHistory,
|
doNotPushHistory,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 页面 / 页面片新增不入历史栈(见上方 isPage / isPageFragment 分支),这里合并补发一次结构变更通知
|
||||||
|
const addedPages = newNodes.filter((node) => isPage(node) || isPageFragment(node)) as (MPage | MPageFragment)[];
|
||||||
|
if (addedPages.length) {
|
||||||
|
historyService.notifyPageStructureChange({ add: addedPages, remove: [] });
|
||||||
|
}
|
||||||
|
|
||||||
return Array.isArray(addNode) ? newNodes : newNodes[0];
|
return Array.isArray(addNode) ? newNodes : newNodes[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -695,6 +701,12 @@ class Editor extends BaseService {
|
|||||||
|
|
||||||
this.emit('remove', nodes);
|
this.emit('remove', nodes);
|
||||||
this.emit('change', { type: 'remove', data: changeItems, historySource, doNotPushHistory });
|
this.emit('change', { type: 'remove', data: changeItems, historySource, doNotPushHistory });
|
||||||
|
|
||||||
|
// 页面 / 页面片删除不入历史栈(见上方 isPage / isPageFragment 分支),这里合并补发一次结构变更通知
|
||||||
|
const removedPages = nodes.filter((node) => isPage(node) || isPageFragment(node)) as (MPage | MPageFragment)[];
|
||||||
|
if (removedPages.length) {
|
||||||
|
historyService.notifyPageStructureChange({ add: [], remove: removedPages });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async doUpdate(
|
public async doUpdate(
|
||||||
@ -1776,6 +1788,10 @@ class Editor extends BaseService {
|
|||||||
const nextMap = new Map(nextPages.map((p) => [`${p.id}`, p]));
|
const nextMap = new Map(nextPages.map((p) => [`${p.id}`, p]));
|
||||||
const indexInItems = (root: MApp, id: Id) => (root.items ?? []).findIndex((item) => `${item.id}` === `${id}`);
|
const indexInItems = (root: MApp, id: Id) => (root.items ?? []).findIndex((item) => `${item.id}` === `${id}`);
|
||||||
|
|
||||||
|
// 收集本次整体替换中增删的页面,循环结束后合并为一次结构变更通知(避免逐页派发多个事件)
|
||||||
|
const addedPages: (MPage | MPageFragment)[] = [];
|
||||||
|
const removedPages: (MPage | MPageFragment)[] = [];
|
||||||
|
|
||||||
nextPages.forEach((nextPage) => {
|
nextPages.forEach((nextPage) => {
|
||||||
const prevPage = prevMap.get(`${nextPage.id}`);
|
const prevPage = prevMap.get(`${nextPage.id}`);
|
||||||
if (!prevPage) {
|
if (!prevPage) {
|
||||||
@ -1785,6 +1801,7 @@ class Editor extends BaseService {
|
|||||||
{ newSchema: cloneDeep(toRaw(nextPage)), parentId: nextRoot.id, index: indexInItems(nextRoot, nextPage.id) },
|
{ newSchema: cloneDeep(toRaw(nextPage)), parentId: nextRoot.id, index: indexInItems(nextRoot, nextPage.id) },
|
||||||
source,
|
source,
|
||||||
);
|
);
|
||||||
|
addedPages.push(nextPage);
|
||||||
} else if (!isEqual(toRaw(prevPage), toRaw(nextPage))) {
|
} else if (!isEqual(toRaw(prevPage), toRaw(nextPage))) {
|
||||||
this.pushPageDiffStep(
|
this.pushPageDiffStep(
|
||||||
'update',
|
'update',
|
||||||
@ -1803,8 +1820,13 @@ class Editor extends BaseService {
|
|||||||
{ oldSchema: cloneDeep(toRaw(prevPage)), parentId: preRoot.id, index: indexInItems(preRoot, prevPage.id) },
|
{ oldSchema: cloneDeep(toRaw(prevPage)), parentId: preRoot.id, index: indexInItems(preRoot, prevPage.id) },
|
||||||
source,
|
source,
|
||||||
);
|
);
|
||||||
|
removedPages.push(prevPage);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (addedPages.length || removedPages.length) {
|
||||||
|
historyService.notifyPageStructureChange({ add: addedPages, remove: removedPages });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
import { reactive } from 'vue';
|
import { reactive } from 'vue';
|
||||||
import type { Writable } from 'type-fest';
|
import type { Writable } from 'type-fest';
|
||||||
|
|
||||||
import type { Id } from '@tmagic/core';
|
import type { Id, MPage, MPageFragment } from '@tmagic/core';
|
||||||
import { guid } from '@tmagic/utils';
|
import { guid } from '@tmagic/utils';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -353,6 +353,27 @@ class History extends BaseService {
|
|||||||
this.emit('mark-saved', { kind: stepType, id });
|
this.emit('mark-saved', { kind: stepType, id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 派发「页面 / 页面片结构变更」事件(`page-structure-change`)。
|
||||||
|
*
|
||||||
|
* 常规 `editorService.add` / `remove` 页面节点不会写入 `page` 历史栈(见 editor.add / remove 中
|
||||||
|
* 对 isPage / isPageFragment 的分支),因此不会产生任何 historyService 事件。该方法用于在这些
|
||||||
|
* 场景(以及 setRoot 整体替换 DSL 增删页面)下,向外统一通知页面结构的增删变化,供业务方感知。
|
||||||
|
*
|
||||||
|
* 一次操作涉及多个页面时,调用方应把本次增删的页面**合并为一个 change 一次性传入**,
|
||||||
|
* 使一次操作只派发一个事件;`add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。
|
||||||
|
*
|
||||||
|
* @param change 本次结构变更:{ add: 新增的页面列表, remove: 删除的页面列表 }
|
||||||
|
*/
|
||||||
|
public notifyPageStructureChange(change: {
|
||||||
|
add: (MPage | MPageFragment)[];
|
||||||
|
remove: (MPage | MPageFragment)[];
|
||||||
|
}): void {
|
||||||
|
// 增删均为空时不派发,避免无意义通知
|
||||||
|
if (!change.add.length && !change.remove.length) return;
|
||||||
|
this.emit('page-structure-change', change);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 把当前内存中的全部历史栈(页面 / 代码块 / 数据源 / 扩展类型)序列化后写入本地 IndexedDB。
|
* 把当前内存中的全部历史栈(页面 / 代码块 / 数据源 / 扩展类型)序列化后写入本地 IndexedDB。
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1385,6 +1385,12 @@ export interface HistoryEvents {
|
|||||||
'mark-saved': [{ kind: HistoryStepType; id?: Id }];
|
'mark-saved': [{ kind: HistoryStepType; id?: Id }];
|
||||||
clear: [{ id: Id; stepType: HistoryStepType }];
|
clear: [{ id: Id; stepType: HistoryStepType }];
|
||||||
'marker-change': [{ id: Id; marker: StepValue; stepType: HistoryStepType }];
|
'marker-change': [{ id: Id; marker: StepValue; stepType: HistoryStepType }];
|
||||||
|
/**
|
||||||
|
* 页面 / 页面片结构变更(新增 / 删除)时派发,见 {@link HistoryService.notifyPageStructureChange}。
|
||||||
|
* 一次操作(add / remove / setRoot 整体替换)涉及多个页面时合并为**一个**事件;
|
||||||
|
* `add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。
|
||||||
|
*/
|
||||||
|
'page-structure-change': [change: { add: (MPage | MPageFragment)[]; remove: (MPage | MPageFragment)[] }];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const canUsePluginMethods = {
|
export const canUsePluginMethods = {
|
||||||
|
|||||||
@ -243,7 +243,7 @@ export const resolveFieldByPath = (
|
|||||||
fields: DataSchema[] | undefined,
|
fields: DataSchema[] | undefined,
|
||||||
fieldNames: string[],
|
fieldNames: string[],
|
||||||
options: { skipNumberIndices?: boolean } = {},
|
options: { skipNumberIndices?: boolean } = {},
|
||||||
): { ok: boolean; field?: DataSchema; fields: DataSchema[] } => {
|
): { ok: boolean; field?: DataSchema; fields: DataSchema[]; failedName?: string } => {
|
||||||
let currentFields = fields || [];
|
let currentFields = fields || [];
|
||||||
let field: DataSchema | undefined;
|
let field: DataSchema | undefined;
|
||||||
|
|
||||||
@ -252,11 +252,11 @@ export const resolveFieldByPath = (
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!currentFields.length) {
|
if (!currentFields.length) {
|
||||||
return { ok: false, fields: currentFields };
|
return { ok: false, fields: currentFields, failedName: name };
|
||||||
}
|
}
|
||||||
field = currentFields.find((item) => item.name === name);
|
field = currentFields.find((item) => item.name === name);
|
||||||
if (!field) {
|
if (!field) {
|
||||||
return { ok: false, fields: currentFields };
|
return { ok: false, fields: currentFields, failedName: name };
|
||||||
}
|
}
|
||||||
currentFields = field.fields || [];
|
currentFields = field.fields || [];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,12 @@ globalThis.requestIdleCallback =
|
|||||||
}, 1);
|
}, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
globalThis.cancelIdleCallback =
|
||||||
|
globalThis.cancelIdleCallback ||
|
||||||
|
function (handle) {
|
||||||
|
clearTimeout(handle as unknown as ReturnType<typeof setTimeout>);
|
||||||
|
};
|
||||||
|
|
||||||
export class IdleTask<T = any> extends EventEmitter {
|
export class IdleTask<T = any> extends EventEmitter {
|
||||||
private taskList: TaskList<T> = [];
|
private taskList: TaskList<T> = [];
|
||||||
|
|
||||||
|
|||||||
@ -16,9 +16,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { AppContext } from 'vue';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
HookType,
|
HookType,
|
||||||
NODE_CONDS_KEY,
|
NODE_CONDS_KEY,
|
||||||
@ -27,18 +24,7 @@ import {
|
|||||||
NODE_DISABLE_DATA_SOURCE_KEY,
|
NODE_DISABLE_DATA_SOURCE_KEY,
|
||||||
} from '@tmagic/core';
|
} from '@tmagic/core';
|
||||||
import { tMagicMessage } from '@tmagic/design';
|
import { tMagicMessage } from '@tmagic/design';
|
||||||
import type {
|
import type { ChildConfig, DisplayCondsConfig, FormConfig, TabConfig, TabPaneConfig } from '@tmagic/form';
|
||||||
ChildConfig,
|
|
||||||
DisplayCondsConfig,
|
|
||||||
FormConfig,
|
|
||||||
FormState,
|
|
||||||
FormValue,
|
|
||||||
TabConfig,
|
|
||||||
TabPaneConfig,
|
|
||||||
} from '@tmagic/form';
|
|
||||||
import { validateForm } from '@tmagic/form';
|
|
||||||
|
|
||||||
import type { Services } from '@editor/type';
|
|
||||||
|
|
||||||
export const arrayOptions = [
|
export const arrayOptions = [
|
||||||
{ text: '包含', value: 'include' },
|
{ text: '包含', value: 'include' },
|
||||||
@ -403,86 +389,3 @@ export const removeStyleDisplayConfig = (formConfig: FormConfig): FormConfig =>
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// #region ValidatePropsFormOptions
|
|
||||||
/**
|
|
||||||
* validatePropsForm 参数
|
|
||||||
*/
|
|
||||||
export interface ValidatePropsFormOptions {
|
|
||||||
/** 组件属性表单配置 */
|
|
||||||
config: FormConfig;
|
|
||||||
/** 待校验的表单值 */
|
|
||||||
values: FormValue;
|
|
||||||
/**
|
|
||||||
* 当前组件实例的 appContext(通常为 `getCurrentInstance()?.appContext`)。
|
|
||||||
* 会与 services 一并合入临时 MForm 的 appContext,使编辑器字段组件(DataSourceInput 等)能正常 inject。
|
|
||||||
*/
|
|
||||||
appContext?: AppContext | null;
|
|
||||||
/** 编辑器服务集合,注入到临时表单的 formState */
|
|
||||||
services?: Services;
|
|
||||||
/** stage 实例,注入到临时表单的 formState */
|
|
||||||
stage?: any;
|
|
||||||
/** 外部扩展的 formState */
|
|
||||||
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
||||||
/**
|
|
||||||
* 调试模式,默认 `true`:以弹层形式可见地渲染表单,点击「确定」才触发校验。
|
|
||||||
* 置为 `false` 时静默挂载后自动校验。
|
|
||||||
*/
|
|
||||||
debug?: boolean;
|
|
||||||
typeMatchValid?: boolean;
|
|
||||||
}
|
|
||||||
// #endregion ValidatePropsFormOptions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对一份「组件属性表单配置 + 值」做一次独立的校验,**不复用也不污染页面上正在展示的表单**。
|
|
||||||
*
|
|
||||||
* 内部基于 `@tmagic/form` 的 `validateForm` 另建一个独立的 MForm 实例完成校验,并统一处理
|
|
||||||
* 编辑器场景所需的上下文注入:将当前组件实例的 provides 合入 appContext,并向 formState 注入
|
|
||||||
* stage / services 及外部扩展状态,保证校验规则依赖的上下文可用。
|
|
||||||
*
|
|
||||||
* 常用于源码编辑器保存后,对最新配置做一次校验,并将校验结果(错误信息)随提交一并抛给上层记录,
|
|
||||||
* 使源码保存的错误状态与表单编辑保持一致。
|
|
||||||
*
|
|
||||||
* @returns 校验通过返回空字符串 `''`,否则返回以 `<br>` 拼接的错误文案。
|
|
||||||
* 仅在初始化超时或挂载失败等异常情况下才会 reject。
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const error = await validatePropsForm({
|
|
||||||
* config,
|
|
||||||
* values,
|
|
||||||
* appContext: getCurrentInstance()?.appContext,
|
|
||||||
* services,
|
|
||||||
* stage: editorService.get('stage'),
|
|
||||||
* extendState,
|
|
||||||
* });
|
|
||||||
* if (error) {
|
|
||||||
* // 配置不合法,error 为错误文案
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const validatePropsForm = ({
|
|
||||||
config,
|
|
||||||
values,
|
|
||||||
appContext = null,
|
|
||||||
services,
|
|
||||||
stage,
|
|
||||||
extendState,
|
|
||||||
debug,
|
|
||||||
typeMatchValid,
|
|
||||||
}: ValidatePropsFormOptions): Promise<string> =>
|
|
||||||
validateForm({
|
|
||||||
config,
|
|
||||||
debug,
|
|
||||||
typeMatchValid,
|
|
||||||
initValues: values,
|
|
||||||
// 将当前组件实例的 provides(含 Editor 顶层的 services / codeOptions 等组件级 provide)
|
|
||||||
// 合入 appContext,使临时 MForm 中的编辑器字段组件(DataSourceInput 等)能正常 inject
|
|
||||||
appContext: appContext ? { ...appContext, provides: { services } } : null,
|
|
||||||
// 与页面表单保持一致:注入 stage/services 及外部扩展状态,保证校验规则依赖的上下文可用
|
|
||||||
extendState: async (state) => ({
|
|
||||||
...((await extendState?.(state)) || {}),
|
|
||||||
stage,
|
|
||||||
services,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import type { DataSourceFieldType, DataSourceSchema, Id } from '@tmagic/core';
|
|||||||
import { NodeType } from '@tmagic/core';
|
import { NodeType } from '@tmagic/core';
|
||||||
import { appendValidateSuggestion } from '@tmagic/design';
|
import { appendValidateSuggestion } from '@tmagic/design';
|
||||||
import type { TypeMatchValidateContext, TypeMatchValidator } from '@tmagic/form';
|
import type { TypeMatchValidateContext, TypeMatchValidator } from '@tmagic/form';
|
||||||
|
import { validateTypeMatch } from '@tmagic/form';
|
||||||
import {
|
import {
|
||||||
DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX,
|
DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX,
|
||||||
DATA_SOURCE_SET_DATA_METHOD_NAME,
|
DATA_SOURCE_SET_DATA_METHOD_NAME,
|
||||||
@ -57,7 +58,7 @@ const stringifyExampleValue = (value: any): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 参考建议中最多展示的可选值个数,超出以「等」省略。
|
// 参考建议中最多展示的可选值个数,超出以「等」省略。
|
||||||
const MAX_SUGGESTION_OPTIONS = 5;
|
const MAX_SUGGESTION_OPTIONS = 20;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成「请使用以下某一个值:xxx;xxx」形式的参考建议;无可选值时返回空字符串(不追加建议)。
|
* 生成「请使用以下某一个值:xxx;xxx」形式的参考建议;无可选值时返回空字符串(不追加建议)。
|
||||||
@ -261,16 +262,20 @@ const validateDataSourceFieldPath = (
|
|||||||
|
|
||||||
const ds = findDataSource(`${dsId}`);
|
const ds = findDataSource(`${dsId}`);
|
||||||
if (!ds) {
|
if (!ds) {
|
||||||
return defaultMessage(options.message, '值不在可选项中', dataSourceIdSuggestion());
|
return defaultMessage(options.message, `数据源(${dsId})不存在`, dataSourceIdSuggestion());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fieldNames.length) {
|
if (!fieldNames.length) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { field, ok } = resolveFieldByPath(ds.fields, fieldNames);
|
const { field, ok, fields, failedName } = resolveFieldByPath(ds.fields, fieldNames);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return defaultMessage(options.message, '值不在可选项中', dataSourceIdSuggestion());
|
return defaultMessage(
|
||||||
|
options.message,
|
||||||
|
`数据源字段(${failedName})不存在`,
|
||||||
|
listSuggestion(fields.map((item) => item.name)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const allowedTypes = options.dataSourceFieldType || ['any'];
|
const allowedTypes = options.dataSourceFieldType || ['any'];
|
||||||
@ -471,11 +476,12 @@ const isDataSourceFieldPathValue = (value: any, config: any): value is string[]
|
|||||||
return `${value[0]}`.startsWith(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);
|
return `${value[0]}`.startsWith(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateDataSourceFieldSelect: TypeMatchValidator = (value, { message, props }) => {
|
const validateDataSourceFieldSelect: TypeMatchValidator = (value, { mForm, message, props }) => {
|
||||||
const config = props.config || {};
|
const config = props.config || {};
|
||||||
|
|
||||||
if (config.fieldConfig && !isDataSourceFieldPathValue(value, config)) {
|
if (config.fieldConfig && !isDataSourceFieldPathValue(value, config)) {
|
||||||
return undefined;
|
// 值不是数据源字段路径时,按 fieldConfig 的类型校验(与表单项自身 typeMatch 行为一致)
|
||||||
|
return validateTypeMatch(value, mForm, { ...props, config: { name: config.name, ...config.fieldConfig } }, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
|
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
|
||||||
|
|||||||
@ -4,10 +4,27 @@
|
|||||||
* Copyright (C) 2025 Tencent.
|
* Copyright (C) 2025 Tencent.
|
||||||
*/
|
*/
|
||||||
import { afterEach, describe, expect, test, vi } from 'vitest';
|
import { afterEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
import { nextTick } from 'vue';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
|
|
||||||
import ContentMenu from '@editor/components/ContentMenu.vue';
|
import ContentMenu from '@editor/components/ContentMenu.vue';
|
||||||
|
|
||||||
|
const roCallbacks: Array<() => void> = [];
|
||||||
|
class FakeResizeObserver {
|
||||||
|
public static disconnectSpy = vi.fn();
|
||||||
|
public cb: () => void;
|
||||||
|
constructor(cb: () => void) {
|
||||||
|
this.cb = cb;
|
||||||
|
roCallbacks.push(cb);
|
||||||
|
}
|
||||||
|
public observe() {}
|
||||||
|
public unobserve() {}
|
||||||
|
public disconnect() {
|
||||||
|
FakeResizeObserver.disconnectSpy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(globalThis as any).ResizeObserver = FakeResizeObserver;
|
||||||
|
|
||||||
const provideServices = () => ({
|
const provideServices = () => ({
|
||||||
global: {
|
global: {
|
||||||
provide: {
|
provide: {
|
||||||
@ -22,6 +39,8 @@ const provideServices = () => ({
|
|||||||
describe('ContentMenu.vue', () => {
|
describe('ContentMenu.vue', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
|
roCallbacks.length = 0;
|
||||||
|
FakeResizeObserver.disconnectSpy.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('show 后触发 show 事件', async () => {
|
test('show 后触发 show 事件', async () => {
|
||||||
@ -67,6 +86,62 @@ describe('ContentMenu.vue', () => {
|
|||||||
expect((wrapper.vm as any).menuPosition.top).toBeLessThanOrEqual(100);
|
expect((wrapper.vm as any).menuPosition.top).toBeLessThanOrEqual(100);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('菜单大小动态变化后修正位置避免超出可视范围', async () => {
|
||||||
|
// jsdom 中 clientWidth/clientHeight 默认为 0,先 mock 一个足够大的视口,避免 show 时位置被修正
|
||||||
|
Object.defineProperty(document.body, 'clientHeight', { value: 1000, configurable: true });
|
||||||
|
Object.defineProperty(document.body, 'clientWidth', { value: 1000, configurable: true });
|
||||||
|
|
||||||
|
const wrapper = mount(ContentMenu as any, {
|
||||||
|
...provideServices(),
|
||||||
|
props: { menuData: [{ id: '1', type: 'button', text: 'a' }] as any },
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
(wrapper.vm as any).show({ clientX: 90, clientY: 90 });
|
||||||
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
|
expect((wrapper.vm as any).menuPosition.top).toBe(90);
|
||||||
|
expect((wrapper.vm as any).menuPosition.left).toBe(90);
|
||||||
|
|
||||||
|
// 视口缩小为 100x100
|
||||||
|
Object.defineProperty(document.body, 'clientHeight', { value: 100, configurable: true });
|
||||||
|
Object.defineProperty(document.body, 'clientWidth', { value: 100, configurable: true });
|
||||||
|
|
||||||
|
const menuEl = wrapper.find('.magic-editor-content-menu').element as HTMLElement;
|
||||||
|
// 模拟菜单内容增多后尺寸变为 50x50
|
||||||
|
Object.defineProperty(menuEl, 'clientHeight', { value: 50, configurable: true });
|
||||||
|
Object.defineProperty(menuEl, 'clientWidth', { value: 50, configurable: true });
|
||||||
|
|
||||||
|
roCallbacks.forEach((cb) => cb());
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect((wrapper.vm as any).menuPosition.top).toBe(50);
|
||||||
|
expect((wrapper.vm as any).menuPosition.left).toBe(50);
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('菜单未显示时尺寸变化不修正位置', async () => {
|
||||||
|
const wrapper = mount(ContentMenu as any, {
|
||||||
|
...provideServices(),
|
||||||
|
props: { menuData: [{ id: '1', type: 'button', text: 'a' }] as any },
|
||||||
|
});
|
||||||
|
Object.defineProperty(document.body, 'clientHeight', { value: 100, configurable: true });
|
||||||
|
|
||||||
|
roCallbacks.forEach((cb) => cb());
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect((wrapper.vm as any).menuPosition.top).toBe(0);
|
||||||
|
expect((wrapper.vm as any).menuPosition.left).toBe(0);
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('卸载时断开 ResizeObserver', () => {
|
||||||
|
const wrapper = mount(ContentMenu as any, {
|
||||||
|
...provideServices(),
|
||||||
|
props: { menuData: [] as any },
|
||||||
|
});
|
||||||
|
wrapper.unmount();
|
||||||
|
expect(FakeResizeObserver.disconnectSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
test('contains 判断 DOM 是否在菜单内部', async () => {
|
test('contains 判断 DOM 是否在菜单内部', async () => {
|
||||||
const wrapper = mount(ContentMenu as any, {
|
const wrapper = mount(ContentMenu as any, {
|
||||||
...provideServices(),
|
...provideServices(),
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|||||||
import { defineComponent, h } from 'vue';
|
import { defineComponent, h } from 'vue';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
|
|
||||||
|
import { FORM_SILENT_MODE_KEY } from '@tmagic/form';
|
||||||
|
|
||||||
import Code from '@editor/fields/Code.vue';
|
import Code from '@editor/fields/Code.vue';
|
||||||
|
|
||||||
// 用一个简单的桩组件代替 MagicCodeEditor,把所有 props 原样渲染到 data-* 属性上,
|
// 用一个简单的桩组件代替 MagicCodeEditor,把所有 props 原样渲染到 data-* 属性上,
|
||||||
@ -45,7 +47,7 @@ vi.mock('@editor/layouts/CodeEditor.vue', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const mountCode = (props: Record<string, any>) =>
|
const mountCode = (props: Record<string, any>, global?: Record<string, any>) =>
|
||||||
mount(Code, {
|
mount(Code, {
|
||||||
props: {
|
props: {
|
||||||
// FieldProps 必填字段,用 as any 绕过测试中类型严格匹配
|
// FieldProps 必填字段,用 as any 绕过测试中类型严格匹配
|
||||||
@ -55,6 +57,7 @@ const mountCode = (props: Record<string, any>) =>
|
|||||||
prop: 'codeField',
|
prop: 'codeField',
|
||||||
...props,
|
...props,
|
||||||
} as any,
|
} as any,
|
||||||
|
global,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getEl = (wrapper: ReturnType<typeof mountCode>) => wrapper.find('.fake-code-editor').element as HTMLElement;
|
const getEl = (wrapper: ReturnType<typeof mountCode>) => wrapper.find('.fake-code-editor').element as HTMLElement;
|
||||||
@ -168,6 +171,28 @@ describe('Code', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('静默模式', () => {
|
||||||
|
test('注入 FORM_SILENT_MODE_KEY=true 时不渲染 CodeEditor', () => {
|
||||||
|
const wrapper = mountCode(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
provide: { [FORM_SILENT_MODE_KEY as symbol]: true },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(wrapper.find('.fake-code-editor').exists()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('注入 FORM_SILENT_MODE_KEY=false 时正常渲染 CodeEditor', () => {
|
||||||
|
const wrapper = mountCode(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
provide: { [FORM_SILENT_MODE_KEY as symbol]: false },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(wrapper.find('.fake-code-editor').exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('diff 模式 (对比)', () => {
|
describe('diff 模式 (对比)', () => {
|
||||||
test('isCompare=true 且有 lastValues 时切换为 diff 模式', () => {
|
test('isCompare=true 且有 lastValues 时切换为 diff 模式', () => {
|
||||||
const wrapper = mountCode({
|
const wrapper = mountCode({
|
||||||
|
|||||||
@ -91,7 +91,7 @@ const mkServices = () => {
|
|||||||
clear: vi.fn(),
|
clear: vi.fn(),
|
||||||
clearTargets: vi.fn(),
|
clearTargets: vi.fn(),
|
||||||
clearIdleTasks: vi.fn(),
|
clearIdleTasks: vi.fn(),
|
||||||
collectIdle: vi.fn(async () => undefined),
|
collectIdle: vi.fn(async () => true),
|
||||||
collectByWorker: vi.fn(async () => undefined),
|
collectByWorker: vi.fn(async () => undefined),
|
||||||
reset: vi.fn(),
|
reset: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -95,7 +95,7 @@ vi.mock('@editor/layouts/props-panel/FormPanel.vue', () => ({
|
|||||||
// 模拟 CodeEditor 源码保存:仅传 values,无 eventData、无 error(对应 saveCode 路径)
|
// 模拟 CodeEditor 源码保存:仅传 values,无 eventData、无 error(对应 saveCode 路径)
|
||||||
h('button', {
|
h('button', {
|
||||||
class: 'code-save-btn',
|
class: 'code-save-btn',
|
||||||
onClick: () => emit('submit', { id: 'n1', style: { color: 'red' } }),
|
onClick: () => emit('submit', { id: 'n1', style: { color: 'red', width: '' } }),
|
||||||
}),
|
}),
|
||||||
h('button', { class: 'submit-err-btn', onClick: () => emit('submit-error', new Error('e')) }),
|
h('button', { class: 'submit-err-btn', onClick: () => emit('submit-error', new Error('e')) }),
|
||||||
h('button', { class: 'form-err-btn', onClick: () => emit('form-error', new Error('e')) }),
|
h('button', { class: 'form-err-btn', onClick: () => emit('form-error', new Error('e')) }),
|
||||||
@ -239,6 +239,16 @@ describe('PropsPanel', () => {
|
|||||||
expect(options.historySource).toBe('code');
|
expect(options.historySource).toBe('code');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('CodeEditor 源码保存时 style 中的空字符串值被保留(表示清除该样式)', async () => {
|
||||||
|
const wrapper = mount(PropsPanel, { props: {} as any });
|
||||||
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
|
await wrapper.find('.code-save-btn').trigger('click');
|
||||||
|
|
||||||
|
const calledNode = (editorService.update.mock.calls[0] as any)[0];
|
||||||
|
expect(calledNode.style.color).toBe('red');
|
||||||
|
expect(calledNode.style.width).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
test('mounted 事件 emit', async () => {
|
test('mounted 事件 emit', async () => {
|
||||||
const wrapper = mount(PropsPanel, { props: {} as any });
|
const wrapper = mount(PropsPanel, { props: {} as any });
|
||||||
await wrapper.find('.mounted-btn').trigger('click');
|
await wrapper.find('.mounted-btn').trigger('click');
|
||||||
|
|||||||
@ -13,16 +13,17 @@ vi.mock('@editor/utils/dep/worker.ts?worker&inline', () => ({
|
|||||||
default: class FakeWorker {
|
default: class FakeWorker {
|
||||||
public static nextData: Record<string, any> = {};
|
public static nextData: Record<string, any> = {};
|
||||||
public static nextError = false;
|
public static nextError = false;
|
||||||
|
public static nextDelay = 0;
|
||||||
public onmessage: ((e: any) => void) | null = null;
|
public onmessage: ((e: any) => void) | null = null;
|
||||||
public onerror: (() => void) | null = null;
|
public onerror: (() => void) | null = null;
|
||||||
public postMessage() {
|
public postMessage() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (FakeWorker.nextError) {
|
if (FakeWorker.nextError) {
|
||||||
this.onerror?.(new Event('error'));
|
this.onerror?.();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.onmessage?.({ data: FakeWorker.nextData });
|
this.onmessage?.({ data: FakeWorker.nextData });
|
||||||
}, 0);
|
}, FakeWorker.nextDelay);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@ -121,7 +122,7 @@ describe('Dep service', () => {
|
|||||||
test('collectIdle - 没有命中时立即 resolve 并 emit collected', async () => {
|
test('collectIdle - 没有命中时立即 resolve 并 emit collected', async () => {
|
||||||
const fn = vi.fn();
|
const fn = vi.fn();
|
||||||
depService.on('collected', fn);
|
depService.on('collected', fn);
|
||||||
await depService.collectIdle([{ id: 'n1', type: 'text' }] as any);
|
await expect(depService.collectIdle([{ id: 'n1', type: 'text' }] as any)).resolves.toBe(true);
|
||||||
expect(fn).toHaveBeenCalled();
|
expect(fn).toHaveBeenCalled();
|
||||||
depService.off('collected', fn);
|
depService.off('collected', fn);
|
||||||
});
|
});
|
||||||
@ -219,6 +220,83 @@ describe('Dep service', () => {
|
|||||||
fakeWorker.nextData = {};
|
fakeWorker.nextData = {};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('collectIdle 命中 target 时最终 resolve 并按批次 emit collected/ds-collected', async () => {
|
||||||
|
depService.addTarget(makeTarget('ds1', DepTargetType.DATA_SOURCE));
|
||||||
|
const collected = vi.fn();
|
||||||
|
const dsCollected = vi.fn();
|
||||||
|
depService.on('collected', collected);
|
||||||
|
depService.on('ds-collected', dsCollected);
|
||||||
|
|
||||||
|
const nodes = [{ id: 'n1', type: 'text' }] as any;
|
||||||
|
await expect(depService.collectIdle(nodes, {}, false, DepTargetType.DATA_SOURCE)).resolves.toBe(true);
|
||||||
|
|
||||||
|
expect(dsCollected).toHaveBeenCalledWith(nodes, false);
|
||||||
|
expect(collected).toHaveBeenCalledWith(nodes, false);
|
||||||
|
expect(depService.get('collecting')).toBe(false);
|
||||||
|
|
||||||
|
depService.off('collected', collected);
|
||||||
|
depService.off('ds-collected', dsCollected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('clearIdleTasks 会结算在途 collectIdle,避免 Promise 永久挂起且 collecting 复位', async () => {
|
||||||
|
depService.addTarget(makeTarget('ds1', DepTargetType.DATA_SOURCE));
|
||||||
|
|
||||||
|
const promise = depService.collectIdle([{ id: 'n1', type: 'text' }] as any, {}, false, DepTargetType.DATA_SOURCE);
|
||||||
|
expect(depService.get('collecting')).toBe(true);
|
||||||
|
|
||||||
|
// 快速触发:任务尚未执行就清空队列,批次应被主动结算而不是永久挂起
|
||||||
|
depService.clearIdleTasks();
|
||||||
|
|
||||||
|
await expect(promise).resolves.toBe(false);
|
||||||
|
expect(depService.get('collecting')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reset 会结算在途 collectIdle', async () => {
|
||||||
|
depService.addTarget(makeTarget('ds1', DepTargetType.DATA_SOURCE));
|
||||||
|
|
||||||
|
const promise = depService.collectIdle([{ id: 'n1', type: 'text' }] as any, {}, false, DepTargetType.DATA_SOURCE);
|
||||||
|
depService.reset();
|
||||||
|
|
||||||
|
await expect(promise).resolves.toBe(false);
|
||||||
|
expect(depService.get('collecting')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reset 会忽略在途 worker 的过期结果,避免覆盖新依赖', async () => {
|
||||||
|
const fakeWorker = (await import('@editor/utils/dep/worker.ts?worker&inline')).default as any;
|
||||||
|
fakeWorker.nextDelay = 20;
|
||||||
|
fakeWorker.nextData = {
|
||||||
|
[DepTargetType.DATA_SOURCE]: { ds1: { n1: { data: {} } } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const workerPromise = depService.collectByWorker({ items: [], id: 'app', type: 'app' } as any);
|
||||||
|
depService.reset();
|
||||||
|
|
||||||
|
const target = makeTarget('ds1', DepTargetType.DATA_SOURCE);
|
||||||
|
depService.addTarget(target);
|
||||||
|
const idlePromise = depService.collectIdle(
|
||||||
|
[{ id: 'n1', type: 'text' }] as any,
|
||||||
|
{},
|
||||||
|
false,
|
||||||
|
DepTargetType.DATA_SOURCE,
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all([workerPromise, idlePromise]);
|
||||||
|
expect(target.deps.n1).toBeUndefined();
|
||||||
|
|
||||||
|
fakeWorker.nextDelay = 0;
|
||||||
|
fakeWorker.nextData = {};
|
||||||
|
});
|
||||||
|
|
||||||
|
test('多个批次并发时各自独立 resolve,全部完成后 collecting 复位', async () => {
|
||||||
|
depService.addTarget(makeTarget('ds1', DepTargetType.DATA_SOURCE));
|
||||||
|
|
||||||
|
const p1 = depService.collectIdle([{ id: 'n1', type: 'text' }] as any, {}, false, DepTargetType.DATA_SOURCE);
|
||||||
|
const p2 = depService.collectIdle([{ id: 'n2', type: 'text' }] as any, {}, false, DepTargetType.DATA_SOURCE);
|
||||||
|
|
||||||
|
await Promise.all([p1, p2]);
|
||||||
|
expect(depService.get('collecting')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
test('destroy 会 reset 并移除监听', () => {
|
test('destroy 会 reset 并移除监听', () => {
|
||||||
depService.addTarget(makeTarget('destroy-me'));
|
depService.addTarget(makeTarget('destroy-me'));
|
||||||
expect(() => depService.destroy()).not.toThrow();
|
expect(() => depService.destroy()).not.toThrow();
|
||||||
|
|||||||
@ -105,8 +105,8 @@ describe('data-source utils', () => {
|
|||||||
fields: [],
|
fields: [],
|
||||||
});
|
});
|
||||||
expect(resolveFieldByPath(fields, ['obj']).field?.name).toBe('obj');
|
expect(resolveFieldByPath(fields, ['obj']).field?.name).toBe('obj');
|
||||||
expect(resolveFieldByPath(fields, ['unknown'])).toEqual({ ok: false, fields });
|
expect(resolveFieldByPath(fields, ['unknown'])).toEqual({ ok: false, fields, failedName: 'unknown' });
|
||||||
expect(resolveFieldByPath(undefined, ['x'])).toEqual({ ok: false, fields: [] });
|
expect(resolveFieldByPath(undefined, ['x'])).toEqual({ ok: false, fields: [], failedName: 'x' });
|
||||||
expect(resolveFieldByPath(fields, []).ok).toBe(true);
|
expect(resolveFieldByPath(fields, []).ok).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -124,7 +124,9 @@ describe('data-source utils', () => {
|
|||||||
expect(result.field?.name).toBe('item');
|
expect(result.field?.name).toBe('item');
|
||||||
expect(result.fields).toEqual([{ name: 'n', type: 'string' }]);
|
expect(result.fields).toEqual([{ name: 'n', type: 'string' }]);
|
||||||
|
|
||||||
expect(resolveFieldByPath(fields, ['arr', '0', 'missing'], { skipNumberIndices: true }).ok).toBe(false);
|
const failed = resolveFieldByPath(fields, ['arr', '0', 'missing'], { skipNumberIndices: true });
|
||||||
|
expect(failed.ok).toBe(false);
|
||||||
|
expect(failed.failedName).toBe('missing');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getFieldType 沿 path 取最终类型', () => {
|
test('getFieldType 沿 path 取最终类型', () => {
|
||||||
|
|||||||
@ -3,10 +3,9 @@
|
|||||||
*
|
*
|
||||||
* Copyright (C) 2025 Tencent.
|
* Copyright (C) 2025 Tencent.
|
||||||
*/
|
*/
|
||||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
import { describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
import { NODE_CONDS_RESULT_KEY } from '@tmagic/core';
|
import { NODE_CONDS_RESULT_KEY } from '@tmagic/core';
|
||||||
import { validateForm } from '@tmagic/form';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
advancedTabConfig,
|
advancedTabConfig,
|
||||||
@ -20,7 +19,6 @@ import {
|
|||||||
numberOptions,
|
numberOptions,
|
||||||
removeStyleDisplayConfig,
|
removeStyleDisplayConfig,
|
||||||
styleTabConfig,
|
styleTabConfig,
|
||||||
validatePropsForm,
|
|
||||||
} from '@editor/utils/props';
|
} from '@editor/utils/props';
|
||||||
|
|
||||||
vi.mock('@tmagic/design', () => ({
|
vi.mock('@tmagic/design', () => ({
|
||||||
@ -172,59 +170,6 @@ describe('fillConfig', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('validatePropsForm', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.mocked(validateForm).mockReset();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('合入 appContext.provides、注入 stage/services 并合并外部 extendState,debug 默认 undefined', async () => {
|
|
||||||
vi.mocked(validateForm).mockResolvedValue('err-text');
|
|
||||||
|
|
||||||
const services = { uiService: {} } as any;
|
|
||||||
const stage = { id: 'stage' };
|
|
||||||
const appContext = { app: {}, provides: { foo: 1 } } as any;
|
|
||||||
|
|
||||||
const result = await validatePropsForm({
|
|
||||||
config: [],
|
|
||||||
values: { a: 1 },
|
|
||||||
appContext,
|
|
||||||
services,
|
|
||||||
stage,
|
|
||||||
extendState: async () => ({ extra: true }),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 直接返回 validateForm 的结果
|
|
||||||
expect(result).toBe('err-text');
|
|
||||||
|
|
||||||
const arg = vi.mocked(validateForm).mock.calls[0][0];
|
|
||||||
expect(arg.config).toEqual([]);
|
|
||||||
expect(arg.debug).toBeUndefined();
|
|
||||||
expect(arg.initValues).toEqual({ a: 1 });
|
|
||||||
// appContext 保留原字段,但 provides 被替换为 { services }
|
|
||||||
expect(arg.appContext).toEqual({ app: {}, provides: { services } });
|
|
||||||
|
|
||||||
// extendState 合并外部返回并注入 stage/services
|
|
||||||
const state = await arg.extendState!({} as any);
|
|
||||||
expect(state).toEqual({ extra: true, stage, services });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('appContext 缺省为 null,extendState 缺省时仍注入 stage/services,可覆盖 debug', async () => {
|
|
||||||
vi.mocked(validateForm).mockResolvedValue('');
|
|
||||||
|
|
||||||
const services = {} as any;
|
|
||||||
const stage = {};
|
|
||||||
|
|
||||||
await validatePropsForm({ config: [], values: {}, services, stage, debug: false });
|
|
||||||
|
|
||||||
const arg = vi.mocked(validateForm).mock.calls[0][0];
|
|
||||||
expect(arg.appContext).toBeNull();
|
|
||||||
expect(arg.debug).toBe(false);
|
|
||||||
|
|
||||||
const state = await arg.extendState!({} as any);
|
|
||||||
expect(state).toEqual({ stage, services });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('removeStyleDisplayConfig', () => {
|
describe('removeStyleDisplayConfig', () => {
|
||||||
test('将 tab 内「样式」pane 的 display 置为 true', () => {
|
test('将 tab 内「样式」pane 的 display 置为 true', () => {
|
||||||
const config = [
|
const config = [
|
||||||
|
|||||||
@ -207,9 +207,18 @@ describe('editorTypeMatchRules', () => {
|
|||||||
expect(firstLine(run('data-source-method-select', ['missing', 'custom']))).toBe('数据源(missing)不存在');
|
expect(firstLine(run('data-source-method-select', ['missing', 'custom']))).toBe('数据源(missing)不存在');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('data-source-field-select 校验路径与 fieldConfig 跳过', () => {
|
test('data-source-field-select 校验路径与 fieldConfig 类型校验', () => {
|
||||||
expect(firstLine(run('data-source-field-select', 'x'))).toBe('x类型应为字符串数组');
|
expect(firstLine(run('data-source-field-select', 'x'))).toBe('x类型应为字符串数组');
|
||||||
expect(run('data-source-field-select', 'text-value', { fieldConfig: { type: 'text' } })).toBeUndefined();
|
|
||||||
|
// 有 fieldConfig 且值不是数据源字段路径时,按 fieldConfig 的类型校验(text 允许数字)
|
||||||
|
expect(run('data-source-field-select', 'text-value', { name: 'f', fieldConfig: { type: 'text' } })).toBeUndefined();
|
||||||
|
expect(run('data-source-field-select', 123, { name: 'f', fieldConfig: { type: 'text' } })).toBeUndefined();
|
||||||
|
expect(firstLine(run('data-source-field-select', { a: 1 }, { name: 'f', fieldConfig: { type: 'text' } }))).toBe(
|
||||||
|
'[object Object] 类型应为字符串',
|
||||||
|
);
|
||||||
|
expect(run('data-source-field-select', 123, { name: 'f', fieldConfig: { type: 'number' } })).toBeUndefined();
|
||||||
|
// fieldConfig 未声明 type 时不做类型校验
|
||||||
|
expect(run('data-source-field-select', 123, { name: 'f', fieldConfig: {} })).toBeUndefined();
|
||||||
|
|
||||||
dataSourcesState.value = [
|
dataSourcesState.value = [
|
||||||
{
|
{
|
||||||
@ -225,7 +234,26 @@ describe('editorTypeMatchRules', () => {
|
|||||||
|
|
||||||
expect(run('data-source-field-select', ['ds1', 'title'], { value: 'key' })).toBeUndefined();
|
expect(run('data-source-field-select', ['ds1', 'title'], { value: 'key' })).toBeUndefined();
|
||||||
expect(run('data-source-field-select', [`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}ds1`, 'title'])).toBeUndefined();
|
expect(run('data-source-field-select', [`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}ds1`, 'title'])).toBeUndefined();
|
||||||
expect(firstLine(run('data-source-field-select', ['ds1', 'missing'], { value: 'key' }))).toBe('值不在可选项中');
|
// 数据源不存在时直出具体的数据源 id
|
||||||
|
expect(firstLine(run('data-source-field-select', ['missing', 'title'], { value: 'key' }))).toBe(
|
||||||
|
'数据源(missing)不存在',
|
||||||
|
);
|
||||||
|
expect(firstLine(run('data-source-field-select', ['title'], { dataSourceId: 'missing' }))).toBe(
|
||||||
|
'数据源(missing)不存在',
|
||||||
|
);
|
||||||
|
// 字段路径解析失败时直出具体失败的字段名
|
||||||
|
expect(firstLine(run('data-source-field-select', ['ds1', 'missing'], { value: 'key' }))).toBe(
|
||||||
|
'数据源字段(missing)不存在',
|
||||||
|
);
|
||||||
|
// 字段路径解析失败时,建议列出失败层级的可用字段名而非数据源 id
|
||||||
|
expect(run('data-source-field-select', ['ds1', 'missing'], { value: 'key' })).toBe(
|
||||||
|
'数据源字段(missing)不存在\n\n请使用以下某一个值:"title";"obj"',
|
||||||
|
);
|
||||||
|
expect(run('data-source-field-select', ['ds1', 'obj', 'missing'], { value: 'key' })).toBe(
|
||||||
|
'数据源字段(missing)不存在\n\n请使用以下某一个值:"a"',
|
||||||
|
);
|
||||||
|
// 父字段无子字段时无建议
|
||||||
|
expect(run('data-source-field-select', ['ds1', 'title', 'x'], { value: 'key' })).toBe('数据源字段(x)不存在');
|
||||||
expect(
|
expect(
|
||||||
firstLine(
|
firstLine(
|
||||||
run('data-source-field-select', ['title'], {
|
run('data-source-field-select', ['title'], {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"name": "@tmagic/element-plus-adapter",
|
"name": "@tmagic/element-plus-adapter",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"name": "@tmagic/form-schema",
|
"name": "@tmagic/form-schema",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"name": "@tmagic/form",
|
"name": "@tmagic/form",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": [
|
"sideEffects": [
|
||||||
|
|||||||
@ -261,6 +261,18 @@ const formState: FormState = reactive<FormState>({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* formState 的内置 key 快照(keyProp / values / $emit / fields / post 等)。
|
||||||
|
*
|
||||||
|
* 在 `extendState` 首次合并前捕获,`applyExtendState` 会据此禁止 `extendState`
|
||||||
|
* 覆盖这些已有字段(只能新增字段),避免表单核心状态被外部意外改写。
|
||||||
|
*
|
||||||
|
* 之所以在此处(effect 之外)捕获而不是在 `applyExtendState` 内动态取:
|
||||||
|
* `watchEffect` 会在依赖变化时重跑,若动态取,`extendState` 自己新增的字段在第二次
|
||||||
|
* 合并时也会被当成「已有 key」而拒绝刷新;这里只锁定内置字段即可规避该问题。
|
||||||
|
*/
|
||||||
|
const reservedStateKeys = new Set<string | symbol>(Reflect.ownKeys(formState));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `extendState` 的同步段(直到第一个 `await` 之前)所访问的任何响应式数据,
|
* `extendState` 的同步段(直到第一个 `await` 之前)所访问的任何响应式数据,
|
||||||
* 都会被 `watchEffect` 自动跟踪。这样可以兼容历史用法 ——
|
* 都会被 `watchEffect` 自动跟踪。这样可以兼容历史用法 ——
|
||||||
@ -299,7 +311,7 @@ watchEffect(async (onCleanup) => {
|
|||||||
}
|
}
|
||||||
if (stale) return;
|
if (stale) return;
|
||||||
|
|
||||||
applyExtendState(formState, state);
|
applyExtendState(formState, state, reservedStateKeys);
|
||||||
});
|
});
|
||||||
|
|
||||||
provide('mForm', formState);
|
provide('mForm', formState);
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@change="changeHandler"
|
@change="changeHandler"
|
||||||
@add-diff-count="onAddDiffCount"
|
@add-diff-count="onAddDiffCount"
|
||||||
|
:is-in-row="true"
|
||||||
></Container>
|
></Container>
|
||||||
</TMagicCol>
|
</TMagicCol>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -240,7 +240,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, inject, readonly, ref, toRaw, watch, watchEffect } from 'vue';
|
import { computed, inject, provide, readonly, ref, toRaw, watch, watchEffect } from 'vue';
|
||||||
import { ArrowDown, ArrowUp, WarningFilled } from '@element-plus/icons-vue';
|
import { ArrowDown, ArrowUp, WarningFilled } from '@element-plus/icons-vue';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
@ -287,6 +287,8 @@ const props = withDefaults(
|
|||||||
size?: string;
|
size?: string;
|
||||||
/** 是否开启对比模式 */
|
/** 是否开启对比模式 */
|
||||||
isCompare?: boolean;
|
isCompare?: boolean;
|
||||||
|
/** 是否在行容器中 */
|
||||||
|
isInRow?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
prop: '',
|
prop: '',
|
||||||
@ -476,6 +478,8 @@ watch(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
provide('isInRow', props.isInRow ?? false);
|
||||||
|
|
||||||
const expandHandler = () => (expand.value = !expand.value);
|
const expandHandler = () => (expand.value = !expand.value);
|
||||||
|
|
||||||
const key = (config: any) => config[mForm?.keyProps];
|
const key = (config: any) => config[mForm?.keyProps];
|
||||||
|
|||||||
@ -29,6 +29,17 @@ export interface FormDiffConfig {
|
|||||||
export const FORM_DIFF_CONFIG_KEY: InjectionKey<FormDiffConfig> = Symbol('mFormDiffConfig');
|
export const FORM_DIFF_CONFIG_KEY: InjectionKey<FormDiffConfig> = Symbol('mFormDiffConfig');
|
||||||
export const FORM_TYPE_MATCH_VALID_KEY: InjectionKey<ComputedRef<boolean>> = Symbol('mFormTypeMatchValid');
|
export const FORM_TYPE_MATCH_VALID_KEY: InjectionKey<ComputedRef<boolean>> = Symbol('mFormTypeMatchValid');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 静默模式标记,由 `submitForm` / `validateForm` 以隐藏方式挂载临时表单时 provide(debug 弹层模式不提供)。
|
||||||
|
*
|
||||||
|
* 重型字段组件(如 vs-code)可 inject 该标记并跳过自身渲染:字段校验由 FormItem 基于 model 值完成,
|
||||||
|
* 与叶子 UI 组件实例无关,跳过可省去无谓的实例化开销(如 monaco worker / model 的创建)。
|
||||||
|
*
|
||||||
|
* 注意:仅「挂载无值副作用」(不在 onMounted/watch immediate 中 emit change、不依赖 DOM 计算值)的
|
||||||
|
* 字段组件可以安全跳过,接入前需逐一审计。
|
||||||
|
*/
|
||||||
|
export const FORM_SILENT_MODE_KEY: InjectionKey<boolean> = Symbol('mFormSilentMode');
|
||||||
|
|
||||||
export interface ValidateError {
|
export interface ValidateError {
|
||||||
message: string;
|
message: string;
|
||||||
field: string;
|
field: string;
|
||||||
|
|||||||
@ -16,11 +16,22 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type AppContext, type Component, createApp, defineComponent, h, nextTick, type Ref, ref, watch } from 'vue';
|
import {
|
||||||
|
type AppContext,
|
||||||
|
type Component,
|
||||||
|
createApp,
|
||||||
|
defineComponent,
|
||||||
|
h,
|
||||||
|
nextTick,
|
||||||
|
provide,
|
||||||
|
type Ref,
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
import { applyExtendState } from './utils/form';
|
import { applyExtendState } from './utils/form';
|
||||||
import Form from './Form.vue';
|
import Form from './Form.vue';
|
||||||
import type { ChangeRecord, FormConfig, FormState } from './schema';
|
import { type ChangeRecord, FORM_SILENT_MODE_KEY, type FormConfig, type FormState } from './schema';
|
||||||
|
|
||||||
// #region SubmitFormOptions
|
// #region SubmitFormOptions
|
||||||
/**
|
/**
|
||||||
@ -76,6 +87,11 @@ export interface SubmitFormOptions {
|
|||||||
*/
|
*/
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
typeMatchValid?: boolean;
|
typeMatchValid?: boolean;
|
||||||
|
/**
|
||||||
|
* 外部中断信号。abort 时会立即以 `signal.reason` reject 并卸载临时表单实例、移除容器。
|
||||||
|
* 主要用于 `debug` 模式(无超时兜底)下取消一个被放弃的表单弹层,避免其无限驻留在页面上。
|
||||||
|
*/
|
||||||
|
signal?: AbortSignal;
|
||||||
}
|
}
|
||||||
// #endregion SubmitFormOptions
|
// #endregion SubmitFormOptions
|
||||||
|
|
||||||
@ -116,7 +132,7 @@ interface MountFormInstanceOptions<T> {
|
|||||||
formProps: Record<string, any>;
|
formProps: Record<string, any>;
|
||||||
/** 父级应用上下文,用于继承全局组件、指令、provide 等 */
|
/** 父级应用上下文,用于继承全局组件、指令、provide 等 */
|
||||||
appContext?: AppContext | null;
|
appContext?: AppContext | null;
|
||||||
/** 等待表单初始化的最长时间(毫秒),<=0 表示不注册超时 */
|
/** 等待表单初始化的最长时间(毫秒),<=0 时回退到默认超时以保证兜底清理生效 */
|
||||||
timeout: number;
|
timeout: number;
|
||||||
/** 超时 reject 的错误文案 */
|
/** 超时 reject 的错误文案 */
|
||||||
timeoutMessage: string;
|
timeoutMessage: string;
|
||||||
@ -124,10 +140,15 @@ interface MountFormInstanceOptions<T> {
|
|||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
/** 是否跳过超时注册。调试模式等待人工操作,应传 `true` */
|
/** 是否跳过超时注册。调试模式等待人工操作,应传 `true` */
|
||||||
skipTimeout?: boolean;
|
skipTimeout?: boolean;
|
||||||
|
/** 外部中断信号:abort 时会 reject 并卸载实例、移除容器,用于取消无超时(如 debug)的挂载 */
|
||||||
|
signal?: AbortSignal;
|
||||||
/** 构造 wrapper 组件 */
|
/** 构造 wrapper 组件 */
|
||||||
createWrapper: FormWrapperFactory<T>;
|
createWrapper: FormWrapperFactory<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 未指定或传入非正数 timeout 时的兜底超时(毫秒),保证非 debug 挂载始终能被清理 */
|
||||||
|
const DEFAULT_MOUNT_TIMEOUT = 10000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* submitForm / validateForm 的公共脚手架:
|
* submitForm / validateForm 的公共脚手架:
|
||||||
*
|
*
|
||||||
@ -138,20 +159,36 @@ interface MountFormInstanceOptions<T> {
|
|||||||
* 容器创建、卸载、超时、上下文注入等模板代码在此统一收口。
|
* 容器创建、卸载、超时、上下文注入等模板代码在此统一收口。
|
||||||
*/
|
*/
|
||||||
const mountFormInstance = <T>(options: MountFormInstanceOptions<T>): Promise<T> => {
|
const mountFormInstance = <T>(options: MountFormInstanceOptions<T>): Promise<T> => {
|
||||||
const { formProps, appContext, timeout, timeoutMessage, hidden = true, skipTimeout = false, createWrapper } = options;
|
const {
|
||||||
|
formProps,
|
||||||
|
appContext,
|
||||||
|
timeout,
|
||||||
|
timeoutMessage,
|
||||||
|
hidden = true,
|
||||||
|
skipTimeout = false,
|
||||||
|
signal,
|
||||||
|
createWrapper,
|
||||||
|
} = options;
|
||||||
|
|
||||||
return new Promise<T>((resolve, reject) => {
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
// 已中断则直接 reject,不创建任何容器/实例
|
||||||
|
if (signal?.aborted) {
|
||||||
|
reject(signal.reason ?? new Error('mountFormInstance aborted'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cleaned = false;
|
||||||
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
let onAbort: (() => void) | null = null;
|
||||||
|
// 用 holder 持有 app,使 cleanup 可在 app 创建之前定义(const app + 无 TDZ / 无 use-before-define)
|
||||||
|
const instance: { app: ReturnType<typeof createApp> | null } = { app: null };
|
||||||
|
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
container.style.display = 'none';
|
container.style.display = 'none';
|
||||||
}
|
}
|
||||||
document.body.appendChild(container);
|
document.body.appendChild(container);
|
||||||
|
|
||||||
let cleaned = false;
|
|
||||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
||||||
// 用 holder 持有 app,使 cleanup 可在 app 创建之前定义(const app + 无 TDZ / 无 use-before-define)
|
|
||||||
const instance: { app: ReturnType<typeof createApp> | null } = { app: null };
|
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
if (cleaned) return;
|
if (cleaned) return;
|
||||||
cleaned = true;
|
cleaned = true;
|
||||||
@ -159,6 +196,10 @@ const mountFormInstance = <T>(options: MountFormInstanceOptions<T>): Promise<T>
|
|||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
timer = null;
|
timer = null;
|
||||||
}
|
}
|
||||||
|
if (signal && onAbort) {
|
||||||
|
signal.removeEventListener('abort', onAbort);
|
||||||
|
onAbort = null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
instance.app?.unmount();
|
instance.app?.unmount();
|
||||||
} catch {
|
} catch {
|
||||||
@ -167,67 +208,101 @@ const mountFormInstance = <T>(options: MountFormInstanceOptions<T>): Promise<T>
|
|||||||
container.parentNode?.removeChild(container);
|
container.parentNode?.removeChild(container);
|
||||||
};
|
};
|
||||||
|
|
||||||
const formRef = ref<any>(null);
|
// 支持外部通过 AbortSignal 主动中断:debug 模式无超时兜底,若调用方放弃了该 Promise,
|
||||||
|
// 可通过 abort 卸载实例、移除遮罩/容器,避免无限驻留在 DOM 中。
|
||||||
|
if (signal) {
|
||||||
|
onAbort = () => {
|
||||||
|
if (cleaned) return;
|
||||||
|
reject(signal.reason ?? new Error('mountFormInstance aborted'));
|
||||||
|
cleanup();
|
||||||
|
};
|
||||||
|
signal.addEventListener('abort', onAbort);
|
||||||
|
}
|
||||||
|
|
||||||
// 将 extendState 从 formProps 中剥离:不由 Form.vue 的 async watchEffect 异步应用,
|
// 从容器创建到 mount 的全流程统一 try/catch:任一步骤(createWrapper/createApp/上下文合并/mount)
|
||||||
// 而是在 wrapper 中通过 sync watch 在 formRef 就绪后直接写入 formState,
|
// 抛错都会走到 cleanup,避免已插入 body 的 container 及未挂载的 app 残留导致泄漏。
|
||||||
// 避免 display 等 filterFunction 在首次渲染时读到 undefined。
|
try {
|
||||||
// 与 CompareForm / FormPanel 中「formRef.value.formState.services = ...」的做法一致。
|
const formRef = ref<any>(null);
|
||||||
const { extendState, ...restFormProps } = formProps;
|
|
||||||
|
|
||||||
const userWrapper = createWrapper({ formRef, formProps: restFormProps, cleanup, resolve, reject });
|
// 将 extendState 从 formProps 中剥离:不由 Form.vue 的 async watchEffect 异步应用,
|
||||||
|
// 而是在 wrapper 中通过 sync watch 在 formRef 就绪后直接写入 formState,
|
||||||
|
// 避免 display 等 filterFunction 在首次渲染时读到 undefined。
|
||||||
|
// 与 CompareForm / FormPanel 中「formRef.value.formState.services = ...」的做法一致。
|
||||||
|
const { extendState, ...restFormProps } = formProps;
|
||||||
|
|
||||||
const wrapperComponent =
|
const userWrapper = createWrapper({ formRef, formProps: restFormProps, cleanup, resolve, reject });
|
||||||
typeof extendState === 'function'
|
|
||||||
|
const wrapperComponent =
|
||||||
|
typeof extendState === 'function'
|
||||||
|
? defineComponent({
|
||||||
|
name: 'MFormExtendStateInjector',
|
||||||
|
setup() {
|
||||||
|
watch(
|
||||||
|
() => formRef.value,
|
||||||
|
(form) => {
|
||||||
|
if (!form) return;
|
||||||
|
let result: any;
|
||||||
|
try {
|
||||||
|
result = extendState(form.formState);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[MForm] extendState failed:', e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// formState 的内置 key 快照:在 extendState 合并前捕获,
|
||||||
|
// 供 applyExtendState 禁止 extendState 覆盖这些已有字段(只能新增),
|
||||||
|
// 与 Form.vue 中 reservedStateKeys 的语义保持一致。
|
||||||
|
const reservedStateKeys = new Set<string | symbol>(Reflect.ownKeys(form.formState));
|
||||||
|
// 合并逻辑收口在 applyExtendState:props 派生的只读 getter 字段
|
||||||
|
// (keyProp 等)以普通字段形式返回时会被跳过并告警,避免 proxy set 抛错
|
||||||
|
const apply = (state: Record<string, any> | null | undefined) =>
|
||||||
|
applyExtendState(form.formState, state, reservedStateKeys);
|
||||||
|
if (result && typeof result.then === 'function') {
|
||||||
|
result.then(apply, (e: any) => console.error('[MForm] extendState failed:', e));
|
||||||
|
} else {
|
||||||
|
apply(result);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ flush: 'sync', immediate: true },
|
||||||
|
);
|
||||||
|
return () => h(userWrapper);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: userWrapper;
|
||||||
|
|
||||||
|
// 静默(隐藏挂载)模式下注入静默标记:vs-code 等重型字段组件可据此跳过自身渲染,
|
||||||
|
// 校验/取值依赖 FormItem 与 model 值,与叶子 UI 组件无关(见 FORM_SILENT_MODE_KEY 注释)。
|
||||||
|
// 用组件级 provide 而非 app.provide:appContext 合并后 app._context.provides 与父级应用
|
||||||
|
// 共享引用,app.provide 会把标记泄漏到父级应用。
|
||||||
|
const rootComponent = hidden
|
||||||
? defineComponent({
|
? defineComponent({
|
||||||
name: 'MFormExtendStateInjector',
|
name: 'MFormSilentProvider',
|
||||||
setup() {
|
setup() {
|
||||||
watch(
|
provide(FORM_SILENT_MODE_KEY, true);
|
||||||
() => formRef.value,
|
return () => h(wrapperComponent);
|
||||||
(form) => {
|
|
||||||
if (!form) return;
|
|
||||||
let result: any;
|
|
||||||
try {
|
|
||||||
result = extendState(form.formState);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[MForm] extendState failed:', e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 合并逻辑收口在 applyExtendState:props 派生的只读 getter 字段
|
|
||||||
// (keyProp 等)以普通字段形式返回时会被跳过并告警,避免 proxy set 抛错
|
|
||||||
const apply = (state: Record<string, any> | null | undefined) =>
|
|
||||||
applyExtendState(form.formState, state);
|
|
||||||
if (result && typeof result.then === 'function') {
|
|
||||||
result.then(apply, (e: any) => console.error('[MForm] extendState failed:', e));
|
|
||||||
} else {
|
|
||||||
apply(result);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ flush: 'sync', immediate: true },
|
|
||||||
);
|
|
||||||
return () => h(userWrapper);
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
: userWrapper;
|
: wrapperComponent;
|
||||||
|
|
||||||
const app = createApp(wrapperComponent);
|
const app = createApp(rootComponent);
|
||||||
instance.app = app;
|
instance.app = app;
|
||||||
|
|
||||||
// 继承父级应用上下文(components / directives / provides / config 等)
|
// 继承父级应用上下文(components / directives / provides / config 等)
|
||||||
if (appContext) {
|
if (appContext) {
|
||||||
Object.assign(app._context, appContext);
|
Object.assign(app._context, appContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeout > 0 && !skipTimeout) {
|
// 非 debug(未跳过超时)场景始终注册超时兜底:timeout 为非正数时回退到默认值,
|
||||||
timer = setTimeout(() => {
|
// 避免表单永不初始化时实例/容器/watcher 无限驻留而泄漏。
|
||||||
if (!cleaned) {
|
if (!skipTimeout) {
|
||||||
reject(new Error(timeoutMessage));
|
const effectiveTimeout = timeout > 0 ? timeout : DEFAULT_MOUNT_TIMEOUT;
|
||||||
cleanup();
|
timer = setTimeout(() => {
|
||||||
}
|
if (!cleaned) {
|
||||||
}, timeout);
|
reject(new Error(timeoutMessage));
|
||||||
}
|
cleanup();
|
||||||
|
}
|
||||||
|
}, effectiveTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
app.mount(container);
|
app.mount(container);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
@ -425,12 +500,13 @@ const createDebugWrapper = (options: DebugWrapperOptions): Component => {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export const submitForm = (options: SubmitFormOptions): Promise<any> => {
|
export const submitForm = (options: SubmitFormOptions): Promise<any> => {
|
||||||
const { native, appContext, timeout = 10000, returnChangeRecords, debug = false, ...formProps } = options;
|
const { native, appContext, timeout = 10000, returnChangeRecords, debug = false, signal, ...formProps } = options;
|
||||||
|
|
||||||
return mountFormInstance<any>({
|
return mountFormInstance<any>({
|
||||||
formProps,
|
formProps,
|
||||||
appContext,
|
appContext,
|
||||||
timeout,
|
timeout,
|
||||||
|
signal,
|
||||||
// 调试模式需把表单展示出来;普通模式隐藏挂载
|
// 调试模式需把表单展示出来;普通模式隐藏挂载
|
||||||
hidden: !debug,
|
hidden: !debug,
|
||||||
// 调试模式等待人工操作,不应用超时
|
// 调试模式等待人工操作,不应用超时
|
||||||
@ -534,6 +610,11 @@ export interface ValidateFormOptions {
|
|||||||
*/
|
*/
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
typeMatchValid?: boolean;
|
typeMatchValid?: boolean;
|
||||||
|
/**
|
||||||
|
* 外部中断信号。abort 时会立即以 `signal.reason` reject 并卸载临时表单实例、移除容器。
|
||||||
|
* 主要用于 `debug` 模式(无超时兜底)下取消一个被放弃的表单弹层,避免其无限驻留在页面上。
|
||||||
|
*/
|
||||||
|
signal?: AbortSignal;
|
||||||
}
|
}
|
||||||
// #endregion ValidateFormOptions
|
// #endregion ValidateFormOptions
|
||||||
|
|
||||||
@ -629,7 +710,7 @@ export const stripTabItemsLazy = (config: FormConfig): FormConfig => {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export const validateForm = (options: ValidateFormOptions): Promise<string> => {
|
export const validateForm = (options: ValidateFormOptions): Promise<string> => {
|
||||||
const { appContext, timeout = 10000, debug = false, config, ...rest } = options;
|
const { appContext, timeout = 10000, debug = false, config, signal, ...rest } = options;
|
||||||
|
|
||||||
// 去掉 tab 容器各标签页的 lazy,确保懒加载标签页内的字段也参与校验
|
// 去掉 tab 容器各标签页的 lazy,确保懒加载标签页内的字段也参与校验
|
||||||
const formProps = { ...rest, config: stripTabItemsLazy(config) };
|
const formProps = { ...rest, config: stripTabItemsLazy(config) };
|
||||||
@ -638,6 +719,7 @@ export const validateForm = (options: ValidateFormOptions): Promise<string> => {
|
|||||||
formProps,
|
formProps,
|
||||||
appContext,
|
appContext,
|
||||||
timeout,
|
timeout,
|
||||||
|
signal,
|
||||||
// 调试模式需把表单展示出来;普通模式隐藏挂载
|
// 调试模式需把表单展示出来;普通模式隐藏挂载
|
||||||
hidden: !debug,
|
hidden: !debug,
|
||||||
// 调试模式等待人工操作,不应用超时
|
// 调试模式等待人工操作,不应用超时
|
||||||
|
|||||||
@ -449,16 +449,27 @@ export const sortChange = (data: any[], { prop, order }: SortProp) => {
|
|||||||
* - accessor 描述符(`{ get stage() { return ... } }`)按原样 defineProperty,调用方
|
* - accessor 描述符(`{ get stage() { return ... } }`)按原样 defineProperty,调用方
|
||||||
* 可控制读时求值;强制 `configurable: true` 以便下一次合并可再 define。
|
* 可控制读时求值;强制 `configurable: true` 以便下一次合并可再 define。
|
||||||
*
|
*
|
||||||
* 注意:formState 上由 props 派生的字段(keyProp / popperClass / config / initValues /
|
* 注意:extendState 只能向 formState「新增」字段,不允许覆盖其已有 key。
|
||||||
* isCompare / lastValues / parentValues)是只读 getter(无 setter),extendState 若以
|
* 调用方可通过 `reservedKeys` 传入合并前已存在的内置 key 快照(keyProp / popperClass /
|
||||||
* 普通字段形式返回同名 key,直接赋值会让 proxy 的 set trap 失败并抛出
|
* config / initValues / isCompare / lastValues / parentValues / values / $emit / fields /
|
||||||
* `TypeError: 'set' on proxy: trap returned falsish`,这里统一跳过并告警;
|
* post 等),命中这些 key 时统一跳过并告警。
|
||||||
* 如确需覆盖,可在 extendState 中以 get 访问器形式返回。
|
*
|
||||||
|
* 兜底:未传 `reservedKeys` 时,仍会拦截 props 派生的只读 getter 字段(无 setter),
|
||||||
|
* 否则以普通字段形式赋值会让 proxy 的 set trap 抛出
|
||||||
|
* `TypeError: 'set' on proxy: trap returned falsish`。
|
||||||
*/
|
*/
|
||||||
export const applyExtendState = (formState: FormState, state: Record<string, any> | null | undefined): void => {
|
export const applyExtendState = (
|
||||||
|
formState: FormState,
|
||||||
|
state: Record<string, any> | null | undefined,
|
||||||
|
reservedKeys?: Set<string | symbol>,
|
||||||
|
): void => {
|
||||||
if (!state) return;
|
if (!state) return;
|
||||||
|
|
||||||
for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(state))) {
|
for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(state))) {
|
||||||
|
if (reservedKeys?.has(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!('value' in descriptor)) {
|
if (!('value' in descriptor)) {
|
||||||
descriptor.configurable = true;
|
descriptor.configurable = true;
|
||||||
Object.defineProperty(formState, key, descriptor);
|
Object.defineProperty(formState, key, descriptor);
|
||||||
|
|||||||
@ -142,7 +142,7 @@ const stringifyExampleValue = (value: any): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 参考建议中最多展示的可选值个数,超出以「等」省略。
|
// 参考建议中最多展示的可选值个数,超出以「等」省略。
|
||||||
const MAX_SUGGESTION_OPTIONS = 5;
|
const MAX_SUGGESTION_OPTIONS = 20;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成「请使用以下某一个值:xxx;xxx」形式的参考建议;无可选值时返回空字符串(不追加建议)。
|
* 生成「请使用以下某一个值:xxx;xxx」形式的参考建议;无可选值时返回空字符串(不追加建议)。
|
||||||
@ -558,6 +558,11 @@ const validateBuiltinTypeMatch = (
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// text 常与数字(如 id、数量)绑定,值允许为数字;错误建议仍按字符串给出
|
||||||
|
if (fieldType === 'text' && isNumberValue(value)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
return defaultMessage(
|
return defaultMessage(
|
||||||
message,
|
message,
|
||||||
@ -697,9 +702,13 @@ export const validateTypeMatch = (
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!props.config?.name) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const rawFieldType = 'type' in (props.config || {}) ? props.config.type : '';
|
const rawFieldType = 'type' in (props.config || {}) ? props.config.type : '';
|
||||||
if (typeof rawFieldType !== 'string' || !rawFieldType) {
|
if (typeof rawFieldType !== 'string' || !rawFieldType) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 统一将驼峰形式(如 radioGroup)归一化为连字符形式(radio-group),与内置规则的 key 保持一致
|
// 统一将驼峰形式(如 radioGroup)归一化为连字符形式(radio-group),与内置规则的 key 保持一致
|
||||||
|
|||||||
@ -189,9 +189,7 @@ describe('Form.vue —— extendState', () => {
|
|||||||
expect(v2).toMatch(/^stage-/);
|
expect(v2).toMatch(/^stage-/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('extendState 以普通字段返回 props 派生的只读字段时跳过并告警,不抛错', async () => {
|
test('extendState 以普通字段返回内置保留字段时静默跳过,其余字段正常合并', async () => {
|
||||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
||||||
|
|
||||||
const wrapper = mountForm({
|
const wrapper = mountForm({
|
||||||
keyProp: 'id',
|
keyProp: 'id',
|
||||||
extendState: () => ({ keyProp: 'custom', config: [], extra: 'ok' }),
|
extendState: () => ({ keyProp: 'custom', config: [], extra: 'ok' }),
|
||||||
@ -201,18 +199,15 @@ describe('Form.vue —— extendState', () => {
|
|||||||
await nextTick();
|
await nextTick();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
// 只读派生字段保持 props 值,未被覆盖
|
// 内置保留字段(keyProp / config)未被 extendState 覆盖
|
||||||
expect((wrapper.vm.formState as any).keyProp).toBe('id');
|
expect((wrapper.vm.formState as any).keyProp).toBe('id');
|
||||||
expect(Array.isArray((wrapper.vm.formState as any).config)).toBe(true);
|
expect(Array.isArray((wrapper.vm.formState as any).config)).toBe(true);
|
||||||
// 其他字段正常合并
|
// 非保留字段正常合并
|
||||||
expect((wrapper.vm.formState as any).extra).toBe('ok');
|
expect((wrapper.vm.formState as any).extra).toBe('ok');
|
||||||
expect(warnSpy).toHaveBeenCalled();
|
|
||||||
expect(wrapper.find('.m-form').exists()).toBe(true);
|
expect(wrapper.find('.m-form').exists()).toBe(true);
|
||||||
|
|
||||||
warnSpy.mockRestore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('extendState 以 get 访问器返回只读字段同名 key 时允许显式覆盖', async () => {
|
test('extendState 以 get 访问器返回内置保留字段同名 key 时仍被拦截,无法覆盖', async () => {
|
||||||
const wrapper = mountForm({
|
const wrapper = mountForm({
|
||||||
keyProp: 'id',
|
keyProp: 'id',
|
||||||
extendState: () =>
|
extendState: () =>
|
||||||
@ -228,7 +223,8 @@ describe('Form.vue —— extendState', () => {
|
|||||||
await nextTick();
|
await nextTick();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
expect((wrapper.vm.formState as any).keyProp).toBe('custom-key');
|
// keyProp 属于内置保留字段,即使以访问器形式返回也不允许覆盖
|
||||||
|
expect((wrapper.vm.formState as any).keyProp).toBe('id');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('extendState 同步段读到的响应式数据变化时会重跑', async () => {
|
test('extendState 同步段读到的响应式数据变化时会重跑', async () => {
|
||||||
|
|||||||
@ -16,18 +16,29 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { afterEach, beforeAll, describe, expect, test, vi } from 'vitest';
|
import { afterEach, beforeAll, describe, expect, test, vi } from 'vitest';
|
||||||
import { type AppContext, createApp, defineComponent, h, nextTick } from 'vue';
|
import { type AppContext, createApp, defineComponent, h, inject, nextTick } from 'vue';
|
||||||
import MagicForm, { submitForm } from '@form/index';
|
import MagicForm, { FORM_SILENT_MODE_KEY, submitForm, validateForm } from '@form/index';
|
||||||
import ElementPlus from 'element-plus';
|
import ElementPlus from 'element-plus';
|
||||||
|
|
||||||
let appContext: AppContext;
|
let appContext: AppContext;
|
||||||
|
|
||||||
|
// 探针字段:挂载时注入静默标记并记录,用于验证 submitForm/validateForm 的 provide 行为
|
||||||
|
const silentProbeValues: (boolean | undefined)[] = [];
|
||||||
|
const SilentProbe = defineComponent({
|
||||||
|
name: 'MFieldsSilentProbe',
|
||||||
|
setup() {
|
||||||
|
silentProbeValues.push(inject(FORM_SILENT_MODE_KEY, undefined));
|
||||||
|
return () => h('div');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
// 构造一个父级 app,把 element-plus 与 m-form 插件装上,
|
// 构造一个父级 app,把 element-plus 与 m-form 插件装上,
|
||||||
// 之后通过 appContext 传给 submitForm 复用全局注册
|
// 之后通过 appContext 传给 submitForm 复用全局注册
|
||||||
const parentApp = createApp(defineComponent({ render: () => h('div') }));
|
const parentApp = createApp(defineComponent({ render: () => h('div') }));
|
||||||
parentApp.use(ElementPlus);
|
parentApp.use(ElementPlus);
|
||||||
parentApp.use(MagicForm);
|
parentApp.use(MagicForm);
|
||||||
|
parentApp.component('m-fields-silent-probe', SilentProbe);
|
||||||
appContext = parentApp._context;
|
appContext = parentApp._context;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -79,9 +90,7 @@ describe('submitForm', () => {
|
|||||||
expect(extendState).toHaveBeenCalled();
|
expect(extendState).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('extendState 返回 keyProp 等只读派生字段时不抛错且正常 resolve', async () => {
|
test('extendState 返回 keyProp 等内置保留字段时静默跳过且正常 resolve', async () => {
|
||||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
||||||
|
|
||||||
const values = await submitForm({
|
const values = await submitForm({
|
||||||
config: [{ type: 'text', name: 'text', text: 'text' }],
|
config: [{ type: 'text', name: 'text', text: 'text' }],
|
||||||
initValues: { text: 'foo' },
|
initValues: { text: 'foo' },
|
||||||
@ -89,10 +98,8 @@ describe('submitForm', () => {
|
|||||||
appContext,
|
appContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// keyProp 属于内置保留字段,被静默跳过,不污染最终 values
|
||||||
expect(values).toEqual({ text: 'foo' });
|
expect(values).toEqual({ text: 'foo' });
|
||||||
expect(warnSpy).toHaveBeenCalled();
|
|
||||||
|
|
||||||
warnSpy.mockRestore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('在嵌套 items 配置下也能正确 resolve', async () => {
|
test('在嵌套 items 配置下也能正确 resolve', async () => {
|
||||||
@ -216,6 +223,44 @@ describe('submitForm', () => {
|
|||||||
expect(caught).toBeInstanceOf(Error);
|
expect(caught).toBeInstanceOf(Error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('静默(隐藏挂载)模式下向字段 provide FORM_SILENT_MODE_KEY=true', async () => {
|
||||||
|
silentProbeValues.length = 0;
|
||||||
|
|
||||||
|
const values = await submitForm({
|
||||||
|
config: [{ type: 'silent-probe', name: 'text', text: 'text' }],
|
||||||
|
initValues: { text: 'hello' },
|
||||||
|
appContext,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(values).toEqual({ text: 'hello' });
|
||||||
|
expect(silentProbeValues).toEqual([true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('静默标记不会泄漏到父级应用的 provides', async () => {
|
||||||
|
silentProbeValues.length = 0;
|
||||||
|
|
||||||
|
await submitForm({
|
||||||
|
config: [{ type: 'silent-probe', name: 'text', text: 'text' }],
|
||||||
|
initValues: { text: 'hello' },
|
||||||
|
appContext,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect((appContext as any).provides[FORM_SILENT_MODE_KEY as symbol]).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('validateForm 静默模式下同样 provide FORM_SILENT_MODE_KEY=true', async () => {
|
||||||
|
silentProbeValues.length = 0;
|
||||||
|
|
||||||
|
const error = await validateForm({
|
||||||
|
config: [{ type: 'silent-probe', name: 'text', text: 'text' }],
|
||||||
|
initValues: { text: 'hello' },
|
||||||
|
appContext,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(error).toBe('');
|
||||||
|
expect(silentProbeValues).toEqual([true]);
|
||||||
|
});
|
||||||
|
|
||||||
test('timeout > 0 时会注册定时器,timeout <= 0 时不注册', async () => {
|
test('timeout > 0 时会注册定时器,timeout <= 0 时不注册', async () => {
|
||||||
const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout');
|
const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout');
|
||||||
|
|
||||||
@ -354,6 +399,24 @@ describe('submitForm —— debug 模式', () => {
|
|||||||
expect(document.body.querySelector('.m-form')).toBeNull();
|
expect(document.body.querySelector('.m-form')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('debug 模式不提供静默标记(表单可见,字段应正常渲染)', async () => {
|
||||||
|
silentProbeValues.length = 0;
|
||||||
|
|
||||||
|
const pending = submitForm({
|
||||||
|
config: [{ type: 'silent-probe', name: 'text', text: 'text' }],
|
||||||
|
initValues: { text: 'hello' },
|
||||||
|
debug: true,
|
||||||
|
appContext,
|
||||||
|
});
|
||||||
|
await nextTick();
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(silentProbeValues).toEqual([undefined]);
|
||||||
|
|
||||||
|
findButton('确定').click();
|
||||||
|
await pending;
|
||||||
|
});
|
||||||
|
|
||||||
test('debug 模式不注册超时定时器(等待人工操作)', async () => {
|
test('debug 模式不注册超时定时器(等待人工操作)', async () => {
|
||||||
const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout');
|
const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout');
|
||||||
|
|
||||||
|
|||||||
@ -16,9 +16,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, expect, test } from 'vitest';
|
import { describe, expect, test, vi } from 'vitest';
|
||||||
import type { FormState } from '@form/index';
|
import type { FormState } from '@form/index';
|
||||||
import {
|
import {
|
||||||
|
applyExtendState,
|
||||||
createObjectProp,
|
createObjectProp,
|
||||||
createValues,
|
createValues,
|
||||||
datetimeFormatter,
|
datetimeFormatter,
|
||||||
@ -1014,3 +1015,130 @@ describe('createObjectProp', () => {
|
|||||||
expect(createObjectProp('a.b.c', 'newKey', 'c')).toBe('a.b.newKey');
|
expect(createObjectProp('a.b.c', 'newKey', 'c')).toBe('a.b.newKey');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('applyExtendState', () => {
|
||||||
|
test('state 为 null 或 undefined 时不修改 formState', () => {
|
||||||
|
const formState = { a: 1 } as any;
|
||||||
|
|
||||||
|
applyExtendState(formState, null);
|
||||||
|
applyExtendState(formState, undefined);
|
||||||
|
|
||||||
|
expect(formState).toEqual({ a: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('普通字段(data descriptor)以新增字段形式合并进 formState', () => {
|
||||||
|
const formState = {} as any;
|
||||||
|
|
||||||
|
applyExtendState(formState, { foo: 'bar', num: 1 });
|
||||||
|
|
||||||
|
expect(formState.foo).toBe('bar');
|
||||||
|
expect(formState.num).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('accessor descriptor 按原样 define 且读时求值', () => {
|
||||||
|
const formState = {} as any;
|
||||||
|
let count = 0;
|
||||||
|
const state = {};
|
||||||
|
Object.defineProperty(state, 'stage', {
|
||||||
|
get() {
|
||||||
|
count += 1;
|
||||||
|
return 'stageValue';
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
applyExtendState(formState, state);
|
||||||
|
|
||||||
|
// 读时才求值
|
||||||
|
expect(count).toBe(0);
|
||||||
|
expect(formState.stage).toBe('stageValue');
|
||||||
|
expect(count).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('accessor descriptor 强制 configurable=true,可再次 define 不报错', () => {
|
||||||
|
const formState = {} as any;
|
||||||
|
const state = {};
|
||||||
|
// 原始描述符 configurable 为 false
|
||||||
|
Object.defineProperty(state, 'stage', {
|
||||||
|
get: () => 'v',
|
||||||
|
enumerable: true,
|
||||||
|
configurable: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
applyExtendState(formState, state);
|
||||||
|
|
||||||
|
const descriptor = Object.getOwnPropertyDescriptor(formState, 'stage');
|
||||||
|
expect(descriptor?.configurable).toBe(true);
|
||||||
|
// 再次合并(重新 define)不应抛出
|
||||||
|
expect(() => applyExtendState(formState, state)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reservedKeys 命中的 key 被跳过,不覆盖已有内置字段', () => {
|
||||||
|
const formState = { values: { origin: true } } as any;
|
||||||
|
const reservedKeys = new Set<string | symbol>(['values']);
|
||||||
|
|
||||||
|
applyExtendState(formState, { values: { changed: true }, extra: 1 }, reservedKeys);
|
||||||
|
|
||||||
|
// reserved key 未被覆盖
|
||||||
|
expect(formState.values).toEqual({ origin: true });
|
||||||
|
// 非 reserved 的新字段正常新增
|
||||||
|
expect(formState.extra).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reservedKeys 对 accessor 形式的同名 key 同样跳过', () => {
|
||||||
|
const formState = { keyProp: 'origin' } as any;
|
||||||
|
const reservedKeys = new Set<string | symbol>(['keyProp']);
|
||||||
|
const state = {};
|
||||||
|
Object.defineProperty(state, 'keyProp', {
|
||||||
|
get: () => 'override',
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
applyExtendState(formState, state, reservedKeys);
|
||||||
|
|
||||||
|
expect(formState.keyProp).toBe('origin');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('未传 reservedKeys 时,只读 getter 字段被跳过并告警', () => {
|
||||||
|
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
|
||||||
|
const formState = {} as any;
|
||||||
|
Object.defineProperty(formState, 'keyProp', {
|
||||||
|
get: () => 'readonly',
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
applyExtendState(formState, { keyProp: 'newValue' });
|
||||||
|
|
||||||
|
expect(formState.keyProp).toBe('readonly');
|
||||||
|
expect(warnSpy).toHaveBeenCalled();
|
||||||
|
warnSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('未传 reservedKeys 时,存在 setter 的字段可被赋值', () => {
|
||||||
|
let inner = 'old';
|
||||||
|
const formState = {} as any;
|
||||||
|
Object.defineProperty(formState, 'writable', {
|
||||||
|
get: () => inner,
|
||||||
|
set: (v: string) => {
|
||||||
|
inner = v;
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
applyExtendState(formState, { writable: 'new' });
|
||||||
|
|
||||||
|
expect(formState.writable).toBe('new');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('未传 reservedKeys 时,普通可写字段正常覆盖赋值', () => {
|
||||||
|
const formState = { editable: 'old' } as any;
|
||||||
|
|
||||||
|
applyExtendState(formState, { editable: 'new' });
|
||||||
|
|
||||||
|
expect(formState.editable).toBe('new');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import type { FormState } from '@form/index';
|
|||||||
import { getRules } from '@form/utils/form';
|
import { getRules } from '@form/utils/form';
|
||||||
import {
|
import {
|
||||||
clearTypeMatchRules,
|
clearTypeMatchRules,
|
||||||
|
createTypeMatchValidator,
|
||||||
deleteTypeMatchRule,
|
deleteTypeMatchRule,
|
||||||
getTypeMatchRule,
|
getTypeMatchRule,
|
||||||
registerTypeMatchRule,
|
registerTypeMatchRule,
|
||||||
@ -51,9 +52,12 @@ const mForm: FormState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const propsOf = (config: Record<string, any>, model: Record<string, any> = {}) => ({
|
const propsOf = (config: Record<string, any>, model: Record<string, any> = {}) => ({
|
||||||
config,
|
config: {
|
||||||
|
name: 'field',
|
||||||
|
...config,
|
||||||
|
},
|
||||||
model,
|
model,
|
||||||
prop: config.name || 'field',
|
prop: config.name || config.prop || 'field',
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('validateTypeMatch', () => {
|
describe('validateTypeMatch', () => {
|
||||||
@ -64,6 +68,19 @@ describe('validateTypeMatch', () => {
|
|||||||
expect(validateTypeMatch([], mForm, propsOf({ type: 'select', multiple: true }))).toBeUndefined();
|
expect(validateTypeMatch([], mForm, propsOf({ type: 'select', multiple: true }))).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('config 未配置 name 时跳过校验', () => {
|
||||||
|
const noNameProps = { config: { type: 'text' }, model: {} };
|
||||||
|
expect(validateTypeMatch(123, mForm, noNameProps)).toBeUndefined();
|
||||||
|
expect(validateTypeMatch({ a: 1 }, mForm, { config: { type: 'number' }, model: {} })).toBeUndefined();
|
||||||
|
expect(
|
||||||
|
validateTypeMatch('bad', mForm, {
|
||||||
|
config: { type: 'select', options: [{ text: 'A', value: 'a' }] },
|
||||||
|
model: {},
|
||||||
|
}),
|
||||||
|
).toBeUndefined();
|
||||||
|
expect(validateTypeMatch(123, mForm, { config: undefined, model: {} })).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
test('0 / false 不视为空值', () => {
|
test('0 / false 不视为空值', () => {
|
||||||
expect(validateTypeMatch(0, mForm, propsOf({ type: 'number' }))).toBeUndefined();
|
expect(validateTypeMatch(0, mForm, propsOf({ type: 'number' }))).toBeUndefined();
|
||||||
expect(validateTypeMatch(false, mForm, propsOf({ type: 'switch' }))).toBeUndefined();
|
expect(validateTypeMatch(false, mForm, propsOf({ type: 'switch' }))).toBeUndefined();
|
||||||
@ -74,12 +91,17 @@ describe('validateTypeMatch', () => {
|
|||||||
expect(validateTypeMatch({ a: 1 }, mForm, propsOf({ type: 'hidden' }))).toBeUndefined();
|
expect(validateTypeMatch({ a: 1 }, mForm, propsOf({ type: 'hidden' }))).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('text 期望 string', () => {
|
test('text 期望 string,允许 number', () => {
|
||||||
expect(validateTypeMatch('ok', mForm, propsOf({ type: 'text' }))).toBeUndefined();
|
expect(validateTypeMatch('ok', mForm, propsOf({ type: 'text' }))).toBeUndefined();
|
||||||
expect(validateTypeMatch(1, mForm, propsOf({ type: 'text' }))).toBe(
|
expect(validateTypeMatch(1, mForm, propsOf({ type: 'text' }))).toBeUndefined();
|
||||||
'1 类型应为字符串\n\n请参考以下示例值:"文本内容"',
|
expect(validateTypeMatch(0, mForm, propsOf({ type: 'text' }))).toBeUndefined();
|
||||||
|
expect(validateTypeMatch(NaN, mForm, propsOf({ type: 'text' }))).toBe(
|
||||||
|
'NaN 类型应为字符串\n\n请参考以下示例值:"文本内容"',
|
||||||
);
|
);
|
||||||
expect(validateTypeMatch(1, mForm, propsOf({ type: 'text' }), '自定义错误')).toBe('自定义错误');
|
expect(validateTypeMatch({ a: 1 }, mForm, propsOf({ type: 'text' }))).toBe(
|
||||||
|
'[object Object] 类型应为字符串\n\n请参考以下示例值:"文本内容"',
|
||||||
|
);
|
||||||
|
expect(validateTypeMatch(true, mForm, propsOf({ type: 'text' }), '自定义错误')).toBe('自定义错误');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('text filter=number 时期望 number', () => {
|
test('text filter=number 时期望 number', () => {
|
||||||
@ -174,13 +196,14 @@ describe('validateTypeMatch', () => {
|
|||||||
expect(validateTypeMatch(3, mForm, propsOf(config))).toBe('3 不在可选项中\n\n请使用以下某一个值:1;2');
|
expect(validateTypeMatch(3, mForm, propsOf(config))).toBe('3 不在可选项中\n\n请使用以下某一个值:1;2');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('可选项超过 5 个时建议仅展示前 5 个并以「等」省略', () => {
|
test('可选项超过 20 个时建议仅展示前 20 个并以「等」省略', () => {
|
||||||
|
const values = Array.from({ length: 21 }, (_, i) => i + 1);
|
||||||
const config = {
|
const config = {
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: [1, 2, 3, 4, 5, 6, 7].map((v) => ({ text: `${v}`, value: v })),
|
options: values.map((v) => ({ text: `${v}`, value: v })),
|
||||||
};
|
};
|
||||||
expect(validateTypeMatch(99, mForm, propsOf(config))).toBe(
|
expect(validateTypeMatch(99, mForm, propsOf(config))).toBe(
|
||||||
'99 不在可选项中\n\n请使用以下某一个值:1;2;3;4;5 等',
|
`99 不在可选项中\n\n请使用以下某一个值:${values.slice(0, 20).join(';')} 等`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -386,6 +409,11 @@ describe('validateTypeMatch', () => {
|
|||||||
).toBe('a 类型应为数组\n\n请参考以下示例值:["a"]');
|
).toBe('a 类型应为数组\n\n请参考以下示例值:["a"]');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('radio-group / checkbox-group 无 options 时跳过校验', () => {
|
||||||
|
expect(validateTypeMatch('a', mForm, propsOf({ type: 'radio-group' }))).toBeUndefined();
|
||||||
|
expect(validateTypeMatch('a', mForm, propsOf({ type: 'checkbox-group' }))).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
test('timerange 按 valueFormat 校验', () => {
|
test('timerange 按 valueFormat 校验', () => {
|
||||||
expect(validateTypeMatch(['12:00:00', '13:00:00'], mForm, propsOf({ type: 'timerange' }))).toBeUndefined();
|
expect(validateTypeMatch(['12:00:00', '13:00:00'], mForm, propsOf({ type: 'timerange' }))).toBeUndefined();
|
||||||
expect(validateTypeMatch(['bad'], mForm, propsOf({ type: 'timerange' }))).toMatch(
|
expect(validateTypeMatch(['bad'], mForm, propsOf({ type: 'timerange' }))).toMatch(
|
||||||
@ -504,8 +532,8 @@ describe('validateTypeMatch', () => {
|
|||||||
expect(validateTypeMatch('1', mForm, propsOf({ type: 'number', defaultValue: () => Promise.resolve(123) }))).toBe(
|
expect(validateTypeMatch('1', mForm, propsOf({ type: 'number', defaultValue: () => Promise.resolve(123) }))).toBe(
|
||||||
'1 类型应为数字\n\n请参考以下示例值:123',
|
'1 类型应为数字\n\n请参考以下示例值:123',
|
||||||
);
|
);
|
||||||
expect(validateTypeMatch(1, mForm, propsOf({ type: 'text', defaultValue: async () => '示例' }))).toBe(
|
expect(validateTypeMatch({ a: 1 }, mForm, propsOf({ type: 'text', defaultValue: async () => '示例' }))).toBe(
|
||||||
'1 类型应为字符串\n\n请参考以下示例值:"文本内容"',
|
'[object Object] 类型应为字符串\n\n请参考以下示例值:"文本内容"',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -523,7 +551,7 @@ describe('getRules typeMatch', () => {
|
|||||||
const rules: any = [{ typeMatch: true, message: '类型错误' }];
|
const rules: any = [{ typeMatch: true, message: '类型错误' }];
|
||||||
const newRules: any = getRules(mForm, rules, propsOf({ type: 'text' }));
|
const newRules: any = getRules(mForm, rules, propsOf({ type: 'text' }));
|
||||||
const callback = vi.fn();
|
const callback = vi.fn();
|
||||||
newRules[0].validator({}, 123, callback);
|
newRules[0].validator({}, { a: 1 }, callback);
|
||||||
expect(callback).toHaveBeenCalledWith(expect.any(Error));
|
expect(callback).toHaveBeenCalledWith(expect.any(Error));
|
||||||
expect(callback.mock.calls[0][0].message).toBe('类型错误');
|
expect(callback.mock.calls[0][0].message).toBe('类型错误');
|
||||||
});
|
});
|
||||||
@ -568,7 +596,7 @@ describe('getRules tdesign validator', () => {
|
|||||||
const rules: any = [{ typeMatch: true, message: '类型错误' }];
|
const rules: any = [{ typeMatch: true, message: '类型错误' }];
|
||||||
const newRules: any = getRules(mForm, rules, propsOf({ type: 'text' }));
|
const newRules: any = getRules(mForm, rules, propsOf({ type: 'text' }));
|
||||||
// TDesign 调用签名:validator(val)
|
// TDesign 调用签名:validator(val)
|
||||||
await expect(newRules[0].validator(123)).resolves.toEqual({
|
await expect(newRules[0].validator({ a: 1 })).resolves.toEqual({
|
||||||
result: false,
|
result: false,
|
||||||
message: '类型错误',
|
message: '类型错误',
|
||||||
});
|
});
|
||||||
@ -694,3 +722,30 @@ describe('plugin typeMatchRules', () => {
|
|||||||
expect(validateTypeMatch('bad', mForm, propsOf({ type: 'install-type' }))).toBe('install error');
|
expect(validateTypeMatch('bad', mForm, propsOf({ type: 'install-type' }))).toBe('install error');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('createTypeMatchValidator', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
clearTypeMatchRules();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
clearTypeMatchRules();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('validateTypeMatch 抛异常时仍执行原始 validator', () => {
|
||||||
|
registerTypeMatchRule('throw', () => {
|
||||||
|
throw new Error('boom');
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
const originalValidator = vi.fn();
|
||||||
|
const validator = createTypeMatchValidator(mForm, propsOf({ type: 'throw' }), {
|
||||||
|
validator: originalValidator,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
validator({}, 'value', () => {}, {}, {});
|
||||||
|
expect(originalValidator).toHaveBeenCalled();
|
||||||
|
expect(errorSpy).toHaveBeenCalled();
|
||||||
|
errorSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"name": "@tmagic/schema",
|
"name": "@tmagic/schema",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"name": "@tmagic/stage",
|
"name": "@tmagic/stage",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"name": "@tmagic/table",
|
"name": "@tmagic/table",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": [
|
"sideEffects": [
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"name": "@tmagic/tdesign-vue-next-adapter",
|
"name": "@tmagic/tdesign-vue-next-adapter",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"name": "@tmagic/utils",
|
"name": "@tmagic/utils",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tmagic-playground",
|
"name": "tmagic-playground",
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -12,11 +12,11 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.2",
|
"@element-plus/icons-vue": "^2.3.2",
|
||||||
"@tmagic/core": "1.8.0-beta.14",
|
"@tmagic/core": "1.8.0-beta.16",
|
||||||
"@tmagic/design": "1.8.0-beta.14",
|
"@tmagic/design": "1.8.0-beta.16",
|
||||||
"@tmagic/editor": "1.8.0-beta.14",
|
"@tmagic/editor": "1.8.0-beta.16",
|
||||||
"@tmagic/element-plus-adapter": "1.8.0-beta.14",
|
"@tmagic/element-plus-adapter": "1.8.0-beta.16",
|
||||||
"@tmagic/tdesign-vue-next-adapter": "1.8.0-beta.14",
|
"@tmagic/tdesign-vue-next-adapter": "1.8.0-beta.16",
|
||||||
"@tmagic/tmagic-form-runtime": "1.1.3",
|
"@tmagic/tmagic-form-runtime": "1.1.3",
|
||||||
"element-plus": "^2.14.3",
|
"element-plus": "^2.14.3",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
|||||||
228
pnpm-lock.yaml
generated
228
pnpm-lock.yaml
generated
@ -564,23 +564,23 @@ importers:
|
|||||||
specifier: ^2.3.2
|
specifier: ^2.3.2
|
||||||
version: 2.3.2(vue@3.5.40(typescript@6.0.3))
|
version: 2.3.2(vue@3.5.40(typescript@6.0.3))
|
||||||
'@tmagic/core':
|
'@tmagic/core':
|
||||||
specifier: 1.8.0-beta.14
|
specifier: 1.8.0-beta.16
|
||||||
version: 1.8.0-beta.14(typescript@6.0.3)
|
version: 1.8.0-beta.16(typescript@6.0.3)
|
||||||
'@tmagic/design':
|
'@tmagic/design':
|
||||||
specifier: 1.8.0-beta.14
|
specifier: 1.8.0-beta.16
|
||||||
version: 1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
version: 1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
||||||
'@tmagic/editor':
|
'@tmagic/editor':
|
||||||
specifier: 1.8.0-beta.14
|
specifier: 1.8.0-beta.16
|
||||||
version: 1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(@tmagic/form-schema@1.8.0-beta.14(typescript@6.0.3))(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(monaco-editor@0.55.1)(type-fest@5.2.0)(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
version: 1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(@tmagic/form-schema@1.8.0-beta.16(typescript@6.0.3))(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(monaco-editor@0.55.1)(type-fest@5.2.0)(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
||||||
'@tmagic/element-plus-adapter':
|
'@tmagic/element-plus-adapter':
|
||||||
specifier: 1.8.0-beta.14
|
specifier: 1.8.0-beta.16
|
||||||
version: 1.8.0-beta.14(@tmagic/design@1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(element-plus@2.14.3(vue@3.5.40(typescript@6.0.3)))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
version: 1.8.0-beta.16(@tmagic/design@1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(element-plus@2.14.3(vue@3.5.40(typescript@6.0.3)))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
||||||
'@tmagic/tdesign-vue-next-adapter':
|
'@tmagic/tdesign-vue-next-adapter':
|
||||||
specifier: 1.8.0-beta.14
|
specifier: 1.8.0-beta.16
|
||||||
version: 1.8.0-beta.14(@tmagic/design@1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(tdesign-vue-next@1.17.3(vue@3.5.40(typescript@6.0.3)))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
version: 1.8.0-beta.16(@tmagic/design@1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(tdesign-vue-next@1.17.3(vue@3.5.40(typescript@6.0.3)))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
||||||
'@tmagic/tmagic-form-runtime':
|
'@tmagic/tmagic-form-runtime':
|
||||||
specifier: 1.1.3
|
specifier: 1.1.3
|
||||||
version: 1.1.3(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(@tmagic/editor@1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(@tmagic/form-schema@1.8.0-beta.14(typescript@6.0.3))(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(monaco-editor@0.55.1)(type-fest@5.2.0)(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(element-plus@2.14.3(vue@3.5.40(typescript@6.0.3)))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
version: 1.1.3(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(@tmagic/editor@1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(@tmagic/form-schema@1.8.0-beta.16(typescript@6.0.3))(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(monaco-editor@0.55.1)(type-fest@5.2.0)(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(element-plus@2.14.3(vue@3.5.40(typescript@6.0.3)))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
||||||
element-plus:
|
element-plus:
|
||||||
specifier: ^2.14.3
|
specifier: ^2.14.3
|
||||||
version: 2.14.3(vue@3.5.40(typescript@6.0.3))
|
version: 2.14.3(vue@3.5.40(typescript@6.0.3))
|
||||||
@ -920,14 +920,14 @@ importers:
|
|||||||
runtime/react:
|
runtime/react:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tmagic/core':
|
'@tmagic/core':
|
||||||
specifier: 1.8.0-beta.14
|
specifier: 1.8.0-beta.16
|
||||||
version: 1.8.0-beta.14(typescript@6.0.3)
|
version: 1.8.0-beta.16(typescript@6.0.3)
|
||||||
'@tmagic/react-runtime-help':
|
'@tmagic/react-runtime-help':
|
||||||
specifier: 0.2.2
|
specifier: 0.2.2
|
||||||
version: 0.2.2(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(@tmagic/stage@1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3))(lodash-es@4.18.1)(react@18.3.1)(typescript@6.0.3)
|
version: 0.2.2(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(@tmagic/stage@1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3))(lodash-es@4.18.1)(react@18.3.1)(typescript@6.0.3)
|
||||||
'@tmagic/stage':
|
'@tmagic/stage':
|
||||||
specifier: 1.8.0-beta.14
|
specifier: 1.8.0-beta.16
|
||||||
version: 1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3)
|
version: 1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3)
|
||||||
axios:
|
axios:
|
||||||
specifier: ^1.13.2
|
specifier: ^1.13.2
|
||||||
version: 1.13.2
|
version: 1.13.2
|
||||||
@ -942,8 +942,8 @@ importers:
|
|||||||
version: 18.3.1(react@18.3.1)
|
version: 18.3.1(react@18.3.1)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@tmagic/cli':
|
'@tmagic/cli':
|
||||||
specifier: 1.8.0-beta.14
|
specifier: 1.8.0-beta.16
|
||||||
version: 1.8.0-beta.14(typescript@6.0.3)
|
version: 1.8.0-beta.16(typescript@6.0.3)
|
||||||
'@types/fs-extra':
|
'@types/fs-extra':
|
||||||
specifier: ^11.0.4
|
specifier: ^11.0.4
|
||||||
version: 11.0.4
|
version: 11.0.4
|
||||||
@ -1018,14 +1018,14 @@ importers:
|
|||||||
runtime/vue:
|
runtime/vue:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tmagic/core':
|
'@tmagic/core':
|
||||||
specifier: 1.8.0-beta.14
|
specifier: 1.8.0-beta.16
|
||||||
version: 1.8.0-beta.14(typescript@6.0.3)
|
version: 1.8.0-beta.16(typescript@6.0.3)
|
||||||
'@tmagic/stage':
|
'@tmagic/stage':
|
||||||
specifier: 1.8.0-beta.14
|
specifier: 1.8.0-beta.16
|
||||||
version: 1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3)
|
version: 1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3)
|
||||||
'@tmagic/vue-runtime-help':
|
'@tmagic/vue-runtime-help':
|
||||||
specifier: ^2.0.4
|
specifier: ^2.0.4
|
||||||
version: 2.0.4(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(@tmagic/stage@1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
version: 2.0.4(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(@tmagic/stage@1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
||||||
axios:
|
axios:
|
||||||
specifier: ^1.13.2
|
specifier: ^1.13.2
|
||||||
version: 1.13.2
|
version: 1.13.2
|
||||||
@ -1034,8 +1034,8 @@ importers:
|
|||||||
version: 3.5.40(typescript@6.0.3)
|
version: 3.5.40(typescript@6.0.3)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@tmagic/cli':
|
'@tmagic/cli':
|
||||||
specifier: 1.8.0-beta.14
|
specifier: 1.8.0-beta.16
|
||||||
version: 1.8.0-beta.14(typescript@6.0.3)
|
version: 1.8.0-beta.16(typescript@6.0.3)
|
||||||
'@types/fs-extra':
|
'@types/fs-extra':
|
||||||
specifier: ^11.0.4
|
specifier: ^11.0.4
|
||||||
version: 11.0.4
|
version: 11.0.4
|
||||||
@ -2997,8 +2997,8 @@ packages:
|
|||||||
'@sxzz/popperjs-es@2.11.8':
|
'@sxzz/popperjs-es@2.11.8':
|
||||||
resolution: {integrity: sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==}
|
resolution: {integrity: sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==}
|
||||||
|
|
||||||
'@tmagic/cli@1.8.0-beta.14':
|
'@tmagic/cli@1.8.0-beta.16':
|
||||||
resolution: {integrity: sha512-y6tTnO+NIi1xYrTDK1b/33ipJmM7jytqCMDcdNOYuG2ng416w+CxWyguAvcCwFPL50pJQereboTVsMDLFnjvBg==}
|
resolution: {integrity: sha512-89nnkge0H1sLIfOs4fPkZ50nhygVCGHs7smY9p8S1byxLL5+kGUg3AZtjRWFlQdQI6RogAzISX7q7CNTz7NVIA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -3025,8 +3025,8 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tmagic/core@1.8.0-beta.14':
|
'@tmagic/core@1.8.0-beta.16':
|
||||||
resolution: {integrity: sha512-DrUnNGTz+LK4pSCTIkmEvAfvNJglPXd+MLLu79lWJgfjyEmjrJ1rsdw6KprPzn6LkHBL9bKi/ZXGAcxCVs7P6w==}
|
resolution: {integrity: sha512-Yp9arlu4FaR0bwCziIf2EX9gmn4IyrM1C0O+CzQbvtm5owo8qUxpDx0qJ+qeHUW7XbLIwWPQWhQnRUdP+5UX8w==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: ^6.0.3
|
typescript: ^6.0.3
|
||||||
@ -3054,11 +3054,11 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tmagic/data-source@1.8.0-beta.14':
|
'@tmagic/data-source@1.8.0-beta.16':
|
||||||
resolution: {integrity: sha512-mpmxcL/tvJx8L7QV3YWz9VM2tEc5WW/9r6JK2Dsv+La6C4wbArZVYoJMhGIxPs4MBCB9VPU+HbOpVLSapwuVaQ==}
|
resolution: {integrity: sha512-NaqiGode45JTpjIqUkEPQzGent34K5ZCy75/rt3gG29s8EXH3Tc6SCHun/lCmcopHGV5PBGUEy/WJ8qyDx92lw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tmagic/core': 1.8.0-beta.14
|
'@tmagic/core': 1.8.0-beta.16
|
||||||
typescript: ^6.0.3
|
typescript: ^6.0.3
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
typescript:
|
typescript:
|
||||||
@ -3086,12 +3086,12 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tmagic/dep@1.8.0-beta.14':
|
'@tmagic/dep@1.8.0-beta.16':
|
||||||
resolution: {integrity: sha512-XPdj2XBXNGTSau9cexQsMerV8MxLlnF0KWIOJlhaZKtrtpkmFgdxhB6LksDxB3H5MsHwmev5qMgWQFc0665EWQ==}
|
resolution: {integrity: sha512-3PjMCuHGuSClRDy7xLttGBjvIoAhNznMvjTU+y5Jw3OBcoIveRfOfw3kBn/sklqQ+3QUDDojSry+XxKW+KEyCw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tmagic/schema': 1.8.0-beta.14
|
'@tmagic/schema': 1.8.0-beta.16
|
||||||
'@tmagic/utils': 1.8.0-beta.14
|
'@tmagic/utils': 1.8.0-beta.16
|
||||||
typescript: ^6.0.3
|
typescript: ^6.0.3
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
typescript:
|
typescript:
|
||||||
@ -3107,8 +3107,8 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tmagic/design@1.8.0-beta.14':
|
'@tmagic/design@1.8.0-beta.16':
|
||||||
resolution: {integrity: sha512-Ia803Ps59npRlBwMh+HiRMzjbspn6xz0nG1CG5b6785njo4yEQS4fd6yOEuTIJy+25zXb0/Gl5ytc7AOjhl+OA==}
|
resolution: {integrity: sha512-J8CLJ5oiHLu/HMlFrzEQ1DXbUWP10kFNogj75HC84orPVn6CAyHybecFFY+wRwTVjnctydWlDO4SbJDXnjkp6Q==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: ^6.0.3
|
typescript: ^6.0.3
|
||||||
@ -3129,11 +3129,11 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tmagic/editor@1.8.0-beta.14':
|
'@tmagic/editor@1.8.0-beta.16':
|
||||||
resolution: {integrity: sha512-AmtB3lA3QVdunNXU6bRd+hWalNf5RiePpsi638foGIaD934mNf+POop5HCWbfuBkU7GDbSElT4cqrvE2EGLK7A==}
|
resolution: {integrity: sha512-YogrYxPzIq/gY0w+OjV/ahzXIsWVKlJMXFsnEIQbUriQE/xXXphVugUsYSmxnDylniOgXN/EVc8+xmX5ypPLoQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tmagic/core': 1.8.0-beta.14
|
'@tmagic/core': 1.8.0-beta.16
|
||||||
monaco-editor: '^0.55.1 '
|
monaco-editor: '^0.55.1 '
|
||||||
type-fest: ^5.2.0
|
type-fest: ^5.2.0
|
||||||
typescript: ^6.0.3
|
typescript: ^6.0.3
|
||||||
@ -3144,11 +3144,11 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tmagic/element-plus-adapter@1.8.0-beta.14':
|
'@tmagic/element-plus-adapter@1.8.0-beta.16':
|
||||||
resolution: {integrity: sha512-MRqnFIyoPA8wfa6fIm4Tie8f3qZjWICq95PZm9GWZMAuOIUNM1GUj86iOZExhte52+xYV4xNC3Rh+gsRntRULQ==}
|
resolution: {integrity: sha512-LMAQhE78BC4dYFVsHVej/569CWWOoA4ht1SlhvQpIQY7fCmClnCFXqxgXdjWRDTmV3lvrCmBDeCI7N3bdj41PQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tmagic/design': 1.8.0-beta.14
|
'@tmagic/design': 1.8.0-beta.16
|
||||||
element-plus: ^2.14.3
|
element-plus: ^2.14.3
|
||||||
typescript: ^6.0.3
|
typescript: ^6.0.3
|
||||||
vue: ^3.5.40
|
vue: ^3.5.40
|
||||||
@ -3165,8 +3165,8 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tmagic/form-schema@1.8.0-beta.14':
|
'@tmagic/form-schema@1.8.0-beta.16':
|
||||||
resolution: {integrity: sha512-dVoW9YEb8A1Vcc/xZiM3lzRlhyN8h4rOkX+7Y03ZphYxvQ4x/lIuPkE07sOY8XV6aJJmwEeCDcrO+DY26a/C4Q==}
|
resolution: {integrity: sha512-pzb7zfdxWiu3JKRJzpZBfV9BsOkcd6GG/JKPc2YD/dsQ+pjMDVYc1uhU1U03cYxW6C3XwBqK4rPLzzA9h7ms5Q==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: ^6.0.3
|
typescript: ^6.0.3
|
||||||
@ -3187,13 +3187,13 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tmagic/form@1.8.0-beta.14':
|
'@tmagic/form@1.8.0-beta.16':
|
||||||
resolution: {integrity: sha512-OuMn2hTugeu8LmprproMsf4BL58GdpfgBSGdzzTKQUX18CPUZoXKSNMrlAJY2P4ZzJoSk0W8InDMbF/Os+SmKA==}
|
resolution: {integrity: sha512-CEkcqP4Qpz/QDbAYachOqIh2mgRy6lA++pD8/zjhnTqbKYiU79y+wfOs1r8Fjqte2WVCP0WTh+kF0SdGaG2JNw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tmagic/design': 1.8.0-beta.14
|
'@tmagic/design': 1.8.0-beta.16
|
||||||
'@tmagic/form-schema': 1.8.0-beta.14
|
'@tmagic/form-schema': 1.8.0-beta.16
|
||||||
'@tmagic/utils': 1.8.0-beta.14
|
'@tmagic/utils': 1.8.0-beta.16
|
||||||
typescript: ^6.0.3
|
typescript: ^6.0.3
|
||||||
vue: ^3.5.40
|
vue: ^3.5.40
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -3235,8 +3235,8 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tmagic/schema@1.8.0-beta.14':
|
'@tmagic/schema@1.8.0-beta.16':
|
||||||
resolution: {integrity: sha512-cjqm0LtXRoaCpqTJxKsPBuQh8RsdRf+EpPstMz7UKHf8DagHDHds/UK3mjDYPj7npKes04E0xw/o5bIsOjYmWQ==}
|
resolution: {integrity: sha512-VYRwr3i4fIdJBVD0CCGr8Ix2gzowSeVg2PExxH1GOERaLIrfrMcOm3YAxQCIet0guA5byz29jXZuuNFBXbjqDQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: ^6.0.3
|
typescript: ^6.0.3
|
||||||
@ -3264,11 +3264,11 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tmagic/stage@1.8.0-beta.14':
|
'@tmagic/stage@1.8.0-beta.16':
|
||||||
resolution: {integrity: sha512-OlDNwlxwV1pZgqyV5ICrbSjrBuqGU20cMagBi0Faq0kSZJ2hDNhhzxXXE1O4lUpZf0hXGl+rKvUjtV+UJ5l+aQ==}
|
resolution: {integrity: sha512-QTWA1Jak2gGwN7DSgHiODjU2OHdG3ZwD/mgy7ai7AIn4GQgqg8q86gCOsCMEb47jraC0AEA/rA3Xe/G6rg2S/A==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tmagic/core': 1.8.0-beta.14
|
'@tmagic/core': 1.8.0-beta.16
|
||||||
typescript: ^6.0.3
|
typescript: ^6.0.3
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
typescript:
|
typescript:
|
||||||
@ -3286,24 +3286,24 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tmagic/table@1.8.0-beta.14':
|
'@tmagic/table@1.8.0-beta.16':
|
||||||
resolution: {integrity: sha512-gCRepIj7OgW+8DJP7Gs5MMXiTweYdOFnr+OLP1CxH8Fpt8d0rtT+06e/GkGiwKwlW0Dve+yrPE41MVRNbrBwYQ==}
|
resolution: {integrity: sha512-wkwDd6YClqvZbfbwkFFAqAVLn6V3obzt90DE8dTdQSGV/ejBgO8uq1l1i3hBx6cNk18tM0rXsJUijgWacOiwBg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tmagic/design': 1.8.0-beta.14
|
'@tmagic/design': 1.8.0-beta.16
|
||||||
'@tmagic/form': 1.8.0-beta.14
|
'@tmagic/form': 1.8.0-beta.16
|
||||||
'@tmagic/utils': 1.8.0-beta.14
|
'@tmagic/utils': 1.8.0-beta.16
|
||||||
typescript: ^6.0.3
|
typescript: ^6.0.3
|
||||||
vue: ^3.5.40
|
vue: ^3.5.40
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tmagic/tdesign-vue-next-adapter@1.8.0-beta.14':
|
'@tmagic/tdesign-vue-next-adapter@1.8.0-beta.16':
|
||||||
resolution: {integrity: sha512-iKEvhPbPK8+QVWxXHTF6jHrbC433vUAm8cChaaT0zm0KEffw+XSpPpdGP2Xqx0Cmd64oDVW4yPY3ZQn3sbk2xA==}
|
resolution: {integrity: sha512-tjZxNtaGNuTmFs0d4ZSx7T6iY+a792FQoOBPtJj67+0jkCB+Rd4TrY06KUmjCQS3qfDXuCtrnTGaFralU84Dgg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tmagic/design': 1.8.0-beta.14
|
'@tmagic/design': 1.8.0-beta.16
|
||||||
tdesign-vue-next: ^1.17.1
|
tdesign-vue-next: ^1.17.1
|
||||||
typescript: ^6.0.3
|
typescript: ^6.0.3
|
||||||
vue: ^3.5.40
|
vue: ^3.5.40
|
||||||
@ -3346,11 +3346,11 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tmagic/utils@1.8.0-beta.14':
|
'@tmagic/utils@1.8.0-beta.16':
|
||||||
resolution: {integrity: sha512-yi5e6pNiSFU0IMc9vBRMBXjHcMrSoosVHCiwvxEHMK0VKJYcV+n408O39UEZIupOtBvl7d6Z91yk1hG0nTk38w==}
|
resolution: {integrity: sha512-A0ik+bHklhXUsTpFQaN6jhb42RJYYwX3tItkFfDApjnA2YjESaVCK3YaVIumVu49VayMKACHjCEK/2DkU44tGg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tmagic/schema': 1.8.0-beta.14
|
'@tmagic/schema': 1.8.0-beta.16
|
||||||
typescript: ^6.0.3
|
typescript: ^6.0.3
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
typescript:
|
typescript:
|
||||||
@ -8737,7 +8737,7 @@ snapshots:
|
|||||||
|
|
||||||
'@sxzz/popperjs-es@2.11.8': {}
|
'@sxzz/popperjs-es@2.11.8': {}
|
||||||
|
|
||||||
'@tmagic/cli@1.8.0-beta.14(typescript@6.0.3)':
|
'@tmagic/cli@1.8.0-beta.16(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
cac: 6.7.14
|
cac: 6.7.14
|
||||||
chokidar: 3.6.0
|
chokidar: 3.6.0
|
||||||
@ -8772,12 +8772,12 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tmagic/core@1.8.0-beta.14(typescript@6.0.3)':
|
'@tmagic/core@1.8.0-beta.16(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tmagic/data-source': 1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3)
|
'@tmagic/data-source': 1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3)
|
||||||
'@tmagic/dep': 1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(@tmagic/utils@1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)
|
'@tmagic/dep': 1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(@tmagic/utils@1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)
|
||||||
'@tmagic/schema': 1.8.0-beta.14(typescript@6.0.3)
|
'@tmagic/schema': 1.8.0-beta.16(typescript@6.0.3)
|
||||||
'@tmagic/utils': 1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3)
|
'@tmagic/utils': 1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3)
|
||||||
events: 3.3.0
|
events: 3.3.0
|
||||||
lodash-es: 4.18.1
|
lodash-es: 4.18.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@ -8801,9 +8801,9 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tmagic/data-source@1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3)':
|
'@tmagic/data-source@1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tmagic/core': 1.8.0-beta.14(typescript@6.0.3)
|
'@tmagic/core': 1.8.0-beta.16(typescript@6.0.3)
|
||||||
deep-state-observer: 5.5.14
|
deep-state-observer: 5.5.14
|
||||||
events: 3.3.0
|
events: 3.3.0
|
||||||
lodash-es: 4.18.1
|
lodash-es: 4.18.1
|
||||||
@ -8824,10 +8824,10 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tmagic/dep@1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(@tmagic/utils@1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)':
|
'@tmagic/dep@1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(@tmagic/utils@1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tmagic/schema': 1.8.0-beta.14(typescript@6.0.3)
|
'@tmagic/schema': 1.8.0-beta.16(typescript@6.0.3)
|
||||||
'@tmagic/utils': 1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3)
|
'@tmagic/utils': 1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
@ -8838,7 +8838,7 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tmagic/design@1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))':
|
'@tmagic/design@1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@popperjs/core': 2.11.8
|
'@popperjs/core': 2.11.8
|
||||||
vue: 3.5.40(typescript@6.0.3)
|
vue: 3.5.40(typescript@6.0.3)
|
||||||
@ -8872,15 +8872,15 @@ snapshots:
|
|||||||
- '@tmagic/form-schema'
|
- '@tmagic/form-schema'
|
||||||
- '@tmagic/schema'
|
- '@tmagic/schema'
|
||||||
|
|
||||||
'@tmagic/editor@1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(@tmagic/form-schema@1.8.0-beta.14(typescript@6.0.3))(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(monaco-editor@0.55.1)(type-fest@5.2.0)(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))':
|
'@tmagic/editor@1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(@tmagic/form-schema@1.8.0-beta.16(typescript@6.0.3))(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(monaco-editor@0.55.1)(type-fest@5.2.0)(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@element-plus/icons-vue': 2.3.2(vue@3.5.40(typescript@6.0.3))
|
'@element-plus/icons-vue': 2.3.2(vue@3.5.40(typescript@6.0.3))
|
||||||
'@tmagic/core': 1.8.0-beta.14(typescript@6.0.3)
|
'@tmagic/core': 1.8.0-beta.16(typescript@6.0.3)
|
||||||
'@tmagic/design': 1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
'@tmagic/design': 1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
||||||
'@tmagic/form': 1.8.0-beta.14(@tmagic/design@1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/form-schema@1.8.0-beta.14(typescript@6.0.3))(@tmagic/utils@1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
'@tmagic/form': 1.8.0-beta.16(@tmagic/design@1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/form-schema@1.8.0-beta.16(typescript@6.0.3))(@tmagic/utils@1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
||||||
'@tmagic/stage': 1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3)
|
'@tmagic/stage': 1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3)
|
||||||
'@tmagic/table': 1.8.0-beta.14(@tmagic/design@1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/form@1.8.0-beta.14(@tmagic/design@1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/form-schema@1.8.0-beta.14(typescript@6.0.3))(@tmagic/utils@1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/utils@1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
'@tmagic/table': 1.8.0-beta.16(@tmagic/design@1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/form@1.8.0-beta.16(@tmagic/design@1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/form-schema@1.8.0-beta.16(typescript@6.0.3))(@tmagic/utils@1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/utils@1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
||||||
'@tmagic/utils': 1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3)
|
'@tmagic/utils': 1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3)
|
||||||
buffer: 6.0.3
|
buffer: 6.0.3
|
||||||
deep-object-diff: 1.1.9
|
deep-object-diff: 1.1.9
|
||||||
emmet-monaco-es: 5.7.0(monaco-editor@0.55.1)
|
emmet-monaco-es: 5.7.0(monaco-editor@0.55.1)
|
||||||
@ -8900,9 +8900,9 @@ snapshots:
|
|||||||
- '@tmagic/form-schema'
|
- '@tmagic/form-schema'
|
||||||
- '@tmagic/schema'
|
- '@tmagic/schema'
|
||||||
|
|
||||||
'@tmagic/element-plus-adapter@1.8.0-beta.14(@tmagic/design@1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(element-plus@2.14.3(vue@3.5.40(typescript@6.0.3)))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))':
|
'@tmagic/element-plus-adapter@1.8.0-beta.16(@tmagic/design@1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(element-plus@2.14.3(vue@3.5.40(typescript@6.0.3)))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tmagic/design': 1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
'@tmagic/design': 1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
||||||
element-plus: 2.14.3(vue@3.5.40(typescript@6.0.3))
|
element-plus: 2.14.3(vue@3.5.40(typescript@6.0.3))
|
||||||
vue: 3.5.40(typescript@6.0.3)
|
vue: 3.5.40(typescript@6.0.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@ -8914,9 +8914,9 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tmagic/form-schema@1.8.0-beta.14(typescript@6.0.3)':
|
'@tmagic/form-schema@1.8.0-beta.16(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tmagic/schema': 1.8.0-beta.14(typescript@6.0.3)
|
'@tmagic/schema': 1.8.0-beta.16(typescript@6.0.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
@ -8934,13 +8934,13 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tmagic/form@1.8.0-beta.14(@tmagic/design@1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/form-schema@1.8.0-beta.14(typescript@6.0.3))(@tmagic/utils@1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))':
|
'@tmagic/form@1.8.0-beta.16(@tmagic/design@1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/form-schema@1.8.0-beta.16(typescript@6.0.3))(@tmagic/utils@1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@element-plus/icons-vue': 2.3.2(vue@3.5.40(typescript@6.0.3))
|
'@element-plus/icons-vue': 2.3.2(vue@3.5.40(typescript@6.0.3))
|
||||||
'@popperjs/core': 2.11.8
|
'@popperjs/core': 2.11.8
|
||||||
'@tmagic/design': 1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
'@tmagic/design': 1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
||||||
'@tmagic/form-schema': 1.8.0-beta.14(typescript@6.0.3)
|
'@tmagic/form-schema': 1.8.0-beta.16(typescript@6.0.3)
|
||||||
'@tmagic/utils': 1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3)
|
'@tmagic/utils': 1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3)
|
||||||
dayjs: 1.11.21
|
dayjs: 1.11.21
|
||||||
lodash-es: 4.18.1
|
lodash-es: 4.18.1
|
||||||
sortablejs: 1.15.6
|
sortablejs: 1.15.6
|
||||||
@ -8948,13 +8948,13 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tmagic/react-runtime-help@0.2.2(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(@tmagic/stage@1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3))(lodash-es@4.18.1)(react@18.3.1)(typescript@6.0.3)':
|
'@tmagic/react-runtime-help@0.2.2(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(@tmagic/stage@1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3))(lodash-es@4.18.1)(react@18.3.1)(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash-es: 4.18.1
|
lodash-es: 4.18.1
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@tmagic/core': 1.8.0-beta.14(typescript@6.0.3)
|
'@tmagic/core': 1.8.0-beta.16(typescript@6.0.3)
|
||||||
'@tmagic/stage': 1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3)
|
'@tmagic/stage': 1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3)
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tmagic/schema@1.7.0(typescript@6.0.3)':
|
'@tmagic/schema@1.7.0(typescript@6.0.3)':
|
||||||
@ -8965,7 +8965,7 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tmagic/schema@1.8.0-beta.14(typescript@6.0.3)':
|
'@tmagic/schema@1.8.0-beta.16(typescript@6.0.3)':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
@ -8995,10 +8995,10 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tmagic/stage@1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3)':
|
'@tmagic/stage@1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@scena/guides': 0.29.2
|
'@scena/guides': 0.29.2
|
||||||
'@tmagic/core': 1.8.0-beta.14(typescript@6.0.3)
|
'@tmagic/core': 1.8.0-beta.16(typescript@6.0.3)
|
||||||
'@zumer/snapdom': 2.8.0
|
'@zumer/snapdom': 2.8.0
|
||||||
events: 3.3.0
|
events: 3.3.0
|
||||||
keycon: 1.4.0
|
keycon: 1.4.0
|
||||||
@ -9018,32 +9018,32 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tmagic/table@1.8.0-beta.14(@tmagic/design@1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/form@1.8.0-beta.14(@tmagic/design@1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/form-schema@1.8.0-beta.14(typescript@6.0.3))(@tmagic/utils@1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/utils@1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))':
|
'@tmagic/table@1.8.0-beta.16(@tmagic/design@1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/form@1.8.0-beta.16(@tmagic/design@1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/form-schema@1.8.0-beta.16(typescript@6.0.3))(@tmagic/utils@1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/utils@1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@element-plus/icons-vue': 2.3.2(vue@3.5.40(typescript@6.0.3))
|
'@element-plus/icons-vue': 2.3.2(vue@3.5.40(typescript@6.0.3))
|
||||||
'@tmagic/design': 1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
'@tmagic/design': 1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
||||||
'@tmagic/form': 1.8.0-beta.14(@tmagic/design@1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/form-schema@1.8.0-beta.14(typescript@6.0.3))(@tmagic/utils@1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
'@tmagic/form': 1.8.0-beta.16(@tmagic/design@1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(@tmagic/form-schema@1.8.0-beta.16(typescript@6.0.3))(@tmagic/utils@1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
||||||
'@tmagic/utils': 1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3)
|
'@tmagic/utils': 1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3)
|
||||||
lodash-es: 4.18.1
|
lodash-es: 4.18.1
|
||||||
vue: 3.5.40(typescript@6.0.3)
|
vue: 3.5.40(typescript@6.0.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tmagic/tdesign-vue-next-adapter@1.8.0-beta.14(@tmagic/design@1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(tdesign-vue-next@1.17.3(vue@3.5.40(typescript@6.0.3)))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))':
|
'@tmagic/tdesign-vue-next-adapter@1.8.0-beta.16(@tmagic/design@1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(tdesign-vue-next@1.17.3(vue@3.5.40(typescript@6.0.3)))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tmagic/design': 1.8.0-beta.14(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
'@tmagic/design': 1.8.0-beta.16(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
||||||
tdesign-vue-next: 1.17.3(vue@3.5.40(typescript@6.0.3))
|
tdesign-vue-next: 1.17.3(vue@3.5.40(typescript@6.0.3))
|
||||||
vue: 3.5.40(typescript@6.0.3)
|
vue: 3.5.40(typescript@6.0.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tmagic/tmagic-form-runtime@1.1.3(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(@tmagic/editor@1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(@tmagic/form-schema@1.8.0-beta.14(typescript@6.0.3))(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(monaco-editor@0.55.1)(type-fest@5.2.0)(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(element-plus@2.14.3(vue@3.5.40(typescript@6.0.3)))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))':
|
'@tmagic/tmagic-form-runtime@1.1.3(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(@tmagic/editor@1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(@tmagic/form-schema@1.8.0-beta.16(typescript@6.0.3))(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(monaco-editor@0.55.1)(type-fest@5.2.0)(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3)))(element-plus@2.14.3(vue@3.5.40(typescript@6.0.3)))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tmagic/editor': 1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(@tmagic/form-schema@1.8.0-beta.14(typescript@6.0.3))(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(monaco-editor@0.55.1)(type-fest@5.2.0)(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
'@tmagic/editor': 1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(@tmagic/form-schema@1.8.0-beta.16(typescript@6.0.3))(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(monaco-editor@0.55.1)(type-fest@5.2.0)(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
|
||||||
element-plus: 2.14.3(vue@3.5.40(typescript@6.0.3))
|
element-plus: 2.14.3(vue@3.5.40(typescript@6.0.3))
|
||||||
vue: 3.5.40(typescript@6.0.3)
|
vue: 3.5.40(typescript@6.0.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@tmagic/core': 1.8.0-beta.14(typescript@6.0.3)
|
'@tmagic/core': 1.8.0-beta.16(typescript@6.0.3)
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tmagic/utils@1.7.0(@tmagic/schema@1.7.0(typescript@6.0.3))(typescript@6.0.3)':
|
'@tmagic/utils@1.7.0(@tmagic/schema@1.7.0(typescript@6.0.3))(typescript@6.0.3)':
|
||||||
@ -9060,19 +9060,19 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tmagic/utils@1.8.0-beta.14(@tmagic/schema@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3)':
|
'@tmagic/utils@1.8.0-beta.16(@tmagic/schema@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tmagic/schema': 1.8.0-beta.14(typescript@6.0.3)
|
'@tmagic/schema': 1.8.0-beta.16(typescript@6.0.3)
|
||||||
lodash-es: 4.18.1
|
lodash-es: 4.18.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tmagic/vue-runtime-help@2.0.4(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(@tmagic/stage@1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))':
|
'@tmagic/vue-runtime-help@2.0.4(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(@tmagic/stage@1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
vue: 3.5.40(typescript@6.0.3)
|
vue: 3.5.40(typescript@6.0.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@tmagic/core': 1.8.0-beta.14(typescript@6.0.3)
|
'@tmagic/core': 1.8.0-beta.16(typescript@6.0.3)
|
||||||
'@tmagic/stage': 1.8.0-beta.14(@tmagic/core@1.8.0-beta.14(typescript@6.0.3))(typescript@6.0.3)
|
'@tmagic/stage': 1.8.0-beta.16(@tmagic/core@1.8.0-beta.16(typescript@6.0.3))(typescript@6.0.3)
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@tybys/wasm-util@0.10.3':
|
'@tybys/wasm-util@0.10.3':
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "runtime-react",
|
"name": "runtime-react",
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -16,16 +16,16 @@
|
|||||||
"build:playground": "node scripts/build.mjs --type=playground"
|
"build:playground": "node scripts/build.mjs --type=playground"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tmagic/core": "1.8.0-beta.14",
|
"@tmagic/core": "1.8.0-beta.16",
|
||||||
"@tmagic/react-runtime-help": "0.2.2",
|
"@tmagic/react-runtime-help": "0.2.2",
|
||||||
"@tmagic/stage": "1.8.0-beta.14",
|
"@tmagic/stage": "1.8.0-beta.16",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"qrcode": "^1.5.0",
|
"qrcode": "^1.5.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1"
|
"react-dom": "^18.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tmagic/cli": "1.8.0-beta.14",
|
"@tmagic/cli": "1.8.0-beta.16",
|
||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "runtime-vue",
|
"name": "runtime-vue",
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.16",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -16,14 +16,14 @@
|
|||||||
"build:playground": "node scripts/build.mjs --type=playground"
|
"build:playground": "node scripts/build.mjs --type=playground"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tmagic/core": "1.8.0-beta.14",
|
"@tmagic/core": "1.8.0-beta.16",
|
||||||
"@tmagic/stage": "1.8.0-beta.14",
|
"@tmagic/stage": "1.8.0-beta.16",
|
||||||
"@tmagic/vue-runtime-help": "^2.0.4",
|
"@tmagic/vue-runtime-help": "^2.0.4",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"vue": "catalog:"
|
"vue": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tmagic/cli": "1.8.0-beta.14",
|
"@tmagic/cli": "1.8.0-beta.16",
|
||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/node": "^24.0.10",
|
"@types/node": "^24.0.10",
|
||||||
"@vitejs/plugin-legacy": "^8.0.1",
|
"@vitejs/plugin-legacy": "^8.0.1",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user