@xbeeant/form-engine-react 0.0.3 → 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 (142) hide show
  1. package/dist/cjs/components/FormController.cjs +1 -1
  2. package/dist/cjs/components/NexusField.cjs +1 -1
  3. package/dist/cjs/components/NexusForm.cjs +1 -1
  4. package/dist/cjs/components/form-controller.cjs +1 -0
  5. package/dist/cjs/components/nexus-field.cjs +1 -0
  6. package/dist/cjs/components/nexus-form-provider.cjs +1 -0
  7. package/dist/cjs/components/nexus-form.cjs +1 -0
  8. package/dist/cjs/components/nexus-layout.cjs +1 -0
  9. package/dist/cjs/components/nexus-object.cjs +1 -0
  10. package/dist/cjs/contexts/field-inherit-context.cjs +1 -0
  11. package/dist/cjs/contexts/grid-context.cjs +1 -0
  12. package/dist/cjs/contexts/layout-config-context.cjs +1 -0
  13. package/dist/cjs/contexts/nexus-context.cjs +1 -0
  14. package/dist/cjs/hooks/index.cjs +1 -1
  15. package/dist/cjs/hooks/use-engine.cjs +1 -0
  16. package/dist/cjs/hooks/use-field-state.cjs +1 -0
  17. package/dist/cjs/hooks/use-field-validator.cjs +1 -0
  18. package/dist/cjs/hooks/use-field-value.cjs +1 -0
  19. package/dist/cjs/hooks/use-form-config.cjs +1 -0
  20. package/dist/cjs/hooks/use-form-data.cjs +1 -0
  21. package/dist/cjs/hooks/use-form-submitting.cjs +1 -0
  22. package/dist/cjs/hooks/use-form.cjs +1 -0
  23. package/dist/cjs/hooks/use-nexus-context.cjs +1 -0
  24. package/dist/cjs/hooks/use-watch-all.cjs +1 -0
  25. package/dist/cjs/hooks/use-watch-multiple.cjs +1 -0
  26. package/dist/cjs/hooks/use-watch-state.cjs +1 -0
  27. package/dist/cjs/hooks/use-watch.cjs +1 -0
  28. package/dist/cjs/hooks/useFormData.cjs +1 -1
  29. package/dist/cjs/hooks/useFormSubmitting.cjs +1 -0
  30. package/dist/cjs/index.cjs +1 -1
  31. package/dist/cjs/styles.css +1 -1
  32. package/dist/cjs/utils/omit-nil.cjs +1 -0
  33. package/dist/cjs/utils/omitNil.cjs +1 -0
  34. package/dist/cjs/utils/persist.cjs +1 -0
  35. package/dist/cjs/utils/render-tree-node.cjs +1 -0
  36. package/dist/cjs/utils/resolve-col-span.cjs +1 -0
  37. package/dist/es/components/FormController.d.ts +73 -19
  38. package/dist/es/components/FormController.js +222 -71
  39. package/dist/es/components/NexusField.js +25 -21
  40. package/dist/es/components/NexusForm.d.ts +33 -3
  41. package/dist/es/components/NexusForm.js +143 -96
  42. package/dist/es/components/form-controller.d.ts +129 -0
  43. package/dist/es/components/form-controller.js +310 -0
  44. package/dist/es/components/nexus-field.d.ts +9 -0
  45. package/dist/es/components/nexus-field.js +131 -0
  46. package/dist/es/components/nexus-form-provider.d.ts +12 -0
  47. package/dist/es/components/nexus-form-provider.js +18 -0
  48. package/dist/es/components/nexus-form.d.ts +114 -0
  49. package/dist/es/components/nexus-form.js +174 -0
  50. package/dist/es/components/nexus-layout.d.ts +9 -0
  51. package/dist/es/components/nexus-layout.js +48 -0
  52. package/dist/es/components/nexus-object.d.ts +16 -0
  53. package/dist/es/components/nexus-object.js +64 -0
  54. package/dist/es/contexts/NexusContext.d.ts +2 -0
  55. package/dist/es/contexts/field-inherit-context.d.ts +18 -0
  56. package/dist/es/contexts/field-inherit-context.js +5 -0
  57. package/dist/es/contexts/grid-context.d.ts +5 -0
  58. package/dist/es/contexts/grid-context.js +5 -0
  59. package/dist/es/contexts/layout-config-context.d.ts +4 -0
  60. package/dist/es/contexts/layout-config-context.js +5 -0
  61. package/dist/es/contexts/nexus-context.d.ts +29 -0
  62. package/dist/es/contexts/nexus-context.js +12 -0
  63. package/dist/es/hooks/index.d.ts +12 -11
  64. package/dist/es/hooks/index.js +20 -18
  65. package/dist/es/hooks/use-engine.d.ts +2 -0
  66. package/dist/es/hooks/use-engine.js +8 -0
  67. package/dist/es/hooks/use-field-state.d.ts +7 -0
  68. package/dist/es/hooks/use-field-state.js +13 -0
  69. package/dist/es/hooks/use-field-validator.d.ts +40 -0
  70. package/dist/es/hooks/use-field-validator.js +35 -0
  71. package/dist/es/hooks/use-field-value.d.ts +6 -0
  72. package/dist/es/hooks/use-field-value.js +13 -0
  73. package/dist/es/hooks/use-form-config.d.ts +5 -0
  74. package/dist/es/hooks/use-form-config.js +8 -0
  75. package/dist/es/hooks/use-form-data.d.ts +6 -0
  76. package/dist/es/hooks/use-form-data.js +13 -0
  77. package/dist/es/hooks/use-form-submitting.d.ts +7 -0
  78. package/dist/es/hooks/use-form-submitting.js +11 -0
  79. package/dist/es/hooks/use-form.d.ts +22 -0
  80. package/dist/es/hooks/use-form.js +12 -0
  81. package/dist/es/hooks/use-nexus-context.d.ts +11 -0
  82. package/dist/es/hooks/use-nexus-context.js +12 -0
  83. package/dist/es/hooks/use-watch-all.d.ts +19 -0
  84. package/dist/es/hooks/use-watch-all.js +29 -0
  85. package/dist/es/hooks/use-watch-multiple.d.ts +20 -0
  86. package/dist/es/hooks/use-watch-multiple.js +29 -0
  87. package/dist/es/hooks/use-watch-state.d.ts +18 -0
  88. package/dist/es/hooks/use-watch-state.js +19 -0
  89. package/dist/es/hooks/use-watch.d.ts +25 -0
  90. package/dist/es/hooks/use-watch.js +20 -0
  91. package/dist/es/hooks/useForm.d.ts +11 -4
  92. package/dist/es/hooks/useFormData.js +9 -9
  93. package/dist/es/hooks/useFormSubmitting.d.ts +7 -0
  94. package/dist/es/hooks/useFormSubmitting.js +11 -0
  95. package/dist/es/index.d.ts +26 -24
  96. package/dist/es/index.js +36 -32
  97. package/dist/es/styles.css +1 -1
  98. package/dist/es/utils/omit-nil.d.ts +9 -0
  99. package/dist/es/utils/omit-nil.js +14 -0
  100. package/dist/es/utils/omitNil.d.ts +9 -0
  101. package/dist/es/utils/omitNil.js +14 -0
  102. package/dist/es/utils/persist.d.ts +24 -0
  103. package/dist/es/utils/persist.js +43 -0
  104. package/dist/es/utils/render-tree-node.d.ts +6 -0
  105. package/dist/es/utils/render-tree-node.js +17 -0
  106. package/dist/es/utils/resolve-col-span.d.ts +5 -0
  107. package/dist/es/utils/resolve-col-span.js +9 -0
  108. package/dist/umd/index.css +1 -1
  109. package/dist/umd/index.umd.cjs +1 -1
  110. package/package.json +5 -4
  111. package/src/components/form-controller.ts +584 -0
  112. package/src/components/{NexusField.tsx → nexus-field.tsx} +57 -6
  113. package/src/components/{NexusFormProvider.tsx → nexus-form-provider.tsx} +3 -3
  114. package/src/components/{NexusForm.tsx → nexus-form.tsx} +135 -18
  115. package/src/components/{NexusLayout.tsx → nexus-layout.tsx} +5 -5
  116. package/src/components/{NexusObject.tsx → nexus-object.tsx} +3 -3
  117. package/src/contexts/{NexusContext.ts → nexus-context.ts} +3 -1
  118. package/src/hooks/index.ts +12 -11
  119. package/src/hooks/{useEngine.ts → use-engine.ts} +1 -1
  120. package/src/hooks/{useFieldState.ts → use-field-state.ts} +1 -1
  121. package/src/hooks/{useFieldValidator.ts → use-field-validator.ts} +1 -1
  122. package/src/hooks/{useFieldValue.ts → use-field-value.ts} +1 -1
  123. package/src/hooks/{useFormConfig.ts → use-form-config.ts} +2 -2
  124. package/src/hooks/{useFormData.ts → use-form-data.ts} +4 -4
  125. package/src/hooks/use-form-submitting.ts +16 -0
  126. package/src/hooks/{useForm.ts → use-form.ts} +12 -5
  127. package/src/hooks/{useNexusContext.ts → use-nexus-context.ts} +2 -2
  128. package/src/index.ts +26 -24
  129. package/src/utils/omit-nil.ts +24 -0
  130. package/src/utils/persist.ts +88 -0
  131. package/src/utils/{renderTreeNode.tsx → render-tree-node.tsx} +3 -3
  132. package/src/utils/{resolveColSpan.ts → resolve-col-span.ts} +1 -1
  133. package/dist/cjs/tailwind.css +0 -1
  134. package/dist/es/tailwind.css +0 -1
  135. package/src/components/FormController.ts +0 -278
  136. /package/src/contexts/{FieldInheritContext.ts → field-inherit-context.ts} +0 -0
  137. /package/src/contexts/{GridContext.ts → grid-context.ts} +0 -0
  138. /package/src/contexts/{LayoutConfigContext.ts → layout-config-context.ts} +0 -0
  139. /package/src/hooks/{useWatchAll.ts → use-watch-all.ts} +0 -0
  140. /package/src/hooks/{useWatchMultiple.ts → use-watch-multiple.ts} +0 -0
  141. /package/src/hooks/{useWatchState.ts → use-watch-state.ts} +0 -0
  142. /package/src/hooks/{useWatch.ts → use-watch.ts} +0 -0
