输入类

级联选择

用于选择多级分类下的某个选项。

使用场景

与 TreeSelect 组件的区别:

  • TreeSelect: 核心价值在于目标节点,层级结构是为了方便用户快速筛选出目标选项,最终的节点才是用户想要的内容,常见于文件/文件夹选择、组织架构、权限分配等场景。
  • Cascader: 核心价值在于路径,用户选择的不是一个孤立的点,而是一条从根到叶的完整路径,常用于地理位置,商品分类等场景。

代码演示

如何引入

<script lang="ts">
    import { Cascader } from '@fibrae/semi-ui-svelte';
</script>

基本用法

最简单的用法,默认只可以选叶子节点。

代码
<script lang="ts">
    import { Cascader } from '@fibrae/semi-ui-svelte';

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<Cascader placeholder="请选择所在地区" style="width: 300px" {treeData} />

多选

设置 multiple,可以进行多选。

代码
<script lang="ts">
    import { Cascader, type CascaderValue } from '@fibrae/semi-ui-svelte';

    // Cascader has no defaultValue: bind a state seeded with the initial selection
    let value = $state<CascaderValue>(['zhejiang', 'ningbo', 'jiangbei']);

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<Cascader bind:value multiple placeholder="请选择所在地区" style="width: 300px" {treeData} />

可搜索的

通过设置 filterTreeNode 属性可支持搜索功能。

默认对 label 值进行搜索(使用字符串的 includes 方法进行匹配,不区分大小写),可通过 treeNodeFilterProp 指定其他属性值进行搜索。label 只接受文本,节点内容用 label snippet 定制时,搜索仍匹配 label 文本;需要匹配其他文本时,可在 treeData 中使用其他字段存储,并通过 treeNodeFilterProp 指定该字段进行搜索。

默认搜索结果只会展示叶子结点的路径,想要显示更多的结果,可以设置 filterLeafOnly 为 false。





filterLeafOnly=false:


