UNPKG

@bokeh/bokehjs

Version:

Interactive, novel data visualization

219 lines 8.2 kB
import { div, empty, px, InlineStyleSheet } from "../../core/dom"; import { DropPane } from "../../core/util/panes"; import { enumerate } from "../../core/util/iterator"; import { color2css } from "../../core/util/color"; import { cycle } from "../../core/util/math"; import { linspace } from "../../core/util/array"; import { assert } from "../../core/util/assert"; import { InputWidget, InputWidgetView } from "./input_widget"; import * as inputs_css from "../../styles/widgets/inputs.css"; import * as palette_select_css from "../../styles/widgets/palette_select.css"; import * as item_css from "../../styles/widgets/palette_select_item.css"; import * as pane_css from "../../styles/widgets/palette_select_pane.css"; import * as icons_css from "../../styles/icons.css"; import { Tuple, Str, Arrayable, Color } from "../../core/kinds"; const Item = Tuple(Str, Arrayable(Color)); export class PaletteSelectView extends InputWidgetView { static __name__ = "PaletteSelectView"; _value_el; _pane; _style = new InlineStyleSheet("", "select"); _style_menu = new InlineStyleSheet("", "menu"); stylesheets() { return [...super.stylesheets(), palette_select_css.default, item_css.default, icons_css.default, this._style]; } connect_signals() { super.connect_signals(); const { value, items, ncols, swatch_width, swatch_height } = this.model.properties; this.on_change([items, swatch_width, swatch_height], () => this.rerender()); this.on_change(value, () => this._update_value()); this.on_change(ncols, () => this._update_ncols()); } _update_value() { empty(this._value_el); const content = this._render_value(); if (content != null) { this._value_el.append(content); } } _update_ncols() { const { ncols } = this.model; this._pane.el.style.setProperty("--number-of-columns", `${ncols}`); } _render_item(item) { const [name] = item; const i = this.model.items.indexOf(item); assert(i != -1); const swatch = div({ class: item_css.swatch, id: `item_${i}` }); return div({ class: item_css.entry }, swatch, div(name)); } _render_value() { const { value, items } = this.model; const entry = items.find(([name]) => name == value); if (entry != null) { return this._render_item(entry); } else { return null; } } _render_input() { this._value_el = div({ class: [palette_select_css.value, item_css.entry] }, this._render_value()); const chevron_el = div({ class: [palette_select_css.chevron, icons_css.tool_icon_chevron_down] }); const input_el = div({ class: [inputs_css.input, palette_select_css.value_input] }, this._value_el, chevron_el); if (this.model.disabled) { input_el.classList.add(inputs_css.disabled); } else { input_el.tabIndex = 0; } this.input_el = input_el; // XXX Div is not an Input-like element return this.input_el; } render() { super.render(); const { swatch_width, swatch_height } = this.model; this._style.replace(` .${item_css.swatch} { width: ${swatch_width}px; height: ${swatch_height == "auto" ? "auto" : px(swatch_height)}; } `); for (const [item, i] of enumerate(this.model.items)) { const [, colors] = item; const n = colors.length; const stops = linspace(0, 100, n + 1); const color_map = []; for (const [color, i] of enumerate(colors)) { const [from, to] = [stops[i], stops[i + 1]]; color_map.push(`${color2css(color)} ${from}% ${to}%`); } const gradient = color_map.join(", "); this._style.append(` #item_${i} { background: linear-gradient(to right, ${gradient}); } `); } // The widget and its menu are independent components, so they need // to have their own stylesheets. this._style_menu.replace(this._style.css); const item_els = []; for (const [item, i] of enumerate(this.model.items)) { const entry_el = this._render_item(item); const item_el = div({ class: item_css.item, tabIndex: 0 }, entry_el); item_el.addEventListener("pointerup", () => { this.select(item); }); item_el.addEventListener("keyup", (event) => { switch (event.key) { case "Enter": { this.select(item); break; } case "Escape": { this.hide(); break; } default: } }); const move_focus = (offset) => { const { items } = this.model; const j = cycle(i + offset, 0, items.length - 1); item_els[j].focus(); }; item_el.addEventListener("keydown", (event) => { const offset = (() => { switch (event.key) { case "ArrowUp": return -this.model.ncols; case "ArrowDown": return +this.model.ncols; case "ArrowLeft": return -1; case "ArrowRight": return +1; default: return null; } })(); if (offset != null) { event.preventDefault(); move_focus(offset); } }); item_els.push(item_el); } this._pane = new DropPane(item_els, { target: this.group_el, prevent_hide: this.input_el, extra_stylesheets: [item_css.default, pane_css.default, this._style_menu], }); this._update_ncols(); this.input_el.addEventListener("pointerup", () => { this.toggle(); }); this.input_el.addEventListener("keyup", (event) => { switch (event.key) { case "Enter": { this.toggle(); break; } case "Escape": { this.hide(); break; } default: } }); const move_selection = (offset) => { const { items, value } = this.model; const i = items.findIndex(([name]) => value == name); if (i != -1) { const j = cycle(i + offset, 0, items.length - 1); this.select(items[j]); } }; this.input_el.addEventListener("keydown", (event) => { const offset = (() => { switch (event.key) { case "ArrowUp": return -1; case "ArrowDown": return +1; default: return null; } })(); if (offset != null) { event.preventDefault(); move_selection(offset); } }); } select(item) { this.hide(); const [name] = item; this.model.value = name; super.change_input(); this.input_el.focus(); } toggle() { if (!this.model.disabled) { this._pane.toggle(); } } hide() { this._pane.hide(); } } export class PaletteSelect extends InputWidget { static __name__ = "PaletteSelect"; constructor(attrs) { super(attrs); } static { this.prototype.default_view = PaletteSelectView; this.define(({ Int, Str, List, NonNegative, Positive, Or, Auto }) => ({ value: [Str], items: [List(Item)], ncols: [Positive(Int), 1], swatch_width: [NonNegative(Int), 100], swatch_height: [Or(Auto, NonNegative(Int)), "auto"], })); } } //# sourceMappingURL=palette_select.js.map