UNPKG

@bokeh/bokehjs

Version:

Interactive, novel data visualization

215 lines 6.84 kB
import { StyledElement, StyledElementView } from "./styled_element"; import { build_view } from "../../core/build_views"; import { InlineStyleSheet } from "../../core/dom"; import { CanvasLayer } from "../../core/util/canvas"; import { BBox } from "../../core/util/bbox"; import { isNumber } from "../../core/util/types"; import { defer } from "../../core/util/defer"; import ui_css from "../../styles/ui.css"; const { round, floor } = Math; export class UIElementView extends StyledElementView { static __name__ = "UIElementView"; display = new InlineStyleSheet("", "display"); computed_stylesheets() { return [...super.computed_stylesheets(), this.display]; } stylesheets() { return [...super.stylesheets(), ui_css]; } update_style() { this.style.clear(); } box_sizing() { return { width_policy: "auto", height_policy: "auto", width: null, height: null, aspect_ratio: null, }; } _bbox = new BBox(); get bbox() { return this._bbox; } update_bbox() { return this._update_bbox(); } _update_bbox() { const displayed = (() => { // Consider using Element.checkVisibility() in the future. // https://w3c.github.io/csswg-drafts/cssom-view-1/#dom-element-checkvisibility if (!this.el.isConnected) { return false; } else if (this.el.offsetParent != null) { return true; } else { const { position, display } = getComputedStyle(this.el); return position == "fixed" && display != "none"; } })(); const bbox = !displayed ? new BBox() : (() => { const self = this.el.getBoundingClientRect(); const { left, top } = (() => { if (this.parent != null) { const parent = this.parent.el.getBoundingClientRect(); return { left: self.left - parent.left, top: self.top - parent.top, }; } else { return { left: 0, top: 0 }; } })(); return new BBox({ left: round(left), top: round(top), width: floor(self.width), height: floor(self.height), }); })(); const changed = !this._bbox.equals(bbox); this._bbox = bbox; this._is_displayed = displayed; return changed; } _resize_observer; _context_menu = null; /** * Allows to provide a context dependent menu when `UIElement.context_menu` is `"auto"`. */ _provide_context_menu() { return null; } initialize() { super.initialize(); this._resize_observer = new ResizeObserver((_entries) => this.after_resize()); this._resize_observer.observe(this.el, { box: "border-box" }); } async lazy_initialize() { await super.lazy_initialize(); const menu = (() => { const { context_menu } = this.model; if (context_menu == "auto") { return this._provide_context_menu(); } else { return context_menu; } })(); if (menu != null) { this._context_menu = await build_view(menu, { parent: this }); } } connect_signals() { super.connect_signals(); const { visible } = this.model.properties; this.on_change(visible, () => this._update_visible()); this.el.addEventListener("contextmenu", (event) => this.show_context_menu(event)); } get_context_menu(_xy) { return this._context_menu; } show_context_menu(event) { if (!event.shiftKey) { const rect = this.el.getBoundingClientRect(); const x = event.x - rect.x; const y = event.y - rect.y; const context_menu = this.get_context_menu({ x, y }); if (context_menu != null) { if (context_menu.show({ x, y })) { event.stopPropagation(); event.preventDefault(); } } } } remove() { this._resize_observer.disconnect(); this._context_menu?.remove(); super.remove(); } _resized = false; _after_resize() { } after_resize() { this._resized = true; if (this.update_bbox()) { this._after_resize(); } this.finish(); } render() { super.render(); this._apply_visible(); } _after_render() { this.update_style(); this.update_bbox(); } after_render() { super.after_render(); this._after_render(); if (!this._has_finished) { // If not displayed, then after_resize() will not be called. if (!this.is_displayed) { this.force_finished(); } else { // In case after_resize() wasn't called (see regression test for issue // #9113), then wait one macro task and consider this view finished. void defer().then(() => { if (!this._resized) { this.finish(); } }); } } } _is_displayed = false; get is_displayed() { return this._is_displayed; } _apply_visible() { if (this.model.visible) { this.display.clear(); } else { this.display.replace(":host { display: none; }"); } } _update_visible() { this._apply_visible(); } export(type = "auto", hidpi = true) { const output_backend = type == "auto" || type == "png" ? "canvas" : "svg"; const canvas = new CanvasLayer(output_backend, hidpi); const { width, height } = this.bbox; canvas.resize(width, height); return canvas; } resolve_symbol(node) { const value = this.bbox.resolve(node.symbol); const { offset } = node; if (isNumber(value)) { return value + offset; } else { const { x, y } = value; return { x: x + offset, y: y + offset }; } } } export class UIElement extends StyledElement { static __name__ = "UIElement"; constructor(attrs) { super(attrs); } static { this.define(({ Bool, AnyRef, Nullable, Or, Auto }) => ({ visible: [Bool, true], context_menu: [Nullable(Or(AnyRef(), Auto)), null], })); } } //# sourceMappingURL=ui_element.js.map