Browse Source

feat(material): type-editor (#613)

* feat: init type editor package

* feat: type-editor code

* fix: bugs

* chore: pnpm-lock

* feat: object type editor

* feat: license

* feat: view configs

* feat: ts check
Yiwei Mao 5 months ago
parent
commit
167b376498
98 changed files with 8609 additions and 11 deletions
  1. 140 9
      common/config/rush/pnpm-lock.yaml
  2. 1 1
      packages/materials/form-materials/src/components/constant-input/config.json
  3. 1 1
      packages/materials/form-materials/src/components/display-schema-tree/config.json
  4. 17 0
      packages/materials/type-editor/.eslintrc.js
  5. 66 0
      packages/materials/type-editor/package.json
  6. 42 0
      packages/materials/type-editor/src/components/feedback/feedback.tsx
  7. 7 0
      packages/materials/type-editor/src/components/feedback/index.ts
  8. 84 0
      packages/materials/type-editor/src/components/feedback/style.ts
  9. 6 0
      packages/materials/type-editor/src/components/feedback/types.ts
  10. 7 0
      packages/materials/type-editor/src/components/index.ts
  11. 110 0
      packages/materials/type-editor/src/components/type-editor/body.tsx
  12. 243 0
      packages/materials/type-editor/src/components/type-editor/cell.tsx
  13. 226 0
      packages/materials/type-editor/src/components/type-editor/columns/default.tsx
  14. 88 0
      packages/materials/type-editor/src/components/type-editor/columns/description.tsx
  15. 24 0
      packages/materials/type-editor/src/components/type-editor/columns/index.ts
  16. 355 0
      packages/materials/type-editor/src/components/type-editor/columns/key.tsx
  17. 209 0
      packages/materials/type-editor/src/components/type-editor/columns/operate.tsx
  18. 122 0
      packages/materials/type-editor/src/components/type-editor/columns/private.tsx
  19. 144 0
      packages/materials/type-editor/src/components/type-editor/columns/required.tsx
  20. 177 0
      packages/materials/type-editor/src/components/type-editor/columns/style.ts
  21. 175 0
      packages/materials/type-editor/src/components/type-editor/columns/type.tsx
  22. 137 0
      packages/materials/type-editor/src/components/type-editor/columns/value.tsx
  23. 44 0
      packages/materials/type-editor/src/components/type-editor/common.ts
  24. 71 0
      packages/materials/type-editor/src/components/type-editor/drop-tip.tsx
  25. 68 0
      packages/materials/type-editor/src/components/type-editor/error.tsx
  26. 44 0
      packages/materials/type-editor/src/components/type-editor/formatter/index.ts
  27. 69 0
      packages/materials/type-editor/src/components/type-editor/header.tsx
  28. 28 0
      packages/materials/type-editor/src/components/type-editor/hooks/active-pos.ts
  29. 36 0
      packages/materials/type-editor/src/components/type-editor/hooks/blink.ts
  30. 25 0
      packages/materials/type-editor/src/components/type-editor/hooks/disabled.ts
  31. 205 0
      packages/materials/type-editor/src/components/type-editor/hooks/drag-drop.ts
  32. 86 0
      packages/materials/type-editor/src/components/type-editor/hooks/editor-listener.tsx
  33. 66 0
      packages/materials/type-editor/src/components/type-editor/hooks/error-cell.ts
  34. 70 0
      packages/materials/type-editor/src/components/type-editor/hooks/formatter-value.ts
  35. 95 0
      packages/materials/type-editor/src/components/type-editor/hooks/hot-key.ts
  36. 12 0
      packages/materials/type-editor/src/components/type-editor/hooks/index.ts
  37. 66 0
      packages/materials/type-editor/src/components/type-editor/hooks/key-visible.tsx
  38. 106 0
      packages/materials/type-editor/src/components/type-editor/hooks/paste-data.ts
  39. 180 0
      packages/materials/type-editor/src/components/type-editor/hooks/type-edit.ts
  40. 17 0
      packages/materials/type-editor/src/components/type-editor/indent.tsx
  41. 52 0
      packages/materials/type-editor/src/components/type-editor/index.tsx
  42. 102 0
      packages/materials/type-editor/src/components/type-editor/mode/declare-assign.ts
  43. 15 0
      packages/materials/type-editor/src/components/type-editor/mode/index.ts
  44. 46 0
      packages/materials/type-editor/src/components/type-editor/mode/type-definition.ts
  45. 80 0
      packages/materials/type-editor/src/components/type-editor/style.ts
  46. 508 0
      packages/materials/type-editor/src/components/type-editor/table.tsx
  47. 69 0
      packages/materials/type-editor/src/components/type-editor/tool-bar.tsx
  48. 121 0
      packages/materials/type-editor/src/components/type-editor/tools/create-by-data.tsx
  49. 27 0
      packages/materials/type-editor/src/components/type-editor/tools/index.module.less
  50. 12 0
      packages/materials/type-editor/src/components/type-editor/tools/style.ts
  51. 49 0
      packages/materials/type-editor/src/components/type-editor/tools/undo-redo.tsx
  52. 43 0
      packages/materials/type-editor/src/components/type-editor/type-editor.tsx
  53. 204 0
      packages/materials/type-editor/src/components/type-editor/type.ts
  54. 220 0
      packages/materials/type-editor/src/components/type-editor/utils.ts
  55. 131 0
      packages/materials/type-editor/src/components/type-selector/cascader-v2/index.tsx
  56. 140 0
      packages/materials/type-editor/src/components/type-selector/cascader-v2/style.ts
  57. 109 0
      packages/materials/type-editor/src/components/type-selector/cascader-v2/trigger.tsx
  58. 325 0
      packages/materials/type-editor/src/components/type-selector/cascader-v2/type-cascader.tsx
  59. 278 0
      packages/materials/type-editor/src/components/type-selector/cascader-v2/type-search.tsx
  60. 238 0
      packages/materials/type-editor/src/components/type-selector/hooks/focus-item.ts
  61. 60 0
      packages/materials/type-editor/src/components/type-selector/hooks/hot-key.ts
  62. 6 0
      packages/materials/type-editor/src/components/type-selector/hooks/index.ts
  63. 98 0
      packages/materials/type-editor/src/components/type-selector/hooks/option-value.ts
  64. 43 0
      packages/materials/type-editor/src/components/type-selector/hooks/root-types.ts
  65. 35 0
      packages/materials/type-editor/src/components/type-selector/hooks/use-hight-light.tsx
  66. 24 0
      packages/materials/type-editor/src/components/type-selector/index.tsx
  67. 97 0
      packages/materials/type-editor/src/components/type-selector/type.ts
  68. 67 0
      packages/materials/type-editor/src/components/type-selector/utils/index.tsx
  69. 112 0
      packages/materials/type-editor/src/contexts/index.tsx
  70. 13 0
      packages/materials/type-editor/src/index.ts
  71. 6 0
      packages/materials/type-editor/src/preset/index.tsx
  72. 130 0
      packages/materials/type-editor/src/preset/object-type-editor/index.tsx
  73. 93 0
      packages/materials/type-editor/src/services/clipboard-service.ts
  74. 11 0
      packages/materials/type-editor/src/services/index.tsx
  75. 9 0
      packages/materials/type-editor/src/services/shortcut-service.ts
  76. 396 0
      packages/materials/type-editor/src/services/type-editor-service.ts
  77. 99 0
      packages/materials/type-editor/src/services/type-operation-service.ts
  78. 12 0
      packages/materials/type-editor/src/services/type-registry-manager.ts
  79. 28 0
      packages/materials/type-editor/src/services/utils.ts
  80. 28 0
      packages/materials/type-editor/src/type-registry/array.tsx
  81. 45 0
      packages/materials/type-editor/src/type-registry/boolean.tsx
  82. 24 0
      packages/materials/type-editor/src/type-registry/index.ts
  83. 26 0
      packages/materials/type-editor/src/type-registry/integer.tsx
  84. 26 0
      packages/materials/type-editor/src/type-registry/number.tsx
  85. 26 0
      packages/materials/type-editor/src/type-registry/object.tsx
  86. 29 0
      packages/materials/type-editor/src/type-registry/string.tsx
  87. 7 0
      packages/materials/type-editor/src/types/index.ts
  88. 98 0
      packages/materials/type-editor/src/types/registry.ts
  89. 291 0
      packages/materials/type-editor/src/types/type-editor.ts
  90. 6 0
      packages/materials/type-editor/src/utils/index.ts
  91. 7 0
      packages/materials/type-editor/src/utils/monitor-data/index.ts
  92. 43 0
      packages/materials/type-editor/src/utils/monitor-data/monitor-data.ts
  93. 27 0
      packages/materials/type-editor/src/utils/monitor-data/use-monitor-data.ts
  94. 95 0
      packages/materials/type-editor/src/utils/registry-adapter.tsx
  95. 8 0
      packages/materials/type-editor/tsconfig.json
  96. 31 0
      packages/materials/type-editor/vitest.config.ts
  97. 6 0
      packages/materials/type-editor/vitest.setup.ts
  98. 9 0
      rush.json

+ 140 - 9
common/config/rush/pnpm-lock.yaml

@@ -2219,6 +2219,85 @@ importers:
         specifier: ^0.34.6
         version: 0.34.6(jsdom@22.1.0)
 
+  ../../packages/materials/type-editor:
+    dependencies:
+      '@douyinfe/semi-icons':
+        specifier: ^2.80.0
+        version: 2.80.0(react@18.3.1)
+      '@douyinfe/semi-illustrations':
+        specifier: ^2.80.0
+        version: 2.80.0(react@18.3.1)
+      '@douyinfe/semi-ui':
+        specifier: ^2.80.0
+        version: 2.80.0(acorn@8.14.0)(react-dom@18.3.1)(react@18.3.1)
+      '@flowgram.ai/json-schema':
+        specifier: workspace:*
+        version: link:../../variable-engine/json-schema
+      '@flowgram.ai/utils':
+        specifier: workspace:*
+        version: link:../../common/utils
+      classnames:
+        specifier: ^2.5.1
+        version: 2.5.1
+      inversify:
+        specifier: ^6.0.1
+        version: 6.2.0(reflect-metadata@0.2.2)
+      lodash:
+        specifier: ^4.17.21
+        version: 4.17.21
+      nanoid:
+        specifier: ^4.0.2
+        version: 4.0.2
+      react-dnd:
+        specifier: 16.0.1
+        version: 16.0.1(@types/react@18.3.16)(react@18.3.1)
+      react-dnd-html5-backend:
+        specifier: 16.0.1
+        version: 16.0.1
+      reflect-metadata:
+        specifier: ~0.2.2
+        version: 0.2.2
+    devDependencies:
+      '@flowgram.ai/eslint-config':
+        specifier: workspace:*
+        version: link:../../../config/eslint-config
+      '@flowgram.ai/ts-config':
+        specifier: workspace:*
+        version: link:../../../config/ts-config
+      '@types/lodash':
+        specifier: ^4.14.137
+        version: 4.17.13
+      '@types/react':
+        specifier: ^18
+        version: 18.3.16
+      '@types/react-dom':
+        specifier: ^18
+        version: 18.3.5(@types/react@18.3.16)
+      '@types/styled-components':
+        specifier: ^5
+        version: 5.1.34
+      eslint:
+        specifier: ^8.54.0
+        version: 8.57.1
+      react:
+        specifier: ^18
+        version: 18.3.1
+      react-dom:
+        specifier: ^18
+        version: 18.3.1(react@18.3.1)
+      styled-components:
+        specifier: ^5
+        version: 5.3.11(@babel/core@7.26.10)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)
+      tsup:
+        specifier: ^8.0.1
+        version: 8.3.5(typescript@5.8.3)
+      typescript:
+        specifier: ^5.8.3
+        version: 5.8.3
+      vitest:
+        specifier: ^0.34.6
+        version: 0.34.6(jsdom@22.1.0)
+
   ../../packages/node-engine/form:
     dependencies:
       '@flowgram.ai/reactive':
@@ -4555,7 +4634,7 @@ packages:
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/traverse': 7.27.0(supports-color@5.5.0)
-      '@babel/types': 7.27.0
+      '@babel/types': 7.27.7
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -4600,7 +4679,7 @@ packages:
     resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.27.0
+      '@babel/types': 7.27.7
     dev: false
 
   /@babel/helper-plugin-utils@7.25.9:
@@ -4657,7 +4736,6 @@ packages:
   /@babel/helper-string-parser@7.27.1:
     resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
     engines: {node: '>=6.9.0'}
-    dev: false
 
   /@babel/helper-validator-identifier@7.25.9:
     resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
@@ -4666,7 +4744,6 @@ packages:
   /@babel/helper-validator-identifier@7.27.1:
     resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
     engines: {node: '>=6.9.0'}
-    dev: false
 
   /@babel/helper-validator-option@7.25.9:
     resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
@@ -4678,7 +4755,7 @@ packages:
     dependencies:
       '@babel/template': 7.27.0
       '@babel/traverse': 7.27.0(supports-color@5.5.0)
-      '@babel/types': 7.27.0
+      '@babel/types': 7.27.7
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -4718,7 +4795,6 @@ packages:
     hasBin: true
     dependencies:
       '@babel/types': 7.27.7
-    dev: false
 
   /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.26.0):
     resolution: {integrity: sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==}
@@ -5705,9 +5781,9 @@ packages:
     dependencies:
       '@babel/code-frame': 7.26.2
       '@babel/generator': 7.27.0
-      '@babel/parser': 7.27.0
+      '@babel/parser': 7.27.7
       '@babel/template': 7.27.0
-      '@babel/types': 7.27.0
+      '@babel/types': 7.27.7
       debug: 4.4.0(supports-color@5.5.0)
       globals: 11.12.0
     transitivePeerDependencies:
@@ -5733,7 +5809,6 @@ packages:
     dependencies:
       '@babel/helper-string-parser': 7.27.1
       '@babel/helper-validator-identifier': 7.27.1
-    dev: false
 
   /@bcoe/v8-coverage@0.2.3:
     resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
@@ -8460,6 +8535,18 @@ packages:
       react-dom: 18.3.1(react@18.3.1)
     dev: false
 
+  /@react-dnd/asap@5.0.2:
+    resolution: {integrity: sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==}
+    dev: false
+
+  /@react-dnd/invariant@4.0.2:
+    resolution: {integrity: sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==}
+    dev: false
+
+  /@react-dnd/shallowequal@4.0.2:
+    resolution: {integrity: sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==}
+    dev: false
+
   /@react-hook/intersection-observer@3.1.2(react@18.3.1):
     resolution: {integrity: sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ==}
     peerDependencies:
@@ -12017,6 +12104,14 @@ packages:
     dependencies:
       path-type: 4.0.0
 
+  /dnd-core@16.0.1:
+    resolution: {integrity: sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==}
+    dependencies:
+      '@react-dnd/asap': 5.0.2
+      '@react-dnd/invariant': 4.0.2
+      redux: 4.2.1
+    dev: false
+
   /doctrine@2.1.0:
     resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
     engines: {node: '>=0.10.0'}
@@ -17662,6 +17757,36 @@ packages:
       es6-symbol: 3.1.4
     dev: false
 
+  /react-dnd-html5-backend@16.0.1:
+    resolution: {integrity: sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==}
+    dependencies:
+      dnd-core: 16.0.1
+    dev: false
+
+  /react-dnd@16.0.1(@types/react@18.3.16)(react@18.3.1):
+    resolution: {integrity: sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==}
+    peerDependencies:
+      '@types/hoist-non-react-statics': '>= 3.3.1'
+      '@types/node': '>= 12'
+      '@types/react': '>= 16'
+      react: '>= 16.14'
+    peerDependenciesMeta:
+      '@types/hoist-non-react-statics':
+        optional: true
+      '@types/node':
+        optional: true
+      '@types/react':
+        optional: true
+    dependencies:
+      '@react-dnd/invariant': 4.0.2
+      '@react-dnd/shallowequal': 4.0.2
+      '@types/react': 18.3.16
+      dnd-core: 16.0.1
+      fast-deep-equal: 3.1.3
+      hoist-non-react-statics: 3.3.2
+      react: 18.3.1
+    dev: false
+
   /react-dom@16.8.6(react@16.14.0):
     resolution: {integrity: sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA==}
     peerDependencies:
@@ -17918,6 +18043,12 @@ packages:
   /reduce-configs@1.1.0:
     resolution: {integrity: sha512-DQxy6liNadHfrLahZR7lMdc227NYVaQZhY5FMsxLEjX8X0SCuH+ESHSLCoz2yDZFq1/CLMDOAHdsEHwOEXKtvg==}
 
+  /redux@4.2.1:
+    resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==}
+    dependencies:
+      '@babel/runtime': 7.26.0
+    dev: false
+
   /reflect-metadata@0.2.2:
     resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
 

+ 1 - 1
packages/materials/form-materials/src/components/constant-input/config.json

@@ -1,7 +1,7 @@
 {
   "name": "constant-input",
   "depMaterials": [
-    "shared/json-schema-preset"
+    "plugins/json-schema-preset"
   ],
   "depPackages": [
     "@douyinfe/semi-ui"

+ 1 - 1
packages/materials/form-materials/src/components/display-schema-tree/config.json

@@ -1,7 +1,7 @@
 {
   "name": "display-schema-tree",
   "depMaterials": [
-    "shared/json-schema-preset"
+    "plugins/json-schema-preset"
   ],
   "depPackages": [
     "@douyinfe/semi-ui",

+ 17 - 0
packages/materials/type-editor/.eslintrc.js

@@ -0,0 +1,17 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+const { defineConfig } = require('@flowgram.ai/eslint-config');
+
+module.exports = defineConfig({
+  preset: 'web',
+  packageRoot: __dirname,
+  rules: {
+    'no-console': 'off',
+    'react/no-deprecated': 'off',
+    '@flowgram.ai/e2e-data-testid': 'off',
+    'react/prop-types': 'off',
+  },
+});

+ 66 - 0
packages/materials/type-editor/package.json

@@ -0,0 +1,66 @@
+{
+  "name": "@flowgram.ai/type-editor",
+  "version": "0.1.8",
+  "homepage": "https://flowgram.ai/",
+  "repository": "https://github.com/bytedance/flowgram.ai",
+  "license": "MIT",
+  "exports": {
+    "types": "./dist/index.d.ts",
+    "import": "./dist/esm/index.js",
+    "require": "./dist/index.js"
+  },
+  "main": "./dist/index.js",
+  "module": "./dist/esm/index.js",
+  "types": "./dist/index.d.ts",
+  "files": [
+    "dist"
+  ],
+  "scripts": {
+    "build": "npm run build:fast -- --dts-resolve",
+    "build:fast": "tsup src/index.ts --format cjs,esm --sourcemap --legacy-output",
+    "build:watch": "npm run build:fast -- --dts-resolve",
+    "clean": "rimraf dist",
+    "test": "exit 0",
+    "test:cov": "exit 0",
+    "ts-check": "tsc --noEmit",
+    "watch": "npm run build:fast -- --dts-resolve --watch --ignore-watch dist"
+  },
+  "dependencies": {
+    "@douyinfe/semi-icons": "^2.80.0",
+    "@douyinfe/semi-illustrations": "^2.80.0",
+    "@douyinfe/semi-ui": "^2.80.0",
+    "@flowgram.ai/utils": "workspace:*",
+    "@flowgram.ai/json-schema": "workspace:*",
+    "classnames": "^2.5.1",
+    "lodash": "^4.17.21",
+    "nanoid": "^4.0.2",
+    "inversify": "^6.0.1",
+    "react-dnd": "16.0.1",
+    "reflect-metadata": "~0.2.2",
+    "react-dnd-html5-backend": "16.0.1"
+  },
+  "devDependencies": {
+    "@flowgram.ai/eslint-config": "workspace:*",
+    "@flowgram.ai/ts-config": "workspace:*",
+    "@types/lodash": "^4.14.137",
+    "@types/react": "^18",
+    "@types/react-dom": "^18",
+    "@types/styled-components": "^5",
+    "eslint": "^8.54.0",
+    "react": "^18",
+    "react-dom": "^18",
+    "styled-components": "^5",
+    "tsup": "^8.0.1",
+    "typescript": "^5.8.3",
+    "vitest": "^0.34.6"
+  },
+  "peerDependencies": {
+    "react": ">=16.8",
+    "react-dom": ">=16.8",
+    "styled-components": ">=4"
+  },
+  "publishConfig": {
+    "access": "public",
+    "registry": "https://registry.npmjs.org/"
+  }
+}

+ 42 - 0
packages/materials/type-editor/src/components/feedback/feedback.tsx

@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useMemo } from 'react';
+
+import { type Level } from './types';
+import { FeedbackStyle, RelativeWrapper } from './style';
+
+interface FeedbackProps {
+  // message 内容
+  message: string;
+  // 不hover状态下是否占用文档流
+  layout: 'relative' | 'absolute';
+  // 错误等级: error | warning
+  level?: Level;
+  // 是否展开:该状态仅适配layout 为relative 的状态
+  expanded?: boolean;
+}
+
+export const Feedback = ({ message, layout, level = 'error', expanded = false }: FeedbackProps) => {
+  const feedbackContent = useMemo(
+    () => (
+      <FeedbackStyle
+        expanded={expanded}
+        error={level === 'error'}
+        warning={level === 'warning'}
+        expandable={!expanded}
+      >
+        {message}
+      </FeedbackStyle>
+    ),
+    [level, message, expanded]
+  );
+
+  return layout === 'relative' ? (
+    <RelativeWrapper expanded={expanded}>{feedbackContent}</RelativeWrapper>
+  ) : (
+    feedbackContent
+  );
+};

+ 7 - 0
packages/materials/type-editor/src/components/feedback/index.ts

@@ -0,0 +1,7 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+export { Feedback } from './feedback';
+export * from './types';

+ 84 - 0
packages/materials/type-editor/src/components/feedback/style.ts

@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import styled from 'styled-components';
+
+export const FeedbackStyle = styled.div<{
+  // 是否展开
+  expanded?: boolean;
+  // 是否错误
+  error?: boolean;
+  // 是否警告
+  warning?: boolean;
+  // 是否可展开
+  expandable?: boolean;
+}>`
+  position: absolute;
+
+  ${(props) =>
+    props.expanded
+      ? `
+  position: relative;
+  `
+      : ''}
+
+  color: #fff;
+  max-width: 100%;
+  width: fit-content;
+  padding: 0 4px;
+  font-size: 10px;
+  line-height: 18px;
+  word-break: break-all;
+
+  &.expandable {
+  }
+
+  ${(props) =>
+    props.expandable
+      ? `
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+
+  &:hover {
+    white-space: normal;
+    overflow: visible;
+  }
+  `
+      : ''}
+
+  ${(props) =>
+    props.error
+      ? `
+    background-color: var(--semi-color-danger);
+    `
+      : ''}
+
+
+  ${(props) =>
+    props.warning
+      ? `
+    background-color:var(--semi-color-warning);
+    `
+      : ''}
+
+
+  z-index: 1;
+`;
+
+export const RelativeWrapper = styled.div<{
+  // 是否展开
+  expanded?: boolean;
+}>`
+  position: relative;
+  height: 18px;
+
+  ${(props) =>
+    props.expanded
+      ? `
+      height: fit-content;
+    `
+      : ''}
+`;

+ 6 - 0
packages/materials/type-editor/src/components/feedback/types.ts

@@ -0,0 +1,6 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+export type Level = 'error' | 'warning';

+ 7 - 0
packages/materials/type-editor/src/components/index.ts

@@ -0,0 +1,7 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+export * from './type-editor';
+export * from './type-selector';

+ 110 - 0
packages/materials/type-editor/src/components/type-editor/body.tsx

@@ -0,0 +1,110 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from 'react';
+
+import classNames from 'classnames';
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Empty } from '@douyinfe/semi-ui';
+import { IllustrationNoContent, IllustrationNoContentDark } from '@douyinfe/semi-illustrations';
+
+import {
+  TypeEditorColumnType,
+  TypeEditorRowData,
+  TypeEditorColumnViewConfig,
+  TypeChangeContext,
+} from '../../types';
+import { DragRowContainer } from './style';
+import { useRowDrag } from './hooks/drag-drop';
+import { ViewCell } from './cell';
+
+interface Props<TypeSchema extends Partial<IJsonSchema>> {
+  displayColumn: TypeEditorColumnType[];
+  dataSourceMap: Record<string, TypeEditorRowData<TypeSchema>>;
+  /**
+   * 自定义空状态
+   */
+  customEmptyNode?: React.ReactElement;
+  /**
+   * 每个列的配置
+   */
+  viewConfigs: TypeEditorColumnViewConfig[];
+  /**
+   * 只读态
+   */
+  readonly?: boolean;
+  onFieldChange?: (ctx: TypeChangeContext) => void;
+  onChange: () => void;
+  onPaste?: (typeSchema?: TypeSchema) => TypeSchema | undefined;
+  onError?: (msg?: string[]) => void;
+  onChildrenVisibleChange: (rowDataId: string, newVal: boolean) => void;
+  unOpenKeys: Record<string, boolean>;
+}
+
+const Row = <TypeSchema extends Partial<IJsonSchema>>({
+  displayColumn,
+  data,
+  ...rest
+}: Props<TypeSchema> & {
+  data: TypeEditorRowData<TypeSchema>;
+  rowIndex: number;
+}) => {
+  const { preview, drag, isDragging } = useRowDrag(data.id, rest.onChildrenVisibleChange);
+
+  return (
+    <DragRowContainer ref={preview} dragging={isDragging} className={classNames('semi-table-row')}>
+      {displayColumn.map((column, columnIndex) => (
+        <ViewCell
+          dragSource={data.cannotDrag ? undefined : drag}
+          key={data.id + column}
+          columnType={column}
+          columnIndex={columnIndex}
+          rowData={data}
+          {...rest}
+        />
+      ))}
+    </DragRowContainer>
+  );
+};
+
+export const Body = <TypeSchema extends Partial<IJsonSchema>>({
+  dataSource,
+  customEmptyNode,
+  ...rest
+}: Props<TypeSchema> & {
+  dataSource: TypeEditorRowData<TypeSchema>[];
+}) => {
+  if (dataSource.length === 0) {
+    const rows = rest.displayColumn.length;
+    return (
+      <tbody>
+        <tr>
+          <td colSpan={rows}>
+            {customEmptyNode ? (
+              customEmptyNode
+            ) : (
+              <Empty
+                style={{ marginTop: 40 }}
+                image={<IllustrationNoContent style={{ width: 100, height: 100 }} />}
+                darkModeImage={<IllustrationNoContentDark style={{ width: 100, height: 100 }} />}
+                description="No content. Please add."
+              />
+            )}
+          </td>
+        </tr>
+      </tbody>
+    );
+  }
+
+  return (
+    <tbody className="semi-table-tbody">
+      <>
+        {dataSource.map((data, rowIndex) => (
+          <Row key={data.id} {...rest} data={data} rowIndex={rowIndex} />
+        ))}
+      </>
+    </tbody>
+  );
+};

+ 243 - 0
packages/materials/type-editor/src/components/type-editor/cell.tsx

@@ -0,0 +1,243 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { type ConnectDragSource } from 'react-dnd';
+import React, { useCallback, useMemo } from 'react';
+
+import classNames from 'classnames';
+import { NOOP } from '@flowgram.ai/utils';
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { Feedback } from '../feedback';
+import {
+  TypeEditorRowData,
+  TypeChangeContext,
+  TypeEditorColumnViewConfig,
+  TypeEditorColumnType,
+} from '../../types';
+import { TypeEditorService } from '../../services';
+import { useService } from '../../contexts';
+import { EditCellContainer, EditorTableCell, ErrorMsgContainer } from './style';
+import { useIsBlink } from './hooks/blink';
+import { useActivePos, useCellDrop, useViewCellErrorMsg, useEditCellErrorMsg } from './hooks';
+import { ErrorCellBorder } from './error';
+import { CELL_HIGHT, HEADER_HIGHT, TAB_BRA_HIGHT } from './common';
+
+export const ViewCell = <TypeSchema extends Partial<IJsonSchema>>({
+  rowData,
+  rowIndex,
+  columnIndex,
+  onChange,
+  dataSourceMap,
+  onError,
+  onFieldChange,
+  onPaste,
+  unOpenKeys,
+  viewConfigs,
+  readonly,
+  onChildrenVisibleChange,
+  dragSource,
+  columnType,
+}: {
+  rowData: TypeEditorRowData<TypeSchema>;
+  rowIndex: number;
+  onError?: (msg: string[]) => void;
+  onPaste?: (typeSchema?: TypeSchema) => TypeSchema | undefined;
+  onChange: () => void;
+  columnIndex: number;
+  onFieldChange?: (ctx: TypeChangeContext) => void;
+  dragSource?: ConnectDragSource;
+  dataSourceMap: Record<string, TypeEditorRowData<TypeSchema>>;
+  onChildrenVisibleChange: (rowDataId: string, val: boolean) => void;
+  /**
+   * 只读态
+   */
+  readonly?: boolean;
+  /**
+   * 每个列的配置
+   */
+  viewConfigs: TypeEditorColumnViewConfig[];
+  unOpenKeys: Record<string, boolean>;
+  columnType: TypeEditorColumnType;
+}) => {
+  const typeEditorService = useService<TypeEditorService<TypeSchema>>(TypeEditorService);
+
+  const config = typeEditorService.getConfigByType(columnType)!;
+
+  const { drop } = useCellDrop(rowData, rowIndex, dataSourceMap, onChange);
+
+  const handleEditMode = useCallback(() => {
+    if (!readonly) {
+      typeEditorService.setActivePos({ x: columnIndex, y: rowIndex });
+    }
+  }, [columnIndex, readonly, rowIndex]);
+
+  const handleViewMode = useCallback(() => {
+    typeEditorService.clearActivePos();
+  }, []);
+
+  const Render = config.viewRender;
+
+  const feedbackInfo = useViewCellErrorMsg(rowData, config, {
+    x: columnIndex,
+    y: rowIndex,
+  });
+
+  return (
+    <EditorTableCell
+      ref={config.customDrop ? undefined : drop}
+      className={classNames('semi-table-row-cell', 'cell-container')}
+    >
+      {feedbackInfo && (
+        <>
+          <ErrorCellBorder level={feedbackInfo.level} />
+          <ErrorMsgContainer>
+            <Feedback message={feedbackInfo.msg!} level={feedbackInfo.level} layout="absolute" />
+          </ErrorMsgContainer>
+        </>
+      )}
+
+      {Render ? (
+        <Render
+          readonly={readonly}
+          config={viewConfigs.find((v) => v.type === columnType) || {}}
+          dragSource={dragSource}
+          unOpenKeys={unOpenKeys}
+          onFieldChange={onFieldChange}
+          typeEditor={typeEditorService}
+          onPaste={onPaste}
+          error={!!feedbackInfo}
+          onError={onError}
+          onChange={onChange}
+          onChildrenVisibleChange={onChildrenVisibleChange}
+          onViewMode={handleViewMode}
+          onEditMode={handleEditMode}
+          rowData={rowData}
+        />
+      ) : (
+        `${(rowData as unknown as Record<string, unknown>)[columnType]}`
+      )}
+    </EditorTableCell>
+  );
+};
+
+export const EditCell = <TypeSchema extends Partial<IJsonSchema>>({
+  displayColumn,
+  onFieldChange,
+  dataSource,
+  onPaste,
+  unOpenKeys,
+  onError,
+  viewConfigs,
+  onChildrenVisibleChange,
+  tableDom,
+  onChange,
+}: {
+  displayColumn: TypeEditorColumnType[];
+  tableDom: HTMLTableElement;
+  onError?: (msg: string[]) => void;
+  onPaste?: (typeSchema?: TypeSchema) => TypeSchema | undefined;
+
+  /**
+   * 每个列的配置
+   */
+  viewConfigs: TypeEditorColumnViewConfig[];
+  unOpenKeys: Record<string, boolean>;
+  onChildrenVisibleChange: (rowDataId: string, newVal: boolean) => void;
+  onChange: () => void;
+  onFieldChange?: (ctx: TypeChangeContext) => void;
+  dataSource: TypeEditorRowData<TypeSchema>[];
+}) => {
+  const typeEditorService = useService<TypeEditorService<TypeSchema>>(TypeEditorService);
+
+  const activePos = useActivePos();
+
+  const handleViewMode = useCallback(() => {
+    typeEditorService.clearActivePos();
+  }, []);
+
+  const columnType = useMemo(() => displayColumn[activePos.x], [displayColumn, activePos.x]);
+
+  /**
+   * 获取每一列的宽度
+   */
+  const columnEachWidth = useMemo(() => {
+    const headerNodes = tableDom.childNodes?.[0]?.childNodes?.[0];
+    const width: number[] = [];
+    for (const item of headerNodes.childNodes.values()) {
+      // 1px border
+      width.push((item as HTMLElement).clientWidth + 1);
+    }
+
+    return width;
+  }, [displayColumn, tableDom, tableDom.clientWidth]);
+
+  /**
+   * 获取每一列距离 left: 0 的宽度
+   */
+  const columnAccWidth = useMemo(() => {
+    let acc = 0;
+    return columnEachWidth.map((width) => {
+      acc += width;
+      return acc - width;
+    });
+  }, [columnEachWidth]);
+
+  const rowData = useMemo(() => dataSource[activePos.y], [dataSource, activePos.y]);
+
+  const config = typeEditorService.getConfigByType(columnType);
+
+  const Render = config && (config.editRender || config.viewRender);
+
+  const errorMsg = useEditCellErrorMsg(activePos);
+
+  const blink = useIsBlink();
+
+  if (!config) {
+    return <></>;
+  }
+
+  return (
+    <>
+      {rowData && (
+        <EditCellContainer
+          error={!!errorMsg}
+          blink={blink}
+          key={rowData.key + columnType}
+          style={{
+            width: columnEachWidth[activePos.x] + 1,
+            top: HEADER_HIGHT + TAB_BRA_HIGHT + activePos.y * CELL_HIGHT + 1,
+            left: columnAccWidth[activePos.x],
+          }}
+        >
+          {Render ? (
+            <Render
+              error={!!errorMsg}
+              onError={onError}
+              config={viewConfigs.find((v) => v.type === columnType) || {}}
+              key={rowData.id}
+              unOpenKeys={unOpenKeys}
+              onChange={onChange}
+              onPaste={onPaste}
+              typeEditor={typeEditorService}
+              onFieldChange={onFieldChange}
+              onChildrenVisibleChange={onChildrenVisibleChange}
+              onEditMode={NOOP}
+              onViewMode={handleViewMode}
+              rowData={rowData}
+            />
+          ) : (
+            `${(rowData as unknown as Record<string, unknown>)[columnType]}`
+          )}
+          {errorMsg && (
+            <ErrorMsgContainer style={{ left: -1 }}>
+              <Feedback message={errorMsg} level="error" layout="absolute" />
+            </ErrorMsgContainer>
+          )}
+        </EditCellContainer>
+      )}
+    </>
+  );
+};

+ 226 - 0
packages/materials/type-editor/src/components/type-editor/columns/default.tsx

@@ -0,0 +1,226 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Tooltip, Typography } from '@douyinfe/semi-ui';
+
+import { useDisabled } from '../hooks/disabled';
+import { TypeEditorColumnConfig, TypeEditorColumnType, TypeEditorRowData } from '../../../types';
+import { TypeEditorService } from '../../../services';
+import { useTypeDefinitionManager } from '../../../contexts';
+import {
+  GlobalSelectStyle,
+  KeyEditorContainer,
+  KeyViewContainer,
+  TypeTextContainer,
+} from './style';
+
+const useFormatValue = ({
+  rowData,
+  onChange,
+  typeEditor,
+}: {
+  rowData: TypeEditorRowData<IJsonSchema>;
+  typeEditor: TypeEditorService<IJsonSchema>;
+  onChange: () => void;
+}) => {
+  const valueRef = useRef(rowData.self.default);
+  const [value, setValue] = useState(rowData.self.default);
+
+  const typeDefinition = useTypeDefinitionManager();
+
+  useEffect(() => {
+    typeEditor.editValue = value;
+  }, [typeEditor, value]);
+
+  useEffect(() => {
+    setValue(rowData.self.default);
+    valueRef.current = rowData.self.default;
+  }, [rowData.self.default]);
+
+  const handleSubmit = useCallback(() => {
+    rowData.self.default = valueRef.current;
+
+    const config = typeDefinition.getTypeBySchema(rowData.self);
+
+    if (config?.formatDefault) {
+      config.formatDefault(valueRef.current, rowData.self);
+    }
+
+    const parenConfig = rowData.parent && typeDefinition.getTypeBySchema(rowData.parent);
+
+    if (parenConfig?.formatDefault) {
+      parenConfig.formatDefault(valueRef.current, rowData.parent!);
+    }
+
+    onChange();
+
+    // onViewMode();
+  }, [onChange, rowData, typeDefinition]);
+
+  const deFormatValue = useMemo(() => {
+    const config = typeDefinition.getTypeBySchema(rowData.self);
+    return config?.deFormatDefault ? config.deFormatDefault(value) : value;
+  }, [value, rowData]);
+
+  const handleChange = useCallback((v: unknown) => {
+    valueRef.current = v;
+    setValue(v);
+  }, []);
+
+  return {
+    handleChange,
+    handleSubmit,
+    deFormatValue,
+  };
+};
+
+export const TypeText = <TypeSchema extends Partial<IJsonSchema>>({
+  value,
+  type,
+}: {
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  value?: any;
+  type: TypeSchema;
+}) => {
+  const typeDefinition = useTypeDefinitionManager();
+
+  const content = useMemo(() => {
+    const config = typeDefinition.getTypeBySchema(type);
+
+    return config?.getValueText ? config.getValueText(value) : value;
+  }, [value, type]);
+
+  return (
+    <TypeTextContainer>
+      <Typography.Text ellipsis={{ showTooltip: true }}>{content}</Typography.Text>
+    </TypeTextContainer>
+  );
+};
+
+const ViewRender: TypeEditorColumnConfig<IJsonSchema>['viewRender'] = ({
+  rowData,
+  typeEditor,
+  onEditMode,
+  onChange,
+}) => {
+  const disabled = useDisabled(TypeEditorColumnType.Default, rowData);
+
+  const typeDefinition = useTypeDefinitionManager();
+  const { defaultMode = 'default' } = rowData.extraConfig;
+
+  const { customDefaultView } = rowData.extraConfig;
+
+  const { deFormatValue, handleChange, handleSubmit } = useFormatValue({
+    rowData,
+    onChange,
+    typeEditor,
+  });
+
+  const customNode =
+    customDefaultView &&
+    customDefaultView({
+      rowData,
+      disabled,
+      value: deFormatValue,
+      onChange: handleChange,
+      onSubmit: (v) => {
+        handleChange(v);
+        handleSubmit();
+      },
+    });
+
+  if (customNode) {
+    return <> {customNode}</>;
+  }
+
+  if (disabled) {
+    return (
+      <KeyViewContainer disabled>
+        <Tooltip content={disabled}>
+          <div style={{ width: '100%', height: '100%' }}>
+            <TypeText value={deFormatValue} type={rowData.self} />
+          </div>
+        </Tooltip>
+      </KeyViewContainer>
+    );
+  }
+
+  if (defaultMode === 'server') {
+    const config = typeDefinition.getTypeBySchema(rowData.self);
+
+    return (
+      <Tooltip content="The default value is not allowed to be modified.">
+        <KeyViewContainer disabled>
+          <Typography.Text>{JSON.stringify(config?.getDefaultValue?.())}</Typography.Text>
+        </KeyViewContainer>
+      </Tooltip>
+    );
+  }
+
+  return (
+    <KeyViewContainer onClick={() => onEditMode()}>
+      <TypeText value={deFormatValue} type={rowData.self} />
+    </KeyViewContainer>
+  );
+};
+
+const EditRender: TypeEditorColumnConfig<IJsonSchema>['editRender'] = ({
+  rowData,
+  onChange,
+  typeEditor,
+  onViewMode,
+}) => {
+  const { deFormatValue, handleChange, handleSubmit } = useFormatValue({
+    rowData,
+    onChange,
+    typeEditor,
+  });
+
+  const typeDefinition = useTypeDefinitionManager();
+  const config = useMemo(() => typeDefinition.getTypeBySchema(rowData.self), [rowData.self]);
+  return (
+    <KeyEditorContainer>
+      <GlobalSelectStyle />
+      {config &&
+        config?.getInputNode?.({
+          value: deFormatValue,
+          onChange: handleChange,
+          type: rowData.self,
+          onSubmit: () => {
+            handleSubmit();
+            onViewMode();
+          },
+        })}
+    </KeyEditorContainer>
+  );
+};
+
+export const defaultColumnConfig: TypeEditorColumnConfig<IJsonSchema> = {
+  type: TypeEditorColumnType.Default,
+  width: 15,
+  label: 'Default',
+  viewRender: ViewRender,
+  shortcuts: {
+    onEnter: ({ rowData, onChange, typeEditor, value, typeDefinitionService }) => {
+      const config = typeDefinitionService.getTypeBySchema(rowData.self);
+      if (config?.typeInputConfig?.canEnter) {
+        rowData.self.default = value;
+
+        onChange();
+
+        typeEditor.moveActivePosToNextLineWithAddLine(rowData);
+      }
+    },
+    onTab: ({ rowData, value, onChange, typeEditor }) => {
+      rowData.self.default = value;
+      onChange();
+      typeEditor.moveActivePosToNextItem();
+    },
+  },
+  editRender: EditRender,
+};

+ 88 - 0
packages/materials/type-editor/src/components/type-editor/columns/description.tsx

@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { useEffect, useState } from 'react';
+import React from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Tooltip } from '@douyinfe/semi-ui';
+
+import { useDisabled } from '../hooks';
+import { TypeEditorColumnConfig, TypeEditorColumnType } from '../../../types';
+import { KeyEditorContainer, KeyEditorInput, KeyViewContainer, KeyViewText } from './style';
+
+const ViewRender: TypeEditorColumnConfig<IJsonSchema>['viewRender'] = ({ rowData, onEditMode }) => {
+  const disabled = useDisabled(TypeEditorColumnType.Description, rowData);
+
+  if (disabled) {
+    return (
+      <KeyViewContainer disabled>
+        <Tooltip content={disabled}>
+          <KeyViewText>{rowData.description}</KeyViewText>
+        </Tooltip>
+      </KeyViewContainer>
+    );
+  }
+
+  return (
+    <KeyViewContainer onClick={() => onEditMode()}>
+      <KeyViewText>{rowData.description}</KeyViewText>
+    </KeyViewContainer>
+  );
+};
+
+const EditRender: TypeEditorColumnConfig<IJsonSchema>['editRender'] = ({
+  rowData,
+  onChange,
+  typeEditor,
+  onViewMode,
+}) => {
+  const [value, setValue] = useState(rowData.description);
+
+  useEffect(() => {
+    typeEditor.editValue = value;
+  }, [typeEditor, value]);
+
+  useEffect(() => {
+    setValue(rowData.description);
+  }, [rowData.description]);
+
+  return (
+    <KeyEditorContainer>
+      <KeyEditorInput
+        onChange={setValue}
+        autoFocus
+        onBlur={() => {
+          const { self } = rowData;
+          self.description = value;
+          onChange();
+
+          onViewMode();
+        }}
+        value={value}
+      />
+    </KeyEditorContainer>
+  );
+};
+
+export const descriptionColumnConfig: TypeEditorColumnConfig<IJsonSchema> = {
+  type: TypeEditorColumnType.Description,
+  width: 15,
+  label: 'Description',
+  viewRender: ViewRender,
+  shortcuts: {
+    onEnter: ({ rowData, value, onChange, typeEditor }) => {
+      rowData.self.description = value;
+      onChange();
+      typeEditor.moveActivePosToNextLineWithAddLine(rowData);
+    },
+    onTab: ({ rowData, value, onChange, typeEditor }) => {
+      rowData.self.description = value;
+      onChange();
+      typeEditor.moveActivePosToNextItem();
+    },
+  },
+  editRender: EditRender,
+};

