更新大量 tsx 原始文件; 已经迁移 login panel; 部分 (#121)

* style(B1-1): 格式化 ink/buddy/cli/context/screens/tasks/services/keybindings/state (43 files)

纯格式化:移除分号、React Compiler import、import 多行展开。
修复了 Box.tsx 和 ScrollBox.tsx 中无效的 global.d.ts import。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-2): 格式化 commands (79 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-3): 格式化 components/messages,permissions,mcp,sandbox,shell (104 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-4): 格式化 components/PromptInput,FeedbackSurvey,tasks,agents,skills,design-system,wizard (73 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-5): 格式化 components其余 + hooks + tools (232 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-6): 格式化 main/entrypoints/utils/moreright (21 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: 更新 README,新增 Run.ps1/TODO.md,删除 V6.md

- README.md: 大幅重写,更详细版本历史和配置示例
- Run.ps1: 新增 Windows 启动脚本
- TODO.md: 新增包完成清单
- V6.md: 删除(架构重构规划已不适用)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: 修复以前的问题

* fix: 修复 login 面板的问题

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
claude-code-best
2026-04-04 23:24:27 +08:00
committed by GitHub
parent 02694918b5
commit 5b1a52b8e0
559 changed files with 103807 additions and 101817 deletions

View File

@@ -1,69 +1,97 @@
import { c as _c } from "react/compiler-runtime";
import figures from 'figures';
import React from 'react';
import { Box, Text } from '../../ink.js';
import type { PastedContent } from '../../utils/config.js';
import type { ImageDimensions } from '../../utils/imageResizer.js';
import type { OptionWithDescription } from './select.js';
import { SelectInputOption } from './select-input-option.js';
import { SelectOption } from './select-option.js';
import { useMultiSelectState } from './use-multi-select-state.js';
import figures from 'figures'
import React from 'react'
import { Box, Text } from '../../ink.js'
import type { PastedContent } from '../../utils/config.js'
import type { ImageDimensions } from '../../utils/imageResizer.js'
import type { OptionWithDescription } from './select.js'
import { SelectInputOption } from './select-input-option.js'
import { SelectOption } from './select-option.js'
import { useMultiSelectState } from './use-multi-select-state.js'
export type SelectMultiProps<T> = {
readonly isDisabled?: boolean;
readonly visibleOptionCount?: number;
readonly options: OptionWithDescription<T>[];
readonly defaultValue?: T[];
readonly onCancel: () => void;
readonly onChange?: (values: T[]) => void;
readonly onFocus?: (value: T) => void;
readonly focusValue?: T;
readonly isDisabled?: boolean
readonly visibleOptionCount?: number
readonly options: OptionWithDescription<T>[]
readonly defaultValue?: T[]
readonly onCancel: () => void
readonly onChange?: (values: T[]) => void
readonly onFocus?: (value: T) => void
readonly focusValue?: T
/**
* Text for the submit button. When provided, a submit button is shown and
* Enter toggles selection (submit only fires when the button is focused).
* When omitted, Enter submits directly and Space toggles selection.
*/
readonly submitButtonText?: string;
readonly submitButtonText?: string
/**
* Callback when user submits. Receives the currently selected values.
*/
readonly onSubmit?: (values: T[]) => void;
readonly onSubmit?: (values: T[]) => void
/**
* When true, hides the numeric indexes next to each option.
*/
readonly hideIndexes?: boolean;
readonly hideIndexes?: boolean
/**
* Callback when user presses down from the last item (submit button).
* If provided, navigation will not wrap to the first item.
*/
readonly onDownFromLastItem?: () => void;
readonly onDownFromLastItem?: () => void
/**
* Callback when user presses up from the first item.
* If provided, navigation will not wrap to the last item.
*/
readonly onUpFromFirstItem?: () => void;
readonly onUpFromFirstItem?: () => void
/**
* Focus the last option initially instead of the first.
*/
readonly initialFocusLast?: boolean;
readonly initialFocusLast?: boolean
/**
* Callback to open external editor for editing input option values.
* When provided, ctrl+g will trigger this callback in input options
* with the current value and a setter function to update the internal state.
*/
readonly onOpenEditor?: (currentValue: string, setValue: (value: string) => void) => void;
readonly onImagePaste?: (base64Image: string, mediaType?: string, filename?: string, dimensions?: ImageDimensions, sourcePath?: string) => void;
readonly pastedContents?: Record<number, PastedContent>;
readonly onRemoveImage?: (id: number) => void;
};
export function SelectMulti(t0) {
const $ = _c(44);
const {
isDisabled: t1,
visibleOptionCount: t2,
readonly onOpenEditor?: (
currentValue: string,
setValue: (value: string) => void,
) => void
readonly onImagePaste?: (
base64Image: string,
mediaType?: string,
filename?: string,
dimensions?: ImageDimensions,
sourcePath?: string,
) => void
readonly pastedContents?: Record<number, PastedContent>
readonly onRemoveImage?: (id: number) => void
}
export function SelectMulti<T>({
isDisabled = false,
visibleOptionCount = 5,
options,
defaultValue = [],
onCancel,
onChange,
onFocus,
focusValue,
submitButtonText,
onSubmit,
onDownFromLastItem,
onUpFromFirstItem,
initialFocusLast,
onOpenEditor,
hideIndexes = false,
onImagePaste,
pastedContents,
onRemoveImage,
}: SelectMultiProps<T>): React.ReactNode {
const state = useMultiSelectState<T>({
isDisabled,
visibleOptionCount,
options,
defaultValue: t3,
onCancel,
defaultValue,
onChange,
onCancel,
onFocus,
focusValue,
submitButtonText,
@@ -71,142 +99,111 @@ export function SelectMulti(t0) {
onDownFromLastItem,
onUpFromFirstItem,
initialFocusLast,
onOpenEditor,
hideIndexes: t4,
onImagePaste,
pastedContents,
onRemoveImage
} = t0;
const isDisabled = t1 === undefined ? false : t1;
const visibleOptionCount = t2 === undefined ? 5 : t2;
let t5;
if ($[0] !== t3) {
t5 = t3 === undefined ? [] : t3;
$[0] = t3;
$[1] = t5;
} else {
t5 = $[1];
}
const defaultValue = t5;
const hideIndexes = t4 === undefined ? false : t4;
let t6;
if ($[2] !== defaultValue || $[3] !== focusValue || $[4] !== hideIndexes || $[5] !== initialFocusLast || $[6] !== isDisabled || $[7] !== onCancel || $[8] !== onChange || $[9] !== onDownFromLastItem || $[10] !== onFocus || $[11] !== onSubmit || $[12] !== onUpFromFirstItem || $[13] !== options || $[14] !== submitButtonText || $[15] !== visibleOptionCount) {
t6 = {
isDisabled,
visibleOptionCount,
options,
defaultValue,
onChange,
onCancel,
onFocus,
focusValue,
submitButtonText,
onSubmit,
onDownFromLastItem,
onUpFromFirstItem,
initialFocusLast,
hideIndexes
};
$[2] = defaultValue;
$[3] = focusValue;
$[4] = hideIndexes;
$[5] = initialFocusLast;
$[6] = isDisabled;
$[7] = onCancel;
$[8] = onChange;
$[9] = onDownFromLastItem;
$[10] = onFocus;
$[11] = onSubmit;
$[12] = onUpFromFirstItem;
$[13] = options;
$[14] = submitButtonText;
$[15] = visibleOptionCount;
$[16] = t6;
} else {
t6 = $[16];
}
const state = useMultiSelectState(t6);
let T0;
let T1;
let t7;
let t8;
let t9;
if ($[17] !== hideIndexes || $[18] !== isDisabled || $[19] !== onCancel || $[20] !== onImagePaste || $[21] !== onOpenEditor || $[22] !== onRemoveImage || $[23] !== options.length || $[24] !== pastedContents || $[25] !== state) {
const maxIndexWidth = options.length.toString().length;
T1 = Box;
t9 = "column";
T0 = Box;
t7 = "column";
t8 = state.visibleOptions.map((option, index) => {
const isOptionFocused = !isDisabled && state.focusedValue === option.value && !state.isSubmitFocused;
const isSelected = state.selectedValues.includes(option.value);
const isFirstVisibleOption = option.index === state.visibleFromIndex;
const isLastVisibleOption = option.index === state.visibleToIndex - 1;
const areMoreOptionsBelow = state.visibleToIndex < options.length;
const areMoreOptionsAbove = state.visibleFromIndex > 0;
const i = state.visibleFromIndex + index + 1;
if (option.type === "input") {
const inputValue = state.inputValues.get(option.value) || "";
return <Box key={String(option.value)} gap={1}><SelectInputOption option={option} isFocused={isOptionFocused} isSelected={false} shouldShowDownArrow={areMoreOptionsBelow && isLastVisibleOption} shouldShowUpArrow={areMoreOptionsAbove && isFirstVisibleOption} maxIndexWidth={maxIndexWidth} index={i} inputValue={inputValue} onInputChange={value => {
state.updateInputValue(option.value, value);
}} onSubmit={_temp} onExit={() => {
onCancel();
}} layout="compact" onOpenEditor={onOpenEditor} onImagePaste={onImagePaste} pastedContents={pastedContents} onRemoveImage={onRemoveImage}><Text color={isSelected ? "success" : undefined}>[{isSelected ? figures.tick : " "}]{" "}</Text></SelectInputOption></Box>;
}
return <Box key={String(option.value)} gap={1}><SelectOption isFocused={isOptionFocused} isSelected={false} shouldShowDownArrow={areMoreOptionsBelow && isLastVisibleOption} shouldShowUpArrow={areMoreOptionsAbove && isFirstVisibleOption} description={option.description}>{!hideIndexes && <Text dimColor={true}>{`${i}.`.padEnd(maxIndexWidth)}</Text>}<Text color={isSelected ? "success" : undefined}>[{isSelected ? figures.tick : " "}]</Text><Text color={isOptionFocused ? "suggestion" : undefined}>{option.label}</Text></SelectOption></Box>;
});
$[17] = hideIndexes;
$[18] = isDisabled;
$[19] = onCancel;
$[20] = onImagePaste;
$[21] = onOpenEditor;
$[22] = onRemoveImage;
$[23] = options.length;
$[24] = pastedContents;
$[25] = state;
$[26] = T0;
$[27] = T1;
$[28] = t7;
$[29] = t8;
$[30] = t9;
} else {
T0 = $[26];
T1 = $[27];
t7 = $[28];
t8 = $[29];
t9 = $[30];
}
let t10;
if ($[31] !== T0 || $[32] !== t7 || $[33] !== t8) {
t10 = <T0 flexDirection={t7}>{t8}</T0>;
$[31] = T0;
$[32] = t7;
$[33] = t8;
$[34] = t10;
} else {
t10 = $[34];
}
let t11;
if ($[35] !== onSubmit || $[36] !== state.isSubmitFocused || $[37] !== submitButtonText) {
t11 = submitButtonText && onSubmit && <Box marginTop={0} gap={1}>{state.isSubmitFocused ? <Text color="suggestion">{figures.pointer}</Text> : <Text> </Text>}<Box marginLeft={3}><Text color={state.isSubmitFocused ? "suggestion" : undefined} bold={true}>{submitButtonText}</Text></Box></Box>;
$[35] = onSubmit;
$[36] = state.isSubmitFocused;
$[37] = submitButtonText;
$[38] = t11;
} else {
t11 = $[38];
}
let t12;
if ($[39] !== T1 || $[40] !== t10 || $[41] !== t11 || $[42] !== t9) {
t12 = <T1 flexDirection={t9}>{t10}{t11}</T1>;
$[39] = T1;
$[40] = t10;
$[41] = t11;
$[42] = t9;
$[43] = t12;
} else {
t12 = $[43];
}
return t12;
hideIndexes,
})
const maxIndexWidth = options.length.toString().length
return (
<Box flexDirection="column">
<Box flexDirection="column">
{state.visibleOptions.map((option, index) => {
const isOptionFocused =
!isDisabled &&
state.focusedValue === option.value &&
!state.isSubmitFocused
const isSelected = state.selectedValues.includes(option.value)
const isFirstVisibleOption = option.index === state.visibleFromIndex
const isLastVisibleOption = option.index === state.visibleToIndex - 1
const areMoreOptionsBelow = state.visibleToIndex < options.length
const areMoreOptionsAbove = state.visibleFromIndex > 0
const i = state.visibleFromIndex + index + 1
if (option.type === 'input') {
const inputValue = state.inputValues.get(option.value) || ''
return (
<Box key={String(option.value)} gap={1}>
<SelectInputOption
option={option}
isFocused={isOptionFocused}
isSelected={
false /* We show selection state differently for multi-select */
}
shouldShowDownArrow={
areMoreOptionsBelow && isLastVisibleOption
}
shouldShowUpArrow={
areMoreOptionsAbove && isFirstVisibleOption
}
maxIndexWidth={maxIndexWidth}
index={i}
inputValue={inputValue}
onInputChange={value => {
state.updateInputValue(option.value, value)
}}
onSubmit={() => {}} /* We handle submit higher up */
onExit={() => {
onCancel()
}}
layout="compact"
onOpenEditor={onOpenEditor}
onImagePaste={onImagePaste}
pastedContents={pastedContents}
onRemoveImage={onRemoveImage}
>
<Text color={isSelected ? 'success' : undefined}>
[{isSelected ? figures.tick : ' '}]{' '}
</Text>
</SelectInputOption>
</Box>
)
}
return (
<Box key={String(option.value)} gap={1}>
<SelectOption
isFocused={isOptionFocused}
isSelected={
false /* We show selection state differently for multi-select */
}
shouldShowDownArrow={areMoreOptionsBelow && isLastVisibleOption}
shouldShowUpArrow={areMoreOptionsAbove && isFirstVisibleOption}
description={option.description}
>
{!hideIndexes && (
<Text dimColor>{`${i}.`.padEnd(maxIndexWidth)}</Text>
)}
<Text color={isSelected ? 'success' : undefined}>
[{isSelected ? figures.tick : ' '}]
</Text>
<Text color={isOptionFocused ? 'suggestion' : undefined}>
{option.label}
</Text>
</SelectOption>
</Box>
)
})}
</Box>
{submitButtonText && onSubmit && (
<Box marginTop={0} gap={1}>
{state.isSubmitFocused ? (
<Text color="suggestion">{figures.pointer}</Text>
) : (
<Text> </Text>
)}
<Box marginLeft={3}>
<Text
color={state.isSubmitFocused ? 'suggestion' : undefined}
bold={true}
>
{submitButtonText}
</Text>
</Box>
</Box>
)}
</Box>
)
}
function _temp() {}

View File

@@ -1,487 +1,412 @@
import { c as _c } from "react/compiler-runtime";
import React, { type ReactNode, useEffect, useRef, useState } from 'react';
import React, { type ReactNode, useEffect, useRef, useState } from 'react'
// eslint-disable-next-line custom-rules/prefer-use-keybindings -- UP arrow exit not in Attachments bindings
import { Box, Text, useInput } from '../../ink.js';
import { useKeybinding, useKeybindings } from '../../keybindings/useKeybinding.js';
import type { PastedContent } from '../../utils/config.js';
import { getImageFromClipboard } from '../../utils/imagePaste.js';
import type { ImageDimensions } from '../../utils/imageResizer.js';
import { ClickableImageRef } from '../ClickableImageRef.js';
import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js';
import { Byline } from '../design-system/Byline.js';
import TextInput from '../TextInput.js';
import type { OptionWithDescription } from './select.js';
import { SelectOption } from './select-option.js';
import { Box, Text, useInput } from '../../ink.js'
import {
useKeybinding,
useKeybindings,
} from '../../keybindings/useKeybinding.js'
import type { PastedContent } from '../../utils/config.js'
import { getImageFromClipboard } from '../../utils/imagePaste.js'
import type { ImageDimensions } from '../../utils/imageResizer.js'
import { ClickableImageRef } from '../ClickableImageRef.js'
import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js'
import { Byline } from '../design-system/Byline.js'
import TextInput from '../TextInput.js'
import type { OptionWithDescription } from './select.js'
import { SelectOption } from './select-option.js'
type Props<T> = {
option: Extract<OptionWithDescription<T>, {
type: 'input';
}>;
isFocused: boolean;
isSelected: boolean;
shouldShowDownArrow: boolean;
shouldShowUpArrow: boolean;
maxIndexWidth: number;
index: number;
inputValue: string;
onInputChange: (value: string) => void;
onSubmit: (value: string) => void;
onExit?: () => void;
layout: 'compact' | 'expanded';
children?: ReactNode;
option: Extract<OptionWithDescription<T>, { type: 'input' }>
isFocused: boolean
isSelected: boolean
shouldShowDownArrow: boolean
shouldShowUpArrow: boolean
maxIndexWidth: number
index: number
inputValue: string
onInputChange: (value: string) => void
onSubmit: (value: string) => void
onExit?: () => void
layout: 'compact' | 'expanded'
children?: ReactNode
/**
* When true, shows the label before the input field.
* When false (default), uses the label as the placeholder.
*/
showLabel?: boolean;
showLabel?: boolean
/**
* Callback to open external editor for editing the input value.
* When provided, ctrl+g will trigger this callback with the current value
* and a setter function to update the internal state.
*/
onOpenEditor?: (currentValue: string, setValue: (value: string) => void) => void;
onOpenEditor?: (
currentValue: string,
setValue: (value: string) => void,
) => void
/**
* When true, automatically reset cursor to end of line when:
* - Option becomes focused
* - Input value changes
* This prevents cursor position bugs when the input value updates asynchronously.
*/
resetCursorOnUpdate?: boolean;
resetCursorOnUpdate?: boolean
/**
* Optional callback when an image is pasted into the input.
*/
onImagePaste?: (base64Image: string, mediaType?: string, filename?: string, dimensions?: ImageDimensions, sourcePath?: string) => void;
onImagePaste?: (
base64Image: string,
mediaType?: string,
filename?: string,
dimensions?: ImageDimensions,
sourcePath?: string,
) => void
/**
* Pasted content to display inline above the input when focused.
*/
pastedContents?: Record<number, PastedContent>;
pastedContents?: Record<number, PastedContent>
/**
* Callback to remove a pasted image by its ID.
*/
onRemoveImage?: (id: number) => void;
onRemoveImage?: (id: number) => void
/**
* Whether image selection mode is active.
*/
imagesSelected?: boolean;
imagesSelected?: boolean
/**
* Currently selected image index within the image attachments array.
*/
selectedImageIndex?: number;
selectedImageIndex?: number
/**
* Callback to set image selection mode on/off.
*/
onImagesSelectedChange?: (selected: boolean) => void;
onImagesSelectedChange?: (selected: boolean) => void
/**
* Callback to change the selected image index.
*/
onSelectedImageIndexChange?: (index: number) => void;
};
export function SelectInputOption(t0) {
const $ = _c(100);
const {
option,
isFocused,
isSelected,
shouldShowDownArrow,
shouldShowUpArrow,
maxIndexWidth,
index,
inputValue,
onInputChange,
onSubmit,
onExit,
layout,
children,
showLabel: t1,
onOpenEditor,
resetCursorOnUpdate: t2,
onImagePaste,
pastedContents,
onRemoveImage,
imagesSelected,
selectedImageIndex: t3,
onImagesSelectedChange,
onSelectedImageIndexChange
} = t0;
const showLabelProp = t1 === undefined ? false : t1;
const resetCursorOnUpdate = t2 === undefined ? false : t2;
const selectedImageIndex = t3 === undefined ? 0 : t3;
let t4;
if ($[0] !== pastedContents) {
t4 = pastedContents ? Object.values(pastedContents).filter(_temp) : [];
$[0] = pastedContents;
$[1] = t4;
} else {
t4 = $[1];
}
const imageAttachments = t4;
const showLabel = showLabelProp || option.showLabelWithValue === true;
const [cursorOffset, setCursorOffset] = useState(inputValue.length);
const isUserEditing = useRef(false);
let t5;
if ($[2] !== inputValue.length || $[3] !== isFocused || $[4] !== resetCursorOnUpdate) {
t5 = () => {
if (resetCursorOnUpdate && isFocused) {
if (isUserEditing.current) {
isUserEditing.current = false;
} else {
setCursorOffset(inputValue.length);
}
onSelectedImageIndexChange?: (index: number) => void
}
export function SelectInputOption<T>({
option,
isFocused,
isSelected,
shouldShowDownArrow,
shouldShowUpArrow,
maxIndexWidth,
index,
inputValue,
onInputChange,
onSubmit,
onExit,
layout,
children,
showLabel: showLabelProp = false,
onOpenEditor,
resetCursorOnUpdate = false,
onImagePaste,
pastedContents,
onRemoveImage,
imagesSelected,
selectedImageIndex = 0,
onImagesSelectedChange,
onSelectedImageIndexChange,
}: Props<T>): React.ReactNode {
const imageAttachments = pastedContents
? Object.values(pastedContents).filter(c => c.type === 'image')
: []
// Allow individual options to force showing the label via showLabelWithValue
const showLabel = showLabelProp || option.showLabelWithValue === true
const [cursorOffset, setCursorOffset] = useState(inputValue.length)
// Track whether the latest inputValue change was from user typing/pasting,
// so we can skip resetting cursor to end on user-initiated changes.
const isUserEditing = useRef(false)
// Reset cursor to end of line when:
// 1. Option becomes focused (user navigates to it)
// 2. Input value changes externally (e.g., async classifier description updates)
// Skip reset when the change was from user typing (which sets isUserEditing ref)
// Only enabled when resetCursorOnUpdate prop is true
useEffect(() => {
if (resetCursorOnUpdate && isFocused) {
if (isUserEditing.current) {
isUserEditing.current = false
} else {
setCursorOffset(inputValue.length)
}
};
$[2] = inputValue.length;
$[3] = isFocused;
$[4] = resetCursorOnUpdate;
$[5] = t5;
} else {
t5 = $[5];
}
let t6;
if ($[6] !== inputValue || $[7] !== isFocused || $[8] !== resetCursorOnUpdate) {
t6 = [resetCursorOnUpdate, isFocused, inputValue];
$[6] = inputValue;
$[7] = isFocused;
$[8] = resetCursorOnUpdate;
$[9] = t6;
} else {
t6 = $[9];
}
useEffect(t5, t6);
let t7;
if ($[10] !== inputValue || $[11] !== onInputChange || $[12] !== onOpenEditor) {
t7 = () => {
onOpenEditor?.(inputValue, onInputChange);
};
$[10] = inputValue;
$[11] = onInputChange;
$[12] = onOpenEditor;
$[13] = t7;
} else {
t7 = $[13];
}
const t8 = isFocused && !!onOpenEditor;
let t9;
if ($[14] !== t8) {
t9 = {
context: "Chat",
isActive: t8
};
$[14] = t8;
$[15] = t9;
} else {
t9 = $[15];
}
useKeybinding("chat:externalEditor", t7, t9);
let t10;
if ($[16] !== onImagePaste) {
t10 = () => {
if (!onImagePaste) {
return;
}
getImageFromClipboard().then(imageData => {
}
}, [resetCursorOnUpdate, isFocused, inputValue])
// ctrl+g to open external editor (reuses chat:externalEditor keybinding)
useKeybinding(
'chat:externalEditor',
() => {
onOpenEditor?.(inputValue, onInputChange)
},
{ context: 'Chat', isActive: isFocused && !!onOpenEditor },
)
// ctrl+v to paste image from clipboard (same as PromptInput)
useKeybinding(
'chat:imagePaste',
() => {
if (!onImagePaste) return
void getImageFromClipboard().then(imageData => {
if (imageData) {
onImagePaste(imageData.base64, imageData.mediaType, undefined, imageData.dimensions);
onImagePaste(
imageData.base64,
imageData.mediaType,
undefined,
imageData.dimensions,
)
}
});
};
$[16] = onImagePaste;
$[17] = t10;
} else {
t10 = $[17];
}
const t11 = isFocused && !!onImagePaste;
let t12;
if ($[18] !== t11) {
t12 = {
context: "Chat",
isActive: t11
};
$[18] = t11;
$[19] = t12;
} else {
t12 = $[19];
}
useKeybinding("chat:imagePaste", t10, t12);
let t13;
if ($[20] !== imageAttachments || $[21] !== onRemoveImage) {
t13 = () => {
})
},
{ context: 'Chat', isActive: isFocused && !!onImagePaste },
)
// Backspace with empty input removes the last pasted image (non-image-selection mode)
useKeybinding(
'attachments:remove',
() => {
if (imageAttachments.length > 0 && onRemoveImage) {
onRemoveImage(imageAttachments.at(-1).id);
onRemoveImage(imageAttachments.at(-1)!.id)
}
};
$[20] = imageAttachments;
$[21] = onRemoveImage;
$[22] = t13;
} else {
t13 = $[22];
}
const t14 = isFocused && !imagesSelected && inputValue === "" && imageAttachments.length > 0 && !!onRemoveImage;
let t15;
if ($[23] !== t14) {
t15 = {
context: "Attachments",
isActive: t14
};
$[23] = t14;
$[24] = t15;
} else {
t15 = $[24];
}
useKeybinding("attachments:remove", t13, t15);
let t16;
let t17;
if ($[25] !== imageAttachments.length || $[26] !== onSelectedImageIndexChange || $[27] !== selectedImageIndex) {
t16 = () => {
if (imageAttachments.length > 1) {
onSelectedImageIndexChange?.((selectedImageIndex + 1) % imageAttachments.length);
}
};
t17 = () => {
if (imageAttachments.length > 1) {
onSelectedImageIndexChange?.((selectedImageIndex - 1 + imageAttachments.length) % imageAttachments.length);
}
};
$[25] = imageAttachments.length;
$[26] = onSelectedImageIndexChange;
$[27] = selectedImageIndex;
$[28] = t16;
$[29] = t17;
} else {
t16 = $[28];
t17 = $[29];
}
let t18;
if ($[30] !== imageAttachments || $[31] !== onImagesSelectedChange || $[32] !== onRemoveImage || $[33] !== onSelectedImageIndexChange || $[34] !== selectedImageIndex) {
t18 = () => {
const img = imageAttachments[selectedImageIndex];
if (img && onRemoveImage) {
onRemoveImage(img.id);
if (imageAttachments.length <= 1) {
onImagesSelectedChange?.(false);
} else {
onSelectedImageIndexChange?.(Math.min(selectedImageIndex, imageAttachments.length - 2));
},
{
context: 'Attachments',
isActive:
isFocused &&
!imagesSelected &&
inputValue === '' &&
imageAttachments.length > 0 &&
!!onRemoveImage,
},
)
// Image selection mode keybindings — reuses existing Attachments actions
useKeybindings(
{
'attachments:next': () => {
if (imageAttachments.length > 1) {
onSelectedImageIndexChange?.(
(selectedImageIndex + 1) % imageAttachments.length,
)
}
}
};
$[30] = imageAttachments;
$[31] = onImagesSelectedChange;
$[32] = onRemoveImage;
$[33] = onSelectedImageIndexChange;
$[34] = selectedImageIndex;
$[35] = t18;
} else {
t18 = $[35];
}
let t19;
if ($[36] !== onImagesSelectedChange) {
t19 = () => {
onImagesSelectedChange?.(false);
};
$[36] = onImagesSelectedChange;
$[37] = t19;
} else {
t19 = $[37];
}
let t20;
if ($[38] !== t16 || $[39] !== t17 || $[40] !== t18 || $[41] !== t19) {
t20 = {
"attachments:next": t16,
"attachments:previous": t17,
"attachments:remove": t18,
"attachments:exit": t19
};
$[38] = t16;
$[39] = t17;
$[40] = t18;
$[41] = t19;
$[42] = t20;
} else {
t20 = $[42];
}
const t21 = isFocused && !!imagesSelected;
let t22;
if ($[43] !== t21) {
t22 = {
context: "Attachments",
isActive: t21
};
$[43] = t21;
$[44] = t22;
} else {
t22 = $[44];
}
useKeybindings(t20, t22);
let t23;
if ($[45] !== onImagesSelectedChange) {
t23 = (_input, key) => {
},
'attachments:previous': () => {
if (imageAttachments.length > 1) {
onSelectedImageIndexChange?.(
(selectedImageIndex - 1 + imageAttachments.length) %
imageAttachments.length,
)
}
},
'attachments:remove': () => {
const img = imageAttachments[selectedImageIndex]
if (img && onRemoveImage) {
onRemoveImage(img.id)
// If no images left after removal, exit image selection
if (imageAttachments.length <= 1) {
onImagesSelectedChange?.(false)
} else {
// Adjust index if we deleted the last image
onSelectedImageIndexChange?.(
Math.min(selectedImageIndex, imageAttachments.length - 2),
)
}
}
},
'attachments:exit': () => {
onImagesSelectedChange?.(false)
},
},
{ context: 'Attachments', isActive: isFocused && !!imagesSelected },
)
// UP arrow exits image selection mode (UP isn't bound to attachments:exit)
useInput(
(_input, key) => {
if (key.upArrow) {
onImagesSelectedChange?.(false);
onImagesSelectedChange?.(false)
}
};
$[45] = onImagesSelectedChange;
$[46] = t23;
} else {
t23 = $[46];
}
const t24 = isFocused && !!imagesSelected;
let t25;
if ($[47] !== t24) {
t25 = {
isActive: t24
};
$[47] = t24;
$[48] = t25;
} else {
t25 = $[48];
}
useInput(t23, t25);
let t26;
let t27;
if ($[49] !== imagesSelected || $[50] !== isFocused || $[51] !== onImagesSelectedChange) {
t26 = () => {
if (!isFocused && imagesSelected) {
onImagesSelectedChange?.(false);
}
};
t27 = [isFocused, imagesSelected, onImagesSelectedChange];
$[49] = imagesSelected;
$[50] = isFocused;
$[51] = onImagesSelectedChange;
$[52] = t26;
$[53] = t27;
} else {
t26 = $[52];
t27 = $[53];
}
useEffect(t26, t27);
const descriptionPaddingLeft = layout === "expanded" ? maxIndexWidth + 3 : maxIndexWidth + 4;
const t28 = layout === "compact" ? 0 : undefined;
const t29 = `${index}.`;
let t30;
if ($[54] !== maxIndexWidth || $[55] !== t29) {
t30 = t29.padEnd(maxIndexWidth + 2);
$[54] = maxIndexWidth;
$[55] = t29;
$[56] = t30;
} else {
t30 = $[56];
}
let t31;
if ($[57] !== t30) {
t31 = <Text dimColor={true}>{t30}</Text>;
$[57] = t30;
$[58] = t31;
} else {
t31 = $[58];
}
let t32;
if ($[59] !== cursorOffset || $[60] !== imagesSelected || $[61] !== inputValue || $[62] !== isFocused || $[63] !== onExit || $[64] !== onImagePaste || $[65] !== onInputChange || $[66] !== onSubmit || $[67] !== option || $[68] !== showLabel) {
t32 = showLabel ? <><Text color={isFocused ? "suggestion" : undefined}>{option.label}</Text>{isFocused ? <><Text color="suggestion">{option.labelValueSeparator ?? ", "}</Text><TextInput value={inputValue} onChange={value => {
isUserEditing.current = true;
onInputChange(value);
option.onChange(value);
}} onSubmit={onSubmit} onExit={onExit} placeholder={option.placeholder} focus={!imagesSelected} showCursor={true} multiline={true} cursorOffset={cursorOffset} onChangeCursorOffset={setCursorOffset} columns={80} onImagePaste={onImagePaste} onPaste={pastedText => {
isUserEditing.current = true;
const before = inputValue.slice(0, cursorOffset);
const after = inputValue.slice(cursorOffset);
const newValue = before + pastedText + after;
onInputChange(newValue);
option.onChange(newValue);
setCursorOffset(before.length + pastedText.length);
}} /></> : inputValue && <Text>{option.labelValueSeparator ?? ", "}{inputValue}</Text>}</> : isFocused ? <TextInput value={inputValue} onChange={value_0 => {
isUserEditing.current = true;
onInputChange(value_0);
option.onChange(value_0);
}} onSubmit={onSubmit} onExit={onExit} placeholder={option.placeholder || (typeof option.label === "string" ? option.label : undefined)} focus={!imagesSelected} showCursor={true} multiline={true} cursorOffset={cursorOffset} onChangeCursorOffset={setCursorOffset} columns={80} onImagePaste={onImagePaste} onPaste={pastedText_0 => {
isUserEditing.current = true;
const before_0 = inputValue.slice(0, cursorOffset);
const after_0 = inputValue.slice(cursorOffset);
const newValue_0 = before_0 + pastedText_0 + after_0;
onInputChange(newValue_0);
option.onChange(newValue_0);
setCursorOffset(before_0.length + pastedText_0.length);
}} /> : <Text color={inputValue ? undefined : "inactive"}>{inputValue || option.placeholder || option.label}</Text>;
$[59] = cursorOffset;
$[60] = imagesSelected;
$[61] = inputValue;
$[62] = isFocused;
$[63] = onExit;
$[64] = onImagePaste;
$[65] = onInputChange;
$[66] = onSubmit;
$[67] = option;
$[68] = showLabel;
$[69] = t32;
} else {
t32 = $[69];
}
let t33;
if ($[70] !== children || $[71] !== t28 || $[72] !== t31 || $[73] !== t32) {
t33 = <Box flexDirection="row" flexShrink={t28}>{t31}{children}{t32}</Box>;
$[70] = children;
$[71] = t28;
$[72] = t31;
$[73] = t32;
$[74] = t33;
} else {
t33 = $[74];
}
let t34;
if ($[75] !== isFocused || $[76] !== isSelected || $[77] !== shouldShowDownArrow || $[78] !== shouldShowUpArrow || $[79] !== t33) {
t34 = <SelectOption isFocused={isFocused} isSelected={isSelected} shouldShowDownArrow={shouldShowDownArrow} shouldShowUpArrow={shouldShowUpArrow} declareCursor={false}>{t33}</SelectOption>;
$[75] = isFocused;
$[76] = isSelected;
$[77] = shouldShowDownArrow;
$[78] = shouldShowUpArrow;
$[79] = t33;
$[80] = t34;
} else {
t34 = $[80];
}
let t35;
if ($[81] !== descriptionPaddingLeft || $[82] !== isFocused || $[83] !== isSelected || $[84] !== option.description || $[85] !== option.dimDescription) {
t35 = option.description && <Box paddingLeft={descriptionPaddingLeft}><Text dimColor={option.dimDescription !== false} color={isSelected ? "success" : isFocused ? "suggestion" : undefined}>{option.description}</Text></Box>;
$[81] = descriptionPaddingLeft;
$[82] = isFocused;
$[83] = isSelected;
$[84] = option.description;
$[85] = option.dimDescription;
$[86] = t35;
} else {
t35 = $[86];
}
let t36;
if ($[87] !== descriptionPaddingLeft || $[88] !== imageAttachments || $[89] !== imagesSelected || $[90] !== isFocused || $[91] !== selectedImageIndex) {
t36 = imageAttachments.length > 0 && <Box flexDirection="row" gap={1} paddingLeft={descriptionPaddingLeft}>{imageAttachments.map((img_0, idx) => <ClickableImageRef key={img_0.id} imageId={img_0.id} isSelected={!!imagesSelected && idx === selectedImageIndex} />)}<Box flexGrow={1} justifyContent="flex-start" flexDirection="row"><Text dimColor={true}>{imagesSelected ? <Byline>{imageAttachments.length > 1 && <><ConfigurableShortcutHint action="attachments:next" context="Attachments" fallback={"\u2192"} description="next" /><ConfigurableShortcutHint action="attachments:previous" context="Attachments" fallback={"\u2190"} description="prev" /></>}<ConfigurableShortcutHint action="attachments:remove" context="Attachments" fallback="backspace" description="remove" /><ConfigurableShortcutHint action="attachments:exit" context="Attachments" fallback="esc" description="cancel" /></Byline> : isFocused ? "(\u2193 to select)" : null}</Text></Box></Box>;
$[87] = descriptionPaddingLeft;
$[88] = imageAttachments;
$[89] = imagesSelected;
$[90] = isFocused;
$[91] = selectedImageIndex;
$[92] = t36;
} else {
t36 = $[92];
}
let t37;
if ($[93] !== layout) {
t37 = layout === "expanded" && <Text> </Text>;
$[93] = layout;
$[94] = t37;
} else {
t37 = $[94];
}
let t38;
if ($[95] !== t34 || $[96] !== t35 || $[97] !== t36 || $[98] !== t37) {
t38 = <Box flexDirection="column" flexShrink={0}>{t34}{t35}{t36}{t37}</Box>;
$[95] = t34;
$[96] = t35;
$[97] = t36;
$[98] = t37;
$[99] = t38;
} else {
t38 = $[99];
}
return t38;
}
function _temp(c) {
return c.type === "image";
},
{ isActive: isFocused && !!imagesSelected },
)
// Exit image mode when option loses focus
useEffect(() => {
if (!isFocused && imagesSelected) {
onImagesSelectedChange?.(false)
}
}, [isFocused, imagesSelected, onImagesSelectedChange])
const descriptionPaddingLeft =
layout === 'expanded' ? maxIndexWidth + 3 : maxIndexWidth + 4
return (
<Box flexDirection="column" flexShrink={0}>
<SelectOption
isFocused={isFocused}
isSelected={isSelected}
shouldShowDownArrow={shouldShowDownArrow}
shouldShowUpArrow={shouldShowUpArrow}
declareCursor={false}
>
<Box
flexDirection="row"
flexShrink={layout === 'compact' ? 0 : undefined}
>
<Text dimColor>{`${index}.`.padEnd(maxIndexWidth + 2)}</Text>
{children}
{showLabel ? (
<>
<Text color={isFocused ? 'suggestion' : undefined}>
{option.label}
</Text>
{isFocused ? (
<>
<Text color="suggestion">
{option.labelValueSeparator ?? ', '}
</Text>
<TextInput
value={inputValue}
onChange={value => {
isUserEditing.current = true
onInputChange(value)
option.onChange(value)
}}
onSubmit={onSubmit}
onExit={onExit}
placeholder={option.placeholder}
focus={!imagesSelected}
showCursor={true}
multiline={true}
cursorOffset={cursorOffset}
onChangeCursorOffset={setCursorOffset}
columns={80}
onImagePaste={onImagePaste}
onPaste={(pastedText: string) => {
isUserEditing.current = true
const before = inputValue.slice(0, cursorOffset)
const after = inputValue.slice(cursorOffset)
const newValue = before + pastedText + after
onInputChange(newValue)
option.onChange(newValue)
setCursorOffset(before.length + pastedText.length)
}}
/>
</>
) : (
inputValue && (
<Text>
{option.labelValueSeparator ?? ', '}
{inputValue}
</Text>
)
)}
</>
) : isFocused ? (
<TextInput
value={inputValue}
onChange={value => {
isUserEditing.current = true
onInputChange(value)
option.onChange(value)
}}
onSubmit={onSubmit}
onExit={onExit}
placeholder={
option.placeholder ||
(typeof option.label === 'string' ? option.label : undefined)
}
focus={!imagesSelected}
showCursor={true}
multiline={true}
cursorOffset={cursorOffset}
onChangeCursorOffset={setCursorOffset}
columns={80}
onImagePaste={onImagePaste}
onPaste={(pastedText: string) => {
isUserEditing.current = true
const before = inputValue.slice(0, cursorOffset)
const after = inputValue.slice(cursorOffset)
const newValue = before + pastedText + after
onInputChange(newValue)
option.onChange(newValue)
setCursorOffset(before.length + pastedText.length)
}}
/>
) : (
<Text color={inputValue ? undefined : 'inactive'}>
{inputValue || option.placeholder || option.label}
</Text>
)}
</Box>
</SelectOption>
{option.description && (
<Box paddingLeft={descriptionPaddingLeft}>
<Text
dimColor={option.dimDescription !== false}
color={
isSelected ? 'success' : isFocused ? 'suggestion' : undefined
}
>
{option.description}
</Text>
</Box>
)}
{imageAttachments.length > 0 && (
<Box flexDirection="row" gap={1} paddingLeft={descriptionPaddingLeft}>
{imageAttachments.map((img, idx) => (
<ClickableImageRef
key={img.id}
imageId={img.id}
isSelected={!!imagesSelected && idx === selectedImageIndex}
/>
))}
<Box flexGrow={1} justifyContent="flex-start" flexDirection="row">
<Text dimColor>
{imagesSelected ? (
<Byline>
{imageAttachments.length > 1 && (
<>
<ConfigurableShortcutHint
action="attachments:next"
context="Attachments"
fallback="→"
description="next"
/>
<ConfigurableShortcutHint
action="attachments:previous"
context="Attachments"
fallback="←"
description="prev"
/>
</>
)}
<ConfigurableShortcutHint
action="attachments:remove"
context="Attachments"
fallback="backspace"
description="remove"
/>
<ConfigurableShortcutHint
action="attachments:exit"
context="Attachments"
fallback="esc"
description="cancel"
/>
</Byline>
) : isFocused ? (
'(↓ to select)'
) : null}
</Text>
</Box>
</Box>
)}
{layout === 'expanded' && <Text> </Text>}
</Box>
)
}

View File

@@ -1,67 +1,64 @@
import { c as _c } from "react/compiler-runtime";
import React, { type ReactNode } from 'react';
import { ListItem } from '../design-system/ListItem.js';
import React, { type ReactNode } from 'react'
import { ListItem } from '../design-system/ListItem.js'
export type SelectOptionProps = {
/**
* Determines if option is focused.
*/
readonly isFocused: boolean;
readonly isFocused: boolean
/**
* Determines if option is selected.
*/
readonly isSelected: boolean;
readonly isSelected: boolean
/**
* Option label.
*/
readonly children: ReactNode;
readonly children: ReactNode
/**
* Optional description to display below the label.
*/
readonly description?: string;
readonly description?: string
/**
* Determines if the down arrow should be shown.
*/
readonly shouldShowDownArrow?: boolean;
readonly shouldShowDownArrow?: boolean
/**
* Determines if the up arrow should be shown.
*/
readonly shouldShowUpArrow?: boolean;
readonly shouldShowUpArrow?: boolean
/**
* Whether ListItem should declare the terminal cursor position.
* Set false when a child declares its own cursor (e.g. BaseTextInput).
*/
readonly declareCursor?: boolean;
};
export function SelectOption(t0) {
const $ = _c(8);
const {
isFocused,
isSelected,
children,
description,
shouldShowDownArrow,
shouldShowUpArrow,
declareCursor
} = t0;
let t1;
if ($[0] !== children || $[1] !== declareCursor || $[2] !== description || $[3] !== isFocused || $[4] !== isSelected || $[5] !== shouldShowDownArrow || $[6] !== shouldShowUpArrow) {
t1 = <ListItem isFocused={isFocused} isSelected={isSelected} description={description} showScrollDown={shouldShowDownArrow} showScrollUp={shouldShowUpArrow} styled={false} declareCursor={declareCursor}>{children}</ListItem>;
$[0] = children;
$[1] = declareCursor;
$[2] = description;
$[3] = isFocused;
$[4] = isSelected;
$[5] = shouldShowDownArrow;
$[6] = shouldShowUpArrow;
$[7] = t1;
} else {
t1 = $[7];
}
return t1;
readonly declareCursor?: boolean
}
export function SelectOption({
isFocused,
isSelected,
children,
description,
shouldShowDownArrow,
shouldShowUpArrow,
declareCursor,
}: SelectOptionProps): React.ReactNode {
return (
<ListItem
isFocused={isFocused}
isSelected={isSelected}
description={description}
showScrollDown={shouldShowDownArrow}
showScrollUp={shouldShowUpArrow}
styled={false}
declareCursor={declareCursor}
>
{children}
</ListItem>
)
}

File diff suppressed because it is too large Load Diff