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

Ensure TooltipIcon description can be updated #6099

Merged
merged 2 commits into from
Dec 20, 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
58 changes: 23 additions & 35 deletions panel/models/tooltip_icon.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,29 @@
import { Control, ControlView } from '@bokehjs/models/widgets/control'
import { Tooltip, TooltipView } from '@bokehjs/models/ui/tooltip'
import { Tooltip } from '@bokehjs/models/ui/tooltip'
import { UIElement } from "@bokehjs/models/ui/ui_element"
import { LayoutDOM, LayoutDOMView } from "@bokehjs/models/layouts/layout_dom"

import type {IterViews} from '@bokehjs/core/build_views'
import {build_view} from '@bokehjs/core/build_views'
import type {StyleSheetLike} from '@bokehjs/core/dom'
import {div, label} from '@bokehjs/core/dom'
import * as p from '@bokehjs/core/properties'

import inputs_css, * as inputs from '@bokehjs/styles/widgets/inputs.css'
import icons_css from '@bokehjs/styles/icons.css'

export class TooltipIconView extends ControlView {
export class TooltipIconView extends LayoutDOMView {
declare model: TooltipIcon

protected description: TooltipView

protected desc_el: HTMLElement

public *controls() {}

override *children(): IterViews {
yield* super.children()
yield this.description
}

override async lazy_initialize(): Promise<void> {
await super.lazy_initialize()

const { description } = this.model
this.description = await build_view(description, { parent: this })
get child_models(): UIElement[] {
if (this.model.description == null)
return []
return [this.model.description]
}

override remove(): void {
this.description?.remove()
super.remove()
override connect_signals(): void {
super.connect_signals()
const {description} = this.model.properties
this.on_change(description, () => this.update_children())
}

override stylesheets(): StyleSheetLike[] {
Expand All @@ -46,37 +36,37 @@ export class TooltipIconView extends ControlView {
const icon_el = div({ class: inputs.icon })
this.desc_el = div({ class: inputs.description }, icon_el)

const { desc_el, description } = this
description.model.target = desc_el
this.model.description.target = this.desc_el

let persistent = false

const toggle = (visible: boolean) => {
description.model.setv({
this.model.description.setv({
visible,
closable: persistent,
})
icon_el.classList.toggle(inputs.opaque, visible && persistent)
}

this.on_change(description.model.properties.visible, () => {
const { visible } = description.model
this.on_change(this.model.description.properties.visible, () => {
const { visible } = this.model.description
if (!visible) {
persistent = false
}
toggle(visible)
})
desc_el.addEventListener('mouseenter', () => {
this.desc_el.addEventListener('mouseenter', () => {
toggle(true)
})
desc_el.addEventListener('mouseleave', () => {
this.desc_el.addEventListener('mouseleave', () => {
if (!persistent) toggle(false)
})
document.addEventListener('mousedown', (event) => {
const path = event.composedPath()
if (path.includes(description.el)) {
const tooltip_view = this._child_views.get(this.model.description)
if (tooltip_view !== undefined && path.includes(tooltip_view.el)) {
return
} else if (path.includes(desc_el)) {
} else if (path.includes(this.desc_el)) {
persistent = !persistent
toggle(persistent)
} else {
Expand All @@ -92,21 +82,19 @@ export class TooltipIconView extends ControlView {
// Label to get highlight when icon is hovered
this.shadow_el.appendChild(label(this.desc_el))
}

change_input(): void {}
}

export namespace TooltipIcon {
export type Attrs = p.AttrsOf<Props>

export type Props = Control.Props & {
export type Props = LayoutDOM.Props & {
description: p.Property<Tooltip>
}
}

export interface TooltipIcon extends TooltipIcon.Attrs {}

export class TooltipIcon extends Control {
export class TooltipIcon extends LayoutDOM {
declare properties: TooltipIcon.Props
declare __view_type__: TooltipIconView
static __module__ = 'panel.models.widgets'
Expand Down
33 changes: 32 additions & 1 deletion panel/tests/ui/widgets/test_indicators.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from bokeh.models import Tooltip
from playwright.sync_api import expect

from panel.tests.util import serve_component
from panel.tests.util import serve_component, wait_until
from panel.widgets import TooltipIcon

pytestmark = pytest.mark.ui
Expand Down Expand Up @@ -49,3 +49,34 @@ def test_plaintext_tooltip(page, value):
page.click("body")
tooltip = page.locator(".bk-tooltip-content")
expect(tooltip).to_have_count(0)


def test_tooltip_text_updates(page):
tooltip_icon = TooltipIcon(value="Test")

serve_component(page, tooltip_icon)

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

# Hovering over the icon should show the tooltip
page.hover(".bk-icon")
tooltip = page.locator(".bk-tooltip-content")
expect(tooltip).to_have_count(1)
expect(tooltip).to_have_text("Test")

tooltip_icon.value = "Updated"

def hover():
page.hover(".bk-icon")
visible = page.locator(".bk-tooltip-content").count() == 1
page.hover("body")
return visible
wait_until(hover, page)

page.hover(".bk-icon")
tooltip = page.locator(".bk-tooltip-content")
expect(tooltip).to_have_count(1)
expect(tooltip).to_have_text("Updated")
7 changes: 4 additions & 3 deletions panel/widgets/indicators.py
Original file line number Diff line number Diff line change
Expand Up @@ -1354,18 +1354,19 @@ class TooltipIcon(Widget):
>>> pn.widgets.TooltipIcon(value="This is a simple tooltip by using a string")
"""

value = param.ClassSelector(default="Description", class_=(str, Tooltip), doc="""
The description in the tooltip.""")

align = Align(default='center', doc="""
Whether the object should be aligned with the start, end or
center of its container. If set as a tuple it will declare
(vertical, horizontal) alignment.""")

_widget_type = _BkTooltipIcon
value = param.ClassSelector(default="Description", class_=(str, Tooltip), doc="""
The description in the tooltip.""")

_rename: ClassVar[Mapping[str, str | None]] = {'name': None, 'value': 'description'}

_widget_type = _BkTooltipIcon


__all__ = [
"BooleanIndicator",
Expand Down