diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldLinearViewToggle.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldLinearViewToggle.tsx
new file mode 100644
index 0000000000..0dac7996eb
--- /dev/null
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldLinearViewToggle.tsx
@@ -0,0 +1,74 @@
+import { IconButton } from '@invoke-ai/ui-library';
+import { createSelector } from '@reduxjs/toolkit';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import {
+ selectWorkflowSlice,
+ workflowExposedFieldAdded,
+ workflowExposedFieldRemoved,
+} from 'features/nodes/store/workflowSlice';
+import { memo, useCallback, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PiMinusBold, PiPlusBold } from 'react-icons/pi';
+
+type Props = {
+ nodeId: string;
+ fieldName: string;
+ isHovered: boolean;
+};
+
+const FieldLinearViewToggle = ({ nodeId, fieldName, isHovered }: Props) => {
+ const dispatch = useAppDispatch();
+ const { t } = useTranslation();
+
+ const selectIsExposed = useMemo(
+ () =>
+ createSelector(selectWorkflowSlice, (workflow) => {
+ return Boolean(workflow.exposedFields.find((f) => f.nodeId === nodeId && f.fieldName === fieldName));
+ }),
+ [fieldName, nodeId]
+ );
+
+ const isExposed = useAppSelector(selectIsExposed);
+
+ const handleExposeField = useCallback(() => {
+ dispatch(workflowExposedFieldAdded({ nodeId, fieldName }));
+ }, [dispatch, fieldName, nodeId]);
+
+ const handleUnexposeField = useCallback(() => {
+ dispatch(workflowExposedFieldRemoved({ nodeId, fieldName }));
+ }, [dispatch, fieldName, nodeId]);
+
+ const ToggleButton = useMemo(() => {
+ if (!isHovered) {
+ return null;
+ } else if (!isExposed) {
+ return (
+ }
+ onClick={handleExposeField}
+ pointerEvents="auto"
+ size="xs"
+ />
+ );
+ } else if (isExposed) {
+ return (
+ }
+ onClick={handleUnexposeField}
+ pointerEvents="auto"
+ size="xs"
+ />
+ );
+ }
+ }, [isHovered, handleExposeField, handleUnexposeField, isExposed, t]);
+
+ return ToggleButton;
+};
+
+export default memo(FieldLinearViewToggle);
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputField.tsx
index b33b65dfb5..bb45f4e3e3 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputField.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputField.tsx
@@ -4,12 +4,13 @@ import { useDoesInputHaveValue } from 'features/nodes/hooks/useDoesInputHaveValu
import { useFieldInputInstance } from 'features/nodes/hooks/useFieldInputInstance';
import { useFieldInputTemplate } from 'features/nodes/hooks/useFieldInputTemplate';
import type { PropsWithChildren } from 'react';
-import { memo, useMemo } from 'react';
+import { memo, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import EditableFieldTitle from './EditableFieldTitle';
import FieldContextMenu from './FieldContextMenu';
import FieldHandle from './FieldHandle';
+import FieldLinearViewToggle from './FieldLinearViewToggle';
import InputFieldRenderer from './InputFieldRenderer';
interface Props {
@@ -22,6 +23,7 @@ const InputField = ({ nodeId, fieldName }: Props) => {
const fieldTemplate = useFieldInputTemplate(nodeId, fieldName);
const fieldInstance = useFieldInputInstance(nodeId, fieldName);
const doesFieldHaveValue = useDoesInputHaveValue(nodeId, fieldName);
+ const [isHovered, setIsHovered] = useState(false);
const { isConnected, isConnectionInProgress, isConnectionStartField, connectionError, shouldDim } =
useConnectionState({ nodeId, fieldName, kind: 'input' });
@@ -46,6 +48,14 @@ const InputField = ({ nodeId, fieldName }: Props) => {
return false;
}, [fieldTemplate, isConnected, doesFieldHaveValue]);
+ const handleMouseOver = useCallback(() => {
+ setIsHovered(true);
+ }, []);
+
+ const handleMouseOut = useCallback(() => {
+ setIsHovered(false);
+ }, []);
+
if (!fieldTemplate || !fieldInstance) {
return (
@@ -87,17 +97,20 @@ const InputField = ({ nodeId, fieldName }: Props) => {
return (
-
+
{(ref) => (
-
+
+
+
+
)}