Files
coze-studio/frontend/packages/workflow/playground/src/hooks/use-ref-input/use-ref-input-node.tsx
fanlv 890153324f feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
2025-07-20 17:36:12 +08:00

218 lines
6.1 KiB
TypeScript

/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
useCallback,
type ReactNode,
type CSSProperties,
useState,
} from 'react';
import classNames from 'classnames';
import {
ValueExpression,
ValueExpressionType,
type RefExpression,
type ViewVariableType,
} from '@coze-workflow/base';
import { type TreeNodeData } from '@coze-arch/bot-semi/Tree';
import { IconCozApply } from '@coze-arch/coze-design/icons';
import { IconButton, type TreeSelectProps } from '@coze-arch/coze-design';
import {
RefValueDisplay,
type RefTagColor,
type RefValueDisplayProps,
} from '@/form-extensions/components/value-expression-input/ref-value-display';
import {
type CustomFilterVar,
type VariableTreeDataNode,
type RenderDisplayVarName,
} from '@/form-extensions/components/tree-variable-selector/types';
import {
VariableSelector,
type VariableSelectorProps,
} from '@/form-extensions/components/tree-variable-selector';
export const useRefInputNode = ({
value,
onChange,
onBlur,
disabled,
variablesDataSource,
validateStatus,
readonly,
invalidContent,
renderDisplayVarName,
testId,
disabledTypes,
showClear = false,
customFilterVar,
setFocused,
style,
refTagColor,
hideDeleteIcon,
variableTagStyle,
optionFilter,
renderExtraOption,
enableSelectNode,
popoverStyle,
handleDataSource,
variableTypeConstraints,
}: {
value?: ValueExpression;
onChange: (v: ValueExpression | undefined) => void;
onBlur?: () => void;
disabled?: boolean;
variablesDataSource?: VariableTreeDataNode[];
validateStatus?: TreeSelectProps['validateStatus'];
readonly?: boolean;
testId?: string;
disabledTypes?: ViewVariableType[];
showClear?: boolean;
invalidContent?: string;
renderDisplayVarName?: RenderDisplayVarName;
customFilterVar?: CustomFilterVar;
setFocused?: (focused: boolean) => void;
style?: CSSProperties;
refTagColor?: RefTagColor;
hideDeleteIcon?: boolean;
variableTagStyle?: CSSProperties;
optionFilter?: VariableSelectorProps['optionFilter'];
handleDataSource?: VariableSelectorProps['handleDataSource'];
renderExtraOption?: (
data?: TreeNodeData[],
action?: {
hiddenPopover: () => void;
},
) => ReactNode;
enableSelectNode?: boolean;
popoverStyle?: CSSProperties;
/* 类型限制,引用类型不满足限制时,显示警告信息 */
variableTypeConstraints?: RefValueDisplayProps['variableTypeConstraints'];
}) => {
const onRefChange = useCallback(
(v: string[] | undefined): void => {
if (v === undefined) {
onChange(undefined);
} else {
onChange({
type: ValueExpressionType.REF,
content: { keyPath: v as string[] },
});
}
},
[onChange],
);
const handleRefRemove = () => {
onChange?.(undefined);
onBlur?.();
};
const [focused, setStateFocused] = useState(false);
const _setFocused = useCallback(
(v: boolean) => {
setStateFocused(v);
setFocused?.(v);
},
[setFocused],
);
const renderVariableSelect = (trigger: ReactNode) =>
readonly && trigger ? (
trigger
) : (
<VariableSelector
testId={testId}
disabled={disabled}
disabledTypes={disabledTypes}
dataSource={variablesDataSource}
value={
value && ValueExpression.isRef(value)
? (value.content?.keyPath as VariableSelectorProps['value'])
: undefined
}
onChange={onRefChange}
onBlur={onBlur}
validateStatus={validateStatus}
readonly={readonly}
showClear={showClear}
customFilterVar={customFilterVar}
onPopoverVisibleChange={_setFocused}
trigger={trigger}
style={style}
invalidContent={invalidContent}
renderDisplayVarName={renderDisplayVarName}
optionFilter={optionFilter}
renderExtraOption={renderExtraOption}
enableSelectNode={enableSelectNode}
popoverStyle={popoverStyle}
handleDataSource={handleDataSource}
/>
);
const renderVariableDisplay = (props?: { needWrapper?: boolean }) =>
props?.needWrapper ? (
<div
className={classNames(
'w-full max-w-[100%] h-[24px] pr-[4px]',
'flex flex-row items-center justify-between',
'bg-transparent hover:bg-background-5 active:bg-background-6',
'rounded-lg border border-solid coz-stroke-plus hover:coz-mg-secondary-hovered active:coz-mg-secondary-pressed',
{
'semi-input-wrapper-error': validateStatus === 'error',
'coz-stroke-primary': validateStatus !== 'error',
'!coz-stroke-hglt': focused,
'pointer-events-none': readonly,
},
)}
>
{renderVariableDisplay()}
<div>
{renderVariableSelect(
<IconButton
size="mini"
color="secondary"
icon={<IconCozApply className="text-[16px]" />}
/>,
)}
</div>
</div>
) : (
renderVariableSelect(
<div
className={classNames(
'cursor-pointer w-full overflow-hidden flex items-center pl-0.5',
)}
>
<RefValueDisplay
value={value as RefExpression}
onClose={handleRefRemove}
tagColor={refTagColor}
closable={!hideDeleteIcon}
style={variableTagStyle}
variableTypeConstraints={variableTypeConstraints}
readonly={readonly}
/>
</div>,
)
);
return {
renderVariableSelect,
renderVariableDisplay,
};
};