+ 24 - 0
packages/materials/type-editor/src/components/type-editor/columns/index.ts

@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { valueColumnConfig } from './value';
+import { typeColumnConfig } from './type';
+import { requiredColumnConfig } from './required';
+import { privateColumnConfig } from './private';
+import { operateColumnConfig } from './operate';
+import { keyColumnConfig } from './key';
+import { descriptionColumnConfig } from './description';
+import { defaultColumnConfig } from './default';
+
+export const columnConfigs = [
+  keyColumnConfig,
+  typeColumnConfig,
+  requiredColumnConfig,
+  descriptionColumnConfig,
+  privateColumnConfig,
+  valueColumnConfig,
+  defaultColumnConfig,
+  operateColumnConfig,
+];

+ 355 - 0
packages/materials/type-editor/src/components/type-editor/columns/key.tsx

@@ -0,0 +1,355 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+/* eslint-disable complexity */
+
+import React, { useEffect, useState } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Tooltip } from '@douyinfe/semi-ui';
+import { IconCopyAdd, IconHandle, IconPlus, IconTreeTriangleDown } from '@douyinfe/semi-icons';
+
+import { getComponentId, typeEditorUtils } from '../utils';
+import { WidthIndent } from '../indent';
+import { useAddType, usePasteAddType } from '../hooks/type-edit';
+import { usePasteData } from '../hooks/paste-data';
+import { useKeyVisible } from '../hooks/key-visible';
+import { useDisabled } from '../hooks/disabled';
+import {
+  ShortcutContext,
+  TypeEditorColumnConfig,
+  TypeEditorColumnType,
+  TypeEditorRowData,
+} from '../../../types';
+import { useTypeDefinitionManager } from '../../../contexts';
+import {
+  BaseIcon,
+  KeyEditorContainer,
+  KeyEditorInput,
+  KeyViewContainer,
+  KeyViewContent,
+  KeyViewText,
+} from './style';
+
+const ViewRender: TypeEditorColumnConfig<IJsonSchema>['viewRender'] = ({
+  rowData,
+  onEditMode,
+  readonly,
+  onChange,
+  onPaste,
+  unOpenKeys,
+  onChildrenVisibleChange,
+  dragSource,
+}) => {
+  const { extraConfig } = rowData;
+
+  const typeService = useTypeDefinitionManager();
+
+  const config = typeService.getTypeBySchema(rowData.self);
+
+  const disabled = useDisabled(TypeEditorColumnType.Key, rowData);
+
+  const { hiddenDrag: originHiddenDrag } = extraConfig;
+
+  const hiddenDrag = originHiddenDrag || readonly;
+
+  const text = (
+    <KeyViewText disabled={!!disabled} ellipsis={{ showTooltip: true }}>
+      {typeEditorUtils.formateKey(rowData.key)}
+    </KeyViewText>
+  );
+
+  const visibleNode = useKeyVisible(rowData, onChange, extraConfig);
+
+  const handleAddType = useAddType(rowData, onChange);
+
+  const draggable = !(rowData.cannotDrag || disabled || hiddenDrag);
+
+  const { pasteData } = usePasteData();
+  const handlePasteAddType = usePasteAddType(rowData, onChange, onPaste);
+
+  if (config && config.canAddField?.(rowData)) {
+    const disabledAdd = extraConfig.disabledAdd ? extraConfig.disabledAdd(rowData) : undefined;
+
+    return (
+      <KeyViewContainer
+        id={getComponentId('key-text')}
+        onClick={
+          disabled
+            ? undefined
+            : () => {
+                onEditMode();
+              }
+        }
+      >
+        {!hiddenDrag && (
+          <BaseIcon draggable disabled={!draggable}>
+            <IconHandle ref={draggable ? dragSource : undefined} />
+          </BaseIcon>
+        )}
+
+        <WidthIndent width={rowData.level * 16} />
+        <KeyViewContent>
+          {rowData.childrenCount ? (
+            <BaseIcon triangle isRotate={!unOpenKeys[rowData.id]}>
+              <IconTreeTriangleDown
+                size="small"
+                onClick={(e) => {
+                  onChildrenVisibleChange(rowData.id, !unOpenKeys[rowData.id]);
+                  e.stopPropagation();
+                }}
+              />
+            </BaseIcon>
+          ) : (
+            <WidthIndent width={12} />
+          )}
+
+          {disabled ? (
+            <div style={{ flex: 1 }}>
+              <Tooltip content={disabled}>{text}</Tooltip>
+            </div>
+          ) : (
+            text
+          )}
+
+          {!readonly && (
+            <>
+              {disabledAdd ? (
+                <Tooltip content={disabledAdd}>
+                  <BaseIcon disabled>
+                    <IconPlus id={getComponentId('add-field')} size="small" />
+                  </BaseIcon>
+                </Tooltip>
+              ) : (
+                <Tooltip content="add child field">
+                  <BaseIcon>
+                    <IconPlus
+                      size="small"
+                      onClick={handleAddType}
+                      id={getComponentId('add-field')}
+                    />
+                  </BaseIcon>
+                </Tooltip>
+              )}
+              {disabledAdd || pasteData.type === 'invalid' ? (
+                <Tooltip
+                  content={disabledAdd || 'Please confirm whether the clipboard value is correct'}
+                >
+                  <BaseIcon disabled>
+                    <IconCopyAdd size="small" />
+                  </BaseIcon>
+                </Tooltip>
+              ) : (
+                <Tooltip content="paste as new child fields">
+                  <BaseIcon>
+                    <IconCopyAdd size="small" onClick={handlePasteAddType} />
+                  </BaseIcon>
+                </Tooltip>
+              )}
+            </>
+          )}
+
+          {extraConfig.editorVisible && visibleNode}
+        </KeyViewContent>
+      </KeyViewContainer>
+    );
+  }
+
+  return (
+    <KeyViewContainer
+      id={getComponentId('key-text')}
+      onClick={
+        disabled
+          ? undefined
+          : () => {
+              onEditMode();
+            }
+      }
+    >
+      {!hiddenDrag && (
+        <BaseIcon draggable disabled={!draggable}>
+          <IconHandle ref={draggable ? dragSource : undefined} />
+        </BaseIcon>
+      )}
+
+      <WidthIndent width={(rowData.level + 1) * 16} />
+      <KeyViewContent>
+        {disabled ? <Tooltip content={disabled}>{text}</Tooltip> : text}
+        {extraConfig.editorVisible && visibleNode}
+      </KeyViewContent>
+    </KeyViewContainer>
+  );
+};
+
+const validate = (value: string, rowData: TypeEditorRowData<IJsonSchema>): string | undefined => {
+  const res = rowData.extraConfig?.customValidateName?.(value);
+
+  const lastValue = rowData.key;
+
+  const parentTypeSchema = rowData.parent;
+
+  if (value !== lastValue && parentTypeSchema?.properties?.[value]) {
+    return 'The same key exists at the current level.';
+  }
+  if (res) {
+    return res;
+  }
+};
+
+const changeValue = ({
+  rowData,
+  value,
+}: {
+  rowData: TypeEditorRowData<IJsonSchema>;
+  value: string;
+}) => {
+  const lastValue = rowData.key;
+
+  const parentTypeSchema = rowData.parent;
+
+  if (value !== lastValue) {
+    if (parentTypeSchema && parentTypeSchema.properties) {
+      parentTypeSchema.properties[value] = parentTypeSchema.properties[lastValue];
+    }
+
+    if (parentTypeSchema?.properties) {
+      delete parentTypeSchema.properties[lastValue];
+    }
+  }
+};
+
+const EditRender: TypeEditorColumnConfig<IJsonSchema>['editRender'] = ({
+  rowData,
+  onChange,
+  typeEditor,
+  onError,
+  onFieldChange,
+  onViewMode,
+}) => {
+  const [value, setValue] = useState(typeEditorUtils.formateKey(rowData.key));
+
+  useEffect(() => {
+    typeEditor.editValue = value;
+  }, [typeEditor, value]);
+
+  return (
+    <KeyEditorContainer>
+      <WidthIndent
+        style={{ background: 'var(--semi-color-fill-0)' }}
+        width={
+          (rowData.level + 2) * 14 +
+          (2 * rowData.level - 1) +
+          (rowData.extraConfig.hiddenDrag ? -16 : 0)
+        }
+      />
+      <KeyEditorInput
+        id={getComponentId('key-edit')}
+        onChange={(v) => {
+          setValue(v);
+          typeEditor.dataSourceTouchedMap[rowData.id] = true;
+          const res = validate(v, rowData);
+          typeEditor.setErrorMsg(typeEditor.activePos, res);
+          if (onError) {
+            onError(res ? [res] : []);
+          }
+        }}
+        autoFocus
+        onBlur={(e) => {
+          const oldValue = rowData.key;
+
+          if (value && validate(value, rowData)) {
+            typeEditor.blink.update(true);
+            e.stopPropagation();
+            e.preventDefault();
+            return;
+          }
+
+          const newVal = typeEditorUtils.deFormateKey(value, oldValue);
+
+          changeValue({ rowData, value: newVal });
+
+          onFieldChange &&
+            onFieldChange({
+              type: TypeEditorColumnType.Key,
+              oldValue,
+              newValue: newVal,
+            });
+
+          if (onError) {
+            onError([]);
+          }
+          onChange();
+          onViewMode();
+        }}
+        value={value}
+      />
+    </KeyEditorContainer>
+  );
+};
+
+const dealMove = (
+  { rowData, value, onChange, typeEditor }: ShortcutContext<IJsonSchema>,
+  cb: () => void
+) => {
+  const validateRes = validate(value, rowData);
+  if (value && validateRes) {
+    typeEditor.blink.update(true);
+  } else {
+    changeValue({
+      value: typeEditorUtils.deFormateKey(value),
+      rowData,
+    });
+    typeEditor.setErrorMsg(typeEditor.activePos);
+    onChange();
+    cb();
+  }
+};
+
+export const keyColumnConfig: TypeEditorColumnConfig<IJsonSchema> = {
+  type: TypeEditorColumnType.Key,
+  label: 'Key',
+  viewRender: ViewRender,
+  width: 43,
+  validateCell: (rowData, ctx) => {
+    const key = typeEditorUtils.formateKey(rowData.key);
+    if (!key) {
+      return {
+        level: 'warning',
+        msg: 'Empty lines will not be saved.',
+      };
+    }
+
+    if (ctx.customValidateName) {
+      const customValidateRes = ctx.customValidateName(key);
+      if (customValidateRes) {
+        return {
+          level: 'error',
+          msg: customValidateRes,
+        };
+      }
+      return;
+    }
+
+    if (validate(key, rowData)) {
+      return {
+        level: 'error',
+        msg: validate(key, rowData),
+      };
+    }
+  },
+  shortcuts: {
+    onEnter: (ctx) => {
+      dealMove(ctx, () => {
+        ctx.typeEditor.moveActivePosToNextLineWithAddLine(ctx.rowData);
+      });
+    },
+    onTab: (ctx) => {
+      dealMove(ctx, () => {
+        ctx.typeEditor.moveActivePosToNextItem();
+      });
+    },
+  },
+  editRender: EditRender,
+};

+ 209 - 0
packages/materials/type-editor/src/components/type-editor/columns/operate.tsx

@@ -0,0 +1,209 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from 'react';
+
+import { pick } from '@flowgram.ai/utils';
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import toast from '@douyinfe/semi-ui/lib/es/toast';
+import { Toast, Tooltip } from '@douyinfe/semi-ui';
+import { IconCopy, IconCopyAdd, IconDelete } from '@douyinfe/semi-icons';
+
+import { getComponentId, typeEditorUtils } from '../utils';
+import { useRemoveType } from '../hooks/type-edit';
+import { type PasteDataType, usePasteData } from '../hooks/paste-data';
+import { useHasErrorCell } from '../hooks/error-cell';
+import { useDisabled } from '../hooks/disabled';
+
+// import s from './index.module.less';
+import { TypeEditorRowData, TypeEditorColumnConfig, TypeEditorColumnType } from '../../../types';
+import { ClipboardService } from '../../../services';
+import { useService, useTypeDefinitionManager } from '../../../contexts';
+import { BaseIcon, CenterContainer } from './style';
+
+const pasteTooltipsMap: Record<PasteDataType<IJsonSchema>['type'], string | undefined> = {
+  invalid: 'Please confirm whether the clipboard value is correct',
+  multiple: 'Multiple fields are not supported to be pasted into this field.',
+  single: undefined,
+};
+
+const setCellValue = (
+  rowData: TypeEditorRowData<IJsonSchema>,
+  newData: IJsonSchema & {
+    extra?: {
+      key: string;
+      required: boolean;
+    };
+  },
+  onPaste?: (typeSchema?: IJsonSchema) => IJsonSchema | undefined
+): void => {
+  const othersProps = pick(rowData.self, ['required', 'description', 'flow', 'extra']);
+
+  if (rowData.parent?.properties) {
+    const { parent } = rowData;
+    // const key = newDat
+    if (newData.extra) {
+      let { key } = newData.extra;
+      const { required } = newData.extra;
+
+      // key !== rowData.key 处理重复对某个字段进行 paste
+      while (parent.properties![key] && key !== rowData.key) {
+        key = `${key}__copy`;
+      }
+
+      delete parent.properties![rowData.key];
+
+      delete newData.extra;
+      let newPasteData = {
+        ...newData,
+        ...othersProps,
+      };
+      if (onPaste) {
+        newPasteData = onPaste(newPasteData) || newPasteData;
+      }
+
+      parent.properties![key] = newPasteData;
+
+      if (!parent.required) {
+        parent.required = [];
+      }
+
+      const idx = parent.required.findIndex((v) => v === rowData.key);
+
+      if (idx !== -1) {
+        parent.required.splice(idx, 1);
+      }
+      if (required) {
+        parent.required.push(key);
+      }
+    } else {
+      let newPasteData = {
+        ...newData,
+        ...othersProps,
+      };
+      if (onPaste) {
+        newPasteData = onPaste(newPasteData) || newPasteData;
+      }
+      parent.properties![rowData.key] = newPasteData;
+    }
+  }
+};
+
+export const operateColumnConfig: TypeEditorColumnConfig<IJsonSchema> = {
+  type: TypeEditorColumnType.Operate,
+  label: 'Operate',
+  width: 11,
+  focusable: false,
+  viewRender: ({ rowData, onChange, typeEditor, onPaste }) => {
+    const disabled = useDisabled(TypeEditorColumnType.Operate, rowData);
+
+    const clipboard = useService<ClipboardService>(ClipboardService);
+
+    const { pasteData } = usePasteData();
+
+    const typeDefinition = useTypeDefinitionManager();
+
+    const hasError = useHasErrorCell(rowData, typeEditor);
+
+    const handleRemove = useRemoveType(rowData, onChange);
+
+    const pasteErrorTips = pasteTooltipsMap[pasteData.type];
+
+    return (
+      <CenterContainer>
+        <Tooltip content={disabled || 'Remove'}>
+          <BaseIcon primary disabled={!!disabled}>
+            <IconDelete
+              id={getComponentId('remove-field')}
+              onClick={disabled ? undefined : handleRemove}
+              size="small"
+            />
+          </BaseIcon>
+        </Tooltip>
+        <Tooltip
+          content={
+            hasError
+              ? 'The current field is incorrect and does not support copying.'
+              : 'Copy this field'
+          }
+        >
+          <BaseIcon primary disabled={!!hasError}>
+            <IconCopy
+              onClick={() => {
+                if (hasError) {
+                  return;
+                }
+                const copyData = {
+                  ...rowData.self,
+                  extra: {
+                    key: rowData.key,
+                    required: rowData.isRequired,
+                    ...(rowData.self.extra || {}),
+                  },
+                };
+
+                clipboard.writeData(JSON.stringify(copyData));
+                toast.success('copy this field success!');
+              }}
+              size="small"
+            />
+          </BaseIcon>
+        </Tooltip>
+        <Tooltip
+          content={disabled ? disabled : !pasteErrorTips ? 'Paste field here' : pasteErrorTips}
+        >
+          <BaseIcon primary disabled={!!(pasteErrorTips || disabled)}>
+            <IconCopyAdd
+              size="small"
+              onClick={() => {
+                if (pasteErrorTips || disabled) {
+                  return;
+                }
+                clipboard.readData().then((data) => {
+                  const parseData = typeEditorUtils.jsonParse(data) as IJsonSchema;
+
+                  const originIndex = rowData.extra?.index || 0;
+
+                  const config = parseData && typeDefinition.getTypeBySchema(parseData);
+                  if (config && typeof parseData === 'object') {
+                    typeEditorUtils.fixFlowIndex(parseData);
+                    parseData.extra!.index = originIndex;
+                    setCellValue(rowData, parseData as any, onPaste);
+
+                    onChange();
+                  } else {
+                    Toast.warning('Please paste the correct type schema!');
+                  }
+                });
+              }}
+            />
+          </BaseIcon>
+        </Tooltip>
+      </CenterContainer>
+    );
+  },
+
+  shortcuts: {
+    onEnter: ({ typeEditor }) => {
+      typeEditor.moveActivePosToNextLine();
+    },
+    onTab: ({ typeEditor }) => {
+      typeEditor.moveActivePosToNextItem();
+    },
+
+    onDown: (ctx) => {
+      ctx.typeEditor.moveActivePosToNextLine();
+    },
+    onLeft: (ctx) => {
+      ctx.typeEditor.moveActivePosToLastColumn();
+    },
+    onUp: (ctx) => {
+      ctx.typeEditor.moveActivePosToLastLine();
+    },
+    onRight: (ctx) => {
+      ctx.typeEditor.moveActivePosToNextColumn();
+    },
+  },
+};

+ 122 - 0
packages/materials/type-editor/src/components/type-editor/columns/private.tsx

@@ -0,0 +1,122 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+/* eslint-disable react/prop-types */
+import React from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Checkbox, Toast, Tooltip } from '@douyinfe/semi-ui';
+
+import { typeEditorUtils } from '../utils';
+import { useDisabled } from '../hooks/disabled';
+
+// import s from './index.module.less';
+import { TypeEditorColumnConfig, TypeEditorColumnType, TypeEditorRowData } from '../../../types';
+import { CenterContainer } from './style';
+
+const setCellValue = (rowData: TypeEditorRowData<IJsonSchema>) => {
+  if (!rowData.extra) {
+    rowData.extra = {};
+  }
+  rowData.extra.private = !rowData.extra.private;
+};
+
+const ViewRender: TypeEditorColumnConfig<IJsonSchema>['viewRender'] = ({
+  rowData,
+  onChange,
+  onEditMode,
+}) => {
+  const disabled = useDisabled(TypeEditorColumnType.Required, rowData);
+
+  const child = (
+    <Checkbox
+      disabled={!!disabled}
+      checked={!!rowData.extra?.private}
+      onChange={() => {
+        setCellValue(rowData);
+        onChange();
+        onEditMode();
+      }}
+    />
+  );
+
+  return (
+    <CenterContainer onClick={!disabled ? () => onEditMode() : undefined}>
+      {disabled ? <Tooltip content={disabled}>{child}</Tooltip> : child}
+    </CenterContainer>
+  );
+};
+
+export const privateColumnConfig: TypeEditorColumnConfig<IJsonSchema> = {
+  type: TypeEditorColumnType.Private,
+  label: 'Private',
+  width: 11,
+  viewRender: ViewRender,
+  info: () => 'Private under the current project.',
+  shortcuts: {
+    onEnter: ({ onChange, rowData, typeEditor }) => {
+      setCellValue(rowData);
+      onChange();
+      typeEditor.moveActivePosToNextLine();
+    },
+    onTab: ({ typeEditor }) => {
+      typeEditor.moveActivePosToNextItem();
+    },
+
+    onCopy: (ctx) => {
+      const {
+        rowData,
+        typeEditor: { clipboard },
+      } = ctx;
+
+      clipboard.writeData(
+        JSON.stringify({
+          private: !!rowData.extra?.private,
+        })
+      );
+
+      Toast.success('Copy required success!');
+    },
+
+    onPaste: (ctx) => {
+      const {
+        typeEditor: { clipboard, onChange },
+        rowData,
+      } = ctx;
+      clipboard.readData().then((data) => {
+        const parseData = typeEditorUtils.jsonParse(data);
+        const disabled = (rowData.disableEditColumn || []).find(
+          (r) => r.column === TypeEditorColumnType.Private
+        );
+        if (disabled) {
+          Toast.warning('The current cell does not support editing!');
+          return;
+        }
+        if (parseData && typeof parseData === 'object' && parseData.private !== undefined) {
+          if (!rowData.extra) {
+            rowData.extra = {};
+          }
+          rowData.extra.private = parseData.private;
+          onChange();
+        } else {
+          Toast.warning('Please paste the correct info!');
+        }
+      });
+    },
+
+    onDown: (ctx) => {
+      ctx.typeEditor.moveActivePosToNextLine();
+    },
+    onLeft: (ctx) => {
+      ctx.typeEditor.moveActivePosToLastColumn();
+    },
+    onUp: (ctx) => {
+      ctx.typeEditor.moveActivePosToLastLine();
+    },
+    onRight: (ctx) => {
+      ctx.typeEditor.moveActivePosToNextColumn();
+    },
+  },
+};

+ 144 - 0
packages/materials/type-editor/src/components/type-editor/columns/required.tsx

