# 级联选择

多级联动选择（省市区、分类树）。数据写成页面里的嵌套列表，或用 ZhaoUI.cascader 传一棵树。支持搜索、悬停展开，窄屏自动变成逐级下钻。

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

## 用法

```html
<div data-zh-cascader data-name="region" data-searchable data-value="浙江/杭州/西湖区" data-placeholder="请选择地区" aria-label="地区">
  <ul hidden>
    <li>浙江
      <ul>
        <li>杭州<ul><li>西湖区</li><li>余杭区</li></ul></li>
        <li>宁波<ul><li>海曙区</li><li>鄞州区</li></ul></li>
      </ul>
    </li>
    <li>江苏<ul><li>南京<ul><li>玄武区</li></ul></li></ul></li>
    <li data-disabled>海南<ul><li>海口<ul><li>龙华区</li></ul></li></ul></li>
  </ul>
</div>

<div id="cas2"></div>

<script>
document.querySelector('[data-zh-cascader]').addEventListener('zh:cascader-change', function (e) {
  console.log(e.detail.text);   // 浙江 / 杭州 / 西湖区
});

ZhaoUI.cascader('#cas2', {
  expandTrigger: 'hover',
  data: [{ label: '前端', value: 'fe', children: [{ label: 'Vue', value: 'vue' }, { label: 'React', value: 'react' }] }],
  onChange: function (labels, text, nodes) { console.log(nodes.map(function (n) { return n.value; })); }
});
</script>
```

## 样式类

`zh-sel`、`zh-cas-panel`、`zh-cas-col`、`zh-cas-item`

## 属性

| 属性 | 用在 | 取值 | 说明 |
|---|---|---|---|
| `data-zh-cascader` | 容器 <div> | 无 | 声明这是一个级联选择；容器里的第一个 <ul> 是数据，读取后会被替换成控件 |
| `data-name` | 容器 | 字段名 | 生成一个隐藏 <input name=…> 参与表单提交，值是用 / 连接的路径（如 浙江/杭州/西湖区）；如果路径上每个节点都写了 data-value，则用 data-value 连接 |
| `data-value` | 容器 | 用 / 连接的标签路径，如 浙江/杭州/西湖区 | 初始选中的路径；标签里含有 / 时请改用命令式 |
| `data-placeholder` | 容器 | 字符串 | 占位文字，默认「请选择」 |
| `data-searchable` | 容器 | 布尔属性 | 可输入搜索，匹配完整路径文字，只搜到叶子节点 |
| `data-clearable` | 容器 | false 表示关闭 | 默认有清除按钮 |
| `data-expand-trigger` | 容器 | click（默认）\| hover | 悬停展开下一级；触屏和窄屏始终是点击 |
| `data-size` | 容器 | sm \| md（默认）\| lg | 尺寸 |
| `data-sep` | 容器 | 字符串，默认「 / 」 | 输入框里各级之间的分隔文字 |
| `data-labelledby` | 容器 | 标签元素的 id | 无障碍名称；也可以直接写 aria-label |
| `data-disabled / data-value` | <li> | 布尔属性 / 字符串 | 禁用该节点及其子树 / 该节点的值（用于表单） |
| `<label for="容器 id">` | 容器所在页面的 <label> | for 指向容器的 id | label 会成为输入框的无障碍名称，点击 label 会聚焦输入框；容器是空 <div>，label 的 for 指向它的 id 即可 |

## 方法

- `ZhaoUI.cascader(host, { data, value, searchable, clearable, expandTrigger, size, placeholder, sep, labelledby, onChange })`：命令式创建。data 是节点树 [{ label, children?, disabled?, …你的字段 }]；value 是标签数组；onChange(labels, text, nodes)
- `el.zhCascader.get() / .getNodes()`：读取当前路径的标签数组 / 节点对象数组
- `el.zhCascader.set(labels) / .setData(tree) / .open() / .close()`：设置路径（不触发 change，并同步隐藏字段）、更换数据、打开、关闭

## 事件

| 事件 | 触发于 | detail | 说明 |
|---|---|---|---|
| `zh:cascader-change` | 容器 | { path, text, nodes } | 选中或清除后触发（冒泡）。path 是标签数组，text 是拼好的文字，nodes 是节点对象数组（能读到你自己加的字段，如 value）；清除时都是空的 |

## 无障碍

- 输入框是 role="combobox"，各级是 role="listbox"，带 aria-selected 与 aria-activedescendant
- ↓ ↑ 在当前一级移动（跳过禁用项），→ 展开子级，← 回到上一级，Enter 选中叶子或展开分支，Esc / Tab 关闭
- 搜索时 ↓ ↑ 在结果里移动，Enter 选中；结果显示完整路径
- 窄屏（≤ 767px）改为逐级下钻，有「返回上一级」按钮，每一级都是整行的大点击区
- 禁用的节点标记 aria-disabled，不可展开也不可选

## 常见问题

### 级联选择是什么？

多级联动选择（省市区、分类树）。数据写成页面里的嵌套列表，或用 ZhaoUI.cascader 传一棵树。支持搜索、悬停展开，窄屏自动变成逐级下钻。

### 怎么使用级联选择？

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

### 级联选择有哪些事件？

zh:cascader-change（选中或清除后触发（冒泡）。path 是标签数组，text 是拼好的文字，nodes 是节点对象数组（能读到你自己加的字段，如 value）；清除时都是空的）。

### 级联选择有哪些方法？

ZhaoUI.cascader(host, { data, value, searchable, clearable, expandTrigger, size, placeholder, sep, labelledby, onChange })：命令式创建。data 是节点树 [{ label, children?, disabled?, …你的字段 }]；value 是标签数组；onChange(labels, text, nodes)；el.zhCascader.get() / .getNodes()：读取当前路径的标签数组 / 节点对象数组；el.zhCascader.set(labels) / .setData(tree) / .open() / .close()：设置路径（不触发 change，并同步隐藏字段）、更换数据、打开、关闭。

### 级联选择支持键盘和读屏吗？

输入框是 role="combobox"，各级是 role="listbox"，带 aria-selected 与 aria-activedescendant；↓ ↑ 在当前一级移动（跳过禁用项），→ 展开子级，← 回到上一级，Enter 选中叶子或展开分支，Esc / Tab 关闭；搜索时 ↓ ↑ 在结果里移动，Enter 选中；结果显示完整路径。

### 级联选择需要依赖框架吗？

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