Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Join the Playtest on Steam Now: SpiritVale

MediaWiki:Common.js: Difference between revisions

MediaWiki interface page
No edit summary
Tags: Mobile edit Mobile web edit
No edit summary
Tags: Mobile edit Mobile web edit
 
(3 intermediate revisions by the same user not shown)
Line 17: Line 17:


   // Versioned init guard (allows safe updates without stale blocks)
   // Versioned init guard (allows safe updates without stale blocks)
   var LEVELS_VERSION = 2;
   var LEVELS_VERSION = 6; // neutral hooks + bubble styling owned by CSS + no tick/check row
 
   if (typeof COMMON.levelsInit === "number" && COMMON.levelsInit >= LEVELS_VERSION) return;
   if (typeof COMMON.levelsInit === "number" && COMMON.levelsInit >= LEVELS_VERSION) return;
   COMMON.levelsInit = LEVELS_VERSION;
   COMMON.levelsInit = LEVELS_VERSION;


   var CARD_SEL = ".sv-gi-card, .sv-skill-card, .sv-passive-card";
  // Prefer neutral shared hooks; keep legacy class fallbacks for compatibility.
   var CARD_SEL =
    "[data-sv-card='1'], .sv-gi-card, .sv-skill-card, .sv-passive-card";


   // Accept the native range input and the custom slider variants.
   // Accept the native range input and the custom slider variants.
Line 27: Line 30:
     "input.sv-level-range[type='range'], .sv-level-range--custom, .sv-level-range[data-sv-slider='1']";
     "input.sv-level-range[type='range'], .sv-level-range--custom, .sv-level-range[data-sv-slider='1']";


   var LEVEL_BOUNDARY_SEL = ".sv-skill-level, .sv-gi-level";
   var LEVEL_BOUNDARY_SEL =
   var LEVEL_SCOPE_SEL = ".sv-gi-bottom, .sv-skill-bottom";
    "[data-sv-level-boundary='1'], .sv-skill-level, .sv-gi-level";
 
   var LEVEL_SCOPE_SEL =
    "[data-sv-level-scope='1'], .sv-gi-bottom, .sv-skill-bottom";
 
   var SERIES_SEL = "[data-series]";
   var SERIES_SEL = "[data-series]";
   var LEVEL_INIT_ATTR = "data-gi-level-init";
   var LEVEL_INIT_ATTR = "data-sv-level-init";


   var _seriesCache = typeof WeakMap !== "undefined" ? new WeakMap() : null;
   var _seriesCache = typeof WeakMap !== "undefined" ? new WeakMap() : null;
Line 115: Line 122:
   }
   }


   function getCardLevel(card, maxLevel) {
  function getCardMinLevel(card, slider, maxLevel) {
    // Prefer explicit neutral hooks, then legacy attrs, then slider attrs.
    var raw =
      card.getAttribute("data-min-level") ||
      card.getAttribute("data-minLevel") ||
      card.getAttribute("data-level-min");
 
    if (raw == null || raw === "") {
      if (slider) {
        if (isRangeInput(slider)) raw = slider.getAttribute("min");
        else if (isCustomSlider(slider))
          raw = slider.getAttribute("aria-valuemin") || slider.getAttribute("data-min");
      }
    }
 
    var min = clampInt(raw, 1, maxLevel, 1);
    if (min > maxLevel) min = maxLevel;
    return min;
  }
 
   function getCardLevel(card, minLevel, maxLevel) {
     var raw =
     var raw =
       card.getAttribute("data-level") ||
       card.getAttribute("data-level") ||
Line 123: Line 150:
         : null);
         : null);


     return clampInt(raw, 1, maxLevel, 1);
     return clampInt(raw, minLevel, maxLevel, minLevel);
  }
 
  function getStep(slider) {
    // Default = 1. Support future: data-step, or <input step="">
    var raw = null;
    if (slider) raw = slider.getAttribute("data-step") || slider.getAttribute("step");
    var s = parseInt(raw, 10);
    if (!s || isNaN(s) || s < 1) s = 1;
    return s;
   }
   }


Line 144: Line 180:
     var span = card.querySelector(".sv-level-num");
     var span = card.querySelector(".sv-level-num");
     if (span) span.textContent = String(level);
     if (span) span.textContent = String(level);
  }
  function setLevelMax(card, maxLevel) {
    var span = card.querySelector(".sv-level-max");
    if (span) span.textContent = String(maxLevel);
   }
   }


