
radix-vue EditableInputEditable 编辑输入部分的 API、焦点管理与源码级行为解析【免费下载链接】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-vueEditableInput是 radix-vuereka-ui中 Editable 组件族的编辑态输入部分Editable 以静态文本形式展示单行内容触发编辑交互后切换为真正的文本输入框而承载输入框职责的正是EditableInput。本文基于 API 元文档 EditableInput.md 完整讲解其 Props 定义并结合 EditableInput.vue 与 EditableRoot.vue 源码深入剖析其值绑定、焦点管理、键盘交互与 autoResize 布局机制。EditableInput 在 Editable 组件中的位置Editable 采用组合式composition结构EditableRoot提供状态机与上下文EditableArea承载预览/输入两种呈现各类 Trigger 负责状态切换。完整的组件解剖如下引自 组件文档script setup import { EditableArea, EditableCancelTrigger, EditableEditTrigger, EditableInput, EditablePreview, EditableRoot, EditableSubmitTrigger } from reka-ui /script template EditableRoot EditableArea EditablePreview / EditableInput / /EditableArea EditableEditTrigger / EditableSubmitTrigger / EditableCancelTrigger / /EditableRoot /templateEditablePreview与EditableInput同时存在于EditableArea中由 Root 的isEditing状态决定哪一个可见。所有子部分都通过injectEditableRootContext()注入 EditableRoot.vue 中 provide 的上下文——这正是理解EditableInput行为的关键它自身不保存值而是完全委托给 Root 管理的inputValue、isEditing、disabled等状态。Props API 参考API 元文档定义了EditableInput的全部公开 Props见 EditableInput.mdNameDescriptionTypeRequiredDefaultasThe element or component this component should render as. Can be overwritten byasChild.AsTag \| ComponentNoinputasChildChange the default rendered element for the one passed as a child, merging their props and behavior.booleanNo-从源码接口定义看这两个 Props 直接继承自库内通用的PrimitiveProps类型// packages/core/src/Editable/EditableInput.vue import type { PrimitiveProps } from /Primitive export interface EditableInputProps extends PrimitiveProps {} const props withDefaults(definePropsEditableInputProps(), { as: input, })也就是说EditableInput没有任何自有业务 Props其全部对外 API 就是以什么元素渲染这一层。下面分别展开。as渲染元素的定制as的类型为AsTag | Component默认值input。AsTag是库内定义的内置标签联合类型定义在 Primitive.ts涵盖a、button、div、span、input、label等常见标签并额外允许任意字符串标签({} string)。Primitive组件的渲染逻辑Primitive.ts值得注意当目标元素是自闭合标签area、img、input见源码中的SELF_CLOSING_TAGS时直接h(asTag, attrs)渲染不提供默认插槽——这是为了规避 hydration 问题。这也解释了为什么EditableInput的默认渲染路径下不关心 slot当asChild生效时元素被替换为template实际渲染委托给Slot组件完成子元素合并。asChild与自定义元素合并asChild遵循 Radix 风格的组合Composition约定将默认渲染元素替换为作为子节点传入的元素并合并双方的 props 与行为。对EditableInput而言典型用途是希望输入框直接渲染为自定义组件例如带校验提示的输入组件同时保留 Editable 注入的value、disabled、readonly、事件处理等行为。渲染层面由Primitive中的asChild ? template : props.as分支Primitive.ts统一实现。值绑定与状态委托源码级剖析EditableInput的模板EditableInput.vue把所有关键属性都绑定到了 Root 上下文Primitive refprimitiveElement v-bindprops :valuecontext.inputValue.value :placeholderplaceholder :disableddisabled :maxlengthcontext.maxLength.value :data-disableddisabled ? : undefined :data-readonlycontext.readonly.value ? : undefined :readonlycontext.readonly.value aria-labeleditable input :hiddencontext.autoResize.value ? undefined : !context.isEditing.value :stylecontext.autoResize.value ? { all: unset, gridArea: 1 / 1 / auto / auto, visibility: !context.isEditing.value ? hidden : undefined } : undefined inputcontext.inputValue.value $event.target.value keydown.enter.spacehandleSubmitKeyDown keydown.esccontext.cancel slot / /Primitive可以逐条对应到 EditableRoot.vue 上下文接口中的成员inputValue输入事件直接写回context.inputValue。注意 Root 内部区分了modelValue已提交的值与inputValue编辑中的值submit()时才会执行modelValue.value inputValue.valueEditableRoot.vue这实现了编辑过程中的草稿值与外部受控值的隔离placeholder取的是context.placeholder.value?.edit——Root 会把字符串形式 placeholder 归一化为{ edit, preview }对象EditableRoot.vuePreview 与 Input 各取所需disabled/readonly透传为原生属性并同时输出data-disabled、data-readonly数据属性供 CSS 选择器如[data-readonly]、[data-disabled]做样式区分maxlength来自 Root 的maxLengthprop默认无限制。焦点管理进入编辑态的完整链路Focus is fully managed 是 组件文档 列出的 Editable 核心特性之一其实现分散在两处1. 挂载时的初始聚焦。若 Root 设置了startWithEditModeEditableInput在onMounted时立即聚焦并可选全选文本EditableInput.vueonMounted(() { context.inputRef.value inputRef.value as HTMLInputElement if (context.startWithEditMode.value) { context.inputRef.value?.focus({ preventScroll: true }) if (context.selectOnFocus.value) context.inputRef.value?.select() } })同时inputRef被注册进上下文供 Root 统一管理焦点。2. 编辑态切换时的响应式聚焦。通过watch(context.isEditing, ...)任何来源Preview 点击、Edit Trigger、Root 的edit()把isEditing置为true时下一 tick 自动聚焦输入框并在selectOnFocus为真时全选内容EditableInput.vue。聚焦使用focus({ preventScroll: true })避免页面滚动跳动。与之对称的是退出编辑态的来源Escape 键模板中keydown.esccontext.cancel直接调用 Root 的cancel()回滚isEditing并触发update:state为cancelEditableRoot.vue点击外部 / 焦点移出Root 通过usePointerDownOutside与useFocusOutsideEditableRoot.vue监听进入handleDismiss()submitMode为blur或both时提交否则取消。键盘提交handleSubmitKeyDown的判定条件Enter 提交逻辑非常严格EditableInput.vuefunction handleSubmitKeyDown(event: KeyboardEvent) { if ((context.submitMode.value enter || context.submitMode.value both) event.key kbd.ENTER !event.shiftKey !event.metaKey !event.isComposing) context.submit() }四个守卫条件各有实际意义submitMode为enter或both才生效SubmitMode类型定义在 EditableRoot.vue取值为blur | enter | none | both默认blur——即默认配置下 Enter 不提交失焦才提交!event.shiftKey避免多行场景下 ShiftEnter 换行被误判为提交!event.metaKey避免快捷键冲突!event.isComposing中文等 IME 输入法在组合状态下按 Enter 是确认候选词而非确认输入该条件防止输入法用户在组词过程中被意外提交——这是输入类组件一个容易遗漏的边界处理。对应的无障碍键盘交互约定来自 组件文档按键行为Tab焦点移入时若activation-mode为focus切入编辑模式Enter若submit-mode为enter或both提交修改Escape焦点在可编辑字段上时取消修改激活模式编辑态由谁触发activationModefocus | dblclick | none默认focus决定 Preview 何时进入编辑态触发点位于 EditablePreview.vuefunction handleFocus() { if (context.activationMode.value focus) context.edit() } function handleDoubleClick() { if (context.activationMode.value dblclick) context.edit() }EditablePreview自身带tabindex0因此Tab进入预览区即触发focus激活设置为dblclick则仅响应双击none时只能靠EditableEditTrigger或调用 Root 暴露的edit()方法defineExpose了submit、cancel、edit三个方法见 EditableRoot.vue来手动切换。EditableInput是这条链路的终端它只负责进入编辑态后接管焦点与输入。autoResize预览与输入的尺寸同步默认模式下Preview 与 Input 通过hidden属性互斥显隐:hidden!context.isEditing.value。启用 Root 的autoResize后策略变为双元素叠放两者都被赋予gridArea: 1 / 1 / auto / auto叠放在同一 grid 单元格中非活动方改用visibility: hidden保持占位。这样隐藏的元素依然参与布局计算输入框的宽度、高度可以精确继承预览文本的尺寸避免切换时布局跳动EditableInput.vue、EditablePreview.vue。实战示例仅在提交按钮触发时提交组件文档 给出一个典型用法默认submitMode为blur失焦即提交若希望只有点击EditableSubmitTrigger才提交将 Root 设置为submit-modenonetemplate EditableRoot submit-modenone EditableArea EditablePreview / EditableInput / /EditableArea EditableEditTrigger / EditableSubmitTrigger / EditableCancelTrigger / /EditableRoot /template此时点击外部、焦点移出都不会触发submit()或cancel()handleDismiss仅在submitMode为blur/both时提交否则取消见 EditableRoot.vue提交权完全交给显式触发的控件。此外Editable 支持受控与非受控两种模式Root 通过useVModel管理modelValuepassive 与否取决于外部是否传入modelValue并配合defaultValue作为初始值EditableRoot.vue。在受控模式下外部值变化会经由watch同步进inputValue从而反映到EditableInput的:value绑定上。小结EditableInput的 API 面虽然只有as与asChild两个继承自PrimitiveProps的渲染定制 Props默认以原生input渲染可经asChild合并进任意子组件但它在源码层面承担了 Editable 状态机的全部编辑态职责草稿值与受控值的双层数据模型、startWithEditMode/selectOnFocus驱动的初始与响应式聚焦、带 IME 守卫的 Enter 提交、Escape 取消以及 autoResize 叠放布局。理解这些行为时关键都指向上游上下文 EditableRoot.vue 中edit()/submit()/cancel()三个状态迁移方法——这也是复用或扩展 EditableInput 行为例如自定义 asChild 组件时最需要注意的边界。【免费下载链接】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),仅供参考