@@ -0,0 +1,144 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Checkbox, Toast, Tooltip } from '@douyinfe/semi-ui';
+
+import { typeEditorUtils } from '../utils';
+import { useDisabled } from '../hooks/disabled';
+import { TypeEditorRowData, TypeEditorColumnConfig, TypeEditorColumnType } from '../../../types';
+import { CenterContainer } from './style';
+
+const setCellValue = (rowData: TypeEditorRowData<IJsonSchema>) => {
+  const { parent } = rowData;
+
+  if (!parent) {
+    return;
+  }
+
+  if (!parent.required) {
+    parent.required = [];
+  }
+
+  const idx = parent.required.findIndex((key) => key === rowData.key);
+  if (idx !== -1) {
+    parent.required.splice(idx, 1);
+  } else {
+    parent.required.push(rowData.key);
+  }
+};
+
+const ViewRender: TypeEditorColumnConfig<IJsonSchema>['viewRender'] = ({
+  rowData,
+  onChange,
+  onEditMode,
+}) => {
+  const disabled = useDisabled(TypeEditorColumnType.Required, rowData);
+
+  const child = (
+    <Checkbox
+      disabled={!!disabled}
+      checked={!!rowData.isRequired}
+      onChange={() => {
+        setCellValue(rowData);
+        onChange();
+
+        onEditMode();
+      }}
+    />
+  );
+
+  return (
+    <CenterContainer onClick={!disabled ? () => onEditMode() : undefined}>
+      {disabled ? <Tooltip content={disabled}>{child}</Tooltip> : child}
+    </CenterContainer>
+  );
+};
+
+export const requiredColumnConfig: TypeEditorColumnConfig<IJsonSchema> = {
+  type: TypeEditorColumnType.Required,
+  label: 'Required',
+  width: 11,
+  viewRender: ViewRender,
+  shortcuts: {
+    onEnter: ({ onChange, rowData, typeEditor }) => {
+      setCellValue(rowData);
+      onChange();
+      typeEditor.moveActivePosToNextLine();
+    },
+    onTab: ({ typeEditor }) => {
+      typeEditor.moveActivePosToNextItem();
+    },
+
+    onCopy: (ctx) => {
+      const {
+        rowData,
+        typeEditor: { clipboard },
+      } = ctx;
+
+      clipboard.writeData(
+        JSON.stringify({
+          required: rowData.isRequired,
+        })
+      );
+
+      Toast.success('Copy required success!');
+    },
+
+    onPaste: (ctx) => {
+      const {
+        typeEditor: { clipboard, onChange },
+        rowData,
+      } = ctx;
+      clipboard.readData().then((data) => {
+        const parseData = typeEditorUtils.jsonParse(data);
+        const disabled = (rowData.disableEditColumn || []).find(
+          (r) => r.column === TypeEditorColumnType.Required
+        );
+        if (disabled) {
+          Toast.warning('The current cell does not support editing!');
+          return;
+        }
+        if (parseData && typeof parseData === 'object' && parseData.required !== undefined) {
+          const { parent } = rowData;
+          if (!parent) {
+            return;
+          }
+          if (!parent.required) {
+            parent.required = [];
+          }
+          const idx = parent.required.findIndex((key) => key === rowData.key);
+          const originRequired = idx !== -1;
+          // 值不一致才更新
+          if (originRequired !== parseData.required) {
+            if (idx !== -1) {
+              parent.required.splice(idx, 1);
+            } else {
+              parent.required.push(rowData.key);
+            }
+            onChange();
+          }
+        } else {
+          Toast.warning('Please paste the correct info!');
+        }
+      });
+    },
+
+    onDown: (ctx) => {
+      ctx.typeEditor.moveActivePosToNextLine();
+    },
+    onLeft: (ctx) => {
+      ctx.typeEditor.moveActivePosToLastColumn();
+    },
+    onUp: (ctx) => {
+      ctx.typeEditor.moveActivePosToLastLine();
+    },
+    onRight: (ctx) => {
+      ctx.typeEditor.moveActivePosToNextColumn();
+    },
+  },
+};

+ 177 - 0
packages/materials/type-editor/src/components/type-editor/columns/style.ts

@@ -0,0 +1,177 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import styled, { createGlobalStyle } from 'styled-components';
+import { Input, Typography } from '@douyinfe/semi-ui';
+
+export const KeyViewContainer = styled.div<{
+  disabled?: boolean;
+}>`
+  width: 100%;
+  display: flex;
+  align-items: center;
+  height: 20px;
+  ${(props) => (props.disabled ? 'cursor: not-allowed !important;' : '')}
+  svg {
+    width: 12px;
+    height: 12px;
+    flex-shrink: 0;
+  }
+`;
+
+export const KeyViewContent = styled.div`
+  font-size: 12px;
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  width: 0;
+  flex: 1;
+`;
+
+export const KeyEditorContainer = styled.div`
+  height: 100%;
+  width: 100%;
+  display: flex;
+
+  svg {
+    flex-shrink: 0;
+  }
+
+  .semi-input-wrapper-focus {
+    border-color: transparent !important;
+  }
+
+  .semi-input-wrapper:hover {
+    background-color: var(--semi-color-fill-0);
+  }
+
+  .semi-cascader:hover {
+    background-color: var(--semi-color-fill-0);
+  }
+
+  .semi-cascader:focus {
+    border-color: transparent !important;
+  }
+
+  .semi-cascader-focus {
+    border-color: transparent !important;
+  }
+`;
+
+export const KeyEditorInput = styled(Input)`
+  flex: 1;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  input {
+    font-size: 12px !important;
+  }
+`;
+
+export const KeyViewText = styled(Typography.Text)<{
+  disabled?: boolean;
+}>`
+  flex: 1;
+  cursor: text;
+  font-size: 12px;
+  height: 100%;
+  ${(props) =>
+    props.disabled
+      ? `
+      cursor: not-allowed !important;
+    `
+      : ''}
+`;
+
+export const BaseIcon = styled.div<{
+  draggable?: boolean;
+  triangle?: boolean;
+  disabled?: boolean;
+  primary?: boolean;
+  isRotate?: boolean;
+}>`
+  color: var(--semi-color-text-2);
+  cursor: pointer;
+
+  ${(props) =>
+    props.draggable
+      ? `
+    cursor: grab;
+    margin-right: 4px;
+  `
+      : ''}
+
+  ${(props) =>
+    props.triangle
+      ? `
+    transform: rotate(270deg);
+
+  `
+      : ''}
+
+  ${(props) =>
+    props.isRotate
+      ? `
+    transform: rotate(360deg);
+
+  `
+      : ''}
+
+  ${(props) =>
+    props.disabled
+      ? `
+      opacity: 0.5;
+      cursor: not-allowed;
+    `
+      : ''}
+
+  ${(props) =>
+    props.primary
+      ? `
+        color: var(--semi-color-primary) !important;
+      `
+      : ''}
+
+  &:hover {
+    color: var(--semi-color-primary);
+  }
+`;
+
+export const CenterContainer = styled.div`
+  width: 100%;
+  height: 20px;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  justify-content: center;
+`;
+
+export const TypeDisableViewContainer = styled(KeyViewContainer)`
+  color: var(--semi-color-disabled-text) !important;
+  cursor: not-allowed;
+  font-size: 12px;
+
+  :global {
+    .semi-typography {
+      color: var(--semi-color-disabled-text) !important;
+    }
+  }
+`;
+
+export const TypeTextContainer = styled.div`
+  width: 100%;
+  height: 100%;
+  display: flex;
+  box-sizing: border-box;
+  align-items: center;
+  padding: 1px 5px;
+`;
+
+export const GlobalSelectStyle = createGlobalStyle`
+  .semi-select {
+      border: none !important
+    }
+
+`;

+ 175 - 0
packages/materials/type-editor/src/components/type-editor/columns/type.tsx

@@ -0,0 +1,175 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useEffect, useMemo, useState } from 'react';
+
+import { pick } from '@flowgram.ai/utils';
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Toast, Tooltip } from '@douyinfe/semi-ui';
+import { IconHelpCircle } from '@douyinfe/semi-icons';
+
+import { typeEditorUtils } from '../utils';
+import { useDisabled } from '../hooks/disabled';
+import { TypeSelector } from '../../type-selector';
+import { TypeEditorColumnConfig, TypeEditorColumnType, TypeEditorRowData } from '../../../types';
+import { useTypeDefinitionManager } from '../../../contexts';
+import { BaseIcon, KeyEditorContainer, KeyViewContainer, TypeDisableViewContainer } from './style';
+
+const ViewRender: TypeEditorColumnConfig<IJsonSchema>['viewRender'] = ({ rowData, onEditMode }) => {
+  const typeService = useTypeDefinitionManager();
+
+  const disabled = useDisabled(TypeEditorColumnType.Type, rowData);
+  const [refresh, setRefresh] = useState(0);
+
+  useEffect(() => {
+    typeService.onTypeRegistryChange(() => {
+      setRefresh((v) => v + 1);
+    });
+  }, [typeService]);
+
+  const typeConfig = useMemo(() => typeService.getTypeBySchema(rowData.self), [refresh, rowData]);
+
+  const unknownNode = (
+    <BaseIcon>
+      <IconHelpCircle style={{ marginRight: 4 }} />
+      Unknown
+    </BaseIcon>
+  );
+
+  return disabled ? (
+    <>
+      <TypeDisableViewContainer>
+        <Tooltip content={disabled}>
+          {typeConfig?.getDisplayLabel?.(rowData) || unknownNode}
+        </Tooltip>
+      </TypeDisableViewContainer>
+    </>
+  ) : (
+    <KeyViewContainer
+      onClick={() => {
+        onEditMode();
+      }}
+    >
+      {typeConfig?.getDisplayLabel?.(rowData) || unknownNode}
+    </KeyViewContainer>
+  );
+};
+
+const setCellValue = (rowData: TypeEditorRowData<IJsonSchema>, newData: IJsonSchema): void => {
+  const othersProps = pick(rowData.self, ['required', 'description', 'flow']);
+
+  if (rowData.parent?.properties) {
+    rowData.parent.properties[rowData.key] = {
+      ...newData,
+      ...othersProps,
+    };
+  }
+};
+
+const EditRender: TypeEditorColumnConfig<IJsonSchema>['editRender'] = ({
+  rowData,
+  onChange,
+  typeEditor,
+  onViewMode,
+}) => {
+  const [value, setValue] = useState(rowData.self);
+  const typeService = useTypeDefinitionManager();
+
+  return (
+    <KeyEditorContainer>
+      <TypeSelector
+        value={value}
+        typeRegistryCreators={typeEditor.typeRegistryCreators}
+        disableTypes={rowData.extraConfig?.customDisabledTypes}
+        onDropdownVisibleChange={(vis) => {
+          if (!vis) {
+            onViewMode();
+          }
+        }}
+        onChange={(newData, ctx) => {
+          if (!newData) {
+            return;
+          }
+
+          setCellValue(rowData, newData);
+
+          setValue(newData);
+          onChange();
+
+          let unClose = false;
+
+          typeEditorUtils.traverseIJsonSchema(newData, (type) => {
+            const def = typeService.getTypeBySchema(type);
+            if (def?.typeCascaderConfig?.unClosePanelAfterSelect) {
+              unClose = true;
+            }
+          });
+
+          if (ctx.source === 'type-selector' && !unClose) {
+            onViewMode();
+          }
+        }}
+        defaultOpen
+        onBlur={() => {
+          onViewMode();
+        }}
+      />
+    </KeyEditorContainer>
+  );
+};
+export const typeColumnConfig: TypeEditorColumnConfig<IJsonSchema> = {
+  type: TypeEditorColumnType.Type,
+  label: 'Type',
+  width: 20,
+  viewRender: ViewRender,
+  shortcuts: {
+    onTab: ({ typeEditor }) => {
+      typeEditor.moveActivePosToNextItem();
+    },
+    onCopy: (ctx) => {
+      const {
+        typeEditor: { typeDefinition, clipboard },
+        rowData,
+      } = ctx;
+      const config = typeDefinition.getTypeBySchema(rowData.self);
+      if (config) {
+        const optionValue = config.getStringValueByTypeSchema?.(rowData.self);
+        const type =
+          optionValue && config.getTypeSchemaByStringValue
+            ? config.getTypeSchemaByStringValue(optionValue)
+            : config.getDefaultSchema();
+        type.properties = rowData.properties;
+        type.required = rowData.required;
+        clipboard.writeData(JSON.stringify(type));
+        Toast.success('Copy type success!');
+      } else {
+        Toast.error('Copy type failed: this type undefined');
+      }
+    },
+
+    onPaste: (ctx) => {
+      const {
+        typeEditor: { typeDefinition, clipboard, onChange },
+        rowData,
+      } = ctx;
+      clipboard.readData().then((data) => {
+        const parseData = typeEditorUtils.jsonParse(data) as IJsonSchema;
+        const originIndex = rowData.extra?.index || 0;
+        const config = parseData && typeDefinition.getTypeBySchema(parseData);
+        if (config && typeof parseData === 'object') {
+          typeEditorUtils.fixFlowIndex(parseData);
+          if (parseData.extra) {
+            parseData.extra.index = originIndex;
+          }
+          setCellValue(rowData, parseData);
+          onChange();
+        } else {
+          Toast.warning('Please paste the correct type schema!');
+        }
+      });
+    },
+  },
+  editRender: EditRender,
+};

+ 137 - 0
packages/materials/type-editor/src/components/type-editor/columns/value.tsx

@@ -0,0 +1,137 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import React from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Tooltip, Typography } from '@douyinfe/semi-ui';
+// import { TypeInput, TypeText } from '@api-builder/base-type-definition';
+
+import { useDisabled } from '../hooks/disabled';
+import { TypeEditorColumnConfig, TypeEditorColumnType } from '../../../types';
+import { useTypeDefinitionManager } from '../../../contexts';
+import {
+  GlobalSelectStyle,
+  KeyEditorContainer,
+  KeyViewContainer,
+  TypeTextContainer,
+} from './style';
+
+export const TypeText: FC<{
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  value?: any;
+  type: IJsonSchema;
+}> = ({ value, type }) => {
+  const typeDefinition = useTypeDefinitionManager();
+
+  const content = useMemo(() => {
+    const config = typeDefinition.getTypeBySchema(type);
+
+    return config?.getValueText ? config.getValueText(value) : value || '';
+  }, [value, type]);
+
+  return (
+    <TypeTextContainer>
+      <Typography.Text ellipsis={{ showTooltip: true }}>{content}</Typography.Text>
+    </TypeTextContainer>
+  );
+};
+
+const ViewRender: TypeEditorColumnConfig<IJsonSchema>['viewRender'] = ({ rowData, onEditMode }) => {
+  const disabled = useDisabled(TypeEditorColumnType.Value, rowData);
+
+  if (disabled) {
+    return (
+      <KeyViewContainer disabled>
+        <Tooltip content={disabled}>
+          <div style={{ width: '100%', height: '100%' }}>
+            <TypeText value={rowData.self?.extra?.value} type={rowData.self} />
+          </div>
+        </Tooltip>
+      </KeyViewContainer>
+    );
+  }
+
+  return (
+    <KeyViewContainer onClick={() => onEditMode()}>
+      <TypeText value={rowData.self?.extra?.value} type={rowData.self} />
+    </KeyViewContainer>
+  );
+};
+
+const EditRender: TypeEditorColumnConfig<IJsonSchema>['editRender'] = ({
+  rowData,
+  onChange,
+  typeEditor,
+  onViewMode,
+}) => {
+  const valueRef = useRef(rowData.self.extra?.value);
+  const [value, setValue] = useState(rowData.self.extra?.value);
+  const typeDefinition = useTypeDefinitionManager();
+  const config = useMemo(() => typeDefinition.getTypeBySchema(rowData.self), [rowData.self]);
+  useEffect(() => {
+    typeEditor.editValue = value;
+  }, [typeEditor, value]);
+
+  useEffect(() => {
+    setValue(rowData.self.extra?.value);
+    valueRef.current = rowData.self.extra?.value;
+  }, [rowData.self.extra?.value]);
+
+  const handleSubmit = useCallback(() => {
+    if (!rowData.self.extra) {
+      rowData.self.extra = {};
+    }
+
+    rowData.self.extra.value = valueRef.current;
+    onChange();
+
+    onViewMode();
+  }, [onChange, rowData]);
+
+  return (
+    <KeyEditorContainer>
+      <GlobalSelectStyle />
+      {config &&
+        config?.getInputNode?.({
+          value: value,
+          onChange: (v: unknown) => {
+            valueRef.current = v;
+            setValue(v);
+          },
+          type: rowData.self,
+          onSubmit: () => {
+            handleSubmit();
+            onViewMode();
+          },
+        })}
+    </KeyEditorContainer>
+  );
+};
+
+export const valueColumnConfig: TypeEditorColumnConfig<IJsonSchema> = {
+  type: TypeEditorColumnType.Value,
+  width: 15,
+  label: 'Value',
+  viewRender: ViewRender,
+  shortcuts: {
+    onEnter: ({ rowData, onChange, typeEditor, value, typeDefinitionService }) => {
+      const config = typeDefinitionService.getTypeBySchema(rowData.self);
+      if (config?.typeInputConfig?.canEnter) {
+        rowData.self.description = value;
+
+        onChange();
+        typeEditor.moveActivePosToNextLine();
+      }
+    },
+    onTab: ({ rowData, value, onChange, typeEditor }) => {
+      rowData.self.description = value;
+      onChange();
+      typeEditor.moveActivePosToNextItem();
+    },
+  },
+  editRender: EditRender,
+};

+ 44 - 0
packages/materials/type-editor/src/components/type-editor/common.ts

@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+/**
+ * empty key 前缀
+ */
+export const SUFFIX = '___empty___';
+
+/**
+ * 表格 header 高度
+ */
+export const HEADER_HIGHT = 36;
+/**
+ * 表格 cell 高度
+ * 36 高度 + 1px border
+ */
+export const CELL_HIGHT = 36 + 1;
+
+/**
+ * cell padding 宽度
+ */
+export const CELL_PADDING = 8;
+
+/**
+ * 单个缩进的宽度
+ */
+export const INDENT_WIDTH = 16;
+
+/**
+ * 操作栏高度
+ */
+export const TAB_BRA_HIGHT = 0;
+
+/**
+ * test id 前缀
+ */
+export const COMPONENT_ID_PREFIX = 'type-editor-component-id';
+
+/**
+ * root 字段 id
+ */
+export const ROOT_FIELD_ID = 'root';

+ 71 - 0
packages/materials/type-editor/src/components/type-editor/drop-tip.tsx

@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { useEffect, useMemo, useState } from 'react';
+import React from 'react';
+
+import styled from 'styled-components';
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { TypeEditorRowData } from '../../types';
+import { TypeEditorService } from '../../services';
+import { useService, useTypeDefinitionManager } from '../../contexts';
+import { CELL_HIGHT, CELL_PADDING, HEADER_HIGHT, INDENT_WIDTH, TAB_BRA_HIGHT } from './common';
+
+const StyledDragTip = styled.div`
+  position: absolute;
+  height: 1px;
+  background-color: var(--semi-color-primary);
+`;
+
+export const DropTip = <TypeSchema extends Partial<IJsonSchema>>({
+  dataSource,
+}: {
+  dataSource: Record<string, TypeEditorRowData<TypeSchema>>;
+}) => {
+  const typeEditor = useService<TypeEditorService<TypeSchema>>(TypeEditorService);
+
+  const [dropInfo, setDropInfo] = useState(typeEditor.dropInfo);
+
+  const typeService = useTypeDefinitionManager();
+
+  useEffect(() => {
+    const dispose = typeEditor.onDropInfoChange.event(setDropInfo);
+    return () => {
+      dispose.dispose();
+    };
+  }, []);
+
+  const rowData = useMemo(() => dataSource[dropInfo.rowDataId], [dropInfo.rowDataId]);
+
+  const rowDataCanHasChildren = useMemo(
+    () =>
+      rowData && rowData.type && typeService.getTypeByName(rowData.type)?.canAddField?.(rowData),
+    [rowData]
+  );
+
+  return (
+    <>
+      {(rowData || dropInfo.rowDataId === 'header') && (
+        <StyledDragTip
+          style={{
+            top: HEADER_HIGHT + TAB_BRA_HIGHT + (dropInfo.index + 1) * CELL_HIGHT - 1,
+            right: 0,
+            width: `calc(100% - ${
+              /**
+               * 表格 padding
+               * 初始 drag icon 占一个缩进
+               * rowData 本身 level 个缩进,如果是含有子节点,因为默认添加到子元素内,还需要额外加一个缩进
+               */
+              CELL_PADDING +
+              INDENT_WIDTH +
+              (dropInfo.indent + (rowDataCanHasChildren ? 1 : 0) + 1) * INDENT_WIDTH
+            }px)`,
+          }}
+        />
+      )}
+    </>
+  );
+};

+ 68 - 0
packages/materials/type-editor/src/components/type-editor/error.tsx

@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { type FC } from 'react';
+import React from 'react';
+
+import styled from 'styled-components';
+
+const TopLine = styled.div<{
+  level?: 'error' | 'warning';
+}>`
+  position: absolute;
+  background-color: ${(props) =>
+    props.level === 'warning' ? 'var(--semi-color-warning)' : 'var(--semi-color-danger)'};
+  width: 100%;
+  height: 1px;
+  top: 0;
+  left: 0;
+`;
+
+const BottomLine = styled.div<{
+  level?: 'error' | 'warning';
+}>`
+  position: absolute;
+  background-color: ${(props) =>
+    props.level === 'warning' ? 'var(--semi-color-warning)' : 'var(--semi-color-danger)'};
+  width: 100%;
+  height: 1px;
+  bottom: -1px;
+  left: 0;
+`;
+
+const RightLine = styled.div<{
+  level?: 'error' | 'warning';
+}>`
+  position: absolute;
+  background-color: ${(props) =>
+    props.level === 'warning' ? 'var(--semi-color-warning)' : 'var(--semi-color-danger)'};
+  height: calc(100% + 1px);
+  width: 1px;
+  bottom: -1px;
+  right: 0px;
+`;
+
+const LeftLine = styled.div<{
+  level?: 'error' | 'warning';
+}>`
+  position: absolute;
+  background-color: ${(props) =>
+    props.level === 'warning' ? 'var(--semi-color-warning)' : 'var(--semi-color-danger)'};
+  height: calc(100% + 1px);
+  width: 1px;
+  bottom: -1px;
+  left: 0;
+`;
+
+export const ErrorCellBorder: FC<{
+  level?: 'error' | 'warning';
+}> = ({ level }) => (
+  <>
+    <TopLine level={level} />
+    <BottomLine level={level} />
+    <LeftLine level={level} />
+    <RightLine level={level} />
+  </>
+);

+ 44 - 0
packages/materials/type-editor/src/components/type-editor/formatter/index.ts

@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { SUFFIX } from '../common';
+export type Formatter = (typeSchema: Partial<IJsonSchema>) => void;
+
+export const extraFormatter: Formatter = (type) => {
+  if (type.extra !== undefined) {
+    type.extra = type.extra;
+
+    delete type.extra;
+  }
+};
+
+export const extraDeFormatter: Formatter = (type) => {
+  if (type.extra !== undefined) {
+    type.extra = type.extra;
+    delete type.extra;
+  }
+};
+
+export const emptyKeyFormatter: Formatter = (type) => {
+  if (type.properties) {
+    Object.keys(type.properties).forEach((k) => {
+      if (k.startsWith(SUFFIX)) {
+        delete type.properties![k];
+      }
+    });
+  }
+};
+
+export const disableFixIndexFormatter: Formatter = (type) => {
+  if (type.extra) {
+    delete type.extra.index;
+
+    if (Object.keys(type.extra).length === 0) {
+      delete type.extra;
+    }
+  }
+};

+ 69 - 0
packages/materials/type-editor/src/components/type-editor/header.tsx

@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Tooltip } from '@douyinfe/semi-ui';
+import { IconInfoCircle } from '@douyinfe/semi-icons';
+
+import { TypeEditorColumnType, TypeEditorRowData } from '../../types/type-editor';
+import { TypeEditorService } from '../../services';
+import { useService } from '../../contexts';
+import { EditorTableTitle, EditorTableHeader, BaseIcon } from './style';
+import { useHeaderDrop } from './hooks';
+
+export const Header = <TypeSchema extends Partial<IJsonSchema>>({
+  displayColumn,
+  value,
+  readonly,
+  dataSourceMap,
+  onChange,
+}: {
+  displayColumn: TypeEditorColumnType[];
+  dataSourceMap: Record<string, TypeEditorRowData<TypeSchema>>;
+  onChange: () => void;
+
+  readonly?: boolean;
+
+  value: TypeSchema;
+}) => {
+  const typeEditorService = useService<TypeEditorService<TypeSchema>>(TypeEditorService);
+
+  const { drop } = useHeaderDrop(value, dataSourceMap, onChange);
+
+  return (
+    <thead className="semi-table-thead">
+      <tr ref={drop} className="semi-table-row">
+        {displayColumn.map((v) => {
+          const config = typeEditorService.getConfigByType(v);
+
+          return (
+            <EditorTableHeader
+              key={v}
+              style={{
+                width: config?.width ? `${config.width}%` : undefined,
+                paddingLeft: readonly ? '24px !important' : undefined,
+              }}
+              className="semi-table-row-head"
+              scope="col"
+            >
+              <EditorTableTitle>
+                {config?.label}
+                {config?.info && (
+                  <Tooltip content={config.info()}>
+                    <BaseIcon>
+                      <IconInfoCircle style={{ marginLeft: 4 }} size="small" />
+                    </BaseIcon>
+                  </Tooltip>
+                )}
+              </EditorTableTitle>
+            </EditorTableHeader>
+          );
+        })}
+      </tr>
+    </thead>
+  );
+};

+ 28 - 0
packages/materials/type-editor/src/components/type-editor/hooks/active-pos.ts

@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { useEffect, useState } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { TypeEditorService } from '../../../services';
+import { useService } from '../../../contexts';
+
+export const useActivePos = (): TypeEditorService<IJsonSchema>['activePos'] => {
+  const typeEditorService = useService<TypeEditorService<IJsonSchema>>(TypeEditorService);
+
+  const [activePos, setActivePos] = useState<{ x: number; y: number }>(typeEditorService.activePos);
+
+  useEffect(() => {
+    const dispose = typeEditorService.onActivePosChange.event((v) => {
+      setActivePos(v);
+    });
+    return () => {
+      dispose.dispose();
+    };
+  }, []);
+
+  return activePos;
+};

+ 36 - 0
packages/materials/type-editor/src/components/type-editor/hooks/blink.ts

@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { useEffect, useMemo, useState } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { TypeEditorService } from '../../../services';
+import { useService } from '../../../contexts';
+
+export const useIsBlink = () => {
+  const typeEditor = useService<TypeEditorService<IJsonSchema>>(TypeEditorService);
+
+  const blinkData = useMemo(() => typeEditor.blink, [typeEditor.blink]);
+  const [blink, setBlink] = useState(blinkData.data);
+
+  useEffect(() => {
+    const dispose = blinkData.onDataChange(({ next }) => {
+      setBlink(next);
+
+      if (next) {
+        setTimeout(() => {
+          blinkData.update(false);
+        }, 200);
+      }
+    });
+
+    return () => {
+      dispose.dispose();
+    };
+  }, [blinkData]);
+
+  return blink;
+};

+ 25 - 0
packages/materials/type-editor/src/components/type-editor/hooks/disabled.ts

@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { TypeEditorColumnType, TypeEditorRowData } from '../../../types';
+
+export const useDisabled = <TypeSchema extends Partial<IJsonSchema>>(
+  type: TypeEditorColumnType,
+  rowData: TypeEditorRowData<TypeSchema>
+): string | undefined => {
+  const disabled = (rowData.disableEditColumn || []).find((r) => r.column === type);
+
+  if (disabled?.reason) {
+    return disabled?.reason;
+  }
+
+  if (typeof rowData.extra?.editable === 'string') {
+    return rowData.extra?.editable;
+  }
+
+  return rowData.extra?.editable === false ? `${type}  is not editable.` : undefined;
+};

+ 205 - 0
packages/materials/type-editor/src/components/type-editor/hooks/drag-drop.ts

@@ -0,0 +1,205 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+/* eslint-disable max-params */
+
+import { useDrag, useDrop } from 'react-dnd';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Toast } from '@douyinfe/semi-ui';
+
+import { typeEditorUtils } from '../utils';
+import { TypeEditorRowData } from '../../../types';
+import { TypeEditorService } from '../../../services';
+import { useService, useTypeDefinitionManager } from '../../../contexts';
+
+export const useRowDrag = (
+  id: string,
+  onChildrenVisibleChange: (rowDataId: string, newVal: boolean) => void
+) => {
+  const [{ isDragging }, drag, preview] = useDrag(
+    () => ({
+      type: 'node-editor-dnd',
+      item: () => {
+        onChildrenVisibleChange(id, true);
+        return {
+          id,
+        };
+      },
+      collect: (monitor) => ({
+        isDragging: monitor.isDragging(),
+      }),
+    }),
+    [id]
+  );
+
+  return {
+    drag,
+    isDragging,
+    preview,
+  };
+};
+
+interface DragItem {
+  id: string;
+}
+
+export const useCellDrop = <TypeSchema extends Partial<IJsonSchema>>(
+  rowData: TypeEditorRowData<TypeSchema>,
+  index: number,
+  dataSource: Record<string, TypeEditorRowData<TypeSchema>>,
+  onChange: () => void
+) => {
+  const typeEditor = useService<TypeEditorService<TypeSchema>>(TypeEditorService);
+
+  const typeService = useTypeDefinitionManager();
+  const [{ isOver }, drop] = useDrop(
+    () => ({
+      accept: 'node-editor-dnd',
+      drop: (item: DragItem) => {
+        if (item.id === rowData.id) {
+          typeEditor.clearDropInfo();
+          return;
+        }
+
+        const dragData = dataSource[item.id];
+
+        const definition = typeService.getTypeBySchema(rowData.self);
+
+        if (definition) {
+          const canHasChild = definition.canAddField?.(rowData.self);
+
+          let dropParent: IJsonSchema;
+          let dropIndex: number;
+          if (canHasChild) {
+            dropParent = definition.getPropertiesParent?.(rowData.self)! as IJsonSchema;
+            dropIndex = -1;
+          } else {
+            dropParent = rowData.parent! as IJsonSchema;
+            dropIndex = (rowData.extra?.index || 0) + 0.1;
+          }
+
+          if (dropParent?.properties?.[dragData.key] && dropParent !== dragData.parent) {
+            Toast.error('drop error: there is a duplicate key in the current object');
+            typeEditor.clearDropInfo();
+
+            return;
+          }
+
+          // 删除原来的引用
+          if (dragData.parent?.properties) {
+            delete dragData.parent.properties[dragData.key];
+          }
+
+          // 添加到新的节点
+          if (!dropParent.properties) {
+            dropParent.properties = {};
+          }
+          dropParent.properties[dragData.key] = dragData.self as IJsonSchema;
+          typeEditorUtils.fixFlowIndex(dragData);
+          dragData.extra!.index = dropIndex;
+
+          // 分别重新 sort dropParent 和 dragParent
+          typeEditorUtils.sortProperties(dropParent);
+          typeEditorUtils.sortProperties(dragData.parent!);
+          onChange();
+        }
+
+        typeEditor.clearDropInfo();
+      },
+      hover() {
+        typeEditor.setDropInfo({
+          indent: rowData.level,
+          index,
+          rowDataId: rowData.id,
+        });
+      },
+      collect: (monitor) => ({
+        isOver: monitor.isOver(),
+      }),
+    }),
+    [rowData]
+  );
+
+  return {
+    drop,
+    isOver,
+  };
+};
+
+export const useHeaderDrop = <TypeSchema extends Partial<IJsonSchema>>(
+  root: TypeSchema,
+  dataSource: Record<string, TypeEditorRowData<TypeSchema>>,
+  onChange: () => void
+) => {
+  const typeEditor = useService<TypeEditorService<TypeSchema>>(TypeEditorService);
+
+  const typeService = useTypeDefinitionManager();
+
+  const [{ isOver }, drop] = useDrop(
+    () => ({
+      accept: 'node-editor-dnd',
+      drop: (item: DragItem) => {
+        const dragData = dataSource[item.id];
+
+        const definition = typeService.getTypeBySchema(root);
+
+        if (definition) {
+          const canHasChild = definition.canAddField(root);
+
+          let dropParent: IJsonSchema;
+          let dropIndex: number;
+          if (canHasChild) {
+            dropParent = definition.getPropertiesParent(root)! as IJsonSchema;
+            dropIndex = -1;
+
+            if (dropParent?.properties?.[dragData.key] && dropParent !== dragData.parent) {
+              Toast.error('drop error: there is a duplicate key in the current object');
+              typeEditor.clearDropInfo();
+
+              return;
+            }
+
+            // 删除原来的引用
+            if (dragData.parent?.properties) {
+              delete dragData.parent.properties[dragData.key];
+            }
+
+            // 添加到新的节点
+            if (!dropParent.properties) {
+              dropParent.properties = {};
+            }
+            dropParent.properties[dragData.key] = dragData.self as IJsonSchema;
+            typeEditorUtils.fixFlowIndex(dragData);
+            dragData.extra!.index = dropIndex;
+
+            // 分别重新 sort dropParent 和 dragParent
+            typeEditorUtils.sortProperties(dropParent);
+            typeEditorUtils.sortProperties(dragData.parent!);
+            onChange();
+          }
+
+          typeEditor.clearDropInfo();
+        }
+      },
+      hover() {
+        typeEditor.setDropInfo({
+          indent: 0,
+          index: -1,
+          rowDataId: 'header',
+        });
+      },
+      collect: (monitor) => ({
+        isOver: monitor.isOver(),
+      }),
+    }),
+    [root]
+  );
+
+  return {
+    drop,
+    isOver,
+  };
+};

