MediaWiki:Common.js: Difference between revisions
MediaWiki interface page
More actions
No edit summary Tags: Mobile edit Mobile web edit |
No edit summary Tags: Mobile edit Mobile web edit |
||
| 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 = | var LEVELS_VERSION = 5; // bumped: end-of-bar value + numbered tick/check row | ||
if (typeof COMMON.levelsInit === "number" && COMMON.levelsInit >= LEVELS_VERSION) return; | if (typeof COMMON.levelsInit === "number" && COMMON.levelsInit >= LEVELS_VERSION) return; | ||
| Line 149: | Line 149: | ||
// Default = 1. Support future: data-step, or <input step=""> | // Default = 1. Support future: data-step, or <input step=""> | ||
var raw = null; | var raw = null; | ||
if (slider) | if (slider) raw = slider.getAttribute("data-step") || slider.getAttribute("step"); | ||
var s = parseInt(raw, 10); | var s = parseInt(raw, 10); | ||
if (!s || isNaN(s) || s < 1) s = 1; | if (!s || isNaN(s) || s < 1) s = 1; | ||
| Line 206: | Line 204: | ||
} | } | ||
} | } | ||
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) { | function getCustomMin(slider, fallback) { | ||
| Line 223: | Line 238: | ||
fallback != null ? fallback : 1 | fallback != null ? fallback : 1 | ||
); | ); | ||
} | |||
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 }; | |||
} | } | ||
| Line 232: | Line 254: | ||
fallback != null ? fallback : min | 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 240: | Line 270: | ||
slider.setAttribute("data-max", String(max)); | slider.setAttribute("data-max", String(max)); | ||
} | } | ||
/* ================================================================== */ | |||
/* Custom slider value bubble (existing behavior; kept) */ | |||
/* ================================================================== */ | |||
function ensureValueLabel(slider) { | function ensureValueLabel(slider) { | ||
| Line 252: | Line 286: | ||
bubble.setAttribute("aria-hidden", "true"); | bubble.setAttribute("aria-hidden", "true"); | ||
// Minimal layout-only styling (no color system changes | // Minimal layout-only styling (no color system changes here). | ||
bubble.style.position = "absolute"; | bubble.style.position = "absolute"; | ||
bubble.style.top = "-28px"; | bubble.style.top = "-28px"; | ||
| Line 266: | Line 299: | ||
slider.appendChild(bubble); | slider.appendChild(bubble); | ||
return bubble; | return bubble; | ||
} | } | ||
| Line 285: | Line 305: | ||
if (!bubble) return; | if (!bubble) return; | ||
var | var b = getCustomBounds(slider, 1); | ||
bubble.textContent = String(value); | bubble.textContent = String(value); | ||
bubble.style.left = | bubble.style.left = pctFromValue(b.min, b.max, value) * 100 + "%"; | ||
} | } | ||
| Line 302: | Line 315: | ||
if (!bubble) return; | if (!bubble) return; | ||
if (slider._svBubbleTimer) { | if (slider._svBubbleTimer) { | ||
clearTimeout(slider._svBubbleTimer); | clearTimeout(slider._svBubbleTimer); | ||
| Line 324: | Line 336: | ||
function updateCustomVisual(slider, value) { | function updateCustomVisual(slider, value) { | ||
var | var b = getCustomBounds(slider, 1); | ||
var | var left = pctFromValue(b.min, b.max, value) * 100; | ||
var thumb = slider.querySelector(".sv-level-thumb"); | var thumb = slider.querySelector(".sv-level-thumb"); | ||
| Line 340: | Line 345: | ||
if (fill && fill.style) fill.style.width = left + "%"; | if (fill && fill.style) fill.style.width = left + "%"; | ||
setValueLabel(slider, value); | setValueLabel(slider, value); | ||
} | } | ||
| Line 368: | Line 372: | ||
function valueFromClientX(slider, clientX) { | function valueFromClientX(slider, clientX) { | ||
var rect = slider.getBoundingClientRect(); | var rect = slider.getBoundingClientRect(); | ||
var | var b = getCustomBounds(slider, 1); | ||
var w = rect.width || 1; | var w = rect.width || 1; | ||
| Line 377: | Line 379: | ||
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 | return snapToStep(raw, b.min, b.max, getStep(slider)); | ||
return | } | ||
/* ================================================================== */ | |||
/* NEW: 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 setTickLabels(boundary, min, max) { | function setEndValueLabel(card, slider, value) { | ||
var out = ensureEndValueLabel(card, slider); | |||
if (out) out.textContent = String(value); | |||
} | |||
/* ================================================================== */ | |||
/* Tick/check row (numbered checkmarks) */ | |||
/* ================================================================== */ | |||
function buildTickNode(level, extraClass) { | |||
var tick = document.createElement("span"); | |||
tick.className = "sv-level-tick" + (extraClass ? " " + extraClass : ""); | |||
tick.setAttribute("data-level", String(level)); | |||
var mark = document.createElement("span"); | |||
mark.className = "sv-level-tickmark"; | |||
mark.textContent = "✓"; | |||
var num = document.createElement("span"); | |||
num.className = "sv-level-ticknum"; | |||
num.textContent = String(level); | |||
tick.appendChild(mark); | |||
tick.appendChild(num); | |||
return tick; | |||
} | |||
function setTickLabels(boundary, min, max, value, step) { | |||
if (!boundary || !boundary.querySelector) return; | if (!boundary || !boundary.querySelector) return; | ||
var ticks = boundary.querySelector(".sv-level-ticklabels"); | var ticks = boundary.querySelector(".sv-level-ticklabels"); | ||
if (!ticks) return; | if (!ticks) return; | ||
// | step = step && step > 0 ? step : 1; | ||
var count = Math.floor((max - min) / step) + 1; | |||
if (count < 2) count = 2; | |||
// Full ticks for small ranges; min/max only for large ranges. | |||
var mode = count <= 25 ? "full" : "minmax"; | |||
var nextKey = String(min) + "/" + String(max) + "/" + String(step) + "/" + mode; | |||
var curKey = ticks.getAttribute("data-sv-ticks") || ""; | var curKey = ticks.getAttribute("data-sv-ticks") || ""; | ||
if (curKey !== nextKey) { | |||
ticks.setAttribute("data-sv-ticks", nextKey); | |||
while (ticks.firstChild) ticks.removeChild(ticks.firstChild); | |||
if (mode === "full") { | |||
for (var lv = min; lv <= max; lv += step) { | |||
var cls = ""; | |||
if (lv === min) cls = "sv-level-tick--min"; | |||
else if (lv === max) cls = "sv-level-tick--max"; | |||
ticks.appendChild(buildTickNode(lv, cls)); | |||
} | |||
} else { | |||
ticks.appendChild(buildTickNode(min, "sv-level-tick--min")); | |||
ticks.appendChild(buildTickNode(max, "sv-level-tick--max")); | |||
} | |||
} | |||
ticks. | // Update checked/current state every time the level changes. | ||
var children = ticks.children; | |||
for (var i = 0; i < children.length; i++) { | |||
var node = children[i]; | |||
if (!node || !node.getAttribute) continue; | |||
var lvStr = node.getAttribute("data-level"); | |||
var lvNum = parseInt(lvStr, 10); | |||
if (node.classList) { | |||
node.classList.remove("sv-level-tick--checked"); | |||
node.classList.remove("sv-level-tick--current"); | |||
} | |||
if (!isNaN(lvNum)) { | |||
if (lvNum <= value && node.classList) node.classList.add("sv-level-tick--checked"); | |||
if (lvNum === value && node.classList) node.classList.add("sv-level-tick--current"); | |||
} | |||
} | |||
} | } | ||
/* ================================================================== */ | |||
/* Core apply / init */ | |||
/* ================================================================== */ | |||
function scheduleApply(card, rawLevel) { | function scheduleApply(card, rawLevel) { | ||
| Line 460: | Line 540: | ||
} | } | ||
} | } | ||
// NEW: value readout at the end/right of the slider bar | |||
setEndValueLabel(card, slider, level); | |||
var boundary = getLevelBoundary(slider); | var boundary = getLevelBoundary(slider); | ||
setTickLabels(boundary, minLevel, maxLevel); | setTickLabels(boundary, minLevel, maxLevel, level, getStep(slider)); | ||
var scope = getLevelScopeContainer(card, boundary); | var scope = getLevelScopeContainer(card, boundary); | ||
| Line 493: | Line 576: | ||
} | } | ||
} | } | ||
/* ================================================================== */ | |||
/* Events */ | |||
/* ================================================================== */ | |||
// Native range inputs | // Native range inputs | ||
| Line 536: | Line 623: | ||
} | } | ||
// Show value | // Show value bubble while interacting (existing behavior) | ||
showValueLabel(slider); | showValueLabel(slider); | ||
| Line 580: | Line 667: | ||
// - Block native <input type="range"> from changing via keyboard (if present) | // - Block native <input type="range"> from changing via keyboard (if present) | ||
var _SV_BLOCK_KEYS = { | var _SV_BLOCK_KEYS = { | ||
ArrowLeft: 1, ArrowRight: 1, ArrowUp: 1, ArrowDown: 1, | ArrowLeft: 1, | ||
Left: 1, Right: 1, Up: 1, Down: 1, | ArrowRight: 1, | ||
Home: 1, End: 1, PageUp: 1, PageDown: 1 | ArrowUp: 1, | ||
ArrowDown: 1, | |||
Left: 1, | |||
Right: 1, | |||
Up: 1, | |||
Down: 1, | |||
Home: 1, | |||
End: 1, | |||
PageUp: 1, | |||
PageDown: 1, | |||
}; | }; | ||
| Line 610: | Line 706: | ||
); | ); | ||
// Show | // Show bubble on focus (discoverability), hide on blur | ||
document.addEventListener( | document.addEventListener( | ||
"focusin", | "focusin", | ||
| Line 619: | Line 715: | ||
if (!slider) return; | if (!slider) return; | ||
var | var b = getCustomBounds(slider, 1); | ||
var v = getCustomValue(slider, b.min, b.max, b.min); | |||
var v = getCustomValue(slider, min, max, min); | |||
setValueLabel(slider, v); | setValueLabel(slider, v); | ||