From 9c668e688624e0003d6dca6ea00fcf79a3f1b782 Mon Sep 17 00:00:00 2001 From: Jae Sung Park Date: Mon, 30 Sep 2024 20:06:37 +0900 Subject: [PATCH] feat(interaction): Intent to ship interaction.onout Implement onout to keep selected state when is away from chart area Fix #3887 --- src/ChartInternal/interactions/eventrect.ts | 11 +++++--- src/ChartInternal/shape/arc.ts | 2 +- src/ChartInternal/shape/funnel.ts | 6 +++-- src/ChartInternal/shape/radar.ts | 6 ++++- src/ChartInternal/shape/treemap.ts | 6 +++-- src/config/Options/interaction/interaction.ts | 10 +++++-- test/interactions/interaction-spec.ts | 27 +++++++++++++++++++ types/options.d.ts | 8 +++++- 8 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/ChartInternal/interactions/eventrect.ts b/src/ChartInternal/interactions/eventrect.ts index 20702a019..59aaee101 100644 --- a/src/ChartInternal/interactions/eventrect.ts +++ b/src/ChartInternal/interactions/eventrect.ts @@ -388,7 +388,7 @@ export default { return $$.isWithinShape(this, d); }); - if (shapeAtIndex.empty() && !isTooltipGrouped) { + if (shapeAtIndex.empty() && !isTooltipGrouped && config.interaction_onout) { $$.hideGridFocus?.(); $$.hideTooltip(); @@ -591,7 +591,10 @@ export default { state.event = event; // chart is destroyed - if (!config || $$.hasArcType() || eventReceiver.currentIdx === -1) { + if ( + !config || $$.hasArcType() || eventReceiver.currentIdx === -1 || + !config.interaction_onout + ) { return; } @@ -637,7 +640,7 @@ export default { */ generateEventRectsForMultipleXs(eventRectEnter): void { const $$ = this; - const {state} = $$; + const {config, state} = $$; eventRectEnter .on("click", function(event) { @@ -656,7 +659,7 @@ export default { state.event = event; // chart is destroyed - if (!$$.config || $$.hasArcType()) { + if (!$$.config || $$.hasArcType() || !config.interaction_onout) { return; } diff --git a/src/ChartInternal/shape/arc.ts b/src/ChartInternal/shape/arc.ts index af3a014de..23d107d39 100644 --- a/src/ChartInternal/shape/arc.ts +++ b/src/ChartInternal/shape/arc.ts @@ -1124,7 +1124,7 @@ export default { $$.setOverOut(true, arcData); }) .on("mouseout", (event, d) => { - if (state.transiting) { // skip while transiting + if (state.transiting || !config.interaction_onout) { // skip while transiting return; } diff --git a/src/ChartInternal/shape/funnel.ts b/src/ChartInternal/shape/funnel.ts index 49c1bce29..2df6318ab 100644 --- a/src/ChartInternal/shape/funnel.ts +++ b/src/ChartInternal/shape/funnel.ts @@ -236,8 +236,10 @@ export default { .on(isTouch ? "touchend" : "mouseout", event => { const data = getTarget(event); - $$.hideTooltip(); - $$.setOverOut(false, data); + if (config.interaction_onout) { + $$.hideTooltip(); + $$.setOverOut(false, data); + } }); } }, diff --git a/src/ChartInternal/shape/radar.ts b/src/ChartInternal/shape/radar.ts index 6efd2cab3..eadae1dd8 100644 --- a/src/ChartInternal/shape/radar.ts +++ b/src/ChartInternal/shape/radar.ts @@ -331,7 +331,7 @@ export default { bindRadarEvent(): void { const $$ = this; - const {state, $el: {radar, svg}} = $$; + const {config, state, $el: {radar, svg}} = $$; const focusOnly = $$.isPointFocusOnly(); const {inputType, transiting} = state; const isMouse = inputType === "mouse"; @@ -339,6 +339,10 @@ export default { const hide = event => { state.event = event; + if (!config.interaction_onout) { + return; + } + // const index = getIndex(event); const index = $$.getDataIndexFromEvent(event); diff --git a/src/ChartInternal/shape/treemap.ts b/src/ChartInternal/shape/treemap.ts index aa0377893..cefcebd4c 100644 --- a/src/ChartInternal/shape/treemap.ts +++ b/src/ChartInternal/shape/treemap.ts @@ -147,8 +147,10 @@ export default { .on(isTouch ? "touchend" : "mouseout", event => { const data = getTarget(event); - $$.hideTooltip(); - $$.setOverOut(false, data); + if (config.interaction_onout) { + $$.hideTooltip(); + $$.setOverOut(false, data); + } }); } }, diff --git a/src/config/Options/interaction/interaction.ts b/src/config/Options/interaction/interaction.ts index 9cd5d1d8b..4170e8524 100644 --- a/src/config/Options/interaction/interaction.ts +++ b/src/config/Options/interaction/interaction.ts @@ -18,6 +18,8 @@ export default { * @property {boolean} [interaction.inputType.mouse=true] enable or disable mouse interaction * @property {boolean} [interaction.inputType.touch=true] enable or disable touch interaction * @property {boolean|number} [interaction.inputType.touch.preventDefault=false] enable or disable to call event.preventDefault on touchstart & touchmove event. It's usually used to prevent document scrolling. + * @property {boolean} [interaction.onout=true] Enable or disable "onout" event.
+ * When is disabled, defocus(hiding tooltip, focused gridline, etc.) event won't work. * @see [Demo: touch.preventDefault](https://naver.github.io/billboard.js/demo/#Interaction.PreventScrollOnTouch) * @example * interaction: { @@ -35,11 +37,15 @@ export default { * // or threshold pixel value (pixel moved from touchstart to touchmove) * preventDefault: 5 * } - * } + * }, + * + * // disable "onout" event + * onout: false * } */ interaction_enabled: true, interaction_brighten: true, interaction_inputType_mouse: true, - interaction_inputType_touch: {} + interaction_inputType_touch: {}, + interaction_onout: true }; diff --git a/test/interactions/interaction-spec.ts b/test/interactions/interaction-spec.ts index 50f5964e2..12b0079bd 100644 --- a/test/interactions/interaction-spec.ts +++ b/test/interactions/interaction-spec.ts @@ -1454,4 +1454,31 @@ describe("INTERACTION", () => { expect(+point.attr("r")).to.be.above(r); }); }); + + describe("interaction.onover", () => { + const spy = sinon.spy(); + + beforeAll(() => { + args = { + data: { + columns: [ + ["data1", 300, 350, 300, 0, 0, 0], + ["data2", 130, 100, 140, 200, 150, 50] + ], + onout: spy + }, + interaction: { + onout: false + } + } + }); + + it("should maintain 'selected' state", () => { + util.hoverChart(chart, "mousemove", {clientX: 250, clientY: 311}); + util.hoverChart(chart, "mouseout", {clientX: -100, clientY: -100}); + + expect(chart.$.tooltip.style("display")).to.be.equal("block"); + expect(spy.called).to.be.false; + }); + }); }); diff --git a/types/options.d.ts b/types/options.d.ts index 2f145af1a..f87c99e93 100644 --- a/types/options.d.ts +++ b/types/options.d.ts @@ -245,7 +245,13 @@ export interface ChartOptions { */ preventDefault?: boolean | number; }; - } + }, + + /** + * Enable or disable "onout" event. + * When is disabled, defocus(hiding tooltip, focused gridline, etc.) event won't work. + */ + onout?: boolean; }; transition?: {