前置确认
需求背景 / 痛点
文档「基础用法」的写法是:v-model:input-value 接住键入文字,由宿主自己过滤后交给 collection。这个写法在没有预选值时没问题;一旦组合框打开时就带着选中值(编辑表单回填、记住上次选择等),会连续遇到三个问题。
实际场景:手机号码输入的国家下拉(可搜索),打开时就选好「台灣 +886」。
1. 接了 input-value 就不预填选中项文字,输入框是空的
prefillInputValue 只在 inputValue / defaultInputValue 都没给时才填:
// ui/packages/engine/headless/src/combobox/combobox.machine.ts
prefillInputValue: ({ prop, context, flush }) => {
if (prop('multiple') || prop('inputValue') !== undefined || prop('defaultInputValue') !== undefined)
return
// ...
}
但照文档要做宿主过滤,就必须接 input-value。结果是有选中值(value=['TW']),输入框却一片空白,看不出选了什么。
2. 宿主从外部改选中值,输入框文字不跟着变
value 变化时 watch 只跑 syncValueText,它只更新内部的 valueText,不动 inputValue。预填又只在 entry 做一次。所以宿主改了选中值(例如同一个编辑弹窗换成另一笔数据),输入框还停在旧的文字。不接 input-value 的非受控用法按源码看也一样(这一点只读了源码,没有单独实测)。
3. 输入框已有文字时,键入会插进文字中间,搜不到任何项
宿主自己补上预填(或非受控情况下组件预填)后,输入框显示「台灣 +886」。用户点进去,光标落在点击处,接着键入「日本」就变成「台灣 日本+886」,过滤结果为空。用户得自己先全选或删光,才能开始搜索。组件目前没有「聚焦 / 点击时整段选取」的选项。
期望的功能
- 受控时也能预填:例如在
prefillInputValue 里,把「给了 inputValue」和「inputValue 为空字符串」分开处理;或新增一个开关(如 syncInputWithValue),开了就在挂载时与 value 变化时把 valueText 写进 inputValue,并经 onInputValueChange 回传给宿主。
value 变化时同步输入框文字:宿主写入 value 后,单选且不在编辑中(没有未提交的键入)时,把 inputValue 换成新选中项的文字。可以跟第 1 点共用同一个开关。
- 聚焦 / 点击时整段选取:新增如
selectOnFocus 选项。输入框文字等于选中项文字时,聚焦或点击就整段选取,让键入直接取代它、开始搜索;已在搜索中(文字不等于选中项)时就不动,留给用户移动光标。
- 文档补一个「有预选值」的示例:其中宿主过滤要留意一点:输入框文字就是选中项文字时不算在搜索,否则一展开下拉只剩已选的那一项。
影响范围
无头组件内核(headless)
相关组件 / 包名
@xihan-ui/headless · combobox(Vue 适配器 XhComboboxRoot 同受影响;@xihan-ui/* 1.1.0,对照源码 1a90b1c39 行为相同)
替代方案
目前在宿主(XiHan.BasicApp 的 PhoneInput.vue)自己补:
// 输入框文字等于已选名称时不算搜索,展开要看到全部
const selectedLabel = computed(() => options.value.find(o => o.value === country.value)?.label ?? '')
const filteredOptions = computed(() => {
const keyword = query.value.trim().toLowerCase()
if (!keyword || query.value === selectedLabel.value)
return options.value
return options.value.filter(o => o.label.toLowerCase().includes(keyword))
})
// 问题 1、2:挂载与选中项变化(含切换语言)时自己把名称写进 input-value
watch(selectedLabel, (label) => { query.value = label }, { immediate: true })
// 问题 3:点击时若显示的是已选名称就整段选取(Tab 聚焦时浏览器本来就会全选)
function selectCountryText(event: MouseEvent) {
const t = event.target
if (t instanceof HTMLInputElement && t.getAttribute('role') === 'combobox' && t.value === selectedLabel.value)
t.select()
}
<div @click="selectCountryText">
<XhComboboxRoot v-model:input-value="query" :collection="filteredOptions" :value="[country]" open-on-click ... />
</div>
可行,但每个「带预选值的可搜索下拉」都得重抄一遍,而且整段选取要靠外层 click 冒泡,再用 role="combobox" 去认输入框,依赖了组件内部的 DOM 结构。
补充说明
- 已在 Chromium、Firefox、WebKit 实测:宿主补丁版本三点都正常。补丁前实际遇到过问题 1(输入框空白)与问题 3(键入变成「台灣 日本+886」);问题 2 在宿主的单元测试里重现过(受控用法)。
- 相关但独立的观察(如需要可另开 Issue):
[data-part='control'] 自带 min-inline-size: var(--xh-control-min-w)(12rem)。宿主给根节点较小宽度时(如 inline-size: 140px),control 会溢出根节点,压到相邻控件,聚焦环也跟着压上去。目前要靠 --xh-combobox-control-min-w: 0 放开;text-field 同样有此行为(--xh-text-field-control-min-w / --xh-text-field-input-min-w)。
- segmented 没有溢出处理(不能横向滚动、文字也不折行),窄容器里最后几段会被裁掉。
参与贡献
前置确认
需求背景 / 痛点
文档「基础用法」的写法是:
v-model:input-value接住键入文字,由宿主自己过滤后交给collection。这个写法在没有预选值时没问题;一旦组合框打开时就带着选中值(编辑表单回填、记住上次选择等),会连续遇到三个问题。实际场景:手机号码输入的国家下拉(可搜索),打开时就选好「台灣 +886」。
1. 接了
input-value就不预填选中项文字,输入框是空的prefillInputValue只在inputValue/defaultInputValue都没给时才填:但照文档要做宿主过滤,就必须接
input-value。结果是有选中值(value=['TW']),输入框却一片空白,看不出选了什么。2. 宿主从外部改选中值,输入框文字不跟着变
value变化时 watch 只跑syncValueText,它只更新内部的valueText,不动inputValue。预填又只在entry做一次。所以宿主改了选中值(例如同一个编辑弹窗换成另一笔数据),输入框还停在旧的文字。不接input-value的非受控用法按源码看也一样(这一点只读了源码,没有单独实测)。3. 输入框已有文字时,键入会插进文字中间,搜不到任何项
宿主自己补上预填(或非受控情况下组件预填)后,输入框显示「台灣 +886」。用户点进去,光标落在点击处,接着键入「日本」就变成「台灣 日本+886」,过滤结果为空。用户得自己先全选或删光,才能开始搜索。组件目前没有「聚焦 / 点击时整段选取」的选项。
期望的功能
prefillInputValue里,把「给了inputValue」和「inputValue为空字符串」分开处理;或新增一个开关(如syncInputWithValue),开了就在挂载时与value变化时把valueText写进inputValue,并经onInputValueChange回传给宿主。value变化时同步输入框文字:宿主写入value后,单选且不在编辑中(没有未提交的键入)时,把inputValue换成新选中项的文字。可以跟第 1 点共用同一个开关。selectOnFocus选项。输入框文字等于选中项文字时,聚焦或点击就整段选取,让键入直接取代它、开始搜索;已在搜索中(文字不等于选中项)时就不动,留给用户移动光标。影响范围
无头组件内核(headless)
相关组件 / 包名
@xihan-ui/headless · combobox(Vue 适配器 XhComboboxRoot 同受影响;@xihan-ui/* 1.1.0,对照源码 1a90b1c39 行为相同)
替代方案
目前在宿主(XiHan.BasicApp 的
PhoneInput.vue)自己补:可行,但每个「带预选值的可搜索下拉」都得重抄一遍,而且整段选取要靠外层
click冒泡,再用role="combobox"去认输入框,依赖了组件内部的 DOM 结构。补充说明
[data-part='control']自带min-inline-size: var(--xh-control-min-w)(12rem)。宿主给根节点较小宽度时(如inline-size: 140px),control 会溢出根节点,压到相邻控件,聚焦环也跟着压上去。目前要靠--xh-combobox-control-min-w: 0放开;text-field 同样有此行为(--xh-text-field-control-min-w/--xh-text-field-input-min-w)。参与贡献