Skip to content

fix: submit shortcut triggers newline #140

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions docs/demos/suggestion/All.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
:placeholder="placeholder"
:clearable="true"
mode="multiple"
:has-content="hasContent"
@submit="handleSubmit"
@keydown="(e) => handleKeyDown(e, onTrigger, onKeyDown)"
@update:modelValue="updateInputValue"
Expand All @@ -53,7 +52,7 @@

<script setup lang="ts">
import { TrSender, TrSuggestion } from '@opentiny/tiny-robot'
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { nextTick, onMounted, ref, watch } from 'vue'
import { templateCategories, templateSuggestions } from './templateData'

// 状态管理
Expand All @@ -67,15 +66,6 @@ const loading = ref(false)
const placeholder = ref('输入 / 打开指令菜单...')
const messages = ref<string[]>([])

// 计算属性:判断是否有内容
const hasContent = computed(() => {
// 当处于指令编辑模式且输入内容不为空时,视为有内容
if (currentTemplate.value) {
return inputText.value.trim().length > 0
}
return inputText.value.trim().length > 0
})

// 更新输入值
const updateInputValue = (value) => {
inputText.value = value
Expand Down
41 changes: 10 additions & 31 deletions packages/components/src/sender/composables/useKeyboardHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type { SenderProps, SenderEmits, SpeechState, SubmitTrigger } from '../in
* @param closeSuggestionsPopup - 关闭建议弹窗的函数
* @param navigateSuggestions - 导航建议列表的函数
* @param toggleSpeech - 切换语音识别函数
* @param isOverLimit - 是否超出字数限制
* @param canSubmit - 是否可以提交
* @param currentMode - 当前输入模式
* @param setMultipleMode - 设置为多行模式的回调函数
* @param isTemplateMode - 是否处于模板编辑模式
Expand All @@ -34,42 +34,17 @@ export function useKeyboardHandler(
closeSuggestionsPopup: (keepFocus?: boolean) => void,
navigateSuggestions: (direction: 'up' | 'down') => void,
toggleSpeech: () => void,
isOverLimit: Ref<boolean>,
canSubmit: ComputedRef<boolean>,
currentMode?: Ref<'single' | 'multiple'>,
setMultipleMode?: () => void,
isTemplateMode?: ComputedRef<boolean>,
exitTemplateMode?: () => void,
) {
/**
* 验证提交条件
* @param value 输入值
* @returns 是否可以提交
*/
const validateSubmission = (value: string): boolean => {
// 基础状态检查:禁用或加载中时不能提交
if (props.disabled || props.loading) {
return false
}

// 内容检查:空内容不能提交
const trimmedValue = value.trim()
if (trimmedValue.length === 0) {
return false
}

// 字数限制检查:超出限制时不能提交
if (isOverLimit.value) {
return false
}

return true
}

/**
* 触发提交
*/
const triggerSubmit = () => {
if (!validateSubmission(inputValue.value)) return
if (!canSubmit.value) return

if (isTemplateMode?.value) {
exitTemplateMode?.()
Expand Down Expand Up @@ -178,10 +153,14 @@ export function useKeyboardHandler(
// 检查是否匹配当前的提交快捷键
const shouldSubmit = checkSubmitShortcut(event, props.submitType as SubmitTrigger)

// 只有当满足提交条件且输入框有内容时才提交
if (shouldSubmit && validateSubmission(inputValue.value)) {
if (shouldSubmit) {
// 只要是提交快捷键,就阻止默认行为(比如换行)
event.preventDefault()
triggerSubmit()

// 如果验证通过,则执行提交
if (canSubmit.value) {
triggerSubmit()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ref, computed, watch, nextTick } from 'vue'
import { ref, computed, watch, nextTick, ComputedRef } from 'vue'
import type { SenderProps, SenderEmits } from '../index.type'

/**
Expand Down Expand Up @@ -92,12 +92,14 @@ const highlightSuggestionText = (suggestionText: string, inputText: string) => {
* @param emit - 组件方法
* @param inputValue - 输入值
* @param isComposing - 是否处于输入法组合状态
* @param showTemplateEditor - 是否显示模板编辑器
*/
export function useSuggestionHandler(
props: SenderProps,
emit: SenderEmits,
inputValue: ReturnType<typeof ref<string>>,
isComposing: ReturnType<typeof ref<boolean>>,
showTemplateEditor: ComputedRef<boolean>,
) {
// 状态变量
/**
Expand Down Expand Up @@ -145,7 +147,7 @@ export function useSuggestionHandler(
* 基于当前输入过滤出匹配的建议项
*/
const filteredSuggestions = computed(() => {
if (!props.suggestions || !inputValue.value || props.template) return []
if (!props.suggestions || !inputValue.value || showTemplateEditor.value) return []
const lowerInputValue = inputValue.value.toLowerCase()
return props.suggestions.filter((item) => item.toLowerCase().includes(lowerInputValue))
})
Expand Down Expand Up @@ -235,7 +237,7 @@ export function useSuggestionHandler(
inputValue.value &&
props.suggestions &&
props.suggestions.length > 0 &&
!props.template &&
!showTemplateEditor.value &&
filteredSuggestions.value.length > 0

if (shouldShowSuggestions) {
Expand Down
2 changes: 0 additions & 2 deletions packages/components/src/sender/index.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ export interface SenderProps {
suggestions?: string[] // 输入建议
suggestionPopupWidth?: string | number // 联想建议弹窗宽度,如 '300px' 或 300
theme?: ThemeType // 主题
template?: string // 模板字符串,格式如 "你好 [称呼],感谢您的 [事项]"
hasContent?: boolean // 手动指定是否有内容,用于模板模式
templateData?: UserItem[] // 模板数据
stopText?: string // 停止按钮文字,不传则只显示图标
}
Expand Down
31 changes: 25 additions & 6 deletions packages/components/src/sender/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ const props = withDefaults(defineProps<SenderProps>(), {
showWordLimit: false,
submitType: 'enter',
theme: 'light',
hasContent: undefined,
template: '',
templateData: () => [],
suggestions: () => [],
suggestionPopupWidth: 400,
Expand All @@ -50,6 +48,28 @@ const showTemplateEditor = computed(() => props.templateData && props.templateDa
// 输入控制
const { inputValue, isComposing, clearInput: originalClearInput }: InputHandler = useInputHandler(props, emit)

const hasContent = computed(() => !!inputValue.value.trim())

// 统一的提交条件验证
const canSubmit = computed(() => {
// 基础状态检查:禁用或加载中时不能提交
if (props.disabled || props.loading) {
return false
}

// 内容检查:空内容不能提交
if (!hasContent.value) {
return false
}

// 字数限制检查:超出限制时不能提交
if (isOverLimit.value) {
return false
}

return true
})

// 建议处理
const {
showSuggestionsPopup,
Expand All @@ -67,7 +87,7 @@ const {
handleSuggestionItemHover,
handleSuggestionItemLeave,
highlightSuggestionText,
} = useSuggestionHandler(props, emit, inputValue, isComposing)
} = useSuggestionHandler(props, emit, inputValue, isComposing, showTemplateEditor)

// 自动模式切换
const currentMode = ref(props.mode)
Expand Down Expand Up @@ -329,7 +349,7 @@ const { handleKeyPress, triggerSubmit }: KeyboardHandler = useKeyboardHandler(
closeSuggestionsPopup,
navigateSuggestions,
toggleSpeech,
isOverLimit,
canSubmit,
currentMode,
setMultipleMode,
showTemplateEditor,
Expand All @@ -340,7 +360,7 @@ const { handleKeyPress, triggerSubmit }: KeyboardHandler = useKeyboardHandler(
const handleFocus = (event: FocusEvent) => {
emit('focus', event)
// 当有输入内容且有匹配的联想项时,显示联想弹窗但不自动选中任何项
if (inputValue.value && filteredSuggestions.value.length > 0 && !props.template) {
if (inputValue.value && filteredSuggestions.value.length > 0 && !showTemplateEditor.value) {
showSuggestionsPopup.value = true
showTabHint.value = true
}
Expand Down Expand Up @@ -382,7 +402,6 @@ const hasDecorativeContent = computed(() => !!slots.decorativeContent)
// 状态计算
const isDisabled = computed((): boolean => props.disabled || hasDecorativeContent.value)
const isLoading = computed(() => props.loading)
const hasContent = computed(() => (props.hasContent !== undefined ? props.hasContent : !!inputValue.value))

// 样式类
const senderClasses = computed(() => ({
Expand Down