Skip to content

Commit b25e6fe

Browse files
cpsievertCopilot
andauthored
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>
1 parent e6b22d8 commit b25e6fe

File tree

8 files changed

+76
-45
lines changed

8 files changed

+76
-45
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
* 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)
1010

11+
* 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)
12+
1113
## Changes
1214

1315
* 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)

inst/www/shared/shiny.js

Lines changed: 14 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

inst/www/shared/shiny.js.map

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

inst/www/shared/shiny.min.js

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

inst/www/shared/shiny.min.js.map

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

srcts/src/bindings/input/inputBinding.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
1+
import type { EventPriority } from "../../inputPolicies/inputPolicy";
12
import type { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
23
import type { BindScope } from "../../shiny/bind";
34

5+
type SubscribeEventPriority =
6+
| EventPriority
7+
| boolean
8+
| { priority: EventPriority };
9+
// Historically, the .subscribe()'s callback value only took a boolean. In this
10+
// case:
11+
// * false: send value immediately (i.e., priority = "immediate")
12+
// * true: send value later (i.e., priority = "deferred")
13+
// * The input rate policy is also consulted on whether to debounce or
14+
// throttle
15+
// In recent versions, the value can also be "event", meaning that the
16+
// value should be sent immediately regardless of whether it has changed.
17+
18+
type InputSubscribeCallback = (value: SubscribeEventPriority) => void;
19+
420
class InputBinding {
521
name!: string;
622

@@ -26,10 +42,7 @@ class InputBinding {
2642
el; // unused var
2743
}
2844

29-
// The callback method takes one argument, whose value is boolean. If true,
30-
// allow deferred (debounce or throttle) sending depending on the value of
31-
// getRatePolicy. If false, send value immediately. Default behavior is `false`
32-
subscribe(el: HTMLElement, callback: (value: boolean) => void): void {
45+
subscribe(el: HTMLElement, callback: InputSubscribeCallback): void {
3346
// empty
3447
el; // unused var
3548
callback; // unused var
@@ -102,3 +115,4 @@ class InputBinding {
102115
//// END NOTES FOR FUTURE DEV
103116

104117
export { InputBinding };
118+
export type { InputSubscribeCallback, SubscribeEventPriority };

srcts/src/shiny/bind.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import $ from "jquery";
22
import { Shiny } from "..";
33
import type { InputBinding, OutputBinding } from "../bindings";
4+
import type { SubscribeEventPriority } from "../bindings/input/inputBinding";
45
import { OutputBindingAdapter } from "../bindings/outputAdapter";
56
import type { BindingRegistry } from "../bindings/registry";
67
import { ShinyClientMessageEvent } from "../components/errorConsole";
78
import type {
89
InputRateDecorator,
910
InputValidateDecorator,
1011
} from "../inputPolicies";
12+
import type { EventPriority } from "../inputPolicies/inputPolicy";
1113
import { shinyAppBindOutput, shinyAppUnbindOutput } from "./initedMethods";
1214
import { sendImageSizeFns } from "./sendImageSize";
1315

@@ -27,7 +29,7 @@ function valueChangeCallback(
2729
inputs: InputValidateDecorator,
2830
binding: InputBinding,
2931
el: HTMLElement,
30-
allowDeferred: boolean
32+
priority: EventPriority
3133
) {
3234
let id = binding.getId(el);
3335

@@ -37,17 +39,7 @@ function valueChangeCallback(
3739

3840
if (type) id = id + ":" + type;
3941

40-
const opts: {
41-
priority: "deferred" | "immediate";
42-
binding: typeof binding;
43-
el: typeof el;
44-
} = {
45-
priority: allowDeferred ? "deferred" : "immediate",
46-
binding: binding,
47-
el: el,
48-
};
49-
50-
inputs.setInput(id, value, opts);
42+
inputs.setInput(id, value, { priority, binding, el });
5143
}
5244
}
5345

@@ -272,8 +264,20 @@ function bindInputs(
272264
const thisBinding = binding;
273265
const thisEl = el;
274266

275-
return function (allowDeferred: boolean) {
276-
valueChangeCallback(inputs, thisBinding, thisEl, allowDeferred);
267+
return function (priority: SubscribeEventPriority) {
268+
// Narrow the type of priority to EventPriority
269+
let normalizedPriority: EventPriority;
270+
if (priority === true) {
271+
normalizedPriority = "deferred";
272+
} else if (priority === false) {
273+
normalizedPriority = "immediate";
274+
} else if (typeof priority === "object" && "priority" in priority) {
275+
normalizedPriority = priority.priority;
276+
} else {
277+
normalizedPriority = priority;
278+
}
279+
280+
valueChangeCallback(inputs, thisBinding, thisEl, normalizedPriority);
277281
};
278282
})();
279283

srcts/types/src/bindings/input/inputBinding.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1+
import type { EventPriority } from "../../inputPolicies/inputPolicy";
12
import type { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
23
import type { BindScope } from "../../shiny/bind";
4+
type SubscribeEventPriority = EventPriority | boolean | {
5+
priority: EventPriority;
6+
};
7+
type InputSubscribeCallback = (value: SubscribeEventPriority) => void;
38
declare class InputBinding {
49
name: string;
510
find(scope: BindScope): JQuery<HTMLElement>;
611
getId(el: HTMLElement): string;
712
getType(el: HTMLElement): string | null;
813
getValue(el: HTMLElement): any;
9-
subscribe(el: HTMLElement, callback: (value: boolean) => void): void;
14+
subscribe(el: HTMLElement, callback: InputSubscribeCallback): void;
1015
unsubscribe(el: HTMLElement): void;
1116
receiveMessage(el: HTMLElement, data: unknown): Promise<void> | void;
1217
getState(el: HTMLElement): unknown;
@@ -18,3 +23,4 @@ declare class InputBinding {
1823
dispose(el: HTMLElement): void;
1924
}
2025
export { InputBinding };
26+
export type { InputSubscribeCallback, SubscribeEventPriority };

0 commit comments

Comments
 (0)