MediaWiki:Common.js
MediaWiki interface page
More actions
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* GameInfo JS (Phase 4.1)
Shared, reusable behaviors for all GameInfo modules. */
(function () {
if (window.SV_GAMEINFO_41_INIT) return;
window.SV_GAMEINFO_41_INIT = 1;
/* ------------------------------------------------------------------ */
/* Selectors */
/* ------------------------------------------------------------------ */
var CARD_SEL = ".sv-gi-card, .sv-skill-card, .sv-passive-card";
// Level (supports legacy <input type="range"> AND Phase 4.1 custom slider)
var LEVEL_RANGE_SEL =
"input.sv-level-range[type='range'], .sv-level-range--custom[data-sv-slider='1']";
var LEVEL_BOUNDARY_SEL = ".sv-skill-level, .sv-gi-level";
var LEVEL_SCOPE_SEL = ".sv-gi-bottom, .sv-skill-bottom";
var SERIES_SEL = "[data-series]";
// Popups (Phase 4.1 markup: span toggles + div popovers; legacy <details> supported)
var POPUP_DETAILS_SEL = "details.sv-tip, details.sv-disclose"; // legacy
var POPUP_WRAP_SEL = ".sv-tip, .sv-disclose";
var POPUP_BTN_SEL =
".sv-tip-btn[data-sv-toggle='1'], .sv-disclose-btn[data-sv-toggle='1']";
var POPUP_POP_SEL = ".sv-tip-pop, .sv-disclose-pop";
var POPUP_HIDDEN_CLASS = "sv-hidden";
// Tabs
var TABS_ROOT_SEL = ".sv-tabs[data-tabs='1']";
var TAB_SEL = ".sv-tab";
var PANEL_SEL = ".sv-tabpanel";
/* ------------------------------------------------------------------ */
/* Internals */
/* ------------------------------------------------------------------ */
var _seriesCache = typeof WeakMap !== "undefined" ? new WeakMap() : null;
var _tabsUidCounter = 0;
// custom slider drag state
var _drag = null;
/* ------------------------------------------------------------------ */
/* Utilities */
/* ------------------------------------------------------------------ */
function clampInt(value, min, max, fallback) {
var n = parseInt(value, 10);
if (isNaN(n)) n = fallback != null ? fallback : min;
if (n < min) return min;
if (n > max) return max;
return n;
}
function closest(el, sel) {
if (!el || el.nodeType !== 1) return null;
if (el.closest) return el.closest(sel);
while (el && el.nodeType === 1) {
if (el.matches && el.matches(sel)) return el;
el = el.parentElement;
}
return null;
}
function hasClass(el, cls) {
return !!(el && el.classList && el.classList.contains(cls));
}
function parseSeries(el) {
if (!el) return null;
if (_seriesCache) {
if (_seriesCache.has(el)) return _seriesCache.get(el);
} else if (el._svSeries !== undefined) {
return el._svSeries;
}
var raw = el.getAttribute("data-series");
var parsed = null;
if (raw != null && raw !== "") {
try {
parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) parsed = null;
} catch (e) {
parsed = null;
}
}
if (_seriesCache) _seriesCache.set(el, parsed);
else el._svSeries = parsed;
return parsed;
}
function isAfter(boundaryNode, el) {
if (!boundaryNode || !el) return true;
if (boundaryNode === el) return false;
if (!boundaryNode.compareDocumentPosition) return true;
return (boundaryNode.compareDocumentPosition(el) & 4) !== 0; // following
}
function isRangeInput(el) {
return !!(el && el.tagName === "INPUT" && el.type === "range");
}
function isCustomSlider(el) {
return !!(el && el.getAttribute && el.getAttribute("data-sv-slider") === "1");
}
/* ------------------------------------------------------------------ */
/* Level Selector */
/* ------------------------------------------------------------------ */
function getCardMaxLevel(card) {
return clampInt(card.getAttribute("data-max-level"), 1, 999, 1);
}
function getCardLevel(card, maxLevel) {
return clampInt(card.getAttribute("data-level"), 1, maxLevel, 1);
}
function getLevelSlider(card) {
return card.querySelector(LEVEL_RANGE_SEL);
}
function getLevelBoundary(slider) {
return closest(slider, LEVEL_BOUNDARY_SEL) || slider;
}
function getLevelScopeContainer(card, boundary) {
var scope = card.querySelector(LEVEL_SCOPE_SEL) || card;
if (boundary && scope !== card && !scope.contains(boundary)) return card;
return scope;
}
function setLevelNumber(card, level) {
var span = card.querySelector(".sv-level-num");
if (span) span.textContent = String(level);
}
function applySeriesToScope(scope, boundary, level) {
var index = level - 1;
var nodes = scope.querySelectorAll(SERIES_SEL);
for (var i = 0; i < nodes.length; i++) {
var el = nodes[i];
// Phase 4.1 rule: only update fields below the level boundary.
if (!isAfter(boundary, el)) continue;
var series = parseSeries(el);
if (!series || series.length === 0) continue;
// Clamp if series shorter than expected.
var safeIndex = index;
if (safeIndex >= series.length) safeIndex = series.length - 1;
if (safeIndex < 0) safeIndex = 0;
var value = series[safeIndex];
if (value == null) value = "";
// Text-only (no HTML mode).
el.textContent = String(value);
}
}
function getCustomMin(slider) {
return clampInt(
slider.getAttribute("aria-valuemin") || slider.getAttribute("data-min"),
1,
999,
1
);
}
function getCustomMax(slider) {
return clampInt(
slider.getAttribute("aria-valuemax") || slider.getAttribute("data-max"),
1,
999,
1
);
}
function getCustomValue(slider, fallback) {
return clampInt(
slider.getAttribute("aria-valuenow") || slider.getAttribute("data-value"),
1,
999,
fallback != null ? fallback : 1
);
}
function setCustomBounds(slider, min, max) {
slider.setAttribute("aria-valuemin", String(min));
slider.setAttribute("aria-valuemax", String(max));
slider.setAttribute("data-min", String(min));
slider.setAttribute("data-max", String(max));
}
function updateCustomVisual(slider, value) {
var min = getCustomMin(slider);
var max = getCustomMax(slider);
if (max < min) max = min;
var pct = max === min ? 0 : (value - min) / (max - min);
if (pct < 0) pct = 0;
if (pct > 1) pct = 1;
var left = pct * 100;
var thumb = slider.querySelector(".sv-level-thumb");
var fill = slider.querySelector(".sv-level-fill");
if (thumb && thumb.style) thumb.style.left = left + "%";
if (fill && fill.style) fill.style.width = left + "%";
}
function setCustomValue(slider, value) {
slider.setAttribute("aria-valuenow", String(value));
slider.setAttribute("data-value", String(value));
updateCustomVisual(slider, value);
}
function valueFromClientX(slider, clientX) {
var rect = slider.getBoundingClientRect();
var min = getCustomMin(slider);
var max = getCustomMax(slider);
if (max < min) max = min;
var w = rect.width || 1;
var x = (clientX - rect.left) / w;
if (x < 0) x = 0;
if (x > 1) x = 1;
var raw = min + x * (max - min);
var rounded = Math.round(raw);
return clampInt(rounded, min, max, min);
}
function applyLevelToCard(card, rawLevel) {
if (!card) return;
var maxLevel = getCardMaxLevel(card);
if (maxLevel <= 1) {
card.setAttribute("data-level", "1");
setLevelNumber(card, 1);
return;
}
var slider = getLevelSlider(card);
var level = clampInt(rawLevel, 1, maxLevel, getCardLevel(card, maxLevel));
// Store on card for other systems / debugging.
card.setAttribute("data-level", String(level));
// Keep slider in sync.
if (slider) {
if (isRangeInput(slider)) {
slider.setAttribute("min", "1");
slider.setAttribute("max", String(maxLevel));
if (String(slider.value) !== String(level)) slider.value = String(level);
} else if (isCustomSlider(slider)) {
setCustomBounds(slider, 1, maxLevel);
setCustomValue(slider, level);
}
}
setLevelNumber(card, level);
// Apply dynamic series values (scoped below boundary).
if (slider) {
var boundary = getLevelBoundary(slider);
var scope = getLevelScopeContainer(card, boundary);
applySeriesToScope(scope, boundary, level);
}
}
function initLevels(root) {
var container = root || document;
var cards = container.querySelectorAll(CARD_SEL);
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
if (card.getAttribute("data-gi-level-init") === "1") continue;
var maxLevel = getCardMaxLevel(card);
var slider = getLevelSlider(card);
// No slider (or max <= 1) => this card doesn't participate.
if (!slider || maxLevel <= 1) {
card.setAttribute("data-gi-level-init", "1");
continue;
}
var start;
if (isRangeInput(slider)) {
start = slider.value;
} else if (isCustomSlider(slider)) {
start = getCustomValue(slider, getCardLevel(card, maxLevel));
} else {
start = getCardLevel(card, maxLevel);
}
if (start == null || start === "") start = getCardLevel(card, maxLevel);
applyLevelToCard(card, start);
card.setAttribute("data-gi-level-init", "1");
}
}
// Slider input (delegated) — range input only
document.addEventListener(
"input",
function (e) {
var t = e.target;
if (!t || t.nodeType !== 1) return;
if (!t.matches || !t.matches("input.sv-level-range[type='range']")) return;
var card = closest(t, CARD_SEL);
if (!card) return;
applyLevelToCard(card, t.value);
},
true
);
// Custom slider pointer drag (delegated)
document.addEventListener(
"pointerdown",
function (e) {
var slider =
e.target && e.target.closest
? e.target.closest(".sv-level-range--custom[data-sv-slider='1']")
: null;
if (!slider) return;
var card = closest(slider, CARD_SEL);
if (!card) return;
e.preventDefault();
// focus for keyboard follow-up
if (slider.focus) slider.focus();
_drag = {
slider: slider,
card: card,
pointerId: e.pointerId,
};
if (slider.setPointerCapture) {
try {
slider.setPointerCapture(e.pointerId);
} catch (err) {}
}
var v = valueFromClientX(slider, e.clientX);
applyLevelToCard(card, v);
},
true
);
document.addEventListener(
"pointermove",
function (e) {
if (!_drag) return;
if (e.pointerId !== _drag.pointerId) return;
e.preventDefault();
var v = valueFromClientX(_drag.slider, e.clientX);
applyLevelToCard(_drag.card, v);
},
true
);
function endDrag(e) {
if (!_drag) return;
if (e && e.pointerId != null && e.pointerId !== _drag.pointerId) return;
if (_drag.slider && _drag.slider.releasePointerCapture) {
try {
_drag.slider.releasePointerCapture(_drag.pointerId);
} catch (err) {}
}
_drag = null;
}
document.addEventListener("pointerup", endDrag, true);
document.addEventListener("pointercancel", endDrag, true);
// Custom slider keyboard support
document.addEventListener(
"keydown",
function (e) {
var el = document.activeElement;
if (!el || !el.closest) return;
var slider = el.closest(".sv-level-range--custom[data-sv-slider='1']");
if (!slider) return;
var card = closest(slider, CARD_SEL);
if (!card) return;
var min = getCustomMin(slider);
var max = getCustomMax(slider);
if (max < min) max = min;
var cur = getCustomValue(slider, getCardLevel(card, max));
var next = cur;
if (e.key === "ArrowLeft" || e.key === "Left" || e.key === "ArrowDown") {
next = cur - 1;
} else if (
e.key === "ArrowRight" ||
e.key === "Right" ||
e.key === "ArrowUp"
) {
next = cur + 1;
} else if (e.key === "Home") {
next = min;
} else if (e.key === "End") {
next = max;
} else if (e.key === "PageDown") {
next = cur - 5;
} else if (e.key === "PageUp") {
next = cur + 5;
} else {
return;
}
e.preventDefault();
next = clampInt(next, min, max, cur);
applyLevelToCard(card, next);
},
true
);
/* ------------------------------------------------------------------ */
/* Popup (Phase 4.1: span toggles + legacy <details> support) */
/* ------------------------------------------------------------------ */
function hidePop(pop) {
if (!pop) return;
if (pop.classList) pop.classList.add(POPUP_HIDDEN_CLASS);
pop.setAttribute("hidden", "hidden");
}
function showPop(pop) {
if (!pop) return;
if (pop.classList) pop.classList.remove(POPUP_HIDDEN_CLASS);
pop.removeAttribute("hidden");
}
function getPopFromBtn(btn) {
if (!btn) return null;
var id = btn.getAttribute("aria-controls");
if (!id) return null;
// Prefer local scope so ids never collide across cards.
var wrap = closest(btn, POPUP_WRAP_SEL);
if (wrap) {
var local = wrap.querySelector("#" + id);
if (local) return local;
}
return document.getElementById(id);
}
function isBtnExpanded(btn) {
return btn && btn.getAttribute("aria-expanded") === "true";
}
function setBtnExpanded(btn, expanded) {
if (!btn) return;
btn.setAttribute("aria-expanded", expanded ? "true" : "false");
}
function closeBtnPopup(btn) {
var pop = getPopFromBtn(btn);
hidePop(pop);
setBtnExpanded(btn, false);
}
function openBtnPopup(btn) {
var pop = getPopFromBtn(btn);
showPop(pop);
setBtnExpanded(btn, true);
}
function closeAllBtnPopups(scope) {
var root = scope || document;
var openBtns = root.querySelectorAll(POPUP_BTN_SEL + "[aria-expanded='true']");
for (var i = 0; i < openBtns.length; i++) closeBtnPopup(openBtns[i]);
}
function closeOtherBtnPopupsWithinCard(btn) {
var card = closest(btn, CARD_SEL);
var scope = card || document;
var openBtns = scope.querySelectorAll(POPUP_BTN_SEL + "[aria-expanded='true']");
for (var i = 0; i < openBtns.length; i++) {
if (openBtns[i] !== btn) closeBtnPopup(openBtns[i]);
}
}
/* ---------- Legacy <details> helpers (safe to keep) ---------------- */
function closeDetails(d) {
if (d && d.open) d.open = false;
}
function closeAllDetails(scope) {
var root = scope || document;
var openOnes = root.querySelectorAll(POPUP_DETAILS_SEL + "[open]");
for (var i = 0; i < openOnes.length; i++) closeDetails(openOnes[i]);
}
function syncPopupAria(detailsEl) {
var summary = detailsEl.querySelector("summary");
if (!summary) return;
summary.setAttribute("aria-expanded", detailsEl.open ? "true" : "false");
var pop = detailsEl.querySelector(".sv-tip-pop, .sv-disclose-pop");
if (pop) {
if (!pop.id) pop.id = "svgi-pop-" + Math.random().toString(36).slice(2);
summary.setAttribute("aria-controls", pop.id);
}
}
/* ---------- Normalize Phase 4.1 popovers --------------------------- */
function normalizeBtnPopups(container) {
var root = container || document;
// Ensure .sv-hidden popovers also carry [hidden] for “no CSS yet” safety.
var pops = root.querySelectorAll(POPUP_POP_SEL);
for (var i = 0; i < pops.length; i++) {
var p = pops[i];
if (hasClass(p, POPUP_HIDDEN_CLASS) && !p.hasAttribute("hidden")) {
p.setAttribute("hidden", "hidden");
}
}
// Ensure aria-expanded matches actual visibility.
var btns = root.querySelectorAll(POPUP_BTN_SEL);
for (var j = 0; j < btns.length; j++) {
var b = btns[j];
var pop = getPopFromBtn(b);
var expanded = pop
? !hasClass(pop, POPUP_HIDDEN_CLASS) && !pop.hasAttribute("hidden")
: false;
setBtnExpanded(b, expanded);
}
}
/* ---------- Click toggle (works everywhere) ------------------------- */
document.addEventListener(
"click",
function (e) {
var btn = e.target && e.target.closest ? e.target.closest(POPUP_BTN_SEL) : null;
if (!btn) return;
e.preventDefault();
if (isBtnExpanded(btn)) {
closeBtnPopup(btn);
return;
}
closeOtherBtnPopupsWithinCard(btn);
openBtnPopup(btn);
},
true
);
/* Close on popover head click (great on touch) */
document.addEventListener(
"click",
function (e) {
var head = e.target && e.target.closest
? e.target.closest(".sv-tip-pop-head, .sv-disclose-pop-head")
: null;
if (!head) return;
var wrap = closest(head, POPUP_WRAP_SEL);
if (!wrap) return;
var btn = wrap.querySelector(POPUP_BTN_SEL);
if (!btn) return;
e.preventDefault();
closeBtnPopup(btn);
},
true
);
/* Keyboard toggle (Enter/Space) */
document.addEventListener(
"keydown",
function (e) {
if (!(e.key === "Enter" || e.key === " " || e.key === "Spacebar")) return;
var btn =
document.activeElement && document.activeElement.closest
? document.activeElement.closest(POPUP_BTN_SEL)
: null;
if (!btn) return;
e.preventDefault();
if (isBtnExpanded(btn)) closeBtnPopup(btn);
else {
closeOtherBtnPopupsWithinCard(btn);
openBtnPopup(btn);
}
},
true
);
/* ---------- Hover / rollover (desktop only) ------------------------ */
var _hoverCapable = false;
try {
_hoverCapable =
window.matchMedia &&
window.matchMedia("(hover:hover) and (pointer:fine)").matches;
} catch (e) {
_hoverCapable = false;
}
function _wrapFromTarget(t) {
return t && t.closest ? t.closest(POPUP_WRAP_SEL) : null;
}
function _btnFromWrap(wrap) {
return wrap ? wrap.querySelector(POPUP_BTN_SEL) : null;
}
if (_hoverCapable) {
// Open when entering wrapper (only once per enter)
document.addEventListener(
"mouseover",
function (e) {
var wrap = _wrapFromTarget(e.target);
if (!wrap) return;
var rel = e.relatedTarget;
if (rel && wrap.contains(rel)) return; // still inside wrapper
var btn = _btnFromWrap(wrap);
if (!btn) return;
if (!isBtnExpanded(btn)) {
closeOtherBtnPopupsWithinCard(btn);
openBtnPopup(btn);
}
},
true
);
// Close when leaving wrapper completely
document.addEventListener(
"mouseout",
function (e) {
var wrap = _wrapFromTarget(e.target);
if (!wrap) return;
var rel = e.relatedTarget;
if (rel && wrap.contains(rel)) return; // moved within wrapper
var btn = _btnFromWrap(wrap);
if (!btn) return;
if (isBtnExpanded(btn)) closeBtnPopup(btn);
},
true
);
}
/* ---------- Legacy <details> toggle (delegated) -------------------- */
document.addEventListener(
"toggle",
function (e) {
var d = e.target;
if (!d || d.nodeType !== 1) return;
if (!d.matches || !d.matches(POPUP_DETAILS_SEL)) return;
if (d.open) {
var card = closest(d, CARD_SEL);
var scope = card || document;
var openOnes = scope.querySelectorAll(POPUP_DETAILS_SEL + "[open]");
for (var i = 0; i < openOnes.length; i++) {
if (openOnes[i] !== d) closeDetails(openOnes[i]);
}
}
syncPopupAria(d);
},
true
);
/* Click outside closes everything */
document.addEventListener(
"click",
function (e) {
var t = e.target;
if (!t) return;
if (closest(t, POPUP_WRAP_SEL)) return;
closeAllBtnPopups(document);
closeAllDetails(document);
},
true
);
/* Escape closes everything */
document.addEventListener(
"keydown",
function (e) {
if (e.key !== "Escape") return;
closeAllBtnPopups(document);
closeAllDetails(document);
},
true
);
/* ------------------------------------------------------------------ */
/* Tabs (Phase 4.1: key-based, uses .sv-hidden + [hidden]) */
/* ------------------------------------------------------------------ */
function getTabs(root) {
return root.querySelectorAll(TAB_SEL);
}
function getPanels(root) {
return root.querySelectorAll(PANEL_SEL);
}
function getPanelByKey(root, key) {
var panels = getPanels(root);
for (var i = 0; i < panels.length; i++) {
if (panels[i].getAttribute("data-panel") === key) return panels[i];
}
return null;
}
function ensureUniqueTabIds(root) {
var uid = root.getAttribute("data-tabs-uid");
if (!uid) {
_tabsUidCounter++;
uid =
(root.getAttribute("data-tabs-root") || "svgi-tabs") +
"-" +
_tabsUidCounter;
root.setAttribute("data-tabs-uid", uid);
}
var tabs = getTabs(root);
for (var i = 0; i < tabs.length; i++) {
var tab = tabs[i];
var key = tab.getAttribute("data-tab") || String(i + 1);
var panel = getPanelByKey(root, key);
var tabId = uid + "-tab-" + key;
tab.id = tabId;
if (panel) {
var panelId = uid + "-panel-" + key;
panel.id = panelId;
tab.setAttribute("aria-controls", panelId);
panel.setAttribute("aria-labelledby", tabId);
}
}
}
function showPanel(panel) {
if (!panel) return;
panel.setAttribute("data-active", "1");
if (panel.classList) panel.classList.remove("sv-hidden");
panel.removeAttribute("hidden");
}
function hidePanel(panel) {
if (!panel) return;
panel.setAttribute("data-active", "0");
if (panel.classList) panel.classList.add("sv-hidden");
panel.setAttribute("hidden", "hidden");
}
function activateTab(root, btn) {
if (!root || !btn) return;
var tabs = getTabs(root);
var key = btn.getAttribute("data-tab") || "";
for (var i = 0; i < tabs.length; i++) {
var t = tabs[i];
var active = t === btn;
t.setAttribute("aria-selected", active ? "true" : "false");
t.setAttribute("tabindex", active ? "0" : "-1");
if (t.classList) t.classList.toggle("sv-tab--active", active);
}
var panels = getPanels(root);
for (var j = 0; j < panels.length; j++) {
var p = panels[j];
var isTarget = p.getAttribute("data-panel") === key;
if (isTarget) showPanel(p);
else hidePanel(p);
}
root.setAttribute("data-active-tab", key);
}
function focusTab(tabs, idx) {
if (idx < 0) idx = 0;
if (idx >= tabs.length) idx = tabs.length - 1;
var t = tabs[idx];
if (t && t.focus) t.focus();
return t;
}
function indexOfTab(tabs, btn) {
for (var i = 0; i < tabs.length; i++) {
if (tabs[i] === btn) return i;
}
return -1;
}
function normalizeTabsRoot(root) {
if (!root) return;
if (root.getAttribute("data-gi-tabs-init") === "1") return;
ensureUniqueTabIds(root);
var tabs = getTabs(root);
var panels = getPanels(root);
if (!tabs.length || !panels.length) {
root.setAttribute("data-gi-tabs-init", "1");
return;
}
var activeBtn = null;
for (var i = 0; i < tabs.length; i++) {
if (tabs[i].getAttribute("aria-selected") === "true") {
activeBtn = tabs[i];
break;
}
}
if (!activeBtn) activeBtn = tabs[0];
activateTab(root, activeBtn);
root.setAttribute("data-gi-tabs-init", "1");
}
function initTabs(root) {
var container = root || document;
var roots = container.querySelectorAll(TABS_ROOT_SEL);
for (var i = 0; i < roots.length; i++) normalizeTabsRoot(roots[i]);
}
document.addEventListener("click", function (e) {
var btn = e.target && e.target.closest ? e.target.closest(TAB_SEL) : null;
if (!btn) return;
var root = closest(btn, ".sv-tabs");
if (!root || root.getAttribute("data-tabs") !== "1") return;
activateTab(root, btn);
});
document.addEventListener("keydown", function (e) {
var btn =
document.activeElement && document.activeElement.closest
? document.activeElement.closest(TAB_SEL)
: null;
if (!btn) return;
var root = closest(btn, ".sv-tabs");
if (!root || root.getAttribute("data-tabs") !== "1") return;
var tabs = getTabs(root);
if (!tabs || !tabs.length) return;
var idx = indexOfTab(tabs, btn);
if (idx < 0) return;
if (e.key === "ArrowLeft" || e.key === "Left") {
e.preventDefault();
activateTab(root, focusTab(tabs, idx - 1));
return;
}
if (e.key === "ArrowRight" || e.key === "Right") {
e.preventDefault();
activateTab(root, focusTab(tabs, idx + 1));
return;
}
if (e.key === "Home") {
e.preventDefault();
activateTab(root, focusTab(tabs, 0));
return;
}
if (e.key === "End") {
e.preventDefault();
activateTab(root, focusTab(tabs, tabs.length - 1));
return;
}
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
activateTab(root, btn);
return;
}
});
/* ------------------------------------------------------------------ */
/* Init */
/* ------------------------------------------------------------------ */
function initAll(root) {
initLevels(root);
initTabs(root);
var container = root || document;
normalizeBtnPopups(container);
var ds = container.querySelectorAll(POPUP_DETAILS_SEL);
for (var i = 0; i < ds.length; i++) syncPopupAria(ds[i]);
}
if (window.mw && mw.hook) {
mw.hook("wikipage.content").add(function ($content) {
initAll($content && $content[0]);
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", function () {
initAll(document);
});
} else {
initAll(document);
}
})();