Răsfoiți Sursa

refactor(material): editor with variables (#782)

* chore(extension): coze-editor-extensions

* feat: editor-with-variables max height

* feat: remove console
Yiwei Mao 4 luni în urmă
părinte
comite
a1cf6cf109
15 a modificat fișierele cu 164 adăugiri și 421 ștergeri
  1. 95 5
      packages/materials/form-materials/src/components/coze-editor-extensions/extensions/inputs-tree.tsx
  2. 0 0
      packages/materials/form-materials/src/components/coze-editor-extensions/extensions/variable-tag.tsx
  3. 9 3
      packages/materials/form-materials/src/components/coze-editor-extensions/extensions/variable-tree.tsx
  4. 33 0
      packages/materials/form-materials/src/components/coze-editor-extensions/index.tsx
  5. 0 0
      packages/materials/form-materials/src/components/coze-editor-extensions/styles.tsx
  6. 5 0
      packages/materials/form-materials/src/components/index.ts
  7. 4 4
      packages/materials/form-materials/src/components/json-editor-with-variables/editor.tsx
  8. 0 83
      packages/materials/form-materials/src/components/json-editor-with-variables/extensions/variable-tree.tsx
  9. 2 3
      packages/materials/form-materials/src/components/prompt-editor-with-inputs/editor.tsx
  10. 0 94
      packages/materials/form-materials/src/components/prompt-editor-with-inputs/extensions/inputs-tree.tsx
  11. 3 5
      packages/materials/form-materials/src/components/prompt-editor-with-variables/editor.tsx
  12. 0 174
      packages/materials/form-materials/src/components/prompt-editor-with-variables/extensions/variable-tag.tsx
  13. 0 44
      packages/materials/form-materials/src/components/prompt-editor-with-variables/styles.tsx
  14. 3 0
      packages/materials/form-materials/src/index.ts
  15. 10 6
      packages/materials/form-materials/src/shared/inject-material/index.tsx

+ 95 - 5
packages/materials/form-materials/src/components/prompt-editor-with-inputs/inputs-picker.tsx → packages/materials/form-materials/src/components/coze-editor-extensions/extensions/inputs-tree.tsx

@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: MIT
  */
 
-import React, { useMemo } from 'react';
+import React, { useMemo, useEffect, useState } from 'react';
 
 import { isPlainObject, last } from 'lodash-es';
 import {
@@ -13,10 +13,18 @@ import {
   type BaseVariableField,
   useScopeAvailable,
 } from '@flowgram.ai/editor';
-import { TreeNodeData } from '@douyinfe/semi-ui/lib/es/tree';
-import { Tree } from '@douyinfe/semi-ui';
-
-import { FlowValueUtils } from '@/shared';
+import {
+  Mention,
+  MentionOpenChangeEvent,
+  getCurrentMentionReplaceRange,
+  useEditor,
+  PositionMirror,
+} from '@flowgram.ai/coze-editor/react';
+import { EditorAPI } from '@flowgram.ai/coze-editor/preset-prompt';
+import { type TreeNodeData } from '@douyinfe/semi-ui/lib/es/tree';
+import { Tree, Popover } from '@douyinfe/semi-ui';
+
+import { IFlowValue, FlowValueUtils } from '@/shared';
 
 type VariableField = BaseVariableField<{ icon?: string | JSX.Element; title?: string }>;
 
@@ -115,3 +123,85 @@ export function InputsPicker({
 
   return <Tree treeData={treeData} onSelect={(v) => onSelect(v)} />;
 }
+
+const DEFAULT_TRIGGER_CHARACTERS = ['{', '{}', '@'];
+
+export function InputsTree({
+  inputsValues,
+  triggerCharacters = DEFAULT_TRIGGER_CHARACTERS,
+}: {
+  inputsValues: Record<string, IFlowValue>;
+  triggerCharacters?: string[];
+}) {
+  const [posKey, setPosKey] = useState('');
+  const [visible, setVisible] = useState(false);
+  const [position, setPosition] = useState(-1);
+  const editor = useEditor<EditorAPI>();
+
+  function insert(variablePath: string) {
+    const range = getCurrentMentionReplaceRange(editor.$view.state);
+
+    if (!range) {
+      return;
+    }
+
+    /**
+     * When user input {{xxxx}}, {{{xxx}}}(more brackets if possible), replace all brackets with {{xxxx}}
+     */
+    let { from, to } = range;
+    while (editor.$view.state.doc.sliceString(from - 1, from) === '{') {
+      from--;
+    }
+    while (editor.$view.state.doc.sliceString(to, to + 1) === '}') {
+      to++;
+    }
+
+    editor.replaceText({
+      ...range,
+      text: '{{' + variablePath + '}}',
+    });
+
+    setVisible(false);
+  }
+
+  function handleOpenChange(e: MentionOpenChangeEvent) {
+    setPosition(e.state.selection.main.head);
+    setVisible(e.value);
+  }
+
+  useEffect(() => {
+    if (!editor) {
+      return;
+    }
+  }, [editor, visible]);
+
+  return (
+    <>
+      <Mention triggerCharacters={triggerCharacters} onOpenChange={handleOpenChange} />
+
+      <Popover
+        visible={visible}
+        trigger="custom"
+        position="topLeft"
+        rePosKey={posKey}
+        content={
+          <div style={{ width: 300, maxHeight: 300, overflowY: 'auto' }}>
+            <InputsPicker
+              inputsValues={inputsValues}
+              onSelect={(v) => {
+                insert(v);
+              }}
+            />
+          </div>
+        }
+      >
+        {/* PositionMirror allows the Popover to appear at the specified cursor position */}
+        <PositionMirror
+          position={position}
+          // When Doc scroll, update position
+          onChange={() => setPosKey(String(Math.random()))}
+        />
+      </Popover>
+    </>
+  );
+}

+ 0 - 0
packages/materials/form-materials/src/components/json-editor-with-variables/extensions/variable-tag.tsx → packages/materials/form-materials/src/components/coze-editor-extensions/extensions/variable-tag.tsx


+ 9 - 3
packages/materials/form-materials/src/components/prompt-editor-with-variables/extensions/variable-tree.tsx → packages/materials/form-materials/src/components/coze-editor-extensions/extensions/variable-tree.tsx

@@ -17,7 +17,13 @@ import { Popover, Tree } from '@douyinfe/semi-ui';
 
 import { useVariableTree } from '@/components/variable-selector';
 
-export function VariableTree() {
+const DEFAULT_TRIGGER_CHARACTER = ['{', '{}', '@'];
+
+export function VariableTree({
+  triggerCharacters = DEFAULT_TRIGGER_CHARACTER,
+}: {
+  triggerCharacters?: string[];
+}) {
   const [posKey, setPosKey] = useState('');
   const [visible, setVisible] = useState(false);
   const [position, setPosition] = useState(-1);
@@ -65,7 +71,7 @@ export function VariableTree() {
 
   return (
     <>
-      <Mention triggerCharacters={['{', '{}', '@']} onOpenChange={handleOpenChange} />
+      <Mention triggerCharacters={triggerCharacters} onOpenChange={handleOpenChange} />
 
       <Popover
         visible={visible}
@@ -73,7 +79,7 @@ export function VariableTree() {
         position="topLeft"
         rePosKey={posKey}
         content={
-          <div style={{ width: 300 }}>
+          <div style={{ width: 300, maxHeight: 300, overflowY: 'auto' }}>
             <Tree
               treeData={treeData}
               onSelect={(v) => {

+ 33 - 0
packages/materials/form-materials/src/components/coze-editor-extensions/index.tsx

@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { lazy } from 'react';
+
+import { createInjectMaterial } from '@/shared';
+
+export const EditorVariableTree = createInjectMaterial(
+  lazy(() =>
+    import('./extensions/variable-tree').then((module) => ({ default: module.VariableTree }))
+  ),
+  {
+    renderKey: 'EditorVariableTree',
+  }
+);
+
+export const EditorVariableTagInject = createInjectMaterial(
+  lazy(() =>
+    import('./extensions/variable-tag').then((module) => ({ default: module.VariableTagInject }))
+  ),
+  {
+    renderKey: 'EditorVariableTagInject',
+  }
+);
+
+export const EditorInputsTree = createInjectMaterial(
+  lazy(() => import('./extensions/inputs-tree').then((module) => ({ default: module.InputsTree }))),
+  {
+    renderKey: 'EditorInputsTree',
+  }
+);

+ 0 - 0
packages/materials/form-materials/src/components/json-editor-with-variables/styles.tsx → packages/materials/form-materials/src/components/coze-editor-extensions/styles.tsx


+ 5 - 0
packages/materials/form-materials/src/components/index.ts

@@ -12,6 +12,11 @@ export { CodeEditor, type CodeEditorPropsType } from './code-editor';
 export { CodeEditorMini } from './code-editor-mini';
 export { ConditionRow, type ConditionRowValueType } from './condition-row';
 export { ConstantInput, type ConstantInputStrategy } from './constant-input';
+export {
+  EditorVariableTagInject,
+  EditorVariableTree,
+  EditorInputsTree,
+} from './coze-editor-extensions';
 export {
   DBConditionRow,
   type DBConditionOptionType,

+ 4 - 4
packages/materials/form-materials/src/components/json-editor-with-variables/editor.tsx

@@ -9,10 +9,10 @@ import { I18n } from '@flowgram.ai/editor';
 import { transformerCreator } from '@flowgram.ai/coze-editor/preset-code';
 import { Text } from '@flowgram.ai/coze-editor/language-json';
 
+import { EditorVariableTree, EditorVariableTagInject } from '@/components/coze-editor-extensions';
 import { CodeEditor, type CodeEditorPropsType } from '@/components/code-editor';
 
-import { VariableTree } from './extensions/variable-tree';
-import { VariableTagInject } from './extensions/variable-tag';
+const TRIGGER_CHARACTERS = ['@'];
 
 type Match = { match: string; range: [number, number] };
 function findAllMatches(inputString: string, regex: RegExp): Match[] {
@@ -62,8 +62,8 @@ export function JsonEditorWithVariables(props: JsonEditorWithVariablesProps) {
         ...(props.options || {}),
       }}
     >
-      <VariableTree />
-      <VariableTagInject />
+      <EditorVariableTree triggerCharacters={TRIGGER_CHARACTERS} />
+      <EditorVariableTagInject />
     </CodeEditor>
   );
 }

+ 0 - 83
packages/materials/form-materials/src/components/json-editor-with-variables/extensions/variable-tree.tsx

@@ -1,83 +0,0 @@
-/**
- * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
- * SPDX-License-Identifier: MIT
- */
-
-import React, { useEffect, useState } from 'react';
-
-import {
-  Mention,
-  MentionOpenChangeEvent,
-  getCurrentMentionReplaceRange,
-  useEditor,
-  PositionMirror,
-} from '@flowgram.ai/coze-editor/react';
-import { EditorAPI } from '@flowgram.ai/coze-editor/preset-prompt';
-import { Popover, Tree } from '@douyinfe/semi-ui';
-
-import { useVariableTree } from '@/components/variable-selector';
-
-export function VariableTree() {
-  const [posKey, setPosKey] = useState('');
-  const [visible, setVisible] = useState(false);
-  const [position, setPosition] = useState(-1);
-  const editor = useEditor<EditorAPI>();
-
-  function insert(variablePath: string) {
-    const range = getCurrentMentionReplaceRange(editor.$view.state);
-
-    if (!range) {
-      return;
-    }
-
-    editor.replaceText({
-      ...range,
-      text: '{{' + variablePath + '}}',
-    });
-
-    setVisible(false);
-  }
-
-  function handleOpenChange(e: MentionOpenChangeEvent) {
-    setPosition(e.state.selection.main.head);
-    setVisible(e.value);
-  }
-
-  useEffect(() => {
-    if (!editor) {
-      return;
-    }
-  }, [editor, visible]);
-
-  const treeData = useVariableTree({});
-
-  return (
-    <>
-      <Mention triggerCharacters={['@']} onOpenChange={handleOpenChange} />
-
-      <Popover
-        visible={visible}
-        trigger="custom"
-        position="topLeft"
-        rePosKey={posKey}
-        content={
-          <div style={{ width: 300 }}>
-            <Tree
-              treeData={treeData}
-              onSelect={(v) => {
-                insert(v);
-              }}
-            />
-          </div>
-        }
-      >
-        {/* PositionMirror allows the Popover to appear at the specified cursor position */}
-        <PositionMirror
-          position={position}
-          // When Doc scroll, update position
-          onChange={() => setPosKey(String(Math.random()))}
-        />
-      </Popover>
-    </>
-  );
-}

+ 2 - 3
packages/materials/form-materials/src/components/prompt-editor-with-inputs/editor.tsx

@@ -6,8 +6,7 @@
 import React from 'react';
 
 import { PromptEditor, PromptEditorPropsType } from '@/components/prompt-editor';
-
-import { InputsTree } from './extensions/inputs-tree';
+import { EditorInputsTree } from '@/components/coze-editor-extensions';
 
 export interface PromptEditorWithInputsProps extends PromptEditorPropsType {
   inputsValues: any;
@@ -19,7 +18,7 @@ export function PromptEditorWithInputs({
 }: PromptEditorWithInputsProps) {
   return (
     <PromptEditor {...restProps}>
-      <InputsTree inputsValues={inputsValues} />
+      <EditorInputsTree inputsValues={inputsValues} />
     </PromptEditor>
   );
 }

+ 0 - 94
packages/materials/form-materials/src/components/prompt-editor-with-inputs/extensions/inputs-tree.tsx

@@ -1,94 +0,0 @@
-/**
- * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
- * SPDX-License-Identifier: MIT
- */
-
-import React, { useEffect, useState } from 'react';
-
-import {
-  Mention,
-  MentionOpenChangeEvent,
-  getCurrentMentionReplaceRange,
-  useEditor,
-  PositionMirror,
-} from '@flowgram.ai/coze-editor/react';
-import { EditorAPI } from '@flowgram.ai/coze-editor/preset-prompt';
-import { Popover } from '@douyinfe/semi-ui';
-
-import { IFlowValue } from '@/shared';
-
-import { InputsPicker } from '../inputs-picker';
-
-export function InputsTree({ inputsValues }: { inputsValues: Record<string, IFlowValue> }) {
-  const [posKey, setPosKey] = useState('');
-  const [visible, setVisible] = useState(false);
-  const [position, setPosition] = useState(-1);
-  const editor = useEditor<EditorAPI>();
-
-  function insert(variablePath: string) {
-    const range = getCurrentMentionReplaceRange(editor.$view.state);
-
-    if (!range) {
-      return;
-    }
-
-    /**
-     * When user input {{xxxx}}, {{{xxx}}}(more brackets if possible), replace all brackets with {{xxxx}}
-     */
-    let { from, to } = range;
-    while (editor.$view.state.doc.sliceString(from - 1, from) === '{') {
-      from--;
-    }
-    while (editor.$view.state.doc.sliceString(to, to + 1) === '}') {
-      to++;
-    }
-
-    editor.replaceText({
-      ...range,
-      text: '{{' + variablePath + '}}',
-    });
-
-    setVisible(false);
-  }
-
-  function handleOpenChange(e: MentionOpenChangeEvent) {
-    setPosition(e.state.selection.main.head);
-    setVisible(e.value);
-  }
-
-  useEffect(() => {
-    if (!editor) {
-      return;
-    }
-  }, [editor, visible]);
-
-  return (
-    <>
-      <Mention triggerCharacters={['{', '{}', '@']} onOpenChange={handleOpenChange} />
-
-      <Popover
-        visible={visible}
-        trigger="custom"
-        position="topLeft"
-        rePosKey={posKey}
-        content={
-          <div style={{ width: 300 }}>
-            <InputsPicker
-              inputsValues={inputsValues}
-              onSelect={(v) => {
-                insert(v);
-              }}
-            />
-          </div>
-        }
-      >
-        {/* PositionMirror allows the Popover to appear at the specified cursor position */}
-        <PositionMirror
-          position={position}
-          // When Doc scroll, update position
-          onChange={() => setPosKey(String(Math.random()))}
-        />
-      </Popover>
-    </>
-  );
-}

+ 3 - 5
packages/materials/form-materials/src/components/prompt-editor-with-variables/editor.tsx

@@ -6,17 +6,15 @@
 import React from 'react';
 
 import { PromptEditor, PromptEditorPropsType } from '@/components/prompt-editor';
-
-import { VariableTree } from './extensions/variable-tree';
-import { VariableTagInject } from './extensions/variable-tag';
+import { EditorVariableTree, EditorVariableTagInject } from '@/components/coze-editor-extensions';
 
 export interface PromptEditorWithVariablesProps extends PromptEditorPropsType {}
 
 export function PromptEditorWithVariables(props: PromptEditorWithVariablesProps) {
   return (
     <PromptEditor {...props}>
-      <VariableTree />
-      <VariableTagInject />
+      <EditorVariableTree />
+      <EditorVariableTagInject />
     </PromptEditor>
   );
 }

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

@@ -1,174 +0,0 @@
-/**
- * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
- * SPDX-License-Identifier: MIT
- */
-
-import React, { useLayoutEffect } from 'react';
-
-import { isEqual, last } from 'lodash-es';
-import {
-  BaseVariableField,
-  Disposable,
-  DisposableCollection,
-  Scope,
-  useCurrentScope,
-} from '@flowgram.ai/editor';
-import { useInjector } from '@flowgram.ai/coze-editor/react';
-import { Popover } from '@douyinfe/semi-ui';
-import { IconIssueStroked } from '@douyinfe/semi-icons';
-import {
-  Decoration,
-  DecorationSet,
-  EditorView,
-  MatchDecorator,
-  ViewPlugin,
-  WidgetType,
-} from '@codemirror/view';
-
-import { IPolyfillRoot, polyfillCreateRoot } from '@/shared';
-
-import { UIPopoverContent, UIRootTitle, UITag, UIVarName } from '../styles';
-
-class VariableTagWidget extends WidgetType {
-  keyPath?: string[];
-
-  toDispose = new DisposableCollection();
-
-  scope: Scope;
-
-  root: IPolyfillRoot;
-
-  constructor({ keyPath, scope }: { keyPath?: string[]; scope: Scope }) {
-    super();
-
-    this.keyPath = keyPath;
-    this.scope = scope;
-  }
-
-  renderIcon = (icon: string | JSX.Element) => {
-    if (typeof icon === 'string') {
-      return <img style={{ marginRight: 8 }} width={12} height={12} src={icon} />;
-    }
-
-    return icon;
-  };
-
-  renderVariable(v?: BaseVariableField) {
-    if (!v) {
-      this.root.render(
-        <UITag prefixIcon={<IconIssueStroked />} color="amber">
-          Unknown
-        </UITag>
-      );
-      return;
-    }
-
-    const rootField = last(v.parentFields);
-
-    const rootTitle = (
-      <UIRootTitle>{rootField?.meta.title ? `${rootField.meta.title} -` : ''}</UIRootTitle>
-    );
-    const rootIcon = this.renderIcon(rootField?.meta.icon);
-
-    this.root.render(
-      <Popover
-        content={
-          <UIPopoverContent>
-            {rootIcon}
-            {rootTitle}
-            <UIVarName>{v?.keyPath.slice(1).join('.')}</UIVarName>
-          </UIPopoverContent>
-        }
-      >
-        <UITag prefixIcon={rootIcon}>
-          {rootTitle}
-          <UIVarName>{v?.key}</UIVarName>
-        </UITag>
-      </Popover>
-    );
-  }
-
-  toDOM(view: EditorView): HTMLElement {
-    const dom = document.createElement('span');
-
-    this.root = polyfillCreateRoot(dom);
-
-    this.toDispose.push(
-      Disposable.create(() => {
-        this.root.unmount();
-      })
-    );
-
-    this.toDispose.push(
-      this.scope.available.trackByKeyPath(
-        this.keyPath,
-        (v) => {
-          this.renderVariable(v);
-        },
-        { triggerOnInit: false }
-      )
-    );
-
-    this.renderVariable(this.scope.available.getByKeyPath(this.keyPath));
-
-    return dom;
-  }
-
-  eq(other: VariableTagWidget) {
-    return isEqual(this.keyPath, other.keyPath);
-  }
-
-  ignoreEvent(): boolean {
-    return false;
-  }
-
-  destroy(dom: HTMLElement): void {
-    this.toDispose.dispose();
-  }
-}
-
-export function VariableTagInject() {
-  const injector = useInjector();
-
-  const scope = useCurrentScope();
-
-  // 基于 {{var}} 的正则进行匹配,匹配后进行自定义渲染
-  useLayoutEffect(() => {
-    const atMatcher = new MatchDecorator({
-      regexp: /\{\{([^\}\{]+)\}\}/g,
-      decoration: (match) =>
-        Decoration.replace({
-          widget: new VariableTagWidget({
-            keyPath: match[1]?.split('.') ?? [],
-            scope,
-          }),
-        }),
-    });
-
-    return injector.inject([
-      ViewPlugin.fromClass(
-        class {
-          decorations: DecorationSet;
-
-          constructor(private view: EditorView) {
-            this.decorations = atMatcher.createDeco(view);
-          }
-
-          update() {
-            this.decorations = atMatcher.createDeco(this.view);
-          }
-        },
-        {
-          decorations: (p) => p.decorations,
-          provide(p) {
-            return EditorView.atomicRanges.of(
-              (view) => view.plugin(p)?.decorations ?? Decoration.none
-            );
-          },
-        }
-      ),
-    ]);
-  }, [injector]);
-
-  return null;
-}

+ 0 - 44
packages/materials/form-materials/src/components/prompt-editor-with-variables/styles.tsx

@@ -1,44 +0,0 @@
-/**
- * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
- * SPDX-License-Identifier: MIT
- */
-
-import styled from 'styled-components';
-import { Tag } from '@douyinfe/semi-ui';
-
-export const UIRootTitle = styled.div`
-  margin-right: 4px;
-  min-width: 20px;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-  color: var(--semi-color-text-2);
-`;
-
-export const UIVarName = styled.div`
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-`;
-
-export const UITag = styled(Tag)`
-  display: inline-flex;
-  align-items: center;
-  justify-content: flex-start;
-  max-width: 300px;
-
-  & .semi-tag-content-center {
-    justify-content: flex-start;
-  }
-
-  &.semi-tag {
-    margin: 0 5px;
-  }
-`;
-
-export const UIPopoverContent = styled.div`
-  padding: 10px;
-  display: inline-flex;
-  align-items: center;
-  justify-content: flex-start;
-`;

+ 3 - 0
packages/materials/form-materials/src/index.ts

@@ -34,6 +34,9 @@ export {
   TypeSelector,
   VariableSelector,
   VariableSelectorProvider,
+  EditorVariableTagInject,
+  EditorVariableTree,
+  EditorInputsTree,
   getTypeSelectValue,
   parseTypeSelectValue,
   type AssignValueType,

+ 10 - 6
packages/materials/form-materials/src/shared/inject-material/index.tsx

@@ -11,6 +11,8 @@ import {
   usePlaygroundContainer,
 } from '@flowgram.ai/editor';
 
+type WithRenderKey<T> = T & { renderKey?: string };
+
 /**
  * Creates a material component wrapper with dependency injection support
  *
@@ -48,12 +50,15 @@ import {
  * @returns Wrapper component with dependency injection support
  */
 export function createInjectMaterial<Props>(
-  Component: React.FC<Props> & { renderKey?: string },
+  Component: WithRenderKey<React.FC<Props> | React.ExoticComponent<Props>>,
   params?: {
     renderKey?: string;
   }
-): React.FC<Props> {
-  const InjectComponent: React.FC<Props> = (props) => {
+): WithRenderKey<React.FC<Props>> {
+  // Determine render key: prioritize param specified, then component renderKey, finally component name
+  const renderKey = params?.renderKey || Component.renderKey || Component.name || '';
+
+  const InjectComponent: WithRenderKey<React.FC<Props>> = (props) => {
     const container = usePlaygroundContainer();
 
     // Check if renderer registry is bound in container
@@ -65,9 +70,6 @@ export function createInjectMaterial<Props>(
     // Get renderer registry instance
     const rendererRegistry = container.get(FlowRendererRegistry);
 
-    // Determine render key: prioritize param specified, then component renderKey, finally component name
-    const renderKey = params?.renderKey || Component.renderKey || Component.name || '';
-
     // Get corresponding renderer from registry
     const renderer = rendererRegistry.tryToGetRendererComponent(renderKey);
 
@@ -83,5 +85,7 @@ export function createInjectMaterial<Props>(
     });
   };
 
+  InjectComponent.renderKey = renderKey;
+
   return InjectComponent;
 }