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