Преглед изворни кода

feat(materials): polyfill create root (#730)

Yiwei Mao пре 4 месеци
родитељ
комит
fac7bad94f

+ 4 - 3
packages/materials/form-materials/src/components/json-editor-with-variables/extensions/variable-tag.tsx

@@ -5,7 +5,6 @@
 
 import React, { useLayoutEffect } from 'react';
 
-import { createRoot, Root } from 'react-dom/client';
 import { isEqual, last } from 'lodash';
 import {
   BaseVariableField,
@@ -26,6 +25,8 @@ import {
   WidgetType,
 } from '@codemirror/view';
 
+import { IPolyfillRoot, polyfillCreateRoot } from '@/shared';
+
 import { UIPopoverContent, UIRootTitle, UITag, UIVarName } from '../styles';
 
 class VariableTagWidget extends WidgetType {
@@ -35,7 +36,7 @@ class VariableTagWidget extends WidgetType {
 
   scope: Scope;
 
-  root: Root;
+  root: IPolyfillRoot;
 
   constructor({ keyPath, scope }: { keyPath?: string[]; scope: Scope }) {
     super();
@@ -90,7 +91,7 @@ class VariableTagWidget extends WidgetType {
   toDOM(view: EditorView): HTMLElement {
     const dom = document.createElement('span');
 
-    this.root = createRoot(dom);
+    this.root = polyfillCreateRoot(dom);
 
     this.toDispose.push(
       Disposable.create(() => {

+ 10 - 18
packages/materials/form-materials/src/components/prompt-editor-with-variables/extensions/variable-tag.tsx

@@ -3,7 +3,6 @@
  * SPDX-License-Identifier: MIT
  */
 
-import ReactDOM from 'react-dom';
 import React, { useLayoutEffect } from 'react';
 
 import { isEqual, last } from 'lodash';
@@ -26,6 +25,8 @@ import {
   WidgetType,
 } from '@codemirror/view';
 
+import { IPolyfillRoot, polyfillCreateRoot } from '@/shared';
+
 import { UIPopoverContent, UIRootTitle, UITag, UIVarName } from '../styles';
 
 class VariableTagWidget extends WidgetType {
@@ -35,7 +36,7 @@ class VariableTagWidget extends WidgetType {
 
   scope: Scope;
 
-  rootDOM?: HTMLSpanElement;
+  root: IPolyfillRoot;
 
   constructor({ keyPath, scope }: { keyPath?: string[]; scope: Scope }) {
     super();
@@ -52,14 +53,9 @@ class VariableTagWidget extends WidgetType {
     return icon;
   };
 
-  renderReact(jsx: React.ReactElement) {
-    // NOTICE: For React 19, upgrade to 'react-dom/client'
-    ReactDOM.render(jsx, this.rootDOM!);
-  }
-
   renderVariable(v?: BaseVariableField) {
     if (!v) {
-      this.renderReact(
+      this.root.render(
         <UITag prefixIcon={<IconIssueStroked />} color="amber">
           Unknown
         </UITag>
@@ -67,17 +63,14 @@ class VariableTagWidget extends WidgetType {
       return;
     }
 
-    const rootField = last(v.parentFields) || v;
-    const isRoot = v.parentFields.length === 0;
+    const rootField = last(v.parentFields);
 
     const rootTitle = (
-      <UIRootTitle>
-        {rootField?.meta.title ? `${rootField.meta.title} ${isRoot ? '' : '-'} ` : ''}
-      </UIRootTitle>
+      <UIRootTitle>{rootField?.meta.title ? `${rootField.meta.title} -` : ''}</UIRootTitle>
     );
     const rootIcon = this.renderIcon(rootField?.meta.icon);
 
-    this.renderReact(
+    this.root.render(
       <Popover
         content={
           <UIPopoverContent>
@@ -89,7 +82,7 @@ class VariableTagWidget extends WidgetType {
       >
         <UITag prefixIcon={rootIcon}>
           {rootTitle}
-          {!isRoot && <UIVarName>{v?.key}</UIVarName>}
+          <UIVarName>{v?.key}</UIVarName>
         </UITag>
       </Popover>
     );
@@ -98,12 +91,11 @@ class VariableTagWidget extends WidgetType {
   toDOM(view: EditorView): HTMLElement {
     const dom = document.createElement('span');
 
-    this.rootDOM = dom;
+    this.root = polyfillCreateRoot(dom);
 
     this.toDispose.push(
       Disposable.create(() => {
-        // NOTICE: For React 19, upgrade to 'react-dom/client'
-        ReactDOM.unmountComponentAtNode(this.rootDOM!);
+        this.root.unmount();
       })
     );
 

+ 1 - 0
packages/materials/form-materials/src/shared/index.ts

@@ -6,3 +6,4 @@
 export * from './format-legacy-refs';
 export * from './inject-material';
 export * from './flow-value';
+export * from './polyfill-create-root';

+ 33 - 0
packages/materials/form-materials/src/shared/polyfill-create-root/index.tsx

@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import * as ReactDOM from 'react-dom';
+
+export interface IPolyfillRoot {
+  render(children: React.ReactNode): void;
+  unmount(): void;
+}
+
+/**
+ * React 18 polyfill
+ * @param dom
+ * @returns
+ */
+let unstableCreateRoot = (dom: HTMLElement): IPolyfillRoot => ({
+  render(children: JSX.Element) {
+    ReactDOM.render(children, dom);
+  },
+  unmount() {
+    ReactDOM.unmountComponentAtNode(dom);
+  },
+});
+
+export function polyfillCreateRoot(dom: HTMLElement): IPolyfillRoot {
+  return unstableCreateRoot(dom);
+}
+
+export function unstableSetCreateRoot(createRoot: (dom: HTMLElement) => IPolyfillRoot) {
+  unstableCreateRoot = createRoot;
+}

+ 1 - 3
packages/plugins/node-variable-plugin/src/form-v2/create-provider-effect.ts

@@ -20,7 +20,7 @@ export function createEffectFromVariableProvider(
   const getScope = (node: FlowNodeEntity): Scope => {
     const variableData: FlowNodeVariableData = node.getData(FlowNodeVariableData);
 
-    if (options.private) {
+    if (options.private || options.scope === 'private') {
       return variableData.initPrivate();
     }
     return variableData.public;
@@ -39,7 +39,6 @@ export function createEffectFromVariableProvider(
         node,
         scope,
         options,
-        formItem: undefined,
       }),
     });
   };
@@ -55,7 +54,6 @@ export function createEffectFromVariableProvider(
           node: context.node,
           scope,
           options,
-          formItem: undefined,
         });
 
         if (disposable) {

+ 3 - 5
packages/plugins/node-variable-plugin/src/types.ts

@@ -9,12 +9,10 @@ import {
   type VariableDeclarationJSON,
 } from '@flowgram.ai/variable-plugin';
 import { Disposable } from '@flowgram.ai/utils';
-import { FormItem } from '@flowgram.ai/form-core';
 import { FlowNodeEntity } from '@flowgram.ai/document';
 
 export interface VariableAbilityCommonContext {
   node: FlowNodeEntity; // 节点
-  formItem?: FormItem; // 表单项 (节点引擎 V2 不存在,所以可能为空)
   scope: Scope; // 作用域
   options: VariableAbilityOptions;
 }
@@ -24,7 +22,9 @@ export interface VariableAbilityInitCtx extends VariableAbilityCommonContext {}
 export interface VariableAbilityOptions {
   // 变量提供能力可复用
   key?: string;
-  // 输出的变量为私有作用域的变量
+  /**
+   * @deprecated use scope: 'private'
+   */
   private?: boolean;
   // 生成 AST 在抽象语法树中的索引,默认用 formItem 的 Path 作为 namespace
   namespace?: string;
@@ -32,8 +32,6 @@ export interface VariableAbilityOptions {
   scope?: 'private' | 'public';
   // 初始化,可以进行额外的操作监听
   onInit?: (ctx: VariableAbilityInitCtx) => Disposable | undefined;
-  // Hack: 老表单引擎使用 Hack 字段,在 FormItem 移除时不移除变量 AST,用于老表单部分复杂联动场景
-  disableRemoveASTWhenFormItemDispose?: boolean;
   // 扩展字段,可以在 ability onInit 时进行一些额外操作
   [key: string]: any;
 }