@tmagic/form 1.8.0-beta.26 → 1.8.0-beta.27

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.
@@ -199,7 +199,10 @@ var Select_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCom
199
199
  prop: props.prop,
200
200
  postOptions
201
201
  });
202
- let initData = getValueByKeyPath(initRoot || root, res);
202
+ let initData;
203
+ try {
204
+ initData = getValueByKeyPath(initRoot || root, res);
205
+ } catch {}
203
206
  if (initData) {
204
207
  if (!Array.isArray(initData)) initData = [initData];
205
208
  if (typeof option.item === "function") options = option.item(initData);
@@ -230,8 +233,12 @@ var Select_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCom
230
233
  const v = props.model[props.name];
231
234
  if (Array.isArray(v) ? !v.length : typeof v === "undefined") return;
232
235
  if (typeof v === "undefined" || hasOption(v)) return;
233
- const data = await getInitOption();
234
- setOptions(data);
236
+ try {
237
+ const data = await getInitOption();
238
+ setOptions(data);
239
+ } catch {
240
+ setOptions([]);
241
+ }
235
242
  };
236
243
  watch(() => props.model?.[props.name], () => {
237
244
  initOption();
@@ -326,7 +333,7 @@ var Select_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCom
326
333
  }), 128)) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(options.value, (option) => {
327
334
  return openBlock(), createBlock(resolveDynamicComponent(unref(optionComponent)?.component || "el-option"), mergeProps({
328
335
  class: "tmagic-design-option",
329
- key: __props.config.valueKey ? option.value[__props.config.valueKey] : option.value
336
+ key: __props.config.valueKey ? option.value?.[__props.config.valueKey] : option.value
330
337
  }, { ref_for: true }, unref(optionComponent)?.props({
331
338
  label: option.text,
332
339
  value: option.value,
@@ -30,6 +30,36 @@ var mergeFormContexts = (...layers) => {
30
30
  });
31
31
  };
32
32
  /**
33
+ * 存量下发配置沿用 Vue2 时代的写法,把回调第一个参数当组件实例用,靠 `vm.mForm.xxx`
34
+ * 取表单状态。这类配置除了读,还会往上面挂方法做跨字段通信,例如:
35
+ *
36
+ * ```js
37
+ * vm.mForm.checkPropertyLimit = async (...) => { ... } // 一个字段的 validator 里挂
38
+ * await vm.mForm.checkPropertyLimit(...) // 另一处再取出来调用
39
+ * ```
40
+ *
41
+ * 所以 `mForm` 必须能指回某个 formState:读能落到 core / context,写能经 `set` trap
42
+ * 落到 coreState 并持久化。
43
+ *
44
+ * 优先级是 core > context > 合成自引用,三者都是刻意的:
45
+ *
46
+ * - **core**:`formState.mForm = formState` 这类直写(如 FormPreview 用自建 services 时)
47
+ * 必须最优先。
48
+ * - **context**:唯一的生产者是把父 formState 整体当 context 传下来的嵌套表单
49
+ * (ComponentForm)。此时 `mForm` 命中 context 指向**父表单**,与 `extendState` 时代
50
+ * 把父 formState 并入子状态的结果一致,跨字段通信仍落在同一份对象上。父 formState
51
+ * 本身也是 Proxy,写入照样持久化,所以这里让 context 赢是对的,别「修正」成指向子表单。
52
+ * - **合成自引用**:前两者都没有时才兜底,让 `vm.mForm.xxx = fn` 落到自己的 coreState。
53
+ *
54
+ * 由此推出一条约束:**不要往 context 里塞普通对象充当 `mForm`**。context 通常是 computed
55
+ * 产物,依赖一变就重建,挂上去的方法会静默丢失。要么直写 core,要么什么都不放交给兜底。
56
+ *
57
+ * 枚举语义上合成的自引用表现得像原型链上的属性:`'mForm' in state` 为真,但不出现在
58
+ * `Object.keys(state)` 里,`getOwnPropertyDescriptor` 也返回 undefined。这是故意的——
59
+ * 按扩展字段打包 formState 的调用方(如发给 AI 的逻辑)会因循环引用炸掉。
60
+ */
61
+ var SELF_REF_KEY = "mForm";
62
+ /**
33
63
  * 将 coreState 与宿主业务上下文关联:读取时优先 core,miss 再读穿到 context。
34
64
  *
35
65
  * - `get` 用属性访问而非 `Reflect.get(t, k, receiver)`,避免破坏 Vue reactive 的 `__v_raw`;
@@ -42,19 +72,24 @@ var mergeFormContexts = (...layers) => {
42
72
  * 独立成文件,避免与 `form.ts` ↔ `typeMatch.ts` 形成循环依赖。
43
73
  */
44
74
  var createFormStateProxy = (coreState, getContext) => {
45
- const resolve = () => (typeof getContext === "function" ? getContext() : unref(getContext)) || EMPTY_CONTEXT;
46
- return new Proxy(coreState, {
75
+ const resolve = () => {
76
+ const ctx = (typeof getContext === "function" ? getContext() : unref(getContext)) || EMPTY_CONTEXT;
77
+ return ctx === proxy ? EMPTY_CONTEXT : ctx;
78
+ };
79
+ const proxy = new Proxy(coreState, {
47
80
  get(t, k) {
48
81
  if (typeof k === "symbol") return Reflect.get(t, k);
49
82
  const v = t[k];
50
83
  if (v !== void 0 || Reflect.has(t, k)) return v;
51
- return resolve()[k];
84
+ const ctx = resolve();
85
+ if (k in ctx) return ctx[k];
86
+ return k === SELF_REF_KEY ? proxy : void 0;
52
87
  },
53
88
  set(t, k, value) {
54
89
  t[k] = value;
55
90
  return true;
56
91
  },
57
- has: (t, k) => Reflect.has(t, k) || typeof k !== "symbol" && k in resolve(),
92
+ has: (t, k) => Reflect.has(t, k) || typeof k !== "symbol" && (k in resolve() || k === SELF_REF_KEY),
58
93
  ownKeys: (t) => [.../* @__PURE__ */ new Set([...Reflect.ownKeys(t), ...Reflect.ownKeys(resolve()).filter((k) => typeof k !== "symbol")])],
59
94
  getOwnPropertyDescriptor: (t, k) => {
60
95
  const own = Reflect.getOwnPropertyDescriptor(t, k);
@@ -70,6 +105,7 @@ var createFormStateProxy = (coreState, getContext) => {
70
105
  };
71
106
  }
72
107
  });
108
+ return proxy;
73
109
  };
74
110
  //#endregion
75
111
  export { createFormStateProxy, mergeFormContexts };
@@ -2557,6 +2557,36 @@
2557
2557
  });
2558
2558
  };
2559
2559
  /**
2560
+ * 存量下发配置沿用 Vue2 时代的写法,把回调第一个参数当组件实例用,靠 `vm.mForm.xxx`
2561
+ * 取表单状态。这类配置除了读,还会往上面挂方法做跨字段通信,例如:
2562
+ *
2563
+ * ```js
2564
+ * vm.mForm.checkPropertyLimit = async (...) => { ... } // 一个字段的 validator 里挂
2565
+ * await vm.mForm.checkPropertyLimit(...) // 另一处再取出来调用
2566
+ * ```
2567
+ *
2568
+ * 所以 `mForm` 必须能指回某个 formState:读能落到 core / context,写能经 `set` trap
2569
+ * 落到 coreState 并持久化。
2570
+ *
2571
+ * 优先级是 core > context > 合成自引用,三者都是刻意的:
2572
+ *
2573
+ * - **core**:`formState.mForm = formState` 这类直写(如 FormPreview 用自建 services 时)
2574
+ * 必须最优先。
2575
+ * - **context**:唯一的生产者是把父 formState 整体当 context 传下来的嵌套表单
2576
+ * (ComponentForm)。此时 `mForm` 命中 context 指向**父表单**,与 `extendState` 时代
2577
+ * 把父 formState 并入子状态的结果一致,跨字段通信仍落在同一份对象上。父 formState
2578
+ * 本身也是 Proxy,写入照样持久化,所以这里让 context 赢是对的,别「修正」成指向子表单。
2579
+ * - **合成自引用**:前两者都没有时才兜底,让 `vm.mForm.xxx = fn` 落到自己的 coreState。
2580
+ *
2581
+ * 由此推出一条约束:**不要往 context 里塞普通对象充当 `mForm`**。context 通常是 computed
2582
+ * 产物,依赖一变就重建,挂上去的方法会静默丢失。要么直写 core,要么什么都不放交给兜底。
2583
+ *
2584
+ * 枚举语义上合成的自引用表现得像原型链上的属性:`'mForm' in state` 为真,但不出现在
2585
+ * `Object.keys(state)` 里,`getOwnPropertyDescriptor` 也返回 undefined。这是故意的——
2586
+ * 按扩展字段打包 formState 的调用方(如发给 AI 的逻辑)会因循环引用炸掉。
2587
+ */
2588
+ var SELF_REF_KEY = "mForm";
2589
+ /**
2560
2590
  * 将 coreState 与宿主业务上下文关联:读取时优先 core,miss 再读穿到 context。
2561
2591
  *
2562
2592
  * - `get` 用属性访问而非 `Reflect.get(t, k, receiver)`,避免破坏 Vue reactive 的 `__v_raw`;
@@ -2569,19 +2599,24 @@
2569
2599
  * 独立成文件,避免与 `form.ts` ↔ `typeMatch.ts` 形成循环依赖。
2570
2600
  */
2571
2601
  var createFormStateProxy = (coreState, getContext) => {
2572
- const resolve = () => (typeof getContext === "function" ? getContext() : (0, vue.unref)(getContext)) || EMPTY_CONTEXT;
2573
- return new Proxy(coreState, {
2602
+ const resolve = () => {
2603
+ const ctx = (typeof getContext === "function" ? getContext() : (0, vue.unref)(getContext)) || EMPTY_CONTEXT;
2604
+ return ctx === proxy ? EMPTY_CONTEXT : ctx;
2605
+ };
2606
+ const proxy = new Proxy(coreState, {
2574
2607
  get(t, k) {
2575
2608
  if (typeof k === "symbol") return Reflect.get(t, k);
2576
2609
  const v = t[k];
2577
2610
  if (v !== void 0 || Reflect.has(t, k)) return v;
2578
- return resolve()[k];
2611
+ const ctx = resolve();
2612
+ if (k in ctx) return ctx[k];
2613
+ return k === SELF_REF_KEY ? proxy : void 0;
2579
2614
  },
2580
2615
  set(t, k, value) {
2581
2616
  t[k] = value;
2582
2617
  return true;
2583
2618
  },
2584
- has: (t, k) => Reflect.has(t, k) || typeof k !== "symbol" && k in resolve(),
2619
+ has: (t, k) => Reflect.has(t, k) || typeof k !== "symbol" && (k in resolve() || k === SELF_REF_KEY),
2585
2620
  ownKeys: (t) => [.../* @__PURE__ */ new Set([...Reflect.ownKeys(t), ...Reflect.ownKeys(resolve()).filter((k) => typeof k !== "symbol")])],
2586
2621
  getOwnPropertyDescriptor: (t, k) => {
2587
2622
  const own = Reflect.getOwnPropertyDescriptor(t, k);
@@ -2597,6 +2632,7 @@
2597
2632
  };
2598
2633
  }
2599
2634
  });
2635
+ return proxy;
2600
2636
  };
2601
2637
  //#endregion
2602
2638
  //#region packages/form/src/utils/form.ts
@@ -3411,6 +3411,36 @@
3411
3411
  });
3412
3412
  };
3413
3413
  /**
3414
+ * 存量下发配置沿用 Vue2 时代的写法,把回调第一个参数当组件实例用,靠 `vm.mForm.xxx`
3415
+ * 取表单状态。这类配置除了读,还会往上面挂方法做跨字段通信,例如:
3416
+ *
3417
+ * ```js
3418
+ * vm.mForm.checkPropertyLimit = async (...) => { ... } // 一个字段的 validator 里挂
3419
+ * await vm.mForm.checkPropertyLimit(...) // 另一处再取出来调用
3420
+ * ```
3421
+ *
3422
+ * 所以 `mForm` 必须能指回某个 formState:读能落到 core / context,写能经 `set` trap
3423
+ * 落到 coreState 并持久化。
3424
+ *
3425
+ * 优先级是 core > context > 合成自引用,三者都是刻意的:
3426
+ *
3427
+ * - **core**:`formState.mForm = formState` 这类直写(如 FormPreview 用自建 services 时)
3428
+ * 必须最优先。
3429
+ * - **context**:唯一的生产者是把父 formState 整体当 context 传下来的嵌套表单
3430
+ * (ComponentForm)。此时 `mForm` 命中 context 指向**父表单**,与 `extendState` 时代
3431
+ * 把父 formState 并入子状态的结果一致,跨字段通信仍落在同一份对象上。父 formState
3432
+ * 本身也是 Proxy,写入照样持久化,所以这里让 context 赢是对的,别「修正」成指向子表单。
3433
+ * - **合成自引用**:前两者都没有时才兜底,让 `vm.mForm.xxx = fn` 落到自己的 coreState。
3434
+ *
3435
+ * 由此推出一条约束:**不要往 context 里塞普通对象充当 `mForm`**。context 通常是 computed
3436
+ * 产物,依赖一变就重建,挂上去的方法会静默丢失。要么直写 core,要么什么都不放交给兜底。
3437
+ *
3438
+ * 枚举语义上合成的自引用表现得像原型链上的属性:`'mForm' in state` 为真,但不出现在
3439
+ * `Object.keys(state)` 里,`getOwnPropertyDescriptor` 也返回 undefined。这是故意的——
3440
+ * 按扩展字段打包 formState 的调用方(如发给 AI 的逻辑)会因循环引用炸掉。
3441
+ */
3442
+ var SELF_REF_KEY = "mForm";
3443
+ /**
3414
3444
  * 将 coreState 与宿主业务上下文关联:读取时优先 core,miss 再读穿到 context。
3415
3445
  *
3416
3446
  * - `get` 用属性访问而非 `Reflect.get(t, k, receiver)`,避免破坏 Vue reactive 的 `__v_raw`;
@@ -3423,19 +3453,24 @@
3423
3453
  * 独立成文件,避免与 `form.ts` ↔ `typeMatch.ts` 形成循环依赖。
3424
3454
  */
3425
3455
  var createFormStateProxy = (coreState, getContext) => {
3426
- const resolve = () => (typeof getContext === "function" ? getContext() : (0, vue.unref)(getContext)) || EMPTY_CONTEXT;
3427
- return new Proxy(coreState, {
3456
+ const resolve = () => {
3457
+ const ctx = (typeof getContext === "function" ? getContext() : (0, vue.unref)(getContext)) || EMPTY_CONTEXT;
3458
+ return ctx === proxy ? EMPTY_CONTEXT : ctx;
3459
+ };
3460
+ const proxy = new Proxy(coreState, {
3428
3461
  get(t, k) {
3429
3462
  if (typeof k === "symbol") return Reflect.get(t, k);
3430
3463
  const v = t[k];
3431
3464
  if (v !== void 0 || Reflect.has(t, k)) return v;
3432
- return resolve()[k];
3465
+ const ctx = resolve();
3466
+ if (k in ctx) return ctx[k];
3467
+ return k === SELF_REF_KEY ? proxy : void 0;
3433
3468
  },
3434
3469
  set(t, k, value) {
3435
3470
  t[k] = value;
3436
3471
  return true;
3437
3472
  },
3438
- has: (t, k) => Reflect.has(t, k) || typeof k !== "symbol" && k in resolve(),
3473
+ has: (t, k) => Reflect.has(t, k) || typeof k !== "symbol" && (k in resolve() || k === SELF_REF_KEY),
3439
3474
  ownKeys: (t) => [.../* @__PURE__ */ new Set([...Reflect.ownKeys(t), ...Reflect.ownKeys(resolve()).filter((k) => typeof k !== "symbol")])],
3440
3475
  getOwnPropertyDescriptor: (t, k) => {
3441
3476
  const own = Reflect.getOwnPropertyDescriptor(t, k);
@@ -3451,6 +3486,7 @@
3451
3486
  };
3452
3487
  }
3453
3488
  });
3489
+ return proxy;
3454
3490
  };
3455
3491
  //#endregion
3456
3492
  //#region packages/form/src/utils/form.ts
@@ -9882,7 +9918,10 @@
9882
9918
  prop: props.prop,
9883
9919
  postOptions
9884
9920
  });
