mirror of
https://github.com/rstudio/shiny.git
synced 2026-04-29 03:00:45 -04: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 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
|
## 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)
|
* 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) {
|
function isJQuery(value) {
|
||||||
return Boolean(value && value.jquery);
|
return Boolean(value && value.jquery);
|
||||||
}
|
}
|
||||||
function valueChangeCallback(inputs, binding, el, allowDeferred) {
|
function valueChangeCallback(inputs, binding, el, priority) {
|
||||||
let id = binding.getId(el);
|
let id = binding.getId(el);
|
||||||
if (id) {
|
if (id) {
|
||||||
const value = binding.getValue(el);
|
const value = binding.getValue(el);
|
||||||
const type = binding.getType(el);
|
const type = binding.getType(el);
|
||||||
if (type)
|
if (type)
|
||||||
id = id + ":" + type;
|
id = id + ":" + type;
|
||||||
const opts = {
|
inputs.setInput(id, value, { priority, binding, el });
|
||||||
priority: allowDeferred ? "deferred" : "immediate",
|
|
||||||
binding,
|
|
||||||
el
|
|
||||||
};
|
|
||||||
inputs.setInput(id, value, opts);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var bindingsRegistry = (() => {
|
var bindingsRegistry = (() => {
|
||||||
@@ -5738,8 +5733,18 @@ ${duplicateIdMsg}`;
|
|||||||
const thisCallback = function() {
|
const thisCallback = function() {
|
||||||
const thisBinding = binding;
|
const thisBinding = binding;
|
||||||
const thisEl = el;
|
const thisEl = el;
|
||||||
return function(allowDeferred) {
|
return function(priority) {
|
||||||
valueChangeCallback(inputs, thisBinding, thisEl, allowDeferred);
|
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);
|
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 { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
|
||||||
import type { BindScope } from "../../shiny/bind";
|
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 {
|
class InputBinding {
|
||||||
name!: string;
|
name!: string;
|
||||||
|
|
||||||
@@ -26,10 +42,7 @@ class InputBinding {
|
|||||||
el; // unused var
|
el; // unused var
|
||||||
}
|
}
|
||||||
|
|
||||||
// The callback method takes one argument, whose value is boolean. If true,
|
subscribe(el: HTMLElement, callback: InputSubscribeCallback): void {
|
||||||
// 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 {
|
|
||||||
// empty
|
// empty
|
||||||
el; // unused var
|
el; // unused var
|
||||||
callback; // unused var
|
callback; // unused var
|
||||||
@@ -102,3 +115,4 @@ class InputBinding {
|
|||||||
//// END NOTES FOR FUTURE DEV
|
//// END NOTES FOR FUTURE DEV
|
||||||
|
|
||||||
export { InputBinding };
|
export { InputBinding };
|
||||||
|
export type { InputSubscribeCallback, SubscribeEventPriority };
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
import { Shiny } from "..";
|
import { Shiny } from "..";
|
||||||
import type { InputBinding, OutputBinding } from "../bindings";
|
import type { InputBinding, OutputBinding } from "../bindings";
|
||||||
|
import type { SubscribeEventPriority } from "../bindings/input/inputBinding";
|
||||||
import { OutputBindingAdapter } from "../bindings/outputAdapter";
|
import { OutputBindingAdapter } from "../bindings/outputAdapter";
|
||||||
import type { BindingRegistry } from "../bindings/registry";
|
import type { BindingRegistry } from "../bindings/registry";
|
||||||
import { ShinyClientMessageEvent } from "../components/errorConsole";
|
import { ShinyClientMessageEvent } from "../components/errorConsole";
|
||||||
@@ -8,6 +9,7 @@ import type {
|
|||||||
InputRateDecorator,
|
InputRateDecorator,
|
||||||
InputValidateDecorator,
|
InputValidateDecorator,
|
||||||
} from "../inputPolicies";
|
} from "../inputPolicies";
|
||||||
|
import type { EventPriority } from "../inputPolicies/inputPolicy";
|
||||||
import { shinyAppBindOutput, shinyAppUnbindOutput } from "./initedMethods";
|
import { shinyAppBindOutput, shinyAppUnbindOutput } from "./initedMethods";
|
||||||
import { sendImageSizeFns } from "./sendImageSize";
|
import { sendImageSizeFns } from "./sendImageSize";
|
||||||
|
|
||||||
@@ -27,7 +29,7 @@ function valueChangeCallback(
|
|||||||
inputs: InputValidateDecorator,
|
inputs: InputValidateDecorator,
|
||||||
binding: InputBinding,
|
binding: InputBinding,
|
||||||
el: HTMLElement,
|
el: HTMLElement,
|
||||||
allowDeferred: boolean
|
priority: EventPriority
|
||||||
) {
|
) {
|
||||||
let id = binding.getId(el);
|
let id = binding.getId(el);
|
||||||
|
|
||||||
@@ -37,17 +39,7 @@ function valueChangeCallback(
|
|||||||
|
|
||||||
if (type) id = id + ":" + type;
|
if (type) id = id + ":" + type;
|
||||||
|
|
||||||
const opts: {
|
inputs.setInput(id, value, { priority, binding, el });
|
||||||
priority: "deferred" | "immediate";
|
|
||||||
binding: typeof binding;
|
|
||||||
el: typeof el;
|
|
||||||
} = {
|
|
||||||
priority: allowDeferred ? "deferred" : "immediate",
|
|
||||||
binding: binding,
|
|
||||||
el: el,
|
|
||||||
};
|
|
||||||
|
|
||||||
inputs.setInput(id, value, opts);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,8 +264,20 @@ function bindInputs(
|
|||||||
const thisBinding = binding;
|
const thisBinding = binding;
|
||||||
const thisEl = el;
|
const thisEl = el;
|
||||||
|
|
||||||
return function (allowDeferred: boolean) {
|
return function (priority: SubscribeEventPriority) {
|
||||||
valueChangeCallback(inputs, thisBinding, thisEl, allowDeferred);
|
// 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 { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
|
||||||
import type { BindScope } from "../../shiny/bind";
|
import type { BindScope } from "../../shiny/bind";
|
||||||
|
type SubscribeEventPriority = EventPriority | boolean | {
|
||||||
|
priority: EventPriority;
|
||||||
|
};
|
||||||
|
type InputSubscribeCallback = (value: SubscribeEventPriority) => void;
|
||||||
declare class InputBinding {
|
declare class InputBinding {
|
||||||
name: string;
|
name: string;
|
||||||
find(scope: BindScope): JQuery<HTMLElement>;
|
find(scope: BindScope): JQuery<HTMLElement>;
|
||||||
getId(el: HTMLElement): string;
|
getId(el: HTMLElement): string;
|
||||||
getType(el: HTMLElement): string | null;
|
getType(el: HTMLElement): string | null;
|
||||||
getValue(el: HTMLElement): any;
|
getValue(el: HTMLElement): any;
|
||||||
subscribe(el: HTMLElement, callback: (value: boolean) => void): void;
|
subscribe(el: HTMLElement, callback: InputSubscribeCallback): void;
|
||||||
unsubscribe(el: HTMLElement): void;
|
unsubscribe(el: HTMLElement): void;
|
||||||
receiveMessage(el: HTMLElement, data: unknown): Promise<void> | void;
|
receiveMessage(el: HTMLElement, data: unknown): Promise<void> | void;
|
||||||
getState(el: HTMLElement): unknown;
|
getState(el: HTMLElement): unknown;
|
||||||
@@ -18,3 +23,4 @@ declare class InputBinding {
|
|||||||
dispose(el: HTMLElement): void;
|
dispose(el: HTMLElement): void;
|
||||||
}
|
}
|
||||||
export { InputBinding };
|
export { InputBinding };
|
||||||
|
export type { InputSubscribeCallback, SubscribeEventPriority };
|
||||||
|
|||||||
Reference in New Issue
Block a user