refactor node
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
import React, { memo, useCallback, useContext, useMemo, useRef } from 'react'
|
||||
import React, { useCallback, useMemo, useRef } from 'react'
|
||||
import { autocompletion } from '@codemirror/autocomplete'
|
||||
import CodeMirror from '@uiw/react-codemirror'
|
||||
import { javascript } from '@codemirror/lang-javascript'
|
||||
import { markdown } from '@codemirror/lang-markdown'
|
||||
import FlowContext from '../../lib/flowContext'
|
||||
import { nodePropsAreEqual } from '../../lib/flowUtils'
|
||||
import {
|
||||
AbstractNodeProps,
|
||||
createAbstractNodeComponent,
|
||||
useAbstractNode,
|
||||
type FlowNode,
|
||||
} from '../../lib/abstractNode'
|
||||
import { useResizeHeight } from '../../hooks/useResizeHeight'
|
||||
import { nunjucksCompletionSource } from '../../lib/nunjucksAutocomplete'
|
||||
import { plantumlLanguage } from '../../lib/plantumlLanguage'
|
||||
@@ -39,73 +43,46 @@ import { NodeFooterEdgeIndicators } from '../base/NodeFooterEdgeIndicators'
|
||||
import { NodeHeaderTitle } from '../base/NodeHeaderTitle'
|
||||
import { NodeMenubar } from '../base/NodeMenubar'
|
||||
|
||||
type Props = {
|
||||
id: string
|
||||
data: any
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
export type ConfigNodeData = { configType?: ConfigTypeId; content?: string; title?: string }
|
||||
|
||||
export const ConfigNode = memo(function ConfigNode({ id, data, width, height }: Props) {
|
||||
const configTypeId = getConfigTypeId(data)
|
||||
type Props = AbstractNodeProps<ConfigNodeData>
|
||||
|
||||
function ConfigNodeComponent({ id, data, width, height }: Props) {
|
||||
const configTypeId = getConfigTypeId(data ?? {})
|
||||
const configType = getConfigType(configTypeId)
|
||||
const content = getConfigContent(data)
|
||||
const content = getConfigContent(data ?? {})
|
||||
const { theme } = useTheme()
|
||||
const ctx = useContext(FlowContext)
|
||||
const setNodes = ctx?.setNodes
|
||||
|
||||
const { nodes, sourceIds, updateData } = useAbstractNode<ConfigNodeData>(id, data ?? {})
|
||||
const editorRef = useRef<unknown>(null)
|
||||
|
||||
const edges = ctx?.edges ?? []
|
||||
const nodes = ctx?.nodes ?? []
|
||||
const incomingEdges = useMemo(() => edges.filter((e: any) => e.target === id), [edges, id])
|
||||
const incomingIds = useMemo(() => incomingEdges.map((e: any) => e.source).sort(), [incomingEdges])
|
||||
const connectedConfigNodes = useMemo(
|
||||
() => (nodes as any[]).filter((n: any) => incomingIds.includes(n.id) && n.type === 'config'),
|
||||
[nodes, incomingIds]
|
||||
() => (nodes as FlowNode[]).filter((n) => sourceIds.includes(n.id) && n.type === 'config'),
|
||||
[nodes, sourceIds]
|
||||
)
|
||||
const connectedVariableNodes = useMemo(
|
||||
() => (nodes as any[]).filter((n: any) => incomingIds.includes(n.id) && n.type === 'variable'),
|
||||
[nodes, incomingIds]
|
||||
() => (nodes as FlowNode[]).filter((n) => sourceIds.includes(n.id) && n.type === 'variable'),
|
||||
[nodes, sourceIds]
|
||||
)
|
||||
const connectedFunctionNodes = useMemo(
|
||||
() => (nodes as any[]).filter((n: any) => incomingIds.includes(n.id) && n.type === 'function'),
|
||||
[nodes, incomingIds]
|
||||
() => (nodes as FlowNode[]).filter((n) => sourceIds.includes(n.id) && n.type === 'function'),
|
||||
[nodes, sourceIds]
|
||||
)
|
||||
const hasDependencies = connectedConfigNodes.length > 0 || connectedVariableNodes.length > 0 || connectedFunctionNodes.length > 0
|
||||
|
||||
const onChange = useCallback(
|
||||
(val: string) => {
|
||||
if (setNodes) {
|
||||
setNodes((nds: any[]) =>
|
||||
nds.map((n) =>
|
||||
n.id === id ? { ...n, data: { ...n.data, content: val, configType: configTypeId } } : n
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
[id, setNodes, configTypeId]
|
||||
(val: string) => updateData({ content: val, configType: configTypeId }),
|
||||
[updateData, configTypeId]
|
||||
)
|
||||
|
||||
const setConfigType = useCallback(
|
||||
(newTypeId: ConfigTypeId) => {
|
||||
if (!setNodes || newTypeId === configTypeId) return
|
||||
setNodes((nds: any[]) =>
|
||||
nds.map((n) =>
|
||||
n.id === id
|
||||
? {
|
||||
...n,
|
||||
data: {
|
||||
...n.data,
|
||||
if (newTypeId === configTypeId) return
|
||||
updateData({
|
||||
configType: newTypeId,
|
||||
content: getConfigContent(n.data) ?? '',
|
||||
content: getConfigContent({ ...data, configType: newTypeId }) ?? '',
|
||||
})
|
||||
},
|
||||
}
|
||||
: n
|
||||
)
|
||||
)
|
||||
},
|
||||
[id, setNodes, configTypeId]
|
||||
[configTypeId, data, updateData]
|
||||
)
|
||||
|
||||
const insertAt = useCallback(
|
||||
@@ -369,8 +346,11 @@ export const ConfigNode = memo(function ConfigNode({ id, data, width, height }:
|
||||
</BaseNodeFooter>
|
||||
</BaseNode>
|
||||
)
|
||||
}, nodePropsAreEqual)
|
||||
}
|
||||
|
||||
ConfigNode.displayName = 'ConfigNode'
|
||||
export const ConfigNode = createAbstractNodeComponent<ConfigNodeData>(
|
||||
'ConfigNode',
|
||||
ConfigNodeComponent
|
||||
)
|
||||
|
||||
export default ConfigNode
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import React, { memo, useCallback, useContext, useMemo, useRef } from 'react'
|
||||
import React, { useCallback, useMemo, useRef } from 'react'
|
||||
import CodeMirror from '@uiw/react-codemirror'
|
||||
import { javascript } from '@codemirror/lang-javascript'
|
||||
import FlowContext from '../../lib/flowContext'
|
||||
import { nodePropsAreEqual } from '../../lib/flowUtils'
|
||||
import {
|
||||
AbstractNodeProps,
|
||||
createAbstractNodeComponent,
|
||||
useAbstractNode,
|
||||
type FlowNode,
|
||||
} from '../../lib/abstractNode'
|
||||
import { useResizeHeight } from '../../hooks/useResizeHeight'
|
||||
import { useTheme } from '../../lib/themeContext'
|
||||
import {
|
||||
@@ -19,45 +23,29 @@ import { MenubarItem, MenubarShortcut } from '../ui/menubar'
|
||||
import { Kbd } from '../ui/kbd'
|
||||
import { Code2, Variable } from 'lucide-react'
|
||||
|
||||
type Props = {
|
||||
id: string
|
||||
data: { body?: string }
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
export type FunctionNodeData = { body?: string }
|
||||
|
||||
export const FunctionNode = memo(function FunctionNode({ id, data, width, height }: Props) {
|
||||
type Props = AbstractNodeProps<FunctionNodeData>
|
||||
|
||||
function FunctionNodeComponent({ id, data, width, height }: Props) {
|
||||
const bodyValue = data?.body ?? ''
|
||||
const { theme } = useTheme()
|
||||
const ctx = useContext(FlowContext)
|
||||
const setNodes = ctx?.setNodes
|
||||
const { nodes, sourceIds, updateData } = useAbstractNode<FunctionNodeData>(id, data ?? {})
|
||||
const editorRef = useRef<unknown>(null)
|
||||
|
||||
const edges = ctx?.edges ?? []
|
||||
const nodes = ctx?.nodes ?? []
|
||||
const incomingEdges = useMemo(() => edges.filter((e: any) => e.target === id), [edges, id])
|
||||
const incomingIds = useMemo(() => incomingEdges.map((e: any) => e.source).sort(), [incomingEdges])
|
||||
const connectedVariableNodes = useMemo(
|
||||
() => (nodes as any[]).filter((n: any) => incomingIds.includes(n.id) && n.type === 'variable'),
|
||||
[nodes, incomingIds]
|
||||
() => (nodes as FlowNode[]).filter((n) => sourceIds.includes(n.id) && n.type === 'variable'),
|
||||
[nodes, sourceIds]
|
||||
)
|
||||
const connectedFunctionNodes = useMemo(
|
||||
() => (nodes as any[]).filter((n: any) => incomingIds.includes(n.id) && n.type === 'function'),
|
||||
[nodes, incomingIds]
|
||||
() => (nodes as FlowNode[]).filter((n) => sourceIds.includes(n.id) && n.type === 'function'),
|
||||
[nodes, sourceIds]
|
||||
)
|
||||
const hasConnectedVariables = connectedVariableNodes.length > 0
|
||||
const hasConnectedFunctions = connectedFunctionNodes.length > 0
|
||||
const hasConnectedInputs = hasConnectedVariables || hasConnectedFunctions
|
||||
const hasConnectedInputs = connectedVariableNodes.length > 0 || connectedFunctionNodes.length > 0
|
||||
|
||||
const onChange = useCallback(
|
||||
(val: string) => {
|
||||
if (setNodes) {
|
||||
setNodes((nds: any[]) =>
|
||||
nds.map((n) => (n.id === id ? { ...n, data: { ...n.data, body: val } } : n))
|
||||
)
|
||||
}
|
||||
},
|
||||
[id, setNodes]
|
||||
(val: string) => updateData({ body: val }),
|
||||
[updateData]
|
||||
)
|
||||
|
||||
const insertAt = useCallback(
|
||||
@@ -165,8 +153,11 @@ export const FunctionNode = memo(function FunctionNode({ id, data, width, height
|
||||
</BaseNodeFooter>
|
||||
</BaseNode>
|
||||
)
|
||||
}, nodePropsAreEqual)
|
||||
}
|
||||
|
||||
FunctionNode.displayName = 'FunctionNode'
|
||||
export const FunctionNode = createAbstractNodeComponent<FunctionNodeData>(
|
||||
'FunctionNode',
|
||||
FunctionNodeComponent
|
||||
)
|
||||
|
||||
export default FunctionNode
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import nunjucks from 'nunjucks'
|
||||
import FlowContext from '../../lib/flowContext'
|
||||
import {
|
||||
AbstractNodeProps,
|
||||
createAbstractNodeComponent,
|
||||
useAbstractNode,
|
||||
} from '../../lib/abstractNode'
|
||||
import { getConfigContent, getConfigType, getConfigTypeId } from '../../lib/configTypes'
|
||||
import { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia } from '../ui/empty'
|
||||
import {
|
||||
@@ -9,7 +13,7 @@ import {
|
||||
BaseNodeFooter,
|
||||
BaseNodeHeaderRow,
|
||||
} from '../base/BaseNode'
|
||||
import { getDefaultDataForType, getNextNodeId, nodePropsAreEqual } from '../../lib/flowUtils'
|
||||
import { getDefaultDataForType, getNextNodeId } from '../../lib/flowUtils'
|
||||
import { getDefaultStyle } from '../../lib/nodeRegistry'
|
||||
import { NodeFooterEdgeIndicators } from '../base/NodeFooterEdgeIndicators'
|
||||
import { NodeHeaderTitle } from '../base/NodeHeaderTitle'
|
||||
@@ -19,27 +23,20 @@ import { MenubarItem, MenubarSeparator, MenubarSub, MenubarSubContent, MenubarSu
|
||||
import { Sparkles } from 'lucide-react'
|
||||
import { InputHandle } from '../base/NodeHandles'
|
||||
|
||||
type Props = {
|
||||
id: string
|
||||
data?: any
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
export type RenderingNodeData = Record<string, unknown>
|
||||
|
||||
export const RenderingNode = memo(function RenderingNode({ id, width, height }: Props) {
|
||||
type Props = AbstractNodeProps<RenderingNodeData>
|
||||
|
||||
function RenderingNodeComponent({ id, width, height }: Props) {
|
||||
const [renderedContent, setRenderedContent] = useState<string | null>(null)
|
||||
const [error, setError] = useState<null | { kind: string; message: string }>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const runIdRef = useRef(0)
|
||||
const loadingStartedAtRef = useRef<number | null>(null)
|
||||
const minLoadingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
const ctx = useContext(FlowContext)
|
||||
const nodes = ctx?.nodes ?? []
|
||||
const edges = ctx?.edges ?? []
|
||||
const setNodes = ctx?.setNodes
|
||||
const setEdges = ctx?.setEdges
|
||||
const { nodes, edges, setNodes, setEdges, sourceIds } = useAbstractNode<RenderingNodeData>(id, {})
|
||||
|
||||
const incomingEdges = useMemo(() => edges.filter((e: any) => e.target === id), [edges, id])
|
||||
const incomingIds = useMemo(() => incomingEdges.map((e: any) => e.source).sort(), [incomingEdges])
|
||||
const incomingIds = sourceIds
|
||||
const srcId = incomingIds.length > 0 ? incomingIds[0] : null
|
||||
const srcNode = nodes.find((n: any) => n.id === srcId)
|
||||
const configTypeId = srcNode?.type === 'config' ? getConfigTypeId(srcNode.data) : 'plantuml'
|
||||
@@ -619,8 +616,11 @@ export const RenderingNode = memo(function RenderingNode({ id, width, height }:
|
||||
</BaseNode>
|
||||
</NodeStatusIndicator>
|
||||
)
|
||||
}, nodePropsAreEqual)
|
||||
}
|
||||
|
||||
RenderingNode.displayName = 'RenderingNode'
|
||||
export const RenderingNode = createAbstractNodeComponent<RenderingNodeData>(
|
||||
'RenderingNode',
|
||||
RenderingNodeComponent
|
||||
)
|
||||
|
||||
export default RenderingNode
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import React, { memo, useCallback, useContext } from 'react'
|
||||
import FlowContext from '../../lib/flowContext'
|
||||
import { nodePropsAreEqual } from '../../lib/flowUtils'
|
||||
import React, { useCallback } from 'react'
|
||||
import {
|
||||
AbstractNodeProps,
|
||||
createAbstractNodeComponent,
|
||||
useAbstractNode,
|
||||
} from '../../lib/abstractNode'
|
||||
import {
|
||||
BaseNode,
|
||||
BaseNodeContent,
|
||||
@@ -16,16 +19,15 @@ import { NodeHeaderTitle } from '../base/NodeHeaderTitle'
|
||||
import { OutputHandle } from '../base/NodeHandles'
|
||||
import { Variable } from 'lucide-react'
|
||||
|
||||
type ValueType = 'string' | 'number' | 'boolean'
|
||||
export type ValueType = 'string' | 'number' | 'boolean'
|
||||
|
||||
type Props = {
|
||||
id: string
|
||||
data: {
|
||||
export type VariableNodeData = {
|
||||
value?: string | number | boolean
|
||||
valueType?: ValueType
|
||||
}
|
||||
}
|
||||
|
||||
type Props = AbstractNodeProps<VariableNodeData>
|
||||
|
||||
const DEFAULT_BY_TYPE: Record<ValueType, string | number | boolean> = {
|
||||
string: '',
|
||||
number: 0,
|
||||
@@ -45,26 +47,13 @@ function coerceValue(raw: string, valueType: ValueType): string | number | boole
|
||||
}
|
||||
}
|
||||
|
||||
export const VariableNode = memo(function VariableNode({ id, data }: Props) {
|
||||
const ctx = useContext(FlowContext)
|
||||
const setNodes = ctx?.setNodes
|
||||
function VariableNodeComponent({ id, data }: Props) {
|
||||
const { updateData } = useAbstractNode<VariableNodeData>(id, data ?? {})
|
||||
|
||||
const valueType: ValueType = data?.valueType ?? 'string'
|
||||
const value = data?.value ?? DEFAULT_BY_TYPE[valueType]
|
||||
const displayValue = typeof value === 'string' ? value : String(value)
|
||||
|
||||
const updateData = useCallback(
|
||||
(updates: { value?: string | number | boolean; valueType?: ValueType }) => {
|
||||
if (!setNodes) return
|
||||
setNodes((nds: any[]) =>
|
||||
nds.map((n) =>
|
||||
n.id === id ? { ...n, data: { ...n.data, ...updates } } : n
|
||||
)
|
||||
)
|
||||
},
|
||||
[id, setNodes]
|
||||
)
|
||||
|
||||
const onTypeChange = useCallback(
|
||||
(nextType: string) => {
|
||||
const type = nextType as ValueType
|
||||
@@ -85,9 +74,7 @@ export const VariableNode = memo(function VariableNode({ id, data }: Props) {
|
||||
)
|
||||
|
||||
const onBooleanChange = useCallback(
|
||||
(checked: boolean) => {
|
||||
updateData({ value: checked })
|
||||
},
|
||||
(checked: boolean) => updateData({ value: checked }),
|
||||
[updateData]
|
||||
)
|
||||
|
||||
@@ -141,8 +128,11 @@ export const VariableNode = memo(function VariableNode({ id, data }: Props) {
|
||||
</BaseNodeFooter>
|
||||
</BaseNode>
|
||||
)
|
||||
}, nodePropsAreEqual)
|
||||
}
|
||||
|
||||
VariableNode.displayName = 'VariableNode'
|
||||
export const VariableNode = createAbstractNodeComponent<VariableNodeData>(
|
||||
'VariableNode',
|
||||
VariableNodeComponent
|
||||
)
|
||||
|
||||
export default VariableNode
|
||||
|
||||
135
src/lib/abstractNode.ts
Normal file
135
src/lib/abstractNode.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Abstract node layer: shared types, hook, and factory for flow node components.
|
||||
*
|
||||
* - **AbstractNodeProps<TData>** — Typed props (id, data, width?, height?, selected?) for your node.
|
||||
* - **useAbstractNode(id, data)** — Flow context plus helpers: nodes, edges, setNodes, setEdges,
|
||||
* updateData(partial), incomingEdges, outgoingEdges, sourceIds, targetIds.
|
||||
* - **createAbstractNodeComponent(displayName, Component)** — Wraps with memo + nodePropsAreEqual.
|
||||
*
|
||||
* Example: define NodeData type, Props = AbstractNodeProps<NodeData>, use useAbstractNode in the
|
||||
* component, then export const MyNode = createAbstractNodeComponent('MyNode', MyNodeComponent).
|
||||
*/
|
||||
|
||||
import React, { useCallback, useContext, useMemo } from 'react'
|
||||
import FlowContext from './flowContext'
|
||||
import { nodePropsAreEqual } from './flowUtils'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Props passed by React Flow to custom node components. Extend data with your node's shape. */
|
||||
export type AbstractNodeProps<TData = Record<string, unknown>> = {
|
||||
id: string
|
||||
data: TData
|
||||
width?: number
|
||||
height?: number
|
||||
selected?: boolean
|
||||
}
|
||||
|
||||
/** Edge shape used in flow context (minimal for connection logic). */
|
||||
export type FlowEdge = { id: string; source: string; target: string; [k: string]: unknown }
|
||||
|
||||
/** Node shape used in flow context (minimal for reading graph). */
|
||||
export type FlowNode = { id: string; type?: string; data?: unknown; position?: { x: number; y: number }; [k: string]: unknown }
|
||||
|
||||
/** Result of useAbstractNode: flow context plus helpers scoped to this node. */
|
||||
export type AbstractNodeContext<TData = Record<string, unknown>> = {
|
||||
id: string
|
||||
data: TData
|
||||
nodes: FlowNode[]
|
||||
edges: FlowEdge[]
|
||||
setNodes: (updater: (nodes: FlowNode[]) => FlowNode[]) => void
|
||||
setEdges: (updater: (edges: FlowEdge[]) => FlowEdge[]) => void
|
||||
/** Merge partial data into this node's data. Stable reference. */
|
||||
updateData: (partial: Partial<TData>) => void
|
||||
/** Incoming edge IDs (edges whose target is this node). */
|
||||
incomingEdges: FlowEdge[]
|
||||
/** Outgoing edge IDs (edges whose source is this node). */
|
||||
outgoingEdges: FlowEdge[]
|
||||
/** Source node IDs connected to this node (incoming). */
|
||||
sourceIds: string[]
|
||||
/** Target node IDs this node connects to (outgoing). */
|
||||
targetIds: string[]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hook
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Provides flow context and helpers for the current node. Use in any node component
|
||||
* that receives id and data; updateData(partial) merges into this node's data.
|
||||
*/
|
||||
export function useAbstractNode<TData = Record<string, unknown>>(
|
||||
id: string,
|
||||
data: TData
|
||||
): AbstractNodeContext<TData> {
|
||||
const ctx = useContext(FlowContext)
|
||||
const nodes = ctx?.nodes ?? []
|
||||
const edges = ctx?.edges ?? []
|
||||
const setNodes = ctx?.setNodes
|
||||
const setEdges = ctx?.setEdges
|
||||
|
||||
const updateData = useCallback(
|
||||
(partial: Partial<TData>) => {
|
||||
if (!setNodes) return
|
||||
setNodes((nds: FlowNode[]) =>
|
||||
nds.map((n) =>
|
||||
n.id === id ? { ...n, data: { ...(n.data as object), ...partial } } : n
|
||||
)
|
||||
)
|
||||
},
|
||||
[id, setNodes]
|
||||
)
|
||||
|
||||
const incomingEdges = useMemo(
|
||||
() => (edges as FlowEdge[]).filter((e) => e.target === id),
|
||||
[edges, id]
|
||||
)
|
||||
const outgoingEdges = useMemo(
|
||||
() => (edges as FlowEdge[]).filter((e) => e.source === id),
|
||||
[edges, id]
|
||||
)
|
||||
const sourceIds = useMemo(
|
||||
() => incomingEdges.map((e) => e.source).sort(),
|
||||
[incomingEdges]
|
||||
)
|
||||
const targetIds = useMemo(
|
||||
() => outgoingEdges.map((e) => e.target).sort(),
|
||||
[outgoingEdges]
|
||||
)
|
||||
|
||||
return {
|
||||
id,
|
||||
data,
|
||||
nodes,
|
||||
edges,
|
||||
setNodes: setNodes ?? (() => {}),
|
||||
setEdges: setEdges ?? (() => {}),
|
||||
updateData,
|
||||
incomingEdges,
|
||||
outgoingEdges,
|
||||
sourceIds,
|
||||
targetIds,
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Component factory
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Wraps a node component with React.memo and nodePropsAreEqual so only id/data/width/height/selected
|
||||
* changes trigger re-renders. Use with AbstractNodeProps<TData> for typed props.
|
||||
*/
|
||||
export function createAbstractNodeComponent<TData = Record<string, unknown>>(
|
||||
displayName: string,
|
||||
Component: React.ComponentType<AbstractNodeProps<TData>>
|
||||
): React.MemoExoticComponent<React.ComponentType<AbstractNodeProps<TData>>> {
|
||||
const Wrapped = React.memo(Component, nodePropsAreEqual) as React.MemoExoticComponent<
|
||||
React.ComponentType<AbstractNodeProps<TData>>
|
||||
>
|
||||
Wrapped.displayName = displayName
|
||||
return Wrapped
|
||||
}
|
||||
Reference in New Issue
Block a user