MediaWiki:Common.js: Difference between revisions
Created page with "→Any JavaScript here will be loaded for all users on every page load.: mw.loader.using('jquery.makeCollapsible').then(function () { $('.collapsible').makeCollapsible(); });" |
No edit summary |
||
(One intermediate revision by the same user not shown) | |||
Line 1: | Line 1: | ||
/* Any JavaScript here will be loaded for all users on every page load. */ | /* Any JavaScript here will be loaded for all users on every page load. */ | ||
mw. | /* ===== OOO Tab Scroll — minimal, production-safe ===== */ | ||
mw.hook('wikipage.content').add(function ($content) { | |||
var wraps = $content.find('.ooo-infobox-tabwrap.ooo-infobox-tabwrap--scroll'); | |||
if (!wraps.length) return; | |||
wraps.each(function () { | |||
var wrap = this; | |||
var tabs = wrap.querySelector('.tabber__tabs'); | |||
if (!tabs) return; | |||
// Avoid duplicates | |||
if (wrap.querySelector('.ooo-tab-arrow')) return; | |||
// Ensure positioned container for absolute arrows | |||
var csWrap = getComputedStyle(wrap); | |||
if (csWrap.position === 'static') wrap.style.position = 'relative'; | |||
// Leave padding so tabs don't sit under the arrows | |||
var PAD = 26; | |||
var csTabs = getComputedStyle(tabs); | |||
var pil = parseInt(csTabs.paddingInlineStart || 0, 10) || 0; | |||
var pie = parseInt(csTabs.paddingInlineEnd || 0, 10) || 0; | |||
tabs.style.scrollBehavior = 'smooth'; | |||
tabs.style.paddingInlineStart = (pil + PAD) + 'px'; | |||
tabs.style.paddingInlineEnd = (pie + PAD) + 'px'; | |||
// RTL awareness (visual direction) | |||
var isRTL = getComputedStyle(wrap).direction === 'rtl'; | |||
function scrollByAmount(dir) { | |||
var amount = Math.max(120, Math.round(tabs.clientWidth * 0.7)); | |||
var sign = (dir === 'prev') ? -1 : 1; | |||
if (isRTL) sign *= -1; | |||
tabs.scrollBy({ left: sign * amount, behavior: 'smooth' }); | |||
} | |||
function makeBtn(dir) { | |||
var b = document.createElement('button'); | |||
b.type = 'button'; | |||
b.className = 'ooo-tab-arrow ooo-tab-arrow--' + dir; | |||
b.setAttribute('aria-label', dir === 'prev' | |||
? 'Scroll tabs ' + (isRTL ? 'right' : 'left') | |||
: 'Scroll tabs ' + (isRTL ? 'left' : 'right') | |||
); | |||
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); | |||
svg.setAttribute('viewBox', '0 0 20 20'); | |||
svg.setAttribute('width', '12'); | |||
svg.setAttribute('height', '12'); | |||
svg.setAttribute('focusable', 'false'); | |||
svg.setAttribute('aria-hidden', 'true'); | |||
svg.classList.add('ooo-tab-arrow__icon'); | |||
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); | |||
path.setAttribute('d', dir === 'prev' ? 'M12.5 15l-5-5 5-5' : 'M7.5 5l5 5-5 5'); | |||
path.setAttribute('fill', 'none'); | |||
path.setAttribute('stroke', 'currentColor'); | |||
path.setAttribute('stroke-width', '2'); | |||
path.setAttribute('stroke-linecap', 'round'); | |||
path.setAttribute('stroke-linejoin', 'round'); | |||
path.setAttribute('vector-effect', 'non-scaling-stroke'); | |||
svg.appendChild(path); | |||
b.appendChild(svg); | |||
b.addEventListener('click', function () { scrollByAmount(dir); }); | |||
b.addEventListener('keydown', function (e) { | |||
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); scrollByAmount(dir); } | |||
if (e.key === 'ArrowLeft') { e.preventDefault(); scrollByAmount(isRTL ? 'next' : 'prev'); } | |||
if (e.key === 'ArrowRight') { e.preventDefault(); scrollByAmount(isRTL ? 'prev' : 'next'); } | |||
if (e.key === 'Home') { e.preventDefault(); tabs.scrollTo({ left: isRTL ? tabs.scrollWidth : 0, behavior: 'smooth' }); } | |||
if (e.key === 'End') { e.preventDefault(); tabs.scrollTo({ left: isRTL ? 0 : tabs.scrollWidth, behavior: 'smooth' }); } | |||
}); | |||
return b; | |||
} | |||
wrap.appendChild(makeBtn('prev')); | |||
wrap.appendChild(makeBtn('next')); | |||
function update() { | |||
var prev = wrap.querySelector('.ooo-tab-arrow--prev'); | |||
var next = wrap.querySelector('.ooo-tab-arrow--next'); | |||
var sl = tabs.scrollLeft; | |||
// Normalize some RTL engines that use negative scrollLeft | |||
if (isRTL) sl = Math.abs(sl); | |||
var atStart = sl <= 2; | |||
var atEnd = sl + tabs.clientWidth >= tabs.scrollWidth - 2; | |||
if (prev) prev.disabled = atStart; | |||
if (next) next.disabled = atEnd; | |||
// Hide both if there is no overflow | |||
var overflow = tabs.scrollWidth > tabs.clientWidth + 2; | |||
if (prev) prev.classList.toggle('is-hidden', !overflow); | |||
if (next) next.classList.toggle('is-hidden', !overflow); | |||
} | |||
tabs.addEventListener('scroll', update, { passive: true }); | |||
window.addEventListener('resize', update); | |||
if (window.ResizeObserver) new ResizeObserver(update).observe(tabs); | |||
// Initial | |||
update(); | |||
}); | |||
}); | }); |