@xbeeant/form-engine-react 0.0.1 → 0.0.3

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.
package/README.md CHANGED
@@ -1,69 +1,97 @@
1
- # React + TypeScript + Vite
2
-
3
- This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
-
5
- Currently, two official plugins are available:
6
-
7
- - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
8
- - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
-
10
- ## Expanding the ESLint configuration
11
-
12
- If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
13
-
14
- ```js
15
- export default tseslint.config([
16
- globalIgnores(['dist']),
17
- {
18
- files: ['**/*.{ts,tsx}'],
19
- extends: [
20
- // Other configs...
21
-
22
- // Remove tseslint.configs.recommended and replace with this
23
- ...tseslint.configs.recommendedTypeChecked,
24
- // Alternatively, use this for stricter rules
25
- ...tseslint.configs.strictTypeChecked,
26
- // Optionally, add this for stylistic rules
27
- ...tseslint.configs.stylisticTypeChecked,
28
-
29
- // Other configs...
30
- ],
31
- languageOptions: {
32
- parserOptions: {
33
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
34
- tsconfigRootDir: import.meta.dirname,
35
- },
36
- // other options...
37
- },
38
- },
39
- ])
1
+ # @xbeeant/form-engine-react
2
+
3
+ `@xbeeant/form-engine` React 渲染适配层。
4
+
5
+ 通过 `useSyncExternalStore` + 按路径精准版本订阅渲染字段,联动更新只重渲染受影响的组件;Schema 解析与路径计算全部在 Core 完成,本包直接消费渲染树。
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ npm install @xbeeant/form-engine-react @xbeeant/form-engine
40
11
  ```
41
12
 
