mirror of
https://github.com/isledecomp/isle.pizza.git
synced 2026-02-28 22:07:39 +00:00
Add dynamic tooltip positioning using Floating UI (#18)
- Use @floating-ui/dom for intelligent tooltip placement - Tooltips now shift/flip to stay within viewport bounds - Collapse hidden tooltips to prevent horizontal scroll overflow - Move tooltip setup to App.svelte for global coverage
This commit is contained in:
parent
eed65faeac
commit
a680bc67ef
23
package-lock.json
generated
23
package-lock.json
generated
@ -8,6 +8,7 @@
|
|||||||
"name": "isle.pizza",
|
"name": "isle.pizza",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@floating-ui/dom": "^1.7.5",
|
||||||
"three": "^0.182.0"
|
"three": "^0.182.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -1892,6 +1893,28 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@floating-ui/core": {
|
||||||
|
"version": "1.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz",
|
||||||
|
"integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/utils": "^0.2.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/dom": {
|
||||||
|
"version": "1.7.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz",
|
||||||
|
"integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/core": "^1.7.4",
|
||||||
|
"@floating-ui/utils": "^0.2.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/utils": {
|
||||||
|
"version": "0.2.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
|
||||||
|
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="
|
||||||
|
},
|
||||||
"node_modules/@isaacs/balanced-match": {
|
"node_modules/@isaacs/balanced-match": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
"prepare:assets": "node scripts/prepare.js"
|
"prepare:assets": "node scripts/prepare.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@floating-ui/dom": "^1.7.5",
|
||||||
"three": "^0.182.0"
|
"three": "^0.182.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import { computePosition, flip, shift, offset } from '@floating-ui/dom';
|
||||||
import { currentPage, debugEnabled } from './stores.js';
|
import { currentPage, debugEnabled } from './stores.js';
|
||||||
import { registerServiceWorker, checkCacheStatus } from './core/service-worker.js';
|
import { registerServiceWorker, checkCacheStatus } from './core/service-worker.js';
|
||||||
import { setupCanvasEvents } from './core/emscripten.js';
|
import { setupCanvasEvents } from './core/emscripten.js';
|
||||||
@ -15,6 +16,54 @@
|
|||||||
import DebugPanel from './lib/DebugPanel.svelte';
|
import DebugPanel from './lib/DebugPanel.svelte';
|
||||||
import CanvasWrapper from './lib/CanvasWrapper.svelte';
|
import CanvasWrapper from './lib/CanvasWrapper.svelte';
|
||||||
|
|
||||||
|
async function positionTooltip(trigger) {
|
||||||
|
const tooltip = trigger.querySelector('.tooltip-content');
|
||||||
|
if (!tooltip) return;
|
||||||
|
|
||||||
|
const { x, y } = await computePosition(trigger, tooltip, {
|
||||||
|
placement: 'top',
|
||||||
|
middleware: [
|
||||||
|
offset(8),
|
||||||
|
flip(),
|
||||||
|
shift({ padding: 8 })
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.assign(tooltip.style, {
|
||||||
|
left: `${x}px`,
|
||||||
|
top: `${y}px`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupTooltips() {
|
||||||
|
const isTouchDevice = window.matchMedia('(any-pointer: coarse)').matches;
|
||||||
|
|
||||||
|
// Touch devices: position and show on click
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
const trigger = e.target.closest('.tooltip-trigger');
|
||||||
|
if (trigger) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const wasActive = trigger.classList.contains('active');
|
||||||
|
document.querySelectorAll('.tooltip-trigger.active').forEach(t => t.classList.remove('active'));
|
||||||
|
if (!wasActive) {
|
||||||
|
positionTooltip(trigger);
|
||||||
|
trigger.classList.add('active');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.querySelectorAll('.tooltip-trigger.active').forEach(t => t.classList.remove('active'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Desktop: position on hover
|
||||||
|
if (!isTouchDevice) {
|
||||||
|
document.addEventListener('mouseenter', (e) => {
|
||||||
|
const trigger = e.target.closest('.tooltip-trigger');
|
||||||
|
if (trigger) positionTooltip(trigger);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
// Disable browser's automatic scroll restoration
|
// Disable browser's automatic scroll restoration
|
||||||
if ('scrollRestoration' in history) {
|
if ('scrollRestoration' in history) {
|
||||||
@ -30,6 +79,9 @@
|
|||||||
// Setup canvas events
|
// Setup canvas events
|
||||||
setupCanvasEvents();
|
setupCanvasEvents();
|
||||||
|
|
||||||
|
// Setup global tooltip positioning
|
||||||
|
setupTooltips();
|
||||||
|
|
||||||
// Initialize history state based on current page
|
// Initialize history state based on current page
|
||||||
const initialHash = window.location.hash;
|
const initialHash = window.location.hash;
|
||||||
if (initialHash) {
|
if (initialHash) {
|
||||||
|
|||||||
16
src/app.css
16
src/app.css
@ -786,13 +786,13 @@ body {
|
|||||||
|
|
||||||
.tooltip-content {
|
.tooltip-content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 140%;
|
top: 0;
|
||||||
left: 50%;
|
left: 0;
|
||||||
transform: translateX(-50%);
|
width: max-content;
|
||||||
width: 220px;
|
max-width: 220px;
|
||||||
background-color: var(--color-bg-panel);
|
background-color: var(--color-bg-panel);
|
||||||
color: var(--color-text-light);
|
color: var(--color-text-light);
|
||||||
padding: 10px;
|
padding: 0;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
@ -802,14 +802,18 @@ body {
|
|||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
transition: opacity 0.2s, visibility 0.2s;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip-trigger:hover>.tooltip-content,
|
.tooltip-trigger:hover>.tooltip-content,
|
||||||
.tooltip-trigger.active>.tooltip-content {
|
.tooltip-trigger.active>.tooltip-content {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
padding: 10px;
|
||||||
|
max-height: none;
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.option-list {
|
.option-list {
|
||||||
|
|||||||
@ -101,28 +101,8 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup tooltip handling for touch devices
|
|
||||||
if (isTouchDevice) {
|
|
||||||
setupTouchTooltips();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function setupTouchTooltips() {
|
|
||||||
document.addEventListener('click', (e) => {
|
|
||||||
const trigger = e.target.closest('.tooltip-trigger');
|
|
||||||
if (trigger) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
const wasActive = trigger.classList.contains('active');
|
|
||||||
document.querySelectorAll('.tooltip-trigger.active').forEach(t => t.classList.remove('active'));
|
|
||||||
if (!wasActive) trigger.classList.add('active');
|
|
||||||
} else {
|
|
||||||
document.querySelectorAll('.tooltip-trigger.active').forEach(t => t.classList.remove('active'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSiFiles() {
|
function getSiFiles() {
|
||||||
const hdMusic = document.getElementById('check-hd-music');
|
const hdMusic = document.getElementById('check-hd-music');
|
||||||
const widescreenBgs = document.getElementById('check-widescreen-bgs');
|
const widescreenBgs = document.getElementById('check-widescreen-bgs');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user