UNPKG

@bokeh/bokehjs

Version:

Interactive, novel data visualization

123 lines 3.97 kB
import { AbstractButton, AbstractButtonView } from "./abstract_button"; import { ButtonClick, MenuItemClick } from "../../core/bokeh_events"; import { div, display, undisplay, empty } from "../../core/dom"; import { isString } from "../../core/util/types"; import { execute } from "../../core/util/callbacks"; import * as buttons from "../../styles/buttons.css"; import dropdown_css, * as dropdown from "../../styles/dropdown.css"; import carets_css, * as carets from "../../styles/caret.css"; export class DropdownView extends AbstractButtonView { static __name__ = "DropdownView"; _open = false; menu_el; stylesheets() { return [...super.stylesheets(), dropdown_css, carets_css]; } connect_signals() { super.connect_signals(); const { menu } = this.model.properties; this.on_change(menu, () => this.rebuild_menu()); } render() { super.render(); const caret = div({ class: [carets.caret, carets.down] }); if (!this.model.is_split) { this.button_el.append(caret); } else { const toggle = this._render_button(caret); toggle.classList.add(buttons.dropdown_toggle); toggle.addEventListener("click", () => this._toggle_menu()); this.group_el.append(toggle); } this.menu_el = div({ class: [dropdown.menu, dropdown.below] }); this.shadow_el.append(this.menu_el); this.rebuild_menu(); undisplay(this.menu_el); } _show_menu() { if (!this._open) { this._open = true; display(this.menu_el); const listener = (event) => { if (!event.composedPath().includes(this.el)) { document.removeEventListener("click", listener); this._hide_menu(); } }; document.addEventListener("click", listener); } } _hide_menu() { if (this._open) { this._open = false; undisplay(this.menu_el); } } _toggle_menu() { if (this._open) { this._hide_menu(); } else { this._show_menu(); } } click() { if (!this.model.is_split) { this._toggle_menu(); } else { this._hide_menu(); this.model.trigger_event(new ButtonClick()); super.click(); } } _item_click(i) { this._hide_menu(); const item = this.model.menu[i]; if (item != null) { const value_or_callback = isString(item) ? item : item[1]; if (isString(value_or_callback)) { this.model.trigger_event(new MenuItemClick(value_or_callback)); } else { void execute(value_or_callback, this.model, { index: i }); } } } rebuild_menu() { empty(this.menu_el); const items = this.model.menu.map((item, i) => { if (item == null) { return div({ class: dropdown.divider }); } else { const label = isString(item) ? item : item[0]; const el = div(label); el.addEventListener("click", () => this._item_click(i)); return el; } }); this.menu_el.append(...items); } } export class Dropdown extends AbstractButton { static __name__ = "Dropdown"; constructor(attrs) { super(attrs); } static { this.prototype.default_view = DropdownView; this.define(({ Null, Bool, Str, List, Tuple, Or }) => ({ split: [Bool, false], menu: [List(Or(Str, Tuple(Str, Or(Str /*TODO*/)), Null)), []], })); this.override({ label: "Dropdown", }); } get is_split() { return this.split; } } //# sourceMappingURL=dropdown.js.map