Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
23 changes: 14 additions & 9 deletions inst/www/shared/shiny.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions inst/www/shared/shiny.js.map

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions inst/www/shared/shiny.min.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions inst/www/shared/shiny.min.js.map

Large diffs are not rendered by default.

23 changes: 19 additions & 4 deletions srcts/src/bindings/input/inputBinding.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
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.

class InputBinding {
name!: string;

Expand All @@ -26,10 +40,10 @@ 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: (value: SubscribeEventPriority) => void
): void {
// empty
el; // unused var
callback; // unused var
Expand Down Expand Up @@ -102,3 +116,4 @@ class InputBinding {
//// END NOTES FOR FUTURE DEV

export { InputBinding };
export type { SubscribeEventPriority };
32 changes: 18 additions & 14 deletions srcts/src/shiny/bind.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
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";
import type {
InputRateDecorator,
InputValidateDecorator,
} from "../inputPolicies";
import type { EventPriority } from "../inputPolicies/inputPolicy";
import { shinyAppBindOutput, shinyAppUnbindOutput } from "./initedMethods";
import { sendImageSizeFns } from "./sendImageSize";

Expand All @@ -27,7 +29,7 @@ function valueChangeCallback(
inputs: InputValidateDecorator,
binding: InputBinding,
el: HTMLElement,
allowDeferred: boolean
priority: EventPriority
) {
let id = binding.getId(el);

Expand All @@ -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 });
}
}

Expand Down Expand Up @@ -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);
};
})();

Expand Down
7 changes: 6 additions & 1 deletion srcts/types/src/bindings/input/inputBinding.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import type { EventPriority } from "../../inputPolicies/inputPolicy";
import type { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
import type { BindScope } from "../../shiny/bind";
type SubscribeEventPriority = EventPriority | boolean | {
priority: EventPriority;
};
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: (value: SubscribeEventPriority) => void): void;
unsubscribe(el: HTMLElement): void;
receiveMessage(el: HTMLElement, data: unknown): Promise<void> | void;
getState(el: HTMLElement): unknown;
Expand All @@ -18,3 +22,4 @@ declare class InputBinding {
dispose(el: HTMLElement): void;
}
export { InputBinding };
export type { SubscribeEventPriority };
Loading