Line 149: Line 190:
     if (!scope) return;
     if (!scope) return;


    // JS arrays are 0-based; Lua uses 1-based "level".
     var index = level - 1;
     var index = level - 1;
     var nodes = scope.querySelectorAll(SERIES_SEL);
     var nodes = scope.querySelectorAll(SERIES_SEL);
Line 170: Line 212:
   }
   }


   function getCustomMin(slider) {
  function unhide(el) {
    if (!el) return;
    if (el.classList) el.classList.remove("sv-hidden");
    if (el.removeAttribute) el.removeAttribute("hidden");
    if (el.style && el.style.display === "none") el.style.display = "";
  }
 
  function hide(el) {
    if (!el) return;
    if (el.classList) el.classList.add("sv-hidden");
    if (el.setAttribute) el.setAttribute("hidden", "hidden");
  }
 
  /* ================================================================== */
  /* Custom slider helpers (bounds/percent)                              */
  /* ================================================================== */
 
   function getCustomMin(slider, fallback) {
     return clampInt(
     return clampInt(
       slider.getAttribute("aria-valuemin") || slider.getAttribute("data-min"),
       slider.getAttribute("aria-valuemin") || slider.getAttribute("data-min"),
       1,
       1,
       999,
       999,
       1
       fallback != null ? fallback : 1
     );
     );
   }
   }


   function getCustomMax(slider) {
   function getCustomMax(slider, fallback) {
     return clampInt(
     return clampInt(
       slider.getAttribute("aria-valuemax") || slider.getAttribute("data-max"),
       slider.getAttribute("aria-valuemax") || slider.getAttribute("data-max"),
       1,
       1,
       999,
       999,
       1
       fallback != null ? fallback : 1
     );
     );
   }
   }


   function getCustomValue(slider, fallback) {
  function getCustomBounds(slider, fallbackMin) {
    var min = getCustomMin(slider, fallbackMin != null ? fallbackMin : 1);
    var max = getCustomMax(slider, min);
    if (max < min) max = min;
    return { min: min, max: max };
  }
 
   function getCustomValue(slider, min, max, fallback) {
     return clampInt(
     return clampInt(
       slider.getAttribute("aria-valuenow") || slider.getAttribute("data-value"),
       slider.getAttribute("aria-valuenow") || slider.getAttribute("data-value"),
       1,
       min,
       999,
       max,
       fallback != null ? fallback : 1
       fallback != null ? fallback : min
     );
     );
  }
  function pctFromValue(min, max, value) {
    if (max === min) return 0;
    var pct = (value - min) / (max - min);
    if (pct < 0) pct = 0;
    if (pct > 1) pct = 1;
    return pct;
   }
   }


