Widget:ConvertCalc2: Difference between revisions

From WyrdeWiki
No edit summary
mNo edit summary
 
(3 intermediate revisions by the same user not shown)
Line 1: Line 1:
<noinclude>__NOTOC__
<noinclude>__NOTOC__
This widget converts with one neutral slider N and two editable fields.
This widget converts between two units with a single slider.


=Usage=
=Usage=
{{#widget:ConvertCalc
{{#widget:ConvertCalc2
| id=cc1
  | TopLabel=years
  | TopLabel=years
  | TopFactor=1.3125
  | TopFactor=1.3125
Line 12: Line 11:
  | SliderMax=100
  | SliderMax=100
  | SliderStep=0.1
  | SliderStep=0.1
| Decimals=1
| NumStep=0.1
}}
}}
{| class="wikitable"
{| class="wikitable"
|-
! param !! meaning !! default
! param !! meaning !! default
|-
|-
| TopLabel || label for the top unit || years
| TopLabel || name shown for the top value || years
|-
|-
| TopFactor || multiplier for top value (Top = N * TopFactor) || 1.3125
| TopFactor || factor for 1 Top unit (used in bottom = top * Bottom/Top) || 1.3125
|-
|-
| BottomLabel || label for the bottom unit || Cycles
| BottomLabel || name shown for the bottom value || Cycles
|-
|-
| BottomFactor || multiplier for bottom value (Bottom = N * BottomFactor) || 1
| BottomFactor || factor for 1 Bottom unit || 1
|-
|-
| SliderMin / SliderMax / SliderStep || slider range & step for N || 1 / 100 / 0.1
| SliderMin || minimum slider value (top value) || 1
|-
|-
| Decimals || fixed decimals to display || 1
| SliderMax || maximum slider value (top value) || 100
|-
|-
| NumStep || step on the number inputs || 0.1
| SliderStep || slider step || 0.1
|}
|}
</noinclude><includeonly>
</noinclude><includeonly>
<div class="wh-convert" style="max-width:32rem;margin:1rem 0;">
<div class="wh-convertcalc2" style="max-width:720px;margin:1rem 0;">
   <!-- Top label -->
   <!-- Top title (Capitalized) -->
   <div style="font-weight:700;margin-bottom:.25rem;" id="<!--{$id|default:'cc'|escape:'html'}-->-topLbl">
   <div id="cc2-title-top" style="font-weight:700;margin-bottom:.35rem;text-transform:capitalize;">years</div>
    <!--{$TopLabel|default:'years'|escape:'html'}-->
  </div>


   <!-- Top value (editable) -->
   <!-- Top value centered above slider -->
   <div style="text-align:center;margin-bottom:.4rem;">
   <div style="display:flex;justify-content:center;margin-bottom:.4rem;">
     <input id="<!--{$id|default:'cc'|escape:'html'}-->-topVal" type="number"
     <input id="cc2-topval" type="number" step="0.01" style="width:10ch;text-align:right;margin-right:.4rem;">
          step="<!--{$NumStep|default:0.1|escape:'html'}-->" inputmode="decimal"
     <span id="cc2-toplabel">years</span>
          style="font:600 1.4rem/1 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
                  width:12ch;padding:.15rem .35rem;text-align:right;">  
     <span id="<!--{$id|default:'cc'|escape:'html'}-->-topUnit"
          style="font-weight:600;margin-left:.25rem;">
      <!--{$TopLabel|default:'years'|escape:'html'}-->
    </span>
   </div>
   </div>


   <!-- Slider row -->
   <!-- Slider row with swap on the right -->
   <div style="display:flex;align-items:center;gap:.5rem;margin:.25rem 0;">
   <div style="display:flex;align-items:center;gap:.6rem;">
     <input id="<!--{$id|default:'cc'|escape:'html'}-->-N" type="range"
     <input id="cc2-slider" type="range" style="flex:1;">
          min="<!--{$SliderMin|default:1|escape:'html'}-->"
     <button id="cc2-swap" title="Swap units" style="font-size:1rem;line-height:1;padding:.25rem .45rem;cursor:pointer;">⇅</button>
          max="<!--{$SliderMax|default:100|escape:'html'}-->"
          step="<!--{$SliderStep|default:0.1|escape:'html'}-->"
          value="<!--{$SliderMin|default:1|escape:'html'}-->"
          style="flex:1;">
     <button id="<!--{$id|default:'cc'|escape:'html'}-->-swap"
            title="Swap units"
            style="font-size:.95rem;padding:.2rem .5rem;cursor:pointer;">⇅</button>
   </div>
   </div>


   <!-- Bottom value (editable) -->
   <!-- Bottom value centered below slider -->
   <div style="text-align:center;margin:.4rem 0;">
   <div style="display:flex;justify-content:center;margin:.5rem 0 .6rem;">
     <input id="<!--{$id|default:'cc'|escape:'html'}-->-botVal" type="number"
     <input id="cc2-botval" type="number" step="0.01" style="width:10ch;text-align:right;margin-right:.4rem;" readonly>
          step="<!--{$NumStep|default:0.1|escape:'html'}-->" inputmode="decimal"
     <span id="cc2-botlabel">Cycles</span>
          style="font:600 1.4rem/1 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
                  width:12ch;padding:.15rem .35rem;text-align:right;">  
     <span id="<!--{$id|default:'cc'|escape:'html'}-->-botUnit"
          style="font-weight:600;margin-left:.25rem;">
      <!--{$BottomLabel|default:'Cycles'|escape:'html'}-->
    </span>
   </div>
   </div>


   <!-- Bottom label -->
   <!-- Bottom title (Capitalized) -->
   <div style="font-weight:700;margin-top:.25rem;" id="<!--{$id|default:'cc'|escape:'html'}-->-botLbl">
   <div id="cc2-title-bot" style="font-weight:700;text-transform:capitalize;">Cycles</div>
    <!--{$BottomLabel|default:'Cycles'|escape:'html'}-->
  </div>
