MediaWiki:Common.js: Difference between revisions

No edit summary
No edit summary
 
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. */
/* ===== 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();
  });
});