Line 204: Line 278:
   }
   }


   function updateCustomVisual(slider, value) {
  /* ================================================================== */
     var min = getCustomMin(slider);
  /* Custom slider value bubble                                          */
     var max = getCustomMax(slider);
  /* - JS owns structure + text/position only                            */
     if (max < min) max = min;
  /* - CSS owns visuals                                                  */
  /* ================================================================== */
 
   function ensureValueLabel(slider) {
    if (!slider || !slider.querySelector || !isCustomSlider(slider)) return null;
 
    var bubble = slider.querySelector(".sv-level-bubble");
    if (bubble) return bubble;
 
    bubble = document.createElement("span");
    bubble.className = "sv-level-bubble sv-hidden";
    bubble.setAttribute("hidden", "hidden");
    bubble.setAttribute("aria-hidden", "true");
 
    slider.appendChild(bubble);
    return bubble;
  }
 
  function setValueLabel(slider, value) {
     var bubble = ensureValueLabel(slider);
    if (!bubble) return;
 
     var b = getCustomBounds(slider, 1);
    bubble.textContent = String(value);
    bubble.style.left = pctFromValue(b.min, b.max, value) * 100 + "%";
  }
 
  function showValueLabel(slider) {
    if (!slider) return;
    var bubble = ensureValueLabel(slider);
     if (!bubble) return;
 
    if (slider._svBubbleTimer) {
      clearTimeout(slider._svBubbleTimer);
      slider._svBubbleTimer = null;
    }
 
    unhide(bubble);
  }
 
  function hideValueLabelSoon(slider, delayMs) {
    if (!slider) return;
    var bubble = slider.querySelector ? slider.querySelector(".sv-level-bubble") : null;
    if (!bubble) return;


     var pct = max === min ? 0 : (value - min) / (max - min);
     if (slider._svBubbleTimer) clearTimeout(slider._svBubbleTimer);
     if (pct < 0) pct = 0;
     slider._svBubbleTimer = setTimeout(function () {
     if (pct > 1) pct = 1;
      hide(bubble);
      slider._svBubbleTimer = null;
     }, delayMs != null ? delayMs : 450);
  }


     var left = pct * 100;
  function updateCustomVisual(slider, value) {
    var b = getCustomBounds(slider, 1);
     var left = pctFromValue(b.min, b.max, value) * 100;


     var thumb = slider.querySelector(".sv-level-thumb");
     var thumb = slider.querySelector(".sv-level-thumb");
Line 220: Line 342:
     if (thumb && thumb.style) thumb.style.left = left + "%";
     if (thumb && thumb.style) thumb.style.left = left + "%";
     if (fill && fill.style) fill.style.width = left + "%";
     if (fill && fill.style) fill.style.width = left + "%";
    setValueLabel(slider, value);
   }
   }


   function setCustomValue(slider, value) {
   function setCustomValue(slider, value, min, max) {
     slider.setAttribute("aria-valuenow", String(value));
     slider.setAttribute("aria-valuenow", String(value));
     slider.setAttribute("data-value", String(value));
     slider.setAttribute("data-value", String(value));
    // Helpful SR text (doesn't affect visuals)
    slider.setAttribute("aria-valuetext", "Level " + String(value) + " of " + String(max));
     updateCustomVisual(slider, value);
     updateCustomVisual(slider, value);
  }
  function snapToStep(raw, min, max, step) {
    if (step < 1) step = 1;
    var x = raw;
    if (x < min) x = min;
    if (x > max) x = max;
    var snapped = min + Math.round((x - min) / step) * step;
    if (snapped < min) snapped = min;
    if (snapped > max) snapped = max;
    return snapped;
   }
   }


   function valueFromClientX(slider, clientX) {
   function valueFromClientX(slider, clientX) {
     var rect = slider.getBoundingClientRect();
     var rect = slider.getBoundingClientRect();
     var min = getCustomMin(slider);
     var b = getCustomBounds(slider, 1);
    var max = getCustomMax(slider);
    if (max < min) max = min;


     var w = rect.width || 1;
     var w = rect.width || 1;
Line 239: Line 377:
     if (x > 1) x = 1;
     if (x > 1) x = 1;


     var raw = min + x * (max - min);
     var raw = b.min + x * (b.max - b.min);
     var rounded = Math.round(raw);
    return snapToStep(raw, b.min, b.max, getStep(slider));
     return clampInt(rounded, min, max, min);
  }
 
  /* ================================================================== */
  /* End-of-bar value label (right-side value)                          */
  /* ================================================================== */
 
  function ensureEndValueLabel(card, slider) {
     var wrap = null;
    if (slider) wrap = closest(slider, ".sv-level-slider");
    if (!wrap && card && card.querySelector) wrap = card.querySelector(".sv-level-slider");
    if (!wrap) return null;
 
    var out = wrap.querySelector(".sv-level-endvalue");
    if (out) return out;
 
    out = document.createElement("span");
    out.className = "sv-level-endvalue";
    out.setAttribute("aria-hidden", "true"); // slider already exposes aria-valuetext
    wrap.appendChild(out);
     return out;
  }
 
  function setEndValueLabel(card, slider, value) {
    var out = ensureEndValueLabel(card, slider);
    if (out) out.textContent = String(value);
   }
   }


   function unhide(el) {
  /* ================================================================== */
     if (!el) return;
  /* Core apply / init                                                    */
     if (el.classList) el.classList.remove("sv-hidden");
  /* ================================================================== */
     if (el.removeAttribute) el.removeAttribute("hidden");
 
    if (el.style && el.style.display === "none") el.style.display = "";
   function scheduleApply(card, rawLevel) {
     if (!card) return;
 
    card._svLevelNext = rawLevel;
     if (card._svLevelRaf) return;
 
     if (window.requestAnimationFrame) {
      card._svLevelRaf = requestAnimationFrame(function () {
        card._svLevelRaf = null;
        var v = card._svLevelNext;
        card._svLevelNext = null;
        applyLevelToCard(card, v);
      });
      return;
    }
 
    card._svLevelRaf = setTimeout(function () {
      card._svLevelRaf = null;
      var v = card._svLevelNext;
      card._svLevelNext = null;
      applyLevelToCard(card, v);
    }, 0);
   }
   }


