MediaWiki:Common.js: Difference between revisions
MediaWiki interface page
More actions
No edit summary |
No edit summary |
||
| Line 348: | Line 348: | ||
* - Reads: data-sv-def-tip, data-sv-def-link | * - Reads: data-sv-def-tip, data-sv-def-link | ||
* - Works with spans output by Module:Definitions | * - Works with spans output by Module:Definitions | ||
* | * ========================================================================= */ | ||
(function (mw) { | (function (mw) { | ||
"use strict"; | "use strict"; | ||
| Line 358: | Line 358: | ||
var activeEl = null; | var activeEl = null; | ||
var pinned = false; | var pinned = false; | ||
var lastPoint = null; // {x,y} for touch positioning | |||
function qTipText(el) { | function qTipText(el) { | ||
| Line 391: | Line 392: | ||
tipEl.style.lineHeight = "1.25"; | tipEl.style.lineHeight = "1.25"; | ||
tipEl.style.boxShadow = "0 8px 24px rgba(0,0,0,0.35)"; | tipEl.style.boxShadow = "0 8px 24px rgba(0,0,0,0.35)"; | ||
tipEl.style.pointerEvents = "none"; | tipEl.style.pointerEvents = "none"; // toggled in position/show | ||
tipInner = document.createElement("div"); | tipInner = document.createElement("div"); | ||
| Line 432: | Line 433: | ||
tipEl.style.top = "0px"; | tipEl.style.top = "0px"; | ||
tipEl.style.display = "block"; | tipEl.style.display = "block"; | ||
tipEl.style.pointerEvents = pinned ? "auto" : "none"; | |||
var tr = tipEl.getBoundingClientRect(); | var tr = tipEl.getBoundingClientRect(); | ||
| Line 448: | Line 450: | ||
tipEl.style.left = Math.round(x) + "px"; | tipEl.style.left = Math.round(x) + "px"; | ||
tipEl.style.top = Math.round(y) + "px"; | tipEl.style.top = Math.round(y) + "px"; | ||
} | |||
// Position relative to a screen point (used for touch so the finger doesn't cover it) | |||
function positionTipPoint(clientX, clientY) { | |||
if (!tipEl) return; | |||
// Ensure we can measure tip size | |||
tipEl.style.left = "0px"; | |||
tipEl.style.top = "0px"; | |||
tipEl.style.display = "block"; | |||
tipEl.style.pointerEvents = pinned ? "auto" : "none"; | |||
var tr = tipEl.getBoundingClientRect(); | |||
var pad = 8; | |||
var gap = 12; | |||
var x = clientX - (tr.width / 2); | |||
x = clamp(x, pad, window.innerWidth - tr.width - pad); | |||
// Prefer ABOVE the finger/cursor | |||
var y = clientY - tr.height - gap; | |||
// If not enough room above, drop below | |||
if (y < pad) { | |||
y = clientY + gap; | |||
} | |||
y = clamp(y, pad, window.innerHeight - tr.height - pad); | |||
tipEl.style.left = Math.round(x) + "px"; | |||
tipEl.style.top = Math.round(y) + "px"; | |||
lastPoint = { x: clientX, y: clientY }; | |||
} | } | ||
| Line 462: | Line 495: | ||
suppressTitle(el); | suppressTitle(el); | ||
// Default show behavior anchors to the element | |||
positionTip(el); | positionTip(el); | ||
} | } | ||
| Line 475: | Line 510: | ||
activeEl = null; | activeEl = null; | ||
pinned = false; | pinned = false; | ||
lastPoint = null; | |||
} | } | ||
| Line 487: | Line 523: | ||
// Hover in/out (desktop) | // Hover in/out (desktop) | ||
document.addEventListener("mouseover", function (e) { | document.addEventListener( | ||
"mouseover", | |||
function (e) { | |||
if (pinned) return; | |||
var el = closestDef(e.target); | |||
if (!el) return; | |||
if (isEntering(el, e.relatedTarget)) return; | |||
}, true); | if (activeEl === el) return; | ||
show(el); | |||
}, | |||
true | |||
); | |||
document.addEventListener( | |||
"mouseout", | |||
function (e) { | |||
if (pinned) return; | |||
var el = closestDef(e.target); | |||
if (!el) return; | |||
if (isEntering(el, e.relatedTarget)) return; | |||
if (activeEl === el) hide(); | |||
}, | |||
true | |||
); | |||
// Touch: pin tooltip and position ABOVE the finger press point. | |||
// We do this on pointerdown to avoid the finger hiding the tooltip. | |||
document.addEventListener( | |||
"pointerdown", | |||
function (e) { | |||
var el = closestDef(e.target); | |||
if (!el) return; | |||
if (e.pointerType !== "touch") return; | |||
// If we have a future link, let normal navigation happen. | |||
if (qLink(el)) return; | |||
var txt = qTipText(el); | |||
if (!txt) return; | |||
pinned = true; | |||
show(el); | |||
positionTipPoint(e.clientX, e.clientY); | |||
// Prevent the follow-up click from immediately toggling/closing. | |||
e.preventDefault(); | |||
e.stopPropagation(); | |||
}, | |||
true | |||
); | |||
// Tap/click behavior: | // Tap/click behavior: | ||
// - If data-sv-def-link is set, navigate (future-ready). | // - If data-sv-def-link is set, navigate (future-ready). | ||
// - Otherwise toggle a pinned tooltip (mobile-friendly). | // - Otherwise toggle a pinned tooltip (mobile-friendly). | ||
document.addEventListener("click", function (e) { | document.addEventListener( | ||
"click", | |||
function (e) { | |||
var el = closestDef(e.target); | |||
// Click outside closes a pinned tooltip | |||
if (!el) { | |||
if (pinned) hide(); | |||
return; | return; | ||
} | |||
var link = qLink(el); | |||
if (link) { | |||
window.location.href = mw.util.getUrl(link); | |||
return; | |||
} | |||
var txt = qTipText(el); | |||
if (!txt) return; | |||
// Toggle pin | |||
e.preventDefault(); | |||
e.stopPropagation(); | |||
if (pinned && activeEl === el) { | |||
hide(); | |||
return; | |||
} | |||
pinned = true; | |||
show(el); | |||
// For non-touch click, keep element-anchored positioning. | |||
// (Touch uses pointerdown positioning already.) | |||
lastPoint = null; | |||
}, | |||
true | |||
); | |||
// Close on Escape | // Close on Escape | ||
document.addEventListener("keydown", function (e) { | document.addEventListener( | ||
"keydown", | |||
function (e) { | |||
if (e.key === "Escape" && (pinned || activeEl)) { | |||
hide(); | |||
} | |||
}, | |||
true | |||
); | |||
// Reposition if visible | // Reposition if visible | ||
window.addEventListener("resize", function () { | window.addEventListener( | ||
"resize", | |||
function () { | |||
if (tipEl && tipEl.style.display === "block") { | |||
if (pinned && lastPoint) positionTipPoint(lastPoint.x, lastPoint.y); | |||
else if (activeEl) positionTip(activeEl); | |||
} | |||
}, | |||
true | |||
); | |||
// Capture scroll events from containers too | // Capture scroll events from containers too | ||
window.addEventListener("scroll", function () { | window.addEventListener( | ||
"scroll", | |||
function () { | |||
if (tipEl && tipEl.style.display === "block") { | |||
if (pinned && lastPoint) positionTipPoint(lastPoint.x, lastPoint.y); | |||
else if (activeEl) positionTip(activeEl); | |||
} | |||
}, | |||
true | |||
); | |||
// Support dynamic page content (VisualEditor, etc.) | // Support dynamic page content (VisualEditor, etc.) | ||