+ 86 - 0
packages/materials/type-editor/src/components/type-editor/hooks/editor-listener.tsx

@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useEffect, useRef, useState } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { TypeEditorProp } from '../type';
+import { columnConfigs } from '../columns';
+import { TypeEditorColumnConfig } from '../../../types';
+import { TypeEditorService } from '../../../services';
+import { useService } from '../../../contexts';
+import { useTypeEditorHotKey } from './hot-key';
+
+export const TypeEditorListener = <TypeSchema extends Partial<IJsonSchema>>({
+  configs = [],
+  children,
+}: React.PropsWithChildren & {
+  configs: TypeEditorProp<'type-definition', TypeSchema>['viewConfigs'];
+}) => {
+  const dom = useRef();
+
+  const lastWidth = useRef(0);
+
+  const typeEditorService = useService<TypeEditorService<TypeSchema>>(TypeEditorService);
+
+  const [init, setInit] = useState(false);
+
+  useEffect(() => {
+    if (dom.current) {
+      const el = dom.current;
+
+      const resize = new ResizeObserver((entries) => {
+        if (lastWidth.current === 0) {
+          lastWidth.current = entries[0].contentRect.width;
+        }
+        if (entries[0].contentRect.width !== lastWidth.current) {
+          typeEditorService.clearActivePos();
+        }
+      });
+
+      resize.observe(el);
+      return () => {
+        resize.unobserve(el);
+      };
+    }
+  }, [dom.current, typeEditorService]);
+
+  useEffect(() => {
+    typeEditorService.registerConfigs(
+      columnConfigs as unknown as TypeEditorColumnConfig<TypeSchema>[]
+    );
+
+    configs.forEach(
+      (config) => config.config && typeEditorService.addConfigProps(config.type, config.config)
+    );
+
+    setInit(true);
+  }, [typeEditorService, configs]);
+
+  const composition = useRef(false);
+
+  const hotkeys = useTypeEditorHotKey();
+
+  return (
+    <div
+      style={{ width: '100%' }}
+      onCompositionStart={() => (composition.current = true)}
+      onCompositionEnd={() => (composition.current = false)}
+      onKeyDown={(e) => {
+        if (composition.current) return;
+        const hotKey = hotkeys.find((item) => item.matcher(e));
+        hotKey?.callback();
+        if (hotKey?.preventDefault) {
+          e.preventDefault();
+          e.stopPropagation();
+        }
+      }}
+      ref={dom as unknown as React.LegacyRef<HTMLDivElement>}
+    >
+      {init && children}
+    </div>
+  );
+};

+ 66 - 0
packages/materials/type-editor/src/components/type-editor/hooks/error-cell.ts

@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+/* eslint-disable max-params */
+import { useMemo } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { useMonitorData } from '../../../utils';
+import { TypeEditorPos, TypeEditorRowData, TypeEditorColumnConfig } from '../../../types';
+import { TypeEditorService } from '../../../services';
+import { useService } from '../../../contexts';
+import { useActivePos } from './active-pos';
+
+export const useEditCellErrorMsg = (pos: TypeEditorPos): string | undefined => {
+  const typeEditor = useService<TypeEditorService<IJsonSchema>>(TypeEditorService);
+
+  const { data: errorMsgs } = useMonitorData(typeEditor.errorMsgs);
+
+  const msg = useMemo(
+    () => errorMsgs?.find((v) => v.pos.x === pos.x && v.pos.y === pos.y)?.msg,
+    [pos, errorMsgs]
+  );
+
+  return msg;
+};
+
+export const useViewCellErrorMsg = <TypeSchema extends Partial<IJsonSchema>>(
+  rowData: TypeEditorRowData<TypeSchema>,
+  config: TypeEditorColumnConfig<TypeSchema>,
+  pos: TypeEditorPos
+) => {
+  const activePos = useActivePos();
+
+  const res = useMemo(
+    () =>
+      (activePos.x !== pos.x || activePos.y !== pos.y) && config.validateCell
+        ? config.validateCell(rowData, rowData.extraConfig)
+        : undefined,
+    [rowData, config, activePos, pos]
+  );
+
+  return res;
+};
+
+export const useHasErrorCell = <TypeSchema extends Partial<IJsonSchema>>(
+  rowData: TypeEditorRowData<TypeSchema>,
+
+  typeEditor: TypeEditorService<TypeSchema>
+): boolean => {
+  const res = useMemo(() => {
+    const configs = typeEditor.columnViewConfig.map((v) => typeEditor.getConfigByType(v.type));
+
+    return (
+      configs
+        .map((config) =>
+          config?.validateCell ? config.validateCell(rowData, rowData.extraConfig) : undefined
+        )
+        .filter(Boolean).length !== 0
+    );
+  }, [rowData, typeEditor]);
+
+  return res;
+};

+ 70 - 0
packages/materials/type-editor/src/components/type-editor/hooks/formatter-value.ts

@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { useMemo } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { ModeValueConfig, TypeEditorMode, TypeEditorProp, TypeEditorValue } from '../type';
+import { modeValueConfig } from '../mode';
+import { extraDeFormatter, extraFormatter, Formatter } from '../formatter';
+import { traverseIJsonSchema } from '../../../services/utils';
+
+export const useFormatter = <Mode extends TypeEditorMode, TypeSchema extends Partial<IJsonSchema>>({
+  extraConfig = {},
+  mode,
+}: Pick<TypeEditorProp<Mode, TypeSchema>, 'mode' | 'extraConfig'>) => {
+  const { useExtra } = extraConfig;
+  const modeConfig = useMemo(
+    () =>
+      modeValueConfig.find((v) => v.mode === mode)! as unknown as ModeValueConfig<Mode, TypeSchema>,
+    [mode]
+  );
+
+  const formatter = useMemo(
+    () => (value: TypeEditorValue<Mode, TypeSchema> | undefined) => {
+      const originSchema = value && modeConfig.convertValueToSchema(value as any);
+      let res = originSchema ? JSON.parse(JSON.stringify(originSchema)) : originSchema;
+
+      const formatters: Formatter[] = [];
+      if (useExtra) {
+        formatters.push(extraFormatter);
+      }
+
+      if (formatters.length !== 0 && res) {
+        traverseIJsonSchema(res, (type) => {
+          formatters.forEach((f) => f(type));
+        });
+      }
+      return res;
+    },
+    [modeConfig, useExtra]
+  );
+
+  const deFormatter = useMemo(
+    () => (originSchema: TypeSchema | undefined) => {
+      let schema = originSchema ? JSON.parse(JSON.stringify(originSchema)) : originSchema;
+
+      const formatters: Formatter[] = [];
+      if (useExtra) {
+        formatters.push(extraDeFormatter);
+      }
+
+      if (formatters.length !== 0 && schema) {
+        traverseIJsonSchema(schema, (type) => {
+          formatters.forEach((f) => f(type));
+        });
+      }
+
+      return schema && modeConfig.convertSchemaToValue(schema as TypeSchema);
+    },
+    [modeConfig, useExtra]
+  );
+
+  return {
+    formatter,
+    deFormatter,
+  };
+};

+ 95 - 0
packages/materials/type-editor/src/components/type-editor/hooks/hot-key.ts

@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useMemo } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { TypeEditorOperationService, TypeEditorService } from '../../../services';
+import { useService } from '../../../contexts';
+
+interface HotKeyConfig {
+  matcher: (event: React.KeyboardEvent) => boolean;
+  callback: () => void;
+  preventDefault?: boolean;
+}
+
+export const useTypeEditorHotKey = () => {
+  const typeEditor = useService<TypeEditorService<IJsonSchema>>(TypeEditorService);
+  const operator = useService<TypeEditorOperationService<IJsonSchema>>(TypeEditorOperationService);
+
+  const hotKeyConfig: HotKeyConfig[] = useMemo(() => {
+    const res: HotKeyConfig[] = [
+      {
+        matcher: (e) => e.key === 'Enter',
+        callback: () => {
+          typeEditor.triggerShortcutEvent('enter');
+        },
+        preventDefault: true,
+      },
+      {
+        matcher: (e) => e.key === 'ArrowUp',
+        callback: () => {
+          typeEditor.triggerShortcutEvent('up');
+        },
+        preventDefault: true,
+      },
+      {
+        matcher: (e) => e.key === 'Tab',
+        callback: () => {
+          typeEditor.triggerShortcutEvent('tab');
+        },
+        preventDefault: true,
+      },
+      {
+        matcher: (e) => e.key === 'ArrowLeft',
+        callback: () => {
+          typeEditor.triggerShortcutEvent('left');
+        },
+      },
+      {
+        matcher: (e) => e.key === 'ArrowRight',
+        callback: () => {
+          typeEditor.triggerShortcutEvent('right');
+        },
+      },
+      {
+        matcher: (e) => e.key === 'ArrowDown',
+        callback: () => {
+          typeEditor.triggerShortcutEvent('down');
+        },
+        preventDefault: true,
+      },
+      {
+        matcher: (e) => (e.metaKey || e.ctrlKey) && e.key === 'c',
+        callback: () => {
+          typeEditor.triggerShortcutEvent('copy');
+        },
+      },
+      {
+        matcher: (e) => (e.metaKey || e.ctrlKey) && e.key === 'v',
+        callback: () => {
+          typeEditor.triggerShortcutEvent('paste');
+        },
+      },
+      {
+        matcher: (e) => (e.metaKey || e.ctrlKey) && e.key === 'z',
+        callback: () => {
+          operator.undo();
+        },
+      },
+      {
+        matcher: (e) => (e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'z',
+        callback: () => {
+          operator.redo();
+        },
+      },
+    ];
+
+    return res;
+  }, [typeEditor]);
+
+  return hotKeyConfig;
+};

+ 12 - 0
packages/materials/type-editor/src/components/type-editor/hooks/index.ts

@@ -0,0 +1,12 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+export { useActivePos } from './active-pos';
+export { TypeEditorListener } from './editor-listener';
+export { useRowDrag, useCellDrop, useHeaderDrop } from './drag-drop';
+export { useKeyVisible } from './key-visible';
+export { useAddType, useRemoveType } from './type-edit';
+export { useEditCellErrorMsg, useViewCellErrorMsg } from './error-cell';
+export { useDisabled } from './disabled';

+ 66 - 0
packages/materials/type-editor/src/components/type-editor/hooks/key-visible.tsx

@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useCallback } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Tooltip } from '@douyinfe/semi-ui';
+import { IconEyeClosed, IconEyeOpened } from '@douyinfe/semi-icons';
+
+import { BaseIcon } from '../columns/style';
+import { TypeEditorRowData, TypeEditorSpecialConfig, TypeEditorColumnType } from '../../../types';
+import { useDisabled } from './disabled';
+
+export const useKeyVisible = <TypeSchema extends Partial<IJsonSchema>>(
+  rowData: TypeEditorRowData<TypeSchema>,
+  onChange: () => void,
+  extraConfig: TypeEditorSpecialConfig<TypeSchema>
+): React.JSX.Element => {
+  const disabled = useDisabled(TypeEditorColumnType.Key, rowData);
+
+  const handleVisibleChange = useCallback(
+    (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
+      const currentSchema = rowData.self;
+
+      if (!currentSchema.extra) {
+        currentSchema.extra = {};
+      }
+
+      currentSchema.extra.hidden = !currentSchema.extra.hidden;
+      onChange();
+
+      e.stopPropagation();
+      e.preventDefault();
+    },
+    [rowData.self, onChange]
+  );
+
+  const disableContent =
+    disabled || typeof extraConfig.editorVisible === 'string'
+      ? disabled || extraConfig.editorVisible
+      : undefined;
+
+  const visibleNode = (
+    <>
+      {disableContent ? (
+        <Tooltip content={disableContent}>
+          <BaseIcon disabled>
+            <IconEyeOpened size="small" />
+          </BaseIcon>
+        </Tooltip>
+      ) : rowData.extra?.hidden ? (
+        <BaseIcon>
+          <IconEyeClosed onClick={handleVisibleChange} size="small" />
+        </BaseIcon>
+      ) : (
+        <BaseIcon>
+          <IconEyeOpened onClick={handleVisibleChange} size="small" />
+        </BaseIcon>
+      )}
+    </>
+  );
+
+  return extraConfig.editorVisible ? visibleNode : <></>;
+};

+ 106 - 0
packages/materials/type-editor/src/components/type-editor/hooks/paste-data.ts

@@ -0,0 +1,106 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { useEffect, useMemo, useState } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { typeEditorUtils } from '../utils';
+import { ClipboardService } from '../../../services';
+import { useService, useTypeDefinitionManager } from '../../../contexts';
+
+export type PasteDataType<TypeSchema> =
+  | {
+      type: 'single';
+      value: TypeSchema;
+    }
+  | {
+      type: 'multiple';
+      value: TypeSchema[];
+    }
+  | {
+      type: 'invalid';
+    };
+
+export const usePasteData = <TypeSchema extends Partial<IJsonSchema>>() => {
+  const clipboard = useService<ClipboardService>(ClipboardService);
+
+  const [pasteData, setPasteData] = useState<PasteDataType<TypeSchema>>({
+    type: 'invalid',
+  });
+
+  const typeDefinition = useTypeDefinitionManager();
+
+  const pasteDataFormate = useMemo(
+    () =>
+      (data: string): PasteDataType<TypeSchema> => {
+        const parseData = typeEditorUtils.jsonParse(data);
+
+        if (!parseData || typeof parseData !== 'object') {
+          return {
+            type: 'invalid',
+          };
+        }
+
+        if (Array.isArray(parseData)) {
+          const isValid = parseData.every((item) => {
+            const config = typeDefinition.getTypeBySchema(item);
+            return !!config;
+          });
+
+          if (isValid) {
+            return {
+              type: 'multiple',
+              value: parseData,
+            };
+          } else {
+            return {
+              type: 'invalid',
+            };
+          }
+        } else {
+          const config = typeDefinition.getTypeBySchema(parseData);
+
+          if (config) {
+            return {
+              type: 'single',
+              value: parseData,
+            };
+          } else {
+            return {
+              type: 'invalid',
+            };
+          }
+        }
+      },
+    [typeDefinition]
+  );
+
+  useEffect(() => {
+    clipboard.readData().then(
+      (v) => {
+        if (v !== undefined) {
+          setPasteData(pasteDataFormate(v));
+        }
+      },
+      (error) => {
+        // console.log(error);
+      }
+    );
+
+    const dispose = clipboard.onClipboardChanged((newData) => {
+      setPasteData(pasteDataFormate(newData));
+    });
+
+    return () => {
+      dispose.dispose();
+    };
+  }, [pasteDataFormate]);
+
+  return {
+    pasteDataFormate,
+    pasteData,
+  };
+};

+ 180 - 0
packages/materials/type-editor/src/components/type-editor/hooks/type-edit.ts

@@ -0,0 +1,180 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useCallback } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Toast } from '@douyinfe/semi-ui';
+
+import { typeEditorUtils } from '../utils';
+import { TypeEditorRowData } from '../../../types';
+import { TypeEditorService, ClipboardService } from '../../../services';
+import { useService, useTypeDefinitionManager } from '../../../contexts';
+import { usePasteData } from './paste-data';
+
+export const useAddType = <TypeSchema extends Partial<IJsonSchema>>(
+  rowData: TypeEditorRowData<TypeSchema>,
+  onChange: () => void
+) => {
+  const typeEditor = useService<TypeEditorService<TypeSchema>>(TypeEditorService);
+
+  const typeService = useTypeDefinitionManager();
+
+  const config = typeService.getTypeBySchema(rowData.self);
+
+  const handleAddType = useCallback(
+    (e: React.MouseEvent) => {
+      if (!config) {
+        return;
+      }
+      const currentSchema = rowData.self;
+      if (currentSchema) {
+        let index = -1;
+
+        const parent = config.getPropertiesParent?.(currentSchema);
+
+        if (!parent) {
+          return false;
+        }
+
+        if (!parent.properties) {
+          parent.properties = {};
+        }
+
+        Object.values(parent.properties).forEach((val) => {
+          if (!val.extra) {
+            val.extra = {
+              index: 0,
+            };
+          }
+
+          index = Math.max(index, val.extra?.index || 0);
+        });
+
+        const [key, schema] = typeEditorUtils.genNewTypeSchema(index + 1);
+
+        parent.properties[key] = schema as IJsonSchema;
+
+        const dataSource = typeEditor.getDataSource();
+
+        let addIndex = rowData.index + 1;
+        for (let i = rowData.index + 1, len = dataSource.length; i < len; i++) {
+          if (dataSource[i].level > rowData.level) {
+            addIndex++;
+          } else {
+            break;
+          }
+        }
+
+        const newPos = {
+          x: 0,
+          y: addIndex,
+        };
+        onChange();
+        typeEditor.setActivePos(newPos);
+      }
+
+      e.stopPropagation();
+      e.preventDefault();
+    },
+    [rowData, config, onChange]
+  );
+
+  return handleAddType;
+};
+
+export const usePasteAddType = <TypeSchema extends Partial<IJsonSchema>>(
+  rowData: TypeEditorRowData<TypeSchema>,
+  onChange: () => void,
+  onPaste?: (type?: TypeSchema) => TypeSchema | undefined
+) => {
+  const typeService = useTypeDefinitionManager();
+
+  const clipboard = useService<ClipboardService>(ClipboardService);
+
+  const { pasteDataFormate } = usePasteData<TypeSchema>();
+
+  const handlePasteAddType = useCallback(
+    (e: React.MouseEvent) => {
+      clipboard.readData().then((data) => {
+        const parseData = pasteDataFormate(data);
+        if (parseData.type === 'invalid') {
+          Toast.warning('Please paste the correct type schema!');
+          e.stopPropagation();
+          e.preventDefault();
+          return;
+        }
+        const currentSchema = rowData.self;
+        let index = -1;
+        const pasteDataItems =
+          parseData.type === 'single' ? [parseData.value] : [...parseData.value];
+        const config = typeService.getTypeBySchema(currentSchema);
+        const parent = config?.getPropertiesParent?.(currentSchema);
+        if (!parent) {
+          return;
+        }
+        if (!parent.properties) {
+          parent.properties = {};
+        }
+        Object.values(parent.properties).forEach((val) => {
+          if (!val.extra) {
+            val.extra = {
+              index: 0,
+            };
+          }
+          index = Math.max(index, val.extra.index || 0);
+        });
+        pasteDataItems.forEach((item) => {
+          let itemKey =
+            (item as { extra?: { key?: string } })?.extra?.key || typeEditorUtils.genEmptyKey();
+          // key !== rowData.key 处理重复对某个字段进行 paste
+          while (parent.properties![itemKey]) {
+            itemKey = `${itemKey}__copy`;
+          }
+          if (!(item as { extra?: { value?: unknown } })?.extra?.value) {
+            delete (item as { extra?: { value?: string } }).extra;
+          }
+          item.extra = { index: ++index };
+          if (onPaste) {
+            item = onPaste(item) || item;
+          }
+          parent.properties![itemKey] = item as IJsonSchema;
+        });
+        onChange();
+      });
+      e.stopPropagation();
+      e.preventDefault();
+    },
+
+    [rowData, onChange]
+  );
+
+  return handlePasteAddType;
+};
+
+export const useRemoveType = <TypeSchema extends Partial<IJsonSchema>>(
+  rowData: TypeEditorRowData<TypeSchema>,
+  onChange: () => void
+) => {
+  const typeEditorService = useService<TypeEditorService<TypeSchema>>(TypeEditorService);
+
+  return useCallback(() => {
+    const { parent } = rowData;
+    if (!parent) {
+      return;
+    }
+    const { properties = {} } = parent;
+
+    delete properties[rowData.key];
+    typeEditorUtils.sortProperties(rowData.parent!);
+
+    if (typeEditorService.activePos.y === rowData.index) {
+      typeEditorService.clearActivePos();
+    }
+    typeEditorService.refreshErrorMsgAfterRemove(rowData.index);
+
+    onChange();
+  }, [onChange, rowData]);
+};

+ 17 - 0
packages/materials/type-editor/src/components/type-editor/indent.tsx

@@ -0,0 +1,17 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from 'react';
+import { type CSSProperties, type FC } from 'react';
+
+export const Indent: FC<{
+  count: number;
+  style?: CSSProperties;
+}> = ({ count, style = {} }) => <div style={{ height: '100%', width: count * 12, ...style }} />;
+
+export const WidthIndent: FC<{
+  width: number;
+  style?: CSSProperties;
+}> = ({ width, style = {} }) => <div style={{ height: '100%', flexShrink: 0, width, ...style }} />;

+ 52 - 0
packages/materials/type-editor/src/components/type-editor/index.tsx

@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useImperativeHandle, useState } from 'react';
+
+import { noop } from 'lodash';
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Space } from '@douyinfe/semi-ui';
+
+import { fixedTSForwardRef } from './utils';
+import { TypeEditorTable } from './type-editor';
+import { TypeEditorMode, TypeEditorProp, TypeEditorRef } from './type';
+import { ToolBar } from './tool-bar';
+export * from './type';
+export { columnConfigs as TypeEditorColumnConfigs } from './columns';
+
+const TypeEditorContainer = <Mode extends TypeEditorMode, TypeSchema extends Partial<IJsonSchema>>(
+  props: TypeEditorProp<Mode, TypeSchema>,
+  ref: React.Ref<TypeEditorRef<Mode, TypeSchema>>
+) => {
+  const [instance, setInstance] = useState<TypeEditorRef<Mode, TypeSchema>>();
+
+  useImperativeHandle(ref, () => ({
+    getContainer: () => instance?.getContainer(),
+    setValue: instance?.setValue || noop,
+    getService: instance?.getService || (() => undefined),
+    undo: instance?.undo || noop,
+    redo: instance?.redo || noop,
+    getValue() {
+      return instance?.getValue();
+    },
+    getOperator() {
+      return instance?.getOperator();
+    },
+  }));
+
+  return (
+    <Space spacing={4} vertical>
+      {instance && <ToolBar {...props} editor={instance} />}
+      <TypeEditorTable
+        onInit={(editor) => {
+          setInstance(editor.current);
+        }}
+        {...props}
+      />
+    </Space>
+  );
+};
+
+export const TypeEditor = fixedTSForwardRef(TypeEditorContainer);

+ 102 - 0
packages/materials/type-editor/src/components/type-editor/mode/declare-assign.ts

@@ -0,0 +1,102 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { set, get } from 'lodash';
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { typeEditorUtils } from '../utils';
+import { type ModeValueConfig } from '../type';
+import { TypeEditorColumnType, TypeEditorSchema } from '../../../types';
+
+const traverseIJsonSchema = (
+  root: TypeEditorSchema<IJsonSchema> | undefined,
+  path: string[],
+  cb: (type: TypeEditorSchema<IJsonSchema>, path: string[]) => void
+): void => {
+  if (root) {
+    cb(root, path);
+
+    if (root.properties) {
+      Object.keys(root.properties).forEach((k) => {
+        traverseIJsonSchema(root.properties![k], [...path, k], cb);
+      });
+    }
+  }
+};
+
+export const declareAssignConfig: ModeValueConfig<'declare-assign', IJsonSchema> = {
+  mode: 'declare-assign',
+  convertSchemaToValue: (val) => {
+    const data = {};
+    const newSchema = JSON.parse(JSON.stringify(val));
+
+    traverseIJsonSchema(newSchema, [], (type, path) => {
+      if (type.extra?.value !== undefined) {
+        set(data, path, type.extra?.value);
+      }
+      if (type.extra) {
+        delete type.extra;
+      }
+    });
+
+    return {
+      data,
+      definition: { schema: newSchema },
+    };
+  },
+  convertValueToSchema: (schema) => {
+    const { data } = schema;
+    const newSchema = JSON.parse(JSON.stringify(schema.definition.schema));
+    traverseIJsonSchema(newSchema, [], (type, path) => {
+      const value = get(data, path);
+      if (value !== undefined && type.type !== 'object') {
+        type.extra = { value };
+      }
+    });
+    return newSchema;
+  },
+  commonValueToSubmitValue: (val) => {
+    const type: IJsonSchema = val
+      ? typeEditorUtils.valueToTypeSchema(val)
+      : {
+          type: 'object',
+          properties: {},
+        };
+    return {
+      data: val || {},
+      definition: {
+        schema: type,
+      },
+    };
+  },
+
+  toolConfig: {
+    createByData: {
+      viewConfig: [
+        {
+          type: TypeEditorColumnType.Key,
+          visible: true,
+        },
+        {
+          type: TypeEditorColumnType.Type,
+          visible: true,
+        },
+        {
+          type: TypeEditorColumnType.Value,
+          visible: true,
+        },
+      ],
+      genDefaultValue: () => ({
+        data: {},
+        definition: {
+          schema: {
+            type: 'object',
+            properties: {},
+          },
+        },
+      }),
+    },
+  },
+};

+ 15 - 0
packages/materials/type-editor/src/components/type-editor/mode/index.ts

@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { type ModeValueConfig, type TypeEditorMode } from '../type';
+import { typeDefinitionConfig } from './type-definition';
+import { declareAssignConfig } from './declare-assign';
+
+export const modeValueConfig: ModeValueConfig<TypeEditorMode, IJsonSchema>[] = [
+  declareAssignConfig,
+  typeDefinitionConfig,
+];

+ 46 - 0
packages/materials/type-editor/src/components/type-editor/mode/type-definition.ts

@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { typeEditorUtils } from '../utils';
+import { type ModeValueConfig } from '../type';
+import { TypeEditorColumnType } from '../../../types';
+
+export const typeDefinitionConfig: ModeValueConfig<'type-definition', IJsonSchema> = {
+  mode: 'type-definition',
+  convertSchemaToValue: (val) => val,
+  convertValueToSchema: (val) => val,
+  commonValueToSubmitValue: (val) => {
+    if (val) {
+      return typeEditorUtils.valueToTypeSchema(val);
+    }
+    return {
+      type: 'object',
+      properties: {},
+    };
+  },
+
+  toolConfig: {
+    createByData: {
+      viewConfig: [
+        {
+          type: TypeEditorColumnType.Key,
+          visible: true,
+        },
+        {
+          type: TypeEditorColumnType.Type,
+          visible: true,
+        },
+      ],
+      genDefaultValue() {
+        return {
+          type: 'object',
+          properties: {},
+        };
+      },
+    },
+  },
+};

+ 80 - 0
packages/materials/type-editor/src/components/type-editor/style.ts

@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import styled from 'styled-components';
+
+export const ErrorMsgContainer = styled.div`
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  transform: translateY(100%);
+  width: calc(100% - 6px);
+`;
+
+export const EditCellContainer = styled.div<{
+  error?: boolean;
+  blink?: boolean;
+}>`
+  position: absolute;
+  width: 100%;
+  left: 0;
+  top: 0;
+  background-color: var(--semi-color-bg-0);
+  border: 1px solid var(--semi-color-focus-border);
+  height: 38px;
+  display: flex;
+  align-items: center;
+  transition: background-color 200ms;
+  box-sizing: border-box;
+  ${(props) => (props.error ? 'border: 1px solid var(--semi-color-danger);' : '')}
+  ${(props) => (props.blink ? 'background-color: rgba(238, 245, 40) !important;' : '')}
+`;
+
+export const DragRowContainer = styled.tr<{
+  dragging?: boolean;
+}>`
+  opacity: ${(props) => (props.dragging ? 0.5 : 1)};
+`;
+
+export const EditorTableHeader = styled.th`
+  border-right: 1px solid var(--semi-color-border);
+  border-bottom-width: 1px !important;
+  height: 37px;
+  box-sizing: border-box;
+  font-size: 12px;
+`;
+
+export const EditorTableCell = styled.td`
+  border-right: 1px solid var(--semi-color-border) !important;
+  position: relative;
+
+  padding: 8px !important;
+  height: 36px;
+`;
+
+export const EditorTable = styled.table`
+  min-width: 600px;
+  border-left-width: 1px;
+  border-top-width: 1px;
+  box-sizing: border-box;
+  border-color: var(--semi-color-border);
+  width: 100%;
+  position: relative;
+  border-style: solid;
+  border-bottom: none;
+  border-right: none;
+`;
+
+export const EditorTableTitle = styled.div`
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+`;
+
+export const BaseIcon = styled.div`
+  width: 12px;
+  height: 12px;
+  flex-shrink: 0;
+`;

+ 508 - 0
packages/materials/type-editor/src/components/type-editor/table.tsx