9885
- let initData = (0, _tmagic_utils.getValueByKeyPath)(initRoot || root, res);
9921
+ let initData;
9922
+ try {
9923
+ initData = (0, _tmagic_utils.getValueByKeyPath)(initRoot || root, res);
9924
+ } catch {}
9886
9925
  if (initData) {
9887
9926
  if (!Array.isArray(initData)) initData = [initData];
9888
9927
  if (typeof option.item === "function") options = option.item(initData);
@@ -9913,8 +9952,12 @@
9913
9952
  const v = props.model[props.name];
9914
9953
  if (Array.isArray(v) ? !v.length : typeof v === "undefined") return;
9915
9954
  if (typeof v === "undefined" || hasOption(v)) return;
9916
- const data = await getInitOption();
9917
- setOptions(data);
9955
+ try {
9956
+ const data = await getInitOption();
9957
+ setOptions(data);
9958
+ } catch {
9959
+ setOptions([]);
9960
+ }
9918
9961
  };
9919
9962
  (0, vue.watch)(() => props.model?.[props.name], () => {
9920
9963
  initOption();
@@ -10009,7 +10052,7 @@
10009
10052
  }), 128)) : ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, (0, vue.renderList)(options.value, (option) => {
10010
10053
  return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)((0, vue.unref)(optionComponent)?.component || "el-option"), (0, vue.mergeProps)({
10011
10054
  class: "tmagic-design-option",
10012
- key: __props.config.valueKey ? option.value[__props.config.valueKey] : option.value
10055
+ key: __props.config.valueKey ? option.value?.[__props.config.valueKey] : option.value
10013
10056
  }, { ref_for: true }, (0, vue.unref)(optionComponent)?.props({
10014
10057
  label: option.text,
10015
10058
  value: option.value,
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.8.0-beta.26",
2
+ "version": "1.8.0-beta.27",
3
3
  "name": "@tmagic/form",
4
4
  "type": "module",
5
5
  "sideEffects": [
@@ -58,9 +58,9 @@
58
58
  "peerDependencies": {
59
59
  "typescript": "^6.0.3",
60
60
  "vue": "^3.5.41",
61
- "@tmagic/design": "1.8.0-beta.26",
62
- "@tmagic/utils": "1.8.0-beta.26",
63
- "@tmagic/form-schema": "1.8.0-beta.26"
61
+ "@tmagic/design": "1.8.0-beta.27",
62
+ "@tmagic/form-schema": "1.8.0-beta.27",
63
+ "@tmagic/utils": "1.8.0-beta.27"
64
64
  },
65
65
  "peerDependenciesMeta": {
66
66
  "typescript": {
@@ -57,7 +57,7 @@
57
57
  <component
58
58
  v-for="option in options as SelectOption[]"
59
59
  class="tmagic-design-option"
60
- :key="config.valueKey ? option.value[config.valueKey] : option.value"
60
+ :key="config.valueKey ? option.value?.[config.valueKey] : option.value"
61
61
  :is="optionComponent?.component || 'el-option'"
62
62
  v-bind="
63
63
  optionComponent?.props({
@@ -372,7 +372,12 @@ const getInitOption = async () => {
372
372
  });
373
373
  }
374
374
 
375
- let initData = getValueByKeyPath(initRoot || root, res);
375
+ let initData: any;
376
+ try {
377
+ initData = getValueByKeyPath(initRoot || root, res);
378
+ } catch {
379
+ // 响应中不存在 initRoot/root 路径时按无数据处理
380
+ }
376
381
  if (initData) {
377
382
  if (!Array.isArray(initData)) {
378
383
  initData = [initData];
@@ -417,8 +422,12 @@ if (typeof props.config.options === 'function') {
417
422
  const v = props.model[props.name];
418
423
  if (Array.isArray(v) ? !v.length : typeof v === 'undefined') return;
419
424
  if (typeof v === 'undefined' || hasOption(v)) return;
420
- const data = await getInitOption();
421
- setOptions(data);
425
+ try {
426
+ const data = await getInitOption();
427
+ setOptions(data);
428
+ } catch {
429
+ setOptions([]);
430
+ }
422
431
  };
423
432
 
424
433
  watch(
@@ -56,6 +56,37 @@ export const mergeFormContexts = (...layers: (FormContext | undefined | null)[])
56
56
  }) as FormContext;
57
57
  };
58
58
 
59
+ /**
60
+ * 存量下发配置沿用 Vue2 时代的写法,把回调第一个参数当组件实例用,靠 `vm.mForm.xxx`
61
+ * 取表单状态。这类配置除了读,还会往上面挂方法做跨字段通信,例如:
62
+ *
63
+ * ```js
64
+ * vm.mForm.checkPropertyLimit = async (...) => { ... } // 一个字段的 validator 里挂
65
+ * await vm.mForm.checkPropertyLimit(...) // 另一处再取出来调用
66
+ * ```
67
+ *
68
+ * 所以 `mForm` 必须能指回某个 formState:读能落到 core / context,写能经 `set` trap
69
+ * 落到 coreState 并持久化。
70
+ *
71
+ * 优先级是 core > context > 合成自引用,三者都是刻意的:
72
+ *
73
+ * - **core**:`formState.mForm = formState` 这类直写(如 FormPreview 用自建 services 时)
74
+ * 必须最优先。
75
+ * - **context**:唯一的生产者是把父 formState 整体当 context 传下来的嵌套表单
76
+ * (ComponentForm)。此时 `mForm` 命中 context 指向**父表单**,与 `extendState` 时代
77
+ * 把父 formState 并入子状态的结果一致,跨字段通信仍落在同一份对象上。父 formState
78
+ * 本身也是 Proxy,写入照样持久化,所以这里让 context 赢是对的,别「修正」成指向子表单。
79
+ * - **合成自引用**:前两者都没有时才兜底,让 `vm.mForm.xxx = fn` 落到自己的 coreState。
80
+ *
81
+ * 由此推出一条约束:**不要往 context 里塞普通对象充当 `mForm`**。context 通常是 computed
82
+ * 产物,依赖一变就重建,挂上去的方法会静默丢失。要么直写 core,要么什么都不放交给兜底。
83
+ *
84
+ * 枚举语义上合成的自引用表现得像原型链上的属性:`'mForm' in state` 为真,但不出现在
85
+ * `Object.keys(state)` 里,`getOwnPropertyDescriptor` 也返回 undefined。这是故意的——
86
+ * 按扩展字段打包 formState 的调用方(如发给 AI 的逻辑)会因循环引用炸掉。
87
+ */
88
+ const SELF_REF_KEY = 'mForm';
89
+
59
90
  /**
60
91
  * 将 coreState 与宿主业务上下文关联:读取时优先 core,miss 再读穿到 context。
61
92
  *
@@ -72,21 +103,33 @@ export const createFormStateProxy = (
72
103
  coreState: FormState,
73
104
  getContext: (() => FormContext) | Ref<FormContext>,
74
105
  ): FormState => {
75
- const resolve = (): Record<string | symbol, any> =>
76
- (typeof getContext === 'function' ? getContext() : unref(getContext)) || EMPTY_CONTEXT;
106
+ const resolve = (): Record<string | symbol, any> => {
107
+ const ctx = (typeof getContext === 'function' ? getContext() : unref(getContext)) || EMPTY_CONTEXT;
108
+ // 把 formState 自己当 context 传回来(`:context="formState"`)会让 get / has 无限递归,
109
+ // 直接爆栈。这种自引用本就提供不了任何额外字段,断掉即可。
110
+ return ctx === proxy ? EMPTY_CONTEXT : ctx;
111
+ };
77
112
 
78
- return new Proxy(coreState as object, {
113
+ const proxy = new Proxy(coreState as object, {
79
114
  get(t, k) {
80
115
  if (typeof k === 'symbol') return Reflect.get(t, k);
81
116
  const v = (t as any)[k];
82
117
  if (v !== undefined || Reflect.has(t, k)) return v;
83
- return resolve()[k];
118
+
119
+ const ctx = resolve();
120
+ if (k in ctx) return ctx[k];
121
+ // 自引用不进 ownKeys:枚举 formState 的调用方(如按扩展字段打包发给 AI 的逻辑)
122
+ // 会因为循环引用炸掉,这里只在显式读取时才合成
123
+ return k === SELF_REF_KEY ? proxy : undefined;
84
124
  },
85
125
  set(t, k, value) {
86
126
  (t as any)[k] = value;
87
127
  return true;
88
128
  },
89
- has: (t, k) => Reflect.has(t, k) || (typeof k !== 'symbol' && k in resolve()),
129
+ // `mForm` 在这里为真,但不进 ownKeys、getOwnPropertyDescriptor 也返回 undefined
130
+ // (见 SELF_REF_KEY 注释)。因此展开 / Object.entries 不会循环引用,
131
+ // 但对 proxy 直接做递归遍历(`for...in`、深拷贝、直接 JSON.stringify(formState))仍会。
132
+ has: (t, k) => Reflect.has(t, k) || (typeof k !== 'symbol' && (k in resolve() || k === SELF_REF_KEY)),
90
133
  ownKeys: (t) => [
91
134
  ...new Set([...Reflect.ownKeys(t), ...Reflect.ownKeys(resolve()).filter((k) => typeof k !== 'symbol')]),
92
135
  ],
@@ -101,4 +144,6 @@ export const createFormStateProxy = (
101
144
  return { configurable: true, enumerable: true, writable: true, value: ctx[k] };
102
145
  },
103
146
  }) as FormState;
147
+
148
+ return proxy;
104
149
  };