Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add delay for tooltip to show up for buttons #5860

Merged
merged 5 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions panel/models/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@ export class ButtonView extends BkButtonView {
visible,
})
}
let timer: number
this.el.addEventListener("mouseenter", () => {
toggle(true)
timer = setTimeout(() => toggle(true), this.model.tooltip_delay)
})
this.el.addEventListener("mouseleave", () => {
clearTimeout(timer)
toggle(false)
})
}
Expand All @@ -51,6 +53,7 @@ export namespace Button {

export type Props = BkButton.Props & {
tooltip: p.Property<Tooltip | null>
tooltip_delay: p.Property<number>
}
}

Expand All @@ -69,8 +72,9 @@ export class Button extends BkButton {
static {
this.prototype.default_view = ButtonView

this.define<Button.Props>(({Nullable, Ref}) => ({
this.define<Button.Props>(({Nullable, Ref, Number}) => ({
tooltip: [ Nullable(Ref(Tooltip)), null ],
tooltip_delay: [ Number, 500],
}))
}
}
8 changes: 6 additions & 2 deletions panel/models/checkbox_button_group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ export class CheckboxButtonGroupView extends bkCheckboxButtonGroupView {
visible,
})
}
let timer: number
this.el.addEventListener("mouseenter", () => {
toggle(true)
timer = setTimeout(() => toggle(true), this.model.tooltip_delay)
})
this.el.addEventListener("mouseleave", () => {
clearTimeout(timer)
toggle(false)
})
}
Expand All @@ -54,6 +56,7 @@ export namespace CheckboxButtonGroup {

export type Props = bkCheckboxButtonGroup.Props & {
tooltip: p.Property<Tooltip | null>
tooltip_delay: p.Property<number>
}
}

Expand All @@ -72,8 +75,9 @@ export class CheckboxButtonGroup extends bkCheckboxButtonGroup {
static {
this.prototype.default_view = CheckboxButtonGroupView

this.define<CheckboxButtonGroup.Props>(({Nullable, Ref}) => ({
this.define<CheckboxButtonGroup.Props>(({Nullable, Ref, Number}) => ({
tooltip: [ Nullable(Ref(Tooltip)), null ],
tooltip_delay: [ Number, 500],
}))
}
}
8 changes: 6 additions & 2 deletions panel/models/radio_button_group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ export class RadioButtonGroupView extends bkRadioButtonGroupView {
visible,
})
}
let timer: number
this.el.addEventListener("mouseenter", () => {
toggle(true)
timer = setTimeout(() => toggle(true), this.model.tooltip_delay)
})
this.el.addEventListener("mouseleave", () => {
clearTimeout(timer)
toggle(false)
})
}
Expand All @@ -54,6 +56,7 @@ export namespace RadioButtonGroup {

export type Props = bkRadioButtonGroup.Props & {
tooltip: p.Property<Tooltip | null>
tooltip_delay: p.Property<number>
}
}