@@ -0,0 +1,508 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+/* eslint-disable max-params */
+/* eslint-disable complexity */
+
+import { HTML5Backend } from 'react-dnd-html5-backend';
+import { DndProvider } from 'react-dnd';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import React from 'react';
+
+import { isEqual } from 'lodash';
+import classNames from 'classnames';
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Space } from '@douyinfe/semi-ui';
+
+import { TypeEditorRowData, TypeEditorColumnType } from '../../types';
+import { TypeEditorOperationService, TypeEditorService } from '../../services';
+import { TypeRegistryCreatorsAdapter, useService, useTypeDefinitionManager } from '../../contexts';
+import { fixedTSForwardRef, typeEditorUtils } from './utils';
+import { type TypeEditorMode, type TypeEditorProp, TypeEditorRef } from './type';
+import { EditorTable } from './style';
+import { useFormatter } from './hooks/formatter-value';
+import { useActivePos } from './hooks';
+import { Header } from './header';
+import { DropTip } from './drop-tip';
+import { ROOT_FIELD_ID } from './common';
+import { EditCell } from './cell';
+import { Body } from './body';
+
+const TableInner = <Mode extends TypeEditorMode, TypeSchema extends Partial<IJsonSchema>>({
+  value,
+  onChange,
+  mode,
+  viewConfigs,
+  onInit,
+  forceUpdate,
+  onEditRowDataSource,
+  rootLevel = 0,
+  customEmptyNode,
+  tableClassName,
+  onError,
+  disableEditColumn,
+  readonly,
+  onPaste,
+  typeRegistryCreators,
+  onFieldChange,
+  getRootSchema,
+  onCustomSetValue,
+  extraConfig: originExtraConfig = {},
+}: TypeEditorProp<Mode, TypeSchema>) => {
+  const extraConfig = useMemo(
+    () => ({
+      ...originExtraConfig,
+    }),
+    [originExtraConfig]
+  );
+
+  const { deFormatter, formatter } = useFormatter<Mode, TypeSchema>({
+    mode,
+    extraConfig,
+  });
+
+  const typeSchema = useMemo(() => formatter(value), [formatter, value]);
+
+  const typeEditor = useService<TypeEditorService<TypeSchema>>(TypeEditorService);
+  const typeOperator = useService<TypeEditorOperationService<TypeSchema>>(
+    TypeEditorOperationService
+  );
+
+  const [tableDom, setTableDom] = useState<HTMLTableElement>();
+
+  const [initialSchema, setInitialSchema] = useState<TypeSchema>(() => {
+    const res = typeEditorUtils.clone(typeSchema) || typeEditorUtils.getInitialSchema<TypeSchema>();
+
+    typeOperator.storeState(res);
+
+    return res;
+  });
+
+  // const
+  const editor = useRef<TypeEditorRef<Mode, TypeSchema>>();
+
+  useEffect(() => {
+    // editor
+    const instance: TypeEditorRef<Mode, TypeSchema> = {
+      getService: () => typeEditor,
+      setValue(originNewVal) {
+        let newVal = originNewVal;
+
+        if (onCustomSetValue) {
+          newVal = onCustomSetValue(newVal);
+        }
+
+        const newSchema = formatter(newVal)!;
+
+        setInitialSchema(newSchema);
+
+        typeOperator.storeState(newSchema);
+
+        const final = typeEditorUtils.formateTypeSchema<TypeSchema>(newSchema, extraConfig);
+
+        const newValue = deFormatter(final)!;
+
+        if (onChange) {
+          onChange(newValue);
+        }
+      },
+      getContainer: () => tableDom,
+      getValue: () => {
+        const rootValue = deFormatter(typeEditor.rootTypeSchema);
+
+        return rootValue;
+      },
+      undo: () => {
+        typeOperator.undo();
+        typeEditor.onChange(typeOperator.getCurrentState(), {
+          storeState: false,
+        });
+      },
+      redo: () => {
+        typeOperator.redo();
+        typeEditor.onChange(typeOperator.getCurrentState(), {
+          storeState: false,
+        });
+      },
+      getOperator() {
+        return typeOperator;
+      },
+    };
+    editor.current = instance;
+  }, [typeEditor, onChange, tableDom, deFormatter, formatter, extraConfig, onCustomSetValue]);
+
+  useEffect(() => {
+    onInit?.(editor);
+  }, [onInit]);
+
+  /**
+   * 当前 rowData 的 children 是否不可见
+   * 定义为不可见的原因:默认可见
+   */
+  const [unOpenKeys, setUnOpenKeys] = useState<Record<string, boolean>>({});
+
+  useEffect(() => {
+    if (
+      !isEqual(typeSchema, initialSchema) &&
+      typeSchema &&
+      (forceUpdate || !typeEditorUtils.isTempState(initialSchema, extraConfig?.customValidateName))
+    ) {
+      setInitialSchema(typeEditorUtils.clone(typeSchema));
+    }
+  }, [typeSchema, extraConfig?.customValidateName]);
+
+  const typeService = useTypeDefinitionManager();
+
+  const displayColumn = useMemo(
+    () => viewConfigs.filter((v) => v.visible).map((v) => v.type),
+    [viewConfigs]
+  );
+
+  const { dataSource } = useMemo(() => {
+    const res: TypeEditorRowData<TypeSchema>[] = [];
+    let index = -1;
+
+    const dfs = (
+      schema: TypeSchema,
+      config: {
+        level: number;
+        parentId?: string;
+        key?: string;
+        parent?: TypeSchema;
+        canDefaultEditable?: true | string;
+        canValueEditable?: true | string;
+        path: string[];
+      }
+    ): void => {
+      const {
+        parentId,
+        level = -1,
+        key = ROOT_FIELD_ID,
+        path,
+        parent,
+        canValueEditable = true,
+        canDefaultEditable = true,
+      } = config;
+
+      const id = [parentId, key || new Date().valueOf().toString()].join('-');
+
+      const typeConfig = typeService.getTypeBySchema(schema);
+
+      const uid = parentId ? id : key;
+
+      const rowData: TypeEditorRowData<TypeSchema> = {
+        ...schema,
+        key,
+        index,
+        id: uid,
+        level,
+        self: schema,
+        parentId,
+        parent,
+        deepChildrenCount: 0,
+        isRequired: (parent?.required || []).includes(key),
+        childrenCount: 0,
+        disableEditColumn: [...(disableEditColumn || [])],
+        path,
+        extraConfig: { ...extraConfig },
+      };
+
+      index += 1;
+
+      typeEditor.dataSourceMap[rowData.id] = rowData;
+
+      res.push(rowData);
+
+      const customDefaultValidate =
+        extraConfig.customDefaultEditable && extraConfig.customDefaultEditable(rowData);
+
+      if (typeof customDefaultValidate === 'string') {
+        rowData.disableEditColumn!.push({
+          column: TypeEditorColumnType.Default,
+          reason: customDefaultValidate,
+        });
+      }
+
+      if (typeof canDefaultEditable === 'string' && level > rootLevel) {
+        rowData.disableEditColumn!.push({
+          column: TypeEditorColumnType.Default,
+          reason: canDefaultEditable,
+        });
+      }
+
+      if (typeof canValueEditable === 'string' && level > rootLevel) {
+        rowData.disableEditColumn!.push({
+          column: TypeEditorColumnType.Value,
+          reason: canValueEditable,
+        });
+      }
+
+      if (typeConfig) {
+        const children =
+          typeConfig.getTypeSchemaProperties && typeConfig.getTypeSchemaProperties(schema);
+
+        const childrenParent =
+          typeConfig.getPropertiesParent && typeConfig.getPropertiesParent(schema);
+        const childrenParentConfig = childrenParent && typeService.getTypeBySchema(childrenParent);
+
+        if (
+          !extraConfig.customDefaultEditable &&
+          typeof childrenParentConfig?.defaultEditable === 'string'
+        ) {
+          rowData.disableEditColumn!.push({
+            column: TypeEditorColumnType.Default,
+            reason: childrenParentConfig?.defaultEditable,
+          });
+        }
+
+        if (children) {
+          const originLen = res.length;
+          rowData.childrenCount = Object.keys(children).length;
+
+          // 如果不可见,不加子节点
+          if (unOpenKeys[uid]) {
+            return;
+          }
+
+          const parentPath = [...path, ...(typeConfig.getJsonPaths?.(schema) || [])];
+
+          let idx = 0;
+
+          const childCanValueEditable = typeConfig?.childrenValueEditable?.(schema);
+
+          Object.keys(children)
+            .map((k) => {
+              // 对不存在 index 的数据先进行修正,填上默认值
+              typeEditorUtils.fixFlowIndex(children[k], idx);
+              idx++;
+              return k;
+            })
+            .sort((k1, k2) => (children[k1].extra?.index || 0) - (children[k2].extra?.index || 0))
+            .forEach((k) => {
+              const canDefaultEditableFromParent = typeConfig.childrenDefaultEditable?.(schema);
+
+              dfs(children[k] as unknown as TypeSchema, {
+                key: k,
+                parentId: id,
+                parent: childrenParent as unknown as TypeSchema,
+                level: level + 1,
+                path: [...parentPath, k],
+                canDefaultEditable:
+                  typeof canDefaultEditableFromParent === 'string'
+                    ? canDefaultEditableFromParent
+                    : canDefaultEditable,
+                canValueEditable: childCanValueEditable,
+              });
+            });
+
+          rowData.deepChildrenCount = res.length - originLen;
+        } else {
+          if (
+            !extraConfig.customDefaultEditable &&
+            typeof typeConfig?.defaultEditable === 'string'
+          ) {
+            rowData.disableEditColumn!.push({
+              column: TypeEditorColumnType.Default,
+              reason: typeConfig?.defaultEditable,
+            });
+          }
+        }
+      }
+    };
+
+    if (initialSchema) {
+      dfs(initialSchema, { level: -1, path: [] });
+      // 不展示 root
+      res.shift();
+
+      const newData = onEditRowDataSource ? onEditRowDataSource(res) : res;
+
+      typeEditor.setDataSource(newData);
+
+      return {
+        dataSource: newData,
+      };
+    }
+
+    return {
+      dataSource: [],
+    };
+  }, [initialSchema, disableEditColumn, unOpenKeys, onEditRowDataSource]);
+
+  const activePos = useActivePos();
+
+  useEffect(() => {
+    typeEditor.rootTypeSchema = initialSchema;
+  }, [initialSchema]);
+
+  /**
+   * 修改单行数据
+   */
+  const handleTypeSchemaChange = useCallback(
+    (
+      type?: TypeSchema,
+      ctx: {
+        storeState?: boolean;
+      } = {}
+    ) => {
+      const { storeState = true } = ctx;
+
+      const newSchema = JSON.parse(JSON.stringify(type || initialSchema)) as TypeSchema;
+
+      setInitialSchema({ ...newSchema });
+
+      const final = typeEditorUtils.formateTypeSchema(newSchema, extraConfig);
+
+      if (storeState) {
+        typeOperator.storeState(newSchema);
+      }
+
+      const newValue = deFormatter(final)!;
+
+      if (onChange) {
+        onChange(newValue);
+      }
+    },
+    [initialSchema, deFormatter, extraConfig]
+  );
+
+  /**
+   * 添加新数据
+   */
+  const handleRowDataAdd = useCallback(
+    (id: string) => {
+      const rowData = typeEditor.dataSourceMap[id];
+
+      const currentSchema = rowData.self;
+
+      const config = typeService.getTypeBySchema(rowData.self);
+
+      if (currentSchema && config) {
+        let index = -1;
+        const parent = config.getPropertiesParent?.(currentSchema);
+        if (!parent) {
+          return;
+        }
+        if (!parent.properties) {
+          parent.properties = {};
+        }
+
+        Object.values(parent.properties).forEach((val) => {
+          if (!val.extra) {
+            val.extra = {
+              index: 0,
+            };
+          }
+
+          index = Math.max(index, val.extra.index || 0);
+        });
+
+        const [key, schema] = typeEditorUtils.genNewTypeSchema(index + 1);
+
+        parent.properties[key] = schema as IJsonSchema;
+
+        const newDataSource = typeEditor.getDataSource();
+
+        let addIndex = rowData.index + 1;
+        for (let i = rowData.index + 1, len = dataSource.length; i < len; i++) {
+          if (newDataSource[i].level > rowData.level) {
+            addIndex++;
+          } else {
+            break;
+          }
+        }
+
+        const newPos = {
+          x: 0,
+          y: addIndex,
+        };
+
+        typeEditor.setActivePos(newPos);
+
+        handleTypeSchemaChange();
+      }
+    },
+    [initialSchema, getRootSchema]
+  );
+
+  useEffect(() => {
+    typeEditor.columnViewConfig = viewConfigs.filter((v) => v.visible);
+    typeEditor.onChange = handleTypeSchemaChange;
+    typeEditor.onGlobalAdd = handleRowDataAdd;
+
+    typeEditor.typeRegistryCreators =
+      typeRegistryCreators as unknown as TypeRegistryCreatorsAdapter<TypeSchema>[];
+  }, [viewConfigs, handleTypeSchemaChange, typeRegistryCreators]);
+
+  /**
+   * 展开收起子字段
+   */
+  const handleChildrenVisibleChange = useCallback(
+    (rowDataId: string, newVal: boolean) => {
+      const newData = { ...unOpenKeys };
+
+      newData[rowDataId] = newVal;
+      setUnOpenKeys(newData);
+    },
+    [unOpenKeys]
+  );
+
+  return (
+    <>
+      <DndProvider backend={HTML5Backend}>
+        <Space vertical style={{ width: '100%' }}>
+          <div style={{ width: '100%' }}>
+            <EditorTable
+              ref={(dom) => setTableDom(dom as React.SetStateAction<HTMLTableElement | undefined>)}
+              className={classNames('semi-table', tableClassName)}
+            >
+              <Header
+                dataSourceMap={typeEditor.dataSourceMap}
+                value={initialSchema}
+                readonly={readonly}
+                onChange={handleTypeSchemaChange}
+                displayColumn={displayColumn}
+              />
+              <Body
+                viewConfigs={viewConfigs}
+                onFieldChange={onFieldChange}
+                onPaste={onPaste}
+                customEmptyNode={customEmptyNode}
+                readonly={readonly}
+                dataSourceMap={typeEditor.dataSourceMap}
+                unOpenKeys={unOpenKeys}
+                onError={onError}
+                onChildrenVisibleChange={handleChildrenVisibleChange}
+                onChange={handleTypeSchemaChange}
+                dataSource={dataSource}
+                displayColumn={displayColumn}
+              />
+            </EditorTable>
+          </div>
+        </Space>
+      </DndProvider>
+
+      {/** 当前编辑的单元格,单独渲染,减少表格的重复渲染 **/}
+      {activePos.x !== -1 && activePos.y !== -1 && tableDom && (
+        <EditCell
+          viewConfigs={viewConfigs}
+          unOpenKeys={unOpenKeys}
+          onPaste={onPaste}
+          onFieldChange={onFieldChange}
+          onError={onError}
+          onChildrenVisibleChange={handleChildrenVisibleChange}
+          onChange={handleTypeSchemaChange}
+          tableDom={tableDom}
+          dataSource={dataSource}
+          displayColumn={displayColumn}
+        />
+      )}
+      {/** 当前拖拽的提示辅助框 **/}
+      <DropTip dataSource={typeEditor.dataSourceMap} />
+    </>
+  );
+};
+
+export const Table = fixedTSForwardRef(TableInner);

+ 69 - 0
packages/materials/type-editor/src/components/type-editor/tool-bar.tsx

@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useMemo } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Space, Tooltip } from '@douyinfe/semi-ui';
+
+import {
+  ToolbarConfig,
+  ToolbarKey,
+  type TypeEditorMode,
+  type TypeEditorProp,
+  type TypeEditorRef,
+} from './type';
+import { UndoRedo } from './tools/undo-redo';
+import { CreateByData } from './tools/create-by-data';
+
+export const ToolBar = <Mode extends TypeEditorMode, TypeSchema extends Partial<IJsonSchema>>({
+  mode,
+  editor,
+  toolbarConfig = [],
+}: TypeEditorProp<Mode, TypeSchema> & {
+  editor?: TypeEditorRef<Mode, TypeSchema>;
+}) => {
+  const config = useMemo(() => {
+    const res = new Map<string, ToolbarConfig>();
+
+    toolbarConfig.forEach((tool) => {
+      if (typeof tool === 'string') {
+        res.set(tool, { type: tool });
+      } else {
+        res.set(tool.type, tool);
+      }
+    });
+    return res;
+  }, [toolbarConfig]);
+
+  const importConfig = config.get(ToolbarKey.Import);
+
+  return (
+    <div style={{ width: '100%' }}>
+      {editor && (
+        <Space style={{ float: 'right' }}>
+          <>
+            {importConfig && (
+              <>
+                {importConfig.disabled ? (
+                  <Tooltip content={importConfig.disabled}>
+                    <CreateByData disabled mode={mode} editor={editor} />
+                  </Tooltip>
+                ) : (
+                  <CreateByData
+                    mode={mode}
+                    customInputRender={importConfig.customInputRender}
+                    editor={editor}
+                  />
+                )}
+              </>
+            )}
+          </>
+          {config.has(ToolbarKey.UndoRedo) && <UndoRedo editor={editor} />}
+        </Space>
+      )}
+    </div>
+  );
+};

+ 121 - 0
packages/materials/type-editor/src/components/type-editor/tools/create-by-data.tsx

@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { FC, useCallback, useMemo, useState } from 'react';
+
+import { debounce } from 'lodash';
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Button, Divider, Empty, Modal, Space, TextArea } from '@douyinfe/semi-ui';
+import { IllustrationFailure, IllustrationFailureDark } from '@douyinfe/semi-illustrations';
+
+import { typeEditorUtils } from '../utils';
+import { TypeEditorTable } from '../type-editor';
+import { type TypeEditorMode, type TypeEditorRef, type TypeEditorValue } from '../type';
+import { modeValueConfig } from '../mode';
+import { DataTransform } from './style';
+
+export const CreateByData = <Mode extends TypeEditorMode, TypeSchema extends Partial<IJsonSchema>>({
+  mode,
+  disabled,
+  customInputRender: CustomInputRender,
+  editor,
+}: {
+  editor: TypeEditorRef<Mode, TypeSchema>;
+  mode: Mode;
+  disabled?: boolean;
+  customInputRender?: FC<{ value: string; onChange: (newVal: string) => void }>;
+}) => {
+  const modeConfig = useMemo(() => modeValueConfig.find((v) => v.mode === mode)!, [mode]);
+
+  const [visible, setVisible] = useState(false);
+
+  const [typeEditorValue, setTypeEditorValue] = useState<TypeEditorValue<Mode, TypeSchema>>(
+    modeConfig.toolConfig.createByData.genDefaultValue() as TypeEditorValue<Mode, TypeSchema>
+  );
+  const [errorEmpty, setErrorEmpty] = useState(false);
+
+  const handleClose = useCallback(() => {
+    setVisible(false);
+    setTypeEditorValue(
+      modeConfig.toolConfig.createByData.genDefaultValue() as TypeEditorValue<Mode, TypeSchema>
+    );
+  }, [modeConfig]);
+
+  const handleOk = useCallback(async () => {
+    if (editor) {
+      editor.setValue(typeEditorValue);
+      handleClose();
+    }
+  }, [handleClose, typeEditorValue, editor]);
+
+  const onChange = debounce((newVal: string) => {
+    const parsedValue = typeEditorUtils.jsonParse(newVal);
+    const error =
+      newVal && !(parsedValue && typeof parsedValue === 'object' && !Array.isArray(parsedValue));
+
+    if (!error) {
+      setTypeEditorValue(
+        modeConfig.commonValueToSubmitValue(parsedValue) as TypeEditorValue<Mode, TypeSchema>
+      );
+      setErrorEmpty(false);
+    } else if (parsedValue) {
+      setErrorEmpty(true);
+    }
+  }, 500);
+
+  return (
+    <div>
+      <Button disabled={disabled} size="small" onClick={() => setVisible(true)}>
+        Import from JSON
+      </Button>
+      <Modal
+        okText={`Import`}
+        width={960}
+        okButtonProps={{}}
+        cancelText="Cancel"
+        title="Import from JSON"
+        visible={visible}
+        onOk={handleOk}
+        onCancel={handleClose}
+      >
+        <DataTransform>
+          <div style={{ height: '100%', flex: 1 }}>
+            {CustomInputRender ? (
+              <CustomInputRender value="{}" onChange={onChange} />
+            ) : (
+              <TextArea defaultValue="{}" onChange={onChange} />
+            )}
+          </div>
+          <Divider layout="vertical" style={{ height: '100%' }} />
+          <Space align="start" vertical style={{ height: '100%', flex: 1 }}>
+            <div style={{ height: '100%', flex: 1, overflowY: 'scroll' }}>
+              <TypeEditorTable
+                readonly
+                mode={mode}
+                forceUpdate
+                // TODO:
+                // tableClassName={s['tool-table']}
+                value={errorEmpty ? undefined : typeEditorValue}
+                customEmptyNode={
+                  errorEmpty ? (
+                    <Empty
+                      style={{ marginTop: 40 }}
+                      image={<IllustrationFailure style={{ width: 100, height: 100 }} />}
+                      darkModeImage={
+                        <IllustrationFailureDark style={{ width: 100, height: 100 }} />
+                      }
+                      description="Invalid value"
+                    />
+                  ) : undefined
+                }
+                viewConfigs={modeConfig.toolConfig.createByData.viewConfig}
+              />
+            </div>
+          </Space>
+        </DataTransform>
+      </Modal>
+    </div>
+  );
+};

+ 27 - 0
packages/materials/type-editor/src/components/type-editor/tools/index.module.less

@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+.tool-table {
+  min-width: 450px;
+}
+
+.data-transform-container {
+  height: 406px;
+  display: flex;
+  gap: 4px;
+}
+
+.full-height {
+  height: 100%;
+}
+
+.full-fill {
+  flex: 1;
+}
+
+
+.type-editor-table-container {
+  overflow-y: scroll;
+}

+ 12 - 0
packages/materials/type-editor/src/components/type-editor/tools/style.ts

@@ -0,0 +1,12 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import styled from 'styled-components';
+
+export const DataTransform = styled.div`
+  height: 406px;
+  display: flex;
+  gap: 4px;
+`;

+ 49 - 0
packages/materials/type-editor/src/components/type-editor/tools/undo-redo.tsx

@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+// import { ShortcutsService, useIDEService } from '@flow-ide/client';
+import React from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Button, Tooltip } from '@douyinfe/semi-ui';
+import { IconRedo, IconUndo } from '@douyinfe/semi-icons';
+// import { TypeEditorCommand, useMonitorData } from '@api-builder/base';
+
+import { type TypeEditorMode, type TypeEditorRef } from '../type';
+import { useMonitorData } from '../../../utils';
+
+export const UndoRedo = <Mode extends TypeEditorMode, TypeSchema extends Partial<IJsonSchema>>({
+  editor,
+}: {
+  editor: TypeEditorRef<Mode, TypeSchema>;
+}) => {
+  const { data: canUndo } = useMonitorData(editor.getOperator()?.canUndo);
+  const { data: canRedo } = useMonitorData(editor.getOperator()?.canRedo);
+
+  return (
+    <>
+      <Tooltip content="Undo">
+        <Button
+          disabled={!canUndo}
+          icon={<IconUndo />}
+          size="small"
+          onClick={() => {
+            editor?.undo();
+          }}
+        />
+      </Tooltip>
+      <Tooltip content="Redo">
+        <Button
+          size="small"
+          icon={<IconRedo />}
+          disabled={!canRedo}
+          onClick={() => {
+            editor?.redo();
+          }}
+        />
+      </Tooltip>
+    </>
+  );
+};

+ 43 - 0
packages/materials/type-editor/src/components/type-editor/type-editor.tsx

@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from 'react';
+
+import styled from 'styled-components';
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { TypeEditorProvider } from '../../contexts';
+import { type TypeEditorMode, type TypeEditorProp } from './type';
+import { Table } from './table';
+import { TypeEditorListener } from './hooks';
+
+const Container = styled.div`
+  position: relative;
+
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+
+  outline: none;
+
+  table {
+    table-layout: fixed;
+  }
+`;
+
+export const TypeEditorTable = <
+  Mode extends TypeEditorMode,
+  TypeSchema extends Partial<IJsonSchema>
+>(
+  props: TypeEditorProp<Mode, TypeSchema>
+) => (
+  <TypeEditorProvider typeRegistryCreators={props.typeRegistryCreators}>
+    <TypeEditorListener configs={props.viewConfigs}>
+      <Container>
+        <Table {...props} />
+      </Container>
+    </TypeEditorListener>
+  </TypeEditorProvider>
+);

+ 204 - 0
packages/materials/type-editor/src/components/type-editor/type.ts

@@ -0,0 +1,204 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import type React from 'react';
+import { FC } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import {
+  type TypeEditorSpecialConfig,
+  type TypeEditorColumnViewConfig,
+  type TypeEditorRowData,
+  type TypeChangeContext,
+  type TypeEditorColumnType,
+  type TypeEditorSchema,
+  TypeEditorColumnConfig,
+} from '../../types';
+import { TypeEditorOperationService, type TypeEditorService } from '../../services';
+import { TypeRegistryCreatorsAdapter } from '../../contexts';
+
+export type TypeEditorMode = 'type-definition' | 'declare-assign';
+
+export interface DeclareAssignValueType<TypeSchema extends Partial<IJsonSchema>> {
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  data: any;
+  definition: {
+    schema: TypeSchema;
+  };
+}
+
+export type TypeEditorValue<
+  Mode extends TypeEditorMode,
+  TypeSchema extends Partial<IJsonSchema>
+> = Mode extends 'type-definition'
+  ? TypeEditorSchema<TypeSchema>
+  : DeclareAssignValueType<TypeSchema>;
+
+export enum ToolbarKey {
+  Import = 'Import',
+  UndoRedo = 'UndoRedo',
+}
+export type ToolbarConfig = {
+  /**
+   * 工具栏配置
+   */
+  type: ToolbarKey;
+  /**
+   * 是否禁用
+   */
+  disabled?: string;
+
+  /**
+   * 自定义输入渲染,仅 Import 使用
+   */
+  customInputRender?: FC<{
+    value: string;
+    onChange: (newVal: string) => void;
+  }>;
+};
+
+export interface TypeEditorProp<
+  Mode extends TypeEditorMode,
+  TypeSchema extends Partial<IJsonSchema>
+> {
+  /**
+   * 菜单栏配置
+   */
+  toolbarConfig?: (ToolbarKey | ToolbarConfig)[];
+  /**
+   * type editor 模式类型
+   */
+  mode: Mode;
+
+  /**
+   * 只读态
+   */
+  readonly?: boolean;
+  /**
+   *
+   */
+  tableClassName?: string;
+
+  /**
+   *  各个 cell 的特化配置
+   */
+  extraConfig?: TypeEditorSpecialConfig<TypeSchema>;
+  /**
+   * 根节点层级
+   */
+  rootLevel?: number;
+
+  /**
+   * 获取全局 add 的 root schema
+   */
+  getRootSchema?: (schema: TypeSchema) => TypeSchema;
+  /**
+   *
+   */
+  typeRegistryCreators?: TypeRegistryCreatorsAdapter<IJsonSchema>[];
+
+  /**
+   * 每个列的配置
+   */
+  viewConfigs: (TypeEditorColumnViewConfig & {
+    config?: Partial<Omit<TypeEditorColumnConfig<TypeSchema>, 'type'>>;
+  })[];
+
+  /**
+   * 每次设置 DataSource 前调用,最后修改值的钩子
+   */
+  onEditRowDataSource?: (data: TypeEditorRowData<TypeSchema>[]) => TypeEditorRowData<TypeSchema>[];
+  /**
+   * 忽略报错强制更新
+   */
+  forceUpdate?: boolean;
+
+  /**
+   * onError
+   */
+  onError?: (msg?: string[]) => void;
+  /**
+   * value
+   */
+  value?: TypeEditorValue<Mode, TypeSchema>;
+  /**
+   * onChange
+   */
+  onChange?: (newValue: TypeEditorValue<Mode, TypeSchema>) => void;
+  /**
+   * onPaste
+   */
+  onPaste?: (typeSchema?: TypeSchema) => TypeSchema | undefined;
+
+  /**
+   * onInit
+   */
+  onInit?: (editor: React.MutableRefObject<TypeEditorRef<Mode, TypeSchema> | undefined>) => void;
+
+  /**
+   * 当具体某个 field change
+   */
+  onFieldChange?: (ctx: TypeChangeContext) => void;
+  /**
+   * 当执行 setValue
+   */
+  onCustomSetValue?: (
+    newValue: TypeEditorValue<Mode, TypeSchema>
+  ) => TypeEditorValue<Mode, TypeSchema>;
+
+  /**
+   * 自定义空状态
+   */
+  customEmptyNode?: React.ReactElement;
+
+  /**
+   * 不能编辑的列
+   * 和 TypeSchema 中 editable 的关系
+   * editable 为 false,会将 disableEditColumn 每个 column 都填上
+   */
+  disableEditColumn?: Array<{ column: TypeEditorColumnType; reason: string }>;
+}
+
+export interface TypeEditorRef<
+  Mode extends TypeEditorMode,
+  TypeSchema extends Partial<IJsonSchema>
+> {
+  setValue: (newVal: TypeEditorValue<Mode, TypeSchema>) => void;
+  getValue: () => TypeEditorValue<Mode, TypeSchema> | undefined;
+  undo: () => void;
+  redo: () => void;
+  getService: () => TypeEditorService<TypeSchema> | undefined;
+  getOperator: () => TypeEditorOperationService<TypeSchema> | undefined;
+  getContainer: () => HTMLDivElement | undefined;
+}
+
+export interface ModeValueConfig<
+  Mode extends TypeEditorMode,
+  TypeSchema extends Partial<IJsonSchema>
+> {
+  mode: Mode;
+  /**
+   * 提交值到 typeSchema
+   */
+  convertValueToSchema: (val: TypeEditorValue<Mode, TypeSchema>) => TypeSchema;
+  /**
+   * typeSchema 到提交值
+   */
+  convertSchemaToValue: (val: TypeSchema) => TypeEditorValue<Mode, TypeSchema>;
+  /**
+   * 常量值生成提交值
+   */
+  commonValueToSubmitValue: (
+    val: Record<string, unknown> | undefined
+  ) => TypeEditorValue<Mode, TypeSchema>;
+
+  toolConfig: {
+    createByData: {
+      viewConfig: TypeEditorColumnViewConfig[];
+      genDefaultValue: () => TypeEditorValue<Mode, TypeSchema>;
+    };
+  };
+}

+ 220 - 0
packages/materials/type-editor/src/components/type-editor/utils.ts

@@ -0,0 +1,220 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { forwardRef } from 'react';
+
+import { nanoid } from 'nanoid';
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { TypeEditorSpecialConfig } from '../../types';
+import { disableFixIndexFormatter, emptyKeyFormatter } from './formatter';
+import { SUFFIX, COMPONENT_ID_PREFIX } from './common';
+
+const genNewTypeSchema = <TypeSchema extends Partial<IJsonSchema>>(
+  index: number
+): [string, TypeSchema] => {
+  const newKey = genEmptyKey();
+
+  return [
+    newKey,
+    {
+      type: 'string',
+      extra: {
+        index,
+      },
+    } as TypeSchema,
+  ];
+};
+
+const traverseIJsonSchema = <TypeSchema extends Partial<IJsonSchema>>(
+  root: TypeSchema | undefined,
+  cb: (type: TypeSchema) => void
+): void => {
+  if (root) {
+    cb(root);
+
+    if (root.items) {
+      traverseIJsonSchema(root.items as TypeSchema, cb);
+    }
+    if (root.additionalProperties) {
+      traverseIJsonSchema(root.additionalProperties as TypeSchema, cb);
+    }
+
+    if (root.properties) {
+      Object.values(root.properties).forEach((v) => {
+        traverseIJsonSchema(v as TypeSchema, cb);
+      });
+    }
+  }
+};
+
+export const jsonParse = (jsonString?: string) => {
+  try {
+    return JSON.parse(jsonString || '');
+  } catch (error) {
+    // todo 兼容错误形态JSON
+    return undefined;
+  }
+};
+
+const sortProperties = <TypeSchema extends Partial<IJsonSchema>>(typeSchema: TypeSchema) => {
+  const { properties = {} } = typeSchema;
+  const originKeys = Object.keys(properties);
+
+  const sortKeys = originKeys.sort(
+    (a, b) => (properties[a].extra?.index || 0) - (properties[b].extra?.index || 0)
+  );
+
+  for (let i = 0; i < sortKeys.length; i++) {
+    const key = sortKeys[i];
+
+    fixFlowIndex(properties[key]);
+
+    properties[key].extra!.index = i;
+  }
+};
+
+const fixFlowIndex = <TypeSchema extends Partial<IJsonSchema>>(type: TypeSchema, idx = 0): void => {
+  if (!type) {
+    return;
+  }
+
+  if (!type.extra) {
+    type.extra = {};
+  }
+
+  if (type.extra.index === undefined) {
+    type.extra.index = idx;
+  }
+};
+
+const getInitialSchema = <TypeSchema extends Partial<IJsonSchema>>(): TypeSchema => {
+  const res: IJsonSchema = {
+    type: 'object',
+    properties: {},
+  };
+  return res as TypeSchema;
+};
+
+const clone = <T>(val: T): T => (val ? JSON.parse(JSON.stringify(val)) : val);
+
+const isTempState = <TypeSchema extends Partial<IJsonSchema>>(
+  type: TypeSchema,
+  customValidateName?: (value: string) => string
+): boolean => {
+  let error = false;
+
+  traverseIJsonSchema(type, (c) => {
+    if (c.properties) {
+      Object.keys(c.properties).forEach((key) => {
+        const res = isEmptyKey(key) || customValidateName?.(key);
+        if (res) {
+          error = true;
+        }
+      });
+    }
+  });
+
+  return error;
+};
+
+const genEmptyKey = () => SUFFIX + nanoid();
+
+const isEmptyKey = (key: string) => key.startsWith(SUFFIX);
+
+const formateKey = (key: string) => (isEmptyKey(key) ? '' : key);
+
+const deFormateKey = (key: string, originKey?: string) => (!key ? originKey || genEmptyKey() : key);
+
+const formateTypeSchema = <TypeSchema extends Partial<IJsonSchema>>(
+  typeSchema: TypeSchema,
+  config: TypeEditorSpecialConfig<TypeSchema>
+): TypeSchema => {
+  const newSchema = JSON.parse(JSON.stringify(typeSchema));
+
+  const formatters = [emptyKeyFormatter];
+  if (config.disableFixIndex) {
+    formatters.push(disableFixIndexFormatter);
+  }
+
+  traverseIJsonSchema(newSchema, (type) => {
+    formatters.forEach((formatter) => {
+      formatter(type);
+    });
+  });
+
+  return newSchema;
+};
+
+const valueToTypeSchema = <TypeSchema extends Partial<IJsonSchema>>(value: unknown): TypeSchema => {
+  // return
+
+  switch (typeof value) {
+    case 'string': {
+      return {
+        type: 'string',
+      } as TypeSchema;
+    }
+    case 'bigint':
+    case 'number': {
+      return {
+        type: 'number',
+      } as TypeSchema;
+    }
+    case 'boolean': {
+      return {
+        type: 'boolean',
+      } as TypeSchema;
+    }
+    case 'object': {
+      if (value) {
+        if (Array.isArray(value)) {
+          return {
+            type: 'array',
+            items: valueToTypeSchema(value[0]),
+          } as TypeSchema;
+        } else {
+          const object: IJsonSchema = {
+            type: 'object',
+            properties: {},
+          };
+          Object.keys(value).forEach((k) => {
+            object.properties![k] = valueToTypeSchema((value as any)[k]);
+          });
+          return object as TypeSchema;
+        }
+      }
+      break;
+    }
+    default:
+      break;
+  }
+  return { type: 'string' } as TypeSchema;
+};
+
+export const typeEditorUtils = {
+  genNewTypeSchema,
+  sortProperties,
+  traverseIJsonSchema,
+  fixFlowIndex,
+  genEmptyKey,
+  jsonParse,
+  clone,
+  formateTypeSchema,
+  isTempState,
+  valueToTypeSchema,
+  deFormateKey,
+  formateKey,
+  getInitialSchema,
+};
+
+export function fixedTSForwardRef<T, P = object>(
+  render: (props: P, ref: React.Ref<T>) => JSX.Element
+): (props: P & React.RefAttributes<T>) => JSX.Element {
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  return forwardRef(render as any) as any;
+}
+
+export const getComponentId = (id: string): string => `${COMPONENT_ID_PREFIX}-${id}`;

