UNPKG

@bokeh/bokehjs

Version:

Interactive, novel data visualization

138 lines 4.89 kB
import Choices from "choices.js"; import { select } from "../../core/dom"; import { isString } from "../../core/util/types"; import { is_equal } from "../../core/util/eq"; import * as inputs from "../../styles/widgets/inputs.css"; import choices_css from "../../styles/widgets/choices.css"; import { InputWidget, InputWidgetView } from "./input_widget"; function retarget(event) { Object.defineProperty(event, "target", { get: () => event.composedPath()[0] ?? null, configurable: true, }); return event; } class OurChoices extends Choices { static __name__ = "OurChoices"; _onFocus(event) { super._onFocus(retarget(event)); } _onBlur(event) { super._onBlur(retarget(event)); } _onKeyUp(event) { super._onKeyUp(retarget(event)); } _onKeyDown(event) { super._onKeyDown(retarget(event)); } _onClick(event) { super._onClick(retarget(event)); } _onTouchEnd(event) { super._onTouchEnd(retarget(event)); } _onMouseDown(event) { super._onMouseDown(retarget(event)); } _onMouseOver(event) { super._onMouseOver(retarget(event)); } } export class MultiChoiceView extends InputWidgetView { static __name__ = "MultiChoiceView"; choice_el; connect_signals() { super.connect_signals(); this.connect(this.model.properties.disabled.change, () => this.set_disabled()); const { value, max_items, option_limit, search_option_limit, delete_button, placeholder, options, name, title } = this.model.properties; this.on_change([max_items, option_limit, search_option_limit, delete_button, placeholder, options, name, title], () => this.rerender()); this.on_change(value, () => { // Detects if value change originated in UI or elsewhere. Choices.js automatically // updates itself, so we don't have to do anything, and in fact we shouldn't do // anything, because the component is finicky and hard to update without breaking // something, losing focus, etc. if (!is_equal(this.model.value, this._current_values)) { this.rerender(); } }); } stylesheets() { return [...super.stylesheets(), choices_css]; } _render_input() { return this.input_el = select({ multiple: true, class: inputs.input, name: this.model.name, disabled: this.model.disabled, }); } render() { super.render(); const selected = new Set(this.model.value); const choices = this.model.options.map((opt) => { let value, label; if (isString(opt)) { value = label = opt; } else { [value, label] = opt; } return { value, label, selected: selected.has(value) }; }); const fill = this.model.solid ? "solid" : "light"; const item = `choices__item ${fill}`; const button = `choices__button ${fill}`; const options = { choices, itemSelectText: "", duplicateItemsAllowed: false, shouldSort: false, removeItemButton: this.model.delete_button, classNames: { item, button }, // XXX: missing typings placeholderValue: this.model.placeholder, maxItemCount: this.model.max_items ?? -1, renderChoiceLimit: this.model.option_limit ?? -1, searchResultLimit: this.model.search_option_limit ?? 4, }; this.choice_el = new OurChoices(this.input_el, options); this.input_el.addEventListener("change", () => this.change_input()); } set_disabled() { if (this.model.disabled) { this.choice_el.disable(); } else { this.choice_el.enable(); } } get _current_values() { const values = this.choice_el.getValue(); return values.map((item) => item.value); } change_input() { this.model.value = this._current_values; super.change_input(); } } export class MultiChoice extends InputWidget { static __name__ = "MultiChoice"; constructor(attrs) { super(attrs); } static { this.prototype.default_view = MultiChoiceView; this.define(({ Bool, Int, Str, List, Tuple, Or, Nullable }) => ({ value: [List(Str), []], options: [List(Or(Str, Tuple(Str, Str))), []], max_items: [Nullable(Int), null], delete_button: [Bool, true], placeholder: [Nullable(Str), null], option_limit: [Nullable(Int), null], search_option_limit: [Nullable(Int), null], solid: [Bool, true], })); } } //# sourceMappingURL=multi_choice.js.map