Line 256: Line 439:
     var maxLevel = getCardMaxLevel(card);
     var maxLevel = getCardMaxLevel(card);
     var slider = getLevelSlider(card);
     var slider = getLevelSlider(card);
     var level = clampInt(rawLevel, 1, maxLevel, getCardLevel(card, maxLevel));
    var minLevel = getCardMinLevel(card, slider, maxLevel);
 
     var level = clampInt(rawLevel, minLevel, maxLevel, getCardLevel(card, minLevel, maxLevel));


     card.setAttribute("data-level", String(level));
     card.setAttribute("data-level", String(level));
     setLevelNumber(card, level);
     setLevelNumber(card, level);
    setLevelMax(card, maxLevel);


     if (slider) {
     if (slider) {
Line 267: Line 453:
     }
     }


     if (slider && maxLevel > 1) {
     if (slider) {
       if (isRangeInput(slider)) {
       if (isRangeInput(slider)) {
         slider.setAttribute("min", "1");
         slider.setAttribute("min", String(minLevel));
         slider.setAttribute("max", String(maxLevel));
         slider.setAttribute("max", String(maxLevel));
        slider.setAttribute("step", String(getStep(slider)));
        slider.setAttribute("aria-valuetext", "Level " + String(level) + " of " + String(maxLevel));
         if (String(slider.value) !== String(level)) slider.value = String(level);
         if (String(slider.value) !== String(level)) slider.value = String(level);
       } else if (isCustomSlider(slider)) {
       } else if (isCustomSlider(slider)) {
         setCustomBounds(slider, 1, maxLevel);
         setCustomBounds(slider, minLevel, maxLevel);
         setCustomValue(slider, level);
         setCustomValue(slider, level, minLevel, maxLevel);
       }
       }
    } else if (slider && isCustomSlider(slider)) {
      setCustomBounds(slider, 1, maxLevel);
      setCustomValue(slider, level);
     }
     }
    setEndValueLabel(card, slider, level);


     var boundary = getLevelBoundary(slider);
     var boundary = getLevelBoundary(slider);
Line 298: Line 485:
       var maxLevel = getCardMaxLevel(card);
       var maxLevel = getCardMaxLevel(card);
       var slider = getLevelSlider(card);
       var slider = getLevelSlider(card);
      var minLevel = getCardMinLevel(card, slider, maxLevel);
      var start = getCardLevel(card, minLevel, maxLevel);


      var start = getCardLevel(card, maxLevel);
       if (slider && maxLevel > minLevel) {
       if (slider && maxLevel > 1) {
         if (isRangeInput(slider)) start = clampInt(slider.value, minLevel, maxLevel, start);
         if (isRangeInput(slider)) start = slider.value;
         else if (isCustomSlider(slider)) start = getCustomValue(slider, minLevel, maxLevel, start);
         else if (isCustomSlider(slider)) start = getCustomValue(slider, start);
       }
       }


Line 310: Line 499:
   }
   }


  /* ================================================================== */
  /* Events                                                              */
  /* ================================================================== */
  // Native range inputs
   document.addEventListener(
   document.addEventListener(
     "input",
     "input",
Line 320: Line 514:
       if (!card) return;
       if (!card) return;


       applyLevelToCard(card, t.value);
       scheduleApply(card, t.value);
     },
     },
     true
     true
   );
   );


  // Custom slider pointer interaction (click anywhere + drag)
   document.addEventListener(
   document.addEventListener(
     "pointerdown",
     "pointerdown",
     function (e) {
     function (e) {
      if (e.button != null && e.button !== 0) return; // primary only
       var slider =
       var slider =
         e.target && e.target.closest
         e.target && e.target.closest
Line 348: Line 545:
       }
       }


       applyLevelToCard(card, valueFromClientX(slider, e.clientX));
       showValueLabel(slider);
      scheduleApply(card, valueFromClientX(slider, e.clientX));
     },
     },
     true
     true