Label 为自定义节点,指定其他属性进行搜索
代码
<script lang="ts">
    import { IconMapPinStroked } from '@fibrae/semi-icons-svelte';
    import { Cascader, TypographyTitle } from '@fibrae/semi-ui-svelte';

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];

    // The label snippet adds a decorative icon in the columns; labelText holds the text that search matches
    const labelNodeTreeData = [
        {
            label: '浙江省',
            labelText: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    labelText: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', labelText: '西湖区', value: 'xihu' },
                        { label: '萧山区', labelText: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', labelText: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    labelText: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', labelText: '海曙区', value: 'haishu' },
                        { label: '江北区', labelText: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<div>
    <Cascader filterTreeNode placeholder="默认对label值进行搜索" style="width: 300px" {treeData} />
    <br />
    <br />
    <Cascader filterTreeNode placeholder="对value值进行搜索" style="width: 300px" {treeData} treeNodeFilterProp="value" />
    <br />
    <br />
    <TypographyTitle heading={6}>filterLeafOnly=false:</TypographyTitle>
    <Cascader filterLeafOnly={false} filterTreeNode placeholder="filterLeafOnly=false" style="width: 300px" {treeData} />
    <br />
    <br />
    <TypographyTitle heading={6}>Label 为自定义节点,指定其他属性进行搜索</TypographyTitle>
    <Cascader displayProp="labelText" filterTreeNode placeholder="Search for labelText" style="width: 300px" treeData={labelNodeTreeData} treeNodeFilterProp="labelText">
        {#snippet label({ item })}
            <span style="display: inline-flex; align-items: center; column-gap: 4px">
                <IconMapPinStroked />
                {item.label}
            </span>
        {/snippet}
    </Cascader>
</div>

可搜索的多选

支持多选和搜索同时使用,在这种场景下,可以通过按下 BackSpace 键来删除对应的已选项目。

海曙区
代码
<script lang="ts">
    import { Cascader, type CascaderValue } from '@fibrae/semi-ui-svelte';

    let value = $state<CascaderValue>(['zhejiang', 'ningbo', 'haishu']);

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<Cascader bind:value filterTreeNode multiple placeholder="请选择所在地区" style="width: 300px" {treeData} />

可以使用 filterSorter 对筛选后的数据进行排序。

代码
<script lang="ts">
    import { Cascader, type CascaderProps } from '@fibrae/semi-ui-svelte';

    const treeData = [
        {
            label: 'Product',
            value: 'Product',
            children: [
                { label: 'Semi-Material', value: 'Semi-Material' },
                { label: 'Semi-DSM', value: 'Semi-DSM' },
                { label: 'Semi', value: 'Semi' },
                { label: 'Semi-C2D', value: 'Semi-C2D' },
                { label: 'Semi-D2C', value: 'Semi-D2C' },
            ],
        },
    ];

    // The sorter receives the two result paths, so compare their last nodes
    const filterSorter: CascaderProps['filterSorter'] = (first, second, inputValue) => {
        const firstData = first[first.length - 1];
        const lastData = second[second.length - 1];
        if (firstData.label === inputValue) {
            return -1;
        } else if (lastData.label === inputValue) {
            return 1;
        } else {
            return firstData.label < lastData.label ? -1 : 1;
        }
    };
</script>

<div>
    <Cascader {filterSorter} filterTreeNode placeholder="输入 s 查看排序效果" style="width: 300px" {treeData} />
</div>

如果想要自定义渲染搜索后的选项,可以使用 filterItem snippet 实现整行的自定义渲染,snippet 参数的类型为 CascaderFilterItemSnippetProps:

interface CascaderFilterItemSnippetProps {
    props: CascaderFilterItemDomProps; // 选项的 id、role、aria-selected、class 和点击处理,需要展开到 snippet 的根元素上
    inputValue: string; // 搜索栏搜索内容
    disabled: boolean; // 是否禁用
    data: CascaderData[]; // 搜索结果数据,即这条路径上的各级节点
    selected: boolean; // 单选时的选中状态
    checkStatus: {
        // 多选时的选中状态
        checked: boolean;
        halfChecked: boolean;
    };
    onCheck: (event: CheckboxEvent) => void; // 多选点击选中,绑定到 Checkbox 的 onChange
}

把 props 展开到根元素上(如 <li {...props}>),单选时点击这一行即选中;开启 virtualizeInSearch 时 props.style 是行的定位样式,自己的 style 要与它拼接,不能覆盖。

使用示例如下

鼠标 hover 到选项可查看被省略文本完整内容



代码
<script lang="ts">
    import { Cascader, Checkbox, TypographyText, type CascaderData, type TypographyEllipsis } from '@fibrae/semi-ui-svelte';

    const treeData = [
        {
            label: 'Semi',
            value: 'Semi',
            children: [
                {
                    label: 'Semi-Material Semi-Material Semi-Material Semi-Material',
                    value: 'Semi-Material',
                },
                {
                    label: 'Semi-DSM Semi-DSM Semi-DSM Semi-DSM',
                    value: 'Semi-DSM',
                },
                {
                    label: 'Semi Design Semi Design Semi Design Semi Design',
                    value: 'Semi',
                },
                {
                    label: 'Semi-C2D Semi-C2D Semi-C2D Semi-C2D Semi-C2D',
                    value: 'Semi-C2D',
                },
                {
                    label: 'Semi-D2C Semi-D2C Semi-D2C Semi-D2C Semi-D2C ',
                    value: 'Semi-D2C',
                },
            ],
        },
    ];
    const ellipsis: TypographyEllipsis = { showTooltip: { opts: { style: 'word-break: break-all' } } };
    const pathLabel = (data: CascaderData[]) => data.map((item) => item.label).join(' / ');
</script>

<div>
    <p>鼠标 hover 到选项可查看被省略文本完整内容</p>
    <br />
    <Cascader filterTreeNode placeholder="单选,输入 s 自定义搜索选项渲染结果" style="width: 320px" {treeData}>
        {#snippet filterItem({ props, data, selected })}
            <li {...props} style="justify-content: flex-start">
                <TypographyText {ellipsis} style={`width: 270px; ${selected ? 'color: var(--semi-color-primary)' : ''}`}>
                    {pathLabel(data)}
                </TypographyText>
            </li>
        {/snippet}
    </Cascader>
    <br />
    <Cascader filterTreeNode multiple placeholder="多选,输入 s 自定义搜索选项渲染结果" style="width: 320px; margin-top: 20px" {treeData}>
        {#snippet filterItem({ props, data, checkStatus, onCheck })}
            <li {...props} aria-label={pathLabel(data)} style="justify-content: flex-start">
                <Checkbox checked={checkStatus.checked} indeterminate={checkStatus.halfChecked} onChange={onCheck} tabindex={-1}>
                    <TypographyText {ellipsis} style="width: 250px">
                        {pathLabel(data)}
                    </TypographyText>
                </Checkbox>
            </li>
        {/snippet}
    </Cascader>
</div>

如果搜索结果中存在大量 Option,可以通过设置 virtualizeInSearch 开启搜索结果面板的虚拟化来优化性能,virtualizeInSearch 是一个包含下列值的对象:

  • height: Option 列表高度值(number,单位 px),缺省 180,与下拉框高度一致
  • width: Option 列表宽度值
  • itemSize: 每行 Option 的高度

限制标签展示数量

在多选的场景中,利用 maxTagCount 可以限制展示的标签数量,超出部分将以 +N 的方式展示。

使用 showRestTagsPopover 可以设置在超出 maxTagCount 后,hover +N 是否显示 Popover,默认为 false。并且,还可以在 restTagsPopoverProps 属性中配置 Popover。

代码
<script lang="ts">
    import { Cascader, type CascaderValue } from '@fibrae/semi-ui-svelte';

    let value = $state<CascaderValue>([
        ['zhejiang', 'ningbo', 'haishu'],
        ['zhejiang', 'hangzhou', 'xihu'],
    ]);

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
    const restTagsPopoverProps = { position: 'top' } as const;
</script>

<Cascader bind:value maxTagCount={1} multiple placeholder="请选择所在地区" {restTagsPopoverProps} showRestTagsPopover style="width: 300px" {treeData} />

限制选中数量

在多选的场景中,利用 max 可以限制多选选中的数量。超出 max 后将触发 onExceed 回调。

代码
<script lang="ts">
    import { Cascader, Toast, type CascaderEntity, type CascaderValue } from '@fibrae/semi-ui-svelte';

    let value = $state<CascaderValue>([['zhejiang', 'ningbo', 'haishu']]);

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];

    const onExceed = (checked: CascaderEntity[]) => {
        Toast.warning('exceed max');
        console.log(checked);
    };
</script>

<Cascader bind:value max={1} multiple {onExceed} placeholder="请选择所在地区" style="width: 300px" {treeData} />

选择即改变

在单选的情况下,还可以通过设置 changeOnSelect,允许选中父级选项。



代码
<script lang="ts">
    import { Cascader } from '@fibrae/semi-ui-svelte';

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<div>
    <Cascader changeOnSelect placeholder="选择即改变" style="width: 300px" {treeData} />
    <br />
    <br />
    <Cascader changeOnSelect filterTreeNode placeholder="可搜索的选择即改变" style="width: 300px" {treeData} />
</div>

自定义显示

可以通过 displayProp 设置回填选项显示的属性值,默认为 label。

单选


多选
代码
<script lang="ts">
    import { Cascader, TypographyTitle, type CascaderValue } from '@fibrae/semi-ui-svelte';

    let single = $state<CascaderValue>(['zhejiang', 'ningbo', 'jiangbei']);
    let multiple = $state<CascaderValue>(['zhejiang', 'ningbo', 'jiangbei']);

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<TypographyTitle heading={6}>单选</TypographyTitle>
<Cascader bind:value={single} displayProp="value" placeholder="回填时显示数据的value值" style="width: 300px" {treeData} />
<br />
<br />
<TypographyTitle heading={6}>多选</TypographyTitle>
<Cascader bind:value={multiple} displayProp="value" multiple placeholder="回填时显示数据的value值" style="width: 300px" {treeData} />

displayRender 拆成两个 snippet。

单选 (multiple 为 false) 时,用 display snippet 设定回填内容,参数为 { labels, path },其中 labels 是路径上各级节点的 displayProp 值,path 是路径上的节点数据(值不在 treeData 中时为原始值,与开启 onChangeWithObject 时 onChange 的参数相同);snippet 内容为空时显示 placeholder。

多选 (multiple 为 true) 时,用 tag snippet 整体替换每个标签,参数为 { entity, index, disabled, onClose },其中 entity 为节点的相关数据,onClose 取消该节点的选中,需要关闭按钮时把它绑定到自己的 Tag 上。可搜索的多选同样生效。entity 的类型为 CascaderEntity,如下:

interface Entity {
    children?: Entity[]; // 子节点列表
    data: CascaderData; // treedata
    ind: number; // index
    key: string; // key
    level: number; // 节点层级
    parent?: Entity; // 父节点数据
    parentKey?: string; // 父节点 key
    path: string[]; // key 路径
    pos: string; // 节点在 treeData 中的层级位置,如 '0-0-1'
    valuePath: string[]; // value 路径
}

数据项的 label 只接受文本。要在列视图的选项里渲染自定义内容,使用 label snippet,参数为 { item },item 为该节点的数据;搜索结果、回填和标签仍显示节点的文本。

单选


多选
代码
<script lang="ts">
    import { Cascader, Tag, TypographyTitle, type CascaderValue } from '@fibrae/semi-ui-svelte';

    let single = $state<CascaderValue>(['zhejiang', 'ningbo', 'jiangbei']);
    let multiple = $state<CascaderValue>(['zhejiang', 'ningbo', 'jiangbei']);

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<TypographyTitle heading={6}>单选</TypographyTitle>
<Cascader bind:value={single} placeholder="自定义回填时显示数据的格式" style="width: 300px" {treeData}>
    {#snippet display({ labels })}
        已选择:{labels.join(' -> ')}
    {/snippet}
</Cascader>
<br />
<br />
<TypographyTitle heading={6}>多选</TypographyTitle>
<Cascader bind:value={multiple} multiple placeholder="自定义回填时显示数据的格式" style="width: 300px" {treeData}>
    {#snippet tag({ entity })}
        <Tag color="white" style="margin-right: 4px">{entity.data.label}</Tag>
    {/snippet}
</Cascader>

自定义分隔符

可以使用 separator 设置分隔符, 包括:搜索时显示在下拉框的内容以及单选时回显到 Trigger 的内容的分隔符。

代码
<script lang="ts">
    import { Cascader, type CascaderValue } from '@fibrae/semi-ui-svelte';

    let value = $state<CascaderValue>(['zhejiang', 'ningbo', 'jiangbei']);

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<Cascader bind:value filterTreeNode separator=" > " style="width: 300px" {treeData} />

禁用



代码
<script lang="ts">
    import { Cascader, type CascaderValue } from '@fibrae/semi-ui-svelte';

    let value = $state<CascaderValue>(['zhejiang', 'hangzhou', 'xihu']);

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
            ],
        },
    ];
</script>

<div>
    <Cascader disabled placeholder="请选择所在地区" style="width: 300px" {treeData} />
    <br />
    <br />
    <Cascader bind:value disabled filterTreeNode placeholder="请选择所在地区" style="width: 300px" {treeData} />
</div>

严格禁用

可以使用 disableStrictly 来开启严格禁用。开启严格禁用后,当节点是 disabled 的时候,则不能通过子级或者父级的关系改变选中状态。

以下面的 demo 为例,节点"宁波"开启了严格禁用,因此,当我们改变其父节点"浙江省"的选中状态时,也不会影响到节点"宁波"的选中状态。

代码
<script lang="ts">
    import { Cascader } from '@fibrae/semi-ui-svelte';

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    disabled: true,
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<Cascader disableStrictly multiple placeholder="请选择所在地区" style="width: 300px" {treeData} />

展示子菜单的时机

可以使用 showNext 设置展开 Dropdown 子菜单的触发时机,可选: click(默认)、hover。

代码
<script lang="ts">
    import { Cascader } from '@fibrae/semi-ui-svelte';

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
            ],
        },
    ];
</script>

<Cascader placeholder="请选择所在地区" showNext="hover" style="width: 300px" {treeData} />

点击选中

在多选模式下,默认情况下点击非叶子节点不会触发选中。你可以通过 clickToSelect 开启点击任意节点即选中的功能。

这个 API 在配合 showNext="hover" 使用时特别有用:鼠标悬浮展开子菜单,点击则选中当前节点。

代码
<script lang="ts">
    import { Cascader } from '@fibrae/semi-ui-svelte';

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [{ label: '海曙区', value: 'haishu' }],
                },
            ],
        },
    ];
