Răsfoiți Sursa

fix: condition auto clear right & test run json cursor jumping (#949)

* fix: condition auto clear right

* fix: test run json cursor

* fix: ts
Yiwei Mao 2 luni în urmă
părinte
comite
8188c6f10a

+ 5 - 21
apps/demo-free-layout/src/components/testrun/hooks/use-fields.ts

@@ -14,29 +14,13 @@ export const useFields = (params: {
 
   // Convert each meta item to a form field with value and onChange handler
   const fields: TestRunFormField[] = formMeta.map((meta) => {
-    // Handle object type specially - serialize object to JSON string for display
-    const getCurrentValue = (): unknown => {
-      const rawValue = values[meta.name] ?? meta.defaultValue;
-      if ((meta.type === 'object' || meta.type === 'array') && rawValue !== null) {
-        return JSON.stringify(rawValue, null, 2);
-      }
-      return rawValue;
-    };
-
-    const currentValue = getCurrentValue();
+    const currentValue = values[meta.name] ?? meta.defaultValue;
 
     const handleChange = (newValue: unknown): void => {
-      if (meta.type === 'object' || meta.type === 'array') {
-        setValues({
-          ...values,
-          [meta.name]: JSON.parse((newValue ?? '{}') as string),
-        });
-      } else {
-        setValues({
-          ...values,
-          [meta.name]: newValue,
-        });
-      }
+      setValues({
+        ...values,
+        [meta.name]: newValue,
+      });
     };
 
     return {

+ 12 - 29
apps/demo-free-layout/src/components/testrun/hooks/use-form-meta.ts

@@ -5,47 +5,30 @@
 
 import { useMemo } from 'react';
 
-import {
-  FlowNodeFormData,
-  FormModelV2,
-  useService,
-  WorkflowDocument,
-} from '@flowgram.ai/free-layout-editor';
+import { useService, WorkflowDocument } from '@flowgram.ai/free-layout-editor';
 import { IJsonSchema, JsonSchemaBasicType } from '@flowgram.ai/form-materials';
 
 import { TestRunFormMetaItem } from '../testrun-form/type';
 import { WorkflowNodeType } from '../../../nodes';
 
-const getWorkflowInputsDeclare = (document: WorkflowDocument): IJsonSchema => {
-  const defaultDeclare = {
-    type: 'object',
-    properties: {},
-  };
-
-  const startNode = document.root.blocks.find(
-    (node) => node.flowNodeType === WorkflowNodeType.Start
-  );
-  if (!startNode) {
-    return defaultDeclare;
-  }
-
-  const startFormModel = startNode.getData(FlowNodeFormData).getFormModel<FormModelV2>();
-  const declare = startFormModel.getValueIn<IJsonSchema>('outputs');
-
-  if (!declare) {
-    return defaultDeclare;
-  }
-
-  return declare;
+const DEFAULT_DECLARE: IJsonSchema = {
+  type: 'object',
+  properties: {},
 };
 
 export const useFormMeta = (): TestRunFormMetaItem[] => {
   const document = useService(WorkflowDocument);
 
+  const startNode = useMemo(
+    () => document.root.blocks.find((node) => node.flowNodeType === WorkflowNodeType.Start),
+    [document]
+  );
+
+  const workflowInputs = startNode?.form?.getValueIn<IJsonSchema>('outputs') || DEFAULT_DECLARE;
+
   // Add state for form values
   const formMeta = useMemo(() => {
     const formFields: TestRunFormMetaItem[] = [];
-    const workflowInputs = getWorkflowInputsDeclare(document);
     Object.entries(workflowInputs.properties!).forEach(([name, property]) => {
       formFields.push({
         type: property.type as JsonSchemaBasicType,
@@ -56,7 +39,7 @@ export const useFormMeta = (): TestRunFormMetaItem[] => {
       });
     });
     return formFields;
-  }, [document]);
+  }, [workflowInputs]);
 
   return formMeta;
 };

+ 47 - 0
apps/demo-free-layout/src/components/testrun/json-value-editor/index.tsx

@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { useEffect, useMemo, useRef, useState } from 'react';
+
+import { JsonCodeEditor } from '@flowgram.ai/form-materials';
+
+export function JsonValueEditor({
+  value,
+  onChange,
+}: {
+  value: Record<string, unknown>;
+  onChange: (value: Record<string, unknown>) => void;
+}) {
+  const defaultJsonText = useMemo(() => JSON.stringify(value, null, 2), [value]);
+
+  const [jsonText, setJsonText] = useState(defaultJsonText);
+
+  const effectVersion = useRef(0);
+  const changeVersion = useRef(0);
+
+  const handleJsonTextChange = (text: string) => {
+    setJsonText(text);
+    try {
+      const jsonValue = JSON.parse(text);
+      onChange(jsonValue);
+      changeVersion.current++;
+    } catch (e) {
+      // ignore
+    }
+  };
+
+  useEffect(() => {
+    // more effect compared with change
+    effectVersion.current = effectVersion.current + 1;
+    if (effectVersion.current === changeVersion.current) {
+      return;
+    }
+    effectVersion.current = changeVersion.current;
+
+    setJsonText(JSON.stringify(value, null, 2));
+  }, [value]);
+
+  return <JsonCodeEditor value={jsonText} onChange={handleJsonTextChange} />;
+}

+ 4 - 3
apps/demo-free-layout/src/components/testrun/testrun-form/index.tsx

@@ -6,9 +6,10 @@
 import { FC } from 'react';
 
 import classNames from 'classnames';
-import { DisplaySchemaTag, JsonCodeEditor } from '@flowgram.ai/form-materials';
+import { DisplaySchemaTag } from '@flowgram.ai/form-materials';
 import { Input, Switch, InputNumber } from '@douyinfe/semi-ui';
 
+import { JsonValueEditor } from '../json-value-editor';
 import { useFormMeta } from '../hooks/use-form-meta';
 import { useFields } from '../hooks/use-fields';
 import { useSyncDefault } from '../hooks';
@@ -67,13 +68,13 @@ export const TestRunForm: FC<TestRunFormProps> = ({ values, setValues }) => {
       case 'object':
         return (
           <div className={classNames(styles.fieldInput, styles.codeEditorWrapper)}>
-            <JsonCodeEditor value={field.value} onChange={(value) => field.onChange(value)} />
+            <JsonValueEditor value={field.value} onChange={(value) => field.onChange(value)} />
           </div>
         );
       case 'array':
         return (
           <div className={classNames(styles.fieldInput, styles.codeEditorWrapper)}>
-            <JsonCodeEditor value={field.value} onChange={(value) => field.onChange(value)} />
+            <JsonValueEditor value={field.value} onChange={(value) => field.onChange(value)} />
           </div>
         );
       default:

+ 2 - 6
apps/demo-free-layout/src/components/testrun/testrun-json-input/index.tsx

@@ -5,8 +5,7 @@
 
 import { FC } from 'react';
 
-import { JsonCodeEditor } from '@flowgram.ai/form-materials';
-
+import { JsonValueEditor } from '../json-value-editor';
 import { useFormMeta, useSyncDefault } from '../hooks';
 
 import styles from './index.module.less';
@@ -27,10 +26,7 @@ export const TestRunJsonInput: FC<TestRunJsonInputProps> = ({ values, setValues
 
   return (
     <div className={styles['testrun-json-input']}>
-      <JsonCodeEditor
-        value={JSON.stringify(values, null, 2)}
-        onChange={(value) => setValues(JSON.parse(value))}
-      />
+      <JsonValueEditor value={values} onChange={setValues} />
     </div>
   );
 };

+ 77 - 0
apps/docs/components/form-materials/components/condition-context.tsx

@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from 'react';
+
+import { Field } from '@flowgram.ai/free-layout-editor';
+import type { ConditionOpConfigs, IConditionRule } from '@flowgram.ai/form-materials';
+
+import { FreeFormMetaStoryBuilder, FormHeader } from '../../free-form-meta-story-builder';
+
+const ConditionRow = React.lazy(() =>
+  import('@flowgram.ai/form-materials').then((module) => ({
+    default: module.ConditionRow,
+  }))
+);
+
+const DBConditionRow = React.lazy(() =>
+  import('@flowgram.ai/form-materials').then((module) => ({
+    default: module.DBConditionRow,
+  }))
+);
+
+const ConditionProvider = React.lazy(() =>
+  import('@flowgram.ai/form-materials').then((module) => ({
+    default: module.ConditionProvider,
+  }))
+);
+
+const OPS: ConditionOpConfigs = {
+  cop: {
+    abbreviation: 'C',
+    label: 'Custom Operator',
+  },
+};
+
+const RULES: Record<string, IConditionRule> = {
+  string: {
+    cop: { type: 'string' },
+  },
+};
+
+export const BasicStory = () => (
+  <FreeFormMetaStoryBuilder
+    filterEndNode
+    formMeta={{
+      render: () => (
+        <>
+          <FormHeader />
+          <ConditionProvider ops={OPS} rules={RULES}>
+            <Field<any | undefined> name="condition_row">
+              {({ field }) => (
+                <ConditionRow value={field.value} onChange={(value) => field.onChange(value)} />
+              )}
+            </Field>
+            <Field<any | undefined> name="db_condition_row">
+              {({ field }) => (
+                <DBConditionRow
+                  options={[
+                    {
+                      label: 'UserName',
+                      value: 'username',
+                      schema: { type: 'string' },
+                    },
+                  ]}
+                  value={field.value}
+                  onChange={(value) => field.onChange(value)}
+                />
+              )}
+            </Field>
+          </ConditionProvider>
+        </>
+      ),
+    }}
+  />
+);

+ 201 - 4
apps/docs/src/en/materials/components/condition-context.mdx

@@ -1,11 +1,208 @@
 import { SourceCode } from '@theme';
+import { BasicStory } from 'components/form-materials/components/condition-context';
+
+# ConditionContext
+
+ConditionContext is a context management system for condition configuration, used to uniformly manage condition rules and operator configurations, providing a consistent configuration environment for condition components.
+
+:::tip
+
+The condition configuration context of ConditionContext can affect the following materials:
+
+- [**ConditionRow**](./condition-row)
+- [**DBConditionRow**](./db-condition-row)
 
-:::warning
-The material has been developed and the documentation is still being improved. Contributions are welcome.
 :::
 
-# ConditionContext (WIP)
+## Examples
+
+### Basic Usage
+
+<BasicStory />
+
+```tsx pure title="form-meta.tsx"
+import React from 'react';
+import { Field } from '@flowgram.ai/free-layout-editor';
+import {
+  ConditionProvider,
+  ConditionRow,
+  DBConditionRow,
+  type ConditionOpConfigs,
+  type IConditionRule
+} from '@flowgram.ai/form-materials';
+
+const OPS: ConditionOpConfigs = {
+  cop: {
+    abbreviation: 'C',
+    label: 'Custom Operator',
+  },
+};
+
+const RULES: Record<string, IConditionRule> = {
+  string: {
+    cop: { type: 'string' },
+  },
+};
+
+const formMeta = {
+  render: () => (
+    <>
+      <FormHeader />
+      <ConditionProvider ops={OPS} rules={RULES}>
+        <Field<any | undefined> name="condition_row">
+          {({ field }) => (
+            <ConditionRow value={field.value} onChange={(value) => field.onChange(value)} />
+          )}
+        </Field>
+        <Field<any | undefined> name="db_condition_row">
+          {({ field }) => (
+            <DBConditionRow
+              options={[
+                {
+                  label: 'UserName',
+                  value: 'username',
+                  schema: { type: 'string' },
+                },
+              ]}
+              value={field.value}
+              onChange={(value) => field.onChange(value)}
+            />
+          )}
+        </Field>
+      </ConditionProvider>
+    </>
+  ),
+};
+```
+
+## API Reference
+
+### ConditionProvider
+
+A context provider component for condition configuration, used to uniformly manage condition rules and operator configurations.
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `rules` | `Record<string, IConditionRule>` | No | Condition rule configuration |
+| `ops` | `ConditionOpConfigs` | No | Operator configuration |
+| `children` | `React.ReactNode` | Yes | Child components |
+
+### useCondition
+
+A hook to get condition configuration, which obtains corresponding configuration information based on data type and operator.
+
+For specific usage, you can refer to the use of `useCondition` in [ConditionRow's source code](https://github.com/bytedance/flowgram.ai/blob/main/packages/materials/form-materials/src/components/condition-row/index.tsx).
+
+### ConditionPresetOp
+
+A preset operator enumeration, providing commonly used comparison operators.
+
+| Enum Value | Description | Abbreviation |
+|------------|-------------|--------------|
+| `EQ` | Equal | `=` |
+| `NEQ` | Not Equal | `≠` |
+| `GT` | Greater Than | `>` |
+| `GTE` | Greater Than or Equal | `>=` |
+| `LT` | Less Than | `<` |
+| `LTE` | Less Than or Equal | `<=` |
+| `IN` | In | `∈` |
+| `NIN` | Not In | `∉` |
+| `CONTAINS` | Contains | `⊇` |
+| `NOT_CONTAINS` | Not Contains | `⊉` |
+| `IS_EMPTY` | Is Empty | `=` |
+| `IS_NOT_EMPTY` | Is Not Empty | `≠` |
+| `IS_TRUE` | Is True | `=` |
+| `IS_FALSE` | Is False | `=` |
+
+### Type Definitions
+
+```typescript
+// Operator configuration
+interface ConditionOpConfig {
+  label: string; // Operator label
+  abbreviation: string; // Operator abbreviation
+  rightDisplay?: string; // Right side display text (when right side is not a value)
+}
+
+// Operator configuration collection
+type ConditionOpConfigs = Record<string, ConditionOpConfig>;
+
+// Condition rule
+type IConditionRule = Record<string, string | IJsonSchema | null>;
+
+// Condition rule factory function
+type IConditionRuleFactory = (
+  schema?: IJsonSchema
+) => Record<string, string | IJsonSchema | null>;
+```
+
+## Source Code Guide
 
 <SourceCode
   href="https://github.com/bytedance/flowgram.ai/tree/main/packages/materials/form-materials/src/components/condition-context"
-/>
+/>
+
+You can copy the source code to your local machine using the CLI command:
+
+```bash
+npx @flowgram.ai/cli@latest materials components/condition-context
+```
+
+### Directory Structure
+
+```
+condition-context/
+├── context.tsx        # Context implementation
+├── hooks/             # Hook functions directory
+│   └── use-condition.tsx  # useCondition hook implementation
+├── index.tsx          # Unified export file
+├── op.ts              # Preset operator definitions
+└── types.ts           # Type definitions
+```
+
+### Core Implementation
+
+#### ConditionContext Workflow
+
+Here is the sequence diagram of the workflow for ConditionProvider and useCondition Hook:
+
+```mermaid
+sequenceDiagram
+    participant App as Application Component
+    participant Provider as ConditionProvider
+    participant Context as ConditionContext
+    participant Child as ConditionRow or DBConditionRow
+    participant Hook as useCondition Hook
+    participant TypeManager as useTypeManager
+
+    %% Initialization Phase
+    App->>Provider: Pass rules and ops configurations
+    Provider->>Context: Create Context and set default values
+    Provider->>Provider: Receive rules and ops parameters
+    Provider->>Context: Update configurations in Context
+    Provider-->>App: Render child components
+
+    %% Usage Phase
+    Child->>Hook: Call useCondition
+    Hook->>Context: Get configurations via useConditionContext()
+    Hook->>TypeManager: Get type manager
+    Hook->>Hook: Merge user rules and context rules
+    Hook->>Hook: Merge user operators and context operators
+    Hook->>TypeManager: Get type configuration based on leftSchema
+    Hook->>Hook: Calculate condition rules for current type
+    Hook->>Hook: Generate available operator options list
+    Hook->>Hook: Calculate target value's data type Schema
+    Hook-->>Child: Return {rule, opConfig, opOptionList, targetSchema}
+    Child-->>App: Render condition component based on returned configurations
+
+```
+
+### Dependencies
+
+#### flowgram API
+
+[**@flowgram.ai/editor**](https://github.com/bytedance/flowgram.ai/tree/main/packages/client/editor)
+- [`I18n`](https://flowgram.ai/auto-docs/editor/variables/I18n): Internationalization utility class
+
+[**@flowgram.ai/json-schema**](https://github.com/bytedance/flowgram.ai/tree/main/packages/common/json-schema)
+- [`IJsonSchema`](https://flowgram.ai/auto-docs/json-schema/types/IJsonSchema): JSON Schema type definition

+ 201 - 3
apps/docs/src/zh/materials/components/condition-context.mdx

@@ -1,11 +1,209 @@
 import { SourceCode } from '@theme';
+import { BasicStory } from 'components/form-materials/components/condition-context';
+
+# ConditionContext
+
+ConditionContext 是一个条件配置的上下文管理系统,用于统一管理条件规则和操作符配置,为条件组件提供一致的配置环境。
+
+:::tip
+
+ConditionContext 的条件配置上下文可以影响到以下物料:
+
+- [**ConditionRow**](./condition-row)
+- [**DBConditionRow**](./db-condition-row)
 
-:::warning
-物料已完成开发,文档还在完善中,欢迎参与贡献
 :::
 
-# ConditionContext (WIP)
+## 案例演示
+
+### 基本使用
+
+<BasicStory />
+
+```tsx pure title="form-meta.tsx"
+import React from 'react';
+import { Field } from '@flowgram.ai/free-layout-editor';
+import {
+  ConditionProvider,
+  ConditionRow,
+  DBConditionRow,
+  type ConditionOpConfigs,
+  type IConditionRule
+} from '@flowgram.ai/form-materials';
+
+const OPS: ConditionOpConfigs = {
+  cop: {
+    abbreviation: 'C',
+    label: 'Custom Operator',
+  },
+};
+
+const RULES: Record<string, IConditionRule> = {
+  string: {
+    cop: { type: 'string' },
+  },
+};
+
+const formMeta = {
+  render: () => (
+    <>
+      <FormHeader />
+      <ConditionProvider ops={OPS} rules={RULES}>
+        <Field<any | undefined> name="condition_row">
+          {({ field }) => (
+            <ConditionRow value={field.value} onChange={(value) => field.onChange(value)} />
+          )}
+        </Field>
+        <Field<any | undefined> name="db_condition_row">
+          {({ field }) => (
+            <DBConditionRow
+              options={[
+                {
+                  label: 'UserName',
+                  value: 'username',
+                  schema: { type: 'string' },
+                },
+              ]}
+              value={field.value}
+              onChange={(value) => field.onChange(value)}
+            />
+          )}
+        </Field>
+      </ConditionProvider>
+    </>
+  ),
+};
+```
+
+## API 参考
+
+### ConditionProvider
+
+条件配置的上下文提供者组件,用于统一管理条件规则和操作符配置。
+
+| 参数名 | 类型 | 必选 | 说明 |
+|--------|------|------|------|
+| `rules` | `Record<string, IConditionRule>` | 否 | 条件规则配置 |
+| `ops` | `ConditionOpConfigs` | 否 | 操作符配置 |
+| `children` | `React.ReactNode` | 是 | 子组件 |
+
+### useCondition
+
+获取条件配置的 Hook,根据数据类型和操作符获取相应的配置信息。
+
+具体使用可以参考 [ConditionRow 的源代码](https://github.com/bytedance/flowgram.ai/blob/main/packages/materials/form-materials/src/components/condition-row/index.tsx) 中 `useCondition` 的使用。
+
+
+### ConditionPresetOp
+
+预设的操作符枚举,提供常用的比较操作符。
+
+| 枚举值 | 说明 | 缩写 |
+|--------|------|------|
+| `EQ` | 等于 | `=` |
+| `NEQ` | 不等于 | `≠` |
+| `GT` | 大于 | `>` |
+| `GTE` | 大于等于 | `>=` |
+| `LT` | 小于 | `<` |
+| `LTE` | 小于等于 | `<=` |
+| `IN` | 在集合中 | `∈` |
+| `NIN` | 不在集合中 | `∉` |
+| `CONTAINS` | 包含 | `⊇` |
+| `NOT_CONTAINS` | 不包含 | `⊉` |
+| `IS_EMPTY` | 为空 | `=` |
+| `IS_NOT_EMPTY` | 不为空 | `≠` |
+| `IS_TRUE` | 为真 | `=` |
+| `IS_FALSE` | 为假 | `=` |
+
+### 类型定义
+
+```typescript
+// 操作符配置
+interface ConditionOpConfig {
+  label: string; // 操作符标签
+  abbreviation: string; // 操作符缩写
+  rightDisplay?: string; // 右侧显示文本(当右侧不是值时)
+}
+
+// 操作符配置集合
+type ConditionOpConfigs = Record<string, ConditionOpConfig>;
+
+// 条件规则
+type IConditionRule = Record<string, string | IJsonSchema | null>;
+
+// 条件规则工厂函数
+type IConditionRuleFactory = (
+  schema?: IJsonSchema
+) => Record<string, string | IJsonSchema | null>;
+```
+
+## 源码导读
 
 <SourceCode
   href="https://github.com/bytedance/flowgram.ai/tree/main/packages/materials/form-materials/src/components/condition-context"
 />
+
+使用 CLI 命令可以复制源代码到本地:
+
+```bash
+npx @flowgram.ai/cli@latest materials components/condition-context
+```
+
+### 目录结构讲解
+
+```
+condition-context/
+├── context.tsx        # Context 相关实现
+├── hooks/             # 钩子函数目录
+│   └── use-condition.tsx  # useCondition 钩子实现
+├── index.tsx          # 统一导出文件
+├── op.ts              # 预设操作符定义
+└── types.ts           # 类型定义
+```
+
+### 核心实现说明
+
+#### ConditionContext 工作流程
+
+以下是 ConditionProvider 和 useCondition Hook 的工作流程时序图:
+
+```mermaid
+sequenceDiagram
+    participant App as 应用组件
+    participant Provider as ConditionProvider
+    participant Context as ConditionContext
+    participant Child as ConditionRow或DBConditionRow
+    participant Hook as useCondition Hook
+    participant TypeManager as useTypeManager
+
+    %% 初始化阶段
+    App->>Provider: 传入 rules 和 ops 配置
+    Provider->>Context: 创建 Context 并设置默认值
+    Provider->>Provider: 接收 rules 和 ops 参数
+    Provider->>Context: 更新 Context 中的配置
+    Provider-->>App: 渲染子组件
+
+    %% 使用阶段
+    Child->>Hook: 调用 useCondition
+    Hook->>Context: 通过 useConditionContext() 获取配置
+    Hook->>TypeManager: 获取类型管理器
+    Hook->>Hook: 合并用户规则和上下文规则
+    Hook->>Hook: 合并用户操作符和上下文操作符
+    Hook->>TypeManager: 根据 leftSchema 获取类型配置
+    Hook->>Hook: 计算当前类型的条件规则
+    Hook->>Hook: 生成可用的操作符选项列表
+    Hook->>Hook: 计算目标值的数据类型 Schema
+    Hook-->>Child: 返回 {rule, opConfig, opOptionList, targetSchema}
+    Child-->>App: 根据返回配置渲染条件组件
+
+```
+
+### 依赖梳理
+
+#### flowgram API
+
+[**@flowgram.ai/editor**](https://github.com/bytedance/flowgram.ai/tree/main/packages/client/editor)
+- [`I18n`](https://flowgram.ai/auto-docs/editor/variables/I18n): 国际化工具类
+
+[**@flowgram.ai/json-schema**](https://github.com/bytedance/flowgram.ai/tree/main/packages/common/json-schema)
+- [`IJsonSchema`](https://flowgram.ai/auto-docs/json-schema/types/IJsonSchema): JSON Schema 类型定义

+ 51 - 7
packages/materials/form-materials/src/components/condition-context/hooks/use-condition.tsx

@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: MIT
  */
 
-import { useMemo } from 'react';
+import { useEffect, useMemo, useRef } from 'react';
 
 import { IJsonSchema } from '@flowgram.ai/json-schema';
 import { I18n } from '@flowgram.ai/editor';
@@ -14,10 +14,28 @@ import { IConditionRule, ConditionOpConfigs } from '../types';
 import { useConditionContext } from '../context';
 
 interface HooksParams {
+  /**
+   * Left schema of condition
+   */
   leftSchema?: IJsonSchema;
+
+  /**
+   * Operator of condition
+   */
   operator?: string;
 
   /**
+   * If op is not in opOptionList, clear it
+   */
+  onClearOp?: () => void;
+
+  /**
+   * If targetSchema updated, clear it
+   */
+  onClearRight?: () => void;
+
+  /**
+   * @deprecated use ConditionProvider instead
    * custom rule config
    */
   ruleConfig?: {
@@ -26,26 +44,32 @@ interface HooksParams {
   };
 }
 
-export function useCondition({ leftSchema, operator, ruleConfig }: HooksParams) {
+export function useCondition({
+  leftSchema,
+  operator,
+  onClearOp,
+  onClearRight,
+  ruleConfig,
+}: HooksParams) {
   const typeManager = useTypeManager();
   const { rules: contextRules, ops: contextOps } = useConditionContext();
 
-  // 合并用户规则和上下文规则
+  // Merge user rules and context rules
   const userRules = useMemo(
     () => ruleConfig?.rules || contextRules || {},
     [contextRules, ruleConfig?.rules]
   );
 
-  // 合并用户操作符和上下文操作符
+  // Merge user operators and context operators
   const allOps = useMemo(() => ruleConfig?.ops || contextOps || {}, [contextOps, ruleConfig?.ops]);
 
-  // 获取类型配置
+  // Get type configuration
   const config = useMemo(
     () => (leftSchema ? typeManager.getTypeBySchema(leftSchema) : undefined),
     [leftSchema, typeManager]
   );
 
-  // 计算规则
+  // Calculate rule
   const rule = useMemo(() => {
     if (!config) {
       return undefined;
@@ -59,7 +83,7 @@ export function useCondition({ leftSchema, operator, ruleConfig }: HooksParams)
     return config.conditionRule;
   }, [userRules, leftSchema, config]);
 
-  // 计算操作符选项列表
+  // Calculate operator option list
   const opOptionList = useMemo(
     () =>
       Object.keys(rule || {})
@@ -72,6 +96,16 @@ export function useCondition({ leftSchema, operator, ruleConfig }: HooksParams)
     [rule, allOps]
   );
 
+  // When op not in list, clear it
+  useEffect(() => {
+    if (!operator || !rule) {
+      return;
+    }
+    if (!opOptionList.find((item) => item.value === operator)) {
+      onClearOp?.();
+    }
+  }, [operator, opOptionList, onClearOp]);
+
   // get target schema
   const targetSchema = useMemo(() => {
     const targetType: string | IJsonSchema | null = rule?.[operator || ''] || null;
@@ -87,6 +121,16 @@ export function useCondition({ leftSchema, operator, ruleConfig }: HooksParams)
     return targetType;
   }, [rule, operator]);
 
+  const prevTargetSchemaRef = useRef<IJsonSchema | undefined>(undefined);
+
+  // When type of target schema updated, clear it
+  useEffect(() => {
+    if (prevTargetSchemaRef.current?.type !== targetSchema?.type) {
+      onClearRight?.();
+    }
+    prevTargetSchemaRef.current = targetSchema;
+  }, [targetSchema, onClearRight]);
+
   // get current operator config
   const opConfig = useMemo(() => allOps[operator || ''], [operator, allOps]);
 

+ 12 - 0
packages/materials/form-materials/src/components/condition-row/index.tsx

@@ -50,6 +50,18 @@ export function ConditionRow({ style, value, onChange, readonly, ruleConfig }: P
     leftSchema,
     operator,
     ruleConfig,
+    onClearOp() {
+      onChange({
+        ...value,
+        operator: undefined,
+      });
+    },
+    onClearRight() {
+      onChange({
+        ...value,
+        right: undefined,
+      });
+    },
   });
 
   const renderOpSelect = () => (

+ 12 - 0
packages/materials/form-materials/src/components/db-condition-row/index.tsx

@@ -56,6 +56,18 @@ export function DBConditionRow({
     leftSchema,
     operator,
     ruleConfig,
+    onClearOp() {
+      onChange({
+        ...value,
+        operator: undefined,
+      });
+    },
+    onClearRight() {
+      onChange({
+        ...value,
+        right: undefined,
+      });
+    },
   });
 
   const renderDBOptionSelect = () => (