mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-10 23:48:01 -05:00
Compare commits
25 Commits
v1.11.0
...
init-promi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da68eb6161 | ||
|
|
dedaf6808e | ||
|
|
3b926fd0c9 | ||
|
|
88a8fa70c7 | ||
|
|
d1e5e3c58f | ||
|
|
7575a92a76 | ||
|
|
cb1bc10461 | ||
|
|
9c7937b3df | ||
|
|
83d9cae6cc | ||
|
|
def783fe3e | ||
|
|
d141faa18d | ||
|
|
8240ebea26 | ||
|
|
0c3af7b8bf | ||
|
|
a2b7207816 | ||
|
|
18660c39c8 | ||
|
|
c54ce06b75 | ||
|
|
8de3737a87 | ||
|
|
98b76b5fa7 | ||
|
|
97f25443ef | ||
|
|
bacdb3204a | ||
|
|
faa0edb899 | ||
|
|
aa973c2d64 | ||
|
|
dcecef2841 | ||
|
|
f18b598ed7 | ||
|
|
13138ffa98 |
@@ -35,10 +35,6 @@ rules:
|
||||
|
||||
default-case:
|
||||
- error
|
||||
indent:
|
||||
- error
|
||||
- 2
|
||||
- SwitchCase: 1
|
||||
linebreak-style:
|
||||
- error
|
||||
- unix
|
||||
|
||||
4
NEWS.md
4
NEWS.md
@@ -2,6 +2,10 @@
|
||||
|
||||
## New features and improvements
|
||||
|
||||
* The client-side TypeScript code for Shiny has been refactored so that the `Shiny` object is now an instance of class `ShinyClass`. (#4063)
|
||||
|
||||
* In TypeScript, the `Shiny` object has a new property `initializedPromise`, which is a Promise-like object that can be `await`ed or chained with `.then()`. This Promise-like object corresponds to the `shiny:sessioninitialized` JavaScript event, but is easier to use because it can be used both before and after the events have occurred. (#4063)
|
||||
|
||||
* Added new functions, `useBusyIndicators()` and `busyIndicatorOptions()`, for enabling and customizing busy indication. Busy indicators provide a visual cue to users when the server is busy calculating outputs or otherwise serving requests to the client. When enabled, a spinner is shown on each calculating/recalculating output, and a pulsing banner is shown at the top of the page when the app is otherwise busy. (#4040)
|
||||
|
||||
* Output bindings now include the `.recalculating` CSS class when they are first bound, up until the first render. This makes it possible/easier to show progress indication when the output is calculating for the first time. (#4039)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
2
inst/www/shared/shiny.min.js
vendored
2
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
@@ -2,19 +2,12 @@
|
||||
// Project: Shiny <https://shiny.rstudio.com/>
|
||||
// Definitions by: RStudio <https://www.rstudio.com/>
|
||||
|
||||
import type { Shiny as RStudioShiny } from "../src/shiny/index";
|
||||
import type { ShinyClass } from "../src/shiny/index";
|
||||
|
||||
declare global {
|
||||
// Tell Shiny variable globally exists
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const Shiny: RStudioShiny;
|
||||
|
||||
// Tell window.Shiny exists
|
||||
interface Window {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
Shiny: RStudioShiny;
|
||||
Shiny: ShinyClass;
|
||||
}
|
||||
|
||||
// Make `Shiny` a globally available type definition. (No need to import the type)
|
||||
type Shiny = RStudioShiny;
|
||||
}
|
||||
|
||||
@@ -18,11 +18,10 @@ import { TextInputBinding } from "./text";
|
||||
import { TextareaInputBinding } from "./textarea";
|
||||
|
||||
// TODO-barret make this an init method
|
||||
type InitInputBindings = {
|
||||
function initInputBindings(): {
|
||||
inputBindings: BindingRegistry<InputBinding>;
|
||||
fileInputBinding: FileInputBinding;
|
||||
};
|
||||
function initInputBindings(): InitInputBindings {
|
||||
} {
|
||||
const inputBindings = new BindingRegistry<InputBinding>();
|
||||
|
||||
inputBindings.register(new TextInputBinding(), "shiny.textInput");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { Shiny } from "..";
|
||||
import { ShinyClientError } from "../shiny/error";
|
||||
|
||||
const buttonStyles = css`
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { init } from "./initialize";
|
||||
export { Shiny, type ShinyClass } from "./initialize";
|
||||
|
||||
init();
|
||||
|
||||
@@ -2,15 +2,20 @@ import { determineBrowserInfo } from "./browser";
|
||||
import { disableFormSubmission } from "./disableForm";
|
||||
import { trackHistory } from "./history";
|
||||
|
||||
import { setShiny } from "../shiny";
|
||||
import { ShinyClass } from "../shiny";
|
||||
import { setUserAgent } from "../utils/userAgent";
|
||||
import { windowShiny } from "../window/libraries";
|
||||
import { windowUserAgent } from "../window/userAgent";
|
||||
|
||||
import { initReactlog } from "../shiny/reactlog";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
let Shiny: ShinyClass;
|
||||
|
||||
function init(): void {
|
||||
setShiny(windowShiny());
|
||||
if (window.Shiny) {
|
||||
throw new Error("Trying to create window.Shiny, but it already exists!");
|
||||
}
|
||||
Shiny = window.Shiny = new ShinyClass();
|
||||
setUserAgent(windowUserAgent()); // before determineBrowserInfo()
|
||||
|
||||
determineBrowserInfo();
|
||||
@@ -21,4 +26,4 @@ function init(): void {
|
||||
initReactlog();
|
||||
}
|
||||
|
||||
export { init };
|
||||
export { init, Shiny, type ShinyClass };
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import $ from "jquery";
|
||||
import { Shiny } from "..";
|
||||
import type { InputBinding, OutputBinding } from "../bindings";
|
||||
import { OutputBindingAdapter } from "../bindings/outputAdapter";
|
||||
import type { BindingRegistry } from "../bindings/registry";
|
||||
|
||||
@@ -3,10 +3,33 @@ import $ from "jquery";
|
||||
import { InputBinding, OutputBinding } from "../bindings";
|
||||
import { initInputBindings } from "../bindings/input";
|
||||
import { initOutputBindings } from "../bindings/output";
|
||||
import type { BindingRegistry } from "../bindings/registry";
|
||||
import { showErrorInClientConsole } from "../components/errorConsole";
|
||||
import { resetBrush } from "../imageutils/resetBrush";
|
||||
import { $escape, compareVersion } from "../utils";
|
||||
import { initShiny } from "./init";
|
||||
import type { InputPolicy } from "../inputPolicies";
|
||||
import {
|
||||
InputBatchSender,
|
||||
InputDeferDecorator,
|
||||
InputEventDecorator,
|
||||
InputNoResendDecorator,
|
||||
InputRateDecorator,
|
||||
InputValidateDecorator,
|
||||
} from "../inputPolicies";
|
||||
import type { InputPolicyOpts } from "../inputPolicies/inputPolicy";
|
||||
import { addDefaultInputOpts } from "../inputPolicies/inputValidateDecorator";
|
||||
import { debounce, Debouncer } from "../time";
|
||||
import {
|
||||
$escape,
|
||||
compareVersion,
|
||||
getComputedLinkColor,
|
||||
getStyle,
|
||||
hasDefinedProperty,
|
||||
mapValues,
|
||||
pixelRatio,
|
||||
} from "../utils";
|
||||
import { createInitStatus, type InitStatusPromise } from "../utils/promise";
|
||||
import type { BindInputsCtx, BindScope } from "./bind";
|
||||
import { bindAll, unbindAll, _bindAll } from "./bind";
|
||||
import type {
|
||||
shinyBindAll,
|
||||
shinyForgetLastInputValue,
|
||||
@@ -14,11 +37,12 @@ import type {
|
||||
shinySetInputValue,
|
||||
shinyUnbindAll,
|
||||
} from "./initedMethods";
|
||||
import { setFileInputBinding } from "./initedMethods";
|
||||
import { setFileInputBinding, setShinyObj } from "./initedMethods";
|
||||
import { removeModal, showModal } from "./modal";
|
||||
import { removeNotification, showNotification } from "./notifications";
|
||||
import { hideReconnectDialog, showReconnectDialog } from "./reconnectDialog";
|
||||
import {
|
||||
registerDependency,
|
||||
renderContent,
|
||||
renderContentAsync,
|
||||
renderDependencies,
|
||||
@@ -26,17 +50,18 @@ import {
|
||||
renderHtml,
|
||||
renderHtmlAsync,
|
||||
} from "./render";
|
||||
import type { Handler, ShinyApp } from "./shinyapp";
|
||||
import { addCustomMessageHandler } from "./shinyapp";
|
||||
import { sendImageSizeFns } from "./sendImageSize";
|
||||
import { addCustomMessageHandler, ShinyApp, type Handler } from "./shinyapp";
|
||||
import { registerNames as singletonsRegisterNames } from "./singletons";
|
||||
|
||||
interface Shiny {
|
||||
class ShinyClass {
|
||||
version: string;
|
||||
$escape: typeof $escape;
|
||||
compareVersion: typeof compareVersion;
|
||||
inputBindings: ReturnType<typeof initInputBindings>["inputBindings"];
|
||||
inputBindings: BindingRegistry<InputBinding>;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
InputBinding: typeof InputBinding;
|
||||
outputBindings: ReturnType<typeof initOutputBindings>["outputBindings"];
|
||||
outputBindings: BindingRegistry<OutputBinding>;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
OutputBinding: typeof OutputBinding;
|
||||
resetBrush: typeof resetBrush;
|
||||
@@ -45,7 +70,6 @@ interface Shiny {
|
||||
remove: typeof removeNotification;
|
||||
};
|
||||
modal: { show: typeof showModal; remove: typeof removeModal };
|
||||
createSocket?: () => WebSocket;
|
||||
showReconnectDialog: typeof showReconnectDialog;
|
||||
hideReconnectDialog: typeof hideReconnectDialog;
|
||||
renderDependenciesAsync: typeof renderDependenciesAsync;
|
||||
@@ -54,9 +78,12 @@ interface Shiny {
|
||||
renderContent: typeof renderContent;
|
||||
renderHtmlAsync: typeof renderHtmlAsync;
|
||||
renderHtml: typeof renderHtml;
|
||||
user: string;
|
||||
progressHandlers?: ShinyApp["progressHandlers"];
|
||||
addCustomMessageHandler: typeof addCustomMessageHandler;
|
||||
|
||||
// The following are added in the initialization, by initShiny()
|
||||
createSocket?: () => WebSocket;
|
||||
user?: string;
|
||||
progressHandlers?: ShinyApp["progressHandlers"];
|
||||
shinyapp?: ShinyApp;
|
||||
setInputValue?: typeof shinySetInputValue;
|
||||
onInputChange?: typeof shinySetInputValue;
|
||||
@@ -65,77 +92,629 @@ interface Shiny {
|
||||
unbindAll?: typeof shinyUnbindAll;
|
||||
initializeInputs?: typeof shinyInitializeInputs;
|
||||
|
||||
// Promise-like object that is resolved after initialization.
|
||||
initializedPromise: InitStatusPromise<void>;
|
||||
|
||||
// Eventually deprecate
|
||||
// For old-style custom messages - should deprecate and migrate to new
|
||||
oncustommessage?: Handler;
|
||||
|
||||
constructor() {
|
||||
// `process.env.SHINY_VERSION` is overwritten to the Shiny version at build time.
|
||||
// During testing, the `Shiny.version` will be `"development"`
|
||||
this.version = process.env.SHINY_VERSION || "development";
|
||||
|
||||
const { inputBindings, fileInputBinding } = initInputBindings();
|
||||
const { outputBindings } = initOutputBindings();
|
||||
|
||||
setFileInputBinding(fileInputBinding);
|
||||
|
||||
this.$escape = $escape;
|
||||
this.compareVersion = compareVersion;
|
||||
this.inputBindings = inputBindings;
|
||||
this.InputBinding = InputBinding;
|
||||
this.outputBindings = outputBindings;
|
||||
this.OutputBinding = OutputBinding;
|
||||
this.resetBrush = resetBrush;
|
||||
this.notifications = {
|
||||
show: showNotification,
|
||||
remove: removeNotification,
|
||||
};
|
||||
this.modal = { show: showModal, remove: removeModal };
|
||||
|
||||
this.addCustomMessageHandler = addCustomMessageHandler;
|
||||
this.showReconnectDialog = showReconnectDialog;
|
||||
this.hideReconnectDialog = hideReconnectDialog;
|
||||
this.renderDependenciesAsync = renderDependenciesAsync;
|
||||
this.renderDependencies = renderDependencies;
|
||||
this.renderContentAsync = renderContentAsync;
|
||||
this.renderContent = renderContent;
|
||||
this.renderHtmlAsync = renderHtmlAsync;
|
||||
this.renderHtml = renderHtml;
|
||||
|
||||
this.initializedPromise = createInitStatus<void>();
|
||||
|
||||
$(() => {
|
||||
// Init Shiny a little later than document ready, so user code can
|
||||
// run first (i.e. to register bindings)
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await this.initialize();
|
||||
} catch (e) {
|
||||
showErrorInClientConsole(e);
|
||||
throw e;
|
||||
}
|
||||
}, 1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to check if Shiny is running in development mode. By packaging as a
|
||||
* method, we can we can avoid needing to look for the `__SHINY_DEV_MODE__`
|
||||
* variable in the global scope.
|
||||
* @returns `true` if Shiny is running in development mode, `false` otherwise.
|
||||
*/
|
||||
inDevMode: () => boolean;
|
||||
}
|
||||
|
||||
let windowShiny: Shiny;
|
||||
|
||||
function setShiny(windowShiny_: Shiny): void {
|
||||
windowShiny = windowShiny_;
|
||||
|
||||
// `process.env.SHINY_VERSION` is overwritten to the Shiny version at build time.
|
||||
// During testing, the `Shiny.version` will be `"development"`
|
||||
windowShiny.version = process.env.SHINY_VERSION || "development";
|
||||
|
||||
const { inputBindings, fileInputBinding } = initInputBindings();
|
||||
const { outputBindings } = initOutputBindings();
|
||||
|
||||
// set variable to be retrieved later
|
||||
setFileInputBinding(fileInputBinding);
|
||||
|
||||
windowShiny.$escape = $escape;
|
||||
windowShiny.compareVersion = compareVersion;
|
||||
windowShiny.inputBindings = inputBindings;
|
||||
windowShiny.InputBinding = InputBinding;
|
||||
windowShiny.outputBindings = outputBindings;
|
||||
windowShiny.OutputBinding = OutputBinding;
|
||||
windowShiny.resetBrush = resetBrush;
|
||||
windowShiny.notifications = {
|
||||
show: showNotification,
|
||||
remove: removeNotification,
|
||||
};
|
||||
windowShiny.modal = { show: showModal, remove: removeModal };
|
||||
|
||||
windowShiny.addCustomMessageHandler = addCustomMessageHandler;
|
||||
windowShiny.showReconnectDialog = showReconnectDialog;
|
||||
windowShiny.hideReconnectDialog = hideReconnectDialog;
|
||||
windowShiny.renderDependenciesAsync = renderDependenciesAsync;
|
||||
windowShiny.renderDependencies = renderDependencies;
|
||||
windowShiny.renderContentAsync = renderContentAsync;
|
||||
windowShiny.renderContent = renderContent;
|
||||
windowShiny.renderHtmlAsync = renderHtmlAsync;
|
||||
windowShiny.renderHtml = renderHtml;
|
||||
|
||||
windowShiny.inDevMode = () => {
|
||||
inDevMode(): boolean {
|
||||
if ("__SHINY_DEV_MODE__" in window)
|
||||
return Boolean(window.__SHINY_DEV_MODE__);
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
$(function () {
|
||||
// Init Shiny a little later than document ready, so user code can
|
||||
// run first (i.e. to register bindings)
|
||||
setTimeout(async function () {
|
||||
try {
|
||||
await initShiny(windowShiny);
|
||||
} catch (e) {
|
||||
showErrorInClientConsole(e);
|
||||
throw e;
|
||||
async initialize(): Promise<void> {
|
||||
setShinyObj(this);
|
||||
this.shinyapp = new ShinyApp();
|
||||
const shinyapp = this.shinyapp;
|
||||
|
||||
this.progressHandlers = shinyapp.progressHandlers;
|
||||
|
||||
const inputBatchSender = new InputBatchSender(shinyapp);
|
||||
const inputsNoResend = new InputNoResendDecorator(inputBatchSender);
|
||||
const inputsEvent = new InputEventDecorator(inputsNoResend);
|
||||
const inputsRate = new InputRateDecorator(inputsEvent);
|
||||
const inputsDefer = new InputDeferDecorator(inputsEvent);
|
||||
|
||||
let target: InputPolicy;
|
||||
|
||||
if ($('input[type="submit"], button[type="submit"]').length > 0) {
|
||||
// If there is a submit button on the page, use defer decorator
|
||||
target = inputsDefer;
|
||||
|
||||
$('input[type="submit"], button[type="submit"]').each(function () {
|
||||
$(this).click(function (event) {
|
||||
event.preventDefault();
|
||||
inputsDefer.submit();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// By default, use rate decorator
|
||||
target = inputsRate;
|
||||
}
|
||||
|
||||
const inputs = new InputValidateDecorator(target);
|
||||
|
||||
this.setInputValue = this.onInputChange = function (
|
||||
name: string,
|
||||
value: unknown,
|
||||
opts: Partial<InputPolicyOpts> = {}
|
||||
): void {
|
||||
const newOpts = addDefaultInputOpts(opts);
|
||||
|
||||
inputs.setInput(name, value, newOpts);
|
||||
};
|
||||
|
||||
// By default, Shiny deduplicates input value changes; that is, if
|
||||
// `setInputValue` is called with the same value as the input already
|
||||
// has, the call is ignored (unless opts.priority = "event"). Calling
|
||||
// `forgetLastInputValue` tells Shiny that the very next call to
|
||||
// `setInputValue` for this input id shouldn't be ignored, even if it
|
||||
// is a dupe of the existing value.
|
||||
this.forgetLastInputValue = function (name) {
|
||||
inputsNoResend.forget(name);
|
||||
};
|
||||
|
||||
// MUST be called after `setShiny()`
|
||||
const inputBindings = this.inputBindings;
|
||||
const outputBindings = this.outputBindings;
|
||||
|
||||
function shinyBindCtx(): BindInputsCtx {
|
||||
return {
|
||||
inputs,
|
||||
inputsRate,
|
||||
sendOutputHiddenState,
|
||||
maybeAddThemeObserver,
|
||||
inputBindings,
|
||||
outputBindings,
|
||||
initDeferredIframes,
|
||||
};
|
||||
}
|
||||
|
||||
this.bindAll = async function (scope: BindScope) {
|
||||
await bindAll(shinyBindCtx(), scope);
|
||||
};
|
||||
this.unbindAll = function (scope: BindScope, includeSelf = false) {
|
||||
unbindAll(shinyBindCtx(), scope, includeSelf);
|
||||
};
|
||||
|
||||
// Calls .initialize() for all of the input objects in all input bindings,
|
||||
// in the given scope.
|
||||
function initializeInputs(scope: BindScope = document.documentElement) {
|
||||
const bindings = inputBindings.getBindings();
|
||||
|
||||
// Iterate over all bindings
|
||||
for (let i = 0; i < bindings.length; i++) {
|
||||
const binding = bindings[i].binding;
|
||||
const inputObjects = binding.find(scope);
|
||||
|
||||
if (inputObjects) {
|
||||
// Iterate over all input objects for this binding
|
||||
for (let j = 0; j < inputObjects.length; j++) {
|
||||
const $inputObjectJ = $(inputObjects[j]);
|
||||
|
||||
if (!$inputObjectJ.data("_shiny_initialized")) {
|
||||
$inputObjectJ.data("_shiny_initialized", true);
|
||||
binding.initialize(inputObjects[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
this.initializeInputs = initializeInputs;
|
||||
|
||||
function getIdFromEl(el: HTMLElement) {
|
||||
const $el = $(el);
|
||||
const bindingAdapter = $el.data("shiny-output-binding");
|
||||
|
||||
if (!bindingAdapter) return null;
|
||||
else return bindingAdapter.getId();
|
||||
}
|
||||
|
||||
// Initialize all input objects in the document, before binding
|
||||
initializeInputs(document.documentElement);
|
||||
|
||||
// The input values returned by _bindAll() each have a structure like this:
|
||||
// { value: 123, opts: { ... } }
|
||||
// We want to only keep the value. This is because when the initialValues is
|
||||
// passed to ShinyApp.connect(), the ShinyApp object stores the
|
||||
// initialValues object for the duration of the session, and the opts may
|
||||
// have a reference to the DOM element, which would prevent it from being
|
||||
// GC'd.
|
||||
const initialValues = mapValues(
|
||||
await _bindAll(shinyBindCtx(), document.documentElement),
|
||||
(x) => x.value
|
||||
);
|
||||
|
||||
// The server needs to know the size of each image and plot output element,
|
||||
// in case it is auto-sizing
|
||||
$(".shiny-image-output, .shiny-plot-output, .shiny-report-size").each(
|
||||
function () {
|
||||
const id = getIdFromEl(this),
|
||||
rect = this.getBoundingClientRect();
|
||||
|
||||
if (rect.width !== 0 || rect.height !== 0) {
|
||||
initialValues[".clientdata_output_" + id + "_width"] = rect.width;
|
||||
initialValues[".clientdata_output_" + id + "_height"] = rect.height;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function getComputedBgColor(
|
||||
el: HTMLElement | null
|
||||
): string | null | undefined {
|
||||
if (!el) {
|
||||
// Top of document, can't recurse further
|
||||
return null;
|
||||
}
|
||||
|
||||
const bgColor = getStyle(el, "background-color");
|
||||
|
||||
if (!bgColor) return bgColor;
|
||||
const m = bgColor.match(
|
||||
/^rgba\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/
|
||||
);
|
||||
|
||||
if (bgColor === "transparent" || (m && parseFloat(m[4]) === 0)) {
|
||||
// No background color on this element. See if it has a background image.
|
||||
const bgImage = getStyle(el, "background-image");
|
||||
|
||||
if (bgImage && bgImage !== "none") {
|
||||
// Failed to detect background color, since it has a background image
|
||||
return null;
|
||||
} else {
|
||||
// Recurse
|
||||
return getComputedBgColor(el.parentElement);
|
||||
}
|
||||
}
|
||||
return bgColor;
|
||||
}
|
||||
|
||||
function getComputedFont(el: HTMLElement) {
|
||||
const fontFamily = getStyle(el, "font-family");
|
||||
const fontSize = getStyle(el, "font-size");
|
||||
|
||||
return {
|
||||
families: fontFamily?.replace(/"/g, "").split(", "),
|
||||
size: fontSize,
|
||||
};
|
||||
}
|
||||
|
||||
$(".shiny-image-output, .shiny-plot-output, .shiny-report-theme").each(
|
||||
function () {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const el = this;
|
||||
const id = getIdFromEl(el);
|
||||
|
||||
initialValues[".clientdata_output_" + id + "_bg"] =
|
||||
getComputedBgColor(el);
|
||||
initialValues[".clientdata_output_" + id + "_fg"] = getStyle(
|
||||
el,
|
||||
"color"
|
||||
);
|
||||
initialValues[".clientdata_output_" + id + "_accent"] =
|
||||
getComputedLinkColor(el);
|
||||
initialValues[".clientdata_output_" + id + "_font"] =
|
||||
getComputedFont(el);
|
||||
maybeAddThemeObserver(el);
|
||||
}
|
||||
);
|
||||
|
||||
// Resend computed styles if *an output element's* class or style attribute changes.
|
||||
// This gives us some level of confidence that getCurrentOutputInfo() will be
|
||||
// properly invalidated if output container is mutated; but unfortunately,
|
||||
// we don't have a reasonable way to detect change in *inherited* styles
|
||||
// (other than session$setCurrentTheme())
|
||||
// https://github.com/rstudio/shiny/issues/3196
|
||||
// https://github.com/rstudio/shiny/issues/2998
|
||||
function maybeAddThemeObserver(el: HTMLElement): void {
|
||||
if (!window.MutationObserver) {
|
||||
return; // IE10 and lower
|
||||
}
|
||||
|
||||
const cl = el.classList;
|
||||
const reportTheme =
|
||||
cl.contains("shiny-image-output") ||
|
||||
cl.contains("shiny-plot-output") ||
|
||||
cl.contains("shiny-report-theme");
|
||||
|
||||
if (!reportTheme) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $el = $(el);
|
||||
|
||||
if ($el.data("shiny-theme-observer")) {
|
||||
return; // i.e., observer is already observing
|
||||
}
|
||||
|
||||
const observerCallback = new Debouncer(null, () => doSendTheme(el), 100);
|
||||
const observer = new MutationObserver(() =>
|
||||
observerCallback.normalCall()
|
||||
);
|
||||
const config = { attributes: true, attributeFilter: ["style", "class"] };
|
||||
|
||||
observer.observe(el, config);
|
||||
$el.data("shiny-theme-observer", observer);
|
||||
}
|
||||
|
||||
function doSendTheme(el: HTMLElement): void {
|
||||
// Sending theme info on error isn't necessary (it'd add an unnecessary additional round-trip)
|
||||
if (el.classList.contains("shiny-output-error")) {
|
||||
return;
|
||||
}
|
||||
const id = getIdFromEl(el);
|
||||
|
||||
inputs.setInput(
|
||||
".clientdata_output_" + id + "_bg",
|
||||
getComputedBgColor(el)
|
||||
);
|
||||
inputs.setInput(
|
||||
".clientdata_output_" + id + "_fg",
|
||||
getStyle(el, "color")
|
||||
);
|
||||
inputs.setInput(
|
||||
".clientdata_output_" + id + "_accent",
|
||||
getComputedLinkColor(el)
|
||||
);
|
||||
inputs.setInput(
|
||||
".clientdata_output_" + id + "_font",
|
||||
getComputedFont(el)
|
||||
);
|
||||
}
|
||||
|
||||
function doSendImageSize() {
|
||||
$(".shiny-image-output, .shiny-plot-output, .shiny-report-size").each(
|
||||
function () {
|
||||
const id = getIdFromEl(this),
|
||||
rect = this.getBoundingClientRect();
|
||||
|
||||
if (rect.width !== 0 || rect.height !== 0) {
|
||||
inputs.setInput(".clientdata_output_" + id + "_width", rect.width);
|
||||
inputs.setInput(
|
||||
".clientdata_output_" + id + "_height",
|
||||
rect.height
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$(".shiny-image-output, .shiny-plot-output, .shiny-report-theme").each(
|
||||
function () {
|
||||
doSendTheme(this);
|
||||
}
|
||||
);
|
||||
|
||||
$(".shiny-bound-output").each(function () {
|
||||
const $this = $(this),
|
||||
binding = $this.data("shiny-output-binding");
|
||||
|
||||
$this.trigger({
|
||||
type: "shiny:visualchange",
|
||||
// @ts-expect-error; Can not remove info on a established, malformed Event object
|
||||
visible: !isHidden(this),
|
||||
binding: binding,
|
||||
});
|
||||
binding.onResize();
|
||||
});
|
||||
}
|
||||
|
||||
sendImageSizeFns.setImageSend(inputBatchSender, doSendImageSize);
|
||||
|
||||
// Return true if the object or one of its ancestors in the DOM tree has
|
||||
// style='display:none'; otherwise return false.
|
||||
function isHidden(obj: HTMLElement | null): boolean {
|
||||
// null means we've hit the top of the tree. If width or height is
|
||||
// non-zero, then we know that no ancestor has display:none.
|
||||
if (obj === null || obj.offsetWidth !== 0 || obj.offsetHeight !== 0) {
|
||||
return false;
|
||||
} else if (getStyle(obj, "display") === "none") {
|
||||
return true;
|
||||
} else {
|
||||
return isHidden(obj.parentNode as HTMLElement | null);
|
||||
}
|
||||
}
|
||||
let lastKnownVisibleOutputs: { [key: string]: boolean } = {};
|
||||
// Set initial state of outputs to hidden, if needed
|
||||
|
||||
$(".shiny-bound-output").each(function () {
|
||||
const id = getIdFromEl(this);
|
||||
|
||||
if (isHidden(this)) {
|
||||
initialValues[".clientdata_output_" + id + "_hidden"] = true;
|
||||
} else {
|
||||
lastKnownVisibleOutputs[id] = true;
|
||||
initialValues[".clientdata_output_" + id + "_hidden"] = false;
|
||||
}
|
||||
});
|
||||
// Send update when hidden state changes
|
||||
function doSendOutputHiddenState() {
|
||||
const visibleOutputs: { [key: string]: boolean } = {};
|
||||
|
||||
$(".shiny-bound-output").each(function () {
|
||||
const id = getIdFromEl(this);
|
||||
|
||||
delete lastKnownVisibleOutputs[id];
|
||||
// Assume that the object is hidden when width and height are 0
|
||||
const hidden = isHidden(this),
|
||||
evt = {
|
||||
type: "shiny:visualchange",
|
||||
visible: !hidden,
|
||||
};
|
||||
|
||||
if (hidden) {
|
||||
inputs.setInput(".clientdata_output_" + id + "_hidden", true);
|
||||
} else {
|
||||
visibleOutputs[id] = true;
|
||||
inputs.setInput(".clientdata_output_" + id + "_hidden", false);
|
||||
}
|
||||
const $this = $(this);
|
||||
|
||||
// @ts-expect-error; Can not remove info on a established, malformed Event object
|
||||
evt.binding = $this.data("shiny-output-binding");
|
||||
// @ts-expect-error; Can not remove info on a established, malformed Event object
|
||||
$this.trigger(evt);
|
||||
});
|
||||
// Anything left in lastKnownVisibleOutputs is orphaned
|
||||
for (const name in lastKnownVisibleOutputs) {
|
||||
if (hasDefinedProperty(lastKnownVisibleOutputs, name))
|
||||
inputs.setInput(".clientdata_output_" + name + "_hidden", true);
|
||||
}
|
||||
// Update the visible outputs for next time
|
||||
lastKnownVisibleOutputs = visibleOutputs;
|
||||
}
|
||||
// sendOutputHiddenState gets called each time DOM elements are shown or
|
||||
// hidden. This can be in the hundreds or thousands of times at startup.
|
||||
// We'll debounce it, so that we do the actual work once per tick.
|
||||
const sendOutputHiddenStateDebouncer = new Debouncer(
|
||||
null,
|
||||
doSendOutputHiddenState,
|
||||
0
|
||||
);
|
||||
|
||||
function sendOutputHiddenState() {
|
||||
sendOutputHiddenStateDebouncer.normalCall();
|
||||
}
|
||||
// We need to make sure doSendOutputHiddenState actually gets called before
|
||||
// the inputBatchSender sends data to the server. The lastChanceCallback
|
||||
// here does that - if the debouncer has a pending call, flush it.
|
||||
inputBatchSender.lastChanceCallback.push(function () {
|
||||
if (sendOutputHiddenStateDebouncer.isPending())
|
||||
sendOutputHiddenStateDebouncer.immediateCall();
|
||||
});
|
||||
|
||||
// Given a namespace and a handler function, return a function that invokes
|
||||
// the handler only when e's namespace matches. For example, if the
|
||||
// namespace is "bs", it would match when e.namespace is "bs" or "bs.tab".
|
||||
// If the namespace is "bs.tab", it would match for "bs.tab", but not "bs".
|
||||
function filterEventsByNamespace(
|
||||
namespace: string,
|
||||
handler: (...handlerArgs: any[]) => void,
|
||||
...args: any[]
|
||||
) {
|
||||
const namespaceArr = namespace.split(".");
|
||||
|
||||
return function (this: HTMLElement, e: JQuery.TriggeredEvent) {
|
||||
const eventNamespace = e.namespace?.split(".") ?? [];
|
||||
|
||||
// If any of the namespace strings aren't present in this event, quit.
|
||||
for (let i = 0; i < namespaceArr.length; i++) {
|
||||
if (eventNamespace.indexOf(namespaceArr[i]) === -1) return;
|
||||
}
|
||||
|
||||
handler.apply(this, [namespaceArr, handler, ...args]);
|
||||
};
|
||||
}
|
||||
|
||||
// The size of each image may change either because the browser window was
|
||||
// resized, or because a tab was shown/hidden (hidden elements report size
|
||||
// of 0x0). It's OK to over-report sizes because the input pipeline will
|
||||
// filter out values that haven't changed.
|
||||
$(window).resize(debounce(500, sendImageSizeFns.regular));
|
||||
// Need to register callbacks for each Bootstrap 3 class.
|
||||
const bs3classes = [
|
||||
"modal",
|
||||
"dropdown",
|
||||
"tab",
|
||||
"tooltip",
|
||||
"popover",
|
||||
"collapse",
|
||||
];
|
||||
|
||||
$.each(bs3classes, function (idx, classname) {
|
||||
$(document.body).on(
|
||||
"shown.bs." + classname + ".sendImageSize",
|
||||
"*",
|
||||
filterEventsByNamespace("bs", sendImageSizeFns.regular)
|
||||
);
|
||||
$(document.body).on(
|
||||
"shown.bs." +
|
||||
classname +
|
||||
".sendOutputHiddenState " +
|
||||
"hidden.bs." +
|
||||
classname +
|
||||
".sendOutputHiddenState",
|
||||
"*",
|
||||
filterEventsByNamespace("bs", sendOutputHiddenState)
|
||||
);
|
||||
});
|
||||
|
||||
// This is needed for Bootstrap 2 compatibility and for non-Bootstrap
|
||||
// related shown/hidden events (like conditionalPanel)
|
||||
$(document.body).on("shown.sendImageSize", "*", sendImageSizeFns.regular);
|
||||
$(document.body).on(
|
||||
"shown.sendOutputHiddenState hidden.sendOutputHiddenState",
|
||||
"*",
|
||||
sendOutputHiddenState
|
||||
);
|
||||
|
||||
// Send initial pixel ratio, and update it if it changes
|
||||
initialValues[".clientdata_pixelratio"] = pixelRatio();
|
||||
$(window).resize(function () {
|
||||
inputs.setInput(".clientdata_pixelratio", pixelRatio());
|
||||
});
|
||||
|
||||
// Send initial URL
|
||||
initialValues[".clientdata_url_protocol"] = window.location.protocol;
|
||||
initialValues[".clientdata_url_hostname"] = window.location.hostname;
|
||||
initialValues[".clientdata_url_port"] = window.location.port;
|
||||
initialValues[".clientdata_url_pathname"] = window.location.pathname;
|
||||
|
||||
// Send initial URL search (query string) and update it if it changes
|
||||
initialValues[".clientdata_url_search"] = window.location.search;
|
||||
|
||||
$(window).on("pushstate", function (e) {
|
||||
inputs.setInput(".clientdata_url_search", window.location.search);
|
||||
return;
|
||||
e;
|
||||
});
|
||||
|
||||
$(window).on("popstate", function (e) {
|
||||
inputs.setInput(".clientdata_url_search", window.location.search);
|
||||
return;
|
||||
e;
|
||||
});
|
||||
|
||||
// This is only the initial value of the hash. The hash can change, but
|
||||
// a reactive version of this isn't sent because watching for changes can
|
||||
// require polling on some browsers. The JQuery hashchange plugin can be
|
||||
// used if this capability is important.
|
||||
initialValues[".clientdata_url_hash_initial"] = window.location.hash;
|
||||
initialValues[".clientdata_url_hash"] = window.location.hash;
|
||||
|
||||
$(window).on("hashchange", function (e) {
|
||||
inputs.setInput(".clientdata_url_hash", window.location.hash);
|
||||
return;
|
||||
e;
|
||||
});
|
||||
|
||||
// The server needs to know what singletons were rendered as part of
|
||||
// the page loading
|
||||
const singletonText = (initialValues[".clientdata_singletons"] = $(
|
||||
'script[type="application/shiny-singletons"]'
|
||||
).text());
|
||||
|
||||
singletonsRegisterNames(singletonText.split(/,/));
|
||||
|
||||
const dependencyText = $(
|
||||
'script[type="application/html-dependencies"]'
|
||||
).text();
|
||||
|
||||
$.each(dependencyText.split(/;/), function (i, depStr) {
|
||||
const match = /\s*^(.+)\[(.+)\]\s*$/.exec(depStr);
|
||||
|
||||
if (match) {
|
||||
registerDependency(match[1], match[2]);
|
||||
}
|
||||
});
|
||||
|
||||
// We've collected all the initial values--start the server process!
|
||||
inputsNoResend.reset(initialValues);
|
||||
shinyapp.connect(initialValues);
|
||||
$(document).one("shiny:connected", () => {
|
||||
initDeferredIframes();
|
||||
});
|
||||
|
||||
$(document).one("shiny:sessioninitialized", () => {
|
||||
this.initializedPromise.resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Give any deferred iframes a chance to load.
|
||||
function initDeferredIframes(): void {
|
||||
// TODO-barret; This method uses `window.Shiny`. Could be replaced with `fullShinyObj_.shinyapp?.isConnected()`,
|
||||
// but that would not use `window.Shiny`. Is it a problem???
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore; Do not want to define `window.Shiny` as a type to discourage usage of `window.Shiny`;
|
||||
// Can not expect error when combining with window available Shiny definition
|
||||
!window.Shiny ||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore; Do not want to define `window.Shiny` as a type to discourage usage of `window.Shiny`;
|
||||
// Can not expect error when combining with window available Shiny definition
|
||||
!window.Shiny.shinyapp ||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore; Do not want to define `window.Shiny` as a type to discourage usage of `window.Shiny`;
|
||||
// Can not expect error when combining with window available Shiny definition
|
||||
!window.Shiny.shinyapp.isConnected()
|
||||
) {
|
||||
// If somehow we accidentally call this before the server connection is
|
||||
// established, just ignore the call. At the time of this writing it
|
||||
// doesn't happen, but it's easy to imagine a later refactoring putting
|
||||
// us in this situation and it'd be hard to notice with either manual
|
||||
// testing or automated tests, because the only effect is on HTTP request
|
||||
// timing. (Update: Actually Aron saw this being called without even
|
||||
// window.Shiny being defined, but it was hard to repro.)
|
||||
return;
|
||||
}
|
||||
|
||||
$(".shiny-frame-deferred").each(function (i, el) {
|
||||
const $el = $(el);
|
||||
|
||||
$el.removeClass("shiny-frame-deferred");
|
||||
// @ts-expect-error; If it is undefined, set using the undefined value
|
||||
$el.attr("src", $el.attr("data-deferred-src"));
|
||||
$el.attr("data-deferred-src", null);
|
||||
});
|
||||
}
|
||||
|
||||
export { windowShiny, setShiny };
|
||||
export type { Shiny };
|
||||
export { ShinyClass };
|
||||
|
||||
@@ -1,565 +0,0 @@
|
||||
import $ from "jquery";
|
||||
import type { Shiny } from ".";
|
||||
import type { InputPolicy } from "../inputPolicies";
|
||||
import {
|
||||
InputBatchSender,
|
||||
InputDeferDecorator,
|
||||
InputEventDecorator,
|
||||
InputNoResendDecorator,
|
||||
InputRateDecorator,
|
||||
InputValidateDecorator,
|
||||
} from "../inputPolicies";
|
||||
import type { InputPolicyOpts } from "../inputPolicies/inputPolicy";
|
||||
import { addDefaultInputOpts } from "../inputPolicies/inputValidateDecorator";
|
||||
import { debounce, Debouncer } from "../time";
|
||||
import {
|
||||
getComputedLinkColor,
|
||||
getStyle,
|
||||
hasDefinedProperty,
|
||||
mapValues,
|
||||
pixelRatio,
|
||||
} from "../utils";
|
||||
import type { BindInputsCtx, BindScope } from "./bind";
|
||||
import { bindAll, unbindAll, _bindAll } from "./bind";
|
||||
import { setShinyObj } from "./initedMethods";
|
||||
import { registerDependency } from "./render";
|
||||
import { sendImageSizeFns } from "./sendImageSize";
|
||||
import { ShinyApp } from "./shinyapp";
|
||||
import { registerNames as singletonsRegisterNames } from "./singletons";
|
||||
|
||||
// "init_shiny.js"
|
||||
async function initShiny(windowShiny: Shiny): Promise<void> {
|
||||
setShinyObj(windowShiny);
|
||||
const shinyapp = (windowShiny.shinyapp = new ShinyApp());
|
||||
|
||||
windowShiny.progressHandlers = shinyapp.progressHandlers;
|
||||
|
||||
const inputBatchSender = new InputBatchSender(shinyapp);
|
||||
const inputsNoResend = new InputNoResendDecorator(inputBatchSender);
|
||||
const inputsEvent = new InputEventDecorator(inputsNoResend);
|
||||
const inputsRate = new InputRateDecorator(inputsEvent);
|
||||
const inputsDefer = new InputDeferDecorator(inputsEvent);
|
||||
|
||||
let target: InputPolicy;
|
||||
|
||||
if ($('input[type="submit"], button[type="submit"]').length > 0) {
|
||||
// If there is a submit button on the page, use defer decorator
|
||||
target = inputsDefer;
|
||||
|
||||
$('input[type="submit"], button[type="submit"]').each(function () {
|
||||
$(this).click(function (event) {
|
||||
event.preventDefault();
|
||||
inputsDefer.submit();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// By default, use rate decorator
|
||||
target = inputsRate;
|
||||
}
|
||||
|
||||
const inputs = new InputValidateDecorator(target);
|
||||
|
||||
windowShiny.setInputValue = windowShiny.onInputChange = function (
|
||||
name: string,
|
||||
value: unknown,
|
||||
opts: Partial<InputPolicyOpts> = {}
|
||||
): void {
|
||||
const newOpts = addDefaultInputOpts(opts);
|
||||
|
||||
inputs.setInput(name, value, newOpts);
|
||||
};
|
||||
|
||||
// By default, Shiny deduplicates input value changes; that is, if
|
||||
// `setInputValue` is called with the same value as the input already
|
||||
// has, the call is ignored (unless opts.priority = "event"). Calling
|
||||
// `forgetLastInputValue` tells Shiny that the very next call to
|
||||
// `setInputValue` for this input id shouldn't be ignored, even if it
|
||||
// is a dupe of the existing value.
|
||||
windowShiny.forgetLastInputValue = function (name) {
|
||||
inputsNoResend.forget(name);
|
||||
};
|
||||
|
||||
// MUST be called after `setShiny()`
|
||||
const inputBindings = windowShiny.inputBindings;
|
||||
const outputBindings = windowShiny.outputBindings;
|
||||
|
||||
function shinyBindCtx(): BindInputsCtx {
|
||||
return {
|
||||
inputs,
|
||||
inputsRate,
|
||||
sendOutputHiddenState,
|
||||
maybeAddThemeObserver,
|
||||
inputBindings,
|
||||
outputBindings,
|
||||
initDeferredIframes,
|
||||
};
|
||||
}
|
||||
|
||||
windowShiny.bindAll = async function (scope: BindScope) {
|
||||
await bindAll(shinyBindCtx(), scope);
|
||||
};
|
||||
windowShiny.unbindAll = function (scope: BindScope, includeSelf = false) {
|
||||
unbindAll(shinyBindCtx(), scope, includeSelf);
|
||||
};
|
||||
|
||||
// Calls .initialize() for all of the input objects in all input bindings,
|
||||
// in the given scope.
|
||||
function initializeInputs(scope: BindScope = document.documentElement) {
|
||||
const bindings = inputBindings.getBindings();
|
||||
|
||||
// Iterate over all bindings
|
||||
for (let i = 0; i < bindings.length; i++) {
|
||||
const binding = bindings[i].binding;
|
||||
const inputObjects = binding.find(scope);
|
||||
|
||||
if (inputObjects) {
|
||||
// Iterate over all input objects for this binding
|
||||
for (let j = 0; j < inputObjects.length; j++) {
|
||||
const $inputObjectJ = $(inputObjects[j]);
|
||||
|
||||
if (!$inputObjectJ.data("_shiny_initialized")) {
|
||||
$inputObjectJ.data("_shiny_initialized", true);
|
||||
binding.initialize(inputObjects[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
windowShiny.initializeInputs = initializeInputs;
|
||||
|
||||
function getIdFromEl(el: HTMLElement) {
|
||||
const $el = $(el);
|
||||
const bindingAdapter = $el.data("shiny-output-binding");
|
||||
|
||||
if (!bindingAdapter) return null;
|
||||
else return bindingAdapter.getId();
|
||||
}
|
||||
|
||||
// Initialize all input objects in the document, before binding
|
||||
initializeInputs(document.documentElement);
|
||||
|
||||
// The input values returned by _bindAll() each have a structure like this:
|
||||
// { value: 123, opts: { ... } }
|
||||
// We want to only keep the value. This is because when the initialValues is
|
||||
// passed to ShinyApp.connect(), the ShinyApp object stores the
|
||||
// initialValues object for the duration of the session, and the opts may
|
||||
// have a reference to the DOM element, which would prevent it from being
|
||||
// GC'd.
|
||||
const initialValues = mapValues(
|
||||
await _bindAll(shinyBindCtx(), document.documentElement),
|
||||
(x) => x.value
|
||||
);
|
||||
|
||||
// The server needs to know the size of each image and plot output element,
|
||||
// in case it is auto-sizing
|
||||
$(".shiny-image-output, .shiny-plot-output, .shiny-report-size").each(
|
||||
function () {
|
||||
const id = getIdFromEl(this),
|
||||
rect = this.getBoundingClientRect();
|
||||
|
||||
if (rect.width !== 0 || rect.height !== 0) {
|
||||
initialValues[".clientdata_output_" + id + "_width"] = rect.width;
|
||||
initialValues[".clientdata_output_" + id + "_height"] = rect.height;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function getComputedBgColor(
|
||||
el: HTMLElement | null
|
||||
): string | null | undefined {
|
||||
if (!el) {
|
||||
// Top of document, can't recurse further
|
||||
return null;
|
||||
}
|
||||
|
||||
const bgColor = getStyle(el, "background-color");
|
||||
|
||||
if (!bgColor) return bgColor;
|
||||
const m = bgColor.match(
|
||||
/^rgba\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/
|
||||
);
|
||||
|
||||
if (bgColor === "transparent" || (m && parseFloat(m[4]) === 0)) {
|
||||
// No background color on this element. See if it has a background image.
|
||||
const bgImage = getStyle(el, "background-image");
|
||||
|
||||
if (bgImage && bgImage !== "none") {
|
||||
// Failed to detect background color, since it has a background image
|
||||
return null;
|
||||
} else {
|
||||
// Recurse
|
||||
return getComputedBgColor(el.parentElement);
|
||||
}
|
||||
}
|
||||
return bgColor;
|
||||
}
|
||||
|
||||
function getComputedFont(el: HTMLElement) {
|
||||
const fontFamily = getStyle(el, "font-family");
|
||||
const fontSize = getStyle(el, "font-size");
|
||||
|
||||
return {
|
||||
families: fontFamily?.replace(/"/g, "").split(", "),
|
||||
size: fontSize,
|
||||
};
|
||||
}
|
||||
|
||||
$(".shiny-image-output, .shiny-plot-output, .shiny-report-theme").each(
|
||||
function () {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const el = this;
|
||||
const id = getIdFromEl(el);
|
||||
|
||||
initialValues[".clientdata_output_" + id + "_bg"] =
|
||||
getComputedBgColor(el);
|
||||
initialValues[".clientdata_output_" + id + "_fg"] = getStyle(el, "color");
|
||||
initialValues[".clientdata_output_" + id + "_accent"] =
|
||||
getComputedLinkColor(el);
|
||||
initialValues[".clientdata_output_" + id + "_font"] = getComputedFont(el);
|
||||
maybeAddThemeObserver(el);
|
||||
}
|
||||
);
|
||||
|
||||
// Resend computed styles if *an output element's* class or style attribute changes.
|
||||
// This gives us some level of confidence that getCurrentOutputInfo() will be
|
||||
// properly invalidated if output container is mutated; but unfortunately,
|
||||
// we don't have a reasonable way to detect change in *inherited* styles
|
||||
// (other than session$setCurrentTheme())
|
||||
// https://github.com/rstudio/shiny/issues/3196
|
||||
// https://github.com/rstudio/shiny/issues/2998
|
||||
function maybeAddThemeObserver(el: HTMLElement): void {
|
||||
if (!window.MutationObserver) {
|
||||
return; // IE10 and lower
|
||||
}
|
||||
|
||||
const cl = el.classList;
|
||||
const reportTheme =
|
||||
cl.contains("shiny-image-output") ||
|
||||
cl.contains("shiny-plot-output") ||
|
||||
cl.contains("shiny-report-theme");
|
||||
|
||||
if (!reportTheme) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $el = $(el);
|
||||
|
||||
if ($el.data("shiny-theme-observer")) {
|
||||
return; // i.e., observer is already observing
|
||||
}
|
||||
|
||||
const observerCallback = new Debouncer(null, () => doSendTheme(el), 100);
|
||||
const observer = new MutationObserver(() => observerCallback.normalCall());
|
||||
const config = { attributes: true, attributeFilter: ["style", "class"] };
|
||||
|
||||
observer.observe(el, config);
|
||||
$el.data("shiny-theme-observer", observer);
|
||||
}
|
||||
|
||||
function doSendTheme(el: HTMLElement): void {
|
||||
// Sending theme info on error isn't necessary (it'd add an unnecessary additional round-trip)
|
||||
if (el.classList.contains("shiny-output-error")) {
|
||||
return;
|
||||
}
|
||||
const id = getIdFromEl(el);
|
||||
|
||||
inputs.setInput(".clientdata_output_" + id + "_bg", getComputedBgColor(el));
|
||||
inputs.setInput(".clientdata_output_" + id + "_fg", getStyle(el, "color"));
|
||||
inputs.setInput(
|
||||
".clientdata_output_" + id + "_accent",
|
||||
getComputedLinkColor(el)
|
||||
);
|
||||
inputs.setInput(".clientdata_output_" + id + "_font", getComputedFont(el));
|
||||
}
|
||||
|
||||
function doSendImageSize() {
|
||||
$(".shiny-image-output, .shiny-plot-output, .shiny-report-size").each(
|
||||
function () {
|
||||
const id = getIdFromEl(this),
|
||||
rect = this.getBoundingClientRect();
|
||||
|
||||
if (rect.width !== 0 || rect.height !== 0) {
|
||||
inputs.setInput(".clientdata_output_" + id + "_width", rect.width);
|
||||
inputs.setInput(".clientdata_output_" + id + "_height", rect.height);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$(".shiny-image-output, .shiny-plot-output, .shiny-report-theme").each(
|
||||
function () {
|
||||
doSendTheme(this);
|
||||
}
|
||||
);
|
||||
|
||||
$(".shiny-bound-output").each(function () {
|
||||
const $this = $(this),
|
||||
binding = $this.data("shiny-output-binding");
|
||||
|
||||
$this.trigger({
|
||||
type: "shiny:visualchange",
|
||||
// @ts-expect-error; Can not remove info on a established, malformed Event object
|
||||
visible: !isHidden(this),
|
||||
binding: binding,
|
||||
});
|
||||
binding.onResize();
|
||||
});
|
||||
}
|
||||
|
||||
sendImageSizeFns.setImageSend(inputBatchSender, doSendImageSize);
|
||||
|
||||
// Return true if the object or one of its ancestors in the DOM tree has
|
||||
// style='display:none'; otherwise return false.
|
||||
function isHidden(obj: HTMLElement | null): boolean {
|
||||
// null means we've hit the top of the tree. If width or height is
|
||||
// non-zero, then we know that no ancestor has display:none.
|
||||
if (obj === null || obj.offsetWidth !== 0 || obj.offsetHeight !== 0) {
|
||||
return false;
|
||||
} else if (getStyle(obj, "display") === "none") {
|
||||
return true;
|
||||
} else {
|
||||
return isHidden(obj.parentNode as HTMLElement | null);
|
||||
}
|
||||
}
|
||||
let lastKnownVisibleOutputs: { [key: string]: boolean } = {};
|
||||
// Set initial state of outputs to hidden, if needed
|
||||
|
||||
$(".shiny-bound-output").each(function () {
|
||||
const id = getIdFromEl(this);
|
||||
|
||||
if (isHidden(this)) {
|
||||
initialValues[".clientdata_output_" + id + "_hidden"] = true;
|
||||
} else {
|
||||
lastKnownVisibleOutputs[id] = true;
|
||||
initialValues[".clientdata_output_" + id + "_hidden"] = false;
|
||||
}
|
||||
});
|
||||
// Send update when hidden state changes
|
||||
function doSendOutputHiddenState() {
|
||||
const visibleOutputs: { [key: string]: boolean } = {};
|
||||
|
||||
$(".shiny-bound-output").each(function () {
|
||||
const id = getIdFromEl(this);
|
||||
|
||||
delete lastKnownVisibleOutputs[id];
|
||||
// Assume that the object is hidden when width and height are 0
|
||||
const hidden = isHidden(this),
|
||||
evt = {
|
||||
type: "shiny:visualchange",
|
||||
visible: !hidden,
|
||||
};
|
||||
|
||||
if (hidden) {
|
||||
inputs.setInput(".clientdata_output_" + id + "_hidden", true);
|
||||
} else {
|
||||
visibleOutputs[id] = true;
|
||||
inputs.setInput(".clientdata_output_" + id + "_hidden", false);
|
||||
}
|
||||
const $this = $(this);
|
||||
|
||||
// @ts-expect-error; Can not remove info on a established, malformed Event object
|
||||
evt.binding = $this.data("shiny-output-binding");
|
||||
// @ts-expect-error; Can not remove info on a established, malformed Event object
|
||||
$this.trigger(evt);
|
||||
});
|
||||
// Anything left in lastKnownVisibleOutputs is orphaned
|
||||
for (const name in lastKnownVisibleOutputs) {
|
||||
if (hasDefinedProperty(lastKnownVisibleOutputs, name))
|
||||
inputs.setInput(".clientdata_output_" + name + "_hidden", true);
|
||||
}
|
||||
// Update the visible outputs for next time
|
||||
lastKnownVisibleOutputs = visibleOutputs;
|
||||
}
|
||||
// sendOutputHiddenState gets called each time DOM elements are shown or
|
||||
// hidden. This can be in the hundreds or thousands of times at startup.
|
||||
// We'll debounce it, so that we do the actual work once per tick.
|
||||
const sendOutputHiddenStateDebouncer = new Debouncer(
|
||||
null,
|
||||
doSendOutputHiddenState,
|
||||
0
|
||||
);
|
||||
|
||||
function sendOutputHiddenState() {
|
||||
sendOutputHiddenStateDebouncer.normalCall();
|
||||
}
|
||||
// We need to make sure doSendOutputHiddenState actually gets called before
|
||||
// the inputBatchSender sends data to the server. The lastChanceCallback
|
||||
// here does that - if the debouncer has a pending call, flush it.
|
||||
inputBatchSender.lastChanceCallback.push(function () {
|
||||
if (sendOutputHiddenStateDebouncer.isPending())
|
||||
sendOutputHiddenStateDebouncer.immediateCall();
|
||||
});
|
||||
|
||||
// Given a namespace and a handler function, return a function that invokes
|
||||
// the handler only when e's namespace matches. For example, if the
|
||||
// namespace is "bs", it would match when e.namespace is "bs" or "bs.tab".
|
||||
// If the namespace is "bs.tab", it would match for "bs.tab", but not "bs".
|
||||
function filterEventsByNamespace(
|
||||
namespace: string,
|
||||
handler: (...handlerArgs: any[]) => void,
|
||||
...args: any[]
|
||||
) {
|
||||
const namespaceArr = namespace.split(".");
|
||||
|
||||
return function (this: HTMLElement, e: JQuery.TriggeredEvent) {
|
||||
const eventNamespace = e.namespace?.split(".") ?? [];
|
||||
|
||||
// If any of the namespace strings aren't present in this event, quit.
|
||||
for (let i = 0; i < namespaceArr.length; i++) {
|
||||
if (eventNamespace.indexOf(namespaceArr[i]) === -1) return;
|
||||
}
|
||||
|
||||
handler.apply(this, [namespaceArr, handler, ...args]);
|
||||
};
|
||||
}
|
||||
|
||||
// The size of each image may change either because the browser window was
|
||||
// resized, or because a tab was shown/hidden (hidden elements report size
|
||||
// of 0x0). It's OK to over-report sizes because the input pipeline will
|
||||
// filter out values that haven't changed.
|
||||
$(window).resize(debounce(500, sendImageSizeFns.regular));
|
||||
// Need to register callbacks for each Bootstrap 3 class.
|
||||
const bs3classes = [
|
||||
"modal",
|
||||
"dropdown",
|
||||
"tab",
|
||||
"tooltip",
|
||||
"popover",
|
||||
"collapse",
|
||||
];
|
||||
|
||||
$.each(bs3classes, function (idx, classname) {
|
||||
$(document.body).on(
|
||||
"shown.bs." + classname + ".sendImageSize",
|
||||
"*",
|
||||
filterEventsByNamespace("bs", sendImageSizeFns.regular)
|
||||
);
|
||||
$(document.body).on(
|
||||
"shown.bs." +
|
||||
classname +
|
||||
".sendOutputHiddenState " +
|
||||
"hidden.bs." +
|
||||
classname +
|
||||
".sendOutputHiddenState",
|
||||
"*",
|
||||
filterEventsByNamespace("bs", sendOutputHiddenState)
|
||||
);
|
||||
});
|
||||
|
||||
// This is needed for Bootstrap 2 compatibility and for non-Bootstrap
|
||||
// related shown/hidden events (like conditionalPanel)
|
||||
$(document.body).on("shown.sendImageSize", "*", sendImageSizeFns.regular);
|
||||
$(document.body).on(
|
||||
"shown.sendOutputHiddenState hidden.sendOutputHiddenState",
|
||||
"*",
|
||||
sendOutputHiddenState
|
||||
);
|
||||
|
||||
// Send initial pixel ratio, and update it if it changes
|
||||
initialValues[".clientdata_pixelratio"] = pixelRatio();
|
||||
$(window).resize(function () {
|
||||
inputs.setInput(".clientdata_pixelratio", pixelRatio());
|
||||
});
|
||||
|
||||
// Send initial URL
|
||||
initialValues[".clientdata_url_protocol"] = window.location.protocol;
|
||||
initialValues[".clientdata_url_hostname"] = window.location.hostname;
|
||||
initialValues[".clientdata_url_port"] = window.location.port;
|
||||
initialValues[".clientdata_url_pathname"] = window.location.pathname;
|
||||
|
||||
// Send initial URL search (query string) and update it if it changes
|
||||
initialValues[".clientdata_url_search"] = window.location.search;
|
||||
|
||||
$(window).on("pushstate", function (e) {
|
||||
inputs.setInput(".clientdata_url_search", window.location.search);
|
||||
return;
|
||||
e;
|
||||
});
|
||||
|
||||
$(window).on("popstate", function (e) {
|
||||
inputs.setInput(".clientdata_url_search", window.location.search);
|
||||
return;
|
||||
e;
|
||||
});
|
||||
|
||||
// This is only the initial value of the hash. The hash can change, but
|
||||
// a reactive version of this isn't sent because watching for changes can
|
||||
// require polling on some browsers. The JQuery hashchange plugin can be
|
||||
// used if this capability is important.
|
||||
initialValues[".clientdata_url_hash_initial"] = window.location.hash;
|
||||
initialValues[".clientdata_url_hash"] = window.location.hash;
|
||||
|
||||
$(window).on("hashchange", function (e) {
|
||||
inputs.setInput(".clientdata_url_hash", window.location.hash);
|
||||
return;
|
||||
e;
|
||||
});
|
||||
|
||||
// The server needs to know what singletons were rendered as part of
|
||||
// the page loading
|
||||
const singletonText = (initialValues[".clientdata_singletons"] = $(
|
||||
'script[type="application/shiny-singletons"]'
|
||||
).text());
|
||||
|
||||
singletonsRegisterNames(singletonText.split(/,/));
|
||||
|
||||
const dependencyText = $(
|
||||
'script[type="application/html-dependencies"]'
|
||||
).text();
|
||||
|
||||
$.each(dependencyText.split(/;/), function (i, depStr) {
|
||||
const match = /\s*^(.+)\[(.+)\]\s*$/.exec(depStr);
|
||||
|
||||
if (match) {
|
||||
registerDependency(match[1], match[2]);
|
||||
}
|
||||
});
|
||||
|
||||
// We've collected all the initial values--start the server process!
|
||||
inputsNoResend.reset(initialValues);
|
||||
shinyapp.connect(initialValues);
|
||||
$(document).one("shiny:connected", function () {
|
||||
initDeferredIframes();
|
||||
});
|
||||
|
||||
// window.console.log("Shiny version: ", windowShiny.version);
|
||||
} // function initShiny()
|
||||
|
||||
// Give any deferred iframes a chance to load.
|
||||
function initDeferredIframes(): void {
|
||||
// TODO-barret; This method uses `window.Shiny`. Could be replaced with `fullShinyObj_.shinyapp?.isConnected()`,
|
||||
// but that would not use `window.Shiny`. Is it a problem???
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore; Do not want to define `window.Shiny` as a type to discourage usage of `window.Shiny`;
|
||||
// Can not expect error when combining with window available Shiny definition
|
||||
!window.Shiny ||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore; Do not want to define `window.Shiny` as a type to discourage usage of `window.Shiny`;
|
||||
// Can not expect error when combining with window available Shiny definition
|
||||
!window.Shiny.shinyapp ||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore; Do not want to define `window.Shiny` as a type to discourage usage of `window.Shiny`;
|
||||
// Can not expect error when combining with window available Shiny definition
|
||||
!window.Shiny.shinyapp.isConnected()
|
||||
) {
|
||||
// If somehow we accidentally call this before the server connection is
|
||||
// established, just ignore the call. At the time of this writing it
|
||||
// doesn't happen, but it's easy to imagine a later refactoring putting
|
||||
// us in this situation and it'd be hard to notice with either manual
|
||||
// testing or automated tests, because the only effect is on HTTP request
|
||||
// timing. (Update: Actually Aron saw this being called without even
|
||||
// window.Shiny being defined, but it was hard to repro.)
|
||||
return;
|
||||
}
|
||||
|
||||
$(".shiny-frame-deferred").each(function (i, el) {
|
||||
const $el = $(el);
|
||||
|
||||
$el.removeClass("shiny-frame-deferred");
|
||||
// @ts-expect-error; If it is undefined, set using the undefined value
|
||||
$el.attr("src", $el.attr("data-deferred-src"));
|
||||
$el.attr("data-deferred-src", null);
|
||||
});
|
||||
}
|
||||
|
||||
export { initShiny };
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Shiny } from ".";
|
||||
import type { ShinyClass } from ".";
|
||||
import type { FileInputBinding } from "../bindings/input/fileinput";
|
||||
import type { OutputBindingAdapter } from "../bindings/outputAdapter";
|
||||
import type { EventPriority } from "../inputPolicies";
|
||||
@@ -10,7 +10,7 @@ let fullShinyObj: FullShinyDef;
|
||||
// TODO-future; It would be nice to have a way to export this type value instead of / in addition to `Shiny`
|
||||
type FullShinyDef = Required<
|
||||
Pick<
|
||||
Shiny,
|
||||
ShinyClass,
|
||||
| "bindAll"
|
||||
| "forgetLastInputValue"
|
||||
| "initializeInputs"
|
||||
@@ -21,9 +21,9 @@ type FullShinyDef = Required<
|
||||
| "user"
|
||||
>
|
||||
> &
|
||||
Shiny;
|
||||
ShinyClass;
|
||||
|
||||
function setShinyObj(shiny: Shiny): void {
|
||||
function setShinyObj(shiny: ShinyClass): void {
|
||||
fullShinyObj = shiny as FullShinyDef;
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,9 @@ function addCustomMessageHandler(type: string, handler: Handler): void {
|
||||
|
||||
//// End message handler variables
|
||||
|
||||
/**
|
||||
* The ShinyApp class handles the communication with the Shiny Server.
|
||||
*/
|
||||
class ShinyApp {
|
||||
$socket: ShinyWebSocket | null = null;
|
||||
|
||||
@@ -1610,4 +1613,4 @@ class ShinyApp {
|
||||
}
|
||||
|
||||
export { ShinyApp, addCustomMessageHandler };
|
||||
export type { Handler, ErrorsMessageValue };
|
||||
export type { Handler, ErrorsMessageValue, ShinyWebSocket };
|
||||
|
||||
46
srcts/src/utils/promise.ts
Normal file
46
srcts/src/utils/promise.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
// A shim for Promise.withResolvers. Once browser support is widespread, we can
|
||||
// remove this.
|
||||
export function promiseWithResolvers<T>(): {
|
||||
promise: Promise<T>;
|
||||
resolve: (value: PromiseLike<T> | T) => void;
|
||||
reject: (reason?: any) => void;
|
||||
} {
|
||||
let resolve: (value: PromiseLike<T> | T) => void;
|
||||
let reject: (reason?: any) => void;
|
||||
const promise = new Promise(
|
||||
(res: (value: PromiseLike<T> | T) => void, rej: (reason?: any) => void) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
}
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return { promise, resolve: resolve!, reject: reject! };
|
||||
}
|
||||
|
||||
export interface InitStatusPromise<T> extends Promise<T> {
|
||||
promise: Promise<T>;
|
||||
resolve(x: T): void;
|
||||
resolved(): boolean;
|
||||
}
|
||||
|
||||
export function createInitStatus<T>(): InitStatusPromise<T> {
|
||||
const { promise, resolve } = promiseWithResolvers<T>();
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
let _resolved = false;
|
||||
|
||||
return {
|
||||
promise,
|
||||
resolve(x: T) {
|
||||
_resolved = true;
|
||||
resolve(x);
|
||||
},
|
||||
then: promise.then.bind(promise),
|
||||
catch: promise.catch.bind(promise),
|
||||
finally: promise.finally.bind(promise),
|
||||
[Symbol.toStringTag]: "InitStatus",
|
||||
resolved() {
|
||||
return _resolved;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { Shiny } from "../shiny";
|
||||
|
||||
function windowShiny(): Shiny {
|
||||
// Use `any` type as we know what we are doing is _dangerous_
|
||||
// Immediately init shiny on the window
|
||||
if (!(window as any)["Shiny"]) {
|
||||
(window as any)["Shiny"] = {};
|
||||
}
|
||||
return (window as any)["Shiny"];
|
||||
}
|
||||
|
||||
export { windowShiny };
|
||||
6
srcts/types/extras/globalShiny.d.ts
vendored
6
srcts/types/extras/globalShiny.d.ts
vendored
@@ -1,8 +1,6 @@
|
||||
import type { Shiny as RStudioShiny } from "../src/shiny/index";
|
||||
import type { ShinyClass } from "../src/shiny/index";
|
||||
declare global {
|
||||
const Shiny: RStudioShiny;
|
||||
interface Window {
|
||||
Shiny: RStudioShiny;
|
||||
Shiny: ShinyClass;
|
||||
}
|
||||
type Shiny = RStudioShiny;
|
||||
}
|
||||
|
||||
3
srcts/types/src/bindings/input/index.d.ts
vendored
3
srcts/types/src/bindings/input/index.d.ts
vendored
@@ -1,9 +1,8 @@
|
||||
import { BindingRegistry } from "../registry";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
import { FileInputBinding } from "./fileinput";
|
||||
type InitInputBindings = {
|
||||
declare function initInputBindings(): {
|
||||
inputBindings: BindingRegistry<InputBinding>;
|
||||
fileInputBinding: FileInputBinding;
|
||||
};
|
||||
declare function initInputBindings(): InitInputBindings;
|
||||
export { initInputBindings, InputBinding };
|
||||
|
||||
2
srcts/types/src/index.d.ts
vendored
2
srcts/types/src/index.d.ts
vendored
@@ -1 +1 @@
|
||||
export {};
|
||||
export { Shiny, type ShinyClass } from "./initialize";
|
||||
|
||||
4
srcts/types/src/initialize/index.d.ts
vendored
4
srcts/types/src/initialize/index.d.ts
vendored
@@ -1,2 +1,4 @@
|
||||
import { ShinyClass } from "../shiny";
|
||||
declare let Shiny: ShinyClass;
|
||||
declare function init(): void;
|
||||
export { init };
|
||||
export { init, Shiny, type ShinyClass };
|
||||
|
||||
29
srcts/types/src/shiny/index.d.ts
vendored
29
srcts/types/src/shiny/index.d.ts
vendored
@@ -1,22 +1,21 @@
|
||||
import { InputBinding, OutputBinding } from "../bindings";
|
||||
import { initInputBindings } from "../bindings/input";
|
||||
import { initOutputBindings } from "../bindings/output";
|
||||
import type { BindingRegistry } from "../bindings/registry";
|
||||
import { resetBrush } from "../imageutils/resetBrush";
|
||||
import { $escape, compareVersion } from "../utils";
|
||||
import { type InitStatusPromise } from "../utils/promise";
|
||||
import type { shinyBindAll, shinyForgetLastInputValue, shinyInitializeInputs, shinySetInputValue, shinyUnbindAll } from "./initedMethods";
|
||||
import { removeModal, showModal } from "./modal";
|
||||
import { removeNotification, showNotification } from "./notifications";
|
||||
import { hideReconnectDialog, showReconnectDialog } from "./reconnectDialog";
|
||||
import { renderContent, renderContentAsync, renderDependencies, renderDependenciesAsync, renderHtml, renderHtmlAsync } from "./render";
|
||||
import type { Handler, ShinyApp } from "./shinyapp";
|
||||
import { addCustomMessageHandler } from "./shinyapp";
|
||||
interface Shiny {
|
||||
import { addCustomMessageHandler, ShinyApp, type Handler } from "./shinyapp";
|
||||
declare class ShinyClass {
|
||||
version: string;
|
||||
$escape: typeof $escape;
|
||||
compareVersion: typeof compareVersion;
|
||||
inputBindings: ReturnType<typeof initInputBindings>["inputBindings"];
|
||||
inputBindings: BindingRegistry<InputBinding>;
|
||||
InputBinding: typeof InputBinding;
|
||||
outputBindings: ReturnType<typeof initOutputBindings>["outputBindings"];
|
||||
outputBindings: BindingRegistry<OutputBinding>;
|
||||
OutputBinding: typeof OutputBinding;
|
||||
resetBrush: typeof resetBrush;
|
||||
notifications: {
|
||||
@@ -27,7 +26,6 @@ interface Shiny {
|
||||
show: typeof showModal;
|
||||
remove: typeof removeModal;
|
||||
};
|
||||
createSocket?: () => WebSocket;
|
||||
showReconnectDialog: typeof showReconnectDialog;
|
||||
hideReconnectDialog: typeof hideReconnectDialog;
|
||||
renderDependenciesAsync: typeof renderDependenciesAsync;
|
||||
@@ -36,9 +34,10 @@ interface Shiny {
|
||||
renderContent: typeof renderContent;
|
||||
renderHtmlAsync: typeof renderHtmlAsync;
|
||||
renderHtml: typeof renderHtml;
|
||||
user: string;
|
||||
progressHandlers?: ShinyApp["progressHandlers"];
|
||||
addCustomMessageHandler: typeof addCustomMessageHandler;
|
||||
createSocket?: () => WebSocket;
|
||||
user?: string;
|
||||
progressHandlers?: ShinyApp["progressHandlers"];
|
||||
shinyapp?: ShinyApp;
|
||||
setInputValue?: typeof shinySetInputValue;
|
||||
onInputChange?: typeof shinySetInputValue;
|
||||
@@ -46,16 +45,16 @@ interface Shiny {
|
||||
bindAll?: typeof shinyBindAll;
|
||||
unbindAll?: typeof shinyUnbindAll;
|
||||
initializeInputs?: typeof shinyInitializeInputs;
|
||||
initializedPromise: InitStatusPromise<void>;
|
||||
oncustommessage?: Handler;
|
||||
constructor();
|
||||
/**
|
||||
* Method to check if Shiny is running in development mode. By packaging as a
|
||||
* method, we can we can avoid needing to look for the `__SHINY_DEV_MODE__`
|
||||
* variable in the global scope.
|
||||
* @returns `true` if Shiny is running in development mode, `false` otherwise.
|
||||
*/
|
||||
inDevMode: () => boolean;
|
||||
inDevMode(): boolean;
|
||||
initialize(): Promise<void>;
|
||||
}
|
||||
declare let windowShiny: Shiny;
|
||||
declare function setShiny(windowShiny_: Shiny): void;
|
||||
export { windowShiny, setShiny };
|
||||
export type { Shiny };
|
||||
export { ShinyClass };
|
||||
|
||||
3
srcts/types/src/shiny/init.d.ts
vendored
3
srcts/types/src/shiny/init.d.ts
vendored
@@ -1,3 +0,0 @@
|
||||
import type { Shiny } from ".";
|
||||
declare function initShiny(windowShiny: Shiny): Promise<void>;
|
||||
export { initShiny };
|
||||
4
srcts/types/src/shiny/initedMethods.d.ts
vendored
4
srcts/types/src/shiny/initedMethods.d.ts
vendored
@@ -1,10 +1,10 @@
|
||||
import type { Shiny } from ".";
|
||||
import type { ShinyClass } from ".";
|
||||
import type { FileInputBinding } from "../bindings/input/fileinput";
|
||||
import type { OutputBindingAdapter } from "../bindings/outputAdapter";
|
||||
import type { EventPriority } from "../inputPolicies";
|
||||
import type { BindScope } from "./bind";
|
||||
import type { Handler, ShinyApp } from "./shinyapp";
|
||||
declare function setShinyObj(shiny: Shiny): void;
|
||||
declare function setShinyObj(shiny: ShinyClass): void;
|
||||
declare function shinySetInputValue(name: string, value: unknown, opts?: {
|
||||
priority?: EventPriority;
|
||||
}): void;
|
||||
|
||||
5
srcts/types/src/shiny/shinyapp.d.ts
vendored
5
srcts/types/src/shiny/shinyapp.d.ts
vendored
@@ -19,6 +19,9 @@ type InputValues = {
|
||||
};
|
||||
type MessageValue = Parameters<WebSocket["send"]>[0];
|
||||
declare function addCustomMessageHandler(type: string, handler: Handler): void;
|
||||
/**
|
||||
* The ShinyApp class handles the communication with the Shiny Server.
|
||||
*/
|
||||
declare class ShinyApp {
|
||||
$socket: ShinyWebSocket | null;
|
||||
taskQueue: AsyncQueue<() => Promise<void> | void>;
|
||||
@@ -104,4 +107,4 @@ declare class ShinyApp {
|
||||
}): string;
|
||||
}
|
||||
export { ShinyApp, addCustomMessageHandler };
|
||||
export type { Handler, ErrorsMessageValue };
|
||||
export type { Handler, ErrorsMessageValue, ShinyWebSocket };
|
||||
|
||||
11
srcts/types/src/utils/promise.d.ts
vendored
Normal file
11
srcts/types/src/utils/promise.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
export declare function promiseWithResolvers<T>(): {
|
||||
promise: Promise<T>;
|
||||
resolve: (value: PromiseLike<T> | T) => void;
|
||||
reject: (reason?: any) => void;
|
||||
};
|
||||
export interface InitStatusPromise<T> extends Promise<T> {
|
||||
promise: Promise<T>;
|
||||
resolve(x: T): void;
|
||||
resolved(): boolean;
|
||||
}
|
||||
export declare function createInitStatus<T>(): InitStatusPromise<T>;
|
||||
3
srcts/types/src/window/libraries.d.ts
vendored
3
srcts/types/src/window/libraries.d.ts
vendored
@@ -1,3 +0,0 @@
|
||||
import type { Shiny } from "../shiny";
|
||||
declare function windowShiny(): Shiny;
|
||||
export { windowShiny };
|
||||
Reference in New Issue
Block a user