MediaWiki:Common.js: Difference between revisions
MediaWiki interface page
More actions
No edit summary Tags: Reverted Mobile edit Mobile web edit |
No edit summary Tags: Reverted Mobile edit Mobile web edit |
||
| Line 1,576: | Line 1,576: | ||
(function () { | (function () { | ||
/* ================================================================== */ | /* ================================================================== */ | ||
/* MODULE: Universal Popups (v2) — refined | /* MODULE: Universal Popups (v2.1) — refined */ | ||
/* | /* Fixes: mobile close, keyboard open, summary default toggle, */ | ||
/* | /* hover enter/leave timer handling */ | ||
/* ================================================================== */ | /* ================================================================== */ | ||
| Line 1,585: | Line 1,584: | ||
var COMMON = (SV.common = SV.common || {}); | var COMMON = (SV.common = SV.common || {}); | ||
var POPV2_VERSION = | var POPV2_VERSION = 21; // 2.1 | ||
if (typeof COMMON.popupsV2Init === "number" && COMMON.popupsV2Init >= POPV2_VERSION) return; | if (typeof COMMON.popupsV2Init === "number" && COMMON.popupsV2Init >= POPV2_VERSION) return; | ||
COMMON.popupsV2Init = POPV2_VERSION; | COMMON.popupsV2Init = POPV2_VERSION; | ||
| Line 1,614: | Line 1,613: | ||
open: false, | open: false, | ||
pinned: false, | pinned: false, | ||
mode: "hover", | mode: "hover", // hover | click | ||
size: "sm", | size: "sm", // sm | lg | ||
trigger: null, | trigger: null, | ||
lastPointerType: "mouse", | lastPointerType: "mouse", | ||
| Line 1,639: | Line 1,638: | ||
function safeIdSelector(id) { | function safeIdSelector(id) { | ||
return "#" + String(id).replace(/([ !"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~])/g, "\\$1"); | return "#" + String(id).replace(/([ !"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~])/g, "\\$1"); | ||
} | |||
function setHidden(el, hidden) { | |||
if (!el) return; | |||
el.setAttribute("aria-hidden", hidden ? "true" : "false"); | |||
} | } | ||
| Line 1,648: | Line 1,652: | ||
el.setAttribute("role", "dialog"); | el.setAttribute("role", "dialog"); | ||
el.setAttribute("aria-hidden", "true"); | el.setAttribute("aria-hidden", "true"); | ||
// Keep hover popups from closing while pointer is over the popup itself | |||
el.addEventListener("mouseenter", function () { | |||
if (!HOVER_CAPABLE) return; | |||
if (!state.open || state.pinned) return; | |||
clearTimeout(state.hideTimer); | |||
state.hideTimer = 0; | |||
}); | |||
el.addEventListener("mouseleave", function () { | |||
if (!HOVER_CAPABLE) return; | |||
if (!state.open || state.pinned) return; | |||
clearTimeout(state.hideTimer); | |||
state.hideTimer = setTimeout(function () { | |||
if (state.open && !state.pinned) hidePopup(); | |||
}, 80); | |||
}); | |||
document.body.appendChild(el); | document.body.appendChild(el); | ||
popEl = el; | popEl = el; | ||
return popEl; | return popEl; | ||
} | } | ||
| Line 1,700: | Line 1,716: | ||
if (t == null) return ""; | if (t == null) return ""; | ||
return String(t).trim(); | return String(t).trim(); | ||
} | |||
function isSummaryTrigger(trigger) { | |||
return !!(trigger && trigger.tagName === "SUMMARY"); | |||
} | } | ||
| Line 1,763: | Line 1,783: | ||
function guessTitle(trigger, sourceNode) { | function guessTitle(trigger, sourceNode) { | ||
var pop = null; | var pop = null; | ||
if (sourceNode && sourceNode.nodeType === 1) | if (sourceNode && sourceNode.nodeType === 1) pop = closest(sourceNode, POPUP_POP_SEL); | ||
if (pop) { | if (pop) { | ||
var t = pop.querySelector(".sv-tip-pop-title") || pop.querySelector(".sv-disclose-pop-title"); | var t = pop.querySelector(".sv-tip-pop-title") || pop.querySelector(".sv-disclose-pop-title"); | ||
| Line 1,793: | Line 1,812: | ||
else a.href = String(linkTitle); | else a.href = String(linkTitle); | ||
// | // Do not prevent default/capture on this link — Popups extension can hook it. | ||
wrap.appendChild(a); | wrap.appendChild(a); | ||
return wrap; | return wrap; | ||
| Line 1,850: | Line 1,869: | ||
head.addEventListener("click", function (e) { | head.addEventListener("click", function (e) { | ||
e.preventDefault(); | e.preventDefault(); | ||
hidePopup( | hidePopup(); | ||
}, { once: true }); | }, { once: true }); | ||
} | } | ||
| Line 1,975: | Line 1,994: | ||
if (!attrSize) size = (mode === "click") ? "lg" : "sm"; | if (!attrSize) size = (mode === "click") ? "lg" : "sm"; | ||
// No-hover devices: “hover” becomes tap-to-open (click-mode) so it can close properly. | |||
if (!HOVER_CAPABLE && mode === "hover") { | |||
mode = "click"; | |||
size = (attrSize === "lg") ? "lg" : "sm"; | |||
} | |||
if (isDef) { | if (isDef) { | ||
| Line 2,019: | Line 2,044: | ||
window.addEventListener("pointerdown", function (e) { | window.addEventListener("pointerdown", function (e) { | ||
if (e && e.pointerType) state.lastPointerType = e.pointerType; | if (!e) return; | ||
// ignore right/middle clicks | |||
if (typeof e.button === "number" && e.button !== 0) return; | |||
if (e.pointerType) state.lastPointerType = e.pointerType; | |||
// If user clicks inside popup, allow normal interactions (links, selection) | // If user clicks inside popup, allow normal interactions (links, selection) | ||
| Line 2,029: | Line 2,059: | ||
var opts = resolveOptsForTrigger(trigger); | var opts = resolveOptsForTrigger(trigger); | ||
if (!opts) return; | if (!opts) return; | ||
// Prevent native <details>/<summary> toggling whenever summary is used | |||
if (isSummaryTrigger(trigger)) e.preventDefault(); | |||
// Hover-mode on mouse: don’t click-open; let hover do it | // Hover-mode on mouse: don’t click-open; let hover do it | ||
| Line 2,040: | Line 2,073: | ||
if (state.open && state.pinned && state.trigger === trigger) { | if (state.open && state.pinned && state.trigger === trigger) { | ||
hidePopup( | hidePopup(); | ||
return; | return; | ||
} | |||
openPopup(trigger, opts); | |||
}, true); | |||
// Keyboard open (Enter/Space) for accessibility + summary safety | |||
window.addEventListener("keydown", function (e) { | |||
if (!e) return; | |||
if (e.key === "Escape") { | |||
if (state.open) { | |||
hidePopup(); | |||
e.stopPropagation(); | |||
} | |||
return; | |||
} | |||
var isActivate = (e.key === "Enter" || e.key === " "); | |||
if (!isActivate) return; | |||
if (isInsidePopup(e.target)) return; | |||
var trigger = findTrigger(e.target); | |||
if (!trigger) return; | |||
var opts = resolveOptsForTrigger(trigger); | |||
if (!opts) return; | |||
// Keyboard activation should behave like click-open so user can read/interact | |||
if (opts.mode === "hover") { | |||
opts.mode = "click"; | |||
opts.pinned = true; | |||
opts.size = "sm"; | |||
opts.hint = ""; | |||
opts.actionsNode = opts.actionsNode || null; | |||
} | |||
e.preventDefault(); | |||
e.stopPropagation(); | |||
// Stop native summary toggling as well | |||
if (isSummaryTrigger(trigger)) { | |||
try { | |||
var det = closest(trigger, POPUP_DETAILS_SEL); | |||
if (det && det.open) det.open = false; | |||
} catch (x) {} | |||
} | } | ||
| Line 2,050: | Line 2,129: | ||
window.addEventListener("mouseover", function (e) { | window.addEventListener("mouseover", function (e) { | ||
if (!HOVER_CAPABLE) return; | if (!HOVER_CAPABLE) return; | ||
if (isInsidePopup(e.target)) return; | |||
// If entering the popup itself, keep it open | |||
if (isInsidePopup(e.target)) { | |||
if (state.open && !state.pinned) { | |||
clearTimeout(state.hideTimer); | |||
state.hideTimer = 0; | |||
} | |||
return; | |||
} | |||
var trigger = findTrigger(e.target); | var trigger = findTrigger(e.target); | ||
if (!trigger) { | if (!trigger) { | ||
if (state.open && !state.pinned) hidePopup( | if (state.open && !state.pinned) hidePopup(); | ||
return; | return; | ||
} | } | ||
| Line 2,080: | Line 2,167: | ||
clearTimeout(state.hideTimer); | clearTimeout(state.hideTimer); | ||
state.hideTimer = setTimeout(function () { | state.hideTimer = setTimeout(function () { | ||
if (state.open && !state.pinned) hidePopup( | if (state.open && !state.pinned) hidePopup(); | ||
}, | }, 80); | ||
}, true); | }, true); | ||
// Outside click closes pinned | // Outside click closes (pinned always; and also closes “tap-open” popups on no-hover devices) | ||
window.addEventListener("click", function (e) { | window.addEventListener("click", function (e) { | ||
if (!state.open) return; | if (!state.open) return; | ||
if (isInsidePopup(e.target)) return; | if (isInsidePopup(e.target)) return; | ||
var trigger = findTrigger(e.target); | var trigger = findTrigger(e.target); | ||
if (trigger) { | if (trigger) { | ||
| Line 2,098: | Line 2,183: | ||
} | } | ||
if (state.pinned | if (state.pinned || !HOVER_CAPABLE) { | ||
hidePopup(); | |||
hidePopup( | |||
e.stopPropagation(); | e.stopPropagation(); | ||
} | } | ||