42
- You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
43
-
44
- ```js
45
- // eslint.config.js
46
- import reactX from 'eslint-plugin-react-x'
47
- import reactDom from 'eslint-plugin-react-dom'
48
-
49
- export default tseslint.config([
50
- globalIgnores(['dist']),
51
- {
52
- files: ['**/*.{ts,tsx}'],
53
- extends: [
54
- // Other configs...
55
- // Enable lint rules for React
56
- reactX.configs['recommended-typescript'],
57
- // Enable lint rules for React DOM
58
- reactDom.configs.recommended,
59
- ],
60
- languageOptions: {
61
- parserOptions: {
62
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
63
- tsconfigRootDir: import.meta.dirname,
64
- },
65
- // other options...
13
+ peerDependencies:`react >= 18`、`react-dom >= 18`。
14
+
15
+ ## 快速开始
16
+
17
+ ```tsx
18
+ import { useForm, NexusForm } from '@xbeeant/form-engine-react';
19
+ import { registerAntdUI } from '@xbeeant/form-engine-ui';
20
+
21
+ const schema = {
22
+ type: 'object',
23
+ displayType: 'row',
24
+ properties: {
25
+ username: {
26
+ type: 'string',
27
+ widget: 'input',
28
+ title: '用户名',
29
+ required: true,
66
30
  },
31
+ age: { type: 'integer', widget: 'number', title: '年龄' },
67
32
  },
68
- ])
33
+ };
34
+
35
+ function App() {
36
+ const [form] = useForm();
37
+ return (
38
+ <NexusForm
39
+ form={form}
40
+ schema={schema}
41
+ initialValues={{ username: 'alice' }}
42
+ onFinish={(values) => console.log(values)}
43
+ onFinishFailed={(errors) => console.log(errors)}
44
+ footer
45
+ />
46
+ );
47
+ }
48
+ ```
49
+
50
+ > 需要先注册 UI 组件:`registerAntdUI(engine)`(engine 由 `useForm()` 内部创建)或 `engine.use(antdPreset)` 注入 `@xbeeant/form-engine-ui`。未注册 widget 的字段会优雅降级渲染。
51
+
52
+ ## API
53
+
54
+ ### NexusForm
55
+
56
+ | Prop | 类型 | 说明 |
57
+ | :--- | :--- | :--- |
58
+ | `form` | `FormController` | 由 `useForm()` 创建的表单实例 |
59
+ | `schema` | `NexusSchema` | Schema 定义(缺省则渲染 children) |
60
+ | `initialValues` | `Record<string, unknown>` | 初始值 |
61
+ | `widgets` / `layouts` | `Record<string, (props) => ReactNode>` | 额外注册的组件 |
62
+ | `onFinish` | `(formData) => void \| Promise` | 提交成功回调 |
63
+ | `onFinishFailed` | `(errors: Map<string, string[]>) => void` | 校验失败回调 |
64
+ | `footer` | `boolean \| ReactNode` | 是否显示默认提交/重置按钮,或自定义 |
65
+ | `displayType` / `labelWidth` / `label` / `colon` / `column` / `readOnly` | — | 表单布局配置,优先级:组件 props > Schema 顶层 > 默认值 |
66
+ | `className` / `style` / `children` | — | 常规属性 |
67
+
68
+ ### useForm
69
+
70
+ ```ts
71
+ const [form] = useForm(); // 返回 [FormController],可选 useForm(engine) 复用已有实例
69
72
  ```
73
+
74
+ `FormController` 常用方法:`submit()`、`resetFields()`、`setValues(values)`、`setValueByPath(path, value)`、`getValues(paths?)`、`getAllValues()`、`getHiddenValues()`、`validateFields(paths?)`、`getFieldError(path)`、`setSchema(schema)`、`setSchemaByPath(path, patch)`、`registerValidator(path, fn)`、`scrollToPath(path)` 等。`submit` 失败时自动滚动聚焦第一个错误字段。
75
+
76
+ ### Hooks
77
+
78
+ | Hook | 说明 |
79
+ | :--- | :--- |
80
+ | `useFormData()` | 订阅完整 formData(全局快照) |
81
+ | `useFieldValue(path)` | 订阅单字段值(精准版本订阅,仅该字段重渲染) |
82
+ | `useFieldState(path)` | 订阅单字段完整状态(value / errors / visible / required ...) |
83
+ | `useWatch(path)` | 监听字段值变化(`(value, oldValue) => void`) |
84
+ | `useWatchState(path)` | 监听字段状态变化 |
85
+ | `useWatchMultiple(paths)` | 同时监听多个字段 |
86
+ | `useWatchAll()` | 监听所有字段值变化 |
87
+ | `useEngine()` | 获取当前引擎实例 |
88
+ | `useFieldValidator(path, validator)` | 为字段注册校验器 |
89
+ | `useFormConfig()` | 获取表单布局配置(displayType / labelWidth / column ...) |
90
+
91
+ ## 关联包
92
+
93
+ | 包 | 说明 |
94
+ | :--- | :--- |
95
+ | `@xbeeant/form-engine` | 表单引擎核心(纯 TypeScript) |
96
+ | `@xbeeant/form-engine-ui` | Ant Design 控件与布局库 |
97
+ | `@xbeeant/form-engine-designer` | Schema 可视化设计器 |
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("@xbeeant/form-engine");class o{engine;formElementRef;getOnFinish;getOnFinishFailed;removeHiddenData=!0;watchers=new Map;globalWatcher=null;constructor(e){this.engine=e??new l.NexusEngine,this.engine.hasPlugin("async-validator")||this.engine.use(new l.AsyncValidatorPlugin(this.engine)),this.formElementRef={current:null},this.getOnFinish=()=>()=>{},this.getOnFinishFailed=()=>()=>{}}_bind(e,t,i){this.formElementRef.current=e,this.getOnFinish=t,this.getOnFinishFailed=i,this.engine.registerOnFieldValueChange((n,r)=>this._onFieldValueChange(n,r))}_syncConfig(e){if(e.removeHiddenData!==void 0&&(this.removeHiddenData=e.removeHiddenData),e.watch){this.watchers.clear(),this.globalWatcher=null;for(const[t,i]of Object.entries(e.watch))t==="#"?this.globalWatcher=i:this.watchers.set(t,i)}}_onFieldValueChange(e,t){const i=this.engine.getFormData(),n=this.removeHiddenData?i:this.engine.getAllFormData();this.globalWatcher&&this.globalWatcher(n,n,e);const r=this.watchers.get(e);r&&r(t,n)}_getEngine(){return this.engine}async submit(){const e=await this.engine.validate();if(e.size>0){this.focusFirstError(e),this.getOnFinishFailed()?.(e);return}const t=this.removeHiddenData?this.engine.getFormData():this.engine.getAllFormData();await this.getOnFinish()?.(t)}focusFirstError(e){const t=this.formElementRef.current;if(!t||e.size===0)return;const i=new Set(e.keys());requestAnimationFrame(()=>{const r=t.querySelectorAll("[data-nexus-field]");for(const s of Array.from(r)){const a=s.getAttribute("data-nexus-field");if(!a||!i.has(a))continue;s.scrollIntoView({behavior:"smooth",block:"center"}),s.querySelector('input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')?.focus();return}})}resetFields(){this.engine.reset()}setErrorFields(e){this.engine.setErrorFields(e)}setValues(e){this.engine.setFieldValues(e)}setValueByPath(e,t){this.engine.setFieldValue(e,t)}setSchemaByPath(e,t){this.engine.setSchemaByPath(e,t)}setSchema(e){this.engine.setSchema(e)}getValues(e){return this.engine.getFormData(e)}getHiddenValues(){return this.engine.getHiddenValues()}getAllValues(){return this.engine.getAllFormData()}getValueByPath(e){return this.engine.getFieldValue(e)}registerValidator(e,t){this.engine.registerFieldValidator(e,t)}unregisterValidator(e,t){this.engine.unregisterFieldValidator(e,t)}revalidateField(e){this.engine.validateField(e,{trigger:"change"})}getSchema(){return this.engine.getSchema()}removeErrorField(e){this.engine.removeErrorField(e)}scrollToPath(e){this.formElementRef.current?.querySelector(`[data-nexus-field="${e}"]`)?.scrollIntoView({behavior:"smooth",block:"center"})}getFieldError(e){return this.engine.getFieldError(e)}getFieldsError(){return this.engine.getFieldsError()}validateFields(e){return this.engine.validate(e)}getFieldState(e){return this.engine.getFieldState(e)}}exports.FormController=o;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("@xbeeant/form-engine");class o{engine;formElementRef;getOnFinish;getOnFinishFailed;removeHiddenData=!0;watchers=new Map;globalWatcher=null;constructor(e){this.engine=e??new l.NexusEngine,this.engine.hasPlugin("async-validator")||this.engine.use(new l.AsyncValidatorPlugin(this.engine)),this.formElementRef={current:null},this.getOnFinish=()=>()=>{},this.getOnFinishFailed=()=>()=>{}}_bind(e,t,i){this.formElementRef.current=e,this.getOnFinish=t,this.getOnFinishFailed=i,this.engine.registerOnFieldValueChange((n,r)=>this._onFieldValueChange(n,r))}_syncConfig(e){if(e.removeHiddenData!==void 0&&(this.removeHiddenData=e.removeHiddenData),e.watch){this.watchers.clear(),this.globalWatcher=null;for(const[t,i]of Object.entries(e.watch))t==="#"?this.globalWatcher=i:this.watchers.set(t,i)}}_onFieldValueChange(e,t){const i=this.engine.getFormData(),n=this.removeHiddenData?i:this.engine.getAllFormData();this.globalWatcher&&this.globalWatcher(n,n,e);const r=this.watchers.get(e);r&&r(t,n)}_getEngine(){return this.engine}getEngine(){return this.engine}async submit(){const e=await this.engine.validate();if(e.size>0){this.focusFirstError(e),this.getOnFinishFailed()?.(e);return}const t=this.removeHiddenData?this.engine.getFormData():this.engine.getAllFormData();await this.getOnFinish()?.(t)}focusFirstError(e){const t=this.formElementRef.current;if(!t||e.size===0)return;const i=new Set(e.keys());requestAnimationFrame(()=>{const r=t.querySelectorAll("[data-nexus-field]");for(const s of Array.from(r)){const a=s.getAttribute("data-nexus-field");if(!a||!i.has(a))continue;s.scrollIntoView({behavior:"smooth",block:"center"}),s.querySelector('input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')?.focus();return}})}resetFields(){this.engine.reset()}setErrorFields(e){this.engine.setErrorFields(e)}setValues(e){this.engine.setFieldValues(e)}setValueByPath(e,t){this.engine.setFieldValue(e,t)}setSchemaByPath(e,t){this.engine.setSchemaByPath(e,t)}setSchema(e){this.engine.setSchema(e)}getValues(e){return this.engine.getFormData(e)}getHiddenValues(){return this.engine.getHiddenValues()}getAllValues(){return this.engine.getAllFormData()}getValueByPath(e){return this.engine.getFieldValue(e)}registerValidator(e,t){this.engine.registerFieldValidator(e,t)}unregisterValidator(e,t){this.engine.unregisterFieldValidator(e,t)}revalidateField(e){this.engine.validateField(e,{trigger:"change"})}getSchema(){return this.engine.getSchema()}removeErrorField(e){this.engine.removeErrorField(e)}scrollToPath(e){this.formElementRef.current?.querySelector(`[data-nexus-field="${e}"]`)?.scrollIntoView({behavior:"smooth",block:"center"})}getFieldError(e){return this.engine.getFieldError(e)}getFieldsError(){return this.engine.getFieldsError()}validateFields(e){return this.engine.validate(e)}getFieldState(e){return this.engine.getFieldState(e)}}exports.FormController=o;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const d=require("react/jsx-runtime"),s=require("react"),O=require("../contexts/FieldInheritContext.cjs"),j=require("../contexts/GridContext.cjs"),T=require("../contexts/LayoutConfigContext.cjs"),V=require("../contexts/NexusContext.cjs"),_=require("../utils/resolveColSpan.cjs");function k({dataPath:t,layoutKey:x}){const{engine:n,config:o,form:f}=V.useNexusContext();s.useSyncExternalStore(i=>n.subscribeField(t,i),()=>n.getFieldVersion(t),()=>n.getFieldVersion(t));const e=n.getFieldState(t),y=s.useContext(j.GridContext),C=s.useContext(T.LayoutConfigContext),u=s.useContext(O.FieldInheritContext),b=s.useCallback(i=>{n.setFieldValue(t,i)},[n,t]),v=s.useCallback(i=>{i.currentTarget.contains(i.relatedTarget)||n.validateField(t,{trigger:"blur"})},[n,t]),F=s.useMemo(()=>e?.meta.enum?e.meta.enum.map((i,r)=>({value:i,label:e.meta.enumNames?.[r]??String(i)})):e?.props.options,[e?.meta.enum,e?.meta.enumNames,e?.props.options]),S=s.useMemo(()=>{const i={};if(e?.reactions){for(const r of e.reactions)if(r.dependencies)for(const p of r.dependencies)i[p]=n.getFieldValue(p)}return i},[e?.reactions,n]);if(!e)return n.getSnapshot()>0&&console.warn(`[NexusField] Field not found: ${t}`),null;if(u.visible===!1||!e.visible)return C.removeHidden===!0?null:d.jsx("div",{className:"hidden","data-nexus-hidden":t});const a=o.readOnly||u.readOnly===!0||e.readOnly,h=u.disabled===!0||e.disabled,c=a&&!!e.meta.readOnlyWidget,W=c?e.meta.readOnlyWidget:e.meta.widget;let l=n.getWidget(W);if(!l&&c&&(l=n.getWidget(e.meta.widget)),!l)return d.jsxs("div",{className:"text-xs text-red-500","data-nexus-field":t,children:['⚠️ Widget "',e.meta.widget,'" 未注册 (path: ',t,")"]});const w=e.meta.displayType??o.displayType,N=e.meta.labelWidth??o.labelWidth,q=e.meta.column??o.column,m=_.resolveColSpan(e.meta.colSpan,y),g={...e.meta.width?{width:e.meta.width,flexShrink:0}:{},...m?{gridColumn:`span ${m}`}:{}};return d.jsx("div",{"data-nexus-field":t,onBlur:v,style:Object.keys(g).length>0?g:void 0,children:d.jsx(l,{dataPath:t,path:t,value:e.value,onChange:b,disabled:h,readOnly:a,loading:e.loading,required:e.required,title:e.meta.title,description:e.meta.description,placeholder:e.meta.placeholder,label:e.meta.label,options:F,errors:e.errors,extra:e.meta.extra,displayType:w,labelWidth:N,column:q,form:f,dependValues:S,items:e.meta.items,...e.props},x)})}exports.NexusField=k;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("react/jsx-runtime"),s=require("react"),_=require("../contexts/FieldInheritContext.cjs"),k=require("../contexts/GridContext.cjs"),L=require("../contexts/LayoutConfigContext.cjs"),M=require("../contexts/NexusContext.cjs"),B=require("../utils/resolveColSpan.cjs");function G({dataPath:t,layoutKey:u}){const{engine:n,config:l,form:b}=M.useNexusContext();s.useSyncExternalStore(i=>n.subscribeField(t,i),()=>n.getFieldVersion(t),()=>n.getFieldVersion(t));const e=n.getFieldState(t),F=s.useContext(k.GridContext),h=s.useContext(L.LayoutConfigContext),c=s.useContext(_.FieldInheritContext),v=s.useCallback(i=>{n.setFieldValue(t,i)},[n,t]),W=s.useCallback(i=>{i.currentTarget.contains(i.relatedTarget)||n.validateField(t,{trigger:"blur"})},[n,t]),w=s.useMemo(()=>e?.meta.enum?e.meta.enum.map((i,d)=>({value:i,label:e.meta.enumNames?.[d]??String(i)})):e?.props.options,[e?.meta.enum,e?.meta.enumNames,e?.props.options]),S=s.useMemo(()=>{const i={};if(e?.reactions){for(const d of e.reactions)if(d.dependencies)for(const y of d.dependencies)i[y]=n.getFieldValue(y)}return i},[e?.reactions,n]);if(!e)return n.getSnapshot()>0&&console.warn(`[NexusField] Field not found: ${t}`),null;if(c.visible===!1||!e.visible)return h.removeHidden===!0?null:o.jsx("div",{className:"hidden","data-nexus-hidden":t});const m=l.readOnly||c.readOnly===!0||e.readOnly,N=c.disabled===!0||e.disabled,p=m&&!!e.meta.readOnlyWidget,j=p?e.meta.readOnlyWidget:e.meta.widget;let r=n.getWidget(j);if(!r&&p&&(r=n.getWidget(e.meta.widget)),!r)return o.jsxs("div",{className:"text-xs text-red-500","data-nexus-field":t,children:['⚠️ Widget "',e.meta.widget,'" 未注册 (path: ',t,")"]});const q=e.meta.displayType??l.displayType,O=e.meta.labelWidth??l.labelWidth,T=e.meta.column??l.column,g=B.resolveColSpan(e.meta.colSpan,F),x={...e.meta.width?{width:e.meta.width,flexShrink:0}:{},...g?{gridColumn:`span ${g}`}:{}},f=n.getFieldWrapper(),V={label:e.meta.label,title:e.meta.title,description:e.meta.description,errors:e.errors,required:e.required,extra:e.meta.extra,width:e.meta.width,displayType:q,labelWidth:O,column:T},C={dataPath:t,path:t,value:e.value,onChange:v,disabled:N,readOnly:m,loading:e.loading,placeholder:e.meta.placeholder,options:w,form:b,dependValues:S,items:e.meta.items,...e.props};let a;return f?a=o.jsx(f,{...V,children:o.jsx(r,{...C})},u):a=o.jsx(r,{...C},u),o.jsx("div",{"data-nexus-field":t,onBlur:W,style:Object.keys(x).length>0?x:void 0,children:a})}exports.NexusField=G;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("react/jsx-runtime"),e=require("react"),B=require("../utils/renderTreeNode.cjs"),G=require("./NexusFormProvider.cjs");function o(){}function J({form:t,schema:r,initialValues:E,widgets:u,layouts:c,onFinish:v,onFinishFailed:_,footer:f=!1,className:h,style:q,children:L,labelCol:a,labelWidth:M,colon:W,label:k,displayType:O,readOnly:b,column:P,watch:y,removeHiddenData:g=!0}){const n=t._getEngine(),R=e.useRef(null),x=O??r?.displayType??"row",F=k??r?.label??!0,m=W??r?.colon,i=M??r?.labelWidth,S=b??r?.readOnly??!1,s=P??r?.column;e.useEffect(()=>{u&&n.registerWidgets(u),c&&n.registerLayouts(c)},[n,u,c]);const T=e.useRef(!0),D=e.useRef(E);e.useEffect(()=>{r&&(T.current?(n.init(r,D.current),T.current=!1):n.init(r,n.getFormData()))},[n,r]);const C=e.useRef(o),N=e.useRef(o);C.current=v??o,N.current=_??o,e.useEffect(()=>{t._bind(R.current,()=>C.current,()=>N.current)},[t]),e.useEffect(()=>{t._syncConfig({removeHiddenData:g,watch:y})},[t,g,y]);const V=e.useSyncExternalStore(n.subscribeStore,n.getSnapshot,n.getSnapshot),$=e.useMemo(()=>n.getRenderTree(),[n,V]),I=e.useCallback(async p=>{p.preventDefault(),await t.submit()},[t]),w=e.useCallback(()=>{t.resetFields()},[t]);let d=null;f===!0?d=l.jsxs("div",{className:"mt-4",children:[l.jsx("button",{type:"submit",children:"提交"})," ",l.jsx("button",{type:"button",onClick:w,children:"重置"})]}):f&&(d=f);const j=e.useMemo(()=>{if(a)return a;if(i)return{style:{width:typeof i=="number"?`${i}px`:i}}},[a,i]),z=e.useMemo(()=>({labelCol:j,labelWidth:i,colon:m,label:F,displayType:x,readOnly:S,column:s}),[j,i,m,F,x,S,s]);return l.jsx(G.NexusFormProvider,{engine:n,config:z,form:t,children:l.jsxs("form",{ref:R,onSubmit:I,className:h,style:{...q,...s&&s>1?{display:"grid",gridTemplateColumns:`repeat(${s}, 1fr)`,gap:"0 16px"}:{}},noValidate:!0,children:[$.map((p,A)=>B.renderTreeNode(p,A)),!b&&d,L]})})}exports.NexusForm=J;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("react/jsx-runtime"),e=require("react"),Q=require("../utils/renderTreeNode.cjs"),U=require("./NexusFormProvider.cjs");function o(){}function X({form:r,schema:n,initialValues:h,widgets:l,layouts:c,onFinish:k,onFinishFailed:q,onMount:b,footer:f=!1,className:L,style:O,children:W,labelCol:a,labelWidth:P,colon:D,label:V,displayType:$,readOnly:y,column:I,watch:g,removeHiddenData:R=!0}){const t=r._getEngine(),x=e.useRef(null),F=$??n?.displayType??"row",S=V??n?.label??!0,j=D??n?.colon,s=P??n?.labelWidth,E=y??n?.readOnly??!1,i=I??n?.column;e.useEffect(()=>{l&&t.registerWidgets(l),c&&t.registerLayouts(c)},[t,l,c]);const T=e.useRef(!0),w=e.useRef(h);e.useEffect(()=>{n&&(T.current?(t.init(n,w.current),T.current=!1):t.init(n,t.getFormData()))},[t,n]);const m=e.useRef(b);m.current=b;const C=e.useRef(!1),N=!n||typeof n=="object"&&Object.keys(n).length===0;e.useEffect(()=>{C.current||N||(C.current=!0,m.current?.())},[N]);const v=e.useRef(o),_=e.useRef(o);v.current=k??o,_.current=q??o,e.useEffect(()=>{r._bind(x.current,()=>v.current,()=>_.current)},[r]),e.useEffect(()=>{r._syncConfig({removeHiddenData:R,watch:g})},[r,R,g]);const z=e.useSyncExternalStore(t.subscribeStore,t.getSnapshot,t.getSnapshot),A=e.useMemo(()=>t.getRenderTree(),[t,z]),B=e.useCallback(async p=>{p.preventDefault(),await r.submit()},[r]),G=e.useCallback(()=>{r.resetFields()},[r]);let d=null;f===!0?d=u.jsxs("div",{className:"mt-4",children:[u.jsx("button",{type:"submit",children:"提交"})," ",u.jsx("button",{type:"button",onClick:G,children:"重置"})]}):f&&(d=f);const M=e.useMemo(()=>{if(a)return a;if(s)return{style:{width:typeof s=="number"?`${s}px`:s}}},[a,s]),J=e.useMemo(()=>({labelCol:M,labelWidth:s,colon:j,label:S,displayType:F,readOnly:E,column:i}),[M,s,j,S,F,E,i]);return u.jsx(U.NexusFormProvider,{engine:t,config:J,form:r,children:u.jsxs("form",{ref:x,onSubmit:B,className:L,style:{...O,...i&&i>1?{display:"grid",gridTemplateColumns:`repeat(${i}, 1fr)`,gap:"0 16px"}:{}},noValidate:!0,children:[A.map((p,K)=>Q.renderTreeNode(p,K)),!y&&d,W]})})}exports.NexusForm=X;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("react"),t=require("../components/FormController.cjs");function n(e){const r=o.useRef(null);return r.current||(r.current=new t.FormController(e)),[r.current]}exports.useForm=n;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("@xbeeant/form-engine"),t=require("react"),u=require("../components/FormController.cjs");function c(r,n){const e=t.useRef(null);return e.current||(e.current=new u.FormController(n??(r?new o.NexusEngine({formId:r}):void 0))),[e.current]}exports.useForm=c;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./components/FormController.cjs"),o=require("./components/NexusField.cjs"),t=require("./components/NexusForm.cjs"),s=require("./components/NexusFormProvider.cjs"),r=require("./components/NexusLayout.cjs"),u=require("./components/NexusObject.cjs"),n=require("./contexts/FieldInheritContext.cjs"),i=require("./contexts/GridContext.cjs"),c=require("./contexts/LayoutConfigContext.cjs"),l=require("./hooks/useEngine.cjs"),a=require("./hooks/useFieldState.cjs"),F=require("./hooks/useFieldValidator.cjs"),x=require("./hooks/useFieldValue.cjs"),d=require("./hooks/useForm.cjs"),h=require("./hooks/useFormConfig.cjs"),m=require("./hooks/useFormData.cjs"),q=require("./hooks/useWatch.cjs"),_=require("./hooks/useWatchAll.cjs"),C=require("./hooks/useWatchMultiple.cjs"),N=require("./hooks/useWatchState.cjs");exports.FormController=e.FormController;exports.NexusField=o.NexusField;exports.NexusForm=t.NexusForm;exports.NexusFormProvider=s.NexusFormProvider;exports.NexusLayout=r.NexusLayout;exports.NexusObject=u.NexusObject;exports.FieldInheritContext=n.FieldInheritContext;exports.GridContext=i.GridContext;exports.LayoutConfigContext=c.LayoutConfigContext;exports.useEngine=l.useEngine;exports.useFieldState=a.useFieldState;exports.useFieldValidator=F.useFieldValidator;exports.useFieldValue=x.useFieldValue;exports.useForm=d.useForm;exports.useFormConfig=h.useFormConfig;exports.useFormData=m.useFormData;exports.useWatch=q.useWatch;exports.useWatchAll=_.useWatchAll;exports.useWatchMultiple=C.useWatchMultiple;exports.useWatchState=N.useWatchState;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});require('./styles.css');;/* empty css */const e=require("./components/FormController.cjs"),o=require("./components/NexusField.cjs"),t=require("./components/NexusForm.cjs"),s=require("./components/NexusFormProvider.cjs"),r=require("./components/NexusLayout.cjs"),u=require("./components/NexusObject.cjs"),n=require("./contexts/FieldInheritContext.cjs"),i=require("./contexts/GridContext.cjs"),c=require("./contexts/LayoutConfigContext.cjs"),l=require("./hooks/useEngine.cjs"),a=require("./hooks/useFieldState.cjs"),F=require("./hooks/useFieldValidator.cjs"),x=require("./hooks/useFieldValue.cjs"),d=require("./hooks/useForm.cjs"),h=require("./hooks/useFormConfig.cjs"),m=require("./hooks/useFormData.cjs"),q=require("./hooks/useWatch.cjs"),_=require("./hooks/useWatchAll.cjs"),C=require("./hooks/useWatchMultiple.cjs"),N=require("./hooks/useWatchState.cjs");exports.FormController=e.FormController;exports.NexusField=o.NexusField;exports.NexusForm=t.NexusForm;exports.NexusFormProvider=s.NexusFormProvider;exports.NexusLayout=r.NexusLayout;exports.NexusObject=u.NexusObject;exports.FieldInheritContext=n.FieldInheritContext;exports.GridContext=i.GridContext;exports.LayoutConfigContext=c.LayoutConfigContext;exports.useEngine=l.useEngine;exports.useFieldState=a.useFieldState;exports.useFieldValidator=F.useFieldValidator;exports.useFieldValue=x.useFieldValue;exports.useForm=d.useForm;exports.useFormConfig=h.useFormConfig;exports.useFormData=m.useFormData;exports.useWatch=q.useWatch;exports.useWatchAll=_.useWatchAll;exports.useWatchMultiple=C.useWatchMultiple;exports.useWatchState=N.useWatchState;
@@ -0,0 +1 @@
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}
@@ -0,0 +1 @@
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}
@@ -25,6 +25,10 @@ export declare class FormController implements NexusFormInstance {
25
25
  _onFieldValueChange(path: string, value: unknown): void;
26
26
  /** 内部:获取 Engine 实例 */
27
27
  _getEngine(): NexusEngine;
28
+ /**
29
+ * 获取底层 Engine 实例(用于跨表单联动:linkForm / setFormId 等)
30
+ */
31
+ getEngine(): NexusEngine;
28
32
  submit(): Promise<void>;
29
33
  /**
30
34
  * 定位到第一个校验失败的字段:按 DOM 渲染顺序查找(保证视觉上的"第一个"),
@@ -40,6 +40,12 @@ class c {
40
40
  _getEngine() {
41
41
  return this.engine;
42
42
  }
43
+ /**
44
+ * 获取底层 Engine 实例(用于跨表单联动:linkForm / setFormId 等)
45
+ */
46
+ getEngine() {
47
+ return this.engine;
48
+ }
43
49
  async submit() {
44
50
  const e = await this.engine.validate();
45
51
  if (e.size > 0) {
@@ -1,98 +1,97 @@
1
- import { jsx as d, jsxs as j } from "react/jsx-runtime";
2
- import { useSyncExternalStore as k, useContext as a, useCallback as g, useMemo as x } from "react";
3
- import { FieldInheritContext as q } from "../contexts/FieldInheritContext.js";
4
- import { GridContext as B } from "../contexts/GridContext.js";
5
- import { LayoutConfigContext as L } from "../contexts/LayoutConfigContext.js";
6
- import { useNexusContext as $ } from "../contexts/NexusContext.js";
7
- import { resolveColSpan as D } from "../utils/resolveColSpan.js";
8
- function A({ dataPath: t, layoutKey: y }) {
9
- const { engine: i, config: o, form: b } = $();
10
- k(
1
+ import { jsx as o, jsxs as L } from "react/jsx-runtime";
2
+ import { useSyncExternalStore as $, useContext as m, useCallback as C, useMemo as W } from "react";
3
+ import { FieldInheritContext as D } from "../contexts/FieldInheritContext.js";
4
+ import { GridContext as E } from "../contexts/GridContext.js";
5
+ import { LayoutConfigContext as G } from "../contexts/LayoutConfigContext.js";
6
+ import { useNexusContext as H } from "../contexts/NexusContext.js";
7
+ import { resolveColSpan as I } from "../utils/resolveColSpan.js";
8
+ function X({ dataPath: t, layoutKey: c }) {
9
+ const { engine: i, config: l, form: h } = H();
10
+ $(
11
11
  (n) => i.subscribeField(t, n),
12
12
  () => i.getFieldVersion(t),
13
13
  () => i.getFieldVersion(t)
14
14
  );
15
- const e = i.getFieldState(t), C = a(B), v = a(L), s = a(q), F = g(
15
+ const e = i.getFieldState(t), w = m(E), F = m(G), d = m(D), v = C(
16
16
  (n) => {
17
17
  i.setFieldValue(t, n);
18
18
  },
19
19
  [i, t]
20
- ), W = g(
20
+ ), S = C(
21
21
  (n) => {
22
22
  n.currentTarget.contains(n.relatedTarget) || i.validateField(t, { trigger: "blur" });
23
23
  },
24
24
  [i, t]
25
- ), h = x(
26
- () => e?.meta.enum ? e.meta.enum.map((n, l) => ({
25
+ ), N = W(
26
+ () => e?.meta.enum ? e.meta.enum.map((n, s) => ({
27
27
  value: n,
28
- label: e.meta.enumNames?.[l] ?? String(n)
28
+ label: e.meta.enumNames?.[s] ?? String(n)
29
29
  })) : e?.props.options,
30
30
  [e?.meta.enum, e?.meta.enumNames, e?.props.options]
31
- ), w = x(() => {
31
+ ), O = W(() => {
32
32
  const n = {};
33
33
  if (e?.reactions) {
34
- for (const l of e.reactions)
35
- if (l.dependencies)
36
- for (const f of l.dependencies)
37
- n[f] = i.getFieldValue(f);
34
+ for (const s of e.reactions)
35
+ if (s.dependencies)
36
+ for (const b of s.dependencies)
37
+ n[b] = i.getFieldValue(b);
38
38
  }
39
39
  return n;
40
40
  }, [e?.reactions, i]);
41
41
  if (!e)
42
42
  return i.getSnapshot() > 0 && console.warn(`[NexusField] Field not found: ${t}`), null;
43
- if (s.visible === !1 || !e.visible)
44
- return v.removeHidden === !0 ? null : /* @__PURE__ */ d("div", { className: "hidden", "data-nexus-hidden": t });
45
- const m = o.readOnly || s.readOnly === !0 || e.readOnly, S = s.disabled === !0 || e.disabled, c = m && !!e.meta.readOnlyWidget, N = c ? e.meta.readOnlyWidget : e.meta.widget;
46
- let r = i.getWidget(N);
47
- if (!r && c && (r = i.getWidget(e.meta.widget)), !r)
48
- return /* @__PURE__ */ j("div", { className: "text-xs text-red-500", "data-nexus-field": t, children: [
43
+ if (d.visible === !1 || !e.visible)
44
+ return F.removeHidden === !0 ? null : /* @__PURE__ */ o("div", { className: "hidden", "data-nexus-hidden": t });
45
+ const p = l.readOnly || d.readOnly === !0 || e.readOnly, T = d.disabled === !0 || e.disabled, u = p && !!e.meta.readOnlyWidget, V = u ? e.meta.readOnlyWidget : e.meta.widget;
46
+ let r = i.getWidget(V);
47
+ if (!r && u && (r = i.getWidget(e.meta.widget)), !r)
48
+ return /* @__PURE__ */ L("div", { className: "text-xs text-red-500", "data-nexus-field": t, children: [
49
49
  '⚠️ Widget "',
50
50
  e.meta.widget,
51
51
  '" 未注册 (path: ',
52
52
  t,
53
53
  ")"
54
54
  ] });
55
- const O = e.meta.displayType ?? o.displayType, T = e.meta.labelWidth ?? o.labelWidth, V = e.meta.column ?? o.column, u = D(e.meta.colSpan, C), p = {
55
+ const j = e.meta.displayType ?? l.displayType, k = e.meta.labelWidth ?? l.labelWidth, q = e.meta.column ?? l.column, f = I(e.meta.colSpan, w), g = {
56
56
  ...e.meta.width ? { width: e.meta.width, flexShrink: 0 } : {},
57
- ...u ? { gridColumn: `span ${u}` } : {}
57
+ ...f ? { gridColumn: `span ${f}` } : {}
58
+ }, x = i.getFieldWrapper(), B = {
59
+ label: e.meta.label,
60
+ title: e.meta.title,
61
+ description: e.meta.description,
62
+ errors: e.errors,
63
+ required: e.required,
64
+ extra: e.meta.extra,
65
+ width: e.meta.width,
66
+ displayType: j,
67
+ labelWidth: k,
68
+ column: q
69
+ }, y = {
70
+ dataPath: t,
71
+ path: t,
72
+ value: e.value,
73
+ onChange: v,
74
+ disabled: T,
75
+ readOnly: p,
76
+ loading: e.loading,
77
+ placeholder: e.meta.placeholder,
78
+ options: N,
79
+ form: h,
80
+ dependValues: O,
81
+ items: e.meta.items,
82
+ ...e.props
58
83
  };
59
- return /* @__PURE__ */ d(
84
+ let a;
85
+ return x ? a = /* @__PURE__ */ o(x, { ...B, children: /* @__PURE__ */ o(r, { ...y }) }, c) : a = /* @__PURE__ */ o(r, { ...y }, c), /* @__PURE__ */ o(
60
86
  "div",
61
87
  {
62
88
  "data-nexus-field": t,
63
- onBlur: W,
64
- style: Object.keys(p).length > 0 ? p : void 0,
65
- children: /* @__PURE__ */ d(
66
- r,
67
- {
68
- dataPath: t,
69
- path: t,
70
- value: e.value,
71
- onChange: F,
72
- disabled: S,
73
- readOnly: m,
74
- loading: e.loading,
75
- required: e.required,
76
- title: e.meta.title,
77
- description: e.meta.description,
78
- placeholder: e.meta.placeholder,
79
- label: e.meta.label,
80
- options: h,
81
- errors: e.errors,
82
- extra: e.meta.extra,
83
- displayType: O,
84
- labelWidth: T,
85
- column: V,
86
- form: b,
87
- dependValues: w,
88
- items: e.meta.items,
89
- ...e.props
90
- },
91
- y
92
- )
89
+ onBlur: S,
90
+ style: Object.keys(g).length > 0 ? g : void 0,
91
+ children: a
93
92
  }
94
93
  );
95
94
  }
96
95
  export {
97
- A as NexusField
96
+ X as NexusField
98
97
  };
@@ -32,6 +32,13 @@ export interface NexusFormProps {
32
32
  onFinish?: (formData: Record<string, unknown>) => void | Promise<void>;
33
33
  /** 校验失败回调 */
34
34
  onFinishFailed?: (errors: Map<string, string[]>) => void;
35
+ /**
36
+ * 表单首次加载回调:非空 schema 首次传入并完成渲染后执行一次
37
+ * - undefined / null / {}(无 properties)均视为「空」schema,不触发
38
+ * - schema 由空变为非空时,于首个非空渲染提交后触发
39
+ * - 后续 schema 变更不重复触发
40
+ */
41
+ onMount?: () => void;
35
42
  /** 是否显示默认 footer(提交/重置按钮),或自定义 footer */
36
43
  footer?: boolean | ReactNode;
37
44
  /** 自定义类名 */
@@ -74,4 +81,4 @@ export interface NexusFormProps {
74
81
  /**
75
82
  * NexusForm — 顶层表单组件
76
83
  */
77
- export declare function NexusForm({ form, schema, initialValues, widgets, layouts, onFinish, onFinishFailed, footer, className, style, children, labelCol, labelWidth, colon, label, displayType, readOnly, column, watch, removeHiddenData, }: NexusFormProps): import("react").JSX.Element;
84
+ export declare function NexusForm({ form, schema, initialValues, widgets, layouts, onFinish, onFinishFailed, onMount, footer, className, style, children, labelCol, labelWidth, colon, label, displayType, readOnly, column, watch, removeHiddenData, }: NexusFormProps): import("react").JSX.Element;
@@ -1,120 +1,127 @@
1
- import { jsx as b, jsxs as L } from "react/jsx-runtime";
2
- import { useRef as l, useEffect as o, useSyncExternalStore as Q, useMemo as y, useCallback as W } from "react";
3
- import { renderTreeNode as U } from "../utils/renderTreeNode.js";
4
- import { NexusFormProvider as X } from "./NexusFormProvider.js";
1
+ import { jsx as b, jsxs as k } from "react/jsx-runtime";
2
+ import { useRef as i, useEffect as l, useSyncExternalStore as Z, useMemo as y, useCallback as O } from "react";
3
+ import { renderTreeNode as H } from "../utils/renderTreeNode.js";
4
+ import { NexusFormProvider as ee } from "./NexusFormProvider.js";
5
5
  function s() {
6
6
  }
7
- function ne({
8
- form: n,
9
- schema: t,
10
- initialValues: _,
7
+ function oe({
8
+ form: t,
9
+ schema: e,
10
+ initialValues: D,
11
11
  widgets: u,
12
12
  layouts: c,
13
- onFinish: j,
14
- onFinishFailed: k,
15
- footer: a = !1,
16
- className: D,
17
- style: O,
18
- children: V,
19
- labelCol: f,
20
- labelWidth: $,
21
- colon: I,
22
- label: M,
23
- displayType: P,
24
- readOnly: g,
25
- column: q,
13
+ onFinish: M,
14
+ onFinishFailed: V,
15
+ onMount: g,
16
+ footer: f = !1,
17
+ className: $,
18
+ style: I,
19
+ children: P,
20
+ labelCol: a,
21
+ labelWidth: q,
22
+ colon: w,
23
+ label: z,
24
+ displayType: A,
25
+ readOnly: R,
26
+ column: B,
26
27
  watch: m,
27
- removeHiddenData: R = !0
28
+ removeHiddenData: F = !0
28
29
  }) {
29
- const e = n._getEngine(), x = l(null), F = P ?? t?.displayType ?? "row", C = M ?? t?.label ?? !0, S = I ?? t?.colon, r = $ ?? t?.labelWidth, T = g ?? t?.readOnly ?? !1, i = q ?? t?.column;
30
- o(() => {
31
- u && e.registerWidgets(u), c && e.registerLayouts(c);
32
- }, [e, u, c]);
33
- const h = l(!0), w = l(_);
34
- o(() => {
35
- t && (h.current ? (e.init(t, w.current), h.current = !1) : e.init(t, e.getFormData()));
36
- }, [e, t]);
37
- const N = l(s), v = l(s);
38
- N.current = j ?? s, v.current = k ?? s, o(() => {
39
- n._bind(
30
+ const n = t._getEngine(), x = i(null), S = A ?? e?.displayType ?? "row", C = z ?? e?.label ?? !0, T = w ?? e?.colon, r = q ?? e?.labelWidth, E = R ?? e?.readOnly ?? !1, o = B ?? e?.column;
31
+ l(() => {
32
+ u && n.registerWidgets(u), c && n.registerLayouts(c);
33
+ }, [n, u, c]);
34
+ const N = i(!0), G = i(D);
35
+ l(() => {
36
+ e && (N.current ? (n.init(e, G.current), N.current = !1) : n.init(e, n.getFormData()));
37
+ }, [n, e]);
38
+ const h = i(g);
39
+ h.current = g;
40
+ const j = i(!1), v = !e || typeof e == "object" && Object.keys(e).length === 0;
41
+ l(() => {
42
+ j.current || v || (j.current = !0, h.current?.());
43
+ }, [v]);
44
+ const L = i(s), W = i(s);
45
+ L.current = M ?? s, W.current = V ?? s, l(() => {
46
+ t._bind(
40
47
  x.current,
41
- () => N.current,
42
- () => v.current
48
+ () => L.current,
49
+ () => W.current
43
50
  );
44
- }, [n]), o(() => {
45
- n._syncConfig({ removeHiddenData: R, watch: m });
46
- }, [n, R, m]);
47
- const z = Q(
48
- e.subscribeStore,
49
- e.getSnapshot,
50
- e.getSnapshot
51
- ), A = y(() => e.getRenderTree(), [e, z]), B = W(
51
+ }, [t]), l(() => {
52
+ t._syncConfig({ removeHiddenData: F, watch: m });
53
+ }, [t, F, m]);
54
+ const J = Z(
55
+ n.subscribeStore,
56
+ n.getSnapshot,
57
+ n.getSnapshot
58
+ ), K = y(() => n.getRenderTree(), [n, J]), Q = O(
52
59
  async (p) => {
53
- p.preventDefault(), await n.submit();
60
+ p.preventDefault(), await t.submit();
54
61
  },
55
- [n]
56
- ), G = W(() => {
57
- n.resetFields();
58
- }, [n]);
62
+ [t]
63
+ ), U = O(() => {
64
+ t.resetFields();
65
+ }, [t]);
59
66
  let d = null;
60
- a === !0 ? d = /* @__PURE__ */ L("div", { className: "mt-4", children: [
67
+ f === !0 ? d = /* @__PURE__ */ k("div", { className: "mt-4", children: [
61
68
  /* @__PURE__ */ b("button", { type: "submit", children: "提交" }),
62
69
  " ",
63
- /* @__PURE__ */ b("button", { type: "button", onClick: G, children: "重置" })
64
- ] }) : a && (d = a);
65
- const E = y(() => {
66
- if (f)
67
- return f;
70
+ /* @__PURE__ */ b("button", { type: "button", onClick: U, children: "重置" })
71
+ ] }) : f && (d = f);
72
+ const _ = y(() => {
73
+ if (a)
74
+ return a;
68
75
  if (r)
69
76
  return {
70
77
  style: {
71
78
  width: typeof r == "number" ? `${r}px` : r
72
79
  }
73
80
  };
74
- }, [f, r]), J = y(
81
+ }, [a, r]), X = y(
75
82
  () => ({
76
- labelCol: E,
83
+ labelCol: _,
77
84
  labelWidth: r,
78
- colon: S,
85
+ colon: T,
79
86
  label: C,
80
- displayType: F,
81
- readOnly: T,
82
- column: i
87
+ displayType: S,
88
+ readOnly: E,
89
+ column: o
83
90
  }),
84
91
  [
85
- E,
92
+ _,
86
93
  r,
87
- S,
88
- C,
89
- F,
90
94
  T,
91
- i
95
+ C,
96
+ S,
97
+ E,
98
+ o
92
99
  ]
93
100
  );
94
- return /* @__PURE__ */ b(X, { engine: e, config: J, form: n, children: /* @__PURE__ */ L(
101
+ return /* @__PURE__ */ b(ee, { engine: n, config: X, form: t, children: /* @__PURE__ */ k(
95
102
  "form",
96
103
  {
97
104
  ref: x,
98
- onSubmit: B,
99
- className: D,
105
+ onSubmit: Q,
106
+ className: $,
100
107
  style: {
101
- ...O,
108
+ ...I,
102
109
  // 当配置了 column 时,使用 CSS Grid 布局
103
- ...i && i > 1 ? {
110
+ ...o && o > 1 ? {
104
111
  display: "grid",
105
- gridTemplateColumns: `repeat(${i}, 1fr)`,
112
+ gridTemplateColumns: `repeat(${o}, 1fr)`,
106
113
  gap: "0 16px"
107
114
  } : {}
108
115
  },
109
116
  noValidate: !0,
110
117
  children: [
111
- A.map((p, K) => U(p, K)),
112
- !g && d,
113
- V
118
+ K.map((p, Y) => H(p, Y)),
119
+ !R && d,
120
+ P
114
121
  ]
115
122
  }
116
123
  ) });
117
124
  }
118
125
  export {
119
- ne as NexusForm
126
+ oe as NexusForm
120
127
  };
@@ -2,5 +2,14 @@ import { NexusEngine } from '@xbeeant/form-engine';
2
2
  import { FormController } from '../components/FormController';
3
3
  /**
4
4
  * useForm — 创建 Form 实例
5
+ *
6
+ * 支持一个页面挂载多个表单实例:
7
+ * - `useForm()`:独立实例(未注册,不参与跨表单联动)
8
+ * - `useForm(formId)`:创建带 formId 的实例,自动注册到默认表单注册表,
9
+ * 可通过 schema `crossForm` reaction / `engine.linkForm` 与其他表单联动
10
+ * - `useForm(formId, engine)`:复用外部创建的引擎(如 `new NexusEngine({ formId })`)
11
+ *
12
+ * @param formId - 可选,表单实例唯一标识(跨表单联动寻址)
13
+ * @param engine - 可选,外部引擎实例(缺省内部创建)
5
14
  */
6
- export declare function useForm(engine?: NexusEngine): [FormController];
15
+ export declare function useForm(formId?: string, engine?: NexusEngine): [FormController];
@@ -1,9 +1,12 @@
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];
1
+ import { NexusEngine as n } from "@xbeeant/form-engine";
2
+ import { useRef as t } from "react";
3
+ import { FormController as u } from "../components/FormController.js";
4
+ function c(e, o) {
5
+ const r = t(null);
6
+ return r.current || (r.current = new u(
7
+ o ?? (e ? new n({ formId: e }) : void 0)
8
+ )), [r.current];
6
9
  }
7
10
  export {
8
- f as useForm
11
+ c as useForm
9
12
  };
package/dist/es/index.js CHANGED
@@ -1,42 +1,43 @@
1
- import { FormController as r } from "./components/FormController.js";
2
- import { NexusField as x } from "./components/NexusField.js";
3
- import { NexusForm as f } from "./components/NexusForm.js";
4
- import { NexusFormProvider as u } from "./components/NexusFormProvider.js";
1
+ import './styles.css';/* empty css */
2
+ import { FormController as t } from "./components/FormController.js";
3
+ import { NexusField as m } from "./components/NexusField.js";
4
+ import { NexusForm as p } from "./components/NexusForm.js";
5
+ import { NexusFormProvider as s } from "./components/NexusFormProvider.js";
5
6
  import { NexusLayout as a } from "./components/NexusLayout.js";
6
- import { NexusObject as l } from "./components/NexusObject.js";
7
- import { FieldInheritContext as n } from "./contexts/FieldInheritContext.js";
8
- import { GridContext as C } from "./contexts/GridContext.js";
9
- import { LayoutConfigContext as h } from "./contexts/LayoutConfigContext.js";
10
- import { useEngine as W } from "./hooks/useEngine.js";
11
- import { useFieldState as y } from "./hooks/useFieldState.js";
12
- import { useFieldValidator as S } from "./hooks/useFieldValidator.js";
13
- import { useFieldValue as b } from "./hooks/useFieldValue.js";
14
- import { useForm as v } from "./hooks/useForm.js";
15
- import { useFormConfig as D } from "./hooks/useFormConfig.js";
16
- import { useFormData as G } from "./hooks/useFormData.js";
17
- import { useWatch as M } from "./hooks/useWatch.js";
18
- import { useWatchAll as P } from "./hooks/useWatchAll.js";
19
- import { useWatchMultiple as q } from "./hooks/useWatchMultiple.js";
20
- import { useWatchState as z } from "./hooks/useWatchState.js";
7
+ import { NexusObject as F } from "./components/NexusObject.js";
8
+ import { FieldInheritContext as d } from "./contexts/FieldInheritContext.js";
9
+ import { GridContext as c } from "./contexts/GridContext.js";
10
+ import { LayoutConfigContext as N } from "./contexts/LayoutConfigContext.js";
11
+ import { useEngine as g } from "./hooks/useEngine.js";
12
+ import { useFieldState as L } from "./hooks/useFieldState.js";
13
+ import { useFieldValidator as V } from "./hooks/useFieldValidator.js";
14
+ import { useFieldValue as j } from "./hooks/useFieldValue.js";
15
+ import { useForm as A } from "./hooks/useForm.js";
16
+ import { useFormConfig as E } from "./hooks/useFormConfig.js";
17
+ import { useFormData as I } from "./hooks/useFormData.js";
18
+ import { useWatch as O } from "./hooks/useWatch.js";
19
+ import { useWatchAll as k } from "./hooks/useWatchAll.js";
20
+ import { useWatchMultiple as w } from "./hooks/useWatchMultiple.js";
21
+ import { useWatchState as B } from "./hooks/useWatchState.js";
21
22
  export {
22
- n as FieldInheritContext,
23
- r as FormController,
24
- C as GridContext,
25
- h as LayoutConfigContext,
26
- x as NexusField,
27
- f as NexusForm,
28
- u as NexusFormProvider,
23
+ d as FieldInheritContext,
24
+ t as FormController,
25
+ c as GridContext,
26
+ N as LayoutConfigContext,
27
+ m as NexusField,
28
+ p as NexusForm,
29
+ s as NexusFormProvider,
29
30
  a as NexusLayout,
30
- l as NexusObject,
31
- W as useEngine,
32
- y as useFieldState,
33
- S as useFieldValidator,
34
- b as useFieldValue,
35
- v as useForm,
36
- D as useFormConfig,
37
- G as useFormData,
38
- M as useWatch,
39
- P as useWatchAll,
40
- q as useWatchMultiple,
41
- z as useWatchState
31
+ F as NexusObject,
32
+ g as useEngine,
33
+ L as useFieldState,
34
+ V as useFieldValidator,
35
+ j as useFieldValue,
36
+ A as useForm,
37
+ E as useFormConfig,
38
+ I as useFormData,
39
+ O as useWatch,
40
+ k as useWatchAll,
41
+ w as useWatchMultiple,
42
+ B as useWatchState
42
43
  };
@@ -0,0 +1 @@
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}
@@ -0,0 +1 @@
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}
@@ -0,0 +1 @@
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 +1 @@
1
- (function(a,N){typeof exports=="object"&&typeof module<"u"?N(exports,require("@xbeeant/form-engine"),require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","@xbeeant/form-engine","react/jsx-runtime","react"],N):(a=typeof globalThis<"u"?globalThis:a||self,N(a.NexusFormEngineReact={},a.formEngine,a.jsxRuntime,a.react))})(this,(function(a,N,c,s){"use strict";class P{engine;formElementRef;getOnFinish;getOnFinishFailed;removeHiddenData=!0;watchers=new Map;globalWatcher=null;constructor(t){this.engine=t??new N.NexusEngine,this.engine.hasPlugin("async-validator")||this.engine.use(new N.AsyncValidatorPlugin(this.engine)),this.formElementRef={current:null},this.getOnFinish=()=>()=>{},this.getOnFinishFailed=()=>()=>{}}_bind(t,i,r){this.formElementRef.current=t,this.getOnFinish=i,this.getOnFinishFailed=r,this.engine.registerOnFieldValueChange((l,n)=>this._onFieldValueChange(l,n))}_syncConfig(t){if(t.removeHiddenData!==void 0&&(this.removeHiddenData=t.removeHiddenData),t.watch){this.watchers.clear(),this.globalWatcher=null;for(const[i,r]of Object.entries(t.watch))i==="#"?this.globalWatcher=r:this.watchers.set(i,r)}}_onFieldValueChange(t,i){const r=this.engine.getFormData(),l=this.removeHiddenData?r:this.engine.getAllFormData();this.globalWatcher&&this.globalWatcher(l,l,t);const n=this.watchers.get(t);n&&n(i,l)}_getEngine(){return this.engine}async submit(){const t=await this.engine.validate();if(t.size>0){this.focusFirstError(t),this.getOnFinishFailed()?.(t);return}const i=this.removeHiddenData?this.engine.getFormData():this.engine.getAllFormData();await this.getOnFinish()?.(i)}focusFirstError(t){const i=this.formElementRef.current;if(!i||t.size===0)return;const r=new Set(t.keys());requestAnimationFrame(()=>{const n=i.querySelectorAll("[data-nexus-field]");for(const o of Array.from(n)){const u=o.getAttribute("data-nexus-field");if(!u||!r.has(u))continue;o.scrollIntoView({behavior:"smooth",block:"center"}),o.querySelector('input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')?.focus();return}})}resetFields(){this.engine.reset()}setErrorFields(t){this.engine.setErrorFields(t)}setValues(t){this.engine.setFieldValues(t)}setValueByPath(t,i){this.engine.setFieldValue(t,i)}setSchemaByPath(t,i){this.engine.setSchemaByPath(t,i)}setSchema(t){this.engine.setSchema(t)}getValues(t){return this.engine.getFormData(t)}getHiddenValues(){return this.engine.getHiddenValues()}getAllValues(){return this.engine.getAllFormData()}getValueByPath(t){return this.engine.getFieldValue(t)}registerValidator(t,i){this.engine.registerFieldValidator(t,i)}unregisterValidator(t,i){this.engine.unregisterFieldValidator(t,i)}revalidateField(t){this.engine.validateField(t,{trigger:"change"})}getSchema(){return this.engine.getSchema()}removeErrorField(t){this.engine.removeErrorField(t)}scrollToPath(t){this.formElementRef.current?.querySelector(`[data-nexus-field="${t}"]`)?.scrollIntoView({behavior:"smooth",block:"center"})}getFieldError(t){return this.engine.getFieldError(t)}getFieldsError(){return this.engine.getFieldsError()}validateFields(t){return this.engine.validate(t)}getFieldState(t){return this.engine.getFieldState(t)}}const R=s.createContext({}),M=s.createContext(null),k=s.createContext({}),H=s.createContext(null);function S(){const e=s.useContext(H);if(!e)throw new Error("[NexusField] Must be used within <NexusFormProvider>");return e}function q(e,t){if(e!==void 0)return e;if(t&&t.column>0)return Math.max(1,Math.round(t.column/24))}function J({dataPath:e,layoutKey:t}){const{engine:i,config:r,form:l}=S();s.useSyncExternalStore(y=>i.subscribeField(e,y),()=>i.getFieldVersion(e),()=>i.getFieldVersion(e));const n=i.getFieldState(e),o=s.useContext(M),u=s.useContext(k),g=s.useContext(R),f=s.useCallback(y=>{i.setFieldValue(e,y)},[i,e]),b=s.useCallback(y=>{y.currentTarget.contains(y.relatedTarget)||i.validateField(e,{trigger:"blur"})},[i,e]),d=s.useMemo(()=>n?.meta.enum?n.meta.enum.map((y,E)=>({value:y,label:n.meta.enumNames?.[E]??String(y)})):n?.props.options,[n?.meta.enum,n?.meta.enumNames,n?.props.options]),F=s.useMemo(()=>{const y={};if(n?.reactions){for(const E of n.reactions)if(E.dependencies)for(const p of E.dependencies)y[p]=i.getFieldValue(p)}return y},[n?.reactions,i]);if(!n)return i.getSnapshot()>0&&console.warn(`[NexusField] Field not found: ${e}`),null;if(g.visible===!1||!n.visible)return u.removeHidden===!0?null:c.jsx("div",{className:"hidden","data-nexus-hidden":e});const h=r.readOnly||g.readOnly===!0||n.readOnly,C=g.disabled===!0||n.disabled,v=h&&!!n.meta.readOnlyWidget,x=v?n.meta.readOnlyWidget:n.meta.widget;let V=i.getWidget(x);if(!V&&v&&(V=i.getWidget(n.meta.widget)),!V)return c.jsxs("div",{className:"text-xs text-red-500","data-nexus-field":e,children:['⚠️ Widget "',n.meta.widget,'" 未注册 (path: ',e,")"]});const j=n.meta.displayType??r.displayType,D=n.meta.labelWidth??r.labelWidth,m=n.meta.column??r.column,O=q(n.meta.colSpan,o),w={...n.meta.width?{width:n.meta.width,flexShrink:0}:{},...O?{gridColumn:`span ${O}`}:{}};return c.jsx("div",{"data-nexus-field":e,onBlur:b,style:Object.keys(w).length>0?w:void 0,children:c.jsx(V,{dataPath:e,path:e,value:n.value,onChange:f,disabled:C,readOnly:h,loading:n.loading,required:n.required,title:n.meta.title,description:n.meta.description,placeholder:n.meta.placeholder,label:n.meta.label,options:d,errors:n.errors,extra:n.meta.extra,displayType:j,labelWidth:D,column:m,form:l,dependValues:F,items:n.meta.items,...n.props},t)})}function B({node:e}){const{engine:t}=S(),i=t.getLayout(e.type),r=e.children.map((h,C)=>$(h,C)),l=s.useContext(M),n=q(e.props.colSpan,l),o={...n?{gridColumn:`span ${n}`}:{},...e.props.width?{width:e.props.width,flexShrink:0}:{}},u=s.useMemo(()=>({removeHidden:e.props.removeHidden}),[e.props.removeHidden]);if(!i)return c.jsx(k.Provider,{value:u,children:c.jsxs("div",{"data-nexus-layout":e.type,className:"mb-4",style:Object.keys(o).length>0?o:void 0,children:[e.title&&c.jsx("div",{className:"mb-2 font-bold",children:e.title}),r]})});const{displayType:g,labelWidth:f,colSpan:b,width:d,...F}=e.props;return c.jsx(k.Provider,{value:u,children:c.jsx("div",{style:Object.keys(o).length>0?o:void 0,children:c.jsx(i,{...F,node:e,title:e.title,children:r})})})}function I({node:e}){const{engine:t,config:i}=S(),r=s.useContext(R);s.useSyncExternalStore(F=>t.subscribeField(e.dataPath,F),()=>t.getFieldVersion(e.dataPath),()=>t.getFieldVersion(e.dataPath));const l=t.getFieldState(e.dataPath),[n,o]=s.useState(!1),u=(i.column??1)>1,g=u?{gridTemplateColumns:`repeat(${i.column}, 1fr)`}:{},f={disabled:r.disabled??(l?.disabled===!0?!0:void 0),readOnly:r.readOnly??(l?.readOnly===!0?!0:void 0),visible:r.visible===!1||l?.visible===!1?!1:void 0},b=f.visible===!1,d=()=>o(F=>!F);return c.jsx(R.Provider,{value:f,children:c.jsxs("div",{"data-nexus-object":e.dataPath,className:`mb-4 ${b?"hidden":""}`,children:[e.title&&c.jsxs("div",{onClick:d,className:"flex gap-1 mb-2 cursor-pointer select-none border-none bg-transparent p-0 font-bold",children:[c.jsx("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:`align-middle transition-transform duration-200 ease-in-out ${n?"rotate-0":"rotate-90"}`,"aria-hidden":"true",children:c.jsx("polyline",{points:"9 18 15 12 9 6"})}),e.title]}),c.jsx("div",{className:`${u?"grid gap-x-4":""} ${n?"hidden":""}`,style:Object.keys(g).length>0?g:void 0,children:e.children.map((F,h)=>$(F,h))})]})})}function $(e,t){return e.type==="field"?c.jsx(J,{dataPath:e.dataPath,layoutKey:e.layoutKey},e.layoutKey||e.dataPath):e.type==="object"?c.jsx(I,{node:e},`object-${e.layoutKey}-${t}`):c.jsx(B,{node:e},`layout-${e.type}-${t}`)}function K({engine:e,config:t,form:i,children:r}){const l=s.useMemo(()=>({engine:e,config:t,form:i}),[e,t,i]);return c.jsx(H.Provider,{value:l,children:r})}function T(){}function Y({form:e,schema:t,initialValues:i,widgets:r,layouts:l,onFinish:n,onFinishFailed:o,footer:u=!1,className:g,style:f,children:b,labelCol:d,labelWidth:F,colon:h,label:C,displayType:v,readOnly:x,column:V,watch:j,removeHiddenData:D=!0}){const m=e._getEngine(),O=s.useRef(null),w=v??t?.displayType??"row",y=C??t?.label??!0,E=h??t?.colon,p=F??t?.labelWidth,G=x??t?.readOnly??!1,W=V??t?.column;s.useEffect(()=>{r&&m.registerWidgets(r),l&&m.registerLayouts(l)},[m,r,l]);const z=s.useRef(!0),ae=s.useRef(i);s.useEffect(()=>{t&&(z.current?(m.init(t,ae.current),z.current=!1):m.init(t,m.getFormData()))},[m,t]);const U=s.useRef(T),Q=s.useRef(T);U.current=n??T,Q.current=o??T,s.useEffect(()=>{e._bind(O.current,()=>U.current,()=>Q.current)},[e]),s.useEffect(()=>{e._syncConfig({removeHiddenData:D,watch:j})},[e,D,j]);const ce=s.useSyncExternalStore(m.subscribeStore,m.getSnapshot,m.getSnapshot),de=s.useMemo(()=>m.getRenderTree(),[m,ce]),fe=s.useCallback(async A=>{A.preventDefault(),await e.submit()},[e]),ge=s.useCallback(()=>{e.resetFields()},[e]);let L=null;u===!0?L=c.jsxs("div",{className:"mt-4",children:[c.jsx("button",{type:"submit",children:"提交"})," ",c.jsx("button",{type:"button",onClick:ge,children:"重置"})]}):u&&(L=u);const X=s.useMemo(()=>{if(d)return d;if(p)return{style:{width:typeof p=="number"?`${p}px`:p}}},[d,p]),he=s.useMemo(()=>({labelCol:X,labelWidth:p,colon:E,label:y,displayType:w,readOnly:G,column:W}),[X,p,E,y,w,G,W]);return c.jsx(K,{engine:m,config:he,form:e,children:c.jsxs("form",{ref:O,onSubmit:fe,className:g,style:{...f,...W&&W>1?{display:"grid",gridTemplateColumns:`repeat(${W}, 1fr)`,gap:"0 16px"}:{}},noValidate:!0,children:[de.map((A,be)=>$(A,be)),!x&&L,b]})})}function _(){const{engine:e}=S();return e}function Z(e){const{engine:t}=S();return s.useSyncExternalStore(i=>t.subscribeField(e,i),()=>t.getFieldVersion(e),()=>t.getFieldVersion(e)),t.getFieldState(e)}function ee(e,t,i,r){const{engine:l}=S(),n=s.useRef(i);n.current=i;const o=s.useRef(r?.dependsOn);o.current=r?.dependsOn;const u=r?.deps;s.useEffect(()=>{if(!e||!t)return;const g=n.current;return e.registerValidator(t,g),()=>{e.unregisterValidator(t,g)}},[e,t,...u??[]]),s.useEffect(()=>{if(!t||!o.current||o.current.length===0)return;const g=l;if(!g)return;const f=t,b=o.current.map(d=>g.subscribeField(d,()=>{e?.revalidateField(f)}));return()=>{for(const d of b)d()}},[l,e,t])}function te(e){const t=_();return s.useSyncExternalStore(i=>t.subscribeField(e,i),()=>t.getFieldVersion(e),()=>t.getFieldVersion(e)),t.getFieldValue(e)}function ne(e){const t=s.useRef(null);return t.current||(t.current=new P(e)),[t.current]}function ie(){const{config:e}=S();return e}function se(){const e=_(),t=s.useSyncExternalStore(e.subscribeStore,e.getSnapshot,e.getSnapshot);return s.useMemo(()=>e.getFormData(),[e,t])}function re(e,t,i,r){const{deep:l=!1}=r||{},n=s.useRef(void 0),o=s.useRef(i);return o.current=i,s.useEffect(()=>{if(!e)return;const u=e.getFieldValue(t);n.current===void 0&&(n.current=u),(l?JSON.stringify(u)!==JSON.stringify(n.current):u!==n.current)&&(n.current=u,o.current(u));const f=e.subscribe(t,b=>{const d=b.value;(l?JSON.stringify(d)!==JSON.stringify(n.current):d!==n.current)&&(n.current=d,o.current(d))});return()=>{f()}},[e,t,l]),n.current}function le(e,t,i){const{deep:r=!1}=i||{},l=s.useRef({}),n=s.useRef({}),o=s.useRef(t);return o.current=t,s.useEffect(()=>{if(!e)return;const u=e.getFormData();l.current=u,n.current=u,o.current(u);const g=e.subscribeAll(f=>{if(r)JSON.stringify(f)!==JSON.stringify(l.current)&&(l.current=f,n.current=f,o.current(f));else{let b=!1;for(const d in f)if(f[d]!==l.current[d]){b=!0;break}b&&(l.current=f,n.current=f,o.current(f))}});return()=>{g()}},[e,r]),n.current}function oe(e,t,i,r){const{deep:l=!1}=r||{},n=s.useRef({}),o=s.useRef({}),u=s.useRef(i);u.current=i;const g=s.useRef(t);g.current=t;const f=[...t].sort().join(",");return s.useEffect(()=>{if(!e)return;const b=g.current,d={};for(const h of b)d[h]=e.getFieldValue(h);n.current=d,o.current=d,u.current(d);const F=b.map(h=>e.subscribe(h,C=>{const v=C.value,x=n.current[h];(l?JSON.stringify(v)!==JSON.stringify(x):v!==x)&&(n.current[h]=v,o.current={[h]:v},u.current(o.current))}));return()=>{for(const h of F)h()}},[e,l,f]),o.current}function ue(e,t,i){const r=s.useRef(void 0),l=s.useRef(i);return l.current=i,s.useEffect(()=>{if(!e)return;const n=e.getFieldState(t);r.current=n,n&&l.current(n);const o=e.subscribe(t,u=>{r.current=u,l.current(u)});return()=>{o()}},[e,t]),r.current||{}}a.FieldInheritContext=R,a.FormController=P,a.GridContext=M,a.LayoutConfigContext=k,a.NexusField=J,a.NexusForm=Y,a.NexusFormProvider=K,a.NexusLayout=B,a.NexusObject=I,a.useEngine=_,a.useFieldState=Z,a.useFieldValidator=ee,a.useFieldValue=te,a.useForm=ne,a.useFormConfig=ie,a.useFormData=se,a.useWatch=re,a.useWatchAll=le,a.useWatchMultiple=oe,a.useWatchState=ue,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(c,x){typeof exports=="object"&&typeof module<"u"?x(exports,require("@xbeeant/form-engine"),require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","@xbeeant/form-engine","react/jsx-runtime","react"],x):(c=typeof globalThis<"u"?globalThis:c||self,x(c.NexusFormEngineReact={},c.formEngine,c.jsxRuntime,c.react))})(this,(function(c,x,a,s){"use strict";class J{engine;formElementRef;getOnFinish;getOnFinishFailed;removeHiddenData=!0;watchers=new Map;globalWatcher=null;constructor(t){this.engine=t??new x.NexusEngine,this.engine.hasPlugin("async-validator")||this.engine.use(new x.AsyncValidatorPlugin(this.engine)),this.formElementRef={current:null},this.getOnFinish=()=>()=>{},this.getOnFinishFailed=()=>()=>{}}_bind(t,i,r){this.formElementRef.current=t,this.getOnFinish=i,this.getOnFinishFailed=r,this.engine.registerOnFieldValueChange((l,n)=>this._onFieldValueChange(l,n))}_syncConfig(t){if(t.removeHiddenData!==void 0&&(this.removeHiddenData=t.removeHiddenData),t.watch){this.watchers.clear(),this.globalWatcher=null;for(const[i,r]of Object.entries(t.watch))i==="#"?this.globalWatcher=r:this.watchers.set(i,r)}}_onFieldValueChange(t,i){const r=this.engine.getFormData(),l=this.removeHiddenData?r:this.engine.getAllFormData();this.globalWatcher&&this.globalWatcher(l,l,t);const n=this.watchers.get(t);n&&n(i,l)}_getEngine(){return this.engine}getEngine(){return this.engine}async submit(){const t=await this.engine.validate();if(t.size>0){this.focusFirstError(t),this.getOnFinishFailed()?.(t);return}const i=this.removeHiddenData?this.engine.getFormData():this.engine.getAllFormData();await this.getOnFinish()?.(i)}focusFirstError(t){const i=this.formElementRef.current;if(!i||t.size===0)return;const r=new Set(t.keys());requestAnimationFrame(()=>{const n=i.querySelectorAll("[data-nexus-field]");for(const o of Array.from(n)){const u=o.getAttribute("data-nexus-field");if(!u||!r.has(u))continue;o.scrollIntoView({behavior:"smooth",block:"center"}),o.querySelector('input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')?.focus();return}})}resetFields(){this.engine.reset()}setErrorFields(t){this.engine.setErrorFields(t)}setValues(t){this.engine.setFieldValues(t)}setValueByPath(t,i){this.engine.setFieldValue(t,i)}setSchemaByPath(t,i){this.engine.setSchemaByPath(t,i)}setSchema(t){this.engine.setSchema(t)}getValues(t){return this.engine.getFormData(t)}getHiddenValues(){return this.engine.getHiddenValues()}getAllValues(){return this.engine.getAllFormData()}getValueByPath(t){return this.engine.getFieldValue(t)}registerValidator(t,i){this.engine.registerFieldValidator(t,i)}unregisterValidator(t,i){this.engine.unregisterFieldValidator(t,i)}revalidateField(t){this.engine.validateField(t,{trigger:"change"})}getSchema(){return this.engine.getSchema()}removeErrorField(t){this.engine.removeErrorField(t)}scrollToPath(t){this.formElementRef.current?.querySelector(`[data-nexus-field="${t}"]`)?.scrollIntoView({behavior:"smooth",block:"center"})}getFieldError(t){return this.engine.getFieldError(t)}getFieldsError(){return this.engine.getFieldsError()}validateFields(t){return this.engine.validate(t)}getFieldState(t){return this.engine.getFieldState(t)}}const k=s.createContext({}),P=s.createContext(null),j=s.createContext({}),B=s.createContext(null);function V(){const e=s.useContext(B);if(!e)throw new Error("[NexusField] Must be used within <NexusFormProvider>");return e}function I(e,t){if(e!==void 0)return e;if(t&&t.column>0)return Math.max(1,Math.round(t.column/24))}function K({dataPath:e,layoutKey:t}){const{engine:i,config:r,form:l}=V();s.useSyncExternalStore(F=>i.subscribeField(e,F),()=>i.getFieldVersion(e),()=>i.getFieldVersion(e));const n=i.getFieldState(e),o=s.useContext(P),u=s.useContext(j),d=s.useContext(k),f=s.useCallback(F=>{i.setFieldValue(e,F)},[i,e]),b=s.useCallback(F=>{F.currentTarget.contains(F.relatedTarget)||i.validateField(e,{trigger:"blur"})},[i,e]),g=s.useMemo(()=>n?.meta.enum?n.meta.enum.map((F,v)=>({value:F,label:n.meta.enumNames?.[v]??String(F)})):n?.props.options,[n?.meta.enum,n?.meta.enumNames,n?.props.options]),y=s.useMemo(()=>{const F={};if(n?.reactions){for(const v of n.reactions)if(v.dependencies)for(const R of v.dependencies)F[R]=i.getFieldValue(R)}return F},[n?.reactions,i]);if(!n)return i.getSnapshot()>0&&console.warn(`[NexusField] Field not found: ${e}`),null;if(d.visible===!1||!n.visible)return u.removeHidden===!0?null:a.jsx("div",{className:"hidden","data-nexus-hidden":e});const h=r.readOnly||d.readOnly===!0||n.readOnly,E=d.disabled===!0||n.disabled,S=h&&!!n.meta.readOnlyWidget,N=S?n.meta.readOnlyWidget:n.meta.widget;let C=i.getWidget(N);if(!C&&S&&(C=i.getWidget(n.meta.widget)),!C)return a.jsxs("div",{className:"text-xs text-red-500","data-nexus-field":e,children:['⚠️ Widget "',n.meta.widget,'" 未注册 (path: ',e,")"]});const A=n.meta.displayType??r.displayType,D=n.meta.labelWidth??r.labelWidth,M=n.meta.column??r.column,m=I(n.meta.colSpan,o),O={...n.meta.width?{width:n.meta.width,flexShrink:0}:{},...m?{gridColumn:`span ${m}`}:{}},w=i.getFieldWrapper(),$={label:n.meta.label,title:n.meta.title,description:n.meta.description,errors:n.errors,required:n.required,extra:n.meta.extra,width:n.meta.width,displayType:A,labelWidth:D,column:M},W={dataPath:e,path:e,value:n.value,onChange:f,disabled:E,readOnly:h,loading:n.loading,placeholder:n.meta.placeholder,options:g,form:l,dependValues:y,items:n.meta.items,...n.props};let p;return w?p=a.jsx(w,{...$,children:a.jsx(C,{...W})},t):p=a.jsx(C,{...W},t),a.jsx("div",{"data-nexus-field":e,onBlur:b,style:Object.keys(O).length>0?O:void 0,children:p})}function G({node:e}){const{engine:t}=V(),i=t.getLayout(e.type),r=e.children.map((h,E)=>_(h,E)),l=s.useContext(P),n=I(e.props.colSpan,l),o={...n?{gridColumn:`span ${n}`}:{},...e.props.width?{width:e.props.width,flexShrink:0}:{}},u=s.useMemo(()=>({removeHidden:e.props.removeHidden}),[e.props.removeHidden]);if(!i)return a.jsx(j.Provider,{value:u,children:a.jsxs("div",{"data-nexus-layout":e.type,className:"mb-4",style:Object.keys(o).length>0?o:void 0,children:[e.title&&a.jsx("div",{className:"mb-2 font-bold",children:e.title}),r]})});const{displayType:d,labelWidth:f,colSpan:b,width:g,...y}=e.props;return a.jsx(j.Provider,{value:u,children:a.jsx("div",{style:Object.keys(o).length>0?o:void 0,children:a.jsx(i,{...y,node:e,title:e.title,children:r})})})}function z({node:e}){const{engine:t,config:i}=V(),r=s.useContext(k);s.useSyncExternalStore(y=>t.subscribeField(e.dataPath,y),()=>t.getFieldVersion(e.dataPath),()=>t.getFieldVersion(e.dataPath));const l=t.getFieldState(e.dataPath),[n,o]=s.useState(!1),u=(i.column??1)>1,d=u?{gridTemplateColumns:`repeat(${i.column}, 1fr)`}:{},f={disabled:r.disabled??(l?.disabled===!0?!0:void 0),readOnly:r.readOnly??(l?.readOnly===!0?!0:void 0),visible:r.visible===!1||l?.visible===!1?!1:void 0},b=f.visible===!1,g=()=>o(y=>!y);return a.jsx(k.Provider,{value:f,children:a.jsxs("div",{"data-nexus-object":e.dataPath,className:`mb-4 ${b?"hidden":""}`,children:[e.title&&a.jsxs("div",{onClick:g,className:"flex gap-1 mb-2 cursor-pointer select-none border-none bg-transparent p-0 font-bold",children:[a.jsx("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:`align-middle transition-transform duration-200 ease-in-out ${n?"rotate-0":"rotate-90"}`,"aria-hidden":"true",children:a.jsx("polyline",{points:"9 18 15 12 9 6"})}),e.title]}),a.jsx("div",{className:`${u?"grid gap-x-4":""} ${n?"hidden":""}`,style:Object.keys(d).length>0?d:void 0,children:e.children.map((y,h)=>_(y,h))})]})})}function _(e,t){return e.type==="field"?a.jsx(K,{dataPath:e.dataPath,layoutKey:e.layoutKey},e.layoutKey||e.dataPath):e.type==="object"?a.jsx(z,{node:e},`object-${e.layoutKey}-${t}`):a.jsx(G,{node:e},`layout-${e.type}-${t}`)}function U({engine:e,config:t,form:i,children:r}){const l=s.useMemo(()=>({engine:e,config:t,form:i}),[e,t,i]);return a.jsx(B.Provider,{value:l,children:r})}function T(){}function ne({form:e,schema:t,initialValues:i,widgets:r,layouts:l,onFinish:n,onFinishFailed:o,onMount:u,footer:d=!1,className:f,style:b,children:g,labelCol:y,labelWidth:h,colon:E,label:S,displayType:N,readOnly:C,column:A,watch:D,removeHiddenData:M=!0}){const m=e._getEngine(),O=s.useRef(null),w=N??t?.displayType??"row",$=S??t?.label??!0,W=E??t?.colon,p=h??t?.labelWidth,F=C??t?.readOnly??!1,v=A??t?.column;s.useEffect(()=>{r&&m.registerWidgets(r),l&&m.registerLayouts(l)},[m,r,l]);const R=s.useRef(!0),ge=s.useRef(i);s.useEffect(()=>{t&&(R.current?(m.init(t,ge.current),R.current=!1):m.init(t,m.getFormData()))},[m,t]);const Q=s.useRef(u);Q.current=u;const X=s.useRef(!1),Y=!t||typeof t=="object"&&Object.keys(t).length===0;s.useEffect(()=>{X.current||Y||(X.current=!0,Q.current?.())},[Y]);const Z=s.useRef(T),ee=s.useRef(T);Z.current=n??T,ee.current=o??T,s.useEffect(()=>{e._bind(O.current,()=>Z.current,()=>ee.current)},[e]),s.useEffect(()=>{e._syncConfig({removeHiddenData:M,watch:D})},[e,M,D]);const he=s.useSyncExternalStore(m.subscribeStore,m.getSnapshot,m.getSnapshot),be=s.useMemo(()=>m.getRenderTree(),[m,he]),ye=s.useCallback(async q=>{q.preventDefault(),await e.submit()},[e]),me=s.useCallback(()=>{e.resetFields()},[e]);let H=null;d===!0?H=a.jsxs("div",{className:"mt-4",children:[a.jsx("button",{type:"submit",children:"提交"})," ",a.jsx("button",{type:"button",onClick:me,children:"重置"})]}):d&&(H=d);const te=s.useMemo(()=>{if(y)return y;if(p)return{style:{width:typeof p=="number"?`${p}px`:p}}},[y,p]),Fe=s.useMemo(()=>({labelCol:te,labelWidth:p,colon:W,label:$,displayType:w,readOnly:F,column:v}),[te,p,W,$,w,F,v]);return a.jsx(U,{engine:m,config:Fe,form:e,children:a.jsxs("form",{ref:O,onSubmit:ye,className:f,style:{...b,...v&&v>1?{display:"grid",gridTemplateColumns:`repeat(${v}, 1fr)`,gap:"0 16px"}:{}},noValidate:!0,children:[be.map((q,pe)=>_(q,pe)),!C&&H,g]})})}function L(){const{engine:e}=V();return e}function ie(e){const{engine:t}=V();return s.useSyncExternalStore(i=>t.subscribeField(e,i),()=>t.getFieldVersion(e),()=>t.getFieldVersion(e)),t.getFieldState(e)}function se(e,t,i,r){const{engine:l}=V(),n=s.useRef(i);n.current=i;const o=s.useRef(r?.dependsOn);o.current=r?.dependsOn;const u=r?.deps;s.useEffect(()=>{if(!e||!t)return;const d=n.current;return e.registerValidator(t,d),()=>{e.unregisterValidator(t,d)}},[e,t,...u??[]]),s.useEffect(()=>{if(!t||!o.current||o.current.length===0)return;const d=l;if(!d)return;const f=t,b=o.current.map(g=>d.subscribeField(g,()=>{e?.revalidateField(f)}));return()=>{for(const g of b)g()}},[l,e,t])}function re(e){const t=L();return s.useSyncExternalStore(i=>t.subscribeField(e,i),()=>t.getFieldVersion(e),()=>t.getFieldVersion(e)),t.getFieldValue(e)}function le(e,t){const i=s.useRef(null);return i.current||(i.current=new J(t??(e?new x.NexusEngine({formId:e}):void 0))),[i.current]}function oe(){const{config:e}=V();return e}function ue(){const e=L(),t=s.useSyncExternalStore(e.subscribeStore,e.getSnapshot,e.getSnapshot);return s.useMemo(()=>e.getFormData(),[e,t])}function ce(e,t,i,r){const{deep:l=!1}=r||{},n=s.useRef(void 0),o=s.useRef(i);return o.current=i,s.useEffect(()=>{if(!e)return;const u=e.getFieldValue(t);n.current===void 0&&(n.current=u),(l?JSON.stringify(u)!==JSON.stringify(n.current):u!==n.current)&&(n.current=u,o.current(u));const f=e.subscribe(t,b=>{const g=b.value;(l?JSON.stringify(g)!==JSON.stringify(n.current):g!==n.current)&&(n.current=g,o.current(g))});return()=>{f()}},[e,t,l]),n.current}function ae(e,t,i){const{deep:r=!1}=i||{},l=s.useRef({}),n=s.useRef({}),o=s.useRef(t);return o.current=t,s.useEffect(()=>{if(!e)return;const u=e.getFormData();l.current=u,n.current=u,o.current(u);const d=e.subscribeAll(f=>{if(r)JSON.stringify(f)!==JSON.stringify(l.current)&&(l.current=f,n.current=f,o.current(f));else{let b=!1;for(const g in f)if(f[g]!==l.current[g]){b=!0;break}b&&(l.current=f,n.current=f,o.current(f))}});return()=>{d()}},[e,r]),n.current}function de(e,t,i,r){const{deep:l=!1}=r||{},n=s.useRef({}),o=s.useRef({}),u=s.useRef(i);u.current=i;const d=s.useRef(t);d.current=t;const f=[...t].sort().join(",");return s.useEffect(()=>{if(!e)return;const b=d.current,g={};for(const h of b)g[h]=e.getFieldValue(h);n.current=g,o.current=g,u.current(g);const y=b.map(h=>e.subscribe(h,E=>{const S=E.value,N=n.current[h];(l?JSON.stringify(S)!==JSON.stringify(N):S!==N)&&(n.current[h]=S,o.current={[h]:S},u.current(o.current))}));return()=>{for(const h of y)h()}},[e,l,f]),o.current}function fe(e,t,i){const r=s.useRef(void 0),l=s.useRef(i);return l.current=i,s.useEffect(()=>{if(!e)return;const n=e.getFieldState(t);r.current=n,n&&l.current(n);const o=e.subscribe(t,u=>{r.current=u,l.current(u)});return()=>{o()}},[e,t]),r.current||{}}c.FieldInheritContext=k,c.FormController=J,c.GridContext=P,c.LayoutConfigContext=j,c.NexusField=K,c.NexusForm=ne,c.NexusFormProvider=U,c.NexusLayout=G,c.NexusObject=z,c.useEngine=L,c.useFieldState=ie,c.useFieldValidator=se,c.useFieldValue=re,c.useForm=le,c.useFormConfig=oe,c.useFormData=ue,c.useWatch=ce,c.useWatchAll=ae,c.useWatchMultiple=de,c.useWatchState=fe,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbeeant/form-engine-react",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -70,6 +70,7 @@
70
70
  }
71
71
  },
72
72
  "dependencies": {
73
- "@xbeeant/form-engine": "^0.0.1"
74
- }
73
+ "@xbeeant/form-engine": "^0.0.3"
74
+ },
75
+ "gitHead": "32282a81ac6acd1c5b26ccf081504b9da53e07de"
75
76
  }
@@ -112,6 +112,13 @@ export class FormController implements NexusFormInstance {
112
112
  return this.engine;
113
113
  }
114
114
 
115
+ /**
116
+ * 获取底层 Engine 实例(用于跨表单联动:linkForm / setFormId 等)
117
+ */
118
+ getEngine(): NexusEngine {
119
+ return this.engine;
120
+ }
121
+
115
122
  async submit(): Promise<void> {
116
123
  const errors = await this.engine.validate();
117
124
  if (errors.size > 0) {
@@ -1,4 +1,4 @@
1
- import type { CSSProperties, FocusEvent } from 'react';
1
+ import type { CSSProperties, FocusEvent, ReactElement } from 'react';
2
2
  import { useCallback, useContext, useMemo, useSyncExternalStore } from 'react';
3
3
  import { FieldInheritContext } from '../contexts/FieldInheritContext';
4
4
  import { GridContext } from '../contexts/GridContext';
@@ -146,37 +146,63 @@ export function NexusField({ dataPath, layoutKey }: NexusFieldProps) {
146
146
  ...(effectiveColSpan ? { gridColumn: `span ${effectiveColSpan}` } : {}),
147
147
  };
148
148
 
149
+ // 默认包裹:所有 widget 统一由 FieldWrapper 包裹(引擎注册,UI 层提供),
150
+ // 仅当 label === false(字段级或表单级)时 FieldWrapper 不包裹 Form.Item。
151
+ // 未注册 FieldWrapper(纯 react 无 ui 层)时直接渲染裸 widget。
152
+ const FieldWrapper = engine.getFieldWrapper();
153
+
154
+ // Form.Item 消费的元数据 props 剥离给 FieldWrapper,避免透传到底层 antd 控件:
155
+ // - required: 会让 <input required> 触发浏览器原生校验
156
+ // - errors/title/description/label/extra/width/displayType/labelWidth/column:
157
+ // 作为未知属性透传到 DOM 会产生 React 警告
158
+ const fieldWrapperProps = {
159
+ label: state.meta.label,
160
+ title: state.meta.title,
161
+ description: state.meta.description,
162
+ errors: state.errors,
163
+ required: state.required,
164
+ extra: state.meta.extra,
165
+ width: state.meta.width,
166
+ displayType: fieldDisplayType,
167
+ labelWidth: fieldLabelWidth,
168
+ column: fieldColumn,
169
+ };
170
+
171
+ // widget 仅接收控件相关 props(value/onChange/状态/选项/表单引用/自有 props)
172
+ const widgetProps = {
173
+ dataPath,
174
+ path: dataPath,
175
+ value: state.value,
176
+ onChange: handleChange,
177
+ disabled,
178
+ readOnly,
179
+ loading: state.loading,
180
+ placeholder: state.meta.placeholder,
181
+ options,
182
+ form,
183
+ dependValues,
184
+ items: state.meta.items,
185
+ ...state.props,
186
+ };
187
+
188
+ let control: ReactElement;
189
+ if (FieldWrapper) {
190
+ control = (
191
+ <FieldWrapper key={layoutKey} {...fieldWrapperProps}>
192
+ <Widget {...widgetProps} />
193
+ </FieldWrapper>
194
+ );
195
+ } else {
196
+ control = <Widget key={layoutKey} {...widgetProps} />;
197
+ }
198
+
149
199
  return (
150
200
  <div
151
201
  data-nexus-field={dataPath}
152
202
  onBlur={handleBlur}
153
203
  style={Object.keys(wrapperStyle).length > 0 ? wrapperStyle : undefined}
154
204
  >
155
- <Widget
156
- key={layoutKey}
157
- dataPath={dataPath}
158
- path={dataPath}
159
- value={state.value}
160
- onChange={handleChange}
161
- disabled={disabled}
162
- readOnly={readOnly}
163
- loading={state.loading}
164
- required={state.required}
165
- title={state.meta.title}
166
- description={state.meta.description}
167
- placeholder={state.meta.placeholder}
168
- label={state.meta.label}
169
- options={options}
170
- errors={state.errors}
171
- extra={state.meta.extra}
172
- displayType={fieldDisplayType}
173
- labelWidth={fieldLabelWidth}
174
- column={fieldColumn}
175
- form={form}
176
- dependValues={dependValues}
177
- items={state.meta.items}
178
- {...state.props}
179
- />
205
+ {control}
180
206
  </div>
181
207
  );
182
208
  }
@@ -48,6 +48,13 @@ export interface NexusFormProps {
48
48
  onFinish?: (formData: Record<string, unknown>) => void | Promise<void>;
49
49
  /** 校验失败回调 */
50
50
  onFinishFailed?: (errors: Map<string, string[]>) => void;
51
+ /**
52
+ * 表单首次加载回调:非空 schema 首次传入并完成渲染后执行一次
53
+ * - undefined / null / {}(无 properties)均视为「空」schema,不触发
54
+ * - schema 由空变为非空时,于首个非空渲染提交后触发
55
+ * - 后续 schema 变更不重复触发
56
+ */
57
+ onMount?: () => void;
51
58
  /** 是否显示默认 footer(提交/重置按钮),或自定义 footer */
52
59
  footer?: boolean | ReactNode;
53
60
  /** 自定义类名 */
@@ -107,6 +114,7 @@ export function NexusForm({
107
114
  layouts,
108
115
  onFinish,
109
116
  onFinishFailed,
117
+ onMount,
110
118
  footer = false,
111
119
  className,
112
120
  style,
@@ -161,6 +169,22 @@ export function NexusForm({
161
169
  // eslint-disable-next-line react-hooks/exhaustive-deps
162
170
  }, [engine, schema]);
163
171
 
172
+ // onMount:首次传入「非空」schema 并完成渲染后执行一次
173
+ // undefined / null / {}(无任何键)均视为空 schema,不触发;
174
+ // schema 由空变为非空时,于首个非空渲染提交(useEffect)后触发。
175
+ const onMountRef = useRef(onMount);
176
+ onMountRef.current = onMount;
177
+ const onMountFiredRef = useRef(false);
178
+ const isSchemaEmpty =
179
+ !schema || (typeof schema === 'object' && Object.keys(schema).length === 0);
180
+ useEffect(() => {
181
+ if (onMountFiredRef.current || isSchemaEmpty) {
182
+ return;
183
+ }
184
+ onMountFiredRef.current = true;
185
+ onMountRef.current?.();
186
+ }, [isSchemaEmpty]);
187
+
164
188
  // 绑定 form controller
165
189
  // 使用 ref 持有 onFinish / onFinishFailed,避免每次 re-render 都造成绑定逻辑重复执行
166
190
  const onFinishRef =
@@ -1,15 +1,29 @@
1
- import type { NexusEngine } from '@xbeeant/form-engine';
1
+ import { NexusEngine } from '@xbeeant/form-engine';
2
2
  import { useRef } from 'react';
3
3
 
4
4
  import { FormController } from '../components/FormController';
5
5
 
6
6
  /**
7
7
  * useForm — 创建 Form 实例
8
+ *
9
+ * 支持一个页面挂载多个表单实例:
10
+ * - `useForm()`:独立实例(未注册,不参与跨表单联动)
11
+ * - `useForm(formId)`:创建带 formId 的实例,自动注册到默认表单注册表,
12
+ * 可通过 schema `crossForm` reaction / `engine.linkForm` 与其他表单联动
13
+ * - `useForm(formId, engine)`:复用外部创建的引擎(如 `new NexusEngine({ formId })`)
14
+ *
15
+ * @param formId - 可选,表单实例唯一标识(跨表单联动寻址)
16
+ * @param engine - 可选,外部引擎实例(缺省内部创建)
8
17
  */
9
- export function useForm(engine?: NexusEngine): [FormController] {
18
+ export function useForm(
19
+ formId?: string,
20
+ engine?: NexusEngine,
21
+ ): [FormController] {
10
22
  const formRef = useRef<FormController | null>(null);
11
23
  if (!formRef.current) {
12
- formRef.current = new FormController(engine);
24
+ formRef.current = new FormController(
25
+ engine ?? (formId ? new NexusEngine({ formId }) : undefined),
26
+ );
13
27
  }
14
28
  return [formRef.current];
15
29
  }
package/src/index.ts CHANGED
@@ -3,6 +3,8 @@
3
3
  // 导出所有React组件、Hooks和类型定义,供上层应用使用
4
4
  // ============================================================================
5
5
 
6
+ import './styles.css';
7
+
6
8
  export { FormController } from './components/FormController';
7
9
  export { NexusField } from './components/NexusField';
8
10
  export type { NexusFormConfig } from './components/NexusForm';
package/src/styles.css ADDED
@@ -0,0 +1,6 @@
1
+ /* Renderer 样式入口(Tailwind CSS v4)
2
+ * 仅引入 theme + utilities(不引入 preflight),随构建产物输出为 index.css,
3
+ * 供未接入 Tailwind 的宿主应用使用;已接入 Tailwind 的应用可忽略该文件
4
+ */
5
+ @import "tailwindcss/theme";
6
+ @import "tailwindcss/utilities";
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />