Reka UI TagsInputInput 组件:标签输入框的 Props、键盘交互与源码级解析
【免费下载链接】radix-vueAn open-source UI component library for building high-quality, accessible design systems and web apps for Vue. Previously Radix Vue项目地址: https://gitcode.com/GitHub_Trending/ra/radix-vue
导读
TagsInputInput是 Reka UI(即本仓库 radix-vue,组件库源码位于 packages/core/src/TagsInput)标签输入组件(Tags Input)中的核心文本输入部件:它负责接收用户键入的标签文本,并把"回车添加标签、粘贴批量添加、失焦添加、Tab 添加"等交互转化为根组件TagsInputRoot的标签状态更新。读完本文,你将掌握TagsInputInput的每一个 Props 的含义与默认值、它与根组件上下文(Context)的协作方式,以及Enter / Tab / Backspace / Delete / 方向键等键盘交互在源码中是如何实现的,从而能在自己的 Vue 项目中正确、高效地使用并定制标签输入。
定位与职责:输入部件如何与根组件协作
Tags Input 组件由多个部件组成:TagsInputRoot(容器,管理标签数组状态)、TagsInputItem(单个标签)、TagsInputItemText(标签文本)、TagsInputItemDelete(删除按钮)、TagsInputInput(文本输入框)以及TagsInputClear(清空按钮)。组件整体结构可参考文档 docs/content/docs/components/tags-input.md 中的 Anatomy 一节:
<script setup> import { TagsInputClear, TagsInputInput, TagsInputItem, TagsInputItemDelete, TagsInputItemText, TagsInputRoot } from 'reka-ui' </script> <template> <TagsInputRoot> <TagsInputItem> <TagsInputItemText /> <TagsInputItemDelete /> </TagsInputItem> <TagsInputInput /> <TagsInputClear /> </TagsInputRoot> </template>从源码结构看,TagsInputInput本身不保存标签数据,它通过injectTagsInputRootContext()注入根组件通过 provideTagsInputRootContext 提供的上下文(Context),然后调用其中的onAddValue、onInputKeydown等回调,把输入事件转化为根组件的modelValue更新。这种"子部件只负责输入事件采集、根组件负责状态管理"的架构,与 Reka UI 其他复合组件(如 Combobox、Select)保持一致。
Props 完整说明
TagsInputInput的 Props 定义位于 TagsInputInput.vue,继承自PrimitiveProps,共 5 个属性:
| Name | Description | Type | Required | Default |
|---|---|---|---|---|
as | 组件要渲染成的元素或组件,可被asChild覆盖。 | AsTag \| Component | No | "input" |
asChild | 将默认渲染元素替换为传入的子元素,并合并其 props 与行为。 | boolean | No | - |
autoFocus | 挂载时自动聚焦该元素。 | boolean | No | - |
maxLength | 允许输入的最大字符数。 | number | No | - |
placeholder | 空标签输入时的占位字符。 | string | No | - |
as与asChild:Composition(组合)能力
这是 Reka UI 所有基础部件的通用能力。as默认值为'input',即默认渲染一个原生<input>;通过as可改渲染成其他标签或组件,通过asChild则可以把自定义子元素作为实际渲染节点(例如用无样式的input子元素包裹自定义图标)。这一机制在 Primitive 中实现,细节可参考仓库文档 docs/content/docs/guides 下的 Composition 指南。
autoFocus:挂载后自动聚焦
源码中,autoFocus在onMounted钩子里实现(TagsInputInput.vue):先查找实际渲染的<input>节点(考虑到asChild场景),然后通过setTimeout(..., 1)延迟一个宏任务,确保 DOM 完全刷新后再调用inputEl.focus():
onMounted(() => { const inputEl = currentElement.value.nodeName === 'INPUT' ? currentElement.value : currentElement.value.querySelector('input') if (!inputEl) return setTimeout(() => { // make sure all DOM was flush then only capture the focus if (props.autoFocus) inputEl?.focus() }, 1) })maxLength:单次输入长度上限
直接透传给原生input的maxlength属性(模板中:maxlength="maxLength")。注意它与根组件的max(标签总数上限)是两个不同的维度:maxLength限制正在输入的文本长度,max限制已添加的标签数量。
placeholder:空输入提示
透传给原生input的placeholder。仓库中的示例用法见 TagsInput/story/_TagsInput.vue:
<TagsInputInput placeholder="Anything..." class="focus:outline-none flex-1 rounded bg-transparent text-white placeholder:text-mauve10 px-1" />数据属性(Data Attributes)
TagsInputInput会向渲染节点输出以下数据属性,便于样式定制与测试:
| Attribute | Values |
|---|---|
[data-invalid] | Present when input value is invalid |
当输入重复标签或超过max上限时,根组件会把isInvalidInput置为true,TagsInputInput的模板中对应渲染:data-invalid="context.isInvalidInput.value ? '' : undefined"(TagsInputInput.vue)。搭配 CSS 选择器[data-invalid]即可实现输入非法值的红色描边等反馈。
输入交互的源码级解析
TagsInputInput的核心价值在于它把 5 类输入事件与根组件的标签管理逻辑挂钩(模板绑定见 TagsInputInput.vue)。
Enter 回车添加标签
@keydown.enter="handleCustomKeydown"在按下回车时调用。handleCustomKeydown(TagsInputInput.vue)的逻辑:
- 若正处于输入法组合(
isComposing)阶段则跳过(避免拼音/日文输入法候选选中干扰); await nextTick()后检查event.defaultPrevented——若用户自定义了@keydown.enter.prevent,则尊重用户逻辑,不再自动添加;- 输入值为空则直接返回;
- 调用
context.onAddValue(target.value)尝试添加,成功(返回true)后清空输入框; event.preventDefault()阻止默认行为,避免组件处于<form>中时触发表单提交。
Tab 添加标签
@keydown.tab="handleTab":当根组件开启addOnTab时,Tab 键行为与回车一致(调用同一个handleCustomKeydown);否则走原生 Tab 焦点切换(TagsInputInput.vue)。
Blur 失焦添加
@blur="handleBlur"(TagsInputInput.vue)在根组件开启addOnBlur时生效,且有两处防御逻辑:
- 先清除当前选中的标签(
selectedElement); - 如果失焦是因为点击了下拉内容(如与 Combobox 组合时的候选项)导致(通过
aria-controls与relatedTarget.closest判断),则不触发添加——因为被点击的候选项应作为新标签添加,而不是把输入框当前值添加进去; - 输入为空则直接返回。
Paste 粘贴批量添加
@paste="handlePaste"(TagsInputInput.vue)在根组件开启addOnPaste时,阻止默认粘贴行为,读取剪贴板文本,并用delimiter(分隔符)切分后逐段调用onAddValue批量添加标签。例如粘贴"vue,react,svelte",在默认逗号分隔符下会一次生成三个标签。
Delimiter 分隔符即时触发
@input="handleInput"(TagsInputInput.vue)监听每次输入:若当前输入字符匹配delimiter(支持字符串或正则),先把该字符从输入值中剔除,再尝试把前面已输入的内容作为标签添加。这样用户输入"vue,"时,vue会立即成为标签,而逗号不会残留在输入框里。输入法组合期间该逻辑同样被跳过。
方向键与删除键:标签键盘导航
@keydown="handleInputKeydown"把键盘事件转发给根组件的onInputKeydown(TagsInputRoot.vue),实现了完整的标签键盘导航:
Backspace:光标位于输入框首位时,先选中最后一个标签;若已有选中标签则删除它并把选中态移到左侧标签(左无标签则回到输入框);Delete:标签处于激活态时删除当前标签,并把激活态移到右侧标签;ArrowLeft:光标在首位时选中上一个标签(RTL 下方向反转);ArrowRight:在最后一个标签上按右方向键则取消选中、回到输入框;Home/End:激活第一个 / 最后一个标签。
这些行为与文档 docs/content/docs/components/tags-input.md 的 Keyboard Interactions 一节描述的键位一一对应。仓库测试 TagsInput.test.ts 中覆盖了"回车添加标签后清空输入框、addTag事件发出、ArrowLeft 选中标签、data-state 在 active/inactive 间切换"等行为,可当作行为契约参考。
与根组件 Props 的联动要点
TagsInputInput的行为受根组件TagsInputRoot若干 Props 驱动,使用时应组合配置(完整参数见 TagsInputRoot.vue 与 docs/content/meta/TagsInputRoot.md):
| Root 参数 | 类型 | 默认值 | 对 Input 的影响 |
|---|---|---|---|
addOnBlur | boolean | - | 失焦时把输入框内容添加为标签 |
addOnPaste | boolean | - | 粘贴时按分隔符切分并批量添加 |
addOnTab | boolean | - | Tab 键等同回车添加标签 |
delimiter | string \| RegExp | "," | 输入/粘贴时的分隔触发与切分规则 |
disabled | boolean | - | 禁用输入框(透传disabled并输出data-invalid相关状态) |
duplicate | boolean | - | 允许重复标签;否则重复输入会被判定为 invalid |
max | number | 0 | 标签数量上限(0表示不限),超限触发invalid事件 |
convertValue | (value: string) => T | - | 使用对象作标签值时,把输入字符串转换为对象,必填 |
displayValue | (value: T) => string | value.toString() | 自定义标签显示文本 |
modelValue/defaultValue | T[] | [] | 受控 / 非受控的标签数组 |
典型组合示例
粘贴自动添加(来自文档 docs/content/docs/components/tags-input.md 的 Paste behavior 示例):
<script setup lang="ts"> import { TagsInputInput, TagsInputItem, TagsInputItemDelete, TagsInputItemText, TagsInputRoot } from 'reka-ui' </script> <template> <TagsInputRoot v-model="modelValue" add-on-paste> … </TagsInputRoot> </template>多分隔符正则(来自文档的 Multiple delimiters 示例,逗号、分号、空格、制表符、换行均可触发添加):
<script setup lang="ts"> import { TagsInputInput, TagsInputItem, TagsInputItemDelete, TagsInputItemText, TagsInputRoot } from 'reka-ui' // split by space, comma, semicolon, tab, or newline const delimiter = /[ ,;\t\n\r]+/ </script> <template> <TagsInputRoot v-model="modelValue" :delimiter="delimiter" add-on-paste> … </TagsInputRoot> </template>对象标签值:当标签值是对象时(参考 story 目录下的 TagsInputObject.story.vue),必须同时提供convertValue(把输入字符串转成对象)与displayValue(把对象渲染成可读文本);否则根组件会抛出错误:You must provide a convertValue function when using objects as values.(见 TagsInputRoot.vue)。
键盘交互一览
TagsInputInput聚焦时支持的完整键盘操作(对应 docs/content/docs/components/tags-input.md 的 Accessibility 章节):
| 按键 | 行为 |
|---|---|
Enter | 把当前输入内容添加为标签(输入法组合期间忽略) |
Tab | 开启addOnTab时等同回车添加 |
Backspace | 光标在首位时:先选中最后一个标签;标签激活时删除并激活左侧标签 |
Delete | 标签激活时删除当前标签并激活右侧标签 |
ArrowLeft/ArrowRight | 在标签间移动激活态(RTL 方向自动反转),最后一个标签上按右方向回到输入框 |
Home/End | 激活第一个 / 最后一个标签 |
总结
TagsInputInput是 Reka UI Tags Input 的"输入入口":它自身只负责事件采集与转发,通过 Context 把回车、Tab、失焦、粘贴、分隔符输入、方向键等交互交给根组件TagsInputRoot统一处理标签状态。实际使用时,请记住三组关键配置:
- 添加时机:
addOnTab/addOnBlur/addOnPaste三个布尔 Props 控制何时把输入内容转为标签; - 分隔规则:
delimiter(字符串或正则)同时作用于输入即时触发与粘贴批量切分; - 约束校验:
maxLength限制单次输入长度,根组件的max限制标签总数,duplicate控制是否允许重复,非法输入会通过[data-invalid]与invalid事件暴露给开发者。
深入源码可继续阅读 TagsInputInput.vue、TagsInputRoot.vue 以及行为测试 TagsInput.test.ts,或参考完整组件文档 docs/content/docs/components/tags-input.md。
【免费下载链接】radix-vueAn open-source UI component library for building high-quality, accessible design systems and web apps for Vue. Previously Radix Vue项目地址: https://gitcode.com/GitHub_Trending/ra/radix-vue
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考