+ 131 - 0
packages/materials/type-editor/src/components/type-selector/cascader-v2/index.tsx

@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useEffect, useRef, useState } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Popover } from '@douyinfe/semi-ui';
+
+import { TypeSelectorRef, type Props } from '../type';
+import { FlowSchemaInitCtx } from '../../../types';
+import { useTypeDefinitionManager } from '../../../contexts';
+import { TypeSearchPanel } from './type-search';
+import { TypeCascader } from './type-cascader';
+import { Trigger } from './trigger';
+import { CascaderContainer, CustomCascaderContainer } from './style';
+
+export const CascaderV2 = (
+  props: Props<IJsonSchema> & {
+    onInit?: (typeEditorRef: TypeSelectorRef) => void;
+  }
+) => {
+  const {
+    triggerRender,
+    disabled,
+    defaultOpen,
+    onInit,
+    value,
+    onDropdownVisibleChange,
+    getPopupContainer = () => document.body,
+  } = props;
+
+  const triggerRef = useRef<HTMLDivElement>(null);
+
+  const typeSelectorRef = useRef<TypeSelectorRef>(null);
+
+  const [searchValue, setSearchValue] = useState('');
+
+  const typeService = useTypeDefinitionManager();
+
+  useEffect(() => {
+    if (typeSelectorRef.current) {
+      onInit?.(typeSelectorRef.current);
+    }
+  }, [typeSelectorRef.current, onInit]);
+
+  const [rePosKey, setReposKey] = useState(0);
+  const [context, setContext] = useState<FlowSchemaInitCtx>(
+    (value && typeService.getTypeBySchema(value)?.typeCascaderConfig?.generateInitCtx?.(value)) ||
+      {}
+  );
+
+  const [visible, setVisible] = useState(defaultOpen);
+
+  if (disabled) {
+    return (
+      <>
+        {triggerRender ? (
+          <CustomCascaderContainer>{triggerRender()}</CustomCascaderContainer>
+        ) : (
+          <CascaderContainer>
+            <Trigger
+              typeSelectorRef={typeSelectorRef}
+              triggerRef={triggerRef}
+              searchValue={searchValue}
+              onSearchChange={setSearchValue}
+              ctx={context}
+              {...props}
+            />
+          </CascaderContainer>
+        )}
+      </>
+    );
+  }
+
+  return (
+    <>
+      <Popover
+        rePosKey={rePosKey}
+        visible={visible}
+        onVisibleChange={(v) => {
+          setVisible(v);
+          if (onDropdownVisibleChange) {
+            onDropdownVisibleChange(v);
+          }
+          if (v) {
+            setReposKey((key) => key + 1);
+          }
+        }}
+        autoAdjustOverflow
+        trigger="click"
+        position="bottomLeft"
+        getPopupContainer={getPopupContainer}
+        content={
+          !searchValue ? (
+            <TypeCascader
+              ref={typeSelectorRef}
+              onContextChange={setContext}
+              onRePos={() => setReposKey((pre) => pre + 1)}
+              {...props}
+            />
+          ) : (
+            <TypeSearchPanel
+              onSearchChange={setSearchValue}
+              ref={typeSelectorRef}
+              triggerRef={triggerRef}
+              query={searchValue}
+              {...props}
+            />
+          )
+        }
+      >
+        {triggerRender ? (
+          <CustomCascaderContainer>{triggerRender()}</CustomCascaderContainer>
+        ) : (
+          <CascaderContainer>
+            <Trigger
+              triggerRef={triggerRef}
+              searchValue={searchValue}
+              onSearchChange={setSearchValue}
+              typeSelectorRef={typeSelectorRef}
+              ctx={context}
+              {...props}
+            />
+          </CascaderContainer>
+        )}
+      </Popover>
+    </>
+  );
+};

+ 140 - 0
packages/materials/type-editor/src/components/type-selector/cascader-v2/style.ts

@@ -0,0 +1,140 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import styled, { createGlobalStyle } from 'styled-components';
+import { Typography } from '@douyinfe/semi-ui';
+
+export const StyledFullContainer = styled.div`
+  width: 100%;
+  height: 100%;
+`;
+
+export const CustomCascaderContainer = styled.span`
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+`;
+
+export const CascaderContainer = styled(StyledFullContainer)`
+  position: relative;
+`;
+
+export const TriggerText = styled.div`
+  display: flex;
+  width: 100%;
+`;
+
+export const CascaderDropdown = styled.div`
+  display: flex;
+  width: 100%;
+  height: 100%;
+`;
+
+export const TriggerGlobalStyle = createGlobalStyle`
+  .semi-cascader-selection {
+    font-size: 12px !important;
+
+    svg {
+      width: 12px;
+      height: 12px;
+    }
+  }
+`;
+
+export const CascaderOptionItem = styled.li<{ focus?: boolean }>`
+  ${(props) => (props.focus ? 'background-color: var(--semi-color-fill-0)' : '')}
+`;
+
+export const DropdownGlobalStyle = createGlobalStyle`
+  .semi-cascader-option-lists {
+    max-width: 510px;
+    overflow-x: auto;
+    height: auto;
+
+
+    .semi-cascader-option-list {
+    width: 150px;
+    flex-shrink: 0;
+    border-left: none;
+    border-right: 1px solid var(--semi-color-fill-0);
+
+    max-height: 50vh;
+
+    ::-webkit-scrollbar {
+      width: 0;
+      height: 0;
+    }
+
+    .semi-cascader-option {
+      font-size: 12px !important;
+      width: 100%;
+      padding: 6px 12px;
+      cursor: pointer;
+      box-sizing: border-box;
+
+      svg {
+        width: 12px;
+        height: 12px;
+      }
+    }
+  }
+
+
+    .semi-cascader-option-disabled {
+      cursor: not-allowed;
+    }
+
+  }
+
+  .semi-cascader-option-icon {
+    margin-right:8px;
+  }
+
+  .semi-cascader-option-icon-empty {
+    margin-right: 0;
+  }
+
+`;
+
+export const StyledSearchList = styled.ul`
+  &::-webkit-scrollbar {
+    display: none;
+  }
+  width: 100%;
+  height: 100%;
+`;
+
+export const TypeSearchText = styled(Typography.Text)`
+  padding: 8px 12px;
+`;
+
+export const TextGlobalStyle = createGlobalStyle`
+  .semi-typography {
+    color: unset !important;
+  }
+
+`;
+export const SearchText = styled.span<{
+  disabled?: boolean;
+}>`
+  display: inline-flex;
+  align-items: center;
+  width: 100%;
+  gap: 8px;
+  cursor: ${(props) => (props.disabled ? 'not-allowed' : 'pointer')};
+  ${(props) => (props.disabled ? 'color: var(--semi-color-disabled-text);' : '')};
+`;
+
+export const SearchIcon = styled.span`
+  display: inline-flex;
+  align-items: center;
+
+  svg {
+    width: 12px;
+    height: 12px;
+  }
+`;

+ 109 - 0
packages/materials/type-editor/src/components/type-selector/cascader-v2/trigger.tsx

@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useMemo, useState } from 'react';
+
+import classNames from 'classnames';
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Input } from '@douyinfe/semi-ui';
+import { IconChevronDown } from '@douyinfe/semi-icons';
+
+import { TypeSelectorRef, type Props } from '../type';
+import { useTypeSelectorHotKey } from '../hooks/hot-key';
+import { FlowSchemaInitCtx } from '../../../types';
+import { useTypeDefinitionManager } from '../../../contexts';
+import { TriggerGlobalStyle, StyledFullContainer, TriggerText } from './style';
+
+export const Trigger = ({
+  value: originValue,
+  ctx,
+  typeSelectorRef,
+  triggerRef,
+  searchValue,
+  onSearchChange,
+}: Props<IJsonSchema> & {
+  ctx: FlowSchemaInitCtx;
+  searchValue: string;
+  triggerRef: React.RefObject<HTMLDivElement | null>;
+  typeSelectorRef: React.MutableRefObject<TypeSelectorRef | null>;
+  onSearchChange: (query: string) => void;
+}) => {
+  const typeService = useTypeDefinitionManager();
+
+  const value = useMemo(() => {
+    if (!originValue) {
+      return;
+    }
+    const type = typeService.getTypeBySchema(originValue);
+
+    return type?.getStringValueByTypeSchema?.(originValue) || '';
+  }, [originValue]);
+
+  const [focus, setFocus] = useState(false);
+
+  const hotkeys = useTypeSelectorHotKey(typeSelectorRef);
+
+  const reverseLabel = useMemo(() => {
+    if (!value || !originValue) {
+      return;
+    }
+
+    const def = typeService.getTypeBySchema(originValue);
+
+    return def ? def.getDisplayLabel(originValue) : <>{value}</>;
+  }, [originValue, value]);
+
+  return (
+    <StyledFullContainer
+      ref={triggerRef as React.RefObject<HTMLDivElement>}
+      className={classNames(
+        'semi-cascader semi-cascader-focus semi-cascader-single semi-cascader-filterable'
+      )}
+    >
+      <TriggerGlobalStyle />
+      <div className="semi-cascader-selection">
+        <div className="semi-cascader-search-wrapper">
+          {!searchValue && (
+            <TriggerText
+              style={{
+                opacity: focus ? 0.5 : 1,
+              }}
+              className={classNames('semi-cascader-selection-text-inactive')}
+            >
+              <>{reverseLabel}</>
+            </TriggerText>
+          )}
+
+          <div className="semi-input-wrapper semi-input-wrapper-focus semi-input-wrapper-default">
+            <Input
+              autoFocus
+              onFocus={() => {
+                setFocus(true);
+              }}
+              onKeyDown={(e) => {
+                const hotKey = hotkeys.find((item) => item.matcher(e));
+                hotKey?.callback();
+                if (hotKey?.preventDefault) {
+                  e.preventDefault();
+                }
+              }}
+              value={searchValue}
+              onChange={(newVal) => {
+                onSearchChange(newVal);
+              }}
+              className="semi-cascader-input"
+              onBlur={() => {
+                setFocus(false);
+              }}
+            />
+          </div>
+        </div>
+      </div>
+      <div className="semi-cascader-arrow">
+        <IconChevronDown />
+      </div>
+    </StyledFullContainer>
+  );
+};

+ 325 - 0
packages/materials/type-editor/src/components/type-selector/cascader-v2/type-cascader.tsx

@@ -0,0 +1,325 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { type FC, forwardRef, useCallback, useMemo, useState } from 'react';
+
+import classNames from 'classnames';
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Tooltip } from '@douyinfe/semi-ui';
+import { IconCheckboxTick, IconChevronRight } from '@douyinfe/semi-icons';
+
+import { typeSelectorUtils } from '../utils';
+import { TypeSelectorRef, type CascaderOption, type Props } from '../type';
+import { useTypeTransform } from '../hooks/option-value';
+import { useFocusItemCascader } from '../hooks/focus-item';
+import { useCascaderRootTypes } from '../hooks';
+import { FlowSchemaInitCtx, TypeEditorRegistry } from '../../../types';
+import { useTypeDefinitionManager } from '../../../contexts';
+import { CascaderDropdown, CascaderOptionItem, DropdownGlobalStyle } from './style';
+interface OptionListProps {
+  options: Array<CascaderOption>;
+
+  /**
+   * 蓝色背景,用于父类型展开/收起
+   */
+  activeType?: string;
+  /**
+   * 蓝色字体,用于表示选中的值
+   */
+  selectValue?: string;
+  /**
+   * 灰色背景,当前 focus 的类型
+   */
+  focusValue?: string;
+  onCollapse?: (val: string) => void;
+  onSelect?: (
+    value: string,
+    source?: Parameters<Required<Props<IJsonSchema>>['onChange']>[1]['source']
+  ) => void;
+}
+
+const OptionList: FC<OptionListProps> = ({
+  options,
+  activeType,
+  selectValue,
+  onCollapse,
+  onSelect,
+  focusValue,
+}) => (
+  <ul className="semi-cascader-option-list">
+    {options.map((opt) => {
+      const child = (
+        <CascaderOptionItem
+          focus={focusValue === opt.value}
+          onClick={
+            !opt.disabled
+              ? () =>
+                  opt.isLeaf
+                    ? onSelect?.(
+                        opt.value,
+                        opt.source as Parameters<
+                          Required<Props<IJsonSchema>>['onChange']
+                        >[1]['source']
+                      )
+                    : onCollapse?.(opt.type)
+              : undefined
+          }
+          key={opt.value}
+          className={classNames(
+            'semi-cascader-option',
+            activeType === opt.type && 'semi-cascader-option-active',
+            selectValue === opt.value && 'semi-cascader-option-select',
+
+            opt.disabled && 'semi-cascader-option-disabled'
+          )}
+        >
+          <span className="semi-cascader-option-label">
+            {selectValue === opt.value ? (
+              <IconCheckboxTick
+                className={classNames('semi-cascader-option-icon')}
+                style={{
+                  color: selectValue === opt.value ? 'var(--semi-color-primary)' : undefined,
+                }}
+              />
+            ) : (
+              <span className="semi-cascader-option-icon semi-cascader-option-icon-empty" />
+            )}
+            {typeof opt.label === 'string' ? <span>{opt.label}</span> : opt.label}
+          </span>
+          {!opt.isLeaf && <IconChevronRight />}
+        </CascaderOptionItem>
+      );
+
+      return opt.disabled ? <Tooltip content={opt.disabled}>{child}</Tooltip> : child;
+    })}
+  </ul>
+);
+
+const useGenerateCascaderTypes = () => {
+  const typeService = useTypeDefinitionManager();
+
+  const generateCascaderTypes = useCallback(
+    (value?: string) => {
+      const res: string[] = [];
+
+      const types = value?.split('-') || [];
+      const arr = types.splice(0, types.length - 1) || [];
+
+      while (arr.length > 0) {
+        const type = arr.shift();
+        if (type) {
+          const config = type ? typeService.getTypeByName(type) : undefined;
+
+          if (config?.customChildOptionValue) {
+            const extras = config.customChildOptionValue();
+            arr.splice(0, extras.length);
+          }
+          res.push(type);
+        }
+      }
+
+      return res;
+    },
+    [typeService]
+  );
+
+  return generateCascaderTypes;
+};
+
+const generateCustomPanelType = (value?: string) => (value?.split('-') || []).pop() || '';
+
+// eslint-disable-next-line react/display-name
+export const TypeCascader = forwardRef<
+  TypeSelectorRef,
+  Props<IJsonSchema> & {
+    onContextChange: (ctx: FlowSchemaInitCtx) => void;
+    onRePos: () => void;
+  }
+>(({ value: originValue, onChange, onContextChange, onRePos, disableTypes = [] }, ref) => {
+  const typeService = useTypeDefinitionManager();
+
+  const {
+    convertOptionValueToModeValue,
+    convertValueToOptionValue,
+
+    getModeOptionChildrenType,
+  } = useTypeTransform();
+
+  const customDisableType = useMemo(() => {
+    const map = new Map<string, string>();
+
+    disableTypes.forEach((v) => {
+      map.set(v.type, v.reason);
+    });
+    return map;
+  }, [disableTypes]);
+
+  const rootTypes = useCascaderRootTypes(customDisableType);
+
+  const generateCascaderTypes = useGenerateCascaderTypes();
+
+  const value = useMemo(() => convertValueToOptionValue(originValue), [originValue]);
+
+  const [cascaderTypes, setCascaderLTypes] = useState<string[]>(generateCascaderTypes(value));
+
+  const [customPanelType, setCustomPanelType] = useState(generateCustomPanelType(value));
+
+  const handleChange = useCallback(
+    (
+      newOptionValue: string,
+      source: Parameters<Required<Props<IJsonSchema>>['onChange']>[1]['source'] = 'type-selector'
+    ) => {
+      if (newOptionValue === value) {
+        setCascaderLTypes(generateCascaderTypes(newOptionValue));
+        setCustomPanelType(generateCustomPanelType(newOptionValue));
+        onRePos();
+
+        return;
+      }
+      const newValue = convertOptionValueToModeValue(newOptionValue);
+
+      if (onChange) {
+        onChange(newValue as IJsonSchema, {
+          source,
+        });
+      }
+
+      setCascaderLTypes(generateCascaderTypes(newOptionValue));
+
+      setCustomPanelType(generateCustomPanelType(newOptionValue));
+
+      const newCtx =
+        (newValue &&
+          typeService.getTypeBySchema(newValue)?.typeCascaderConfig?.generateInitCtx?.(newValue)) ||
+        {};
+
+      onContextChange(newCtx);
+    },
+    [onChange, onContextChange, value]
+  );
+
+  const renderData = useMemo(
+    () =>
+      cascaderTypes.map((item, level) => {
+        const parentDef = typeService.getTypeByName(item);
+
+        const childrenType = getModeOptionChildrenType(
+          parentDef as TypeEditorRegistry<IJsonSchema>,
+          {
+            parentType: item,
+            level: level + 1,
+            parentTypes: [...cascaderTypes].splice(0, level),
+          }
+        );
+
+        const prefix = [...cascaderTypes]
+          .splice(0, level + 1)
+          .map((type) => {
+            const config = typeService.getTypeByName(type);
+            if (config?.customChildOptionValue) {
+              return [type, config.customChildOptionValue()];
+            }
+            return type;
+          })
+          .flat()
+          .join('-');
+
+        const options: CascaderOption[] = childrenType
+          .map((child) => {
+            const def = typeService.getTypeByName(child.type);
+            if (def) {
+              return typeSelectorUtils.definitionToCascaderOption({
+                config: def,
+                customDisableType,
+                prefix,
+                // parentConfig: parentDef,
+                level: level + 1,
+                disabled: child.disabled,
+                parentType: item,
+                parentTypes: [...cascaderTypes].splice(0, level + 1),
+              });
+            }
+          })
+          .filter(Boolean) as CascaderOption[];
+
+        return {
+          item,
+          options,
+        };
+      }),
+    [cascaderTypes, customDisableType]
+  );
+
+  const handleCollapse = useCallback(
+    (type: string, level: number) => {
+      const newCascaderTypes = [...cascaderTypes];
+
+      if (cascaderTypes[level] !== type) {
+        newCascaderTypes.splice(level);
+        newCascaderTypes.push(type);
+      } else {
+        newCascaderTypes.splice(level);
+      }
+      setCustomPanelType('');
+      onRePos();
+      setCascaderLTypes(newCascaderTypes);
+    },
+    [cascaderTypes]
+  );
+
+  const { focusValue } = useFocusItemCascader({
+    rootTypes,
+    onCollapse: handleCollapse,
+    renderData,
+    cascaderTypes,
+    onChange: (v) => handleChange(v, 'type-selector'),
+    ref,
+  });
+
+  return (
+    <CascaderDropdown>
+      <DropdownGlobalStyle />
+      <div className="semi-cascader-option-lists">
+        <OptionList
+          activeType={cascaderTypes[0]}
+          selectValue={value}
+          options={rootTypes}
+          focusValue={focusValue}
+          onSelect={handleChange}
+          onCollapse={(type) => handleCollapse(type, 0)}
+        />
+
+        {renderData.map(({ item, options }, level) => (
+          <OptionList
+            onSelect={handleChange}
+            key={item + level}
+            focusValue={focusValue}
+            activeType={cascaderTypes[level + 1]}
+            selectValue={value}
+            options={options}
+            onCollapse={(type) => handleCollapse(type, level + 1)}
+          />
+        ))}
+
+        {(originValue &&
+          typeService.getTypeByName(customPanelType)?.typeCascaderConfig?.customCascaderPanel?.({
+            typeSchema: originValue,
+            onChange: ((newVal: IJsonSchema) => {
+              onChange?.(newVal, {
+                source: 'custom-panel',
+              });
+              const newCtx =
+                typeService
+                  .getTypeBySchema(newVal)
+                  ?.typeCascaderConfig?.generateInitCtx?.(newVal) || {};
+
+              onContextChange(newCtx);
+            }) as (typeSchema: Partial<IJsonSchema>) => void,
+          })) ||
+          null}
+      </div>
+    </CascaderDropdown>
+  );
+});

+ 278 - 0
packages/materials/type-editor/src/components/type-selector/cascader-v2/type-search.tsx

@@ -0,0 +1,278 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, {
+  type FC,
+  forwardRef,
+  useCallback,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from 'react';
+
+import classNames from 'classnames';
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Typography } from '@douyinfe/semi-ui';
+
+import { type SearchResultItem, type Props, TypeSelectorRef } from '../type';
+import { useHighlightKeyword } from '../hooks/use-hight-light';
+import { useTypeTransform } from '../hooks/option-value';
+import { useFocusItemSearch } from '../hooks/focus-item';
+import { TypeEditorRegistry } from '../../../types';
+import { useTypeDefinitionManager } from '../../../contexts';
+import { SearchIcon, SearchText, StyledSearchList, TextGlobalStyle, TypeSearchText } from './style';
+
+const defaultDeep = 2;
+
+interface CacheItem {
+  /** array-array-string **/
+  key: string;
+  type: string;
+  disabled?: string;
+}
+
+const SearchItem: FC<{
+  item: SearchResultItem;
+  query: string;
+  focusValue: string;
+  selectValue: string;
+  onSelect: (item: SearchResultItem, value: IJsonSchema) => void;
+}> = ({ item, query, focusValue, onSelect, selectValue }) => {
+  const typeService = useTypeDefinitionManager();
+
+  const { convertOptionValueToModeValue } = useTypeTransform();
+
+  const config = typeService.getTypeByName(item.type);
+
+  const typeSchema = useMemo(
+    () => convertOptionValueToModeValue(item.value) as IJsonSchema,
+    [item.value]
+  );
+
+  const text = useMemo(() => config?.getDisplayText(typeSchema), [typeSchema, config]);
+
+  const label = useHighlightKeyword(
+    text || '',
+    query,
+    {
+      color: 'var(--semi-color-primary)',
+      fontWeight: 600,
+    },
+    item.value
+  );
+
+  return (
+    <li
+      key={item.value}
+      onClick={() => (item.disabled ? undefined : onSelect(item, typeSchema))}
+      style={{
+        minWidth: 'unset',
+        background: focusValue === item.value ? 'var(--semi-color-fill-0)' : undefined,
+      }}
+      className={classNames(
+        'semi-cascader-option',
+        item.disabled && 'semi-cascader-option-disabled',
+        selectValue === item.value && 'semi-cascader-option-select'
+      )}
+    >
+      <TextGlobalStyle />
+      <SearchText disabled={!!item.disabled}>
+        <SearchIcon>{config?.getDisplayIcon?.(typeSchema)}</SearchIcon>
+        <Typography.Text ellipsis={{ showTooltip: true }}>
+          <span>{label}</span>
+        </Typography.Text>
+      </SearchText>
+    </li>
+  );
+};
+// eslint-disable-next-line react/display-name
+export const TypeSearchPanel = forwardRef<
+  TypeSelectorRef,
+  Props<IJsonSchema> & {
+    query: string;
+    triggerRef: React.RefObject<HTMLDivElement | null>;
+    onSearchChange: (query: string) => void;
+  }
+>(({ query, disableTypes = [], onChange, triggerRef, value, onSearchChange }, ref) => {
+  const typeService = useTypeDefinitionManager();
+  const {
+    convertValueToOptionValue,
+    checkHasChildren,
+    convertOptionValueToModeValue,
+    getModeOptionChildrenType,
+  } = useTypeTransform();
+
+  const customDisableType = useMemo(() => {
+    const map = new Map<string, string>();
+
+    disableTypes.forEach((v) => {
+      map.set(v.type, v.reason);
+    });
+    return map;
+  }, [disableTypes]);
+
+  const selectValue = useMemo(() => convertValueToOptionValue(value), [value]);
+
+  const [searchResult, setSearchResult] = useState<SearchResultItem[]>([]);
+
+  const levelCache = useRef<CacheItem[][]>([]);
+
+  const handleGenLevelInfo = useCallback(
+    (level: number) => {
+      for (let i = 0; i < level; i++) {
+        if (levelCache.current[i]) {
+          continue;
+        }
+
+        if (i === 0) {
+          const newRootTypes = typeService.getTypeRegistriesWithParentType();
+          levelCache.current[i] = newRootTypes.map((type) => ({
+            key: type.type,
+            type: type.type,
+            parentLabels: [],
+            disabled: customDisableType.get(type.type),
+          }));
+        } else {
+          const lastLevelCache = levelCache.current[i - 1];
+          const newCacheTypes: CacheItem[] = [];
+          levelCache.current[i] = newCacheTypes;
+          lastLevelCache.forEach((type) => {
+            // disabled 的就不用下钻了
+            if (type.disabled) {
+              return;
+            }
+            const config = typeService.getTypeByName(type.type);
+
+            const parentTypes = type.key.split('-');
+            if (
+              config &&
+              checkHasChildren(config as TypeEditorRegistry<IJsonSchema>, {
+                level: i,
+              })
+            ) {
+              const childrenTypes = getModeOptionChildrenType(
+                config as TypeEditorRegistry<IJsonSchema>,
+                {
+                  parentType: type.type,
+                  level: i,
+                  parentTypes,
+                }
+              );
+
+              childrenTypes.forEach((child) => {
+                const childConfig = typeService.getTypeByName(child.type);
+
+                newCacheTypes.push({
+                  type: child.type,
+                  key: [...parentTypes, child.type].join('-'),
+                  disabled:
+                    child.disabled ||
+                    (childConfig?.customDisabled
+                      ? childConfig.customDisabled({
+                          level: i + 1,
+                          parentType: type.type,
+                          parentTypes,
+                        })
+                      : undefined),
+                });
+              });
+            }
+          });
+        }
+      }
+    },
+    [customDisableType]
+  );
+
+  const handleGenSearchResult = useCallback(() => {
+    const len = levelCache.current.length;
+    if (!query) {
+      setSearchResult([]);
+      return;
+    }
+
+    const newSearchResult: SearchResultItem[] = [];
+    for (let i = 0; i < len; i++) {
+      const cacheTypes = levelCache.current[i] || [];
+
+      cacheTypes.forEach((type) => {
+        const config = typeService.getTypeByName(type.type);
+
+        if (config && config.label.toLocaleLowerCase().indexOf(query.toLocaleLowerCase()) > -1) {
+          newSearchResult.push({
+            value: type.key,
+            icon: config.icon,
+            disabled: type.disabled,
+            level: i,
+            type: type.type,
+          });
+        }
+      });
+    }
+
+    setSearchResult(newSearchResult);
+  }, [query]);
+
+  useEffect(() => {
+    handleGenLevelInfo(defaultDeep);
+  }, [handleGenLevelInfo]);
+
+  useEffect(() => {
+    handleGenSearchResult();
+  }, [query]);
+
+  const viewValue = useMemo(
+    () =>
+      searchResult.filter((item) => {
+        const config = typeService.getTypeByName(item.type);
+        return (
+          config &&
+          !checkHasChildren(config as TypeEditorRegistry<IJsonSchema>, {
+            level: item.level,
+          })
+        );
+      }),
+    [searchResult, typeService]
+  );
+
+  const { focusValue } = useFocusItemSearch({
+    ref,
+    viewValue,
+    onChange: (v) => {
+      const newVal = convertOptionValueToModeValue(v);
+      onChange?.(newVal as IJsonSchema, { source: 'custom-panel' });
+      onSearchChange('');
+    },
+  });
+
+  return (
+    <div
+      className={classNames('semi-cascader-option-lists')}
+      style={triggerRef.current ? { width: Math.max(triggerRef.current.clientWidth, 150) } : {}}
+    >
+      <StyledSearchList className={classNames('semi-cascader-option-list')}>
+        <>
+          {viewValue.length === 0 ? (
+            <TypeSearchText type="secondary">No results.</TypeSearchText>
+          ) : (
+            <>
+              {viewValue.map((item) => (
+                <SearchItem
+                  selectValue={selectValue}
+                  key={item.value}
+                  query={query}
+                  focusValue={focusValue}
+                  item={item}
+                  onSelect={(_, v) => onChange?.(v, { source: 'type-selector' })}
+                />
+              ))}
+            </>
+          )}
+        </>
+      </StyledSearchList>
+    </div>
+  );
+});

+ 238 - 0
packages/materials/type-editor/src/components/type-selector/hooks/focus-item.ts

@@ -0,0 +1,238 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { useCallback, useImperativeHandle, useMemo, useState } from 'react';
+
+import { noop } from 'lodash';
+
+import { TypeSelectorRef, type CascaderOption, type SearchResultItem } from '../type';
+
+interface Pos {
+  x: number;
+  y: number;
+}
+
+const validatePos = (pos: Pos): boolean => pos.x >= 0 && pos.y >= 0;
+
+export const useFocusItemCascader = ({
+  rootTypes,
+  renderData,
+  cascaderTypes,
+  ref,
+  onCollapse,
+  onChange,
+}: {
+  rootTypes: CascaderOption[];
+  cascaderTypes: string[];
+  onChange: (newVal: string) => void;
+  renderData: { item: string; options: CascaderOption[] }[];
+  ref: React.ForwardedRef<TypeSelectorRef>;
+  onCollapse: (type: string, level: number) => void;
+}) => {
+  const [focusPos, setFocusPos] = useState<Pos>({ x: -1, y: -1 });
+
+  const allOptions = useMemo(
+    () => [rootTypes, ...renderData.map((item) => item.options)],
+    [rootTypes, renderData]
+  );
+
+  const initPos = useCallback(() => {
+    setFocusPos({
+      x: 0,
+      y: 0,
+    });
+  }, []);
+
+  const focusItem = useMemo(() => allOptions[focusPos.x]?.[focusPos.y], [focusPos, allOptions]);
+
+  const focusValue = useMemo(
+    () => allOptions[focusPos.x]?.[focusPos.y]?.value,
+    [focusPos, allOptions]
+  );
+
+  useImperativeHandle(
+    ref,
+    (): TypeSelectorRef => ({
+      initFocusItem() {
+        initPos();
+      },
+      clearFocusItem() {
+        setFocusPos({
+          x: -1,
+          y: -1,
+        });
+      },
+      moveFocusItemUp() {
+        if (!validatePos(focusPos)) {
+          initPos();
+          return;
+        }
+        const { x, y } = focusPos;
+
+        if (!allOptions[x]?.[y]) {
+          return;
+        }
+
+        // 向上查找,直到找到一个 disabled not 的 item
+        let newY = (y - 1 + allOptions[x].length) % allOptions[x].length;
+        let item = allOptions[x]?.[newY];
+
+        while (item?.disabled && newY !== y) {
+          newY = (newY - 1 + allOptions[x].length) % allOptions[x].length;
+          item = allOptions[x]?.[newY];
+        }
+
+        if (newY !== y) {
+          setFocusPos({
+            x,
+            y: newY,
+          });
+        }
+      },
+      moveFocusItemDown() {
+        if (!validatePos(focusPos)) {
+          initPos();
+          return;
+        }
+
+        const { x, y } = focusPos;
+
+        if (!allOptions[x]?.[y]) {
+          return;
+        }
+
+        // 向上查找,直到找到一个 disabled not 的 item
+        let newY = (y + 1) % allOptions[x].length;
+        let item = allOptions[x]?.[newY];
+
+        while (item?.disabled && newY !== y) {
+          newY = (newY + 1) % allOptions[x].length;
+          item = allOptions[x]?.[newY];
+        }
+
+        if (newY !== y) {
+          setFocusPos({
+            x,
+            y: newY,
+          });
+        }
+      },
+      moveFocusItemLeft() {
+        if (!validatePos(focusPos)) {
+          initPos();
+          return;
+        }
+
+        const childrenPanelLen = cascaderTypes.length;
+
+        if (childrenPanelLen > 0) {
+          const lastParentType = cascaderTypes[childrenPanelLen - 1];
+
+          const newY = allOptions[childrenPanelLen - 1]?.findIndex(
+            (v) => v.type === lastParentType
+          );
+
+          onCollapse(lastParentType, childrenPanelLen - 1);
+
+          setFocusPos({
+            x: focusPos.x - 1,
+            y: newY || 0,
+          });
+        }
+      },
+      moveFocusItemRight() {
+        if (!validatePos(focusPos)) {
+          initPos();
+          return;
+        }
+
+        if (focusItem && !focusItem.isLeaf && cascaderTypes[focusPos.x] !== focusItem.type) {
+          onCollapse(focusItem.type, focusPos.x);
+          setFocusPos({
+            x: focusPos.x + 1,
+            y: 0,
+          });
+        }
+      },
+
+      selectFocusItem() {
+        if (!validatePos(focusPos)) {
+          return;
+        }
+        if (focusItem?.isLeaf) {
+          onChange(focusValue);
+        }
+      },
+    })
+  );
+
+  return {
+    focusValue,
+  };
+};
+export const useFocusItemSearch = ({
+  ref,
+  onChange,
+  viewValue,
+}: {
+  viewValue: SearchResultItem[];
+  onChange: (newVal: string) => void;
+
+  ref: React.ForwardedRef<TypeSelectorRef>;
+}) => {
+  const [focusPos, setFocusPos] = useState(-1);
+
+  const focusValue = useMemo(() => viewValue[focusPos]?.value, [viewValue, focusPos]);
+
+  const focusItem = useMemo(() => viewValue[focusPos], [viewValue, focusPos]);
+
+  useImperativeHandle(ref, () => ({
+    selectFocusItem() {
+      if (!focusItem.disabled) {
+        onChange(focusValue);
+      }
+    },
+    moveFocusItemDown() {
+      if (focusPos < 0) {
+        setFocusPos(0);
+        return;
+      }
+
+      let newPos = (focusPos + 1) % viewValue.length;
+
+      while (viewValue[newPos]?.disabled && newPos !== focusPos) {
+        newPos = (newPos + 1) % viewValue.length;
+      }
+
+      setFocusPos(newPos);
+    },
+    moveFocusItemLeft: noop,
+    moveFocusItemRight: noop,
+    moveFocusItemUp() {
+      if (focusPos < 0) {
+        setFocusPos(0);
+        return;
+      }
+
+      let newPos = (focusPos - 1 + viewValue.length) % viewValue.length;
+
+      while (viewValue[newPos]?.disabled && newPos !== focusPos) {
+        newPos = (newPos - 1 + viewValue.length) % viewValue.length;
+      }
+
+      setFocusPos(newPos);
+    },
+    initFocusItem() {
+      setFocusPos(0);
+    },
+    clearFocusItem() {
+      setFocusPos(-1);
+    },
+  }));
+
+  return {
+    focusValue,
+  };
+};

