Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 40 additions & 13 deletions packages/base/src/Boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import fixSafariActiveState from "./util/fixSafariActiveState.js";

let booted = false;
let bootPromise: Promise<void>;
let openUI5ListenersAttached = false;
const eventProvider = new EventProvider<void, void>();

const isBooted = (): boolean => {
Expand All @@ -36,6 +37,36 @@ const attachBoot = (listener: () => void) => {
listener();
};

/**
* This function may now be called twice - once without OpenUI5Support, and then later again, when OpenUI5 is loaded dynamically
* In this case, deregister the UI5 Web Components listener
*/
const initF6Navigation = async () => {
const openUI5Support = getFeature<typeof OpenUI5Support>("OpenUI5Support");
const isOpenUI5Loaded = openUI5Support ? openUI5Support.isOpenUI5Detected() : false;
const f6Navigation = getFeature<typeof F6Navigation>("F6Navigation");

if (openUI5Support) {
f6Navigation && f6Navigation.destroy(); // F6Navigation is not needed when OpenUI5 is used
await openUI5Support.init();
}

if (f6Navigation && !isOpenUI5Loaded) {
f6Navigation.init();
}
};

const attachOpenUI5SupportListeners = () => {
if (openUI5ListenersAttached) {
return;
}

const openUI5Support = getFeature<typeof OpenUI5Support>("OpenUI5Support");
if (openUI5Support) {
openUI5ListenersAttached = openUI5Support.attachListeners(); // listeners will be attached (return true) only if OpenUI5 is loaded
}
};

const boot = async (): Promise<void> => {
if (bootPromise !== undefined) {
return bootPromise;
Expand All @@ -51,21 +82,10 @@ const boot = async (): Promise<void> => {

attachThemeRegistered(onThemeRegistered);

const openUI5Support = getFeature<typeof OpenUI5Support>("OpenUI5Support");
const isOpenUI5Loaded = openUI5Support ? openUI5Support.isOpenUI5Detected() : false;
const f6Navigation = getFeature<typeof F6Navigation>("F6Navigation");

if (openUI5Support) {
await openUI5Support.init();
}

if (f6Navigation && !isOpenUI5Loaded) {
f6Navigation.init();
}

await initF6Navigation(); // depends on OpenUI5Support
await whenDOMReady();
await applyTheme(getTheme());
openUI5Support && openUI5Support.attachListeners();
attachOpenUI5SupportListeners(); // depends on OpenUI5Support
insertFontFace();
insertSystemCSSVars();
insertScrollbarStyles();
Expand All @@ -81,6 +101,12 @@ const boot = async (): Promise<void> => {
return bootPromise;
};

const secondaryBoot = async (): Promise<void> => {
await boot(); // make sure we're not in the middle of boot before re-running the skipped parts
Copy link
Preview

Copilot AI Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The secondaryBoot function calls boot() which may perform unnecessary work if already completed. Consider checking isBooted() first or restructuring to only re-run the OpenUI5-specific parts.

Suggested change
await boot(); // make sure we're not in the middle of boot before re-running the skipped parts
if (!isBooted()) {
await boot(); // make sure we're not in the middle of boot before re-running the skipped parts
}

Copilot uses AI. Check for mistakes.

await initF6Navigation();
attachOpenUI5SupportListeners();
};

/**
* Callback, executed after theme properties registration
* to apply the newly registered theme.
Expand All @@ -95,6 +121,7 @@ const onThemeRegistered = (theme: string) => {

export {
boot,
secondaryBoot,
attachBoot,
isBooted,
};
5 changes: 5 additions & 0 deletions packages/base/src/features/F6Navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,11 @@ class F6Navigation {
f6Registry.instance = new F6Navigation();
}
}

static destroy() {
const f6Registry = getSharedResource<F6Registry>("F6Registry", {});
f6Registry.instance?.destroy();
}
}

registerFeature("F6Navigation", F6Navigation);
Expand Down
41 changes: 38 additions & 3 deletions packages/base/src/features/OpenUI5Support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { registerFeature } from "../FeaturesRegistry.js";
import { setTheme } from "../config/Theme.js";
import type { CLDRData } from "../asset-registries/LocaleData.js";
import type { LegacyDateCalendarCustomizing } from "../features/LegacyDateFormats.js";
import { secondaryBoot } from "../Boot.js";

type OpenUI5Core = {
attachInit: (callback: () => void) => void,
Expand Down Expand Up @@ -90,9 +91,42 @@ class OpenUI5Support {

static initPromise?: Promise<void>;

/**
* Important - if OpenUI5 is loaded after UI5 Web Components, configuration is not synchronized and it's up to the app to initialize OpenUI5 with the same settings as UI5 Web Components for consistency.
*/
static OpenUI5DelayedInit = async () => {
OpenUI5Support.init(); // This ensures patchPopover and patchPatcher are called; and from this point OpenUI5 CSS vars start being detected
await secondaryBoot(); // Re-run the parts of boot that were skipped due to OpenUI5 not having been loaded
}

static awaitForOpenUI5() {
const w = window as Record<string, any>;

const patchOnInit = (target: Record<string, any>) => {
const oldOnInit = target.onInit;
if (!oldOnInit) {
target.onInit = OpenUI5Support.OpenUI5DelayedInit;
} else {
target.onInit = function onInit(...rest: any[]) {
oldOnInit.apply(this, rest);
OpenUI5Support.OpenUI5DelayedInit();
};
}
};

// First, look for window.onInit and if found, patch it
if (w.onInit) {
patchOnInit(w);
}

// If window.onInit is not found, go for the sap-ui-config script
w["sap-ui-config"] = w["sap-ui-config"] || {};
patchOnInit(w["sap-ui-config"] as Record<string, any>);
}

static init() {
if (!OpenUI5Support.isOpenUI5Detected()) {
return Promise.resolve();
return OpenUI5Support.awaitForOpenUI5();
}

if (!OpenUI5Support.initPromise) {
Expand Down Expand Up @@ -152,7 +186,7 @@ class OpenUI5Support {
formatSettings: {
firstDayOfWeek: CalendarUtils.getWeekConfigurationValues().firstDayOfWeek,
legacyDateCalendarCustomizing: Formatting.getCustomIslamicCalendarData?.()
?? Formatting.getLegacyDateCalendarCustomizing?.(),
?? Formatting.getLegacyDateCalendarCustomizing?.(),
},
};
}
Expand Down Expand Up @@ -210,10 +244,11 @@ class OpenUI5Support {

static attachListeners() {
if (!OpenUI5Support.isOpenUI5Detected()) {
return;
return false;
}

OpenUI5Support._listenForThemeChange();
return true;
}

static cssVariablesLoaded() {
Expand Down
44 changes: 44 additions & 0 deletions packages/main/test/pages/Openui5.delayed.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="EN">

<head>
<title>OpenUI5 delayed loading</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta charset="utf-8">

<script>
window["sap-ui-config"] = {
onInit: function () {
console.log("The application has already defined onInit that must not be overwritten");
},
theme: "sap_horizon",
libs: "sap.m"
};
</script>

<script src="%VITE_BUNDLE_PATH%" type="module"></script>

<script>
setTimeout(() => {
var script = document.createElement("script");
script.id = "sap-ui-bootstrap";
script.src = "https://sdk.openui5.org/resources/sap-ui-core.js";
script.async = true;
document.head.appendChild(script);
setTimeout(() => {
new sap.m.DatePicker().placeAt("sapMDatePicker");
}, 1000);
}, 1000);
</script>
</head>

<body>
<div id="sapMDatePicker"></div>

<ui5-list id="side-nav-list" selection-mode="Single" separators="Inner">
<ui5-li type="Active" id="arg1" icon="developer-settings">ui5-button</ui5-li>
<ui5-li type="Active" id="bg1" icon="developer-settings">ui5-card</ui5-li>
<ui5-li type="Active" id="bg1" icon="developer-settings">ui5-checkbox</ui5-li>
</ui5-list>
</body>
</html>
Loading