</script>

<Cascader clickToSelect multiple placeholder="请选择所在地区" showNext="hover" style="width: 300px" {treeData} />

在顶部/底部渲染附加项

我们在级联选择器的顶部、底部分别预留了插槽,你可以通过 topSlot 或 bottomSlot snippet 来设置。

代码
<script lang="ts">
    import { Cascader, TypographyText } from '@fibrae/semi-ui-svelte';

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<Cascader placeholder="请选择所在地区" style="width: 300px" {treeData}>
    {#snippet bottomSlot()}
        <div style="height: 36px; display: flex; padding: 0 32px; align-items: center; cursor: pointer; border-top: 1px solid var(--semi-color-border)">
            <TypographyText>找不到相关选项?</TypographyText>
            <TypographyText link>去新建</TypographyText>
        </div>
    {/snippet}
</Cascader>

受控

用 bind:value 绑定 value 即为受控组件,onChange 同样会触发。Svelte 版没有 defaultValue:需要初始选中项时,绑定一个带初始值的 $state,不绑定则为非受控;bind:value={get, set} 为完全受控。

代码
<script lang="ts">
    import { Cascader, type CascaderValue } from '@fibrae/semi-ui-svelte';

    let value = $state<CascaderValue>([]);

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<Cascader bind:value placeholder="请选择所在地区" style="width: 300px" {treeData} />

自动合并 value

在多选(multiple=true)场景中,当我们选中祖先节点时,如果希望 value 不包含它对应的子孙节点,则可以通过 autoMergeValue 来设置,默认为 true。当 autoMergeValue 和 leafOnly 同时开启时,后者优先级更高。

代码
<script lang="ts">
    import { Cascader, type CascaderValue } from '@fibrae/semi-ui-svelte';

    let value = $state<CascaderValue>([]);

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<Cascader autoMergeValue={false} bind:value multiple placeholder="autoMergeValue 为 false" style="width: 300px" {treeData} />

仅叶子节点

多选时,可以通过开启 leafOnly 来设置 value 只包含叶子节点,即显示的 Tag 和 bind:value、onChange 的值只包含叶子节点。

代码
<script lang="ts">
    import { Cascader, type CascaderChangeValue, type CascaderValue } from '@fibrae/semi-ui-svelte';

    let value = $state<CascaderValue>([]);

    const onChange = (v: CascaderChangeValue) => {
        console.log(v);
    };

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<Cascader bind:value leafOnly multiple {onChange} placeholder="开启 leafOnly" style="width: 300px" {treeData} />

节点选中关系

多选时,可以使用 checkRelation 来设置节点之间选中关系的类型,可选:'related'(默认)、'unRelated'。当选中关系为 'unRelated' 时,意味着节点之间的选中互不影响。

代码
<script lang="ts">
    import { Cascader, type CascaderValue } from '@fibrae/semi-ui-svelte';

    let value = $state<CascaderValue>([['Asia'], ['Asia', 'China', 'Beijing']]);

    const treeData = [
        {
            label: '亚洲',
            value: 'Asia',
            children: [
                {
                    label: '中国',
                    value: 'China',
                    children: [
                        { label: '北京', value: 'Beijing' },
                        { label: '上海', value: 'Shanghai' },
                    ],
                },
            ],
        },
        {
            label: '北美洲',
            value: 'North America',
        },
    ];
</script>

<Cascader bind:value checkRelation="unRelated" multiple style="width: 300px" {treeData} />

动态更新数据

treeData 可以整体替换,也可以就地修改 $state 数据(如 push、给 children 赋值),都会被识别并重新计算;内容相同的新数组不会重置展开和选中状态。



代码
<script lang="ts">
    import { Button, Cascader, type CascaderData } from '@fibrae/semi-ui-svelte';

    let treeData = $state<CascaderData[]>([]);

    const add = () => {
        const itemLength = Math.floor(Math.random() * 3) + 1;
        treeData = Array.from({ length: itemLength }, (_, i) => {
            const length = Math.floor(Math.random() * 3);
            const children = Array.from({ length }, (_, ci) => ({
                key: `${i}-${ci}`,
                label: `Item-${i}-${ci}`,
                value: `${i}-${ci}`,
            }));
            return {
                key: `${i}`,
                label: `Item-${i}`,
                value: `${i}`,
                children,
            };
        });
    };
</script>

<Cascader placeholder="请选择" style="width: 300px" {treeData} />
<br />
<br />
<Button onclick={add}>动态改变数据</Button>

异步加载数据

可以使用 loadData 实现异步加载数据

不能与搜索同时使用

代码
<script lang="ts">
    import { Cascader, type CascaderData } from '@fibrae/semi-ui-svelte';

    let treeData = $state<CascaderData[]>([
        { label: 'Node1', value: '0-0' },
        { label: 'Node2', value: '0-1' },
        { label: 'Node3', value: '0-2', isLeaf: true },
    ]);

    const updateTreeData = (list: CascaderData[], value: CascaderData['value'], children: CascaderData[]): CascaderData[] =>
        list.map((node) => {
            if (node.value === value) {
                return { ...node, children };
            }
            if (node.children) {
                return { ...node, children: updateTreeData(node.children, value, children) };
            }
            return node;
        });

    const loadData = (selectedOpt: CascaderData[]) => {
        const targetOpt = selectedOpt[selectedOpt.length - 1];
        const { label, value } = targetOpt;
        return new Promise<void>((resolve) => {
            if (targetOpt.children) {
                resolve();
                return;
            }

            setTimeout(() => {
                treeData = updateTreeData(treeData, value, [
                    {
                        label: `${label} - 1`,
                        value: `${label}-1`,
                        isLeaf: selectedOpt.length > 1,
                    },
                    {
                        label: `${label} - 2`,
                        value: `${label}-2`,
                        isLeaf: selectedOpt.length > 1,
                    },
                ]);
                resolve();
            }, 1000);
        });
    };
</script>

<Cascader {loadData} placeholder="Please select" style="width: 300px" {treeData} />

远程搜索

设置 remote 后,搜索输入不再走本地匹配,而是仅触发 onSearch 回调,由你根据输入异步拉取 treeData。这与 Select 的 remote 行为一致。

超长列表

当你的数据结构层级特别深时,Cascader下拉菜单可能会超出屏幕,此时我们建议为下拉菜单设置 overflow-x: auto 以及一个合适的 width 宽度( 建议以N+0.5列的宽度为准,最右侧显示半列,以给用户一种右侧尚有待展开项,可以水平方向滚动的视觉暗示)

代码
<script lang="ts">
    import { Cascader } from '@fibrae/semi-ui-svelte';

    const treeData = [
        {
            label: 'A',
            value: 'A',
            children: [
                {
                    label: 'B',
                    value: 'B',
                    children: [
                        {
                            label: 'C',
                            value: 'C',
                            children: [
                                {
                                    label: 'D',
                                    value: 'D',
                                    children: [
                                        {
                                            label: 'E',
                                            value: 'E',
                                            children: [{ label: 'F', value: 'F' }],
                                        },
                                    ],
                                },
                            ],
                        },
                    ],
                },
            ],
        },
    ];
</script>

<Cascader dropdownClassName="components-cascader-demo" placeholder="请选择所在地区" style="width: 300px" {treeData} />
.components-cascader-demo {
    .semi-cascader-option-lists {
        max-width: 510px;
        overflow-x: auto;
    }
}

自定义 Trigger

如果默认的触发器样式满足不了你的需求,可以用 trigger snippet 自定义选择框的展示。

trigger 的 snippet 参数类型为 CascaderTriggerSnippetProps,如下:

interface CascaderTriggerSnippetProps {
    /**
     * 已选中的 node 在 treeData 中的层级位置,如下例子,
     * 当选中浙江省-杭州市-萧山区时,此处 value 为 '0-0-1';多选时为 Set<string>
     */
    value: string | Set<string> | undefined;
    /* 展开时为搜索词;可搜索的单选收起时为已选路径的展示文本 */
    inputValue: string;
    /* Placeholder */
    placeholder?: string;
    /* 是否禁用 Cascader */
    disabled: boolean;
    /**
     * 用于更新 input 框值的函数,当你在 trigger 自定义的
     * Input 组件值更新时,你应该调用该函数,用于向 Cascader 内部
     * 同步状态, 使用时需要设置 filterTreeNode 参数非 false
     */
    onSearch: (inputValue: string) => void;
    /* 用于清空值的函数;绑定在触发器内的控件上时传入点击事件,避免这次点击同时打开下拉 */
    onClear: (event?: MouseEvent) => void;
    /* 用于删除单个 item,入参为该节点在 treeData 中的层级位置(与 value 的格式相同) */
    onRemove: (pos: string) => void;
}

代码
<script lang="ts">
    import { IconChevronDown, IconClose } from '@fibrae/semi-icons-svelte';
    import { Button, ButtonGroup, Cascader, Tag, TagInput, type CascaderData } from '@fibrae/semi-ui-svelte';

    const treeData: CascaderData[] = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];

    const getLabelFromValue = (value: string) => {
        const [first, ...rest] = value.split('-').map((item) => Number(item));
        let node: CascaderData | undefined = treeData[first];
        rest.forEach((item) => {
            node = node?.children?.[item];
        });
        return node?.label;
    };
