@xbeeant/form-engine-designer 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/dist/es/Canvas.js +42 -46
  2. package/dist/es/Canvas.js.map +1 -1
  3. package/dist/es/Designer.js +121 -98
  4. package/dist/es/Designer.js.map +1 -1
  5. package/dist/es/DesignerContext.js +86 -73
  6. package/dist/es/DesignerContext.js.map +1 -1
  7. package/dist/es/Palette.js +3 -3
  8. package/dist/es/Palette.js.map +1 -1
  9. package/dist/es/PropertyPanel.js +97 -97
  10. package/dist/es/PropertyPanel.js.map +1 -1
  11. package/dist/es/catalog.js +19 -11
  12. package/dist/es/catalog.js.map +1 -1
  13. package/dist/es/designer-context.js +110 -0
  14. package/dist/es/designer-context.js.map +1 -0
  15. package/dist/es/history.js +73 -0
  16. package/dist/es/history.js.map +1 -0
  17. package/dist/es/index.js +3 -3
  18. package/dist/es/property/basic-property.js +6 -0
  19. package/dist/es/property/basic-property.js.map +1 -1
  20. package/dist/es/property-panel.js +294 -0
  21. package/dist/es/property-panel.js.map +1 -0
  22. package/dist/es/resizable-panel.js +103 -0
  23. package/dist/es/resizable-panel.js.map +1 -0
  24. package/dist/es/schema-utils.js +286 -0
  25. package/dist/es/schema-utils.js.map +1 -0
  26. package/dist/es/widgets/BindEditor.js +60 -49
  27. package/dist/es/widgets/BindEditor.js.map +1 -1
  28. package/dist/es/widgets/CodeEditor.js +41 -49
  29. package/dist/es/widgets/CodeEditor.js.map +1 -1
  30. package/dist/es/widgets/DefaultEditor.js +25 -30
  31. package/dist/es/widgets/DefaultEditor.js.map +1 -1
  32. package/dist/es/widgets/DependenciesEditor.js +21 -21
  33. package/dist/es/widgets/DependenciesEditor.js.map +1 -1
  34. package/dist/es/widgets/EnumEditor.js +25 -31
  35. package/dist/es/widgets/EnumEditor.js.map +1 -1
  36. package/dist/es/widgets/ExpressionSwitch.js +32 -30
  37. package/dist/es/widgets/ExpressionSwitch.js.map +1 -1
  38. package/dist/es/widgets/ReactionsEditor.js +103 -103
  39. package/dist/es/widgets/ReactionsEditor.js.map +1 -1
  40. package/dist/es/widgets/RemoteDataEditor.js +63 -63
  41. package/dist/es/widgets/RemoteDataEditor.js.map +1 -1
  42. package/dist/es/widgets/ValidateEditor.js +49 -49
  43. package/dist/es/widgets/ValidateEditor.js.map +1 -1
  44. package/dist/es/widgets/bind-editor.js +78 -0
  45. package/dist/es/widgets/bind-editor.js.map +1 -0
  46. package/dist/es/widgets/code-editor.js +76 -0
  47. package/dist/es/widgets/code-editor.js.map +1 -0
  48. package/dist/es/widgets/default-editor.js +50 -0
  49. package/dist/es/widgets/default-editor.js.map +1 -0
  50. package/dist/es/widgets/dependencies-editor.js +36 -0
  51. package/dist/es/widgets/dependencies-editor.js.map +1 -0
  52. package/dist/es/widgets/enum-editor.js +43 -0
  53. package/dist/es/widgets/enum-editor.js.map +1 -0
  54. package/dist/es/widgets/expression-builder.js +367 -0
  55. package/dist/es/widgets/expression-builder.js.map +1 -0
  56. package/dist/es/widgets/expression-model.js +340 -0
  57. package/dist/es/widgets/expression-model.js.map +1 -0
  58. package/dist/es/widgets/expression-switch.js +49 -0
  59. package/dist/es/widgets/expression-switch.js.map +1 -0
  60. package/dist/es/widgets/index.js +13 -13
  61. package/dist/es/widgets/index.js.map +1 -1
  62. package/dist/es/widgets/options-editor.js +102 -0
  63. package/dist/es/widgets/options-editor.js.map +1 -0
  64. package/dist/es/widgets/reactions-editor.js +401 -0
  65. package/dist/es/widgets/reactions-editor.js.map +1 -0
  66. package/dist/es/widgets/reactions-model.js +107 -0
  67. package/dist/es/widgets/reactions-model.js.map +1 -0
  68. package/dist/es/widgets/remote-data-editor.js +314 -0
  69. package/dist/es/widgets/remote-data-editor.js.map +1 -0
  70. package/dist/es/widgets/use-form-data-fields.js +16 -0
  71. package/dist/es/widgets/use-form-data-fields.js.map +1 -0
  72. package/dist/es/widgets/validate-editor.js +118 -0
  73. package/dist/es/widgets/validate-editor.js.map +1 -0
  74. package/package.json +4 -5
  75. package/dist/es/react/src/components/FormController.js +0 -154
  76. package/dist/es/react/src/components/FormController.js.map +0 -1
  77. package/dist/es/react/src/components/NexusField.js +0 -99
  78. package/dist/es/react/src/components/NexusField.js.map +0 -1
  79. package/dist/es/react/src/components/NexusForm.js +0 -121
  80. package/dist/es/react/src/components/NexusForm.js.map +0 -1
  81. package/dist/es/react/src/components/NexusFormProvider.js +0 -19
  82. package/dist/es/react/src/components/NexusFormProvider.js.map +0 -1
  83. package/dist/es/react/src/components/NexusLayout.js +0 -49
  84. package/dist/es/react/src/components/NexusLayout.js.map +0 -1
  85. package/dist/es/react/src/components/NexusObject.js +0 -65
  86. package/dist/es/react/src/components/NexusObject.js.map +0 -1
  87. package/dist/es/react/src/contexts/FieldInheritContext.js +0 -6
  88. package/dist/es/react/src/contexts/FieldInheritContext.js.map +0 -1
  89. package/dist/es/react/src/contexts/GridContext.js +0 -6
  90. package/dist/es/react/src/contexts/GridContext.js.map +0 -1
  91. package/dist/es/react/src/contexts/LayoutConfigContext.js +0 -6
  92. package/dist/es/react/src/contexts/LayoutConfigContext.js.map +0 -1
  93. package/dist/es/react/src/contexts/NexusContext.js +0 -13
  94. package/dist/es/react/src/contexts/NexusContext.js.map +0 -1
  95. package/dist/es/react/src/hooks/useForm.js +0 -10
  96. package/dist/es/react/src/hooks/useForm.js.map +0 -1
  97. package/dist/es/react/src/utils/renderTreeNode.js +0 -18
  98. package/dist/es/react/src/utils/renderTreeNode.js.map +0 -1
  99. package/dist/es/react/src/utils/resolveColSpan.js +0 -10
  100. package/dist/es/react/src/utils/resolveColSpan.js.map +0 -1
  101. package/dist/es/schemaUtils.test.js +0 -188
  102. package/dist/es/schemaUtils.test.js.map +0 -1
  103. package/dist/es/vite-env.d.js +0 -2
  104. package/dist/es/vite-env.d.js.map +0 -1
  105. package/dist/es/widgets/expressionModel.test.js +0 -193
  106. package/dist/es/widgets/expressionModel.test.js.map +0 -1
  107. package/src/Canvas.tsx +0 -1111
  108. package/src/Designer.css +0 -206
  109. package/src/Designer.tsx +0 -254
  110. package/src/DesignerContext.tsx +0 -192
  111. package/src/Palette.tsx +0 -261
  112. package/src/PropertyPanel.tsx +0 -486
  113. package/src/ResizablePanel.tsx +0 -210
  114. package/src/catalog.ts +0 -526
  115. package/src/index.ts +0 -25
  116. package/src/property/basic-property.ts +0 -126
  117. package/src/schemaUtils.test.ts +0 -261
  118. package/src/schemaUtils.ts +0 -627
  119. package/src/types.ts +0 -38
  120. package/src/vite-env.d.ts +0 -1
  121. package/src/widgets/BindEditor.tsx +0 -105
  122. package/src/widgets/CodeEditor.tsx +0 -114
  123. package/src/widgets/DefaultEditor.tsx +0 -73
  124. package/src/widgets/DependenciesEditor.tsx +0 -56
  125. package/src/widgets/EnumEditor.tsx +0 -66
  126. package/src/widgets/ExpressionBuilder.tsx +0 -509
  127. package/src/widgets/ExpressionSwitch.tsx +0 -62
  128. package/src/widgets/OptionsEditor.tsx +0 -141
  129. package/src/widgets/ReactionsEditor.tsx +0 -532
  130. package/src/widgets/RemoteDataEditor.tsx +0 -382
  131. package/src/widgets/ValidateEditor.tsx +0 -162
  132. package/src/widgets/expressionModel.test.ts +0 -262
  133. package/src/widgets/expressionModel.ts +0 -573
  134. package/src/widgets/index.ts +0 -45
  135. package/src/widgets/reactionsModel.ts +0 -190
  136. package/src/widgets/useFormDataFields.ts +0 -21
  137. /package/dist/es/{form-engine-designer.css → index.css} +0 -0
