mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 06:15:51 +00:00
更新大量 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:
@@ -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() {}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user