Sfoglia il codice sorgente

doc: add form effect examples

YuanHeDx 10 mesi fa
parent
commit
b4450db944

+ 6 - 0
apps/demo-node-form/src/components/field-wrapper.css

@@ -16,3 +16,9 @@
   margin-bottom: 6px;
 }
 
+.field-note{
+  color: #a3a0a0 !important;
+  font-size: 12px;
+  margin: 6px 0;
+}
+

+ 4 - 1
apps/demo-node-form/src/components/field-wrapper.tsx

@@ -7,15 +7,18 @@ interface FieldWrapperProps {
   title: string;
   children?: React.ReactNode;
   error?: string;
+  note?: string;
 }
 
-export const FieldWrapper = ({ required, title, children, error }: FieldWrapperProps) => (
+export const FieldWrapper = ({ required, title, children, error, note }: FieldWrapperProps) => (
   <div className="field-wrapper">
     <div className="field-title">
       {title}
+      {note ? <p className="field-note">{note}</p> : null}
       {required ? <span className="required">*</span> : null}
     </div>
     {children}
     <p className="error-message">{error}</p>
+    {note ? <br /> : null}
   </div>
 );

+ 26 - 2
apps/demo-node-form/src/constant.ts

@@ -1,22 +1,25 @@
 export const fieldWrapperTs = `import React from 'react';
 
-import './index.css';
+import './field-wrapper.css';
 
 interface FieldWrapperProps {
   required?: boolean;
   title: string;
   children?: React.ReactNode;
   error?: string;
+  note?: string;
 }
 
-export const FieldWrapper = ({ required, title, children, error }: FieldWrapperProps) => (
+export const FieldWrapper = ({ required, title, children, error, note }: FieldWrapperProps) => (
   <div className="field-wrapper">
     <div className="field-title">
       {title}
+      {note ? <p className="field-note">{note}</p> : null}
       {required ? <span className="required">*</span> : null}
     </div>
     {children}
     <p className="error-message">{error}</p>
+    {note ? <br /> : null}
   </div>
 );
 `;
@@ -38,4 +41,25 @@ export const fieldWrapperCss = `.error-message {
 .field-title {
   margin-bottom: 6px;
 }
+
+.field-note{
+  color: #a3a0a0 !important;
+  font-size: 12px;
+  margin: 6px 0;
+}
 `;
+
+export const defaultInitialDataTs = `import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
+
+export const DEFAULT_INITIAL_DATA: WorkflowJSON = {
+  nodes: [
+    {
+      id: 'node_0',
+      type: 'custom',
+      meta: {
+        position: { x: 400, y: 0 },
+      },
+    },
+  ],
+  edges: [],
+};`;

+ 1 - 1
apps/demo-node-form/src/editor.tsx

@@ -14,7 +14,7 @@ interface EditorProps {
 }
 
 export const Editor = ({ registry, initialData }: EditorProps) => {
-  const editorProps = useEditorProps({ registry, initialData });
+  const editorProps = useEditorProps({ registries: [registry], initialData });
   return (
     <FreeLayoutEditorProvider {...editorProps}>
       <div className="demo-free-container">

+ 1 - 1
apps/demo-node-form/src/index.tsx

@@ -3,4 +3,4 @@ export { FieldTitle, FieldWrapper } from './components';
 export { DEFAULT_FORM_META } from './form-meta';
 export { DEFAULT_DEMO_REGISTRY } from './node-registries';
 export { DEFAULT_INITIAL_DATA } from './initial-data';
-export { fieldWrapperTs, fieldWrapperCss } from './constant';
+export { fieldWrapperTs, fieldWrapperCss, defaultInitialDataTs } from './constant';

+ 1 - 1
apps/docs/components/index.ts

@@ -5,4 +5,4 @@ export { FreeLayoutSimple } from './free-layout-simple';
 export { FreeLayoutSimplePreview } from './free-layout-simple/preview';
 export { FixedLayoutSimple } from './fixed-layout-simple';
 export { FixedLayoutSimplePreview } from './fixed-layout-simple/preview';
-export { NodeFormBasicPreview } from './form-basic/preview.tsx';
+export { NodeFormBasicPreview, NodeFormEffectPreview } from './node-form';

+ 3 - 20
apps/docs/components/form-basic/preview.tsx → apps/docs/components/node-form/basic-preview.tsx

@@ -1,12 +1,13 @@
 import {
   DEFAULT_DEMO_REGISTRY,
   DEFAULT_INITIAL_DATA,
+  defaultInitialDataTs,
   fieldWrapperCss,
   fieldWrapperTs,
 } from '@flowgram.ai/demo-node-form';
 
 import { PreviewEditor } from '../preview-editor';
-import { Editor } from '.';
+import { Editor } from './editor';
 
 const registryCode = {
   code: `import {
@@ -71,28 +72,10 @@ export const nodeRegistry: WorkflowNodeRegistry = {
   active: true,
 };
 
-const initialDataCode = {
-  code: `import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
-
-export const DEFAULT_INITIAL_DATA: WorkflowJSON = {
-  nodes: [
-    {
-      id: 'node_0',
-      type: 'custom',
-      meta: {
-        position: { x: 400, y: 0 },
-      },
-    },
-  ],
-  edges: [],
-};`,
-  active: true,
-};
-
 export const NodeFormBasicPreview = () => {
   const files = {
     'node-registry.tsx': registryCode,
-    'initial-data.ts': initialDataCode,
+    'initial-data.ts': { code: defaultInitialDataTs, active: true },
     'field-wrapper.tsx': { code: fieldWrapperTs, active: true },
     'field-wrapper.css': { code: fieldWrapperCss, active: true },
   };

+ 0 - 0
apps/docs/components/form-basic/index.tsx → apps/docs/components/node-form/editor.tsx


+ 82 - 0
apps/docs/components/node-form/effect/node-registry.tsx

@@ -0,0 +1,82 @@
+import {
+  DataEvent,
+  EffectProps,
+  Field,
+  FieldRenderProps,
+  FormMeta,
+  ValidateTrigger,
+  WorkflowNodeRegistry,
+} from '@flowgram.ai/free-layout-editor';
+import { FieldWrapper } from '@flowgram.ai/demo-node-form';
+import { Input } from '@douyinfe/semi-ui';
+import '../index.css';
+
+const render = () => (
+  <div className="demo-node-content">
+    <div className="demo-node-title">Effect Examples</div>
+    <Field name="field1">
+      {({ field }: FieldRenderProps<string>) => (
+        <FieldWrapper
+          title="Basic effect"
+          note={'The following field will console.log field value on value change'}
+        >
+          <Input size={'small'} {...field} />
+        </FieldWrapper>
+      )}
+    </Field>
+
+    <Field name="field2">
+      {({ field }: FieldRenderProps<string>) => (
+        <FieldWrapper
+          title="Control other fields"
+          note={'The following field will change Field 3 value on value change'}
+        >
+          <Input size={'small'} {...field} />
+        </FieldWrapper>
+      )}
+    </Field>
+    <Field name="field3">
+      {({ field }: FieldRenderProps<string>) => (
+        <FieldWrapper title="Controlled by other fields">
+          <Input size={'small'} {...field} />
+        </FieldWrapper>
+      )}
+    </Field>
+  </div>
+);
+
+interface FormData {
+  field1: string;
+  field2: string;
+  field3: string;
+}
+
+const formMeta: FormMeta<FormData> = {
+  render,
+  validateTrigger: ValidateTrigger.onChange,
+  effect: {
+    field1: [
+      {
+        event: DataEvent.onValueChange,
+        effect: ({ value }: EffectProps<string, FormData>) => {
+          console.log('field1 value:', value);
+        },
+      },
+    ],
+    field2: [
+      {
+        event: DataEvent.onValueChange,
+        effect: ({ value, form }: EffectProps<string, FormData>) => {
+          form.setValueIn('field3', 'field2 value is ' + value);
+        },
+      },
+    ],
+  },
+};
+
+export const nodeRegistry: WorkflowNodeRegistry = {
+  type: 'custom',
+  meta: {},
+  defaultPorts: [{ type: 'output' }, { type: 'input' }],
+  formMeta,
+};

+ 112 - 0
apps/docs/components/node-form/effect/preview.tsx

@@ -0,0 +1,112 @@
+import {
+  DEFAULT_INITIAL_DATA,
+  defaultInitialDataTs,
+  fieldWrapperCss,
+  fieldWrapperTs,
+} from '@flowgram.ai/demo-node-form';
+
+import { Editor } from '../editor.tsx';
+import { PreviewEditor } from '../../preview-editor.tsx';
+import { nodeRegistry } from './node-registry.tsx';
+
+const nodeRegistryFile = {
+  code: `import {
+  DataEvent,
+  EffectProps,
+  Field,
+  FieldRenderProps,
+  FormMeta,
+  ValidateTrigger,
+  WorkflowNodeRegistry,
+} from '@flowgram.ai/free-layout-editor';
+import { FieldWrapper } from '@flowgram.ai/demo-node-form';
+import { Input } from '@douyinfe/semi-ui';
+import '../index.css';
+
+const render = () => (
+  <div className="demo-node-content">
+    <div className="demo-node-title">Effect Examples</div>
+    <Field name="field1">
+      {({ field }: FieldRenderProps<string>) => (
+        <FieldWrapper
+          title="Basic effect"
+          note={'The following field will console.log field value on value change'}
+        >
+          <Input size={'small'} {...field} />
+        </FieldWrapper>
+      )}
+    </Field>
+
+    <Field name="field2">
+      {({ field }: FieldRenderProps<string>) => (
+        <FieldWrapper
+          title="Control other fields"
+          note={'The following field will change Field 3 value on value change'}
+        >
+          <Input size={'small'} {...field} />
+        </FieldWrapper>
+      )}
+    </Field>
+    <Field name="field3">
+      {({ field }: FieldRenderProps<string>) => (
+        <FieldWrapper title="Controlled by other fields">
+          <Input size={'small'} {...field} />
+        </FieldWrapper>
+      )}
+    </Field>
+  </div>
+);
+
+interface FormData {
+  field1: string;
+  field2: string;
+  field3: string;
+}
+
+const formMeta: FormMeta<FormData> = {
+  render,
+  validateTrigger: ValidateTrigger.onChange,
+  effect: {
+    field1: [
+      {
+        event: DataEvent.onValueChange,
+        effect: ({ value }: EffectProps<string, FormData>) => {
+          console.log('field1 value:', value);
+        },
+      },
+    ],
+    field2: [
+      {
+        event: DataEvent.onValueChange,
+        effect: ({ value, form }: EffectProps<string, FormData>) => {
+          form.setValueIn('field3', 'field2 value is ' + value);
+        },
+      },
+    ],
+  },
+};
+
+export const nodeRegistry: WorkflowNodeRegistry = {
+  type: 'custom',
+  meta: {},
+  defaultPorts: [{ type: 'output' }, { type: 'input' }],
+  formMeta,
+};
+
+`,
+  active: true,
+};
+
+export const NodeFormEffectPreview = () => {
+  const files = {
+    'node-registry.tsx': nodeRegistryFile,
+    'initial-data.ts': { code: defaultInitialDataTs, active: true },
+    'field-wrapper.tsx': { code: fieldWrapperTs, active: true },
+    'field-wrapper.css': { code: fieldWrapperCss, active: true },
+  };
+  return (
+    <PreviewEditor files={files} previewStyle={{ height: 500 }} editorStyle={{ height: 500 }}>
+      <Editor registry={nodeRegistry} initialData={DEFAULT_INITIAL_DATA} />
+    </PreviewEditor>
+  );
+};

+ 12 - 0
apps/docs/components/node-form/index.css

@@ -0,0 +1,12 @@
+.demo-node-content {
+  padding: 8px 12px;
+  flex-grow: 1;
+  width: 100%;
+}
+
+.demo-node-title {
+  font-weight: 500;
+  font-size: 14px;
+  width: 100%;
+  margin: 4px 0px 12px 0px;
+}

+ 2 - 0
apps/docs/components/node-form/index.ts

@@ -0,0 +1,2 @@
+export { NodeFormBasicPreview } from './basic-preview';
+export { NodeFormEffectPreview } from './effect/preview';

+ 2 - 1
apps/docs/src/zh/examples/node-form/_meta.json

@@ -1,3 +1,4 @@
 [
-  "basic"
+  "basic",
+  "effect"
 ]

+ 10 - 0
apps/docs/src/zh/examples/node-form/effect.mdx

@@ -0,0 +1,10 @@
+---
+outline: false
+---
+
+
+# 表单项变更副作用 ( effect)
+
+import { NodeFormEffectPreview } from '../../../../components';
+
+<NodeFormEffectPreview />

+ 6 - 2
packages/node-engine/node/src/types.ts

@@ -51,14 +51,18 @@ export enum DataEvent {
 
 export type EffectReturn = () => void;
 
-export type Effect<TFieldValue = any, TFormValues = any> = (props: {
+export interface EffectProps<TFieldValue = any, TFormValues = any> {
   name: FieldName;
   value: TFieldValue;
   prevValue?: TFieldValue;
   formValues: TFormValues;
   form: IForm;
   context: NodeContext;
-}) => void | EffectReturn;
+}
+
+export type Effect<TFieldValue = any, TFormValues = any> = (
+  props: EffectProps<TFieldValue, TFormValues>
+) => void | EffectReturn;
 
 export type ArrayAppendEffect<TFieldValue = any, TFormValues = any> = (props: {
   index: number;