mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-09 15:08:04 -05:00
feat(InputBinding): subscribe callback now supports event priority (#4211)
* feat(InputBinding): subscribe callback now supports event priority * Update NEWS.md * Update srcts/src/shiny/bind.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * `yarn build` (GitHub Actions) * Simpler and more consistent typing * Support a suitable object as input * Provide a type for the callback itself, not just the valueit's given --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
This commit is contained in:
2
NEWS.md
2
NEWS.md
@@ -8,6 +8,8 @@
|
||||
|
||||
* The family of `update*Input()` functions can now render HTML content passed to the `label` argument (e.g., `updateInputText(label = tags$b("New label"))`). (#3996)
|
||||
|
||||
* The `callback` argument of Shiny.js' `InputBinding.subscribe()` method gains support for a value of `"event"`. This makes it possible for an input binding to use event priority when updating the value (i.e., send immediately and always resend, even if the value hasn't changed). (#4211)
|
||||
|
||||
## Changes
|
||||
|
||||
* Shiny no longer suspends input changes when _any_ `<input type="submit">` or `<button type="submit">` is on the page. Instead, it now only suspends when a `submitButton()` is present. If you have reason for creating a submit button from custom HTML, add a CSS class of `shiny-submit-button` to the button. (#4209)
|
||||
|
||||
@@ -5611,19 +5611,14 @@
|
||||
function isJQuery(value) {
|
||||
return Boolean(value && value.jquery);
|
||||
}
|
||||
function valueChangeCallback(inputs, binding, el, allowDeferred) {
|
||||
function valueChangeCallback(inputs, binding, el, priority) {
|
||||
let id = binding.getId(el);
|
||||
if (id) {
|
||||
const value = binding.getValue(el);
|
||||
const type = binding.getType(el);
|
||||
if (type)
|
||||
id = id + ":" + type;
|
||||
const opts = {
|
||||
priority: allowDeferred ? "deferred" : "immediate",
|
||||
binding,
|
||||
el
|
||||
};
|
||||
inputs.setInput(id, value, opts);
|
||||
inputs.setInput(id, value, { priority, binding, el });
|
||||
}
|
||||
}
|
||||
var bindingsRegistry = (() => {
|
||||
@@ -5738,8 +5733,18 @@ ${duplicateIdMsg}`;
|
||||
const thisCallback = function() {
|
||||
const thisBinding = binding;
|
||||
const thisEl = el;
|
||||
return function(allowDeferred) {
|
||||
valueChangeCallback(inputs, thisBinding, thisEl, allowDeferred);
|
||||
return function(priority) {
|
||||
let normalizedPriority;
|
||||
if (priority === true) {
|
||||
normalizedPriority = "deferred";
|
||||
} else if (priority === false) {
|
||||
normalizedPriority = "immediate";
|
||||
} else if (typeof priority === "object" && "priority" in priority) {
|
||||
normalizedPriority = priority.priority;
|
||||
} else {
|
||||
normalizedPriority = priority;
|
||||
}
|
||||
valueChangeCallback(inputs, thisBinding, thisEl, normalizedPriority);
|
||||
};
|
||||
}();
|
||||
binding.subscribe(el, thisCallback);
|
||||
|
||||
File diff suppressed because one or more lines are too long
24
inst/www/shared/shiny.min.js
vendored
24
inst/www/shared/shiny.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +1,22 @@
|
||||
import type { EventPriority } from "../../inputPolicies/inputPolicy";
|
||||
import type { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
|
||||
import type { BindScope } from "../../shiny/bind";
|
||||
|
||||
type SubscribeEventPriority =
|
||||
| EventPriority
|
||||
| boolean
|
||||
| { priority: EventPriority };
|
||||
// Historically, the .subscribe()'s callback value only took a boolean. In this
|
||||
// case:
|
||||
// * false: send value immediately (i.e., priority = "immediate")
|
||||
// * true: send value later (i.e., priority = "deferred")
|
||||
// * The input rate policy is also consulted on whether to debounce or
|
||||
// throttle
|
||||
// In recent versions, the value can also be "event", meaning that the
|
||||
// value should be sent immediately regardless of whether it has changed.
|
||||
|
||||
type InputSubscribeCallback = (value: SubscribeEventPriority) => void;
|
||||
|
||||
class InputBinding {
|
||||
name!: string;
|
||||
|
||||
@@ -26,10 +42,7 @@ class InputBinding {
|
||||
el; // unused var
|
||||
}
|
||||
|
||||
// The callback method takes one argument, whose value is boolean. If true,
|
||||
// allow deferred (debounce or throttle) sending depending on the value of
|
||||
// getRatePolicy. If false, send value immediately. Default behavior is `false`
|
||||
subscribe(el: HTMLElement, callback: (value: boolean) => void): void {
|
||||
subscribe(el: HTMLElement, callback: InputSubscribeCallback): void {
|
||||
// empty
|
||||
el; // unused var
|
||||
callback; // unused var
|
||||
@@ -102,3 +115,4 @@ class InputBinding {
|
||||
//// END NOTES FOR FUTURE DEV
|
||||
|
||||
export { InputBinding };
|
||||
export type { InputSubscribeCallback, SubscribeEventPriority };
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import $ from "jquery";
|
||||
import { Shiny } from "..";
|
||||
import type { InputBinding, OutputBinding } from "../bindings";
|
||||
import type { SubscribeEventPriority } from "../bindings/input/inputBinding";
|
||||
import { OutputBindingAdapter } from "../bindings/outputAdapter";
|
||||
import type { BindingRegistry } from "../bindings/registry";
|
||||
import { ShinyClientMessageEvent } from "../components/errorConsole";
|
||||
@@ -8,6 +9,7 @@ import type {
|
||||
InputRateDecorator,
|
||||
InputValidateDecorator,
|
||||
} from "../inputPolicies";
|
||||
import type { EventPriority } from "../inputPolicies/inputPolicy";
|
||||
import { shinyAppBindOutput, shinyAppUnbindOutput } from "./initedMethods";
|
||||
import { sendImageSizeFns } from "./sendImageSize";
|
||||
|
||||
@@ -27,7 +29,7 @@ function valueChangeCallback(
|
||||
inputs: InputValidateDecorator,
|
||||
binding: InputBinding,
|
||||
el: HTMLElement,
|
||||
allowDeferred: boolean
|
||||
priority: EventPriority
|
||||
) {
|
||||
let id = binding.getId(el);
|
||||
|
||||
@@ -37,17 +39,7 @@ function valueChangeCallback(
|
||||
|
||||
if (type) id = id + ":" + type;
|
||||
|
||||
const opts: {
|
||||
priority: "deferred" | "immediate";
|
||||
binding: typeof binding;
|
||||
el: typeof el;
|
||||
} = {
|
||||
priority: allowDeferred ? "deferred" : "immediate",
|
||||
binding: binding,
|
||||
el: el,
|
||||
};
|
||||
|
||||
inputs.setInput(id, value, opts);
|
||||
inputs.setInput(id, value, { priority, binding, el });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,8 +264,20 @@ function bindInputs(
|
||||
const thisBinding = binding;
|
||||
const thisEl = el;
|
||||
|
||||
return function (allowDeferred: boolean) {
|
||||
valueChangeCallback(inputs, thisBinding, thisEl, allowDeferred);
|
||||
return function (priority: SubscribeEventPriority) {
|
||||
// Narrow the type of priority to EventPriority
|
||||
let normalizedPriority: EventPriority;
|
||||
if (priority === true) {
|
||||
normalizedPriority = "deferred";
|
||||
} else if (priority === false) {
|
||||
normalizedPriority = "immediate";
|
||||
} else if (typeof priority === "object" && "priority" in priority) {
|
||||
normalizedPriority = priority.priority;
|
||||
} else {
|
||||
normalizedPriority = priority;
|
||||
}
|
||||
|
||||
valueChangeCallback(inputs, thisBinding, thisEl, normalizedPriority);
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import type { EventPriority } from "../../inputPolicies/inputPolicy";
|
||||
import type { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
|
||||
import type { BindScope } from "../../shiny/bind";
|
||||
type SubscribeEventPriority = EventPriority | boolean | {
|
||||
priority: EventPriority;
|
||||
};
|
||||
type InputSubscribeCallback = (value: SubscribeEventPriority) => void;
|
||||
declare class InputBinding {
|
||||
name: string;
|
||||
find(scope: BindScope): JQuery<HTMLElement>;
|
||||
getId(el: HTMLElement): string;
|
||||
getType(el: HTMLElement): string | null;
|
||||
getValue(el: HTMLElement): any;
|
||||
subscribe(el: HTMLElement, callback: (value: boolean) => void): void;
|
||||
subscribe(el: HTMLElement, callback: InputSubscribeCallback): void;
|
||||
unsubscribe(el: HTMLElement): void;
|
||||
receiveMessage(el: HTMLElement, data: unknown): Promise<void> | void;
|
||||
getState(el: HTMLElement): unknown;
|
||||
@@ -18,3 +23,4 @@ declare class InputBinding {
|
||||
dispose(el: HTMLElement): void;
|
||||
}
|
||||
export { InputBinding };
|
||||
export type { InputSubscribeCallback, SubscribeEventPriority };
|
||||
|
||||
Reference in New Issue
Block a user