Skip to content
Merged
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
10 changes: 5 additions & 5 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"url": "git+https://github.com/opencor/webapp.git"
},
"type": "module",
"version": "0.20260611.2",
"version": "0.20260612.0",
"engines": {
"bun": ">=1.2.0"
},
Expand Down
10 changes: 5 additions & 5 deletions src/renderer/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
},
"./style.css": "./dist/opencor.css"
},
"version": "0.20260611.2",
"version": "0.20260612.0",
"libopencorVersion": "0.20260604.0",
"scripts": {
"build": "vite build && bun scripts/generate.version.js",
Expand Down
43 changes: 36 additions & 7 deletions src/renderer/src/common/vueCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,47 @@ export const trackElementHeight = (
return stopTrackingElementHeight;
};

// A composable that provides the `.opencor` element as an append target for overlays.
// Note: this is needed when OpenCOR is embedded as a Vue 3 component in a host app that uses full-screen mode. In such
// a case, the Fullscreen API only renders the full-screen element and its descendants, so teleporting to
// `document.body` makes overlays invisible. Instead, teleporting to `.opencor` means that overlays are visible in
// full-screen mode. And, since `.opencor` is a child of the full-screen element, they are also visible in normal
// mode.
// A composable that provides a `position: fixed` overlay container inside `.opencor` as an append target for overlays.
// This is needed when OpenCOR is embedded as a Vue 3 component in a host app that uses full-screen mode. In such a
// case, the Fullscreen API only renders the full-screen element and its descendants, so teleporting to `document.body`
// makes overlays invisible. Teleporting to `.opencor` (via this fixed container) keeps overlays visible in full-screen
// mode.
//
// The `position: fixed` container at viewport origin (0, 0) also solves vertical offset issues when the host app has a
// positioned ancestor (e.g., `position: relative` on a wrapper). PrimeVue's `absolutePosition()` computes
// document-absolute coordinates, which rely on the offset parent being at document origin. A fixed container at (0, 0)
// provides that reference frame, so overlays appear at the correct position.

export const useAppendTarget = () => {
const appendTarget = vue.shallowRef<HTMLElement | undefined>(undefined);
const containerClass = 'opencor-overlay-container';

vue.onMounted(() => {
appendTarget.value = (document.querySelector('.opencor') as HTMLElement | null) ?? undefined;
const opencor = document.querySelector('.opencor');

if (opencor) {
let overlayContainer = opencor.querySelector(`.${containerClass}`) as HTMLElement | null;

if (!overlayContainer) {
overlayContainer = document.createElement('div');

overlayContainer.className = containerClass;
overlayContainer.style.cssText =
'position: fixed; top: 0; left: 0; width: 0; height: 0; overflow: visible; pointer-events: none; z-index: 99999;';

// Restore pointer events for overlay content teleported into the container.

overlayContainer.appendChild(
Object.assign(document.createElement('style'), {
textContent: `.${containerClass} > * { pointer-events: auto; }`
})
);

opencor.appendChild(overlayContainer);
}

appendTarget.value = overlayContainer;
}
});

return appendTarget;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@
optionValue="value"
class="w-full"
size="small"
:appendTo="issuesContainer"
:appendTo="appendTarget"
/>
<label>Type</label>
</FloatLabel>
Expand Down Expand Up @@ -344,7 +344,7 @@
size="small"
filter filterMode="lenient"
:options="editableModelParameters"
:appendTo="issuesContainer"
:appendTo="appendTarget"
/>
<label>Model parameter</label>
</FloatLabel>
Expand Down Expand Up @@ -415,7 +415,7 @@
size="small"
filter filterMode="lenient"
:options="allModelParameters"
:appendTo="issuesContainer"
:appendTo="appendTarget"
/>
<label>Model parameter</label>
</FloatLabel>
Expand Down Expand Up @@ -553,7 +553,7 @@
size="small"
filter filterMode="lenient"
:options="externalDataSeries(externalDataFile)"
:appendTo="issuesContainer"
:appendTo="appendTarget"
/>
<label>External data</label>
</FloatLabel>
Expand Down Expand Up @@ -790,7 +790,7 @@
<!-- Simulation issues -->

<template v-if="activeTab === 'simulation'">
<Popover ref="simulationSettingsIssuesPopoverRef" v-if="simulationSettingsIssues.length" :appendTo="issuesContainer">
<Popover ref="simulationSettingsIssuesPopoverRef" v-if="simulationSettingsIssues.length" :appendTo="appendTarget">
<div class="issues-popover-content">
<IssuesView :issues="simulationSettingsIssues" :extraSpace="false" />
</div>
Expand All @@ -813,7 +813,7 @@
<!-- Solvers issues -->

<template v-else-if="activeTab === 'solvers'">
<Popover ref="solversSettingsIssuesPopoverRef" v-if="solversSettingsIssues.length" :appendTo="issuesContainer">
<Popover ref="solversSettingsIssuesPopoverRef" v-if="solversSettingsIssues.length" :appendTo="appendTarget">
<div class="issues-popover-content">
<IssuesView :issues="solversSettingsIssues" :extraSpace="false" />
</div>
Expand All @@ -836,7 +836,7 @@
<!-- UI JSON issues -->

<template v-else-if="activeTab === 'interactive'">
<Popover ref="uiJsonIssuesPopoverRef" v-if="uiJsonIssues.length" :appendTo="issuesContainer">
<Popover ref="uiJsonIssuesPopoverRef" v-if="uiJsonIssues.length" :appendTo="appendTarget">
<div class="issues-popover-content">
<IssuesView :issues="uiJsonIssues" :extraSpace="false" />
</div>
Expand Down Expand Up @@ -873,10 +873,11 @@
import Popover from 'primevue/popover';
import * as vue from 'vue';

import * as locApi from '../../libopencor/locApi';
import * as common from '../../common/common';
import { TOAST_LIFE } from '../../common/constants';
import * as externalData from '../../common/externalData';
import * as vueCommon from '../../common/vueCommon';
import * as locApi from '../../libopencor/locApi';
import * as locUiJsonApi from '../../libopencor/locUiJsonApi';
import { validateUiJson } from '../../libopencor/locUiJsonApi';
import { EIssueType } from '../../libopencor/locLoggerApi';
Expand Down Expand Up @@ -958,7 +959,7 @@ vue.watch(
const simulationSettingsIssuesPopoverRef = vue.ref<InstanceType<typeof Popover> | null>(null);
const solversSettingsIssuesPopoverRef = vue.ref<InstanceType<typeof Popover> | null>(null);
const uiJsonIssuesPopoverRef = vue.ref<InstanceType<typeof Popover> | null>(null);
const issuesContainer = vue.ref<HTMLElement>();
const appendTarget = vueCommon.useAppendTarget();
const activeTab = vue.ref(DEFAULT_TAB);
const activeInteractiveTab = vue.ref(DEFAULT_INTERACTIVE_TAB);
const showSimulationSettingsIssuesPanel = vue.ref(false);
Expand Down Expand Up @@ -1549,15 +1550,6 @@ const toggleUiJsonIssues = (event: Event) => {

showUiJsonIssuesPanel.value = !showUiJsonIssuesPanel.value;
};

vue.onMounted(() => {
// Set the popover append target to the main OpenCOR element ('.opencor') so that Tailwind styles scoped with
// `important: '.opencor'` in tailwind.config.ts are applied correctly.
// Note: it should never be null, but just in case we add a fallback to undefined (which will result in the popover
// being wrongly styled but at least functional instead of not being displayed at all).

issuesContainer.value = (document.querySelector('.opencor') as HTMLElement | null) || undefined;
});
</script>

<style scoped>
Expand Down
Loading