# 下拉选择

增强原生 <select>：单选、多选、搜索、分组、清除、选项描述，选中值同步回原生 select 并触发原生 change，表单提交、name、required 照常工作。也可用 ZhaoUI.select 命令式创建，支持远程搜索和自动完成。

**依赖**：`zhaoui.css` 与 `zhaoui.js`（原生 HTML、CSS、JS，不需要框架或 jQuery）。只用核心包时，还要加 `modular/select.min.js` 和 `modular/select.min.css`；全量版已包含。

## 用法

```html
<label for="region">区域</label>
<select id="region" name="region" data-zh-select data-clearable required>
  <option value="">请选择区域</option>
  <option value="east">华东</option>
  <option value="south">华南</option>
  <option value="north">华北</option>
</select>

<label for="city">城市</label>
<select id="city" name="city" data-zh-select data-searchable data-empty-text="没有这个城市">
  <option value="">请选择城市</option>
  <optgroup label="直辖市">
    <option value="bj" data-keys="beijing bj" data-desc="首都">北京</option>
    <option value="sh" data-keys="shanghai sh">上海</option>
  </optgroup>
  <optgroup label="广东省">
    <option value="gz" data-keys="guangzhou gz">广州</option>
    <option value="sz" data-keys="shenzhen sz" disabled>深圳</option>
  </optgroup>
</select>

<label for="tags">标签</label>
<select id="tags" name="tags" multiple data-zh-select data-searchable data-clearable data-max-tags="2">
  <option value="fe" selected>前端</option>
  <option value="be" selected>后端</option>
  <option value="ux">设计</option>
</select>

<div id="remote"></div>

<script>
document.getElementById('region').addEventListener('change', function (e) {
  console.log(e.target.value);
});

// 远程搜索
ZhaoUI.select('#remote', {
  searchable: true, clearable: true, placeholder: '搜索技术',
  source: function (q) {
    return fetch('/api/tech?q=' + encodeURIComponent(q)).then(function (r) { return r.json(); });   // 返回 [{ value, label }, …]
  }
});
</script>
```

## 样式类

`zh-sel`、`zh-sel-native`

## 属性

| 属性 | 用在 | 取值 | 说明 |
|---|---|---|---|
| `data-zh-select` | <select> | 无 | 声明要增强这个 select；选项、optgroup、disabled、multiple、selected 都从它读取 |
| `multiple` | <select> | 布尔属性 | 多选：选中项显示为标签，列表保持打开 |
| `data-searchable` | <select> | 布尔属性 | 可输入搜索；按选项文字、data-desc、data-keys 匹配并高亮 |
| `data-clearable` | <select> | 布尔属性 | 有值时显示清除按钮 |
| `data-size` | <select> | sm \| md（默认）\| lg | 尺寸 |
| `data-placeholder` | <select> | 字符串 | 占位文字；不写则取值为空的那个 <option> 的文字，再没有则是「请选择」 |
| `data-empty-text` | <select> | 字符串 | 搜索无结果时的提示，默认「无匹配项」 |
| `data-max-tags` | <select multiple> | 整数 | 最多显示几个标签，其余折叠为「+N」 |
| `data-prefix-icon` | <select> | 图标名 | 输入框前的图标 |
| `data-desc / data-icon / data-keys` | <option> | 字符串 | 选项的第二行说明 / 图标 / 额外的搜索关键词（如拼音） |
| `value=""` | <option> | 空值 | 放一个空值的 option 作为占位；它不会出现在选项列表里，清除后 select 会回到它 |
| `aria-invalid="true"` | <select> | true | 错误状态外观 |

## 方法

- `selectEl.zhSelect.get() / .set(value)`：读取或设置值（多选传数组）。set 会同步原生 select，但不触发 change
- `selectEl.zhSelect.setOptions([{ value, label, desc?, icon?, disabled? }, …])`：更换选项（支持分组：{ label, options: [...] }）。原生 <select> 里的 <option> 会一起重建，已选且仍存在的值会保留，不再存在的会被移除
- `selectEl.zhSelect.setDisabled(bool) / .setInvalid(bool) / .open() / .close()`：禁用、错误状态、打开、关闭
- `ZhaoUI.select(host, config)`：命令式创建。host 是一个空 <div> 或选择器；config：options、value、multiple、searchable、clearable、size、placeholder、emptyText、maxTags、prefixIcon、disabled、invalid、labelledby、onChange(value, labels)。
远程搜索：source(query) 返回 Promise<选项数组>，可配 debounce（毫秒，默认 250）和 loading。
自动完成：freeText: true，输入的文字本身就是值，chevron: false 可隐藏箭头。返回同一套方法（get / set / setOptions / setDisabled / setInvalid / open / close）