@@ -1,6 +0,0 @@
1
- import { createContext as t } from "react";
2
- const e = t(null);
3
- export {
4
- e as GridContext
5
- };
6
- //# sourceMappingURL=GridContext.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"GridContext.js","sources":["../../../../../../react/src/contexts/GridContext.ts"],"sourcesContent":["import { createContext } from 'react';\n\n// 子项未显式设置 colSpan 时使用此默认值\nexport interface GridContextValue {\n /** 当前 grid 的列数(tailwind colSpan 基准) */\n column: number;\n}\nexport const GridContext = createContext<GridContextValue | null>(null);\n"],"names":["GridContext","createContext"],"mappings":";AAOO,MAAMA,IAAcC,EAAuC,IAAI;"}
@@ -1,6 +0,0 @@
1
- import { createContext as t } from "react";
2
- const e = t({});
3
- export {
4
- e as LayoutConfigContext
5
- };
6
- //# sourceMappingURL=LayoutConfigContext.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"LayoutConfigContext.js","sources":["../../../../../../react/src/contexts/LayoutConfigContext.ts"],"sourcesContent":["import { createContext } from 'react';\n\nexport interface LayoutConfigContextValue {\n removeHidden?: boolean;\n}\nexport const LayoutConfigContext = createContext<LayoutConfigContextValue>({});\n"],"names":["LayoutConfigContext","createContext"],"mappings":";AAKO,MAAMA,IAAsBC,EAAwC,CAAA,CAAE;"}
@@ -1,13 +0,0 @@
1
- import { useContext as t, createContext as o } from "react";
2
- const r = o(null);
3
- function u() {
4
- const e = t(r);
5
- if (!e)
6
- throw new Error("[NexusField] Must be used within <NexusFormProvider>");
7
- return e;
8
- }
9
- export {
10
- r as NexusContext,
11
- u as useNexusContext
12
- };
13
- //# sourceMappingURL=NexusContext.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"NexusContext.js","sources":["../../../../../../react/src/contexts/NexusContext.ts"],"sourcesContent":["import type { NexusEngine } from '@xbeeant/form-engine';\nimport { createContext, type ReactNode, useContext } from 'react';\n\nimport type { FormController } from '../components/FormController';\n\n// ────────────────────────────────────────────────────────────────────────────\n// Form 布局配置\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface NexusFormConfig {\n /** label 列配置(由 ui 层 Form.Item 消费) */\n labelCol?: Record<string, unknown>;\n /** label 宽度(px 或 %),快捷方式 — 映射到 labelCol.style.width */\n labelWidth?: number | string;\n /** 是否显示冒号 */\n colon?: boolean | ReactNode;\n /** 是否显示 label(默认 true) */\n label?: boolean;\n /** 表单布局方向 */\n displayType?: 'row' | 'column' | 'inline';\n /** 整个表单只读,所有字段以文本展示 */\n readOnly?: boolean;\n /** 表单每行显示多少列 */\n column?: number;\n}\n\ninterface NexusContextValue {\n engine: NexusEngine;\n config: NexusFormConfig;\n form: FormController;\n}\n\nexport const NexusContext = createContext<NexusContextValue | null>(null);\n\nexport function useNexusContext(): NexusContextValue {\n const ctx = useContext(NexusContext);\n if (!ctx) {\n throw new Error('[NexusField] Must be used within <NexusFormProvider>');\n }\n return ctx;\n}\n"],"names":["NexusContext","createContext","useNexusContext","ctx","useContext"],"mappings":";AAgCO,MAAMA,IAAeC,EAAwC,IAAI;AAEjE,SAASC,IAAqC;AACnD,QAAMC,IAAMC,EAAWJ,CAAY;AACnC,MAAI,CAACG;AACH,UAAM,IAAI,MAAM,sDAAsD;AAExE,SAAOA;AACT;"}
@@ -1,10 +0,0 @@
1
- import { useRef as e } from "react";
2
- import { FormController as n } from "../components/FormController.js";
3
- function f(o) {
4
- const r = e(null);
5
- return r.current || (r.current = new n(o)), [r.current];
6
- }
7
- export {
8
- f as useForm
9
- };
10
- //# sourceMappingURL=useForm.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useForm.js","sources":["../../../../../../react/src/hooks/useForm.ts"],"sourcesContent":["import type { NexusEngine } from '@xbeeant/form-engine';\nimport { useRef } from 'react';\n\nimport { FormController } from '../components/FormController';\n\n/**\n * useForm — 创建 Form 实例\n */\nexport function useForm(engine?: NexusEngine): [FormController] {\n const formRef = useRef<FormController | null>(null);\n if (!formRef.current) {\n formRef.current = new FormController(engine);\n }\n return [formRef.current];\n}\n"],"names":["useForm","engine","formRef","useRef","FormController"],"mappings":";;AAQO,SAASA,EAAQC,GAAwC;AAC9D,QAAMC,IAAUC,EAA8B,IAAI;AAClD,SAAKD,EAAQ,YACXA,EAAQ,UAAU,IAAIE,EAAeH,CAAM,IAEtC,CAACC,EAAQ,OAAO;AACzB;"}
@@ -1,18 +0,0 @@
1
- import { jsx as r } from "react/jsx-runtime";
2
- import { NexusField as e } from "../components/NexusField.js";
3
- import { NexusLayout as u } from "../components/NexusLayout.js";
4
- import { NexusObject as y } from "../components/NexusObject.js";
5
- function p(t, a) {
6
- return t.type === "field" ? /* @__PURE__ */ r(
7
- e,
8
- {
9
- dataPath: t.dataPath,
10
- layoutKey: t.layoutKey
11
- },
12
- t.layoutKey || t.dataPath
13
- ) : t.type === "object" ? /* @__PURE__ */ r(y, { node: t }, `object-${t.layoutKey}-${a}`) : /* @__PURE__ */ r(u, { node: t }, `layout-${t.type}-${a}`);
14
- }
15
- export {
16
- p as renderTreeNode
17
- };
18
- //# sourceMappingURL=renderTreeNode.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"renderTreeNode.js","sources":["../../../../../../react/src/utils/renderTreeNode.tsx"],"sourcesContent":["import type { RenderTreeNode } from '@xbeeant/form-engine';\nimport type { ReactElement } from 'react';\n\nimport { NexusField } from '../components/NexusField';\nimport { NexusLayout } from '../components/NexusLayout';\nimport { NexusObject } from '../components/NexusObject';\n\n/**\n * renderTreeNode — 递归渲染\n */\nexport function renderTreeNode(\n node: RenderTreeNode,\n index: number,\n): ReactElement {\n if (node.type === 'field') {\n return (\n <NexusField\n key={node.layoutKey || node.dataPath}\n dataPath={node.dataPath}\n layoutKey={node.layoutKey}\n />\n );\n }\n if (node.type === 'object') {\n return (\n <NexusObject key={`object-${node.layoutKey}-${index}`} node={node} />\n );\n }\n return <NexusLayout key={`layout-${node.type}-${index}`} node={node} />;\n}\n"],"names":["renderTreeNode","node","index","jsx","NexusField","NexusObject","NexusLayout"],"mappings":";;;;AAUO,SAASA,EACdC,GACAC,GACc;AACd,SAAID,EAAK,SAAS,UAEd,gBAAAE;AAAA,IAACC;AAAA,IAAA;AAAA,MAEC,UAAUH,EAAK;AAAA,MACf,WAAWA,EAAK;AAAA,IAAA;AAAA,IAFXA,EAAK,aAAaA,EAAK;AAAA,EAAA,IAM9BA,EAAK,SAAS,WAEd,gBAAAE,EAACE,KAAsD,MAAAJ,KAArC,UAAUA,EAAK,SAAS,IAAIC,CAAK,EAAgB,IAGhE,gBAAAC,EAACG,KAAiD,MAAAL,KAAhC,UAAUA,EAAK,IAAI,IAAIC,CAAK,EAAgB;AACvE;"}
@@ -1,10 +0,0 @@
1
- function u(o, n) {
2
- if (o !== void 0)
3
- return o;
4
- if (n && n.column > 0)
5
- return Math.max(1, Math.round(n.column / 24));
6
- }
7
- export {
8
- u as resolveColSpan
9
- };
10
- //# sourceMappingURL=resolveColSpan.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"resolveColSpan.js","sources":["../../../../../../react/src/utils/resolveColSpan.ts"],"sourcesContent":["import type { GridContextValue } from '../contexts/GridContext';\n\n/**\n * resolveColSpan — 统一解析子项在父 Grid 中的跨列数\n */\nexport function resolveColSpan(\n colSpan: number | undefined,\n gridCtx: GridContextValue | null,\n): number | undefined {\n if (colSpan !== undefined) {\n return colSpan;\n }\n\n // 在 grid 容器内:把 24 栅格语义缩放到当前 column\n if (gridCtx && gridCtx.column > 0) {\n return Math.max(1, Math.round(gridCtx.column / 24));\n }\n}\n"],"names":["resolveColSpan","colSpan","gridCtx"],"mappings":"AAKO,SAASA,EACdC,GACAC,GACoB;AACpB,MAAID,MAAY;AACd,WAAOA;AAIT,MAAIC,KAAWA,EAAQ,SAAS;AAC9B,WAAO,KAAK,IAAI,GAAG,KAAK,MAAMA,EAAQ,SAAS,EAAE,CAAC;AAEtD;"}
@@ -1,188 +0,0 @@
1
- import { NexusEngine as h } from "@xbeeant/form-engine";
2
- import { describe as s, it as n, expect as t } from "/Users/amybee/Codes/xbeeant/nexus-form-engine/node_modules/.bun/vitest@4.1.10+0a9a94c82e2ce81a/node_modules/vitest/dist/index.js";
3
- import { diffPropertyPatch as l, updateNodeWithNesting as r, addChildToSchema as m, flattenNodeForPropertyEditor as y, extractFormLevelConfig as c, collectDataFieldOptions as u } from "./schemaUtils.js";
4
- const p = {
5
- type: "string",
6
- widget: "radio",
7
- title: "单选",
8
- enum: ["a", "b"],
9
- enumNames: ["选项A", "选项B"]
10
- };
11
- s("diffPropertyPatch", () => {
12
- n("filters out empty-string defaults for untouched fields", () => {
13
- const e = l({
14
- title: "单选",
15
- options: [
16
- { value: "a", label: "选项A" },
17
- { value: "b", label: "选项B" }
18
- ]
19
- }, {
20
- title: "单选",
21
- description: "",
22
- placeholder: "",
23
- default: "",
24
- disabled: "",
25
- readOnly: "",
26
- hidden: "",
27
- required: "",
28
- pattern: "",
29
- bind: "",
30
- dependencies: "",
31
- options: [
32
- { value: "a", label: "选项A" },
33
- { value: "b", label: "选项B" }
34
- ]
35
- });
36
- t(e).toEqual({});
37
- }), n("keeps genuinely changed values", () => {
38
- const a = { title: "单选", options: [{ value: "a", label: "A" }] }, i = {
39
- title: "新标题",
40
- options: [
41
- { value: "a", label: "A" },
42
- { value: "b", label: "B" }
43
- ]
44
- }, e = l(a, i);
45
- t(e).toEqual(i);
46
- }), n("empty string is treated as unassigned and never written to schema", () => {
47
- const e = l({ title: "单选", description: "说明", bind: "user.name" }, { title: "单选", description: "", bind: "" });
48
- t(e).toEqual({});
49
- }), n("user-cleared field is written back as undefined for deletion", () => {
50
- const o = l({ title: "单选", variant: "outlined", description: "说明" }, { title: "单选", variant: "", description: "" }, /* @__PURE__ */ new Set(["variant"]));
51
- t(o).toEqual({ variant: void 0 });
52
- }), n("untouched empty fields stay skipped even with changedFields set", () => {
53
- const o = l({ title: "单选" }, { title: "单选", description: "", placeholder: "" }, /* @__PURE__ */ new Set(["title"]));
54
- t(o).toEqual({});
55
- });
56
- });
57
- s("updateNodeWithNesting deletion semantics", () => {
58
- n("cleared UI prop is removed from node.props instead of written as undefined", () => {
59
- const e = r({
60
- type: "object",
61
- properties: {
62
- card: {
63
- type: "card",
64
- title: "卡片",
65
- props: { variant: "outlined", hoverable: !0 },
66
- properties: {}
67
- }
68
- }
69
- }, ["card"], {
70
- variant: void 0
71
- }).properties.card;
72
- t(e.props.variant).toBeUndefined(), t("variant" in e.props).toBe(!1), t(e.props.hoverable).toBe(!0);
73
- }), n("cleared schema-level key is removed from the node", () => {
74
- const a = {
75
- type: "object",
76
- properties: {
77
- gender: { ...p, bind: "user.gender" }
78
- }
79
- }, e = r(a, ["gender"], { bind: void 0 }).properties.gender;
80
- t(e.bind).toBeUndefined(), t("bind" in e).toBe(!1), t(e.title).toBe("单选");
81
- }), n("widget display format is written into node.props (antd passthrough)", () => {
82
- const e = r({
83
- type: "object",
84
- properties: {
85
- birthday: { type: "string", widget: "date", title: "出生日期" }
86
- }
87
- }, ["birthday"], {
88
- format: "YYYY/MM/DD"
89
- }).properties.birthday;
90
- t(e.format).toBeUndefined(), t("format" in e).toBe(!1), t(e.props.format).toBe("YYYY/MM/DD");
91
- });
92
- });
93
- s("designer full property-panel patch", () => {
94
- n("empty defaults are not written, radio key preserved in formData", () => {
95
- let a = { type: "object", properties: {} };
96
- a = m(a, [], "gender", p);
97
- const i = y(
98
- a.properties.gender
99
- ), o = l(i, {
100
- title: "单选",
101
- description: "",
102
- placeholder: "",
103
- default: "",
104
- extra: "",
105
- disabled: "",
106
- readOnly: "",
107
- readOnlyWidget: "",
108
- hidden: "",
109
- width: "",
110
- options: [
111
- { value: "a", label: "选项A" },
112
- { value: "b", label: "选项B" }
113
- ],
114
- optionType: "",
115
- buttonStyle: "",
116
- required: "",
117
- pattern: "",
118
- validate: "",
119
- bind: "",
120
- dependencies: ""
121
- });
122
- t(o.bind).toBeUndefined(), a = r(a, ["gender"], o);
123
- const d = new h();
124
- d.init(a), d.setFieldValue("gender", "a"), t(d.getFormData()).toEqual({ gender: "a" });
125
- });
126
- });
127
- s("extractFormLevelConfig", () => {
128
- n("only extracts top-level form config keys, not the whole schema", () => {
129
- const e = c(
130
- {
131
- type: "object",
132
- displayType: "row",
133
- colon: !0,
134
- properties: { gender: p }
135
- },
136
- [
137
- "displayType",
138
- "labelWidth",
139
- "colon",
140
- "label",
141
- "readOnly",
142
- "column"
143
- ]
144
- );
145
- t(e).toEqual({ displayType: "row", colon: !0 }), t("properties" in e).toBe(!1), t("type" in e).toBe(!1);
146
- }), n("omits form-level keys that are not set on the schema", () => {
147
- const i = c(
148
- { type: "object", properties: {} },
149
- ["labelWidth", "column"]
150
- );
151
- t(i).toEqual({});
152
- });
153
- });
154
- s("collectDataFieldOptions", () => {
155
- n("labels options with field title, values with path key", () => {
156
- const i = u({
157
- properties: {
158
- user: {
159
- type: "object",
160
- properties: {
161
- name: { type: "string", title: "姓名" },
162
- gender: { type: "string", widget: "radio", title: "性别" }
163
- }
164
- },
165
- tags: {
166
- type: "array",
167
- widget: "list",
168
- items: { type: "string", widget: "input", title: "标签项" }
169
- }
170
- }
171
- });
172
- t(i).toEqual([
173
- { value: "user.name", label: "姓名" },
174
- { value: "user.gender", label: "性别" },
175
- { value: "tags", label: "tags" },
176
- { value: "tags[0]", label: "标签项" }
177
- ]);
178
- }), n("falls back to key when title is missing", () => {
179
- t(u({
180
- properties: {
181
- birthday: { type: "string", widget: "date" }
182
- }
183
- })).toEqual([
184
- { value: "birthday", label: "birthday" }
185
- ]);
186
- });
187
- });
188
- //# sourceMappingURL=schemaUtils.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"schemaUtils.test.js","sources":["../../src/schemaUtils.test.ts"],"sourcesContent":["import type { NexusSchema, SchemaNode } from '@xbeeant/form-engine';\nimport { NexusEngine } from '@xbeeant/form-engine';\nimport { describe, expect, it } from 'vitest';\nimport {\n addChildToSchema,\n collectDataFieldOptions,\n diffPropertyPatch,\n extractFormLevelConfig,\n flattenNodeForPropertyEditor,\n updateNodeWithNesting,\n} from './schemaUtils';\n\nconst radioNode: SchemaNode = {\n type: 'string',\n widget: 'radio',\n title: '单选',\n enum: ['a', 'b'],\n enumNames: ['选项A', '选项B'],\n};\n\ndescribe('diffPropertyPatch', () => {\n it('filters out empty-string defaults for untouched fields', () => {\n const initial = {\n title: '单选',\n options: [\n { value: 'a', label: '选项A' },\n { value: 'b', label: '选项B' },\n ],\n };\n const allValues = {\n title: '单选',\n description: '',\n placeholder: '',\n default: '',\n disabled: '',\n readOnly: '',\n hidden: '',\n required: '',\n pattern: '',\n bind: '',\n dependencies: '',\n options: [\n { value: 'a', label: '选项A' },\n { value: 'b', label: '选项B' },\n ],\n };\n const patch = diffPropertyPatch(initial, allValues);\n expect(patch).toEqual({});\n });\n\n it('keeps genuinely changed values', () => {\n const initial = { title: '单选', options: [{ value: 'a', label: 'A' }] };\n const allValues = {\n title: '新标题',\n options: [\n { value: 'a', label: 'A' },\n { value: 'b', label: 'B' },\n ],\n };\n const patch = diffPropertyPatch(initial, allValues);\n expect(patch).toEqual(allValues);\n });\n\n it('empty string is treated as unassigned and never written to schema', () => {\n const initial = { title: '单选', description: '说明', bind: 'user.name' };\n const allValues = { title: '单选', description: '', bind: '' };\n const patch = diffPropertyPatch(initial, allValues);\n expect(patch).toEqual({});\n });\n\n it('user-cleared field is written back as undefined for deletion', () => {\n const initial = { title: '单选', variant: 'outlined', description: '说明' };\n const allValues = { title: '单选', variant: '', description: '' };\n const changed = new Set(['variant']);\n const patch = diffPropertyPatch(initial, allValues, changed);\n expect(patch).toEqual({ variant: undefined });\n });\n\n it('untouched empty fields stay skipped even with changedFields set', () => {\n const initial = { title: '单选' };\n const allValues = { title: '单选', description: '', placeholder: '' };\n const changed = new Set(['title']);\n const patch = diffPropertyPatch(initial, allValues, changed);\n expect(patch).toEqual({});\n });\n});\n\ndescribe('updateNodeWithNesting deletion semantics', () => {\n it('cleared UI prop is removed from node.props instead of written as undefined', () => {\n const schema: NexusSchema = {\n type: 'object',\n properties: {\n card: {\n type: 'card',\n title: '卡片',\n props: { variant: 'outlined', hoverable: true },\n properties: {},\n },\n },\n };\n const next = updateNodeWithNesting(schema, ['card'], {\n variant: undefined,\n });\n const node = next.properties.card as Record<string, unknown>;\n expect((node.props as Record<string, unknown>).variant).toBeUndefined();\n expect('variant' in (node.props as Record<string, unknown>)).toBe(false);\n expect((node.props as Record<string, unknown>).hoverable).toBe(true);\n });\n\n it('cleared schema-level key is removed from the node', () => {\n const schema: NexusSchema = {\n type: 'object',\n properties: {\n gender: { ...radioNode, bind: 'user.gender' },\n },\n };\n const next = updateNodeWithNesting(schema, ['gender'], { bind: undefined });\n const node = next.properties.gender as Record<string, unknown>;\n expect(node.bind).toBeUndefined();\n expect('bind' in node).toBe(false);\n expect(node.title).toBe('单选');\n });\n\n it('widget display format is written into node.props (antd passthrough)', () => {\n // date 等控件的 format 是显示格式,必须走 props 通道才能被 widget 消费;\n // 顶层 node.format 保留给 JSON Schema 校验语义(email/url)\n const schema: NexusSchema = {\n type: 'object',\n properties: {\n birthday: { type: 'string', widget: 'date', title: '出生日期' },\n },\n };\n const next = updateNodeWithNesting(schema, ['birthday'], {\n format: 'YYYY/MM/DD',\n });\n const node = next.properties.birthday as Record<string, unknown>;\n expect(node.format).toBeUndefined();\n expect('format' in node).toBe(false);\n expect((node.props as Record<string, unknown>).format).toBe('YYYY/MM/DD');\n });\n});\n\ndescribe('designer full property-panel patch', () => {\n it('empty defaults are not written, radio key preserved in formData', () => {\n let schema: NexusSchema = { type: 'object', properties: {} };\n schema = addChildToSchema(schema, [], 'gender', radioNode);\n\n // simulate property form allValues (includes empty-string defaults) + diff filter\n const initial = flattenNodeForPropertyEditor(\n schema.properties.gender as never,\n );\n const allValues = {\n title: '单选',\n description: '',\n placeholder: '',\n default: '',\n extra: '',\n disabled: '',\n readOnly: '',\n readOnlyWidget: '',\n hidden: '',\n width: '',\n options: [\n { value: 'a', label: '选项A' },\n { value: 'b', label: '选项B' },\n ],\n optionType: '',\n buttonStyle: '',\n required: '',\n pattern: '',\n validate: '',\n bind: '',\n dependencies: '',\n };\n const patch = diffPropertyPatch(initial, allValues);\n expect(patch.bind).toBeUndefined();\n\n schema = updateNodeWithNesting(schema, ['gender'], patch);\n\n const engine = new NexusEngine();\n engine.init(schema);\n engine.setFieldValue('gender', 'a');\n expect(engine.getFormData()).toEqual({ gender: 'a' });\n });\n});\n\ndescribe('extractFormLevelConfig', () => {\n it('only extracts top-level form config keys, not the whole schema', () => {\n const schema: NexusSchema = {\n type: 'object',\n displayType: 'row',\n colon: true,\n properties: { gender: radioNode },\n };\n const keys = [\n 'displayType',\n 'labelWidth',\n 'colon',\n 'label',\n 'readOnly',\n 'column',\n ];\n const config = extractFormLevelConfig(\n schema as unknown as Record<string, unknown>,\n keys,\n );\n expect(config).toEqual({ displayType: 'row', colon: true });\n expect('properties' in config).toBe(false);\n expect('type' in config).toBe(false);\n });\n\n it('omits form-level keys that are not set on the schema', () => {\n const schema: NexusSchema = { type: 'object', properties: {} };\n const config = extractFormLevelConfig(\n schema as unknown as Record<string, unknown>,\n ['labelWidth', 'column'],\n );\n expect(config).toEqual({});\n });\n});\n\ndescribe('collectDataFieldOptions', () => {\n it('labels options with field title, values with path key', () => {\n const schema: NexusSchema = {\n type: 'object',\n properties: {\n user: {\n type: 'object',\n properties: {\n name: { type: 'string', title: '姓名' },\n gender: { type: 'string', widget: 'radio', title: '性别' },\n },\n },\n tags: {\n type: 'array',\n widget: 'list',\n items: { type: 'string', widget: 'input', title: '标签项' },\n },\n },\n };\n const options = collectDataFieldOptions(schema);\n expect(options).toEqual([\n { value: 'user.name', label: '姓名' },\n { value: 'user.gender', label: '性别' },\n { value: 'tags', label: 'tags' },\n { value: 'tags[0]', label: '标签项' },\n ]);\n });\n\n it('falls back to key when title is missing', () => {\n const schema: NexusSchema = {\n type: 'object',\n properties: {\n birthday: { type: 'string', widget: 'date' },\n },\n };\n expect(collectDataFieldOptions(schema)).toEqual([\n { value: 'birthday', label: 'birthday' },\n ]);\n });\n});\n"],"names":["radioNode","describe","it","patch","diffPropertyPatch","expect","initial","allValues","node","updateNodeWithNesting","schema","addChildToSchema","flattenNodeForPropertyEditor","engine","NexusEngine","config","extractFormLevelConfig","options","collectDataFieldOptions"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
-
2
- //# sourceMappingURL=vite-env.d.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"vite-env.d.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -1,193 +0,0 @@
1
- import { describe as c, it as o, expect as t } from "/Users/amybee/Codes/xbeeant/nexus-form-engine/node_modules/.bun/vitest@4.1.10+0a9a94c82e2ce81a/node_modules/vitest/dist/index.js";
2
- import { collectDataFieldPaths as n } from "../schemaUtils.js";
3
- import { buildExpression as e, parseExpression as i, stripBraces as p, variableToString as s, parseVariable as d } from "./expressionModel.js";
4
- c("buildExpression", () => {
5
- o("构建基本比较表达式(文本 / 数字 / 布尔 / 变量右值)", () => {
6
- t(
7
- e([
8
- {
9
- id: "1",
10
- logic: "and",
11
- variable: { source: "formData", path: "usePromo" },
12
- operator: "eq",
13
- rightMode: "boolean",
14
- right: "true"
15
- }
16
- ])
17
- ).toBe("{{ formData.usePromo === true }}"), t(
18
- e([
19
- {
20
- id: "1",
21
- logic: "and",
22
- variable: { source: "formData", path: "amount" },
23
- operator: "gte",
24
- rightMode: "number",
25
- right: "100"
26
- }
27
- ])
28
- ).toBe("{{ formData.amount >= 100 }}"), t(
29
- e([
30
- {
31
- id: "1",
32
- logic: "and",
33
- variable: { source: "self", path: "" },
34
- operator: "neq",
35
- rightMode: "string",
36
- right: "special"
37
- }
38
- ])
39
- ).toBe("{{ $self.value !== 'special' }}"), t(
40
- e([
41
- {
42
- id: "1",
43
- logic: "and",
44
- variable: { source: "formData", path: "amount" },
45
- operator: "eq",
46
- rightMode: "variable",
47
- right: "formData.limit"
48
- }
49
- ])
50
- ).toBe("{{ formData.amount === formData.limit }}");
51
- }), o("空右值 / 未选变量视为不完整条件,返回空模板", () => {
52
- t(
53
- e([
54
- {
55
- id: "1",
56
- logic: "and",
57
- variable: { source: "formData", path: "" },
58
- operator: "eq",
59
- rightMode: "string",
60
- right: "x"
61
- }
62
- ])
63
- ).toBe("{{ }}"), t(
64
- e([
65
- {
66
- id: "1",
67
- logic: "and",
68
- variable: { source: "formData", path: "a" },
69
- operator: "eq",
70
- rightMode: "string",
71
- right: ""
72
- }
73
- ])
74
- ).toBe("{{ }}");
75
- }), o("无条件时返回空模板", () => {
76
- t(e([])).toBe("{{ }}");
77
- }), o("按 and/or 连接多条条件", () => {
78
- const a = (u, m) => ({
79
- id: Math.random().toString(),
80
- logic: "and",
81
- variable: { source: "formData", path: "a" },
82
- operator: u,
83
- rightMode: "string",
84
- right: m
85
- }), r = { ...a("eq", "x"), id: "1" }, l = { ...a("neq", "y"), id: "2", logic: "or" };
86
- t(e([r, l])).toBe(
87
- "{{ formData.a === 'x' || formData.a !== 'y' }}"
88
- );
89
- }), o("contains / startsWith / empty / truthy 等操作符", () => {
90
- const a = (r, l = "") => ({
91
- id: "1",
92
- logic: "and",
93
- variable: { source: "formData", path: "code" },
94
- operator: r,
95
- rightMode: "string",
96
- right: l
97
- });
98
- t(e([a("contains", "CN")])).toBe(
99
- "{{ String(formData.code).includes('CN') }}"
100
- ), t(e([a("notEmpty")])).toBe(
101
- "{{ (formData.code != null && String(formData.code).trim() !== '') }}"
102
- ), t(e([a("truthy")])).toBe("{{ !!(formData.code) }}");
103
- });
104
- });
105
- c("parseExpression", () => {
106
- o("解析基本表达式为行模型", () => {
107
- const a = i("{{ formData.usePromo === true }}");
108
- t(a).not.toBeNull(), t(a[0]).toMatchObject({
109
- variable: { source: "formData", path: "usePromo" },
110
- operator: "eq",
111
- rightMode: "boolean",
112
- right: "true"
113
- });
114
- }), o("解析 $deps / $self / 文本字面量", () => {
115
- const a = i("{{ $deps[0] === true }}");
116
- t(a[0].variable).toEqual({ source: "deps", path: "0" });
117
- const r = i("{{ $self.value === 'x' }}");
118
- t(r[0].variable).toEqual({ source: "self", path: "" }), t(r[0].rightMode).toBe("string"), t(r[0].right).toBe("x");
119
- }), o("解析 and/or 连接", () => {
120
- const a = i(
121
- "{{ formData.a === 'x' && formData.b > 3 || formData.c == 'y' }}"
122
- );
123
- t(a.length).toBe(3), t(a[0].logic).toBe("and"), t(a[1].logic).toBe("and"), t(a[2].logic).toBe("or");
124
- }), o("解析空模板 / 空串为 []", () => {
125
- t(i("{{ }}")).toEqual([]), t(i("")).toEqual([]), t(i(void 0)).toEqual([]);
126
- }), o("无法解析的表达式返回 null(回退高级模式)", () => {
127
- t(i("{{ formData.a.toUpperCase() }}")).toBeNull(), t(i("{{ $deps[0] + 1 > 2 }}")).toBeNull();
128
- }), o("round-trip:build → parse → build 保持一致", () => {
129
- const a = "{{ formData.a === 'x' || formData.b >= 3 }}", r = i(a);
130
- t(r.length).toBe(2);
131
- const l = e(r);
132
- t(l).toBe(a);
133
- });
134
- });
135
- c("变量工具", () => {
136
- o("stripBraces 去除包裹", () => {
137
- t(p("{{ a === 1 }}")).toBe("a === 1"), t(p("a === 1")).toBe("a === 1");
138
- }), o("variableToString 各来源", () => {
139
- t(s({ source: "formData", path: "user.name" })).toBe(
140
- "formData.user.name"
141
- ), t(s({ source: "deps", path: "2" })).toBe("$deps[2]"), t(s({ source: "self", path: "" })).toBe("$self.value"), t(s({ source: "index", path: "" })).toBe("$index"), t(s({ source: "root", path: "" })).toBe("rootValue");
142
- }), o("parseVariable 非标识符字段用括号访问", () => {
143
- t(d("formData['a-b']")).toEqual({
144
- source: "formData",
145
- path: "a-b"
146
- }), t(s(d("formData['a-b']"))).toBe(
147
- "formData['a-b']"
148
- );
149
- });
150
- });
151
- c("collectDataFieldPaths", () => {
152
- o("布局节点 Key 不进路径,对象 / 数组透传", () => {
153
- t(n({
154
- properties: {
155
- card: {
156
- type: "card",
157
- title: "卡片",
158
- properties: {
159
- name: { type: "string", widget: "input", title: "姓名" },
160
- address: {
161
- type: "object",
162
- properties: {
163
- city: { type: "string", widget: "input" }
164
- }
165
- },
166
- tags: {
167
- type: "array",
168
- widget: "list",
169
- items: {
170
- type: "object",
171
- properties: {
172
- label: { type: "string", widget: "input" }
173
- }
174
- }
175
- }
176
- }
177
- }
178
- }
179
- })).toEqual([
180
- "name",
181
- "address.city",
182
- "tags",
183
- "tags[0].label"
184
- ]);
185
- }), o("子表单(带 widget 的 object)视为叶子字段", () => {
186
- t(n({
187
- properties: {
188
- sub: { type: "object", widget: "subForm", properties: {} }
189
- }
190
- })).toEqual(["sub"]);
191
- });
192
- });
193
- //# sourceMappingURL=expressionModel.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"expressionModel.test.js","sources":["../../../src/widgets/expressionModel.test.ts"],"sourcesContent":["import type { NexusSchema } from '@xbeeant/form-engine';\nimport { describe, expect, it } from 'vitest';\nimport { collectDataFieldPaths } from '../schemaUtils';\nimport {\n buildExpression,\n type ConditionOperator,\n parseExpression,\n parseVariable,\n stripBraces,\n variableToString,\n} from './expressionModel';\n\ndescribe('buildExpression', () => {\n it('构建基本比较表达式(文本 / 数字 / 布尔 / 变量右值)', () => {\n expect(\n buildExpression([\n {\n id: '1',\n logic: 'and',\n variable: { source: 'formData', path: 'usePromo' },\n operator: 'eq',\n rightMode: 'boolean',\n right: 'true',\n },\n ]),\n ).toBe('{{ formData.usePromo === true }}');\n\n expect(\n buildExpression([\n {\n id: '1',\n logic: 'and',\n variable: { source: 'formData', path: 'amount' },\n operator: 'gte',\n rightMode: 'number',\n right: '100',\n },\n ]),\n ).toBe('{{ formData.amount >= 100 }}');\n\n expect(\n buildExpression([\n {\n id: '1',\n logic: 'and',\n variable: { source: 'self', path: '' },\n operator: 'neq',\n rightMode: 'string',\n right: 'special',\n },\n ]),\n ).toBe(\"{{ $self.value !== 'special' }}\");\n\n expect(\n buildExpression([\n {\n id: '1',\n logic: 'and',\n variable: { source: 'formData', path: 'amount' },\n operator: 'eq',\n rightMode: 'variable',\n right: 'formData.limit',\n },\n ]),\n ).toBe('{{ formData.amount === formData.limit }}');\n });\n\n it('空右值 / 未选变量视为不完整条件,返回空模板', () => {\n expect(\n buildExpression([\n {\n id: '1',\n logic: 'and',\n variable: { source: 'formData', path: '' },\n operator: 'eq',\n rightMode: 'string',\n right: 'x',\n },\n ]),\n ).toBe('{{ }}');\n\n expect(\n buildExpression([\n {\n id: '1',\n logic: 'and',\n variable: { source: 'formData', path: 'a' },\n operator: 'eq',\n rightMode: 'string',\n right: '',\n },\n ]),\n ).toBe('{{ }}');\n });\n\n it('无条件时返回空模板', () => {\n expect(buildExpression([])).toBe('{{ }}');\n });\n\n it('按 and/or 连接多条条件', () => {\n const cond = (op: 'eq' | 'neq', right: string) => ({\n id: Math.random().toString(),\n logic: 'and' as const,\n variable: { source: 'formData' as const, path: 'a' },\n operator: op,\n rightMode: 'string' as const,\n right,\n });\n const c1 = { ...cond('eq', 'x'), id: '1' };\n const c2 = { ...cond('neq', 'y'), id: '2', logic: 'or' as const };\n expect(buildExpression([c1, c2])).toBe(\n \"{{ formData.a === 'x' || formData.a !== 'y' }}\",\n );\n });\n\n it('contains / startsWith / empty / truthy 等操作符', () => {\n const base = (operator: ConditionOperator, right = '') => ({\n id: '1',\n logic: 'and' as const,\n variable: { source: 'formData' as const, path: 'code' },\n operator,\n rightMode: 'string' as const,\n right,\n });\n expect(buildExpression([base('contains', 'CN')])).toBe(\n \"{{ String(formData.code).includes('CN') }}\",\n );\n expect(buildExpression([base('notEmpty')])).toBe(\n \"{{ (formData.code != null && String(formData.code).trim() !== '') }}\",\n );\n expect(buildExpression([base('truthy')])).toBe('{{ !!(formData.code) }}');\n });\n});\n\ndescribe('parseExpression', () => {\n it('解析基本表达式为行模型', () => {\n const conds = parseExpression('{{ formData.usePromo === true }}');\n expect(conds).not.toBeNull();\n expect(conds![0]).toMatchObject({\n variable: { source: 'formData', path: 'usePromo' },\n operator: 'eq',\n rightMode: 'boolean',\n right: 'true',\n });\n });\n\n it('解析 $deps / $self / 文本字面量', () => {\n const conds = parseExpression('{{ $deps[0] === true }}');\n expect(conds![0].variable).toEqual({ source: 'deps', path: '0' });\n\n const self = parseExpression(\"{{ $self.value === 'x' }}\");\n expect(self![0].variable).toEqual({ source: 'self', path: '' });\n expect(self![0].rightMode).toBe('string');\n expect(self![0].right).toBe('x');\n });\n\n it('解析 and/or 连接', () => {\n const conds = parseExpression(\n \"{{ formData.a === 'x' && formData.b > 3 || formData.c == 'y' }}\",\n );\n expect(conds!.length).toBe(3);\n expect(conds![0].logic).toBe('and');\n expect(conds![1].logic).toBe('and');\n expect(conds![2].logic).toBe('or');\n });\n\n it('解析空模板 / 空串为 []', () => {\n expect(parseExpression('{{ }}')).toEqual([]);\n expect(parseExpression('')).toEqual([]);\n expect(parseExpression(undefined)).toEqual([]);\n });\n\n it('无法解析的表达式返回 null(回退高级模式)', () => {\n expect(parseExpression('{{ formData.a.toUpperCase() }}')).toBeNull();\n expect(parseExpression('{{ $deps[0] + 1 > 2 }}')).toBeNull();\n });\n\n it('round-trip:build → parse → build 保持一致', () => {\n const src = \"{{ formData.a === 'x' || formData.b >= 3 }}\";\n const conds = parseExpression(src)!;\n expect(conds.length).toBe(2);\n const rebuilt = buildExpression(conds);\n expect(rebuilt).toBe(src);\n });\n});\n\ndescribe('变量工具', () => {\n it('stripBraces 去除包裹', () => {\n expect(stripBraces('{{ a === 1 }}')).toBe('a === 1');\n expect(stripBraces('a === 1')).toBe('a === 1');\n });\n\n it('variableToString 各来源', () => {\n expect(variableToString({ source: 'formData', path: 'user.name' })).toBe(\n 'formData.user.name',\n );\n expect(variableToString({ source: 'deps', path: '2' })).toBe('$deps[2]');\n expect(variableToString({ source: 'self', path: '' })).toBe('$self.value');\n expect(variableToString({ source: 'index', path: '' })).toBe('$index');\n expect(variableToString({ source: 'root', path: '' })).toBe('rootValue');\n });\n\n it('parseVariable 非标识符字段用括号访问', () => {\n expect(parseVariable(\"formData['a-b']\")).toEqual({\n source: 'formData',\n path: 'a-b',\n });\n expect(variableToString(parseVariable(\"formData['a-b']\"))).toBe(\n \"formData['a-b']\",\n );\n });\n});\n\ndescribe('collectDataFieldPaths', () => {\n it('布局节点 Key 不进路径,对象 / 数组透传', () => {\n const schema = {\n type: 'object',\n properties: {\n card: {\n type: 'card',\n title: '卡片',\n properties: {\n name: { type: 'string', widget: 'input', title: '姓名' },\n address: {\n type: 'object',\n properties: {\n city: { type: 'string', widget: 'input' },\n },\n },\n tags: {\n type: 'array',\n widget: 'list',\n items: {\n type: 'object',\n properties: {\n label: { type: 'string', widget: 'input' },\n },\n },\n },\n },\n },\n },\n } as unknown as NexusSchema;\n\n expect(collectDataFieldPaths(schema)).toEqual([\n 'name',\n 'address.city',\n 'tags',\n 'tags[0].label',\n ]);\n });\n\n it('子表单(带 widget 的 object)视为叶子字段', () => {\n const schema = {\n type: 'object',\n properties: {\n sub: { type: 'object', widget: 'subForm', properties: {} },\n },\n } as unknown as NexusSchema;\n expect(collectDataFieldPaths(schema)).toEqual(['sub']);\n });\n});\n"],"names":["describe","it","expect","buildExpression","cond","op","right","c1","c2","base","operator","conds","parseExpression","self","src","rebuilt","stripBraces","variableToString","parseVariable","collectDataFieldPaths"],"mappings":"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"}