Compare commits

...

1 Commits

Author SHA1 Message Date
Carson Sievert
0bcf877835 fix: restore theme output refresh compatibility (#4378) 2026-04-28 21:26:52 -05:00
6 changed files with 89 additions and 53 deletions

View File

@@ -8,7 +8,9 @@
output-info pipeline (image/plot sizing, hidden-state tracking, theme
reporting) work automatically in any layout — including CSS-only show/hide,
third-party tab components, and non-Bootstrap frameworks — without requiring
custom event hooks. (#3682)
custom event hooks. This also introduces a `shiny:themechange` event
for code that needs to trigger theme clientdata refreshes after changing
surrounding visual theme context. (#3682)
# shiny 1.13.0

View File

@@ -7488,18 +7488,36 @@ ${duplicateIdMsg}`;
function reportsTheme(el) {
return el.classList.contains("shiny-image-output") || el.classList.contains("shiny-plot-output") || el.classList.contains("shiny-report-theme");
}
function handleVisualChange(el) {
doTriggerResize(el);
doSendHiddenState(el);
if (reportsSize(el)) doSendSize(el);
if (reportsTheme(el)) doSendTheme(el);
function refreshOutputInfo(el, initial = false) {
if (!initial) doTriggerResize(el);
doSendHiddenState(el, initial);
if (reportsSize(el)) doSendSize(el, initial);
if (reportsTheme(el)) doSendTheme(el, initial);
}
function refreshThemeOutputs(initial = false) {
(0, import_jquery40.default)(".shiny-bound-output").each(function() {
const el = this;
if (reportsTheme(el)) doSendTheme(el, initial);
});
}
function registerThemeRefreshSignals() {
const scheduleThemeInfoRefresh = sendOutputInfoFns.createObserverCallback(
100,
() => refreshThemeOutputs()
);
(0, import_jquery40.default)(window).resize(function() {
scheduleThemeInfoRefresh();
});
(0, import_jquery40.default)(document).on("shiny:themechange", function() {
scheduleThemeInfoRefresh();
});
}
function ensureObservers(el) {
const $el = (0, import_jquery40.default)(el);
if (!$el.data("shiny-resize-observer")) {
const onResize = sendOutputInfoFns.createObserverCallback(
100,
() => handleVisualChange(el)
() => refreshOutputInfo(el)
);
const ro = new ResizeObserver(() => onResize());
ro.observe(el);
@@ -7509,7 +7527,7 @@ ${duplicateIdMsg}`;
if (!$el.data("shiny-intersection-observer")) {
const onIntersect = sendOutputInfoFns.createObserverCallback(
100,
() => handleVisualChange(el)
() => refreshOutputInfo(el)
);
const io = new IntersectionObserver(() => onIntersect());
io.observe(el);
@@ -7536,14 +7554,7 @@ ${duplicateIdMsg}`;
const id = getIdFromEl(el);
if (id) outputIds.add(id);
ensureObservers(el);
if (!initial) doTriggerResize(el);
doSendHiddenState(el, initial);
if (reportsSize(el)) {
doSendSize(el, initial);
}
if (reportsTheme(el)) {
doSendTheme(el, initial);
}
refreshOutputInfo(el, initial);
});
visibleOutputs.forEach((id) => {
if (!outputIds.has(id)) {
@@ -7554,6 +7565,7 @@ ${duplicateIdMsg}`;
}
doSendOutputInfo(true);
sendOutputInfoFns.setSendMethod(inputBatchSender, doSendOutputInfo);
registerThemeRefreshSignals();
initialValues[".clientdata_pixelratio"] = pixelRatio();
(0, import_jquery40.default)(window).resize(function() {
inputs.setInput(".clientdata_pixelratio", pixelRatio());

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -420,11 +420,39 @@ class ShinyClass {
);
}
function handleVisualChange(el: HTMLElement): void {
doTriggerResize(el);
doSendHiddenState(el);
if (reportsSize(el)) doSendSize(el);
if (reportsTheme(el)) doSendTheme(el);
function refreshOutputInfo(el: HTMLElement, initial = false): void {
if (!initial) doTriggerResize(el);
doSendHiddenState(el, initial);
if (reportsSize(el)) doSendSize(el, initial);
if (reportsTheme(el)) doSendTheme(el, initial);
}
function refreshThemeOutputs(initial = false): void {
$(".shiny-bound-output").each(function () {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const el = this;
if (reportsTheme(el)) doSendTheme(el, initial);
});
}
function registerThemeRefreshSignals(): void {
const scheduleThemeInfoRefresh = sendOutputInfoFns.createObserverCallback(
100,
() => refreshThemeOutputs(),
);
// Compatibility: window resize used to refresh theme-reporting output
// info.
$(window).resize(function () {
scheduleThemeInfoRefresh();
});
// Explicit API for code that changes surrounding theme context without
// changing the output element itself.
$(document).on("shiny:themechange", function () {
scheduleThemeInfoRefresh();
});
}
function ensureObservers(el: HTMLElement): void {
@@ -432,7 +460,7 @@ class ShinyClass {
if (!$el.data("shiny-resize-observer")) {
const onResize = sendOutputInfoFns.createObserverCallback(100, () =>
handleVisualChange(el),
refreshOutputInfo(el),
);
const ro = new ResizeObserver(() => onResize());
@@ -443,7 +471,7 @@ class ShinyClass {
if (!$el.data("shiny-intersection-observer")) {
const onIntersect = sendOutputInfoFns.createObserverCallback(100, () =>
handleVisualChange(el),
refreshOutputInfo(el),
);
const io = new IntersectionObserver(() => onIntersect());
@@ -479,14 +507,7 @@ class ShinyClass {
if (id) outputIds.add(id);
ensureObservers(el);
if (!initial) doTriggerResize(el);
doSendHiddenState(el, initial);
if (reportsSize(el)) {
doSendSize(el, initial);
}
if (reportsTheme(el)) {
doSendTheme(el, initial);
}
refreshOutputInfo(el, initial);
});
visibleOutputs.forEach((id) => {
@@ -499,6 +520,7 @@ class ShinyClass {
doSendOutputInfo(true);
sendOutputInfoFns.setSendMethod(inputBatchSender, doSendOutputInfo);
registerThemeRefreshSignals();
// Send initial pixel ratio, and update it if it changes
initialValues[".clientdata_pixelratio"] = pixelRatio();