Gradio 下拉组件 @gradio/dropdown 源码解读:从 Svelte 组件 Props 到单选/多选/示例的完整实现
【免费下载链接】gradioBuild and share delightful machine learning apps, all in Python. 🌟 Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/gr/gradio
导读
@gradio/dropdown是 Gradio 前端(Svelte 5)中承载gr.Dropdown交互下拉选择能力的基础组件包。本文以 js/dropdown/README.md 为主体,结合 Index.svelte、shared/Dropdown.svelte、shared/Multiselect.svelte 等源码与 dropdown.test.ts 测试用例,系统讲解该包对外导出的三个基础组件——BaseDropdown、BaseMultiselect、BaseExample——的全部公开 Props、默认值、交互行为与前后端参数映射。阅读完你将能够独立完成该组件的复用、二次封装,以及理解其在 Blocks / Chatbot 应用中的实际工作方式。
一、包定位与目录结构
@gradio/dropdown是 Gradio 前端工作区(pnpm workspace)中的一个独立组件包,其 package.json 声明了三个入口:
.指向Index.svelte(Dropdown / Multiselect 主组件);./example指向Example.svelte(下拉选项在 Examples 区 / 数据集表格中的展示);- 同时依赖
@gradio/atoms、@gradio/icons、@gradio/statustracker、@gradio/utils等基础包。
包内源码组织如下:
| 文件 | 职责 |
|---|---|
| Index.svelte | 对外主入口,接收 Gradio 运行时 Props,按multiselect分支渲染单/多选 |
| Example.svelte | 示例展示组件(BaseExample),把存储值映射回可读名称 |
| types.ts | DropdownProps、DropdownEvents、Item类型声明 |
| shared/Dropdown.svelte | 无框架依赖的纯单选下拉实现(BaseDropdown) |
| shared/Multiselect.svelte | 纯多选实现(BaseMultiselect) |
| shared/DropdownOptions.svelte | 共享选项列表弹出层(BaseDropdownOptions) |
| shared/utils.ts | 过滤、键盘导航等共享逻辑 |
| dropdown.test.ts | Vitest + Testing Library 单元测试 |
从 Index.svelte 的<script module>可确认对外导出名:
<script> import {BaseDropdown, BaseMultiselect, BaseExample } from "@gradio/dropdown"; </script>二、BaseDropdown:单选下拉的公开 Props
README 中BaseDropdown声明的 Props 及源码(shared/Dropdown.svelte)中可见的类型与默认值如下:
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
label | string | "Dropdown" | 输入框上方显示的标题文本 |
info | string \| undefined | undefined | 位于标题下的附加说明文本 |
value | string \| number \| (string \| number)[] \| undefined | [] | 当前选中值;实际绑定时为单个string \| number \| null |
value_is_output | boolean | false | 标记 value 是否为后端输出,影响是否额外派发input事件(见 utils.ts 的 handle_change) |
choices | [string, string \| number][] | — | 选项列表,二元组第一项为展示名、第二项为存储/提交值 |
disabled | boolean | false | 是否禁用(源码中用!interactive派生) |
show_label | boolean | — | 是否显示标题 |
container | boolean | true | 是否包裹在带边框/阴影的容器中 |
allow_custom_value | boolean | false | 是否允许输入不在choices中的自定义值 |
filterable | boolean | true | 是否允许键入文字过滤选项 |
需要特别说明的是,choices的二元组结构与 Python 侧gr.Dropdown(choices=[("显示名", 值), ...])一一对应:界面上展示的是元组第一项,真正提交给事件处理函数的是第二项。测试用例中也专门验证了这一语义(见下文"元组选项"部分)。
三、BaseMultiselect:多选下拉的公开 Props
BaseMultiselect(源码 shared/Multiselect.svelte)在单选基础上增加一个关键 Props:
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
label/info/show_label/container/disabled | 同单选 | 同单选 | 含义一致 |
value | string \| number \| (string \| number)[] \| undefined | [] | 已选项数组(实际为Item[],即(string \| number)[]) |
max_choices | number \| null | null | 最多可选数量,null表示不限 |
choices | [string, string \| number][] | — | 同单选 |
allow_custom_value | boolean | false | 允许输入自定义项 |
filterable | boolean | true | 允许键入过滤 |
i18n | I18nFormatter | — | 国际化格式化器,用于实时翻译展示名 |
多选组件内部依赖@gradio/icons中的Remove、DropdownArrow图标渲染"已选 token 的删除按钮"与"清空全部按钮";每次增删选项后,会把新的索引数组映射回真实值并写回gradio.props.value,见 shared/Multiselect.svelte 的remove_selected_choice/add_selected_choice。
四、BaseExample:示例区(Examples)的选项展示
BaseExample(源码 Example.svelte)用于在 Examples / Dataset 场景中渲染下拉示例值,它不渲染下拉控件本身:
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | string | — | 示例中存储的下拉值(可为string \| string[] \| null) |
type | "gallery" \| "table" | — | 展示位置类型,决定样式类名 |
selected | boolean | false | 当前示例是否处于选中态 |
它的核心逻辑(Example.svelte)是:把存储的value(内部值)通过choices反查回展示名——choices.find((pair) => pair[1] === val)?.[0]——再以逗号连接成可读文本输出。例如选项为[["苹果", "apple"], ...]且存储值为"apple",页面显示的就是"苹果"。这样保证了即使在示例表格中,用户看到的仍是友好的显示名而非内部代号。
五、从 Props 到交互:源码中的行为细节
README 只列出 Props,下面结合源码补充每个 Props 背后真正影响到的行为,便于二次封装时理解取舍。
5.1 value 与展示文本的同步(受控组件)
shared/Dropdown.svelte 用一个$effect监听外部value变化:焦点不在输入框时,若 value 为undefined/null/ 空数组则清空输入;若命中某个选项则把输入框文本更新为该选项的展示名并把selected_index定位到对应索引;若未命中且allow_custom_value为真,则把原始值当作输入文本回显。同时通过$effect比较old_value !== value来决定是否派发change(见 Dropdown.svelte),从而避免挂载时产生多余事件——测试"no spurious change event on mount"、"change event deduplication"都验证了这一行为。
5.2 filterable:可过滤与纯下拉两种形态
filterable直接映射到输入框的readonly属性(Dropdown.svelte):filterable=false时输入框只读、不能键入过滤;filterable=true时键入内容触发 utils.ts 的 handle_filter,对每个选项做大小写不敏感的子串匹配(o[0].toLowerCase().includes(input_text.toLowerCase())),返回保留顺序的索引数组。测试用例覆盖了"键入 BAN 只显示 banana"、"部分匹配 pineapple/apple/grape"等场景。
5.3 键盘导航与选择
handle_shared_keys 统一处理ArrowDown/ArrowUp(在过滤结果内循环移动高亮,越界时取首/尾)与Escape(关闭列表);随后在 Dropdown.svelte 的 handle_key_down 中处理Enter:若有高亮项则选中它并失焦关闭,若输入文本与某展示名完全一致则选中对应值,否则在allow_custom_value时把输入文本作为新值。测试用例"ArrowDown 从已选项移到下一项 / ArrowUp 移到上一项"均验证了以当前选中项为起点的导航逻辑。
5.4 allow_custom_value:失焦回退与自定义提交
这是最容易踩坑的语义:allow_custom_value=false时,失焦(blur)会把输入框文本强制回退为当前 value 对应的展示名,任何不匹配的键入都会被丢弃(Dropdown.svelte);allow_custom_value=true时,失焦或按 Enter 会把键入文本直接写为新的 value。测试用例以"apple" + "pie"为例验证了两种模式的分野,并以回归测试#12548确认"开启自定义值时选中元组选项仍提交内部值而非显示名"。
5.5 多选的 token 交互
多选把已选项渲染为可删除 token:Multiselect.svelte 中,当输入框为空且按下Backspace时会移除最后一个选项;remove_all一键清空(L145-L150);达到max_choices后自动收起列表并失焦。删除某项时会派发携带selected: false的select事件。
5.6 弹出层定位
共享的 DropdownOptions.svelte 负责渲染固定定位的<ul role="listbox">:它会实时测量输入框与视口上/下边缘的距离,空间不足时自动向上展开(L95-L103),并通过scroll_listener节流监听窗口滚动以跟随重定位;多选模式下开启remember_scroll在重新打开时恢复上次滚动位置。列表项带data-testid="dropdown-option"供测试与自动化使用。
六、事件模型与对外 Props(DropdownEvents)
主入口 Index.svelte 把内部回调统一派发为 Gradio 运行时事件。types.ts声明的完整事件集为:
change:选中值发生变化;input:用户交互导致值更新;select:携带SelectData(index、value、selected)的选中/取消选中详情;focus/blur:焦点进出;key_up:携带{ key, input_value }(KeyUpData)的按键事件;clear_status:清除加载状态;custom_button_click:自定义按钮点击({ id })。
其中key_up事件在上屏键抬起时派发,且input_value为当前最新的输入框文本(见 Dropdown.svelte),回归测试#12634专门验证了"连续键入 1、5、4 时最后一次key_up携带的是154而非过期值"。clear_status用于与@gradio/statustracker的加载指示联动。
七、与 Python 侧gr.Dropdown的参数对照
前端这些 Props 直接由后端 gradio/components/dropdown.py 中gr.Dropdown.__init__的参数序列化而来,两边默认值保持一致:
| 后端参数 | 前端 Props | 默认值 | 语义补充(源码 L85-L91 文档串) |
|---|---|---|---|
choices | choices | 必填 | 可为(name, value)元组列表,name 为展示名、value 传入函数 |
value | value | 首选项 | 单选默认选第一个选项;显式None则初始不选 |
multiselect | multiselect | None(自动推断) | 为 True 时 value 应为列表 |
allow_custom_value | allow_custom_value | False | 允许用户输入 choices 之外的自定义值 |
max_choices | max_choices | None | 最大可选数;multiselect=False时被忽略 |
filterable | filterable | True | 不可同时与allow_custom_value=True关用——源码会在冲突时自动回退为 True |
后端还会校验传入值必须属于 choices(除非allow_custom_value=True),见 dropdown.py 的错误提示。想要一个"可选任意值"的搜索式下拉,只需gr.Dropdown(choices=[...], value=None, allow_custom_value=True, filterable=True)。
八、测试、Storybook 与无障碍佐证
- 单元测试:dropdown.test.ts(约 1800 行)覆盖了"渲染/选项展示/过滤/选择/自定义值/事件/get_data-set_data/无障碍/动态 choices/父级值更新"十个分组,其中通过
run_shared_prop_tests与同仓库其他组件共享 Props 契约测试;测试环境同时注入了与后端I18nData一致的 i18n 标记(__i18n__...),验证了"仅翻译展示层、事件载荷保留原始值"的设计(对应 Index.svelte 的translated_choices)。 - Storybook:Dropdown.stories.svelte 提供单选中"可交互 / 静态禁用"两种故事并配置了 desktop/mobile 视觉回归模式;Multiselect.stories.svelte 覆盖多选故事,可直接本地预览。
- 无障碍(ARIA):输入框采用
role="combobox",通过aria-controls关联role="listbox"选项列表,aria-activedescendant随键盘高亮实时指向当前活动项,选项带role="option"、aria-selected与可见性化的对勾标记;相关断言集中在测试的Accessibility分组中,可作为复用时保持可访问性的行为基准。
九、实践:在自定义 Blocks 中复用的边界提示
在二次封装或编写自定义前端组件时,可以遵循以下从源码得出的结论:
- 优先从
@gradio/dropdown导入Index.svelte,并把multiselect、choices、value、max_choices、filterable、allow_custom_value作为受控 Props 由父级管理;纯展示/无状态版本(如文档站嵌入)则直接用shared/Dropdown.svelte并监听其on_change回调。 - 不要把展示名当成事件值:所有
change/select/input载荷中的value一律是 choices 元组的第二项,界面文案随时可做 i18n 翻译或改文案而不影响数据层。 value的双向同步是响应式的:当把 Dropdown 作为输出组件(value_is_output=true)时后端每次下发的新值都会驱动输入框文本与选中态刷新;此时应避免在聚焦态手动改动输入框文本,因为源码在focused状态下会跳过外部 value 的同步(Dropdown.svelte)。
如需查看真实接入效果,可运行仓库内 demo/dropdown_component/run.py(单选、多选、filterable、allow_custom_value、max_choices的完整示例),并参考 js/dropdown/CHANGELOG.md 了解历次行为修复与版本演进。
【免费下载链接】gradioBuild and share delightful machine learning apps, all in Python. 🌟 Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/gr/gradio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考