mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-10 23:48:01 -05:00
Compare commits
9 Commits
session-to
...
resize-obs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc29bc97b7 | ||
|
|
8384c73e5c | ||
|
|
ced223b6d2 | ||
|
|
f9c0d46910 | ||
|
|
eb41aff793 | ||
|
|
751b546230 | ||
|
|
fbe9d247d4 | ||
|
|
9ec33e7597 | ||
|
|
bd9f0493fe |
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
||||
/*! shiny 1.7.2 | (c) 2012-2022 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.7.2.9000 | (c) 2012-2022 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
#showcase-well{border-radius:0}.shiny-code{background-color:#fff;margin-bottom:0}.shiny-code code{font-family:Menlo,Consolas,"Courier New",monospace}.shiny-code-container{margin-top:20px;clear:both}.shiny-code-container h3{display:inline;margin-right:15px}.showcase-header{font-size:16px;font-weight:normal}.showcase-code-link{text-align:right;padding:15px}#showcase-app-container{vertical-align:top}#showcase-code-tabs{margin-right:15px}#showcase-code-tabs pre{border:none;line-height:1em}#showcase-code-tabs .nav{margin-bottom:0}#showcase-code-tabs ul{margin-bottom:0}#showcase-code-tabs .tab-content{border-style:solid;border-color:#e5e5e5;border-width:0px 1px 1px 1px;overflow:auto;border-bottom-right-radius:4px;border-bottom-left-radius:4px}#showcase-app-code{width:100%}#showcase-code-position-toggle{float:right}#showcase-sxs-code{padding-top:20px;vertical-align:top}.showcase-code-license{display:block;text-align:right}#showcase-code-content pre{background-color:#fff}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
|
||||
/*! shiny 1.7.2 | (c) 2012-2022 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.7.2.9000 | (c) 2012-2022 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
(function(){var a=eval;window.addEventListener("message",function(i){var e=i.data;e.code&&a(e.code)});})();
|
||||
//# sourceMappingURL=shiny-testmode.js.map
|
||||
|
||||
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.css
vendored
2
inst/www/shared/shiny.min.css
vendored
File diff suppressed because one or more lines are too long
4
inst/www/shared/shiny.min.js
vendored
4
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
@@ -3,7 +3,7 @@
|
||||
"homepage": "https://shiny.rstudio.com",
|
||||
"repository": "github:rstudio/shiny",
|
||||
"name": "@types/rstudio-shiny",
|
||||
"version": "1.7.2",
|
||||
"version": "1.7.2-alpha.9000",
|
||||
"license": "GPL-3.0-only",
|
||||
"main": "",
|
||||
"browser": "",
|
||||
@@ -24,6 +24,7 @@
|
||||
"@types/datatables.net": "^1.10.19",
|
||||
"@types/ion-rangeslider": "2.3.0",
|
||||
"@types/jquery": "patch:@types/jquery@3.5.5#./srcts/patch/types-jquery.patch",
|
||||
"@types/resize-observer-browser": "^0.1.7",
|
||||
"@types/selectize": "0.12.34"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -290,10 +290,12 @@ class ImageOutputBinding extends OutputBinding {
|
||||
width: number | string,
|
||||
height: number | string
|
||||
): void {
|
||||
$(el).find("img").trigger("resize");
|
||||
return;
|
||||
width;
|
||||
height;
|
||||
const img = $(el).find("img");
|
||||
|
||||
img.attr("width", width);
|
||||
img.attr("height", height);
|
||||
|
||||
img.trigger("resize");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
InputValidateDecorator,
|
||||
} from "../inputPolicies";
|
||||
import { shinyAppBindOutput, shinyAppUnbindOutput } from "./initedMethods";
|
||||
import { sendImageSizeFns } from "./sendImageSize";
|
||||
import { sendOutputInfoFns } from "./sendOutputInfo";
|
||||
|
||||
const boundInputs = {};
|
||||
|
||||
@@ -38,8 +38,6 @@ type BindInputsCtx = {
|
||||
inputsRate: InputRateDecorator;
|
||||
inputBindings: BindingRegistry<InputBinding>;
|
||||
outputBindings: BindingRegistry<OutputBinding>;
|
||||
sendOutputHiddenState: () => void;
|
||||
maybeAddThemeObserver: (el: HTMLElement) => void;
|
||||
initDeferredIframes: () => void;
|
||||
};
|
||||
function bindInputs(
|
||||
@@ -120,11 +118,7 @@ function bindInputs(
|
||||
}
|
||||
|
||||
function bindOutputs(
|
||||
{
|
||||
sendOutputHiddenState,
|
||||
maybeAddThemeObserver,
|
||||
outputBindings,
|
||||
}: BindInputsCtx,
|
||||
{ outputBindings }: BindInputsCtx,
|
||||
scope: BindScope = document.documentElement
|
||||
): void {
|
||||
const $scope = $(scope);
|
||||
@@ -155,12 +149,6 @@ function bindOutputs(
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this element reports its CSS styles to getCurrentOutputInfo()
|
||||
// then it should have a MutationObserver() to resend CSS if its
|
||||
// style/class attributes change. This observer should already exist
|
||||
// for _static_ UI, but not yet for _dynamic_ UI
|
||||
maybeAddThemeObserver(el);
|
||||
|
||||
const bindingAdapter = new OutputBindingAdapter(el, binding);
|
||||
|
||||
shinyAppBindOutput(id, bindingAdapter);
|
||||
@@ -177,8 +165,7 @@ function bindOutputs(
|
||||
}
|
||||
|
||||
// Send later in case DOM layout isn't final yet.
|
||||
setTimeout(sendImageSizeFns.regular, 0);
|
||||
setTimeout(sendOutputHiddenState, 0);
|
||||
setTimeout(sendOutputInfoFns.regular, 0);
|
||||
}
|
||||
|
||||
function unbindInputs(
|
||||
@@ -212,7 +199,6 @@ function unbindInputs(
|
||||
}
|
||||
}
|
||||
function unbindOutputs(
|
||||
{ sendOutputHiddenState }: BindInputsCtx,
|
||||
scope: BindScope = document.documentElement,
|
||||
includeSelf = false
|
||||
) {
|
||||
@@ -243,8 +229,7 @@ function unbindOutputs(
|
||||
}
|
||||
|
||||
// Send later in case DOM layout isn't final yet.
|
||||
setTimeout(sendImageSizeFns.regular, 0);
|
||||
setTimeout(sendOutputHiddenState, 0);
|
||||
setTimeout(sendOutputInfoFns.regular, 0);
|
||||
}
|
||||
|
||||
// (Named used before TS conversion)
|
||||
@@ -256,13 +241,9 @@ function _bindAll(
|
||||
bindOutputs(shinyCtx, scope);
|
||||
return bindInputs(shinyCtx, scope);
|
||||
}
|
||||
function unbindAll(
|
||||
shinyCtx: BindInputsCtx,
|
||||
scope: BindScope,
|
||||
includeSelf = false
|
||||
): void {
|
||||
function unbindAll(scope: BindScope, includeSelf = false): void {
|
||||
unbindInputs(scope, includeSelf);
|
||||
unbindOutputs(shinyCtx, scope, includeSelf);
|
||||
unbindOutputs(scope, includeSelf);
|
||||
}
|
||||
function bindAll(shinyCtx: BindInputsCtx, scope: BindScope): void {
|
||||
// _bindAll returns input values; it doesn't send them to the server.
|
||||
|
||||
@@ -10,11 +10,11 @@ import {
|
||||
} from "../inputPolicies";
|
||||
import type { InputPolicy } from "../inputPolicies";
|
||||
import { addDefaultInputOpts } from "../inputPolicies/inputValidateDecorator";
|
||||
import { debounce, Debouncer } from "../time";
|
||||
import { debounce } from "../time";
|
||||
import {
|
||||
getComputedLinkColor,
|
||||
getStyle,
|
||||
hasOwnProperty,
|
||||
isHidden,
|
||||
mapValues,
|
||||
pixelRatio,
|
||||
} from "../utils";
|
||||
@@ -22,7 +22,7 @@ import { bindAll, unbindAll, _bindAll } from "./bind";
|
||||
import type { BindInputsCtx, BindScope } from "./bind";
|
||||
import { setShinyObj } from "./initedMethods";
|
||||
import { registerDependency } from "./render";
|
||||
import { sendImageSizeFns } from "./sendImageSize";
|
||||
import { sendOutputInfoFns } from "./sendOutputInfo";
|
||||
import { ShinyApp } from "./shinyapp";
|
||||
import { registerNames as singletonsRegisterNames } from "./singletons";
|
||||
import type { InputPolicyOpts } from "../inputPolicies/inputPolicy";
|
||||
@@ -87,8 +87,6 @@ function initShiny(windowShiny: Shiny): void {
|
||||
return {
|
||||
inputs,
|
||||
inputsRate,
|
||||
sendOutputHiddenState,
|
||||
maybeAddThemeObserver,
|
||||
inputBindings,
|
||||
outputBindings,
|
||||
initDeferredIframes,
|
||||
@@ -99,7 +97,7 @@ function initShiny(windowShiny: Shiny): void {
|
||||
bindAll(shinyBindCtx(), scope);
|
||||
};
|
||||
windowShiny.unbindAll = function (scope: BindScope, includeSelf = false) {
|
||||
unbindAll(shinyBindCtx(), scope, includeSelf);
|
||||
unbindAll(scope, includeSelf);
|
||||
};
|
||||
|
||||
// Calls .initialize() for all of the input objects in all input bindings,
|
||||
@@ -127,12 +125,11 @@ function initShiny(windowShiny: Shiny): void {
|
||||
}
|
||||
windowShiny.initializeInputs = initializeInputs;
|
||||
|
||||
function getIdFromEl(el: HTMLElement) {
|
||||
function getIdFromEl(el: HTMLElement): string | null {
|
||||
const $el = $(el);
|
||||
const bindingAdapter = $el.data("shiny-output-binding");
|
||||
|
||||
if (!bindingAdapter) return null;
|
||||
else return bindingAdapter.getId();
|
||||
return bindingAdapter ? bindingAdapter.getId() : null;
|
||||
}
|
||||
|
||||
// Initialize all input objects in the document, before binding
|
||||
@@ -150,306 +147,204 @@ function initShiny(windowShiny: Shiny): void {
|
||||
(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);
|
||||
|
||||
if (this.offsetWidth !== 0 || this.offsetHeight !== 0) {
|
||||
initialValues[".clientdata_output_" + id + "_width"] = this.offsetWidth;
|
||||
initialValues[".clientdata_output_" + id + "_height"] =
|
||||
this.offsetHeight;
|
||||
}
|
||||
// Helper function for both initializing and updating input values
|
||||
function setInput(name: string, value: unknown, initial = false): void {
|
||||
if (initial) {
|
||||
initialValues[name] = value;
|
||||
} else {
|
||||
inputs.setInput(name, value);
|
||||
}
|
||||
);
|
||||
|
||||
function getComputedBgColor(el) {
|
||||
if (!el) {
|
||||
// Top of document, can't recurse further
|
||||
return null;
|
||||
}
|
||||
|
||||
const bgColor = getStyle(el, "background-color");
|
||||
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) {
|
||||
const fontFamily = getStyle(el, "font-family");
|
||||
const fontSize = getStyle(el, "font-size");
|
||||
function doSendSize(el: HTMLElement, initial = false): void {
|
||||
const id = getIdFromEl(el);
|
||||
|
||||
return {
|
||||
families: fontFamily.replace(/"/g, "").split(", "),
|
||||
size: fontSize,
|
||||
};
|
||||
if (el.offsetWidth !== 0 || el.offsetHeight !== 0) {
|
||||
setInput(".clientdata_output_" + id + "_width", el.offsetWidth, initial);
|
||||
setInput(
|
||||
".clientdata_output_" + id + "_height",
|
||||
el.offsetHeight,
|
||||
initial
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$(".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);
|
||||
function doTriggerResize(el: HTMLElement): void {
|
||||
const $el = $(el),
|
||||
binding = $el.data("shiny-output-binding");
|
||||
|
||||
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);
|
||||
$el.trigger({
|
||||
type: "shiny:visualchange",
|
||||
// @ts-expect-error; Can not remove info on a established, malformed Event object
|
||||
visible: !isHidden(el),
|
||||
binding: binding,
|
||||
});
|
||||
binding.onResize();
|
||||
}
|
||||
|
||||
function doSendTheme(el) {
|
||||
function doSendTheme(el: HTMLElement, initial = false): 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 getComputedBgColor(el: HTMLElement): string {
|
||||
if (!el) {
|
||||
// Top of document, can't recurse further
|
||||
return null;
|
||||
}
|
||||
|
||||
function doSendImageSize() {
|
||||
$(".shiny-image-output, .shiny-plot-output, .shiny-report-size").each(
|
||||
function () {
|
||||
const id = getIdFromEl(this);
|
||||
const bgColor = getStyle(el, "background-color");
|
||||
const m = bgColor.match(
|
||||
/^rgba\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/
|
||||
);
|
||||
|
||||
if (this.offsetWidth !== 0 || this.offsetHeight !== 0) {
|
||||
inputs.setInput(
|
||||
".clientdata_output_" + id + "_width",
|
||||
this.offsetWidth
|
||||
);
|
||||
inputs.setInput(
|
||||
".clientdata_output_" + id + "_height",
|
||||
this.offsetHeight
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
$(".shiny-image-output, .shiny-plot-output, .shiny-report-theme").each(
|
||||
function () {
|
||||
doSendTheme(this);
|
||||
}
|
||||
);
|
||||
function getComputedFont(el: HTMLElement): {
|
||||
families: string[];
|
||||
size: string;
|
||||
} {
|
||||
const fontFamily = getStyle(el, "font-family");
|
||||
const fontSize = getStyle(el, "font-size");
|
||||
|
||||
return {
|
||||
families: fontFamily.replace(/"/g, "").split(", "),
|
||||
size: fontSize,
|
||||
};
|
||||
}
|
||||
|
||||
const id = getIdFromEl(el);
|
||||
|
||||
setInput(
|
||||
".clientdata_output_" + id + "_bg",
|
||||
getComputedBgColor(el),
|
||||
initial
|
||||
);
|
||||
setInput(
|
||||
".clientdata_output_" + id + "_fg",
|
||||
getStyle(el, "color"),
|
||||
initial
|
||||
);
|
||||
setInput(
|
||||
".clientdata_output_" + id + "_accent",
|
||||
getComputedLinkColor(el),
|
||||
initial
|
||||
);
|
||||
setInput(
|
||||
".clientdata_output_" + id + "_font",
|
||||
getComputedFont(el),
|
||||
initial
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let visibleOutputs = new Set();
|
||||
|
||||
function doSendHiddenState(el: HTMLElement, initial = false): void {
|
||||
const id = getIdFromEl(el);
|
||||
const hidden = isHidden(el);
|
||||
|
||||
if (!hidden) visibleOutputs.add(id);
|
||||
setInput(".clientdata_output_" + id + "_hidden", hidden, initial);
|
||||
}
|
||||
|
||||
function doSendOutputInfo(initial = false) {
|
||||
const outputIds = new Set();
|
||||
|
||||
// TODO: can we always rely on this class existing when we're calling this (especially initially)?
|
||||
$(".shiny-bound-output").each(function () {
|
||||
const $this = $(this),
|
||||
binding = $this.data("shiny-output-binding");
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const el = this,
|
||||
id = getIdFromEl(el),
|
||||
isPlot =
|
||||
el.classList.contains("shiny-image-output") ||
|
||||
el.classList.contains("shiny-plot-output");
|
||||
|
||||
$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();
|
||||
outputIds.add(id);
|
||||
|
||||
function handleResize(initial = false) {
|
||||
doTriggerResize(el);
|
||||
doSendHiddenState(el, initial);
|
||||
if (isPlot || el.classList.contains("shiny-report-size")) {
|
||||
doSendSize(el, initial);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: do we need a polyfill for ResizeObserver?
|
||||
if (!$(el).data("shiny-resize-observer")) {
|
||||
const onResize = debounce(100, handleResize);
|
||||
const ro = new ResizeObserver(() => onResize(false));
|
||||
|
||||
ro.observe(el);
|
||||
$(el).data("shiny-resize-observer", ro);
|
||||
}
|
||||
|
||||
function handleIntersect(entries) {
|
||||
entries.forEach(() => {
|
||||
handleResize(false);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: do we need a polyfill for IntersectionObserver?
|
||||
if (!$(el).data("shiny-intersection-observer")) {
|
||||
const onIntersect = debounce(100, handleIntersect);
|
||||
const io = new IntersectionObserver(onIntersect);
|
||||
|
||||
io.observe(el);
|
||||
$(el).data("shiny-intersection-observer", io);
|
||||
}
|
||||
|
||||
function handleMutate(initial = false) {
|
||||
if (isPlot || el.classList.contains("shiny-report-theme")) {
|
||||
doSendTheme(el, initial);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: do we need a polyfill for MutationObserver?
|
||||
if (!$(el).data("shiny-mutate-observer")) {
|
||||
const onMutate = debounce(100, handleMutate);
|
||||
const mo = new MutationObserver(() => onMutate(false));
|
||||
|
||||
mo.observe(el, {
|
||||
attributes: true,
|
||||
attributeFilter: ["style", "class"],
|
||||
});
|
||||
|
||||
$(el).data("shiny-mutate-observer", mo);
|
||||
}
|
||||
|
||||
handleResize(initial);
|
||||
handleMutate(initial);
|
||||
});
|
||||
|
||||
// It could be that previously visible outputs have been removed from the DOM,
|
||||
// in that case, consider them hidden.
|
||||
visibleOutputs.forEach((id) => {
|
||||
if (!outputIds.has(id)) {
|
||||
visibleOutputs.delete(id);
|
||||
setInput(".clientdata_output_" + id + "_hidden", true, initial);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
let lastKnownVisibleOutputs = {};
|
||||
// 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 = {};
|
||||
|
||||
$(".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 (hasOwnProperty(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, handler, ...args) {
|
||||
namespace = namespace.split(".");
|
||||
|
||||
return function (e) {
|
||||
const eventNamespace = e.namespace.split(".");
|
||||
|
||||
// If any of the namespace strings aren't present in this event, quit.
|
||||
for (let i = 0; i < namespace.length; i++) {
|
||||
if (eventNamespace.indexOf(namespace[i]) === -1) return;
|
||||
}
|
||||
|
||||
handler.apply(this, [namespace, 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 input values to the server and also register a callback
|
||||
// to send updated input values whenever we receive new UI, etc.
|
||||
doSendOutputInfo(true);
|
||||
sendOutputInfoFns.setSendMethod(inputBatchSender, doSendOutputInfo);
|
||||
|
||||
// Send initial pixel ratio, and update it if it changes
|
||||
initialValues[".clientdata_pixelratio"] = pixelRatio();
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
shinyInitializeInputs,
|
||||
shinyUnbindAll,
|
||||
} from "./initedMethods";
|
||||
import { sendImageSizeFns } from "./sendImageSize";
|
||||
import { sendOutputInfoFns } from "./sendOutputInfo";
|
||||
|
||||
import { renderHtml as singletonsRenderHtml } from "./singletons";
|
||||
import type { WherePosition } from "./singletons";
|
||||
@@ -250,7 +250,7 @@ function addStylesheetsAndRestyle(links: HTMLLinkElement[]): void {
|
||||
// should have been applied synchronously.
|
||||
oldStyle.remove();
|
||||
removeSheet(oldSheet);
|
||||
sendImageSizeFns.transitioned();
|
||||
sendOutputInfoFns.transitioned();
|
||||
};
|
||||
xhr.send();
|
||||
};
|
||||
@@ -310,7 +310,7 @@ function addStylesheetsAndRestyle(links: HTMLLinkElement[]): void {
|
||||
// base64-encoded and inlined into the href. We also add a dummy DOM
|
||||
// element that the CSS applies to. The dummy CSS includes a
|
||||
// transition, and when the `transitionend` event happens, we call
|
||||
// sendImageSizeFns.transitioned() and remove the old sheet. We also remove the
|
||||
// sendOutputInfoFns.transitioned() and remove the old sheet. We also remove the
|
||||
// dummy DOM element and dummy CSS content.
|
||||
//
|
||||
// The reason this works is because (we assume) that if multiple
|
||||
@@ -320,7 +320,7 @@ function addStylesheetsAndRestyle(links: HTMLLinkElement[]): void {
|
||||
//
|
||||
// Because it is common for multiple stylesheets to arrive close
|
||||
// together, but not on exactly the same tick, we call
|
||||
// sendImageSizeFns.transitioned(), which is debounced. Otherwise, it can result in
|
||||
// sendOutputInfoFns.transitioned(), which is debounced. Otherwise, it can result in
|
||||
// the same plot being redrawn multiple times with different
|
||||
// styling.
|
||||
$link.attr("onload", () => {
|
||||
@@ -333,7 +333,7 @@ function addStylesheetsAndRestyle(links: HTMLLinkElement[]): void {
|
||||
$dummyEl.one("transitionend", () => {
|
||||
$dummyEl.remove();
|
||||
removeSheet(oldSheet);
|
||||
sendImageSizeFns.transitioned();
|
||||
sendOutputInfoFns.transitioned();
|
||||
});
|
||||
$(document.body).append($dummyEl);
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { InputBatchSender } from "../inputPolicies";
|
||||
import { debounce, Debouncer } from "../time";
|
||||
|
||||
class SendImageSize {
|
||||
// This function gets defined in initShiny() and 'hoisted' so it can be reused
|
||||
// (to send CSS info) inside of Shiny.renderDependencies()
|
||||
regular: () => void;
|
||||
transitioned: () => void;
|
||||
|
||||
setImageSend(
|
||||
inputBatchSender: InputBatchSender,
|
||||
doSendImageSize: () => void
|
||||
): Debouncer<typeof doSendImageSize> {
|
||||
const sendImageSizeDebouncer = new Debouncer(null, doSendImageSize, 0);
|
||||
|
||||
this.regular = function () {
|
||||
sendImageSizeDebouncer.normalCall();
|
||||
};
|
||||
|
||||
// Make sure sendImageSize actually gets called before the inputBatchSender
|
||||
// sends data to the server.
|
||||
inputBatchSender.lastChanceCallback.push(function () {
|
||||
if (sendImageSizeDebouncer.isPending())
|
||||
sendImageSizeDebouncer.immediateCall();
|
||||
});
|
||||
|
||||
// A version of sendImageSize which debounces for longer.
|
||||
this.transitioned = debounce(200, this.regular);
|
||||
|
||||
return sendImageSizeDebouncer;
|
||||
}
|
||||
}
|
||||
|
||||
const sendImageSizeFns = new SendImageSize();
|
||||
|
||||
export { sendImageSizeFns };
|
||||
36
srcts/src/shiny/sendOutputInfo.ts
Normal file
36
srcts/src/shiny/sendOutputInfo.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { InputBatchSender } from "../inputPolicies";
|
||||
import { debounce, Debouncer } from "../time";
|
||||
|
||||
class SendOutputInfo {
|
||||
// This function gets defined in initShiny() and 'hoisted' so it can be reused
|
||||
// (to send CSS info) inside of Shiny.renderDependencies()
|
||||
regular: () => void;
|
||||
transitioned: () => void;
|
||||
|
||||
setSendMethod(
|
||||
inputBatchSender: InputBatchSender,
|
||||
doSendOutputInfo: () => void
|
||||
): Debouncer<typeof doSendOutputInfo> {
|
||||
const sendOutputInfoDebouncer = new Debouncer(null, doSendOutputInfo, 0);
|
||||
|
||||
this.regular = function () {
|
||||
sendOutputInfoDebouncer.normalCall();
|
||||
};
|
||||
|
||||
// Make sure sendOutputInfo actually gets called before the inputBatchSender
|
||||
// sends data to the server.
|
||||
inputBatchSender.lastChanceCallback.push(function () {
|
||||
if (sendOutputInfoDebouncer.isPending())
|
||||
sendOutputInfoDebouncer.immediateCall();
|
||||
});
|
||||
|
||||
// A version of sendOutputInfo which debounces for longer.
|
||||
this.transitioned = debounce(200, this.regular);
|
||||
|
||||
return sendOutputInfoDebouncer;
|
||||
}
|
||||
}
|
||||
|
||||
const sendOutputInfoFns = new SendOutputInfo();
|
||||
|
||||
export { sendOutputInfoFns };
|
||||
@@ -9,7 +9,7 @@ function escapeHTML(str: string): string {
|
||||
"<": "<",
|
||||
">": ">",
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
"\"": """,
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"/": "/",
|
||||
};
|
||||
@@ -54,6 +54,20 @@ function getStyle(el: Element, styleProp: string): string | undefined {
|
||||
return x;
|
||||
}
|
||||
|
||||
// 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): 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.parentElement);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a number to a string with leading zeros
|
||||
function padZeros(n: number, digits: number): string {
|
||||
let str = n.toString();
|
||||
@@ -384,6 +398,7 @@ export {
|
||||
randomId,
|
||||
strToBool,
|
||||
getStyle,
|
||||
isHidden,
|
||||
padZeros,
|
||||
roundSignif,
|
||||
parseDate,
|
||||
|
||||
4
srcts/types/src/shiny/bind.d.ts
vendored
4
srcts/types/src/shiny/bind.d.ts
vendored
@@ -7,8 +7,6 @@ declare type BindInputsCtx = {
|
||||
inputsRate: InputRateDecorator;
|
||||
inputBindings: BindingRegistry<InputBinding>;
|
||||
outputBindings: BindingRegistry<OutputBinding>;
|
||||
sendOutputHiddenState: () => void;
|
||||
maybeAddThemeObserver: (el: HTMLElement) => void;
|
||||
initDeferredIframes: () => void;
|
||||
};
|
||||
declare function bindInputs(shinyCtx: BindInputsCtx, scope?: BindScope): {
|
||||
@@ -22,7 +20,7 @@ declare function bindInputs(shinyCtx: BindInputsCtx, scope?: BindScope): {
|
||||
};
|
||||
};
|
||||
declare function _bindAll(shinyCtx: BindInputsCtx, scope: BindScope): ReturnType<typeof bindInputs>;
|
||||
declare function unbindAll(shinyCtx: BindInputsCtx, scope: BindScope, includeSelf?: boolean): void;
|
||||
declare function unbindAll(scope: BindScope, includeSelf?: boolean): void;
|
||||
declare function bindAll(shinyCtx: BindInputsCtx, scope: BindScope): void;
|
||||
export { unbindAll, bindAll, _bindAll };
|
||||
export type { BindScope, BindInputsCtx };
|
||||
|
||||
9
srcts/types/src/shiny/sendImageSize.d.ts
vendored
9
srcts/types/src/shiny/sendImageSize.d.ts
vendored
@@ -1,9 +0,0 @@
|
||||
import type { InputBatchSender } from "../inputPolicies";
|
||||
import { Debouncer } from "../time";
|
||||
declare class SendImageSize {
|
||||
regular: () => void;
|
||||
transitioned: () => void;
|
||||
setImageSend(inputBatchSender: InputBatchSender, doSendImageSize: () => void): Debouncer<typeof doSendImageSize>;
|
||||
}
|
||||
declare const sendImageSizeFns: SendImageSize;
|
||||
export { sendImageSizeFns };
|
||||
9
srcts/types/src/shiny/sendOutputInfo.d.ts
vendored
Normal file
9
srcts/types/src/shiny/sendOutputInfo.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { InputBatchSender } from "../inputPolicies";
|
||||
import { Debouncer } from "../time";
|
||||
declare class SendOutputInfo {
|
||||
regular: () => void;
|
||||
transitioned: () => void;
|
||||
setSendMethod(inputBatchSender: InputBatchSender, doSendOutputInfo: () => void): Debouncer<typeof doSendOutputInfo>;
|
||||
}
|
||||
declare const sendOutputInfoFns: SendOutputInfo;
|
||||
export { sendOutputInfoFns };
|
||||
3
srcts/types/src/utils/index.d.ts
vendored
3
srcts/types/src/utils/index.d.ts
vendored
@@ -4,6 +4,7 @@ declare function escapeHTML(str: string): string;
|
||||
declare function randomId(): string;
|
||||
declare function strToBool(str: string): boolean | undefined;
|
||||
declare function getStyle(el: Element, styleProp: string): string | undefined;
|
||||
declare function isHidden(obj: HTMLElement): boolean;
|
||||
declare function padZeros(n: number, digits: number): string;
|
||||
declare function roundSignif(x: number, digits?: number): number;
|
||||
declare function parseDate(dateString: string): Date;
|
||||
@@ -29,4 +30,4 @@ declare function updateLabel(labelTxt: string | undefined, labelNode: JQuery<HTM
|
||||
declare function getComputedLinkColor(el: HTMLElement): string;
|
||||
declare function isBS3(): boolean;
|
||||
declare function toLowerCase<T extends string>(str: T): Lowercase<T>;
|
||||
export { escapeHTML, randomId, strToBool, getStyle, padZeros, roundSignif, parseDate, formatDateUTC, makeResizeFilter, pixelRatio, scopeExprToFunc, asArray, mergeSort, $escape, mapValues, isnan, _equal, equal, compareVersion, updateLabel, getComputedLinkColor, makeBlob, hasOwnProperty, isBS3, toLowerCase, };
|
||||
export { escapeHTML, randomId, strToBool, getStyle, isHidden, padZeros, roundSignif, parseDate, formatDateUTC, makeResizeFilter, pixelRatio, scopeExprToFunc, asArray, mergeSort, $escape, mapValues, isnan, _equal, equal, compareVersion, updateLabel, getComputedLinkColor, makeBlob, hasOwnProperty, isBS3, toLowerCase, };
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"declaration": true,
|
||||
"compilerOptions": {
|
||||
"target": "ES5",
|
||||
"lib": ["dom"],
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
|
||||
@@ -1935,6 +1935,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/resize-observer-browser@npm:^0.1.7":
|
||||
version: 0.1.7
|
||||
resolution: "@types/resize-observer-browser@npm:0.1.7"
|
||||
checksum: eef8c82c9d3f9f4c57cad6134e795f4eebfcf11a8790d21636539e4314bf0e0c140e525c4a8cb440dd42788855daddb2c52c18f93e1c59bed20e9be6e65de290
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/rstudio-shiny@workspace:.":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@types/rstudio-shiny@workspace:."
|
||||
@@ -1958,6 +1965,7 @@ __metadata:
|
||||
"@types/jqueryui": 1.12.15
|
||||
"@types/lodash": ^4.14.170
|
||||
"@types/node": ^15.6.1
|
||||
"@types/resize-observer-browser": ^0.1.7
|
||||
"@types/selectize": 0.12.34
|
||||
"@types/showdown": ^1.9.3
|
||||
"@typescript-eslint/eslint-plugin": ^4.25.0
|
||||
|
||||
Reference in New Issue
Block a user