</div>
</div>


<script type="text/javascript">
<script type="text/javascript">
(function(){
(function(){
   var ID = "<!--{$id|default:'cc'|escape:'js'}-->";
   // Pull parameters (only the ones you defined)
  function ge(s){ return document.getElementById(ID + s); }
 
  // Params
   var TopLabel    = "<!--{$TopLabel|default:'years'|escape:'js'}-->";
   var TopLabel    = "<!--{$TopLabel|default:'years'|escape:'js'}-->";
  var TopFactor    = parseFloat("<!--{$TopFactor|default:1.3125|escape:'js'}-->") || 1.3125;
   var BottomLabel  = "<!--{$BottomLabel|default:'Cycles'|escape:'js'}-->";
   var BottomLabel  = "<!--{$BottomLabel|default:'Cycles'|escape:'js'}-->";
  var TopFactor    = parseFloat("<!--{$TopFactor|default:1.3125|escape:'js'}-->") || 1.3125;
   var BottomFactor = parseFloat("<!--{$BottomFactor|default:1|escape:'js'}-->") || 1;
   var BottomFactor = parseFloat("<!--{$BottomFactor|default:1|escape:'js'}-->") || 1;
   var MIN  = parseFloat("<!--{$SliderMin|default:1|escape:'js'}-->") || 1;
 
   var MAX  = parseFloat("<!--{$SliderMax|default:100|escape:'js'}-->") || 100;
   var SL_MIN  = parseFloat("<!--{$SliderMin|default:1|escape:'js'}-->") || 1;
   var STEP = parseFloat("<!--{$SliderStep|default:0.1|escape:'js'}-->") || 0.1;
   var SL_MAX  = parseFloat("<!--{$SliderMax|default:100|escape:'js'}-->") || 100;
   var DEC  = parseInt("<!--{$Decimals|default:1|escape:'js'}-->") || 1;
   var SL_STEP  = parseFloat("<!--{$SliderStep|default:0.1|escape:'js'}-->") || 0.1;
 
  // Simple helpers
  function ge(id){ return document.getElementById(id); }
   function cap(s){ return (s||'').slice(0,1).toUpperCase() + (s||'').slice(1); }
  function fix2(n){ return (Math.round(n*100)/100).toFixed(2); }
  function clamp(x,a,b){ return Math.min(b, Math.max(a, x)); }


   // Elements
   // Elements
   var el = {
   var eTopTitle = ge("cc2-title-top");
    topLbl: ge("-topLbl"),
  var eBotTitle = ge("cc2-title-bot");
    botLbl: ge("-botLbl"),
  var eTopLbl  = ge("cc2-toplabel");
    topUnit: ge("-topUnit"),
  var eBotLbl  = ge("cc2-botlabel");
    botUnit: ge("-botUnit"),
  var eTopVal  = ge("cc2-topval");
    topVal: ge("-topVal"),
  var eBotVal  = ge("cc2-botval");
    botVal: ge("-botVal"),
  var eSlider  = ge("cc2-slider");
    N: ge("-N"),
  var eSwap     = ge("cc2-swap");
     swap: ge("-swap")
  };
 
  // Format helpers
  function clamp(v,min,max){ return Math.max(min, Math.min(max, v)); }
  function fix(v){ return Number(v).toFixed(DEC); }


   // Core updates (N drives both)
   // Init labels/titles
   function setFromN(n){
   function paintLabels(){
     n = clamp(n, MIN, MAX);
     eTopTitle.textContent = cap(TopLabel);
     el.N.value = n;
     eBotTitle.textContent = cap(BottomLabel);
     el.topVal.value = fix(n * TopFactor);
     eTopLbl.textContent  = TopLabel;
     el.botVal.value = fix(n * BottomFactor);
     eBotLbl.textContent  = BottomLabel;
   }
   }
  paintLabels();


   // When top changes, derive N from top
   // Slider config (slider always controls TOP value)
  function setFromTop(x){
  eSlider.min  = SL_MIN;
    var n = x / TopFactor;
  eSlider.max  = SL_MAX;
    setFromN(n);
   eSlider.step = SL_STEP;
   }
   eSlider.value = SL_MIN;
  // When bottom changes, derive N from bottom
   function setFromBot(x){
    var n = x / BottomFactor;
    setFromN(n);
  }


   // Bind events (with light debouncing via rAF)
   // Set initial numbers
   var raf = null;
   eTopVal.value = fix2(parseFloat(eSlider.value));
  function queue(fn){ if(raf) return; raf = requestAnimationFrame(function(){ raf=null; fn(); }); }
  eBotVal.value = fix2(parseFloat(eTopVal.value) * (BottomFactor / TopFactor));


   el.N.addEventListener("input", function(){ queue(function(){ setFromN(parseFloat(el.N.value)); }); });
   // Update cascade from top value (slider or manual input)
  el.topVal.addEventListener("input", function(){ queue(function(){ setFromTop(parseFloat(el.topVal.value)); }); });
  function fromTop(v){
  el.botVal.addEventListener("input", function(){ queue(function(){ setFromBot(parseFloat(el.botVal.value)); }); });
    var topNum = clamp(parseFloat(v)||0, SL_MIN, SL_MAX);
    eSlider.value = topNum;
    eTopVal.value = fix2(topNum);
    var bottom = topNum * (BottomFactor / TopFactor);
    eBotVal.value = fix2(bottom);
  }


   // Swap labels & factors but preserve the visible numbers by keeping N constant
   // Events
   el.swap.addEventListener("click", function(){
   eSlider.addEventListener("input", function(){ fromTop(this.value); });
    // swap labels
  eTopVal.addEventListener("input", function(){ fromTop(this.value); });
    var tl = TopLabel, bl = BottomLabel;
    TopLabel = bl; BottomLabel = tl;
    el.topLbl.textContent = TopLabel;
    el.topUnit.textContent = TopLabel;
    el.botLbl.textContent = BottomLabel;
    el.botUnit.textContent = BottomLabel;


    // swap factors
  // Swap button: swap labels & factors; slider still drives TOP
  eSwap.addEventListener("click", function(){
    var tl = TopLabel; TopLabel = BottomLabel; BottomLabel = tl;
     var tf = TopFactor; TopFactor = BottomFactor; BottomFactor = tf;
     var tf = TopFactor; TopFactor = BottomFactor; BottomFactor = tf;
 
    paintLabels();
     // recompute from current N
     // keep same numeric top value; just recompute bottom
     setFromN(parseFloat(el.N.value));
     fromTop(eTopVal.value);
   });
   });
  // Init starting state
  setFromN(MIN);
})();
})();
</script>
</script>
</includeonly>
</includeonly>

Latest revision as of 23:35, 11 November 2025

This widget converts between two units with a single slider.

Usage

years
years
Cycles
Cycles

param meaning default
TopLabel name shown for the top value years
TopFactor factor for 1 Top unit (used in bottom = top * Bottom/Top) 1.3125
BottomLabel name shown for the bottom value Cycles
BottomFactor factor for 1 Bottom unit 1
SliderMin minimum slider value (top value) 1
SliderMax maximum slider value (top value) 100
SliderStep slider step 0.1