From 8b8e513ecdf89d37014cef0ea4b785bace672c10 Mon Sep 17 00:00:00 2001 From: Carson Date: Tue, 21 Apr 2026 15:55:08 -0500 Subject: [PATCH 1/3] Replace resize event listeners with ResizeObserver Static widgets now use a single shared ResizeObserver instead of window resize, Bootstrap tab/collapse, and ioslides event listeners. This detects container-level size changes regardless of cause. Co-Authored-By: Claude Opus 4.6 --- inst/www/htmlwidgets.js | 48 +++++++++++------------------------------ 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/inst/www/htmlwidgets.js b/inst/www/htmlwidgets.js index 1067d02..93589ed 100644 --- a/inst/www/htmlwidgets.js +++ b/inst/www/htmlwidgets.js @@ -565,6 +565,15 @@ } } + var resizeObserver = new ResizeObserver(function(entries) { + entries.forEach(function(entry) { + var resizeHandler = elementData(entry.target, "resize_handler"); + if (resizeHandler) { + resizeHandler(entry); + } + }); + }); + // Render static widgets after the document finishes loading // Statically render all elements that are of this widget's class window.HTMLWidgets.staticRender = function() { @@ -596,7 +605,7 @@ if (binding.resize) { var lastSize = getSize(el); - var resizeHandler = function(e) { + elementData(el, "resize_handler", function() { var size = getSize(el); if (size.w === 0 && size.h === 0) return; @@ -604,41 +613,8 @@ return; lastSize = size; binding.resize(el, size.w, size.h, initResult); - }; - - on(window, "resize", resizeHandler); - - // This is needed for cases where we're running in a Shiny - // app, but the widget itself is not a Shiny output, but - // rather a simple static widget. One example of this is - // an rmarkdown document that has runtime:shiny and widget - // that isn't in a render function. Shiny only knows to - // call resize handlers for Shiny outputs, not for static - // widgets, so we do it ourselves. - if (window.jQuery) { - window.jQuery(document).on( - "shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets", - resizeHandler - ); - window.jQuery(document).on( - "hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets", - resizeHandler - ); - } - - // This is needed for the specific case of ioslides, which - // flips slides between display:none and display:block. - // Ideally we would not have to have ioslide-specific code - // here, but rather have ioslides raise a generic event, - // but the rmarkdown package just went to CRAN so the - // window to getting that fixed may be long. - if (window.addEventListener) { - // It's OK to limit this to window.addEventListener - // browsers because ioslides itself only supports - // such browsers. - on(document, "slideenter", resizeHandler); - on(document, "slideleave", resizeHandler); - } + }); + resizeObserver.observe(el); } var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']"); From fd5030c2dfe790f80bec1ae289a79781ada4cde5 Mon Sep 17 00:00:00 2001 From: Carson Date: Tue, 21 Apr 2026 17:23:29 -0500 Subject: [PATCH 2/3] Add NEWS entry for ResizeObserver change Co-Authored-By: Claude Opus 4.6 --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 34f2619..c7da2b7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,8 @@ * htmlwidgets hex sticker added +* Static widget resize detection now uses `ResizeObserver` instead of `window.resize`, Bootstrap tab/collapse, and ioslides event listeners. This enables widgets to detect container-level size changes (e.g., CSS-driven resizing, sidebar toggles, flexbox/grid layout changes) that were previously missed. (#496) + # htmlwidgets 1.6.4 This release reverts the change made in v1.6.3 (to no longer recurse into list-like objects when searching for JavaScript strings wrapped in `JS()`). Although that change helped prevent infinite recursion in some cases, it ultimately broke too many existing widgets that relied on the previous behavior. If you encounter infinite recursion errors ("C stack usage is too close to the limit"), the best strategy is to coerce the offending items to a character string. (#478) From 3761641689f28fd7b45fd257a599432c195079e8 Mon Sep 17 00:00:00 2001 From: Carson Date: Tue, 21 Apr 2026 17:24:31 -0500 Subject: [PATCH 3/3] Add feature detection for ResizeObserver with window.resize fallback Co-Authored-By: Claude Opus 4.6 --- inst/www/htmlwidgets.js | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/inst/www/htmlwidgets.js b/inst/www/htmlwidgets.js index 93589ed..34b24ab 100644 --- a/inst/www/htmlwidgets.js +++ b/inst/www/htmlwidgets.js @@ -565,14 +565,15 @@ } } - var resizeObserver = new ResizeObserver(function(entries) { - entries.forEach(function(entry) { - var resizeHandler = elementData(entry.target, "resize_handler"); - if (resizeHandler) { - resizeHandler(entry); - } - }); - }); + var resizeObserver = typeof ResizeObserver !== "undefined" ? + new ResizeObserver(function(entries) { + entries.forEach(function(entry) { + var resizeHandler = elementData(entry.target, "resize_handler"); + if (resizeHandler) { + resizeHandler(entry); + } + }); + }) : null; // Render static widgets after the document finishes loading // Statically render all elements that are of this widget's class @@ -605,7 +606,7 @@ if (binding.resize) { var lastSize = getSize(el); - elementData(el, "resize_handler", function() { + var resizeHandler = function() { var size = getSize(el); if (size.w === 0 && size.h === 0) return; @@ -613,8 +614,14 @@ return; lastSize = size; binding.resize(el, size.w, size.h, initResult); - }); - resizeObserver.observe(el); + }; + + if (resizeObserver) { + elementData(el, "resize_handler", resizeHandler); + resizeObserver.observe(el); + } else { + on(window, "resize", resizeHandler); + } } var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']");