fix: skip default select when interactions already registers element-select - #4696
Conversation
…select When spec.interactions includes element-select (including isMultiple: true), do not also register the default single-select trigger so the two do not fight. Co-authored-by: David <dyk1454683243-sudo@users.noreply.github.com>
Co-authored-by: David <dyk1454683243-sudo@users.noreply.github.com>
xile611
left a comment
There was a problem hiding this comment.
@dyk1454683243-sudo 已完成 review。这个改动确实修复了饼图 interactions: [{ type: 'element-select', isMultiple: true }] 与默认单选冲突的问题,但目前会关闭自定义交互作用范围以外的默认选中,建议修复这一回归后再合并。
验证基线 c4191139a340f18f0e32dcb353c51009d88a6205 与 PR 312350584a389c56dbfc6010f6cdceb8591509b0:
| 场景 | 基线 | PR |
|---|---|---|
| 饼图连续点击两个扇区(配置 selected 样式) | 仅保留一个选中 | 正确多选 |
| 柱线组合图仅对 bar 配置 element-select,点击折线 point | 正常选中 | 无法选中 |
| 折线图仅对 line 配置 element-select,点击 point | 正常选中 | 无法选中 |
本地补充用例通过真实 VChart 创建图元,在 Node + canvas 环境显式配置 select: { trigger: 'pointertap' },通过已注册的事件处理器依次发送 pointertap / click;没有手动修改系列字段或替换图元。未做浏览器鼠标实测。PR 自带 3 个测试及修改文件的 ESLint、Prettier 均通过,但未覆盖限定 markNames 的场景。
请在解析实际匹配的图元后处理冲突,保留未被自定义交互覆盖的图元的默认选中;同时补充上述局部图元、跨系列场景的回归测试。具体位置见行内意见。
| if (interactions?.some(interaction => interaction.type === TRIGGER_TYPE_ENUM.ELEMENT_SELECT)) { | ||
| const defaultSelectIndex = res.findIndex(item => item.trigger.type === TRIGGER_TYPE_ENUM.ELEMENT_SELECT); | ||
| if (defaultSelectIndex >= 0) { | ||
| res.splice(defaultSelectIndex, 1); |
There was a problem hiding this comment.
[P2] 保留自定义交互作用范围外的默认选中
这里只要存在 element-select 就删除整个默认 select,忽略了 markNames / markIds 的作用范围。例如柱线组合图在图表级配置 interactions: [{ type: 'element-select', markNames: ['bar'], isMultiple: true }]:该配置会传给两个系列,折线系列虽然没有匹配的 bar 图元,也会先删除默认 select,随后又因匹配结果为空而不注册自定义交互,最终折线点完全无法选中。单个折线图配置 markNames: ['line'] 时,未被覆盖的 point 同样失去选中能力。上述两个场景均已验证基线通过、PR 失败。请先解析实际匹配的图元,仅处理重叠范围内的冲突,保留其他图元的默认选中,并补充回归测试。
There was a problem hiding this comment.
已核对冻结 diff、默认与自定义 element-select 的注册及状态更新路径,新增发现 1 项独立的反选样式回归。既有作用范围缺陷仍待解决,参见 https://github.com/VisActor/VChart/pull/4696#discussion_r4056020786;本次不重复提交该问题。
验证:静态核验:默认 select 配置 reverseState='selected_reverse';本次改动移除该触发器;自定义 ElementSelect 默认选项未补 reverseState;Interaction.updateStates 因而绕开反选状态分支。依赖未安装,未运行自动化或视觉测试,不能视为测试通过。
限制:隔离环境未安装仓库依赖且无网络,未运行 Jest、浏览器交互或视觉回归;结论基于冻结源码的交互配置和状态更新调用链静态核对。;未对基线和 PR 进行可执行渲染对照;既有 review 所述局部图元与跨系列回归仅引用既有讨论,不作为本次新发现。
状态:completed
| if (interactions?.some(interaction => interaction.type === TRIGGER_TYPE_ENUM.ELEMENT_SELECT)) { | ||
| const defaultSelectIndex = res.findIndex(item => item.trigger.type === TRIGGER_TYPE_ENUM.ELEMENT_SELECT); | ||
| if (defaultSelectIndex >= 0) { | ||
| res.splice(defaultSelectIndex, 1); |
There was a problem hiding this comment.
[P2] 替换默认选中时保留 selected_reverse 状态
当系列配置 state.selected_reverse,同时使用文档形式的 interactions: [{ type: 'element-select', isMultiple: true }](未显式填写 reverseState)时,删除默认 select 后,未选中的图元不再进入 selected_reverse,原本的淡化等样式消失。原因是 _defaultSelectConfig 为默认触发器设置了 reverseState: STATE_SELECTED_REVERSE,而自定义触发器直接使用原始 spec;ElementSelect.defaultOptions 仅补 state 和 trigger,Interaction.updateStates 收到空的 reverseState 时只执行选中分支。此前默认触发器会设置未选中图元的反选状态;且 IElementSelectSpec 的注释明确允许用 state.selected_reverse 配置非激活图元。建议在避免双触发器冲突的同时为未显式覆盖该字段的自定义 select 保留默认反选状态,并增加该样式的真实状态回归测试。此问题与现有讨论所述 markNames / markIds 导致图元无法选中的作用范围问题不同:这里即使自定义交互覆盖全部图元,反选视觉状态也会丢失。
Keep default select for marks outside a custom element-select, and copy selected_reverse onto that trigger when it replaces the default. Co-authored-by: David <dyk1454683243-sudo@users.noreply.github.com>
| }); | ||
|
|
||
| if (defaultSelect && coveredDefaultSelectMarkIds.size) { | ||
| defaultSelect.marks = defaultSelect.marks.filter(mark => !coveredDefaultSelectMarkIds.has(mark.id)); |
There was a problem hiding this comment.
[P2] 保留局部自定义选中时其他图元的跨图元反选样式
当同一折线系列的 line、point 都配置 selected_reverse,且 interactions 只对 line 配置 element-select 时,这里把 line 从默认 select 的 marks 中剔除。点击 point 后,默认触发器虽仍能选中 point,但 Interaction.addBothStateOfGraphics 只遍历该触发器的 marks,因而不再给未选中的 line 图元添加 selected_reverse;反过来点击 line 也不会反选 point。基线的默认触发器包含两种图元,可正确将另一种图元置为反选态。这与既有讨论中“point 完全无法选中”的问题不同:现在 point 可选中,但跨图元的反选视觉效果丢失。建议分离默认选中的事件覆盖范围与反选状态传播范围,或在共享状态的触发器中保留对未选图元的反选处理,并补充点击一种图元后检查另一种图元真实状态的用例。已用独立 Node 简化复现验证 marks 范围差异,未运行工程交互或视觉测试。
When a custom element-select covers only some default-select marks, keep click handling split but still apply selected_reverse across the original mark set. Co-authored-by: David <dyk1454683243-sudo@users.noreply.github.com>
| const eventMarkIds = new Set(customSelect.marks.map(mark => mark.id)); | ||
| const reverseMarks = originalMarks.filter(mark => !eventMarkIds.has(mark.id)); | ||
| if (reverseMarks.length) { | ||
| customSelect.reverseMarks = reverseMarks; |
There was a problem hiding this comment.
[P2] 拆分选中触发器后需同步跨 mark 的选中状态
当折线系列的 interactions 仅对 line 配置 element-select、而默认 select 仍负责 point,且两者都配置 selected / selected_reverse 样式时,先点击 point 再点击 line:point 默认触发器已将 point 设为 selected、line 设为 selected_reverse;这里让 line 自定义触发器又将 point 纳入反选范围,但两个触发器分别保存 statedGraphics。第二次点击时 addBothStateOfGraphics 对 point 追加 selected_reverse,不会移除其原有 selected,结果 point 同时为选中和反选、line 也保持选中,单选视觉及状态不一致。此前默认触发器覆盖两个 mark,第二次点击会清除 point 的 selected。建议在两个触发器之间协调已选图元的状态/清理,补充“先点 point、再点 line、再取消”的连续交互回归测试;目前仅以源码和简化 Node 状态序列核验,未运行真实 VChart 交互测试。
Co-authored-by: David <dyk1454683243-sudo@users.noreply.github.com>
| const markId = graphic.context?.markId; | ||
| const hasAnimation = this._hasAnimationByGraphicState(graphic, markById); | ||
| if (graphicHasState(graphic, state)) { | ||
| removeGraphicState(graphic, state, hasAnimation); |
There was a problem hiding this comment.
[P2] 保留 setSelected 跨拆分图元的批量选中
当折线系列仅对 line 配置 interactions: [{ type: 'element-select', markNames: ['line'] }],再用公开的 setSelected(datumArray) 同时选中 line 与 point 图元时,updateStateOfGraphics 会依次将同一批图元交给拆分后的默认触发器与自定义触发器;第二个触发器在这里把第一个触发器刚设好的 selected 移除并清空其选中记录,随后 addBothStateOfGraphics 还会将其置为 selected_reverse。最终只能留下后处理的那一种图元选中,调用者指定的另一种图元丢失;此前默认触发器同时覆盖两种图元,批量调用可以保留两者。建议对程序化批量设置按整批目标协调拆分触发器,而不是逐个触发器互斥释放;增加同时命中 line/point 的 setSelected 状态断言。此结论是冻结源码调用链推导,当前环境未运行工程测试。
Programmatic setSelected that hits both line and point was applied one split trigger at a time, so the second trigger cleared the first mark and put it into selected_reverse. Treat that batch as one target set. Co-authored-by: David <dyk1454683243-sudo@users.noreply.github.com>
Fixes #4563
🤔 This is a ...
🔗 Related issue link
Fixes #4563
💡 Background and solution
Specs that follow the official pie multi-select example:
could not keep more than one sector selected.
Default
select.enable: truestill registered a single-selectelement-selecttrigger (pointertap, replace). The specinteractionsentry registered a second one (click, accumulate). Those configs are notisEqual, soBaseChart._initInteractionskept both live. On click, default select appliedselectedfirst; the multi-select trigger then sawhasStateand did nothing.This change skips the default select trigger when
spec.interactionsalready contains anelement-selectentry. Hover / dimension-hover defaults are unchanged.interactionsmulti-select works withoutselect: falseinteractions) stays single-selectselect: { mode: 'multiple' }withoutinteractionsstill accumulates📝 Changelog
interactionsalready registerselement-select, soisMultiplemulti-select worksinteractions已配置element-select时不再注册默认单选,使isMultiple多选生效☑️ Self-Check before Merge
Verification
eslint --quietonpackages/vchart/src/series/base/base-series.tsand the new test: cleanprettier --checkon the same files: cleanjest __tests__/unit/interaction/element-select.test.ts --runner=jest-runner --testEnvironment=node: 3 passedjest-electronrunner is the default; the node runner is enough for this non-render wiring/state regression.