Skip to content

[Feature] Combobox:有预选值时输入框文字的预填、同步与聚焦选取 #33

Description

@TW199501

前置确认

  • 我已搜索过现有 Issue 和讨论,确认这不是重复建议
  • 我确认这是对 XiHan.UI 的功能建议,而非使用问题咨询

需求背景 / 痛点

文档「基础用法」的写法是: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」,过滤结果为空。用户得自己先全选或删光,才能开始搜索。组件目前没有「聚焦 / 点击时整段选取」的选项。

期望的功能

  1. 受控时也能预填:例如在 prefillInputValue 里,把「给了 inputValue」和「inputValue 为空字符串」分开处理;或新增一个开关(如 syncInputWithValue),开了就在挂载时与 value 变化时把 valueText 写进 inputValue,并经 onInputValueChange 回传给宿主。
  2. value 变化时同步输入框文字:宿主写入 value 后,单选且不在编辑中(没有未提交的键入)时,把 inputValue 换成新选中项的文字。可以跟第 1 点共用同一个开关。
  3. 聚焦 / 点击时整段选取:新增如 selectOnFocus 选项。输入框文字等于选中项文字时,聚焦或点击就整段选取,让键入直接取代它、开始搜索;已在搜索中(文字不等于选中项)时就不动,留给用户移动光标。
  4. 文档补一个「有预选值」的示例:其中宿主过滤要留意一点:输入框文字就是选中项文字时不算在搜索,否则一展开下拉只剩已选的那一项。

影响范围

无头组件内核(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 没有溢出处理(不能横向滚动、文字也不折行),窄容器里最后几段会被裁掉。

参与贡献

  • 我愿意提交 PR 来实现这个功能

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions