feat: add shinyRemoveButton selectize.js option (for py-shiny) (#4276)

* fix: supply and retain default selectize.js plugins (for py-shiny)

* `npm run build` (GitHub Actions)

* Move more in a 'remove_button' attribute direction

* `npm run build` (GitHub Actions)

* Move to a JSON-only approach

* `npm run build` (GitHub Actions)

* Drop sticky update logic by always sending 'missing' value and resolving client-side

* Cleanup

* Don't mutate options; better typing pattern

* `npm run build` (GitHub Actions)

---------

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
This commit is contained in:
Carson Sievert
2025-08-28 15:49:25 -05:00
committed by GitHub
parent 6d6b0ea6f9
commit f981ed6363
6 changed files with 94 additions and 17 deletions

View File

@@ -2414,6 +2414,7 @@
},
JSON.parse(config.html())
);
options = this._addShinyRemoveButton(options, el.hasAttribute("multiple"));
if (typeof config.data("nonempty") !== "undefined") {
el.nonempty = true;
options = import_jquery19.default.extend(options, {
@@ -2450,6 +2451,34 @@
}
return control;
}
// Translate shinyRemoveButton option into selectize plugins
_addShinyRemoveButton(options, multiple) {
let removeButton = options.shinyRemoveButton;
if (removeButton === void 0) {
return options;
}
if (removeButton === "none") {
removeButton = multiple ? "true" : "false";
}
if (removeButton === "false") {
return options;
}
const plugins = [];
if (removeButton === "both") {
plugins.push("remove_button", "clear_button");
} else if (removeButton === "true") {
plugins.push(multiple ? "remove_button" : "clear_button");
}
return {
...options,
plugins: Array.from(
/* @__PURE__ */ new Set([
...Array.isArray(options.plugins) ? options.plugins : [],
...plugins
])
)
};
}
};
// srcts/src/bindings/input/slider.ts

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -13,9 +13,23 @@ type SelectInputReceiveMessageData = {
value?: string;
};
type SelectizeOptions = Selectize.IOptions<string, unknown>;
type SelectizeInfo = Selectize.IApi<string, unknown> & {
settings: SelectizeOptions;
settings: Selectize.IOptions<string, unknown>;
};
type SelectizeOptions = Selectize.IOptions<string, unknown> & {
// Provide some stronger typing for the Selectize options
labelField: "label";
valueField: "value";
searchField: ["label"];
onItemRemove?: (value: string) => void;
onDropdownClose?: () => void;
};
// Adds a py-shiny specific "option" that makes the
// input_selectize(remove_button) parameter possible
type SelectizeShinyOptions = SelectizeOptions & {
shinyRemoveButton?: "none" | "true" | "false" | "both";
};
function getLabelNode(el: SelectHTMLElement): JQuery<HTMLElement> {
@@ -244,13 +258,7 @@ class SelectInputBinding extends InputBinding {
if (config.length === 0) return undefined;
let options: SelectizeOptions & {
labelField: "label";
valueField: "value";
searchField: ["label"];
onItemRemove?: (value: string) => void;
onDropdownClose?: () => void;
} = $.extend(
let options: SelectizeShinyOptions = $.extend(
{
labelField: "label",
valueField: "value",
@@ -259,6 +267,8 @@ class SelectInputBinding extends InputBinding {
JSON.parse(config.html()),
);
options = this._addShinyRemoveButton(options, el.hasAttribute("multiple"));
// selectize created from selectInput()
if (typeof config.data("nonempty") !== "undefined") {
el.nonempty = true;
@@ -305,6 +315,44 @@ class SelectInputBinding extends InputBinding {
return control;
}
// Translate shinyRemoveButton option into selectize plugins
private _addShinyRemoveButton(
options: SelectizeShinyOptions,
multiple: boolean,
): SelectizeOptions {
let removeButton = options.shinyRemoveButton;
if (removeButton === undefined) {
return options;
}
// None really means 'smart default'
if (removeButton === "none") {
removeButton = multiple ? "true" : "false";
}
if (removeButton === "false") {
return options;
}
const plugins = [];
if (removeButton === "both") {
plugins.push("remove_button", "clear_button");
} else if (removeButton === "true") {
plugins.push(multiple ? "remove_button" : "clear_button");
}
// Add plugins to existing plugins if not already present
return {
...options,
plugins: Array.from(
new Set([
...(Array.isArray(options.plugins) ? options.plugins : []),
...plugins,
]),
),
};
}
}
export { SelectInputBinding };

View File

@@ -9,9 +9,8 @@ type SelectInputReceiveMessageData = {
url?: string;
value?: string;
};
type SelectizeOptions = Selectize.IOptions<string, unknown>;
type SelectizeInfo = Selectize.IApi<string, unknown> & {
settings: SelectizeOptions;
settings: Selectize.IOptions<string, unknown>;
};
declare class SelectInputBinding extends InputBinding {
find(scope: HTMLElement): JQuery<HTMLElement>;
@@ -32,6 +31,7 @@ declare class SelectInputBinding extends InputBinding {
unsubscribe(el: HTMLElement): void;
initialize(el: SelectHTMLElement): void;
protected _selectize(el: SelectHTMLElement, update?: boolean): SelectizeInfo | undefined;
private _addShinyRemoveButton;
}
export { SelectInputBinding };
export type { SelectInputReceiveMessageData };