## 事件

| 事件 | 触发于 | detail | 说明 |
|---|---|---|---|
| `change / input` | 原生 <select> | 原生事件 | 选中值变化时在原生 select 上触发（冒泡），已有的表单代码不用改 |
| `zh:select-change` | 生成的控件（select 后面的 .zh-sel） | { value, labels } | 单选时 value 是字符串，多选时是数组；labels 是对应的文字数组 |

## 无障碍

- 输入框是 role="combobox"，带 aria-expanded / aria-controls / aria-activedescendant；列表是 role="listbox"，选项是 role="option"，多选时有 aria-multiselectable
- <label for> 指向原生 select 时，点击 label 或表单校验聚焦，都会转到可见的输入框
- ↓ ↑ 打开并移动（到头会循环），Home / End 到首尾，Enter 选中，Esc 关闭，Tab 关闭并继续；不可搜索时直接输入字母可跳到对应选项，空格打开
- 多选时在空输入上按退格删除最后一个标签；标签上有带 aria-label 的移除按钮
- 禁用的选项标记 aria-disabled，不可选中，方向键会跳过
- 原生 select 保留在表单里（视觉隐藏、aria-hidden、不可 Tab），所以 required 的浏览器校验仍然有效

## 常见问题

### 下拉选择是什么？

增强原生 <select>：单选、多选、搜索、分组、清除、选项描述，选中值同步回原生 select 并触发原生 change，表单提交、name、required 照常工作。也可用 ZhaoUI.select 命令式创建，支持远程搜索和自动完成。

### 怎么使用下拉选择？

先在页面里引入 zhaoui.css 和 zhaoui.js（放在 </body> 前，不加 defer），再照本页「用法」里的标记书写；需要用代码控制时，用「方法」里列出的接口。

### 下拉选择有哪些事件？

change / input（选中值变化时在原生 select 上触发（冒泡），已有的表单代码不用改）；zh:select-change（单选时 value 是字符串，多选时是数组；labels 是对应的文字数组）。

### 下拉选择有哪些方法？

selectEl.zhSelect.get() / .set(value)：读取或设置值（多选传数组）。set 会同步原生 select，但不触发 change；selectEl.zhSelect.setOptions([{ value, label, desc?, icon?, disabled? }, …])：更换选项（支持分组：{ label, options: [...] }）。原生 <select> 里的 <option> 会一起重建，已选且仍存在的值会保留，不再存在的会被移除；selectEl.zhSelect.setDisabled(bool) / .setInvalid(bool) / .open() / .close()：禁用、错误状态、打开、关闭；ZhaoUI.select(host, config)：命令式创建。host 是一个空 <div> 或选择器；config：options、value、multiple、searchable、clearable、size、placeholder、emptyText、maxTags、prefixIcon、disabled、invalid、labelledby、onChange(value, labels)。
远程搜索：source(query) 返回 Promise<选项数组>，可配 debounce（毫秒，默认 250）和 loading。
自动完成：freeText: true，输入的文字本身就是值，chevron: false 可隐藏箭头。返回同一套方法（get / set / setOptions / setDisabled / setInvalid / open / close）。

### 下拉选择支持键盘和读屏吗？

输入框是 role="combobox"，带 aria-expanded / aria-controls / aria-activedescendant；列表是 role="listbox"，选项是 role="option"，多选时有 aria-multiselectable；<label for> 指向原生 select 时，点击 label 或表单校验聚焦，都会转到可见的输入框；↓ ↑ 打开并移动（到头会循环），Home / End 到首尾，Enter 选中，Esc 关闭，Tab 关闭并继续；不可搜索时直接输入字母可跳到对应选项，空格打开。

### 下拉选择需要依赖框架吗？

不需要。ZhaoUI 是原生 HTML、CSS 和 JavaScript，不依赖 jQuery、Vue 或 React。