Line 359: Line 557:
       if (e.pointerId !== _drag.pointerId) return;
       if (e.pointerId !== _drag.pointerId) return;
       e.preventDefault();
       e.preventDefault();
       applyLevelToCard(_drag.card, valueFromClientX(_drag.slider, e.clientX));
 
       showValueLabel(_drag.slider);
      scheduleApply(_drag.card, valueFromClientX(_drag.slider, e.clientX));
     },
     },
     true
     true
Line 373: Line 573:
       } catch (err) {}
       } catch (err) {}
     }
     }
    hideValueLabelSoon(_drag.slider, 650);
     _drag = null;
     _drag = null;
   }
   }
Line 378: Line 580:
   document.addEventListener("pointerup", endDrag, true);
   document.addEventListener("pointerup", endDrag, true);
   document.addEventListener("pointercancel", endDrag, true);
   document.addEventListener("pointercancel", endDrag, true);
  // Mouse-only slider interaction:
  // - Remove keyboard-driven adjustments on custom sliders
  // - Block native <input type="range"> from changing via keyboard (if present)
  var _SV_BLOCK_KEYS = {
    ArrowLeft: 1,
    ArrowRight: 1,
    ArrowUp: 1,
    ArrowDown: 1,
    Left: 1,
    Right: 1,
    Up: 1,
    Down: 1,
    Home: 1,
    End: 1,
    PageUp: 1,
    PageDown: 1
  };


   document.addEventListener(
   document.addEventListener(
     "keydown",
     "keydown",
     function (e) {
     function (e) {
      var key = e && e.key;
      if (!_SV_BLOCK_KEYS[key]) return;
      var el = document.activeElement;
      if (!el) return;
      // Native range inputs (keep mouse drag, disable keyboard nudges)
      if (isRangeInput(el) && el.classList && el.classList.contains("sv-level-range")) {
        e.preventDefault();
        return;
      }
      // Custom sliders (we keep focus behaviors, but no keyboard changes)
      var slider = closest(el, ".sv-level-range--custom, .sv-level-range[data-sv-slider='1']");
      if (slider) {
        e.preventDefault();
        return;
      }
    },
    true
  );
  // Show bubble on focus (discoverability), hide on blur
  document.addEventListener(
    "focusin",
    function () {
       var el = document.activeElement;
       var el = document.activeElement;
       if (!el || !el.closest) return;
       if (!el || !el.closest) return;
       var slider = el.closest(".sv-level-range--custom, .sv-level-range[data-sv-slider='1']");
       var slider = el.closest(".sv-level-range--custom, .sv-level-range[data-sv-slider='1']");
       if (!slider) return;
       if (!slider) return;


       var card = closest(slider, CARD_SEL);
       var b = getCustomBounds(slider, 1);
       if (!card) return;
       var v = getCustomValue(slider, b.min, b.max, b.min);


       var min = getCustomMin(slider);
       setValueLabel(slider, v);
       var max = getCustomMax(slider);
       showValueLabel(slider);
      if (max < min) max = min;
    },
    true
  );


      var cur = getCustomValue(slider, getCardLevel(card, max));
  document.addEventListener(
       var next = cur;
    "focusout",
 
    function (e) {
       if (e.key === "ArrowLeft" || e.key === "Left" || e.key === "ArrowDown") {
       var t = e.target;
        next = cur - 1;
       if (!t || !t.closest) return;
       } else if (e.key === "ArrowRight" || e.key === "Right" || e.key === "ArrowUp") {
       var slider = t.closest(".sv-level-range--custom, .sv-level-range[data-sv-slider='1']");
        next = cur + 1;
       if (!slider) return;
      } 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();
       hideValueLabelSoon(slider, 300);
      next = clampInt(next, min, max, cur);
      applyLevelToCard(card, next);
     },
     },
     true
     true
Line 751: Line 985:
       var vh = window.innerHeight || document.documentElement.clientHeight || 0;
       var vh = window.innerHeight || document.documentElement.clientHeight || 0;


      // CHANGE (safe): stricter edge padding on small screens
       var tight = vw <= 520;
       var tight = vw <= 520;
       var margin = tight ? 18 : 10;
       var margin = tight ? 18 : 10;