</script>

{#snippet chevron()}
    <IconChevronDown />
{/snippet}

{#snippet close()}
    <IconClose />
{/snippet}

<Cascader placeholder="Custom Trigger" {treeData}>
    {#snippet trigger({ value, placeholder, onClear })}
        <!-- The clear button is a sibling of the trigger button: buttons cannot nest -->
        <ButtonGroup theme="light">
            <Button icon={chevron} iconPosition="right">
                {value ? getLabelFromValue(value as string) : placeholder}
            </Button>
            {#if value}
                <Button
                    aria-label="清除"
                    icon={close}
                    onclick={(event) => {
                        event.stopPropagation();
                        onClear(event);
                    }}
                />
            {/if}
        </ButtonGroup>
    {/snippet}
</Cascader>
<br />
<Cascader filterTreeNode multiple placeholder="Custom Trigger" style="width: 300px" {treeData}>
    {#snippet trigger({ value, onSearch, onRemove })}
        <TagInput onInputChange={onSearch} value={Array.from(value as Set<string>)}>
            {#snippet tag({ value: pos })}
                <Tag
                    closable
                    onClose={(event) => {
                        event.preventDefault();
                        onRemove(pos);
                    }}
                    style="margin-left: 2px"
                    tagKey={pos}
                >
                    {getLabelFromValue(pos)}
                </Tag>
            {/snippet}
        </TagInput>
    {/snippet}
</Cascader>

API 参考

Cascader

Props

属性说明类型默认值
arrowIcon自定义右侧下拉箭头;传 null 隐藏箭头。有清除按钮可见时,悬停优先显示清除按钮。Snippet | null-
autoAdjustOverflow下拉被边缘遮挡时是否自动调整展开方向。boolean-
autoMergeValue多选时,某个父节点被选中后,值不再包含它的子孙节点。挂载后修改不会生效;与 leafOnly 同时开启时,后者优先。booleantrue
borderless是否使用无边框模式。booleanfalse
bottomSlot下拉底部的附加内容。Snippet-
changeOnSelect单选时是否允许选择非叶子节点。booleanfalse
checkRelation多选时节点之间选中状态的关系:related 为父子联动,unRelated 为互不影响。'related' | 'unRelated'"related"
clearIcon自定义清除按钮的图标,showClear 为 true 时有效;内容为空时使用默认图标。Snippet-
clickToSelect多选时,点击任意节点即触发选中,常与 showNext 为 hover 配合:悬停展开子菜单,点击选中当前节点。booleanfalse
defaultOpen是否在挂载时展开下拉。booleanfalse
disabled是否禁用。禁用后不可 Tab 到达。boolean-
disableStrictly是否严格禁用。开启后,禁用的节点不会因父级或子级的选中状态变化而改变。booleanfalse
display单选时已选路径的回显,参数为 { labels, path }:labels 是各级节点的 displayProp,path 是节点数据,值不在 treeData 中时为原始值;内容为空时显示 placeholder。TypeSnippet<[CascaderDisplaySnippetProps]>-
displayProp回填选项和标签显示的节点属性,默认 label。string"label"
dropdownClassName下拉的 class。string-
dropdownStyle下拉的样式,为 CSS 字符串。string-
emptyContent没有可选数据或搜索无结果时展示的内容;未提供时显示语言包中的默认文案,传 null 隐藏空状态。Snippet | null-
enableLeafClick多选时,点击叶子节点所在的选项是否触发勾选。booleanfalse
expandIcon自定义展开图标;内容为空时使用默认图标。Snippet-
filterItem替换搜索结果中的一行,参数为 { props, inputValue, disabled, data, checkStatus, selected, onCheck };props 要展开到行的根元素上。TypeSnippet<[CascaderFilterItemSnippetProps]>-
filterLeafOnly搜索结果是否只展示叶子节点的路径。booleantrue
filterSorter对筛选后的结果排序,参数为两条结果路径(从根节点到匹配节点的节点数组)与输入内容。Type(first: CascaderData[], second: CascaderData[], inputValue: string) => number-
filterTreeNode是否开启搜索:true 时按 treeNodeFilterProp 对应的属性值匹配输入,不区分大小写;也可以传入自定义的匹配函数。Typeboolean | ((inputValue: string, treeNodeString: string, data?: CascaderData) => boolean)false
insetLabel嵌在选择框内的标签;同时提供 prefix 时不渲染。Snippet-
insetLabelIdinsetLabel 内容的 id,用于关联 combobox 的名称;不传时在使用 insetLabel snippet 时由组件生成。string-
keyMaps自定义节点中 value、label、children、disabled、isLeaf 的字段名。CascaderKeyMapProps-
label列视图中选项的内容,参数为 { item };搜索结果、回显和标签仍显示节点的文本。TypeSnippet<[CascaderLabelSnippetProps]>-
leafOnly多选时,值与展示的标签只包含叶子节点。挂载后修改不会生效。booleanfalse
loadData异步加载数据,参数为从根到当前节点的路径,需要返回 Promise;reject 后节点不再显示加载态,可以再次展开重试。不能与搜索同时使用。Type(selectOptions: CascaderData[]) => Promise<void>-
max多选时选中数量的上限,超出时触发 onExceed。number-
maxTagCount多选时标签的最大展示数量,超出的部分以 +N 展示。number-
motion是否开启下拉的弹出动画。booleantrue
multiple是否多选。它没有全局默认值。booleanfalse
onBlur关闭下拉时的回调:点击外部时参数为 MouseEvent,Esc 为 KeyboardEvent,通过方法 close 关闭时为 undefined。不是 DOM 失焦事件。Type(event?: MouseEvent | KeyboardEvent | FocusEvent) => void-
onChange选中值变化时的回调。单选为所选路径,多选为各选中路径的数组;开启 onChangeWithObject 时为节点数据。Type(value: CascaderChangeValue) => void-
onChangeWithObject是否让 onChange 携带每个节点的数据而不是它的 value。开启后绑定的 value 也要传节点数据,并含有 value。这是布尔属性,不是回调。booleanfalse
onClear点击清除按钮清空选中值时的回调。Type() => void-
onDropdownVisibleChange下拉展开或收起时的回调,参数是是否展开。Type(visible: boolean) => void-
onExceed多选时选中数量超出 max 时的回调,参数是已选节点的 Entity 数组。Type(checkedItem: CascaderEntity[]) => void-
onFocus点击或按键打开下拉时的回调,参数是那次 MouseEvent 或 KeyboardEvent。不是 DOM 焦点事件,Tab 进出不会触发。Type(event: MouseEvent | KeyboardEvent | FocusEvent) => void-
onKeyDown在 Cascader 处理按键之前的回调,每次按键只调用一次;在其中调用 preventDefault 不会阻止 Cascader 的处理。除清除按钮与自定义触发器内控件上的按键外,事件在触发器的捕获阶段发出,此时调用 stopPropagation 会让搜索框收不到该键,并跳过触发器对它的处理(打开下拉、Esc 关闭),选项仍会处理该键。Type(event: KeyboardEvent) => void-
onListScroll滚动列视图中的某一列时的回调,参数是滚动事件与该列的信息。搜索结果的滚动不触发。Type(event: Event, panel: CascaderScrollPanelProps) => void-
onLoad节点加载完毕时的回调,参数是已加载的 key 集合与该节点的数据。Type(newLoadedKeys: Set<string>, data: CascaderData) => void-
onSearch搜索输入变化时的回调,参数是输入的文本。Type(value: string) => void-
onSelect选中某个节点时的回调,参数是该节点的 value;多选时只勾选了一个节点时是它的 value,勾选多个时是 value 数组。Type(value: string | number | Array<string | number>) => void-
placeholder选择框的占位文字。string-
position下拉的展开方向,默认 bottomLeft,RTL 下为 bottomRight。Position-
prefix前缀内容。Snippet-
preventScroll组件内的 focus 是否阻止浏览器滚动文档以显示获得焦点的元素。boolean-
remote是否开启远程搜索。开启后输入不再做本地过滤,只触发 onSearch,由使用方异步更新 treeData。booleanfalse
restTagsPopoverProps剩余标签 Popover 的配置,可设置 position、trigger 等,参见 Popover。TypeOmit<PopoverProps, 'children' | 'child' | 'content'>-
searchPlaceholder搜索框的占位文字,不传时使用 placeholder。string-
searchPosition搜索框的位置:trigger 在触发器内,custom 由使用方自定义。'trigger' | 'custom'"trigger"
separator分隔符,用于搜索结果和单选回填到触发器的路径。string" / "
showClear是否展示清除按钮。booleanfalse
showNext展开子菜单的时机:click 为点击,hover 为悬停。'click' | 'hover'"click"
showRestTagsPopover超出 maxTagCount 后,悬停 +N 时是否用 Popover 展示剩余标签。booleanfalse
size选择框的尺寸。CascaderSize-
stopPropagation是否阻止下拉上的点击事件冒泡。boolean | stringtrue
suffix后缀内容。Snippet-
tag多选时整体替换每个标签,参数为 { entity, index, disabled, onClose },onClose 取消该节点的选中。TypeSnippet<[CascaderTagSnippetProps]>-
topSlot下拉顶部的附加内容。Snippet-
treeData节点数据,label 只接受文本,节点内容用 label snippet。原地修改 $state 数据也会被识别,内容相同的新数组不会重置展开和选中状态。CascaderData[][]
treeNodeFilterProp搜索时用于匹配输入的节点属性,默认 label。string"label"
trigger替换整个触发器内容,参数为 { value, inputValue, placeholder, disabled, onSearch, onClear, onRemove }。TypeSnippet<[CascaderTriggerSnippetProps]>-
validateStatus触发器的校验状态,只影响样式。ValidateStatus-
value当前选中的条目,用 bind:value 绑定。不传时为非受控,需要初始选中项时绑定一个带初始值的 $state;bind:value={get, set} 为完全受控。单选时是节点或节点路径,多选时可以是路径数组。CascaderValue-
virtualizeInSearch搜索结果的虚拟列表配置,由 height、width、itemSize 组成,用于结果很多的场景;height 缺省为 180,与下拉框高度一致。CascaderVirtualize-

Methods

方法说明类型
blur触发器或搜索框失焦。Type() => void
close关闭下拉。Type() => void
focus聚焦触发器或搜索框。Type() => void
open打开下拉。Type() => void
search按输入了该关键词来搜索,需同时设置 filterTreeNode 开启搜索。Type(keyword: string) => void

Cascader 把 class、style 与未声明的原生属性落在根元素(触发器)上,aria-label、aria-labelledby、aria-describedby、aria-errormessage、aria-invalid、aria-required 落在当前的 combobox 上(见 ARIA)。onChange、onSelect、onFocus、onBlur、onKeyDown 是组件的回调,会遮住根元素上同名的原生事件;onFocus、onBlur 在打开、关闭下拉时触发,不是 DOM 焦点事件。style 和 dropdownStyle 是 CSS 字符串。

CascaderData

属性说明类型默认值
children子节点CascaderData[]-
disabled不可选状态boolean-
isLeaf叶子节点boolean-
label展示的文本(必填),自定义内容使用 label snippetstring|number-
loading正在加载boolean-
value属性值(必填)string|number-

Methods

Cascader 通过 export function 暴露 open()、close()、focus()、blur() 和 search(value),用 bind:this 取得实例后调用。search 按输入了该值来搜索,需同时设置 filterTreeNode 开启搜索,searchPosition 为 custom 时自定义展示搜索框。

<script lang="ts">
    import { Cascader } from '@fibrae/semi-ui-svelte';

    let cascader = $state<Cascader>();
</script>

<Cascader bind:this={cascader} {treeData} />
<button onclick={() => cascader?.open()}>打开</button>

Accessibility

ARIA

  • Cascader 支持传入 aria-label、aria-describedby、aria-errormessage、aria-invalid、aria-labelledby、aria-required 来表示该 Cascader 的相关信息;
  • 这些 aria-* 属性落在当前的 combobox 上:不可搜索时是根元素(触发器),可搜索时是搜索框,多选且搜索框在触发器内时是标签输入框;
  • combobox 的名称按优先级取:aria-labelledby、aria-label、实际渲染出来的 insetLabel(combobox 的 aria-labelledby 指向它,未传 insetLabelId 时组件生成 id;有 prefix 时 insetLabel 不渲染,也不作名称),都没有时为语言包中的「级联选择」。下拉中的 tree 或搜索结果 listbox 使用同一个名称来源;
  • 按 ARIA 1.2 的 combobox 写法,任一时刻只有一个元素是 combobox,aria-controls 指向下拉中的列表,下拉与 combobox 同名。不可搜索时触发器是 select-only combobox,已选路径是它的值;可搜索时搜索框本身是 combobox,带 aria-autocomplete="list",触发器不再有角色和 Tab 停靠点。单选的搜索框只在搜索时出现,未搜索时触发器仍是 combobox;搜索框在输入前为空,已选路径以 aria-describedby 关联为它的描述;搜索中选中结果后焦点回到触发器;
  • 各列合起来是一棵 role="tree":选项为 treeitem,带 aria-level(所在列)、aria-setsize、aria-posinset、aria-disabled,可展开的选项带 aria-expanded;单选时用 aria-selected,多选时用 aria-checked(半选为 mixed);
  • 搜索结果是 role="listbox",选项为 option,用 aria-selected 表示选中,多选时表示勾选。filterItem 的 props 带有选项的 id、role="option" 和 aria-selected,需要展开到 snippet 的根元素上;
  • 选项前的勾选图标和多选复选框只是状态的图示,对读屏软件隐藏,也不接收焦点。

键盘交互

Cascader 按 WAI-ARIA Combobox 处理键盘:焦点始终留在触发器或搜索框上,aria-activedescendant 指向当前选项,当前选项显示焦点环。下拉刚打开时没有当前选项。

  • 下拉关闭时:ArrowDown、ArrowUp、Enter 打开下拉;不可搜索时,Space 也打开下拉,Home、End 打开下拉并移到第一列的第一个、最后一个选项。可搜索时,Space、Home、End 用于编辑文字
  • Enter:有当前选项时等同点击它,多选时勾选或取消勾选;禁用选项不响应
  • ArrowDown、ArrowUp:在当前选项所在的列(或搜索结果)中移到下一个、上一个选项,第一次按 ArrowDown 移到第一列的第一个选项;Home、End:移到这一列的第一个、最后一个选项
  • ArrowRight:展开当前选项,显示它的下一列;已展开时移到下一列的第一个选项(RTL 下与 ArrowLeft 互换)
  • ArrowLeft:收起展开的当前选项;否则移到上一列的父选项
  • Space:有当前选项时与 Enter 相同(焦点在搜索框里时用于输入)
  • 输入字符:下拉打开时,从当前选项往后,移到已显示的各列中第一个以输入内容开头的选项,各列保持不变;快速连续输入的字符合成一个搜索词。焦点在搜索框里时字符用于输入;下拉关闭时不响应
  • Esc:关闭下拉

焦点在搜索框里且已输入文字时,只有 ArrowDown、ArrowUp、Enter 用于移动和选择,其余按键用于编辑文字。方向键只移动当前选项,不像悬停那样自动展开下一列,也不触发 loadData;ArrowRight 展开时才加载。

showClear 开启、有已选值或搜索文本且未禁用时,触发器内渲染清除按钮。它是原生 button,名称为语言包中的「清空已选值」,在 hover、下拉打开或焦点位于 Cascader 内时显示。从触发器或搜索框按 Tab 移到清除按钮,Enter 或 Space 清空,效果与鼠标点击相同,清空后焦点回到触发器或搜索框。多选时也可在搜索框中用 Backspace 删除标签。

文案规范

  • 选择器选项
    • 如果没有默认选项,就使用 "Select" 做占位文案
    • 选项要按首字母顺序或者其他有逻辑的排列顺序,使用户更好地找到选项
    • 使用语句书写规范(首字母大写,其余小写),避免在句尾使用逗号和分号
    • 清晰表达出选项所表示的选择目的

设计变量

下表是组件内部的 Sass 变量,不能覆写。定制样式请覆写 --semi-* CSS 变量(见 Design Token 页),或写不分层的 CSS 规则。
Sass 变量值用法
$transition_duration-cascader_option-bgvar(--semi-transition_duration-none)
$transition_function-cascader_option-bgvar(--semi-transition_function-easeIn)
$transition_delay-cascader_option-bgvar(--semi-transition_delay-none)
$radius-cascadervar(--semi-border-radius-small)级联选择触发器圆角
$radius-cascader_option_emptyvar(--semi-border-radius-medium)级联选择菜单空状态圆角
$color-cascader_default-border-defaulttransparent级联选择触发器描边颜色 - 默认
$color-cascader_default-border-hovertransparent级联选择触发器描边颜色 - 悬浮
$color-cascader_default-border-focusvar(--semi-color-focus-border)级联选择触发器描边颜色 - 选中
$color-cascader_default-bg-defaultvar(--semi-color-fill-0)级联选择触发器背景颜色 - 默认
$color-cascader_default-bg-hovervar(--semi-color-fill-1)级联选择触发器背景颜色 - 悬浮
$color-cascader_default-bg-activevar(--semi-color-fill-2)级联选择触发器背景颜色 - 按下
$color-cascader_option_list-border-defaultvar(--semi-color-fill-0)级联选择各级菜单分割线颜色
$color-cascader_option_main-text-defaultvar(--semi-color-text-0)级联选择菜单项文字颜色
$color-cascader_option-bg-hovervar(--semi-color-fill-0)级联选择菜单项背景颜色 - 悬浮
$color-cascader_option-bg-activevar(--semi-color-fill-1)级联选择菜单项背景颜色 - 按下
$color-cascader_option-bg-selectedvar(--semi-color-primary-light-default)级联选择菜单项背景颜色 - 命中
$color-cascader_option_disabled-text-defaultvar(--semi-color-disabled-text)级联选择菜单项文字颜色 - 禁用
$color-cascader_option_disabled-bg-hovertransparent级联选择菜禁用单项背景颜色 - 悬浮
$color-cascader_option_disabled-bg-activetransparent级联选择菜禁用单项背景颜色 - 按下
$color-cascader_tag_disabled-bg-defaulttransparent级联选择菜禁用Tag背景颜色
$color-cascader_warning-bg-defaultvar(--semi-color-warning-light-default)级联选择触发器警告背景颜色
$color-cascader_warning-border-defaultvar(--semi-color-warning-light-default)级联选择触发器警告描边颜色
$color-cascader_warning-bg-hovervar(--semi-color-warning-light-hover)级联选择触发器警告背景颜色 - 悬浮
$color-cascader_warning-border-hovervar(--semi-color-warning-light-hover)级联选择触发器警告描边颜色 - 悬浮
$color-cascader_warning-bg-focusvar(--semi-color-warning-light-default)级联选择触发器警告背景颜色 - 选中
$color-cascader_warning-border-focusvar(--semi-color-warning)级联选择触发器警告描边颜色 - 选中
$color-cascader_warning-bg-activevar(--semi-color-warning-light-active)级联选择触发器警告背景颜色 - 按下
$color-cascader_warning-border-activevar(--semi-color-warning-light-active)级联选择触发器警告描边颜色 - 按下
$color-cascader_danger-bg-defaultvar(--semi-color-danger-light-default)级联选择触发器危险背景颜色
$color-cascader_danger-border-defaultvar(--semi-color-danger-light-default)级联选择触发器危险描边颜色
$color-cascader_danger-bg-hovervar(--semi-color-danger-light-hover)级联选择触发器危险背景颜色 - 悬浮
$color-cascader_danger-border-hovervar(--semi-color-danger-light-hover)级联选择触发器危险描边颜色 - 悬浮
$color-cascader_danger-bg-focusvar(--semi-color-danger-light-default)级联选择触发器危险背景颜色 - 选中
$color-cascader_danger-border-focusvar(--semi-color-danger)级联选择触发器危险描边颜色 - 选中
$color-cascader_danger-bg-activevar(--semi-color-danger-light-active)级联选择触发器危险背景颜色 - 按下
$color-cascader_danger-border-activevar(--semi-color-danger-light-active)级联选择触发器危险描边颜色 - 按下
$color-cascader_prefix_suffix_text-defaultvar(--semi-color-text-2)级联选择前后缀文字颜色
$spacing-cascader_selection-paddingLeft12px级联选择触发器左侧内边距 - 默认
$spacing-cascader_selection-paddingRight12px级联选择触发器右侧内边距 - 默认
$spacing-cascader_small_selection-paddingLeft12px级联选择触发器左侧内边距 - 小尺寸
$spacing-cascader_small_selection-paddingRight12px级联选择触发器右侧内边距 - 小尺寸
$spacing-cascader_large_selection-paddingLeft12px级联选择触发器左侧内边距 - 大尺寸
$spacing-cascader_large_selection-paddingRight12px级联选择触发器右侧内边距 - 大尺寸
$spacing-cascader_selection_multiple-paddingLeft$spacing-extra-tight级联选择触发器多选时左侧内边距 - 默认
$spacing-cascader_selection_multiple-paddingRight$spacing-extra-tight级联选择触发器多选时右侧内边距 - 默认
$spacing-cascader_small_selection_multiple-paddingLeft$spacing-extra-tight级联选择触发器多选时左侧内边距 - 小尺寸
$spacing-cascader_small_selection_multiple-paddingRight$spacing-extra-tight级联选择触发器多选时左侧内边距 - 小尺寸
$spacing-cascader_large_selection_multiple-paddingLeft$spacing-extra-tight级联选择触发器多选时左侧内边距 - 大尺寸
$spacing-cascader_large_selection_multiple-paddingRight$spacing-extra-tight级联选择触发器多选时左侧内边距 - 大尺寸
$spacing-cascader_selection_tag-marginLeft$spacing-none级联选择触发器多选时标签的水平左外边距
$spacing-cascader_selection_tag-marginRight$spacing-super-tight级联选择触发器多选时标签的水平右外边距
$spacing-cascader_selection_tag-marginY1px级联选择触发器多选时标签的垂直外边距
$spacing-cascader_selection_tagInput-marginLeft-$spacing-extra-tight级联选择触发器多选搜索时 TagInput 的左外边距
$spacing-cascader_selection_input-marginLeft$spacing-extra-tight级联选择触发器多选搜索时输入框的左外边距
$spacing-cascader_selection_n-marginRight$spacing-extra-tight超出 maxTagCount 后,+n 的右外边距
$spacing-cascader_option-icon-marginLeft8px级联选择菜单项图标左侧外边距
$color-cascader_selection_n-text-defaultvar(--semi-color-text-0)超出 maxTagCount 后,+n 的文字默认颜色
$color-cascader_selection_n-text-disabledvar(--semi-color-disabled-text)超出 maxTagCount 后,+n 的文字disabled颜色
$color-cascader_selection_text_inactivevar(--semi-color-text-2)级联选择单选inpu输入框和text并存时,text颜色
$color-cascader_selection-text-defaultvar(--semi-color-text-0)级联选择选中项文字颜色
$color-cascader_placeholder-text-defaultvar(--semi-color-text-2)级联选择未选中项文字颜色
$color-cascader-icon-defaultvar(--semi-color-text-2)级联选择触发器图标颜色 - 默认
$color-cascader-icon-hovervar(--semi-color-primary-hover)级联选择触发器图标颜色 - 悬浮
$color-cascader-icon-activevar(--semi-color-primary-active)级联选择触发器图标颜色 - 按下
$color-cascader_option-icon-default$color-cascader-icon-default级联选择菜单项图标颜色
$color-cascader_input_disabled-bg-defaultvar(--semi-color-disabled-fill)级联选择禁用态背景颜色
$color-cascader_input_disabled-bg-hovervar(--semi-color-disabled-fill)级联选择禁用态背景颜色 - 悬浮
$color-cascader_input_disabled-text-defaultvar(--semi-color-disabled-text)级联选择禁用态文字颜色
$color-cascader_input_disabled-icon-defaultvar(--semi-color-disabled-text)级联选择禁用态图标颜色
$color-cascader_search-border-defaultvar(--semi-color-fill-0)级联选择搜索描边颜色
$color-cascader_option_empty-text-defaultvar(--semi-color-disabled-text)级联选择空状态文字颜色
$color-cascader_input-bg-defaulttransparent级联选择输入框背景颜色
$color-cascader_input-border-defaultnone级联选择输入框描边颜色
$color-cascader_select_highlightvar(--semi-color-primary)级联选择器搜索命中后文字高亮的颜色
$font-cascader_select-fontWeight$font-weight-bold级联选择菜单项字重 - 选中
$font-cascader-fontWeight$font-weight-regular级联选择菜单项字重 - 未选中
$font-cascader_selection_n-fontSize$font-size-small超出 maxTagCount 后,+n 的文字尺寸
$font-cascader_prefix_suffix_fontWeight$font-weight-bold级联选择 prefix/suffix 文字字重
$width-cascader-border1px级联选择触发器描边宽度
$width-cascader_option_list-border1px级联选择各级菜单分割线宽度
$width-cascader80px级联选择触发器最小宽度
$height-cascader_default$height-control-default级联选择触发器高度 - 默认
$height-cascader_small$height-control-small级联选择触发器高度 - 小尺寸
$height-cascader_large$height-control-large级联选择触发器高度 - 大尺寸
$width-cascader_hover-border$width-cascader-border级联选择触发器描边宽度 - 悬浮
$width-cascader_focus-border$border-thickness-control-focus级联选择触发器描边宽度 - 选中态
$width-cascader_search-border1px级联选择触搜索描边宽度
$width-cascader-icon32px级联选择触发器图标尺寸
$width-cascader-option-icon16px级联选择菜单项图标尺寸
$width-cascader_option150px级联选择各级菜单宽度
$height-cascader_option_list180px级联选择菜单高度
$height-cascader_selection_tagInput_wrapper_small22px级联选择多选搜索时搜索框最小高度 - 小尺寸
$height-cascader_selection_tagInput_wrapper_default30px级联选择多选搜索时搜索框最小高度 - 默认尺寸
$height-cascader_selection_tagInput_wrapper_large38px级联选择多选搜索时搜索框最小高度 - 大尺寸
$height-cascader_selection_wrapper_small22px级联选择单选搜索时搜索框高度 - 小尺寸
$height-cascader_selection_wrapper30px级联选择单选搜索时搜索框高度 - 默认尺寸
$height-cascader_selection_wrapper_large38px级联选择单选搜索时搜索框高度 - 大尺寸
$spacing-cascader_text-marginX$spacing-base-tight级联选择 prefix/suffix 文字水平内间距
$spacing-cascader_icon-marginX$spacing-tight级联选择 prefix/suffix 图标水平内间距
$spacing-cascader_label_checkbox-marginRight$spacing-tight级联选择 checkbox 的右间距
$spacing-cascader_selection_n-paddingX$spacing-tight级联选择 +N 的水平间距
$spacing-cascader_flatten_list-paddingRight64px级联选择搜索结果菜单右侧内边距
$spacing-cascader_empty_icon-marginRight8px级联选择搜索结果菜单
$spacing-cacader_option_list-paddingY4px级联选择下拉菜单列表垂直内边距
$spacing-cacader_option_list-paddingX0px级联选择下拉菜单列表水平内边距
$spacing-cascader_search-paddingX12px级联选择搜索水平内边距
$spacing-cascader_search-paddingY8px级联选择搜索垂直内边距
$spacing-cascader_option_empty-paddingY8px级联选择空结果垂直内边距
$spacing-cascader_option_empty-paddingX12px级联选择空结果水平内边距
$spacing-cascader_option-paddingTop8px级联选择菜单项顶部内边距
$spacing-cascader_option-paddingBottom8px级联选择菜单项底部内边距
$spacing-cascader_option-paddingLeft12px级联选择菜单项左侧内边距
$spacing-cascader_option-paddingRight16px级联选择菜单项右侧内边距