MediaWiki:Common.js: Difference between revisions
MediaWiki interface page
More actions
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
/* GameInfo JS (Phase 4.1) | /* GameInfo JS (Phase 4.1 Refactor) | ||
Shared | Shared behaviors for all GameInfo modules: | ||
- Level selector (data-series swap) | |||
- Popups (sv-tip / sv-disclose using sv-hidden) | |||
- Tabs (hidden attribute toggling + ARIA ids) */ | |||
(function () { | (function () { | ||
| Line 6: | Line 9: | ||
window.SV_GAMEINFO_41_INIT = 1; | window.SV_GAMEINFO_41_INIT = 1; | ||
/ | // --------------------------------------------------------------------------- | ||
/ | // Selectors | ||
/ | // --------------------------------------------------------------------------- | ||
var CARD_SEL = ".sv-gi-card, .sv-skill-card, .sv-passive-card"; | var CARD_SEL = ".sv-gi-card[data-gi='1'], .sv-skill-card, .sv-passive-card"; | ||
var LEVEL_RANGE_SEL = "input.sv-level-range[type='range']"; | var LEVEL_RANGE_SEL = "input.sv-level-range[type='range']"; | ||
var LEVEL_BOUNDARY_SEL = ".sv-skill-level, .sv-gi-level"; | var LEVEL_BOUNDARY_SEL = ".sv-skill-level, .sv-gi-level"; | ||
| Line 18: | Line 20: | ||
var SERIES_SEL = "[data-series]"; | var SERIES_SEL = "[data-series]"; | ||
var POP_WRAP_SEL = ".sv-tip, .sv-disclose"; | |||
var | var POP_BTN_SEL = "[data-sv-toggle='1']"; | ||
var | var HIDDEN_CLASS = "sv-hidden"; | ||
var | |||
var TABS_ROOT_SEL = ".sv-tabs[data-tabs='1']"; | var TABS_ROOT_SEL = ".sv-tabs[data-tabs='1']"; | ||
var TAB_SEL = ".sv-tab"; | var TAB_SEL = ".sv-tab[role='tab'], .sv-tab"; | ||
var PANEL_SEL = ".sv-tabpanel"; | var PANEL_SEL = ".sv-tabpanel"; | ||
/ | // Internals | ||
var _seriesCache = typeof WeakMap !== "undefined" ? new WeakMap() : null; | var _seriesCache = typeof WeakMap !== "undefined" ? new WeakMap() : null; | ||
var _tabsUidCounter = 0; | var _tabsUidCounter = 0; | ||
/ | // --------------------------------------------------------------------------- | ||
/ | // Utils | ||
/ | // --------------------------------------------------------------------------- | ||
function clampInt(value, min, max, fallback) { | function clampInt(value, min, max, fallback) { | ||
| Line 58: | Line 52: | ||
} | } | ||
return null; | return null; | ||
} | } | ||
| Line 98: | Line 88: | ||
} | } | ||
/ | // --------------------------------------------------------------------------- | ||
/ | // Level Selector | ||
/ | // --------------------------------------------------------------------------- | ||
function getCardMaxLevel(card) { | function getCardMaxLevel(card) { | ||
| Line 136: | Line 126: | ||
var el = nodes[i]; | var el = nodes[i]; | ||
// | // Update only nodes below the level boundary. | ||
if (!isAfter(boundary, el)) continue; | if (!isAfter(boundary, el)) continue; | ||
| Line 142: | Line 132: | ||
if (!series || series.length === 0) continue; | if (!series || series.length === 0) continue; | ||
var safeIndex = index; | var safeIndex = index; | ||
if (safeIndex >= series.length) safeIndex = series.length - 1; | if (safeIndex >= series.length) safeIndex = series.length - 1; | ||
| Line 150: | Line 139: | ||
if (value == null) value = ""; | if (value == null) value = ""; | ||
el.textContent = String(value); | el.textContent = String(value); | ||
} | } | ||
| Line 168: | Line 156: | ||
var level = clampInt(rawLevel, 1, maxLevel, getCardLevel(card, maxLevel)); | var level = clampInt(rawLevel, 1, maxLevel, getCardLevel(card, maxLevel)); | ||
card.setAttribute("data-level", String(level)); | card.setAttribute("data-level", String(level)); | ||
if (slider) { | if (slider) { | ||
slider.setAttribute("min", "1"); | slider.setAttribute("min", "1"); | ||
| Line 180: | Line 166: | ||
setLevelNumber(card, level); | setLevelNumber(card, level); | ||
if (slider) { | if (slider) { | ||
var boundary = getLevelBoundary(slider); | var boundary = getLevelBoundary(slider); | ||
| Line 199: | Line 184: | ||
var slider = getLevelSlider(card); | var slider = getLevelSlider(card); | ||
if (!slider || maxLevel <= 1) { | if (!slider || maxLevel <= 1) { | ||
card.setAttribute("data-gi-level-init", "1"); | card.setAttribute("data-gi-level-init", "1"); | ||
| Line 213: | Line 197: | ||
} | } | ||
document.addEventListener( | document.addEventListener( | ||
"input", | "input", | ||
| Line 229: | Line 212: | ||
); | ); | ||
/ | // --------------------------------------------------------------------------- | ||
/ | // Popups (sv-hidden toggles) | ||
/ | // --------------------------------------------------------------------------- | ||
function | function getPopupPanel(btn) { | ||
if (!btn) return null; | if (!btn) return null; | ||
var id = btn.getAttribute("aria-controls"); | var id = btn.getAttribute("aria-controls"); | ||
if (!id) return null; | if (!id) return null; | ||
return document.getElementById(id); | |||
} | } | ||
function | function isPanelOpen(panel) { | ||
return | return panel && panel.classList && !panel.classList.contains(HIDDEN_CLASS); | ||
} | } | ||
function | function setPanelOpen(btn, panel, open) { | ||
if (! | if (!panel || !panel.classList) return; | ||
if (open) panel.classList.remove(HIDDEN_CLASS); | |||
else panel.classList.add(HIDDEN_CLASS); | |||
if (btn) btn.setAttribute("aria-expanded", open ? "true" : "false"); | |||
} | } | ||
function | function closeAllPopups(scope) { | ||
var root = scope || document; | var root = scope || document; | ||
var | var btns = root.querySelectorAll(POP_BTN_SEL); | ||
for (var i = 0; i < btns.length; i++) { | |||
var b = btns[i]; | |||
for (var i = 0; i < | var p = getPopupPanel(b); | ||
if (p) setPanelOpen(b, p, false); | |||
} | |||
} | } | ||
function | function closeOtherPopupsWithinCard(currentBtn) { | ||
var card = closest( | var card = closest(currentBtn, CARD_SEL); | ||
var scope = card || document; | var scope = card || document; | ||
var | |||
var btns = scope.querySelectorAll(POP_BTN_SEL); | |||
for (var i = 0; i < btns.length; i++) { | |||
for (var i = 0; i < | var b = btns[i]; | ||
if (b === currentBtn) continue; | |||
var p = getPopupPanel(b); | |||
if (p) setPanelOpen(b, p, false); | |||
} | } | ||
} | } | ||
function | function togglePopup(btn) { | ||
var | var panel = getPopupPanel(btn); | ||
if (!panel) return; | |||
var open = !isPanelOpen(panel); | |||
var | if (open) closeOtherPopupsWithinCard(btn); | ||
setPanelOpen(btn, panel, open); | |||
} | } | ||
// Click | // Click to toggle | ||
document.addEventListener( | document.addEventListener( | ||
"click", | "click", | ||
function (e) { | function (e) { | ||
var | var btn = e.target && e.target.closest ? e.target.closest(POP_BTN_SEL) : null; | ||
if (btn) { | if (btn) { | ||
togglePopup(btn); | |||
e.preventDefault(); | e.preventDefault(); | ||
return; | return; | ||
} | } | ||
// | // Click outside closes all | ||
if (!closest( | if (!closest(e.target, POP_WRAP_SEL)) closeAllPopups(document); | ||
}, | }, | ||
true | true | ||
); | ); | ||
// Keyboard toggle (Enter/Space) | // Keyboard toggle (Enter/Space on popup buttons) | ||
document.addEventListener( | document.addEventListener( | ||
"keydown", | "keydown", | ||
function (e) { | function (e) { | ||
var t = e.target; | |||
if (!t || t.nodeType !== 1) return; | |||
if (e.key === "Escape") { | if (e.key === "Escape") { | ||
closeAllPopups(document); | |||
return; | return; | ||
} | } | ||
var btn = t.closest ? t.closest(POP_BTN_SEL) : null; | |||
var btn = | |||
if (!btn) return; | if (!btn) return; | ||
e. | if (e.key === "Enter" || e.key === " ") { | ||
togglePopup(btn); | |||
e.preventDefault(); | |||
} | } | ||
}, | }, | ||
| Line 399: | Line 308: | ||
); | ); | ||
/ | // --------------------------------------------------------------------------- | ||
/ | // Tabs (hidden attribute toggling + ARIA ids) | ||
/ | // --------------------------------------------------------------------------- | ||
function getTabs(root) { | function getTabs(root) { | ||
| Line 409: | Line 318: | ||
function getPanels(root) { | function getPanels(root) { | ||
return root.querySelectorAll(PANEL_SEL); | return root.querySelectorAll(PANEL_SEL); | ||
} | } | ||
function ensureUniqueTabIds(root) { | function ensureUniqueTabIds(root) { | ||
var uid = root.getAttribute("data-tabs-uid"); | var uid = root.getAttribute("data-tabs-uid"); | ||
if (!uid) { | if (!uid) { | ||
_tabsUidCounter++; | _tabsUidCounter++; | ||
uid = | uid = (root.getAttribute("data-tabs-root") || "svgi-tabs") + "-" + _tabsUidCounter; | ||
root.setAttribute("data-tabs-uid", uid); | root.setAttribute("data-tabs-uid", uid); | ||
} | } | ||
var tabs = getTabs(root); | var tabs = getTabs(root); | ||
for (var i = 0; i < | var panels = getPanels(root); | ||
var n = Math.min(tabs.length, panels.length); | |||
for (var i = 0; i < n; i++) { | |||
var tabId = uid + "-tab-" + (i + 1); | |||
var panelId = uid + "-panel-" + (i + 1); | |||
var tab = tabs[i]; | var tab = tabs[i]; | ||
var | var panel = panels[i]; | ||
tab.id = tabId; | tab.id = tabId; | ||
tab.setAttribute("aria-controls", panelId); | |||
panel.id = panelId; | |||
panel.setAttribute("aria-labelledby", tabId); | |||
} | } | ||
} | } | ||
| Line 468: | Line 351: | ||
var tabs = getTabs(root); | var tabs = getTabs(root); | ||
var | var panels = getPanels(root); | ||
for (var i = 0; i < tabs.length; i++) { | for (var i = 0; i < tabs.length; i++) { | ||
var t = tabs[i]; | var t = tabs[i]; | ||
| Line 476: | Line 358: | ||
t.setAttribute("aria-selected", active ? "true" : "false"); | t.setAttribute("aria-selected", active ? "true" : "false"); | ||
t.setAttribute("tabindex", active ? "0" : "-1"); | t.setAttribute("tabindex", active ? "0" : "-1"); | ||
if (t.classList) t.classList. | if (t.classList) { | ||
if (active) t.classList.add("sv-tab--active"); | |||
else t.classList.remove("sv-tab--active"); | |||
} | |||
} | } | ||
var targetId = btn.getAttribute("aria-controls"); | |||
var | |||
for (var j = 0; j < panels.length; j++) { | for (var j = 0; j < panels.length; j++) { | ||
var p = panels[j]; | var p = panels[j]; | ||
var isTarget = p. | var isTarget = p.id === targetId; | ||
if (isTarget) | |||
else | if (isTarget) p.removeAttribute("hidden"); | ||
else p.setAttribute("hidden", "hidden"); | |||
} | } | ||
root.setAttribute("data-active-tab", | root.setAttribute("data-active-tab", targetId || ""); | ||
} | } | ||
| Line 500: | Line 385: | ||
function indexOfTab(tabs, btn) { | function indexOfTab(tabs, btn) { | ||
for (var i = 0; i < tabs.length; i++) | for (var i = 0; i < tabs.length; i++) if (tabs[i] === btn) return i; | ||
return -1; | return -1; | ||
} | } | ||
| Line 519: | Line 402: | ||
} | } | ||
var activeBtn = null; | var activeBtn = null; | ||
for (var i = 0; i < tabs.length; i++) { | for (var i = 0; i < tabs.length; i++) { | ||
| Line 539: | Line 421: | ||
} | } | ||
// Click to activate | // Click to activate | ||
document.addEventListener("click", function (e) { | document.addEventListener("click", function (e) { | ||
var btn = e.target && e.target.closest ? e.target.closest(TAB_SEL) : null; | var btn = e.target && e.target.closest ? e.target.closest(TAB_SEL) : null; | ||
| Line 550: | Line 432: | ||
}); | }); | ||
// Keyboard navigation | // Keyboard navigation | ||
document.addEventListener("keydown", function (e) { | document.addEventListener("keydown", function (e) { | ||
var btn = | var btn = | ||
| Line 594: | Line 476: | ||
}); | }); | ||
/ | // --------------------------------------------------------------------------- | ||
/ | // Init | ||
/ | // --------------------------------------------------------------------------- | ||
function initAll(root) { | function initAll(root) { | ||
initLevels(root); | initLevels(root); | ||
initTabs(root); | initTabs(root); | ||
} | } | ||