+ 60 - 0
packages/materials/type-editor/src/components/type-selector/hooks/hot-key.ts

@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useMemo } from 'react';
+
+import { TypeSelectorRef } from '../type';
+
+interface HotKeyConfig {
+  matcher: (event: React.KeyboardEvent) => boolean;
+  callback: () => void;
+  preventDefault?: boolean;
+}
+
+export const useTypeSelectorHotKey = (selector: React.MutableRefObject<TypeSelectorRef | null>) => {
+  const hotKeyConfig: HotKeyConfig[] = useMemo(() => {
+    const res: HotKeyConfig[] = [
+      {
+        matcher: (e) => e.key === 'Enter',
+        callback: () => {
+          selector.current?.selectFocusItem();
+        },
+      },
+      {
+        matcher: (e) => e.key === 'ArrowUp',
+        callback: () => {
+          selector.current?.moveFocusItemUp();
+        },
+        preventDefault: true,
+      },
+
+      {
+        matcher: (e) => e.key === 'ArrowLeft',
+        callback: () => {
+          selector.current?.moveFocusItemLeft();
+        },
+        preventDefault: true,
+      },
+      {
+        matcher: (e) => e.key === 'ArrowRight',
+        callback: () => {
+          selector.current?.moveFocusItemRight();
+        },
+        preventDefault: true,
+      },
+      {
+        matcher: (e) => e.key === 'ArrowDown',
+        callback: () => {
+          selector.current?.moveFocusItemDown();
+        },
+        preventDefault: true,
+      },
+    ];
+
+    return res;
+  }, []);
+
+  return hotKeyConfig;
+};

+ 6 - 0
packages/materials/type-editor/src/components/type-selector/hooks/index.ts

@@ -0,0 +1,6 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+export { useCascaderRootTypes } from './root-types';

+ 98 - 0
packages/materials/type-editor/src/components/type-selector/hooks/option-value.ts

@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { useMemo } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { TypeEditorRegistry } from '../../../types';
+import { useTypeDefinitionManager } from '../../../contexts';
+
+export const useTypeTransform = () => {
+  const typeService = useTypeDefinitionManager();
+
+  /**
+   * 根据提交值获取选项值
+   * 正常和 OptionValue 一致,不用特殊转化
+   * 但是在 IJsonSchema 的场景,这两个值会不同
+   * 级联场景也会不一样,因为 级联场景会 type-type
+   */
+  const convertValueToOptionValue = useMemo(
+    () => (originValue: IJsonSchema | undefined) => {
+      const type = originValue && typeService.getTypeBySchema(originValue);
+
+      return (originValue && type?.getStringValueByTypeSchema?.(originValue)) || '';
+    },
+    [typeService]
+  );
+
+  /**
+   * 根据选项值获取提交值
+   * 正常和 OptionValue 一致,不用特殊转化
+   * 但是在 IJsonSchema 的场景,这两个值会不同
+   * 级联场景也会不一样,因为 级联场景会 type-type
+   */
+  const convertOptionValueToModeValue = useMemo(
+    () => (optionValue: string | undefined) => {
+      const [root, ...rest] = (optionValue || '').split('-');
+
+      const rooType = typeService.getTypeByName(root);
+
+      if (rooType?.getTypeSchemaByStringValue) {
+        return rooType.getTypeSchemaByStringValue(rest.join('-'));
+      }
+
+      return rooType?.getDefaultSchema();
+    },
+    [typeService]
+  );
+
+  /**
+   * 判断是否有子类型
+   */
+  const checkHasChildren = useMemo(
+    () =>
+      (
+        typeDef: TypeEditorRegistry<IJsonSchema>,
+        ctx: {
+          level: number;
+        }
+      ): boolean =>
+        (typeDef?.getSupportedItemTypes && typeDef.getSupportedItemTypes(ctx).length !== 0) ||
+        !!typeDef.container,
+    [typeService]
+  );
+
+  const getModeOptionChildrenType = useMemo(
+    () =>
+      (
+        typeDef: TypeEditorRegistry<IJsonSchema> | undefined,
+        ctx: {
+          parentType: string;
+          level: number;
+          parentTypes?: string[];
+        }
+      ) => {
+        const getSupportType = (parentType = ''): TypeEditorRegistry<IJsonSchema>[] =>
+          typeService.getTypeRegistriesWithParentType(
+            parentType
+          ) as TypeEditorRegistry<IJsonSchema>[];
+
+        const support = new Set(getSupportType(ctx.parentType).map((v) => v.type));
+        return (
+          (typeDef?.getSupportedItemTypes && typeDef.getSupportedItemTypes(ctx)) ||
+          []
+        ).filter((v) => support.has(v.type));
+      },
+    [typeService]
+  );
+
+  return {
+    convertOptionValueToModeValue,
+    convertValueToOptionValue,
+    checkHasChildren,
+    getModeOptionChildrenType,
+  };
+};

+ 43 - 0
packages/materials/type-editor/src/components/type-selector/hooks/root-types.ts

@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { useEffect, useState } from 'react';
+
+import { typeSelectorUtils } from '../utils';
+import { type CascaderOption } from '../type';
+import { useTypeDefinitionManager } from '../../../contexts';
+
+const genId = (options: CascaderOption[]): string =>
+  options.map((v) => `${v.disabled}${v.label}`).join('-');
+
+export const useCascaderRootTypes = (customDisableType: Map<string, string>) => {
+  const [rootTypes, setRootTypes] = useState<CascaderOption[]>([]);
+  const typeService = useTypeDefinitionManager();
+
+  useEffect(() => {
+    const init = () => {
+      const newRootTypes = typeService.getTypeRegistriesWithParentType().map((config) => {
+        const res = typeSelectorUtils.definitionToCascaderOption({
+          customDisableType,
+          config,
+          level: 0,
+          parentTypes: [],
+        });
+        return res;
+      });
+
+      if (genId(newRootTypes) !== genId(rootTypes)) {
+        setRootTypes(newRootTypes);
+      }
+    };
+    init();
+    const dispose = typeService.onTypeRegistryChange(init);
+    return () => {
+      dispose.dispose();
+    };
+  }, [rootTypes, customDisableType]);
+
+  return rootTypes;
+};

+ 35 - 0
packages/materials/type-editor/src/components/type-selector/hooks/use-hight-light.tsx

@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { type CSSProperties } from 'react';
+/**
+ * 搜索结果高亮
+ */
+export const useHighlightKeyword = (
+  label: string,
+  keyword?: string,
+  hightLightStyle: CSSProperties = {},
+  id?: string
+  // color = 'rgba(var(--semi-orange-5), 1)',
+): (string | React.JSX.Element)[] => {
+  if (label && keyword) {
+    return label.split(new RegExp(`(${keyword})`, 'gi')).map((c, i) =>
+      c.toLocaleLowerCase() === keyword.toLocaleLowerCase() ? (
+        <span
+          key={c + i + id}
+          style={{
+            color: 'rgba(var(--semi-orange-5), 1)',
+            ...hightLightStyle,
+          }}
+        >
+          {c}
+        </span>
+      ) : (
+        <span key={c + i + id}>{c}</span>
+      )
+    );
+  }
+  return [label];
+};

+ 24 - 0
packages/materials/type-editor/src/components/type-selector/index.tsx

@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useState } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { TypeEditorProvider } from '../../contexts';
+import { type Props } from './type';
+import { CascaderV2 } from './cascader-v2';
+
+export const TypeSelector = <TypeSchema extends Partial<IJsonSchema>>(props: Props<TypeSchema>) => {
+  const [init, setInit] = useState(false);
+  return (
+    <TypeEditorProvider
+      typeRegistryCreators={props.typeRegistryCreators}
+      onInit={() => setInit(true)}
+    >
+      {init ? <CascaderV2 {...(props as unknown as Props<IJsonSchema>)} /> : <></>}
+    </TypeEditorProvider>
+  );
+};

+ 97 - 0
packages/materials/type-editor/src/components/type-selector/type.ts

@@ -0,0 +1,97 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import type React from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import {
+  CascaderProps,
+  type CascaderData as OriginCascaderData,
+} from '@douyinfe/semi-ui/lib/es/cascader';
+
+import { DisableTypeInfo } from '../../types';
+import { TypeRegistryCreatorsAdapter } from '../../contexts';
+
+export interface Props<TypeSchema extends Partial<IJsonSchema>>
+  extends Omit<CascaderProps, 'value' | 'onChange' | 'triggerRender'> {
+  /**
+   *
+   */
+  value?: TypeSchema;
+
+  /**
+   * 禁用类型
+   */
+  disableTypes?: Array<DisableTypeInfo>;
+
+  onChange?: (
+    val: TypeSchema | undefined,
+    ctx: {
+      source: 'type-selector' | 'custom-panel';
+    }
+  ) => void;
+  triggerRender?: () => JSX.Element;
+  /**
+   *
+   */
+  typeRegistryCreators?: TypeRegistryCreatorsAdapter<TypeSchema>[];
+}
+
+export type CascaderData = OriginCascaderData & {
+  originType: string;
+  text: string;
+  extra: {
+    label: string;
+    icon: React.JSX.Element;
+  };
+};
+
+export interface CascaderOption {
+  label: string | JSX.Element;
+  value: string;
+  type: string;
+  disabled?: string;
+  source?: string;
+  isLeaf?: boolean;
+}
+
+export interface SearchResultItem {
+  value: string;
+  type: string;
+  icon: JSX.Element;
+  level: number;
+  disabled?: string;
+}
+
+export interface TypeSelectorRef {
+  /**
+   * 清除 item
+   */
+  clearFocusItem: () => void;
+  /**
+   * 初始化 item
+   */
+  initFocusItem: () => void;
+  /**
+   * 将当前激活的 item 向上移动
+   */
+  moveFocusItemUp: () => void;
+  /**
+   * 将当前激活的 item 向下移动
+   */
+  moveFocusItemDown: () => void;
+  /**
+   * 将当前激活的 item 向左移动,并关闭子项
+   */
+  moveFocusItemLeft: () => void;
+  /**
+   * 将当前激活的 item 向右移动,并展开子项
+   */
+  moveFocusItemRight: () => void;
+  /**
+   * 选择当前激活的 item
+   */
+  selectFocusItem: () => void;
+}

+ 67 - 0
packages/materials/type-editor/src/components/type-selector/utils/index.tsx

@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+/* eslint-disable max-params */
+
+import React from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { type CascaderOption } from '../type';
+import { TypeEditorRegistry } from '../../../types';
+
+// import s from '../index.module.less';
+
+const definitionToCascaderOption = <TypeSchema extends Partial<IJsonSchema>>({
+  config,
+  customDisableType = new Map(),
+  prefix,
+  parentTypes,
+  disabled,
+  parentType,
+  level,
+}: {
+  level: number;
+  config: TypeEditorRegistry<TypeSchema>;
+  parentType?: string;
+  customDisableType?: Map<string, string>;
+  parentTypes: string[];
+  prefix?: string;
+  disabled?: string;
+}): CascaderOption => {
+  const typeValue = config.type;
+
+  const optionValue = prefix ? [prefix, typeValue].join('-') : typeValue;
+
+  const label = (
+    <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
+      {config.icon}
+      <span>{config.label}</span>
+    </div>
+  );
+
+  const customDisabled =
+    config.customDisabled && config.customDisabled({ level, parentType: parentType!, parentTypes });
+
+  const reason = disabled || customDisableType.get(typeValue) || customDisabled;
+
+  return {
+    disabled: reason,
+    value: optionValue,
+    label,
+    type: typeValue,
+    source: config.typeCascaderConfig?.unClosePanelAfterSelect ? 'custom-panel' : 'type-selector',
+    isLeaf: reason
+      ? true
+      : !(
+          (config.getSupportedItemTypes && config.getSupportedItemTypes({ level }).length !== 0) ||
+          config.container
+        ),
+  };
+};
+
+export const typeSelectorUtils = {
+  definitionToCascaderOption,
+};

+ 112 - 0
packages/materials/type-editor/src/contexts/index.tsx

@@ -0,0 +1,112 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { createContext, useContext, useEffect, useMemo } from 'react';
+
+import { Container, interfaces } from 'inversify';
+import { IJsonSchema, JsonSchemaTypeRegistryCreator } from '@flowgram.ai/json-schema';
+
+import {
+  getTypeDefinitionAdapter,
+  ITypeDefinitionAdapter,
+  registryFormatter,
+} from '../utils/registry-adapter';
+import { TypeEditorRegistry } from '../types';
+import { defaultTypeRegistryCreators } from '../type-registry';
+import { TypeEditorRegistryManager } from '../services/type-registry-manager';
+import {
+  ClipboardService,
+  ShortcutsService,
+  TypeEditorOperationService,
+  TypeEditorService,
+} from '../services';
+
+export type TypeRegistryCreatorsAdapter<TypeSchema extends Partial<IJsonSchema>> = (
+  param: Parameters<JsonSchemaTypeRegistryCreator<TypeSchema, TypeEditorRegistry<TypeSchema>>>[0] &
+    ITypeDefinitionAdapter<TypeSchema>
+) => ReturnType<JsonSchemaTypeRegistryCreator<TypeSchema, TypeEditorRegistry<TypeSchema>>>;
+
+export const TypeEditorContext = createContext<{
+  /**
+   * @deprecated
+   */
+  typeRegistryCreators?: TypeRegistryCreatorsAdapter<IJsonSchema>[];
+}>({});
+
+interface Context {
+  container: Container;
+}
+
+const TypeContext = createContext<Context>({
+  container: new Container(),
+});
+
+export function useService<T>(identifier: interfaces.ServiceIdentifier): T {
+  const container = useContext(TypeContext).container;
+
+  return container.get(identifier) as T;
+}
+
+export const TypeEditorProvider = <TypeSchema extends Partial<IJsonSchema>>({
+  children,
+  typeRegistryCreators = [],
+  onInit,
+}: Parameters<
+  React.FunctionComponent<{
+    children: JSX.Element;
+    onInit?: () => void;
+    typeRegistryCreators?: TypeRegistryCreatorsAdapter<TypeSchema>[];
+  }>
+>[0]) => {
+  const container = useMemo(() => {
+    const res = new Container();
+
+    res.bind(TypeEditorService).toSelf().inSingletonScope();
+    res.bind(TypeEditorOperationService).toSelf().inSingletonScope();
+    res.bind(TypeEditorRegistryManager).toSelf().inSingletonScope();
+    res.bind(ShortcutsService).toSelf().inSingletonScope();
+    res.bind(ClipboardService).toSelf().inSingletonScope();
+
+    return res;
+  }, []);
+
+  useEffect(() => {
+    const typeManager =
+      container.get<TypeEditorRegistryManager<TypeSchema>>(TypeEditorRegistryManager);
+
+    [...defaultTypeRegistryCreators].forEach((creator) => {
+      typeManager.register(
+        creator as unknown as JsonSchemaTypeRegistryCreator<
+          TypeSchema,
+          TypeEditorRegistry<TypeSchema>
+        >
+      );
+    });
+  }, [container]);
+
+  useEffect(() => {
+    const typeManager =
+      container.get<TypeEditorRegistryManager<TypeSchema>>(TypeEditorRegistryManager);
+
+    const adapter = getTypeDefinitionAdapter(typeManager);
+
+    typeRegistryCreators.forEach((creator) => {
+      typeManager.register(creator({ typeManager, ...adapter }));
+    });
+
+    typeManager.getAllTypeRegistries().forEach((registry) => {
+      const res = registryFormatter(registry, typeManager);
+      typeManager.register(res);
+    });
+
+    typeManager.triggerChanges();
+    onInit?.();
+  }, [typeRegistryCreators, onInit, container]);
+
+  return <TypeContext.Provider value={{ container }}>{children}</TypeContext.Provider>;
+};
+
+export const useTypeDefinitionManager = <TypeSchema extends Partial<IJsonSchema>>() =>
+  useService<TypeEditorRegistryManager<TypeSchema>>(TypeEditorRegistryManager);

+ 13 - 0
packages/materials/type-editor/src/index.ts

@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+export * from './types';
+export { TypeEditorContext } from './contexts';
+export { columnConfigs as typeEditorColumnConfigs } from './components/type-editor/columns';
+export * from './components';
+export * from './services/type-editor-service';
+export * from './services/type-registry-manager';
+export * from '@flowgram.ai/json-schema';
+export * from './preset';

+ 6 - 0
packages/materials/type-editor/src/preset/index.tsx

@@ -0,0 +1,6 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+export { ObjectTypeEditor } from './object-type-editor';

+ 130 - 0
packages/materials/type-editor/src/preset/object-type-editor/index.tsx

@@ -0,0 +1,130 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useMemo } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { TypeEditorColumnType, TypeEditorColumnViewConfig } from '../../types';
+import { ToolbarKey, TypeEditor } from '../../components';
+
+const defaultViewConfigs = [
+  {
+    type: TypeEditorColumnType.Key,
+    visible: true,
+  },
+  {
+    type: TypeEditorColumnType.Type,
+    visible: true,
+  },
+  {
+    type: TypeEditorColumnType.Description,
+    visible: true,
+  },
+  {
+    type: TypeEditorColumnType.Required,
+    visible: true,
+  },
+  {
+    type: TypeEditorColumnType.Default,
+    visible: true,
+  },
+  {
+    type: TypeEditorColumnType.Operate,
+    visible: true,
+  },
+];
+
+interface PropsType {
+  value?: IJsonSchema;
+  onChange?: (value?: IJsonSchema) => void;
+  readonly?: boolean;
+  config?: {
+    rootKey?: string;
+    viewConfigs?: TypeEditorColumnViewConfig[];
+  };
+}
+
+export function ObjectTypeEditor(props: PropsType) {
+  const { value, onChange, config, readonly } = props;
+
+  const { rootKey = 'outputs', viewConfigs = defaultViewConfigs } = config || {};
+
+  const wrapValue: IJsonSchema = useMemo(
+    () => ({
+      type: 'object',
+      properties: { [rootKey]: value || { type: 'object' } },
+    }),
+    [value, rootKey]
+  );
+
+  const disableEditColumn = useMemo(() => {
+    const res: any[] = [];
+
+    if (readonly) {
+      viewConfigs.forEach((v) => {
+        res.push({
+          column: v.type,
+          reason: 'This field is not editable.',
+        });
+      });
+    }
+
+    return res;
+  }, [readonly, viewConfigs]);
+
+  return (
+    <div>
+      <TypeEditor
+        readonly={readonly}
+        mode="type-definition"
+        toolbarConfig={[ToolbarKey.Import, ToolbarKey.UndoRedo]}
+        rootLevel={1}
+        value={wrapValue}
+        disableEditColumn={disableEditColumn}
+        onChange={(_v) => onChange?.(_v?.properties?.[rootKey])}
+        onCustomSetValue={(newType) => ({
+          type: 'object',
+          properties: {
+            [rootKey]: newType,
+          },
+        })}
+        getRootSchema={(type) => type.properties![rootKey]}
+        viewConfigs={defaultViewConfigs}
+        onEditRowDataSource={(dataSource) => {
+          // 不允许该行编辑 key、required
+          if (dataSource[0]) {
+            dataSource[0].disableEditColumn = [
+              {
+                column: TypeEditorColumnType.Key,
+                reason: 'This field is not editable.',
+              },
+              {
+                column: TypeEditorColumnType.Type,
+                reason: 'This field is not editable.',
+              },
+              {
+                column: TypeEditorColumnType.Required,
+                reason: 'This field is not editable.',
+              },
+              {
+                column: TypeEditorColumnType.Default,
+                reason: 'This field is not editable.',
+              },
+              {
+                column: TypeEditorColumnType.Operate,
+                reason: 'This field is not editable.',
+              },
+            ];
+
+            dataSource[0].cannotDrag = true;
+          }
+
+          return dataSource;
+        }}
+      />
+    </div>
+  );
+}

+ 93 - 0
packages/materials/type-editor/src/services/clipboard-service.ts

@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { injectable } from 'inversify';
+import { Event, Emitter } from '@flowgram.ai/utils';
+
+@injectable()
+export class ClipboardService {
+  public readonly onClipboardChangedEmitter = new Emitter<string>();
+
+  readonly onClipboardChanged: Event<string> = this.onClipboardChangedEmitter.event;
+
+  /**
+   * 读取浏览器数据
+   */
+  private get data(): Promise<string> {
+    return navigator.clipboard.readText();
+  }
+
+  private async saveReadData(): Promise<{
+    error?: string;
+    data?: string;
+  }> {
+    try {
+      const data = await this.data;
+      return {
+        data,
+      };
+    } catch (error) {
+      return {
+        error: error as string,
+      };
+    }
+  }
+
+  /**
+   * 设置剪切板数据
+   */
+  public async writeData(newStrData: string): Promise<void> {
+    const oldSaveData = await this.saveReadData();
+
+    // 读取错误可能是没有读取权限,此时不校验是否相等,直接写入剪切板
+    if (oldSaveData.error || oldSaveData.data !== newStrData) {
+      if (navigator.clipboard && window.isSecureContext) {
+        await navigator.clipboard.writeText(newStrData);
+        const event = document.createEvent('Event');
+        event.initEvent('onchange');
+        (event as unknown as { value: string }).value = newStrData;
+        navigator.clipboard.dispatchEvent(event);
+      } else {
+        const textarea = document.createElement('textarea');
+        textarea.value = newStrData;
+
+        // 视区以外渲染 dom,无法 display none,否则无文本 copy
+        textarea.style.display = 'absolute';
+        textarea.style.left = '-99999999px';
+
+        document.body.prepend(textarea);
+
+        // highlight the content of the textarea element
+        textarea.select();
+
+        try {
+          document.execCommand('copy');
+        } catch (err) {
+          console.log(err);
+        } finally {
+          textarea.remove();
+        }
+      }
+
+      this.onClipboardChangedEmitter.fire(newStrData);
+    }
+  }
+
+  /**
+   * 获取剪切板数据
+   */
+
+  public async readData(): Promise<string> {
+    const res = await this.saveReadData();
+    if (res.error) {
+      throw Error(res.error);
+    }
+    return res.data || '';
+  }
+
+  public clearData(): void {
+    this.writeData('');
+  }
+}

+ 11 - 0
packages/materials/type-editor/src/services/index.tsx

@@ -0,0 +1,11 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+/* eslint-disable import/no-unresolved */
+import 'reflect-metadata';
+export * from './type-editor-service';
+export * from './shortcut-service';
+export * from './clipboard-service';
+export * from './type-operation-service';

+ 9 - 0
packages/materials/type-editor/src/services/shortcut-service.ts

@@ -0,0 +1,9 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { injectable } from 'inversify';
+
+@injectable()
+export class ShortcutsService {}

+ 396 - 0
packages/materials/type-editor/src/services/type-editor-service.ts

@@ -0,0 +1,396 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { inject, injectable } from 'inversify';
+import { Emitter } from '@flowgram.ai/utils';
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { MonitorData } from '../utils';
+import {
+  type TypeEditorColumnType,
+  type TypeEditorRowData,
+  TypeEditorDropInfo,
+  TypeEditorColumnConfig,
+  TypeEditorPos,
+  TypeEditorColumnViewConfig,
+  ShortcutContext,
+} from '../types';
+import { TypeRegistryCreatorsAdapter } from '../contexts';
+import { ROOT_FIELD_ID } from '../components/type-editor/common';
+import { TypeEditorRegistryManager } from './type-registry-manager';
+import { ClipboardService } from './clipboard-service';
+
+@injectable()
+export class TypeEditorService<TypeSchema extends Partial<IJsonSchema>> {
+  private _configs: Map<TypeEditorColumnType, TypeEditorColumnConfig<TypeSchema>> = new Map();
+
+  private _activePos: TypeEditorPos = { x: -1, y: -1 };
+
+  // -1 为 header
+  private _dropInfo: TypeEditorDropInfo = {
+    rowDataId: '',
+    indent: -1,
+    index: -2,
+  };
+
+  public errorMsgs = new MonitorData<{ pos: TypeEditorPos; msg?: string }[]>([]);
+
+  public editValue: unknown;
+
+  public onChange: (
+    typeSchema?: TypeSchema,
+    ctx?: {
+      storeState?: boolean;
+    }
+  ) => void;
+
+  public onRemoveEmptyLine: (id: string) => void;
+
+  public onGlobalAdd: ((id: string) => void) | undefined;
+
+  public typeRegistryCreators?: TypeRegistryCreatorsAdapter<TypeSchema>[];
+
+  private dataSource: TypeEditorRowData<TypeSchema>[] = [];
+
+  public dataSourceMap: Record<string, TypeEditorRowData<TypeSchema>> = {};
+
+  public dataSourceTouchedMap: Record<string, boolean> = {};
+
+  public blink = new MonitorData(false);
+
+  public columnViewConfig: TypeEditorColumnViewConfig[] = [];
+
+  public onActivePosChange = new Emitter<TypeEditorPos>();
+
+  public onDropInfoChange = new Emitter<TypeEditorDropInfo>();
+
+  @inject(ClipboardService)
+  public clipboard: ClipboardService;
+
+  @inject(TypeEditorRegistryManager)
+  public typeDefinition: TypeEditorRegistryManager<TypeSchema>;
+
+  public rootTypeSchema: TypeSchema;
+
+  public setErrorMsg = (pos: TypeEditorPos, msg?: string) => {
+    const newMsgs = [...this.errorMsgs.data];
+    const item = newMsgs.find((v) => v.pos.x === pos.x && v.pos.y === pos.y);
+    if (item) {
+      item.msg = msg;
+    } else {
+      newMsgs.push({ pos, msg });
+    }
+    this.errorMsgs.update(newMsgs);
+  };
+
+  public refreshErrorMsgAfterRemove = (index: number) => {
+    // 删除被删去那行的 errorMsgs
+    const newMsgs = this.errorMsgs.data.filter((msg) => msg.pos.y !== index);
+
+    newMsgs.forEach((msg) => {
+      if (msg.pos.y > index) {
+        msg.pos.y = msg.pos.y - 1;
+      }
+    });
+
+    this.errorMsgs.update(newMsgs);
+  };
+
+  public checkActivePosError = () => {
+    const pos = this.activePos;
+
+    return !!this.errorMsgs.data.find((v) => v.pos.x === pos.x && v.pos.y === pos.y && v.msg);
+  };
+
+  public setEditValue = (val: unknown) => {
+    this.editValue = val;
+  };
+
+  public registerConfigs(
+    config: TypeEditorColumnConfig<TypeSchema> | TypeEditorColumnConfig<TypeSchema>[]
+  ): void {
+    const configs = Array.isArray(config) ? config : [config];
+
+    configs.map((c) => {
+      this._configs.set(c.type, c);
+    });
+  }
+
+  public addConfigProps(
+    type: TypeEditorColumnType,
+    config: Partial<Omit<TypeEditorColumnConfig<TypeSchema>, 'type'>>
+  ): void {
+    const configByType = this.getConfigByType(type);
+
+    if (!configByType) {
+      return;
+    }
+
+    const newConfig = {
+      ...configByType,
+      ...config,
+    };
+
+    this._configs.set(type, newConfig);
+  }
+
+  public getConfigs = (): TypeEditorColumnConfig<TypeSchema>[] =>
+    Array.from(this._configs.values());
+
+  public getConfigByType(
+    type: TypeEditorColumnType
+  ): TypeEditorColumnConfig<TypeSchema> | undefined {
+    return this._configs.get(type);
+  }
+
+  public triggerShortcutEvent(
+    event: 'enter' | 'tab' | 'left' | 'right' | 'up' | 'down' | 'copy' | 'paste' | 'delete'
+  ): void {
+    const column = this.columnViewConfig[this.activePos.x];
+
+    const columnConfig = this.getConfigByType(column?.type);
+    if (!columnConfig) {
+      return;
+    }
+
+    const ctx: ShortcutContext<TypeSchema> = {
+      value: this.editValue,
+      rowData: this.dataSource[this.activePos.y],
+      onRemoveEmptyLine: this.onRemoveEmptyLine,
+      onChange: this.onChange,
+      typeEditor: this,
+      typeDefinitionService: this.typeDefinition,
+    };
+
+    switch (event) {
+      case 'enter': {
+        columnConfig.shortcuts?.onEnter?.(ctx);
+        return;
+      }
+      case 'tab': {
+        columnConfig.shortcuts?.onTab?.(ctx);
+        return;
+      }
+      case 'down': {
+        columnConfig.shortcuts?.onDown?.(ctx);
+        return;
+      }
+      case 'up': {
+        columnConfig.shortcuts?.onUp?.(ctx);
+        return;
+      }
+      case 'left': {
+        columnConfig.shortcuts?.onLeft?.(ctx);
+        return;
+      }
+      case 'right': {
+        columnConfig.shortcuts?.onRight?.(ctx);
+        return;
+      }
+      case 'copy': {
+        columnConfig.shortcuts?.onCopy?.(ctx);
+        return;
+      }
+      case 'paste': {
+        columnConfig.shortcuts?.onPaste?.(ctx);
+        return;
+      }
+      case 'delete': {
+        columnConfig.shortcuts?.onDelete?.(ctx);
+        return;
+      }
+
+      default: {
+        return;
+      }
+    }
+  }
+
+  public get activePos(): TypeEditorPos {
+    return this._activePos;
+  }
+
+  private checkRowDataColumnCanEdit = (
+    rowData: TypeEditorRowData<TypeSchema>,
+    column: TypeEditorColumnType
+  ): boolean =>
+    !(rowData.disableEditColumn || []).map((v) => v.column).includes(column) &&
+    this.getConfigByType(column)?.focusable !== false;
+
+  /**
+   * 获取可编辑的下一列/上一列
+   */
+  private getCanEditColumn(originPos: TypeEditorPos, direction: 'next' | 'last'): TypeEditorPos {
+    const newX =
+      (originPos.x + this.columnViewConfig.length + (direction === 'next' ? 1 : -1)) %
+      this.columnViewConfig.length;
+
+    const newPos = {
+      y: originPos.y,
+      x: newX,
+    };
+
+    if (
+      this.checkRowDataColumnCanEdit(
+        this.dataSource[newPos.y],
+        this.columnViewConfig[newPos.x].type
+      )
+    ) {
+      return newPos;
+    }
+
+    return this.getCanEditColumn(newPos, direction);
+  }
+
+  /**
+   * 获取可编辑的下一行/上一行
+   */
+  private getCanEditLine(originPos: TypeEditorPos, direction: 'next' | 'last'): TypeEditorPos {
+    const newY =
+      (originPos.y + this.dataSource.length + (direction === 'next' ? 1 : -1)) %
+      this.dataSource.length;
+
+    const newPos = {
+      y: newY,
+      x: originPos.x,
+    };
+
+    if (
+      this.checkRowDataColumnCanEdit(
+        this.dataSource[newPos.y],
+        this.columnViewConfig[newPos.x].type
+      )
+    ) {
+      return newPos;
+    }
+
+    return this.getCanEditLine(newPos, direction);
+  }
+
+  /**
+   * 获取下一个可编辑的
+   */
+  private getNextEditItem = (pos: TypeEditorPos): TypeEditorPos => {
+    const newPos = { ...pos };
+
+    if (newPos.x === this.columnViewConfig.length - 1) {
+      newPos.y = (1 + newPos.y) % this.dataSource.length;
+      newPos.x = 0;
+    } else {
+      newPos.x = newPos.x + 1;
+    }
+
+    if (
+      this.checkRowDataColumnCanEdit(
+        this.dataSource[newPos.y],
+        this.columnViewConfig[newPos.x].type
+      )
+    ) {
+      return newPos;
+    }
+
+    return this.getNextEditItem(newPos);
+  };
+
+  public moveActivePosToNextLine(): void {
+    const newPos = this.getCanEditLine(this.activePos, 'next');
+
+    this.setActivePos(newPos);
+  }
+
+  public moveActivePosToNextLineWithAddLine(rowData: TypeEditorRowData<TypeSchema>): void {
+    const newPos = { ...this.activePos };
+
+    if (!rowData.parentId) {
+      return;
+    }
+
+    const parentData = this.dataSourceMap[rowData.parentId] || this.dataSourceMap[ROOT_FIELD_ID];
+
+    const id = this.dataSourceMap[rowData.parentId] ? rowData.parentId : ROOT_FIELD_ID;
+    const addChild = parentData.index + parentData.deepChildrenCount === rowData.index;
+
+    if (addChild) {
+      if (this.onGlobalAdd) {
+        this.onGlobalAdd(id);
+        newPos.y = newPos.y + 1;
+      } else {
+        newPos.y = -1;
+      }
+    } else {
+      newPos.y = newPos.y + 1;
+    }
+    this.setActivePos(newPos);
+  }
+
+  public moveActivePosToLastLine(): void {
+    const newPos = this.getCanEditLine(this.activePos, 'last');
+
+    this.setActivePos(newPos);
+  }
+
+  public moveActivePosToLastColumn(): void {
+    const newPos = this.getCanEditColumn(this.activePos, 'last');
+    this.setActivePos(newPos);
+  }
+
+  public moveActivePosToNextColumn(): void {
+    const newPos = this.getCanEditColumn(this.activePos, 'next');
+
+    this.setActivePos(newPos);
+  }
+
+  public moveActivePosToNextItem(): void {
+    const newPos = this.getNextEditItem(this.activePos);
+
+    this.setActivePos(newPos);
+  }
+
+  public setActivePos(pos: TypeEditorPos): void {
+    if (this.checkActivePosError()) {
+      return;
+    }
+    this._activePos = pos;
+
+    this.onActivePosChange.fire(this._activePos);
+  }
+
+  public clearActivePos(): void {
+    this._activePos = { x: -1, y: -1 };
+    this.onActivePosChange.fire(this._activePos);
+  }
+
+  public setDataSource(newData: TypeEditorRowData<TypeSchema>[]): void {
+    this.dataSource = newData;
+  }
+
+  public getDataSource(): TypeEditorRowData<TypeSchema>[] {
+    return this.dataSource;
+  }
+
+  public setColumnViewConfig(config: TypeEditorColumnViewConfig[]): void {
+    this.columnViewConfig = config;
+  }
+
+  public get dropInfo(): TypeEditorDropInfo {
+    return this._dropInfo;
+  }
+
+  public setDropInfo(dropInfo: TypeEditorDropInfo): void {
+    if (
+      dropInfo.indent === this.dropInfo.indent &&
+      this.dropInfo.rowDataId === dropInfo.rowDataId &&
+      this.dropInfo.index === dropInfo.index
+    ) {
+      return;
+    }
+    this._dropInfo = dropInfo;
+    this.onDropInfoChange.fire(dropInfo);
+  }
+
+  public clearDropInfo(): void {
+    this.setDropInfo({ rowDataId: '', indent: -1, index: -2 });
+  }
+}

