diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/AppShell/BitAppShellJsRuntimeExtensions.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/AppShell/BitAppShellJsRuntimeExtensions.cs
index 6b9bf75064..e14d0a851e 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/AppShell/BitAppShellJsRuntimeExtensions.cs
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/AppShell/BitAppShellJsRuntimeExtensions.cs
@@ -6,21 +6,21 @@ internal static class BitAppShellJsRuntimeExtensions
{
internal static ValueTask BitAppShellInitScroll(this IJSRuntime jsRuntime, ElementReference container, string url)
{
- return jsRuntime.InvokeVoid("BitBlazorUI.AppShell.initScroll", container, url);
+ return jsRuntime.FastInvokeVoid("BitBlazorUI.AppShell.initScroll", container, url);
}
internal static ValueTask BitAppShellLocationChangedScroll(this IJSRuntime jsRuntime, string url)
{
- return jsRuntime.InvokeVoid("BitBlazorUI.AppShell.locationChangedScroll", url);
+ return jsRuntime.FastInvokeVoid("BitBlazorUI.AppShell.locationChangedScroll", url);
}
internal static ValueTask BitAppShellAfterRenderScroll(this IJSRuntime jsRuntime, string url)
{
- return jsRuntime.InvokeVoid("BitBlazorUI.AppShell.afterRenderScroll", url);
+ return jsRuntime.FastInvokeVoid("BitBlazorUI.AppShell.afterRenderScroll", url);
}
internal static ValueTask BitAppShellDisposeScroll(this IJSRuntime jsRuntime)
{
- return jsRuntime.InvokeVoid("BitBlazorUI.AppShell.disposeScroll");
+ return jsRuntime.FastInvokeVoid("BitBlazorUI.AppShell.disposeScroll");
}
}
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/Chart/BitChart.razor.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/Chart/BitChart.razor.cs
index d3d809f266..70de3e3154 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/Chart/BitChart.razor.cs
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/Chart/BitChart.razor.cs
@@ -147,13 +147,21 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
await _js.BitChartJsSetupChart(Config);
}
+ // Always signal completion on first render, matching the long-standing behavior: consumers may
+ // rely on SetupCompletedCallback firing once the component has rendered regardless of whether the
+ // chart setup itself succeeded (e.g. when Config is still null, or when interop was unavailable
+ // and the result was swallowed on the WebAssembly fast path).
await SetupCompletedCallback.InvokeAsync(this);
+
return;
}
if (Config is not null)
{
- await _js.BitChartJsSetupChart(Config);
+ // Re-runs setup after a Config change. The readiness result is intentionally discarded here:
+ // SetupCompletedCallback is raised only once, on first render, so subsequent re-setups don't
+ // re-signal readiness.
+ _ = await _js.BitChartJsSetupChart(Config);
}
}
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/Chart/BitChart.ts b/src/BlazorUI/Bit.BlazorUI.Extras/Components/Chart/BitChart.ts
index f4eb9ac8e9..31096398ac 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/Chart/BitChart.ts
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/Chart/BitChart.ts
@@ -54,7 +54,7 @@ namespace BitBlazorUI {
public static updateChart(config: BitChartConfiguration): boolean {
if (!BitChart._bitCharts.has(config.canvasId))
- throw `Could not find a chart with the given id. ${config.canvasId}`;
+ throw `Could not find a chart with the given id: ${config.canvasId}`;
let myChart = BitChart._bitCharts.get(config.canvasId);
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/Chart/JsInterop/BitChartJsInterop.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/Chart/JsInterop/BitChartJsInterop.cs
index 391b21674f..98606e0fbc 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/Chart/JsInterop/BitChartJsInterop.cs
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/Chart/JsInterop/BitChartJsInterop.cs
@@ -22,7 +22,7 @@ internal static class BitChartJsInterop
public static ValueTask BitChartJsRemoveChart(this IJSRuntime jsRuntime, string? canvasId)
{
- return jsRuntime.InvokeVoid("BitBlazorUI.BitChart.removeChart", canvasId);
+ return jsRuntime.FastInvokeVoid("BitBlazorUI.BitChart.removeChart", canvasId);
}
///
@@ -30,12 +30,15 @@ public static ValueTask BitChartJsRemoveChart(this IJSRuntime jsRuntime, string?
///
///
/// The config for the new chart.
- ///
- public static ValueTask BitChartJsSetupChart(this IJSRuntime jsRuntime, BitChartConfigBase chartConfig)
+ ///
+ /// when setup succeeded, when the chart could not be updated in place,
+ /// or when interop could not run or an error was swallowed on the in-process (WASM) path.
+ ///
+ public static ValueTask BitChartJsSetupChart(this IJSRuntime jsRuntime, BitChartConfigBase chartConfig)
{
var dynParam = StripNulls(chartConfig);
Dictionary param = ConvertExpandoObjectToDictionary(dynParam!);
- return jsRuntime.Invoke("BitBlazorUI.BitChart.setupChart", param);
+ return jsRuntime.FastInvoke("BitBlazorUI.BitChart.setupChart", param);
}
///
@@ -43,12 +46,15 @@ public static ValueTask BitChartJsSetupChart(this IJSRuntime jsRuntime, Bi
///
///
/// The updated config of the chart you want to update.
- ///
- public static ValueTask BitChartJsUpdateChart(this IJSRuntime jsRuntime, BitChartConfigBase chartConfig)
+ ///
+ /// when the chart was updated, when the chart instance was missing,
+ /// or when interop could not run or an error was swallowed on the in-process (WASM) path.
+ ///
+ public static ValueTask BitChartJsUpdateChart(this IJSRuntime jsRuntime, BitChartConfigBase chartConfig)
{
var dynParam = StripNulls(chartConfig);
var param = ConvertExpandoObjectToDictionary(dynParam!);
- return jsRuntime.Invoke("BitBlazorUI.BitChart.updateChart", param);
+ return jsRuntime.FastInvoke("BitBlazorUI.BitChart.updateChart", param);
}
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts b/src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
index 1b8c80517a..01cdeb330d 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGrid.ts
@@ -44,7 +44,7 @@ namespace BitBlazorUI {
colOptions.style.transform = `translateX(${applyOffset}px)`;
}
- colOptions.scrollIntoViewIfNeeded();
+ colOptions.scrollIntoViewIfNeeded?.();
const autoFocusElem = colOptions.querySelector('[autofocus]');
if (autoFocusElem) {
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
index 684418b1a8..a052ebf4b0 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/DataGrid/BitDataGridJsRuntimeExtensions.cs
@@ -2,11 +2,22 @@
internal static class BitDataGridJsRuntimeExtensions
{
- public static async ValueTask BitDataGridInit(this IJSRuntime jsRuntime, ElementReference tableElement)
+ // FastInvoke returns default (null) when the runtime can't service interop or a JSON/JS interop
+ // error is swallowed on the in-process (WASM) path. Callers must null-check before using the
+ // reference; a null result means DataGrid JS hooks were not initialized.
+ public static async ValueTask BitDataGridInit(this IJSRuntime jsRuntime, ElementReference tableElement)
{
- return await jsRuntime.Invoke("BitBlazorUI.DataGrid.init", tableElement);
+ const string identifier = "BitBlazorUI.DataGrid.init";
+ var result = await jsRuntime.FastInvoke(identifier, tableElement);
+ return jsRuntime.ReportIfUnexpectedNull(identifier, result);
}
+ // This is a fire-and-forget call from OnAfterRenderAsync that runs DOM-heavy positioning logic
+ // (getBoundingClientRect, scrollIntoViewIfNeeded, focus). It deliberately uses the regular async
+ // invocation rather than FastInvokeVoid: on WebAssembly FastInvokeVoid runs synchronously and can
+ // alter Promise/ordering and error-propagation semantics, so we use the async Invoke pattern to keep
+ // any JS-side failure (e.g. scrollIntoViewIfNeeded being unsupported) contained within the returned
+ // task instead of letting it escape synchronously into the render loop.
public static async ValueTask BitDataGridCheckColumnOptionsPosition(this IJSRuntime jsRuntime, ElementReference tableElement)
{
await jsRuntime.InvokeVoid("BitBlazorUI.DataGrid.checkColumnOptionsPosition", tableElement);
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/InfiniteScrolling/BitInfiniteScrollingJsRuntimeExtensions.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/InfiniteScrolling/BitInfiniteScrollingJsRuntimeExtensions.cs
index ecfcd131e9..0557a3c4fe 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/InfiniteScrolling/BitInfiniteScrollingJsRuntimeExtensions.cs
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/InfiniteScrolling/BitInfiniteScrollingJsRuntimeExtensions.cs
@@ -10,18 +10,18 @@ public static ValueTask BitInfiniteScrollingSetup(this IJSRuntime jsRuntime,
decimal? threshold,
DotNetObjectReference> dotnetObj)
{
- return jsRuntime.InvokeVoid("BitBlazorUI.InfiniteScrolling.setup", id, scrollerSelector, rootElement, lastElement, threshold, dotnetObj);
+ return jsRuntime.FastInvokeVoid("BitBlazorUI.InfiniteScrolling.setup", id, scrollerSelector, rootElement, lastElement, threshold, dotnetObj);
}
public static ValueTask BitInfiniteScrollingReobserve(this IJSRuntime jsRuntime,
string id,
ElementReference lastElement)
{
- return jsRuntime.InvokeVoid("BitBlazorUI.InfiniteScrolling.reobserve", id, lastElement);
+ return jsRuntime.FastInvokeVoid("BitBlazorUI.InfiniteScrolling.reobserve", id, lastElement);
}
public static ValueTask BitInfiniteScrollingDispose(this IJSRuntime jsRuntime, string id)
{
- return jsRuntime.InvokeVoid("BitBlazorUI.InfiniteScrolling.dispose", id);
+ return jsRuntime.FastInvokeVoid("BitBlazorUI.InfiniteScrolling.dispose", id);
}
}
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts b/src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
index 1249277d6e..9ab7b4500f 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
@@ -5,6 +5,10 @@ namespace BitBlazorUI {
}
public static parse(md: string) {
+ // The `async: false` option MUST remain. This method is FastInvoked (FastInvoke)
+ // from the .NET side, which requires a synchronous string return. marked.parse returns a
+ // Promise when async is true, which would silently turn the FastInvoke call into a
+ // fire-and-forget with no test catching the regression. Use parseAsync for async needs.
let html = marked.parse(md, { async: false });
return html;
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
index 1a6accf590..5bb13c6d94 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
@@ -2,15 +2,19 @@
internal static class BitMarkdownViewerJsRuntimeExtensions
{
- public static ValueTask BitMarkdownViewerCheckScriptLoaded(this IJSRuntime jsRuntime, string script)
+ // FastInvoke returns null when the runtime can't service interop or a JSON/JS interop error is
+ // swallowed on the in-process (WASM) path. Nullable distinguishes that from a legitimate false.
+ public static ValueTask BitMarkdownViewerCheckScriptLoaded(this IJSRuntime jsRuntime, string script)
{
- return jsRuntime.FastInvoke("BitBlazorUI.MarkdownViewer.checkScriptLoaded", script);
+ return jsRuntime.FastInvoke("BitBlazorUI.MarkdownViewer.checkScriptLoaded", script);
}
- public static ValueTask BitMarkdownViewerParse(this IJSRuntime jsRuntime, string markdown, string? middleware)
+ // FastInvoke/Invoke return null when the runtime can't service interop or a JSON/JS interop error
+ // is swallowed on the in-process (WASM) path. Nullable surfaces that so call sites can coalesce.
+ public static ValueTask BitMarkdownViewerParse(this IJSRuntime jsRuntime, string markdown, string? middleware)
{
return OperatingSystem.IsBrowser() && middleware.HasNoValue()
- ? jsRuntime.FastInvoke("BitBlazorUI.MarkdownViewer.parse", markdown)
- : jsRuntime.Invoke("BitBlazorUI.MarkdownViewer.parseAsync", markdown, middleware);
+ ? jsRuntime.FastInvoke("BitBlazorUI.MarkdownViewer.parse", markdown)
+ : jsRuntime.Invoke("BitBlazorUI.MarkdownViewer.parseAsync", markdown, middleware);
}
}
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfReader/BitPdfReaderJsRuntimeExtensions.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfReader/BitPdfReaderJsRuntimeExtensions.cs
index f1e7c1eff2..d489b10eaa 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfReader/BitPdfReaderJsRuntimeExtensions.cs
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfReader/BitPdfReaderJsRuntimeExtensions.cs
@@ -9,16 +9,21 @@ public static ValueTask BitPdfReaderSetup(this IJSRuntime jsRuntime, BitPdf
public static ValueTask BitPdfReaderRenderPage(this IJSRuntime jsRuntime, string id, int pageNumber)
{
+ // The JS renderPage is async (awaits pdf.js page rendering). FastInvoke would use the
+ // synchronous in-process path in WASM, discarding the returned Promise (fire-and-forget),
+ // so callers would proceed/raise events before rendering completes and errors would be lost.
return jsRuntime.InvokeVoid("BitBlazorUI.PdfReader.renderPage", id, pageNumber);
}
public static ValueTask BitPdfReaderRefreshPage(this IJSRuntime jsRuntime, BitPdfReaderConfig config, int pageNumber)
{
+ // The JS refreshPage is async (awaits renderPage). See BitPdfReaderRenderPage for why
+ // the asynchronous invocation must be used instead of the synchronous fast-invoke.
return jsRuntime.InvokeVoid("BitBlazorUI.PdfReader.refreshPage", config, pageNumber);
}
public static ValueTask BitPdfReaderDispose(this IJSRuntime jsRuntime, string id)
{
- return jsRuntime.InvokeVoid("BitBlazorUI.PdfReader.dispose", id);
+ return jsRuntime.FastInvokeVoid("BitBlazorUI.PdfReader.dispose", id);
}
}
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PhoneInput/BitPhoneInput.razor.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PhoneInput/BitPhoneInput.razor.cs
index baa5dc51a6..b1e2a710d8 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PhoneInput/BitPhoneInput.razor.cs
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PhoneInput/BitPhoneInput.razor.cs
@@ -254,7 +254,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
if (_isOpen && _activeIndex >= 0 && _activeIndex != _lastScrolledIndex)
{
_lastScrolledIndex = _activeIndex;
- await _js.BitExtrasScrollOptionIntoView(GetOptionId(_activeIndex));
+ await _js.BitExtrasScrollElementIntoView(GetOptionId(_activeIndex));
}
await base.OnAfterRenderAsync(firstRender);
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.razor.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.razor.cs
index 7995d58d1b..4c15ac6d63 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.razor.cs
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.razor.cs
@@ -317,11 +317,11 @@ private async Task ToggleScroll(bool isOpen)
if (ScrollerElement.HasValue)
{
- _offsetTop = await _js.BitUtilsToggleOverflow(ScrollerElement.Value, isOpen);
+ _offsetTop = await _js.BitUtilsToggleOverflow(ScrollerElement.Value, isOpen) ?? 0;
}
else
{
- _offsetTop = await _js.BitUtilsToggleOverflow(ScrollerSelector ?? "body", isOpen);
+ _offsetTop = await _js.BitUtilsToggleOverflow(ScrollerSelector ?? "body", isOpen) ?? 0;
}
}
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Extensions/JsInterop/ExtrasJsRuntimeExtensions.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Extensions/JsInterop/ExtrasJsRuntimeExtensions.cs
index 68a2da41d3..4f78091e41 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Extensions/JsInterop/ExtrasJsRuntimeExtensions.cs
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Extensions/JsInterop/ExtrasJsRuntimeExtensions.cs
@@ -4,17 +4,17 @@ internal static class ExtrasJsRuntimeExtensions
{
internal static ValueTask BitExtrasApplyRootClasses(this IJSRuntime jsRuntime, List cssClasses, Dictionary cssVariables)
{
- return jsRuntime.InvokeVoid("BitBlazorUI.Extras.applyRootClasses", cssClasses, cssVariables);
+ return jsRuntime.FastInvokeVoid("BitBlazorUI.Extras.applyRootClasses", cssClasses, cssVariables);
}
internal static ValueTask BitExtrasGoToTop(this IJSRuntime jsRuntime, ElementReference element, BitScrollBehavior? behavior = null)
{
- return jsRuntime.InvokeVoid("BitBlazorUI.Extras.goToTop", element, behavior?.ToString().ToLowerInvariant());
+ return jsRuntime.FastInvokeVoid("BitBlazorUI.Extras.goToTop", element, behavior?.ToString().ToLowerInvariant());
}
internal static ValueTask BitExtrasScrollBy(this IJSRuntime jsRuntime, ElementReference element, decimal x, decimal y)
{
- return jsRuntime.InvokeVoid("BitBlazorUI.Extras.scrollBy", element, x, y);
+ return jsRuntime.FastInvokeVoid("BitBlazorUI.Extras.scrollBy", element, x, y);
}
public static ValueTask BitExtrasInitScripts(this IJSRuntime jsRuntime, IEnumerable scripts, bool isModule = false)
@@ -29,16 +29,16 @@ public static ValueTask BitExtrasInitStylesheets(this IJSRuntime jsRuntime, IEnu
internal static ValueTask BitExtrasSetPreventKeys(this IJSRuntime jsRuntime, ElementReference element, string[] keys)
{
- return jsRuntime.InvokeVoid("BitBlazorUI.Extras.setPreventKeys", element, keys);
+ return jsRuntime.FastInvokeVoid("BitBlazorUI.Extras.setPreventKeys", element, keys);
}
internal static ValueTask BitExtrasDisposePreventKeys(this IJSRuntime jsRuntime, ElementReference element)
{
- return jsRuntime.InvokeVoid("BitBlazorUI.Extras.disposePreventKeys", element);
+ return jsRuntime.FastInvokeVoid("BitBlazorUI.Extras.disposePreventKeys", element);
}
- internal static ValueTask BitExtrasScrollOptionIntoView(this IJSRuntime jsRuntime, string optionId)
+ internal static ValueTask BitExtrasScrollElementIntoView(this IJSRuntime jsRuntime, string elementId)
{
- return jsRuntime.InvokeVoid("BitBlazorUI.Extras.scrollOptionIntoView", optionId);
+ return jsRuntime.FastInvokeVoid("BitBlazorUI.Extras.scrollElementIntoView", elementId);
}
}
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Scripts/Extras.ts b/src/BlazorUI/Bit.BlazorUI.Extras/Scripts/Extras.ts
index f5f215e628..5997f3f255 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Scripts/Extras.ts
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Scripts/Extras.ts
@@ -18,7 +18,7 @@ namespace BitBlazorUI {
element.scrollBy(x, y);
}
-
+
// Attaches (or updates) a deterministic keydown listener that calls preventDefault
// for the provided keys. Unlike Blazor's `@onkeydown:preventDefault` binding -- whose
// value is evaluated at render time and therefore only applies to the *next* key event
@@ -52,93 +52,240 @@ namespace BitBlazorUI {
delete el.bitPreventKeys;
}
- // Scrolls the option element into the visible area of its scroll container using
+ // Scrolls the element into the visible area of its scroll container using
// 'nearest' so keyboard navigation keeps the active item on screen with minimal movement.
- public static scrollOptionIntoView(optionId: string) {
- if (!optionId) return;
+ public static scrollElementIntoView(elementId: string) {
+ if (!elementId) return;
- const element = document.getElementById(optionId);
+ const element = document.getElementById(elementId);
if (!element) return;
try {
element.scrollIntoView({ block: 'nearest', inline: 'nearest' });
- } catch (e) { console.error('BitBlazorUI.Extras.scrollOptionIntoView:', e); }
+ } catch (e) { console.error('BitBlazorUI.Extras.scrollElementIntoView:', e); }
}
-
- private static _initScriptsPromises: { [key: string]: Promise } = {};
+
public static async initScripts(scripts: string[], isModule: boolean) {
- const key = scripts.join('|');
- if (Extras._initScriptsPromises[key] !== undefined) {
- return Extras._initScriptsPromises[key];
+ // Resolve only when every script has actually executed. Loading is tracked per-url so that
+ // concurrent callers (e.g. several components, or a re-mount) await the same execution instead
+ // of a second caller seeing the