Expand All @@ -72,8 +75,9 @@ export class RadioButtonGroup extends bkRadioButtonGroup {
static {
this.prototype.default_view = RadioButtonGroupView

this.define<RadioButtonGroup.Props>(({Nullable, Ref}) => ({
this.define<RadioButtonGroup.Props>(({Nullable, Ref, Number}) => ({
tooltip: [ Nullable(Ref(Tooltip)), null ],
tooltip_delay: [ Number, 500],
}))
}
}
15 changes: 15 additions & 0 deletions panel/models/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ class Button(bkButton):
description of a widget's or component's function.
""")

tooltip_delay = Int(500, help="""
Delay (in milliseconds) to display the tooltip after the cursor has
hovered over the Button, default is 500ms.
""")


class CheckboxButtonGroup(bkCheckboxButtonGroup):

Expand All @@ -231,10 +236,20 @@ class CheckboxButtonGroup(bkCheckboxButtonGroup):
description of a widget's or component's function.
""")

tooltip_delay = Int(500, help="""
Delay (in milliseconds) to display the tooltip after the cursor has
hovered over the Button, default is 500ms.
""")


class RadioButtonGroup(bkRadioButtonGroup):

tooltip = Nullable(Instance(Tooltip), help="""
A tooltip with plain text or rich HTML contents, providing general help or
description of a widget's or component's function.
""")

tooltip_delay = Int(500, help="""
Delay (in milliseconds) to display the tooltip after the cursor has
hovered over the Button, default is 500ms.
""")
52 changes: 48 additions & 4 deletions panel/tests/ui/widgets/test_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
pytest.importorskip("playwright")

from bokeh.models import Tooltip
from playwright.sync_api import expect
from playwright.sync_api import Expect, expect

from panel.tests.util import serve_component, wait_until
from panel.widgets import (
Expand All @@ -29,7 +29,7 @@ def cb(event):


@pytest.mark.parametrize(
"tooltip",
"description",
["Test", Tooltip(content="Test", position="right"), TooltipIcon(value="Test")],
ids=["str", "Tooltip", "TooltipIcon"],
)
Expand All @@ -42,8 +42,8 @@ def cb(event):
],
ids=["Button", "CheckButtonGroup", "RadioButtonGroup"],
)
def test_button_tooltip(page, button_fn, button_locator, tooltip):
pn_button = button_fn(name="test", description="Test")
def test_button_tooltip(page, button_fn, button_locator, description):
pn_button = button_fn(name="test", description=description, description_delay=0)

serve_component(page, pn_button)

Expand All @@ -62,3 +62,47 @@ def test_button_tooltip(page, button_fn, button_locator, tooltip):
page.hover("body")
tooltip = page.locator(".bk-tooltip-content")
expect(tooltip).to_have_count(0)


@pytest.mark.parametrize(
"button_fn,button_locator",
[
(lambda **kw: Button(**kw), ".bk-btn"),
(lambda **kw: CheckButtonGroup(options=["A", "B"], **kw), ".bk-btn-group"),
(lambda **kw: RadioButtonGroup(options=["A", "B"], **kw), ".bk-btn-group"),
],
ids=["Button", "CheckButtonGroup", "RadioButtonGroup"],
)
def test_button_tooltip_with_delay(page, button_fn, button_locator):
pn_button = button_fn(name="test", description="Test", description_delay=300)

exp = Expect()
exp.set_options(timeout=200)

serve_component(page, pn_button)

button = page.locator(button_locator)
expect(button).to_have_count(1)
tooltip = page.locator(".bk-tooltip-content")
expect(tooltip).to_have_count(0)

# Hovering over the button should not show the tooltip
page.hover(button_locator)
tooltip = page.locator(".bk-tooltip-content")
exp(tooltip).to_have_count(0)

# After 100 ms the tooltip should be visible
page.wait_for_timeout(200)
exp(tooltip).to_have_count(1)

# Removing hover should hide the tooltip
page.hover("body")
tooltip = page.locator(".bk-tooltip-content")
exp(tooltip).to_have_count(0)

# Hovering over the button for a short time should not show the tooltip
page.hover(button_locator)
page.wait_for_timeout(50)
page.hover("body")
page.wait_for_timeout(300)
exp(tooltip).to_have_count(0)
8 changes: 7 additions & 1 deletion panel/widgets/_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ class TooltipMixin(Widget):
description = param.ClassSelector(default=None, class_=(str, BkTooltip, TooltipIcon), doc="""
The description in the tooltip.""")

_rename: ClassVar[Mapping[str, str | None]] = {'description': 'tooltip'}
description_delay = param.Integer(default=500, doc="""
Delay (in milliseconds) to display the tooltip after the cursor has
hovered over the Button, default is 500ms.""")

_rename: ClassVar[Mapping[str, str | None]] = {
'description': 'tooltip', 'description_delay': 'tooltip_delay'
}

def _process_param_change(self, params) -> dict[str, Any]:
desc = params.get('description')
Expand Down
2 changes: 1 addition & 1 deletion panel/widgets/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ class Button(_ClickButton, TooltipMixin):
Toggles from False to True while the event is being processed.""")

_rename: ClassVar[Mapping[str, str | None]] = {
'clicks': None, 'name': 'label', 'value': None,
**TooltipMixin._rename, 'clicks': None, 'name': 'label', 'value': None,
}

_source_transforms: ClassVar[Mapping[str, str | None]] = {
Expand Down
4 changes: 4 additions & 0 deletions panel/widgets/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,8 @@ class RadioButtonGroup(_RadioGroupBase, _ButtonBase, TooltipMixin):
objects=['horizontal', 'vertical'], doc="""
Button group orientation, either 'horizontal' (default) or 'vertical'.""")

_rename: ClassVar[Mapping[str, str | None]] = {**_RadioGroupBase._rename, **TooltipMixin._rename}

_source_transforms = {
'value': "source.labels[value]", 'button_style': None, 'description': None
}
Expand Down Expand Up @@ -755,6 +757,8 @@ class CheckButtonGroup(_CheckGroupBase, _ButtonBase, TooltipMixin):
objects=['horizontal', 'vertical'], doc="""
Button group orientation, either 'horizontal' (default) or 'vertical'.""")

_rename: ClassVar[Mapping[str, str | None]] = {**_CheckGroupBase._rename, **TooltipMixin._rename}

_source_transforms = {
'value': "value.map((index) => source.labels[index])", 'button_style': None,
'description': None
Expand Down