+ 99 - 0
packages/materials/type-editor/src/services/type-operation-service.ts

@@ -0,0 +1,99 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { isEqual } from 'lodash';
+import { injectable } from 'inversify';
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { MonitorData } from '../utils';
+
+interface StackItem {
+  id: string;
+  value: string;
+}
+
+// 操作注册
+@injectable()
+export class TypeEditorOperationService<TypeSchema extends Partial<IJsonSchema>> {
+  public undoStack: StackItem[] = [];
+
+  public redoStack: StackItem[] = [];
+
+  private _id_idx = 0;
+
+  private _getNewId(): string {
+    return `${this._id_idx++}`;
+  }
+
+  public _storeState = (value: TypeSchema) => {
+    if (this.redoStack.length > 0) {
+      this.redoStack.splice(0);
+    }
+
+    this.undoStack.push({
+      id: this._getNewId(),
+      value: JSON.stringify(value),
+    });
+    this.refreshUndoRedoStatus();
+  };
+
+  public canUndo = new MonitorData(false);
+
+  public canRedo = new MonitorData(false);
+
+  public constructor() {
+    this.refreshUndoRedoStatus();
+  }
+
+  public refreshUndoRedoStatus() {
+    this.canRedo.update(this.redoStack.length !== 0);
+    this.canUndo.update(this.undoStack.length > 1);
+  }
+
+  public getCurrentState(): TypeSchema | undefined {
+    const top = this.undoStack[this.undoStack.length - 1];
+
+    if (top) {
+      return JSON.parse(top.value);
+    }
+    return;
+  }
+
+  public clear(): void {
+    this.undoStack = [];
+    this.redoStack = [];
+  }
+
+  public storeState(value: TypeSchema): void {
+    if (isEqual(this.getCurrentState(), value)) {
+      return;
+    }
+
+    this._storeState(value);
+  }
+
+  public async undo(): Promise<void> {
+    const top = this.undoStack.pop();
+    if (top) {
+      this.redoStack.push(top);
+    }
+
+    this.refreshUndoRedoStatus();
+  }
+
+  public async redo(): Promise<void> {
+    const top = this.redoStack.pop();
+
+    if (top) {
+      this.undoStack.push(top);
+    }
+
+    this.refreshUndoRedoStatus();
+  }
+
+  public debugger(): void {
+    console.log('getCurrentState - debugger', this.getCurrentState());
+  }
+}

+ 12 - 0
packages/materials/type-editor/src/services/type-registry-manager.ts

@@ -0,0 +1,12 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { JsonSchemaTypeManager, IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { TypeEditorRegistry } from '../types';
+
+export class TypeEditorRegistryManager<
+  TypeSchema extends Partial<IJsonSchema>
+> extends JsonSchemaTypeManager<TypeSchema, TypeEditorRegistry<TypeSchema>> {}

+ 28 - 0
packages/materials/type-editor/src/services/utils.ts

@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+export const traverseIJsonSchema = (
+  root: Partial<IJsonSchema> | undefined,
+  cb: (type: Partial<IJsonSchema>) => void
+): void => {
+  if (root) {
+    cb(root);
+
+    if (root.items) {
+      traverseIJsonSchema(root.items, cb);
+    }
+    if (root.additionalProperties) {
+      traverseIJsonSchema(root.additionalProperties, cb);
+    }
+
+    if (root.properties) {
+      Object.values(root.properties).forEach((v) => {
+        traverseIJsonSchema(v, cb);
+      });
+    }
+  }
+};

+ 28 - 0
packages/materials/type-editor/src/type-registry/array.tsx

@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from 'react';
+
+import { IJsonSchema, JsonSchemaTypeRegistryCreator } from '@flowgram.ai/json-schema';
+import { Space, Typography } from '@douyinfe/semi-ui';
+
+export const arrayRegistryCreator: JsonSchemaTypeRegistryCreator = ({ typeManager }) => ({
+  type: 'array',
+
+  getDisplayLabel: (type: IJsonSchema) => {
+    const config = typeManager.getTypeBySchema(type);
+
+    return (
+      <Space style={{ width: '100%' }}>
+        {config?.getDisplayIcon(type) || config?.icon}
+        <div style={{ flex: 1, width: 0, display: 'flex' }}>
+          <Typography.Text size="small" ellipsis={{ showTooltip: true }}>
+            {typeManager.getComplexText(type)}
+          </Typography.Text>
+        </div>
+      </Space>
+    );
+  },
+});

+ 45 - 0
packages/materials/type-editor/src/type-registry/boolean.tsx

@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from 'react';
+
+import { IJsonSchema, JsonSchemaTypeRegistryCreator } from '@flowgram.ai/json-schema';
+import { Select } from '@douyinfe/semi-ui';
+
+import { TypeInputContext } from '../types';
+
+export const booleanRegistryCreator: JsonSchemaTypeRegistryCreator = () => ({
+  type: 'boolean',
+
+  getInputNode({ value, onChange, onSubmit }: TypeInputContext<IJsonSchema>): React.JSX.Element {
+    return (
+      <Select
+        style={{
+          width: '100%',
+          height: '100%',
+          display: 'flex',
+          alignItems: 'center',
+          background: 'var(--semi-color-bg-0)',
+        }}
+        optionList={[
+          {
+            value: 1,
+            label: 'True',
+          },
+          {
+            value: 0,
+            label: 'False',
+          },
+        ]}
+        className={'flow-type-select'}
+        value={Number(value)}
+        onSelect={(v) => {
+          onChange(Boolean(v));
+          onSubmit();
+        }}
+      />
+    );
+  },
+});

+ 24 - 0
packages/materials/type-editor/src/type-registry/index.ts

@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+// import { TypeDefinitionRegistryCreator } from '@flow-ide-editor/flow-schema-type-definitions';
+
+import { JsonSchemaTypeRegistryCreator } from '@flowgram.ai/json-schema';
+
+import { stringRegistryCreator } from './string';
+import { objectRegistryCreator } from './object';
+import { numberRegistryCreator } from './number';
+import { integerRegistryCreator } from './integer';
+import { booleanRegistryCreator } from './boolean';
+import { arrayRegistryCreator } from './array';
+
+export const defaultTypeRegistryCreators: JsonSchemaTypeRegistryCreator[] = [
+  stringRegistryCreator,
+  numberRegistryCreator,
+  integerRegistryCreator,
+  booleanRegistryCreator,
+  objectRegistryCreator,
+  arrayRegistryCreator,
+];

+ 26 - 0
packages/materials/type-editor/src/type-registry/integer.tsx

@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from 'react';
+
+import { IJsonSchema, JsonSchemaTypeRegistryCreator } from '@flowgram.ai/json-schema';
+import { InputNumber } from '@douyinfe/semi-ui';
+
+import { TypeInputContext } from '../types';
+
+export const integerRegistryCreator: JsonSchemaTypeRegistryCreator = () => ({
+  type: 'integer',
+  getInputNode({ value, onChange, onSubmit }: TypeInputContext<IJsonSchema>): React.JSX.Element {
+    return (
+      <InputNumber
+        autoFocus
+        value={value}
+        style={{ width: '100%', height: '100%' }}
+        onChange={onChange}
+        onBlur={onSubmit}
+      />
+    );
+  },
+});

+ 26 - 0
packages/materials/type-editor/src/type-registry/number.tsx

@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from 'react';
+
+import { IJsonSchema, JsonSchemaTypeRegistryCreator } from '@flowgram.ai/json-schema';
+import { InputNumber } from '@douyinfe/semi-ui';
+
+import { TypeInputContext } from '../types';
+
+export const numberRegistryCreator: JsonSchemaTypeRegistryCreator = () => ({
+  type: 'number',
+  getInputNode({ value, onChange, onSubmit }: TypeInputContext<IJsonSchema>): React.JSX.Element {
+    return (
+      <InputNumber
+        autoFocus
+        value={value}
+        style={{ width: '100%', height: '100%' }}
+        onChange={onChange}
+        onBlur={onSubmit}
+      />
+    );
+  },
+});

+ 26 - 0
packages/materials/type-editor/src/type-registry/object.tsx

@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from 'react';
+
+import { JsonSchemaTypeRegistryCreator } from '@flowgram.ai/json-schema';
+import { Typography } from '@douyinfe/semi-ui';
+
+export const objectRegistryCreator: JsonSchemaTypeRegistryCreator = () => ({
+  type: 'object',
+  getInputNode: (): React.JSX.Element => (
+    <div
+      style={{
+        width: '100%',
+        height: '100%',
+        display: 'flex',
+        alignItems: 'center',
+        padding: '0 8px',
+      }}
+    >
+      <Typography.Text>Not Supported</Typography.Text>
+    </div>
+  ),
+});

+ 29 - 0
packages/materials/type-editor/src/type-registry/string.tsx

@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from 'react';
+
+import { IJsonSchema, JsonSchemaTypeRegistryCreator } from '@flowgram.ai/json-schema';
+import { Input } from '@douyinfe/semi-ui';
+
+import { TypeInputContext } from '../types';
+
+export const stringRegistryCreator: JsonSchemaTypeRegistryCreator = () => ({
+  type: 'string',
+  getInputNode: ({ value, onChange, onSubmit }: TypeInputContext<IJsonSchema>) => (
+    <Input
+      style={{
+        width: '100%',
+        height: '100%',
+        display: 'flex',
+        alignItems: 'center',
+      }}
+      autoFocus
+      value={value}
+      onChange={onChange}
+      onBlur={onSubmit}
+    />
+  ),
+});

+ 7 - 0
packages/materials/type-editor/src/types/index.ts

@@ -0,0 +1,7 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+export * from './type-editor';
+export * from './registry';

+ 98 - 0
packages/materials/type-editor/src/types/registry.ts

@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { IJsonSchema, JsonSchemaTypeRegistry } from '@flowgram.ai/json-schema';
+
+export interface TypeInputConfig<TypeSchema extends Partial<IJsonSchema>> {
+  canEnter?: boolean;
+
+  getProps?: (typeSchema: TypeSchema) => Record<string, unknown>;
+}
+
+export interface FlowSchemaInitCtx {
+  enum?: string[];
+}
+
+export interface TypeInputContext<TypeSchema extends Partial<IJsonSchema>> {
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  value?: any;
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  onChange: (newVal: any) => void;
+  type: TypeSchema;
+  onSubmit: () => void;
+}
+export interface TypeCascaderConfig<TypeSchema extends Partial<IJsonSchema>> {
+  /**
+   * 自定义 CascaderPanel
+   */
+  customCascaderPanel?: (ctx: {
+    typeSchema: TypeSchema;
+    onChange: (typeSchema: TypeSchema) => void;
+    onFocus?: () => void;
+    onBlur?: () => void;
+  }) => JSX.Element;
+  /**
+   * 选中后是否不关闭面板
+   */
+  unClosePanelAfterSelect?: boolean;
+  /**
+   * 获取生成 schema 的 ctx
+   */
+  generateInitCtx?: (typeSchema: TypeSchema) => FlowSchemaInitCtx;
+}
+
+export interface TypeEditorRegistry<TypeSchema extends Partial<IJsonSchema>>
+  extends JsonSchemaTypeRegistry<TypeSchema> {
+  /**
+   * 当前字段是否支持 default
+   */
+  defaultEditable?: true | string;
+  /**
+   * 自定义 disabled
+   */
+  customDisabled?: (ctx: { level: number; parentType: string; parentTypes: string[] }) => string;
+
+  /**
+   * typeInput 设置
+   */
+  typeInputConfig?: TypeInputConfig<TypeSchema>;
+  /**
+   * typeCascader 设置
+   */
+  typeCascaderConfig?: TypeCascaderConfig<TypeSchema>;
+
+  /**
+   * 从默认值上下文
+   */
+  formatDefault?: (val: any, type: IJsonSchema) => IJsonSchema;
+
+  /**
+   *
+   */
+  deFormatDefault?: (val: any) => any;
+
+  /**
+   * 子字段是否可以编辑 default
+   */
+  childrenDefaultEditable?: (type: TypeSchema) => true | string;
+  /**
+   * 子字段是否可以编辑 default
+   */
+  childrenValueEditable?: (type: TypeSchema) => true | string;
+
+  getInputNode?: (ctx: TypeInputContext<TypeSchema>) => JSX.Element;
+  /**
+   * 自定义生成子类型的 optionValue
+   */
+  customChildOptionValue?: () => string[];
+
+  /**
+   * @deprecated api 已废弃,能力仍保留,请优先使用或定义 getSupportedItemTypes
+   */
+  getItemTypes?: (ctx: {
+    level: number;
+    parentTypes?: string[];
+  }) => Array<{ type: string; disabled?: string }>;
+}

+ 291 - 0
packages/materials/type-editor/src/types/type-editor.ts

@@ -0,0 +1,291 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { FC, Ref } from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+
+import { TypeEditorRegistryManager } from '../services/type-registry-manager';
+import { type ShortcutsService, type TypeEditorService } from '../services';
+
+export interface TypeEditorPos {
+  x: number;
+  y: number;
+}
+
+export interface TypeEditorDropInfo {
+  rowDataId: string;
+  indent: number;
+  index: number;
+}
+
+export interface TypeChangeContext {
+  type: TypeEditorColumnType;
+  oldValue: unknown;
+  newValue: unknown;
+}
+
+export interface EditorProps {
+  keyCheck: boolean;
+}
+
+export interface RenderProps<TypeSchema extends Partial<IJsonSchema>> {
+  rowData: TypeEditorRowData<TypeSchema>;
+  readonly?: boolean;
+  onViewMode: () => void;
+  typeEditor: TypeEditorService<TypeSchema>;
+  onChildrenVisibleChange: (rowDataKey: string, newVal: boolean) => void;
+  onChange: () => void;
+  onPaste?: (typeSchema?: TypeSchema) => TypeSchema | undefined;
+  onFieldChange?: (ctx: TypeChangeContext) => void;
+  onEditMode: () => void;
+  dragSource?: Ref<HTMLSpanElement>;
+  error: boolean;
+  onError?: (msg: string[]) => void;
+  unOpenKeys: Record<string, boolean>;
+  config: Omit<TypeEditorColumnViewConfig, 'type' | 'visible'>;
+}
+
+export interface ShortcutContext<TypeSchema extends Partial<IJsonSchema>> {
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  value: any;
+  rowData: TypeEditorRowData<TypeSchema>;
+  onChange: () => void;
+  onRemoveEmptyLine: (id: string) => void;
+  typeEditor: TypeEditorService<TypeSchema>;
+  onError?: (msg?: string) => void;
+  typeDefinitionService: TypeEditorRegistryManager<TypeSchema>;
+}
+
+export interface InfoContext {
+  shortcuts: ShortcutsService;
+}
+export interface TypeEditorColumnConfig<TypeSchema extends Partial<IJsonSchema>> {
+  /**
+   * type
+   */
+  type: TypeEditorColumnType;
+  /**
+   * 标题
+   */
+  label: string;
+  /**
+   * 百分比
+   */
+  width?: number;
+  /**
+   * 是否可 focus
+   */
+  focusable?: boolean;
+  /**
+   * label ❓ 提示
+   */
+  info?: () => string;
+  /**
+   * 只读态 render
+   */
+  viewRender?: FC<RenderProps<TypeSchema>>;
+  /**
+   * 编辑态 render
+   */
+  editRender?: FC<RenderProps<TypeSchema>>;
+  /**
+   * 是否自定义拖拽
+   */
+  customDrop?: boolean;
+
+  /**
+   * 校验该行是否存在错误
+   */
+  validateCell?: (
+    rowData: TypeEditorRowData<TypeSchema>,
+    extra: TypeEditorSpecialConfig<TypeSchema>
+  ) =>
+    | {
+        level: 'error' | 'warning';
+        msg?: string;
+      }
+    | undefined;
+
+  /**
+   * 快捷键响应
+   */
+  shortcuts?: {
+    onEnter?: (ctx: ShortcutContext<TypeSchema>) => void;
+    onTab?: (ctx: ShortcutContext<TypeSchema>) => void;
+    onUp?: (ctx: ShortcutContext<TypeSchema>) => void;
+    onDown?: (ctx: ShortcutContext<TypeSchema>) => void;
+    onLeft?: (ctx: ShortcutContext<TypeSchema>) => void;
+    onRight?: (ctx: ShortcutContext<TypeSchema>) => void;
+    onCopy?: (ctx: ShortcutContext<TypeSchema>) => void;
+    onPaste?: (ctx: ShortcutContext<TypeSchema>) => void;
+    onDelete?: (ctx: ShortcutContext<TypeSchema>) => void;
+  };
+}
+
+export interface TypeEditorColumnViewConfig {
+  /**
+   * 类型
+   */
+  type: TypeEditorColumnType;
+  /**
+   * 是否可见
+   */
+  visible: boolean;
+}
+
+export interface DisableTypeInfo {
+  type: string;
+  reason: string;
+}
+
+export interface TypeEditorColumnViewConfig {
+  /**
+   * 类型
+   */
+  type: TypeEditorColumnType;
+  /**
+   * 是否可见
+   */
+  visible: boolean;
+}
+
+export enum TypeEditorColumnType {
+  /**
+   *
+   */
+  Key = 'key',
+  Type = 'type',
+  Required = 'required',
+  Description = 'description',
+  Default = 'default',
+  Operate = 'operate',
+  Value = 'value',
+  Private = 'private',
+}
+
+export interface TypeEditorExtraInfo {
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  value?: any;
+}
+
+export type TypeEditorSchema<TypeSchema extends Partial<IJsonSchema>> = TypeSchema & {
+  extra?: TypeEditorExtraInfo;
+};
+
+export interface TypeEditorSpecialConfig<TypeSchema extends Partial<IJsonSchema>> {
+  /**
+   * 默认值展示模式
+   *
+   * default 默认编辑模式
+   * server 为展示后端兜底默认值
+   */
+  defaultMode?: 'default' | 'server';
+  /**
+   * 支持自定义校验 Name 函数
+   */
+  customValidateName?: (name: string) => string;
+  /**
+   * 关闭自动修复 index
+   */
+  disableFixIndex?: boolean;
+
+  /**
+   * 是否可以编辑 key 的可见
+   */
+  editorVisible?: boolean | string;
+  /**
+   * 隐藏拖拽
+   */
+  hiddenDrag?: boolean;
+  /**
+   * 使用 extra 字段,而非 flow 字段
+   */
+  useExtra?: boolean;
+  /**
+   * type-selector 禁用类型
+   */
+  customDisabledTypes?: Array<DisableTypeInfo>;
+  /**
+   * 是否禁用 add
+   */
+  disabledAdd?: (rowData: TypeEditorRowData<TypeSchema>) => string;
+  /**
+   * 自定义默认值展示
+   */
+  customDefaultView?: (ctx: {
+    rowData: TypeEditorRowData<TypeSchema>;
+    value: unknown;
+    disabled?: string;
+    onChange: (value: unknown) => void;
+    onSubmit: (value: unknown) => void;
+  }) => JSX.Element;
+  /**
+   * 自定义 default 禁用规则
+   */
+  customDefaultEditable?: (rowData: TypeEditorRowData<TypeSchema>) => true | string;
+}
+
+export type TypeEditorRowData<TypeSchema extends Partial<IJsonSchema>> = TypeSchema & {
+  /**
+   * 当前行的唯一值
+   */
+  id: string;
+  /**
+   * key 值
+   */
+  key: string;
+  /**
+   * 是否必填
+   */
+  isRequired: boolean;
+  /**
+   * 层数
+   */
+  level: number;
+  /**
+   * rowData 关联的 IJsonSchema
+   */
+  self: TypeEditorSchema<TypeSchema>;
+  /**
+   * 父节点 IJsonSchema
+   */
+  parent?: TypeEditorSchema<TypeSchema>;
+  /**
+   * 子节点个数,只包括子类型
+   */
+  childrenCount: number;
+  /**
+   * 子节点个数,包括子类型嵌套类型
+   */
+  deepChildrenCount: number;
+  /**
+   * 不能编辑的列
+   * 和 IJsonSchema 中 editable 的关系
+   * editable 为 false,会将 disableEditColumn 每个 column 都填上
+   */
+  disableEditColumn?: Array<{ column: TypeEditorColumnType; reason: string }>;
+  /**
+   * 是否不能拖拽
+   */
+  cannotDrag?: boolean;
+  /**
+   * 行
+   */
+  index: number;
+  /**
+   *
+   */
+  extraConfig: TypeEditorSpecialConfig<TypeSchema>;
+
+  /**
+   * 父节点 rowId
+   */
+  parentId?: string;
+  /**
+   * path
+   */
+  path: string[];
+};

+ 6 - 0
packages/materials/type-editor/src/utils/index.ts

@@ -0,0 +1,6 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+export * from './monitor-data';

+ 7 - 0
packages/materials/type-editor/src/utils/monitor-data/index.ts

@@ -0,0 +1,7 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+export { MonitorData } from './monitor-data';
+export { useMonitorData } from './use-monitor-data';

+ 43 - 0
packages/materials/type-editor/src/utils/monitor-data/monitor-data.ts

@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { Emitter, type Event } from '@flowgram.ai/utils';
+
+/**
+ * 定义用于在 service 的一些需要被监听 onChange 的 data
+ * 搭配 useMonitorData 使用
+ */
+export class MonitorData<Type> {
+  private _data: Type;
+
+  protected readonly onDataChangeEmitter = new Emitter<{
+    prev: Type;
+    next: Type;
+  }>();
+
+  readonly onDataChange: Event<{
+    prev: Type;
+    next: Type;
+  }> = this.onDataChangeEmitter.event;
+
+  public get data(): Type {
+    return this._data;
+  }
+
+  public constructor(initialValue?: Type) {
+    if (initialValue !== undefined) {
+      this._data = initialValue;
+    }
+  }
+
+  public update(data: Type) {
+    const prev = this._data;
+    this._data = data;
+    this.onDataChangeEmitter.fire({
+      prev,
+      next: data,
+    });
+  }
+}

+ 27 - 0
packages/materials/type-editor/src/utils/monitor-data/use-monitor-data.ts

@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { useEffect, useState } from 'react';
+
+import { type MonitorData } from './monitor-data';
+
+export const useMonitorData = <Type>(
+  monitorData: MonitorData<Type> | undefined
+): { data: Type | undefined } => {
+  const [data, setData] = useState(monitorData?.data);
+
+  useEffect(() => {
+    const dispose = monitorData?.onDataChange(({ prev, next }) => {
+      setData(next);
+    });
+    return () => {
+      dispose?.dispose();
+    };
+  }, [monitorData]);
+
+  return {
+    data,
+  };
+};

+ 95 - 0
packages/materials/type-editor/src/utils/registry-adapter.tsx

@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+import React from 'react';
+
+import { IJsonSchema } from '@flowgram.ai/json-schema';
+import { Space, Typography } from '@douyinfe/semi-ui';
+
+import { TypeEditorRegistry } from '../types';
+import { TypeEditorRegistryManager } from '../services/type-registry-manager';
+
+export const registryFormatter = <TypeSchema extends Partial<IJsonSchema>>(
+  registry: Partial<TypeEditorRegistry<TypeSchema>>,
+  manager: TypeEditorRegistryManager<TypeSchema>
+): Partial<TypeEditorRegistry<TypeSchema>> => {
+  const res: Record<string, unknown> = {
+    ...registry,
+  };
+
+  const apiMap: Record<string, string> = {
+    getItemTypes: 'getSupportedItemTypes',
+    getIJsonSchemaByStringValue: 'getTypeSchemaByStringValue',
+    getStringValue: 'getStringValueByTypeSchema',
+    getIJsonSchemaProperties: 'getTypeSchemaProperties',
+    getIJsonSchemaPropertiesParent: 'getPropertiesParent',
+    getChildrenExtraJsonPaths: 'getJsonPaths',
+    getDefaultIJsonSchema: 'getDefaultSchema',
+  };
+
+  Object.keys(apiMap).forEach((api) => {
+    if (res[api]) {
+      res[apiMap[api]] = res[api];
+    }
+    if (res[apiMap[api]]) {
+      res[api] = res[apiMap[api]];
+    }
+  });
+
+  return {
+    ...res,
+    getDisplayLabel: (type: IJsonSchema) => (
+      <Space style={{ width: '100%' }}>
+        {manager?.getDisplayIcon(type as TypeSchema)}
+        <div style={{ flex: 1, width: 0, display: 'flex' }}>
+          <Typography.Text size="small" ellipsis={{ showTooltip: true }}>
+            {manager.getComplexText(type as TypeSchema)}
+          </Typography.Text>
+        </div>
+      </Space>
+    ),
+    getIJsonSchemaDeepField: (type: TypeSchema) => manager.getTypeSchemaDeepChildField(type),
+  } as unknown as TypeEditorRegistry<TypeSchema>;
+};
+
+interface ITypeDefinitionManager<TypeSchema extends Partial<IJsonSchema>> {
+  getDefinitionByIJsonSchema: (
+    typeSchema?: TypeSchema
+  ) => TypeEditorRegistry<TypeSchema> | undefined;
+  getAllTypeDefinitions: () => TypeEditorRegistry<TypeSchema>[];
+  getDefinitionByType: (type: string) => TypeEditorRegistry<TypeSchema> | undefined;
+  getUndefinedIJsonSchema: () => TypeSchema;
+}
+
+export interface ITypeDefinitionAdapter<TypeSchema extends Partial<IJsonSchema>> {
+  /**
+   * @deprecated 兼容旧接口,已废弃,请使用 typeRegistryManager
+   */
+  typeDefinitionManager: ITypeDefinitionManager<TypeSchema>;
+  /**
+   * @deprecated 兼容旧接口,已废弃,请使用 typeRegistryManager 的 getComplexText 方法
+   */
+  utils: {
+    getComposedLabel: (type: TypeSchema) => string;
+  };
+}
+
+export const getTypeDefinitionAdapter = <TypeSchema extends Partial<IJsonSchema>>(
+  manager: TypeEditorRegistryManager<TypeSchema>
+): ITypeDefinitionAdapter<TypeSchema> => {
+  const typeDefinitionManager: ITypeDefinitionManager<TypeSchema> = {
+    getDefinitionByIJsonSchema: (typeSchema?: TypeSchema) =>
+      typeSchema ? manager.getTypeBySchema(typeSchema) : undefined,
+    getAllTypeDefinitions: () => manager.getTypeRegistriesWithParentType(),
+    getDefinitionByType: (type) => manager.getTypeByName(type),
+    getUndefinedIJsonSchema: () => manager.getTypeByName('unknown')!.getDefaultSchema(),
+  };
+
+  return {
+    typeDefinitionManager,
+    utils: {
+      getComposedLabel: (type: TypeSchema) => manager.getComplexText(type),
+    },
+  };
+};

+ 8 - 0
packages/materials/type-editor/tsconfig.json

@@ -0,0 +1,8 @@
+{
+  "extends": "@flowgram.ai/ts-config/tsconfig.flow.path.json",
+  "compilerOptions": {
+    "jsx": "react",
+  },
+  "include": ["./src"],
+  "exclude": ["node_modules"]
+}

+ 31 - 0
packages/materials/type-editor/vitest.config.ts

@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+const path = require('path');
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+  build: {
+    commonjsOptions: {
+      transformMixedEsModules: true,
+    },
+  },
+  test: {
+    globals: true,
+    mockReset: false,
+    environment: 'jsdom',
+    setupFiles: [path.resolve(__dirname, './vitest.setup.ts')],
+    include: ['**/?(*.){test,spec}.?(c|m)[jt]s?(x)'],
+    exclude: [
+      '**/__mocks__**',
+      '**/node_modules/**',
+      '**/dist/**',
+      '**/lib/**', // lib 编译结果忽略掉
+      '**/cypress/**',
+      '**/.{idea,git,cache,output,temp}/**',
+      '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
+    ],
+  },
+});

+ 6 - 0
packages/materials/type-editor/vitest.setup.ts

@@ -0,0 +1,6 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import 'reflect-metadata';

+ 9 - 0
rush.json

@@ -860,6 +860,15 @@
                 "team-flow"
             ]
         },
+        {
+            "packageName": "@flowgram.ai/type-editor",
+            "projectFolder": "packages/materials/type-editor",
+            "versionPolicyName": "publishPolicy",
+            "tags": [
+                "level-1",
+                "team-flow"
+            ]
+        },
         {
             "packageName": "@flowgram.ai/free-auto-layout-plugin",
             "projectFolder": "packages/plugins/free-auto-layout-plugin",