@@ -1,9 +1,9 @@
1
1
  import type { RenderTreeNode } from '@xbeeant/form-engine';
2
2
  import type { ReactElement } from 'react';
3
3
 
4
- import { NexusField } from '../components/NexusField';
5
- import { NexusLayout } from '../components/NexusLayout';
6
- import { NexusObject } from '../components/NexusObject';
4
+ import { NexusField } from '../components/nexus-field';
5
+ import { NexusLayout } from '../components/nexus-layout';
6
+ import { NexusObject } from '../components/nexus-object';
7
7
 
8
8
  /**
9
9
  * renderTreeNode — 递归渲染
@@ -1,4 +1,4 @@
1
- import type { GridContextValue } from '../contexts/GridContext';
1
+ import type { GridContextValue } from '../contexts/grid-context';
2
2
 
3
3
  /**
4
4
  * resolveColSpan — 统一解析子项在父 Grid 中的跨列数
@@ -1 +0,0 @@
1
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-font-weight:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial}}}:root,:host{--color-red-500:oklch(63.7% .237 25.331);--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--font-weight-bold:700;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1)}.visible{visibility:visible}.mt-4{margin-top:calc(var(--spacing) * 4)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.rotate-0{rotate:0deg}.rotate-90{rotate:90deg}.cursor-pointer{cursor:pointer}.gap-1{gap:var(--spacing)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.border-none{--tw-border-style:none;border-style:none}.bg-transparent{background-color:#0000}.p-0{padding:0}.align-middle{vertical-align:middle}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.text-red-500{color:var(--color-red-500)}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.select-none{-webkit-user-select:none;user-select:none}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
@@ -1 +0,0 @@
1
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-font-weight:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial}}}:root,:host{--color-red-500:oklch(63.7% .237 25.331);--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--font-weight-bold:700;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1)}.visible{visibility:visible}.mt-4{margin-top:calc(var(--spacing) * 4)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.rotate-0{rotate:0deg}.rotate-90{rotate:90deg}.cursor-pointer{cursor:pointer}.gap-1{gap:var(--spacing)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.border-none{--tw-border-style:none;border-style:none}.bg-transparent{background-color:#0000}.p-0{padding:0}.align-middle{vertical-align:middle}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.text-red-500{color:var(--color-red-500)}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.select-none{-webkit-user-select:none;user-select:none}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
@@ -1,278 +0,0 @@
1
- import type {
2
- FieldState,
3
- NexusFormInstance,
4
- NexusSchema,
5
- } from '@xbeeant/form-engine';
6
- import { AsyncValidatorPlugin, NexusEngine } from '@xbeeant/form-engine';
7
- import type { RefObject } from 'react';
8
-
9
- /**
10
- * FormController — 包裹 Engine,暴露 Form 实例 API
11
- */
12
- export class FormController implements NexusFormInstance {
13
- private engine: NexusEngine;
14
- private formElementRef: RefObject<HTMLFormElement | null>;
15
- /** 稳定的 getter:每次 submit 执行时从外部 ref 读取最新的 onFinish/onFinishFailed 回调 */
16
- private getOnFinish: () => (
17
- data: Record<string, unknown>,
18
- ) => void | Promise<void>;
19
- private getOnFinishFailed: () => (errors: Map<string, string[]>) => void;
20
- private removeHiddenData: boolean = true;
21
- private watchers: Map<
22
- string,
23
- (value: unknown, allValues: Record<string, unknown>) => void
24
- > = new Map();
25
- private globalWatcher:
26
- | ((
27
- value: Record<string, unknown>,
28
- allValues: Record<string, unknown>,
29
- changedPath?: string,
30
- ) => void)
31
- | null = null;
32
-
33
- constructor(engine?: NexusEngine) {
34
- this.engine = engine ?? new NexusEngine();
35
- // 默认注入异步校验器插件:useFieldValidator / registerFieldValidator
36
- // 注册的异步校验器即可在字段值变化时被触发(防抖调度,与默认 'change' trigger 对齐)。
37
- // 外部已注入同名插件时不重复注入(hasPlugin 幂等)。
38
- if (!this.engine.hasPlugin('async-validator')) {
39
- this.engine.use(new AsyncValidatorPlugin(this.engine));
40
- }
41
- this.formElementRef = {
42
- current: null,
43
- } as RefObject<HTMLFormElement | null>;
44
- this.getOnFinish = () => () => {};
45
- this.getOnFinishFailed = () => () => {};
46
- }
47
-
48
- /** 内部:首次绑定表单 DOM + 回调 getter(由 NexusForm 在挂载时调用一次) */
49
- _bind(
50
- formEl: HTMLFormElement | null,
51
- getOnFinish: () => (data: Record<string, unknown>) => void | Promise<void>,
52
- getOnFinishFailed: () => (errors: Map<string, string[]>) => void,
53
- ): void {
54
- (this.formElementRef as { current: HTMLFormElement | null }).current =
55
- formEl;
56
- this.getOnFinish = getOnFinish;
57
- this.getOnFinishFailed = getOnFinishFailed;
58
- // 注册值变更回调到 Engine,只需一次(回调闭包引用了稳定的实例)
59
- this.engine.registerOnFieldValueChange((path, value) =>
60
- this._onFieldValueChange(path, value),
61
- );
62
- }
63
-
64
- /** 内部:同步 watch / removeHiddenData 配置(由 NexusForm 在它们变化时调用) */
65
- _syncConfig(config: {
66
- removeHiddenData?: boolean;
67
- watch?: {
68
- [path: string]: (
69
- value: unknown,
70
- allValues: Record<string, unknown>,
71
- changedPath?: string,
72
- ) => void;
73
- };
74
- }): void {
75
- if (config.removeHiddenData !== undefined) {
76
- this.removeHiddenData = config.removeHiddenData;
77
- }
78
- if (config.watch) {
79
- this.watchers.clear();
80
- this.globalWatcher = null;
81
- for (const [path, fn] of Object.entries(config.watch)) {
82
- if (path === '#') {
83
- this.globalWatcher = fn;
84
- } else {
85
- this.watchers.set(path, fn);
86
- }
87
- }
88
- }
89
- }
90
-
91
- /** 内部:值变更时调用(由 Engine 通知) */
92
- _onFieldValueChange(path: string, value: unknown): void {
93
- const allValues = this.engine.getFormData();
94
- const globalData = this.removeHiddenData
95
- ? allValues
96
- : this.engine.getAllFormData();
97
-
98
- // 全局 watcher(# 监听所有字段变化,value 即为全部表单值;
99
- // 第三参携带本次实际变更的字段路径,供清空值场景区分「未赋值默认值」与「用户主动清空」)
100
- if (this.globalWatcher) {
101
- this.globalWatcher(globalData, globalData, path);
102
- }
103
- // 路径匹配的 watcher
104
- const fn = this.watchers.get(path);
105
- if (fn) {
106
- fn(value, globalData);
107
- }
108
- }
109
-
110
- /** 内部:获取 Engine 实例 */
111
- _getEngine(): NexusEngine {
112
- return this.engine;
113
- }
114
-
115
- /**
116
- * 获取底层 Engine 实例(用于跨表单联动:linkForm / setFormId 等)
117
- */
118
- getEngine(): NexusEngine {
119
- return this.engine;
120
- }
121
-
122
- async submit(): Promise<void> {
123
- const errors = await this.engine.validate();
124
- if (errors.size > 0) {
125
- this.focusFirstError(errors);
126
- this.getOnFinishFailed()?.(errors);
127
- return;
128
- }
129
- const formData = this.removeHiddenData
130
- ? this.engine.getFormData()
131
- : this.engine.getAllFormData();
132
- await this.getOnFinish()?.(formData);
133
- }
134
-
135
- /**
136
- * 定位到第一个校验失败的字段:按 DOM 渲染顺序查找(保证视觉上的"第一个"),
137
- * 滚动入视并聚焦其内部可交互控件。
138
- */
139
- private focusFirstError(errors: Map<string, string[]>): void {
140
- const formEl = this.formElementRef.current;
141
- if (!formEl || errors.size === 0) {
142
- return;
143
- }
144
-
145
- const errorPaths = new Set(errors.keys());
146
-
147
- const focus = () => {
148
- const fieldEls =
149
- formEl.querySelectorAll<HTMLElement>('[data-nexus-field]');
150
- for (const el of Array.from(fieldEls)) {
151
- const path = el.getAttribute('data-nexus-field');
152
- if (!path || !errorPaths.has(path)) {
153
- continue;
154
- }
155
-
156
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
157
- const focusable = el.querySelector<HTMLElement>(
158
- 'input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])',
159
- );
160
- focusable?.focus();
161
- return;
162
- }
163
- };
164
-
165
- // 等待 React 将错误态刷到 DOM 后再定位,保证错误提示已可见
166
- requestAnimationFrame(focus);
167
- }
168
-
169
- resetFields(): void {
170
- this.engine.reset();
171
- }
172
-
173
- setErrorFields(errors: Array<{ path: string; errors: string[] }>): void {
174
- this.engine.setErrorFields(errors);
175
- }
176
-
177
- setValues(values: Record<string, unknown>): void {
178
- this.engine.setFieldValues(values);
179
- }
180
-
181
- setValueByPath(path: string, value: unknown): void {
182
- this.engine.setFieldValue(path, value);
183
- }
184
-
185
- setSchemaByPath(path: string, patch: Record<string, unknown>): void {
186
- this.engine.setSchemaByPath(path, patch);
187
- }
188
-
189
- setSchema(schema: NexusSchema): void {
190
- this.engine.setSchema(schema);
191
- }
192
-
193
- getValues(paths?: string[]): Record<string, unknown> {
194
- return this.engine.getFormData(paths);
195
- }
196
-
197
- getHiddenValues(): Record<string, unknown> {
198
- return this.engine.getHiddenValues();
199
- }
200
-
201
- /** 获取所有字段值(含 hidden) */
202
- getAllValues(): Record<string, unknown> {
203
- return this.engine.getAllFormData();
204
- }
205
-
206
- getValueByPath(path: string): unknown {
207
- return this.engine.getFieldValue(path);
208
- }
209
-
210
- /**
211
- * 注册字段校验逻辑
212
- * @param path 字段路径(如 'username')
213
- * @param validator 校验函数,返回错误消息数组(空数组表示通过)
214
- */
215
- registerValidator(
216
- path: string,
217
- validator: (
218
- value: unknown,
219
- formData: Record<string, unknown>,
220
- ) => string[] | Promise<string[]>,
221
- ): void {
222
- // 注册到 Engine(validate 与 实时校验 统一由 Engine 执行)
223
- this.engine.registerFieldValidator(path, validator);
224
- }
225
-
226
- /**
227
- * 注销字段校验逻辑(按函数引用移除)
228
- * 与 registerValidator 配对;widget 组件卸载时清理,避免校验器累积
229
- */
230
- unregisterValidator(
231
- path: string,
232
- validator: (
233
- value: unknown,
234
- formData: Record<string, unknown>,
235
- ) => string[] | Promise<string[]>,
236
- ): void {
237
- this.engine.unregisterFieldValidator(path, validator);
238
- }
239
-
240
- /**
241
- * 实时重校验指定字段(同步)
242
- * 供 widget 组件内部状态变化(非字段值变化)时主动刷新错误态
243
- */
244
- revalidateField(path: string): void {
245
- this.engine.validateField(path, { trigger: 'change' });
246
- }
247
-
248
- getSchema(): NexusSchema | null {
249
- return this.engine.getSchema();
250
- }
251
-
252
- removeErrorField(path: string): void {
253
- this.engine.removeErrorField(path);
254
- }
255
-
256
- scrollToPath(path: string): void {
257
- const el = this.formElementRef.current?.querySelector(
258
- `[data-nexus-field="${path}"]`,
259
- );
260
- el?.scrollIntoView({ behavior: 'smooth', block: 'center' });
261
- }
262
-
263
- getFieldError(path: string): string[] {
264
- return this.engine.getFieldError(path);
265
- }
266
-
267
- getFieldsError(): Map<string, string[]> {
268
- return this.engine.getFieldsError();
269
- }
270
-
271
- validateFields(paths?: string[]): Promise<Map<string, string[]>> {
272
- return this.engine.validate(paths);
273
- }
274
-
275
- getFieldState(path: string): FieldState | undefined {
276
- return this.engine.getFieldState(path);
277
- }
278
- }
File without changes
File without changes