@bokeh/bokehjs
Version:
Interactive, novel data visualization
215 lines • 6.84 kB
JavaScript
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