Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

HTML

              
                <main>

  <h1>Prevent numeric form input invalidity</h1>

  <p>Prevent illegal input while entering numbers into form fields which perform real-time calculations.</p>
  
  <p>Behaviour update for the <a href="https://websemantics.uk/tools/convert-pixel-point-em-rem-percent/" target=_blank title="[new window]">Font-size converter</a> on webSemantics.</p>

  <form class=frm aria-labelledby=frm_ttl>

    <h2 id=frm_ttl class=-visually-hidden>Font-size conversion calculator</h2>

    <div class=frm_fld-root>
        <label class=frm_lbl-root>
          <span class=lbl_txt>Browser default font-size </span>
          <span class=-nowrap>
            <input class=IN-rootFS type=text>
            px
          </span>
        </label>
    </div>

    <div class=frm_fld-convert>

      <label class=frm_lbl>
        <span class=lbl_txt>Convert<span class=-visually-hidden> font-size</span></span>
        <input class=IN-fromFS type=text>
      </label>

      <fieldset class=frm_set>

        <legend class=-visually-hidden>From:</legend>

        <label class=IN_lbl-radio>
          <span class=lbl_txt>px</span>
          <input class="IN_radio-from" type=radio name=unitFrom id=IN_radio_from-px value=px checked>
          <span></span>
        </label>

        <label class=IN_lbl-radio>
          <span class=lbl_txt>%</span>
          <input class="IN_radio-from" type=radio name=unitFrom id=IN_radio_from-percent value=percent>
          <span></span>
        </label>

        <label class=IN_lbl-radio>
          <span class=lbl_txt>pt</span>
          <input class="IN_radio-from" type=radio name=unitFrom id=IN_radio_from-pt value=pt>
          <span></span>
        </label>

        <label class=IN_lbl-radio>
          <span class=lbl_txt>rem</span>
          <input class="IN_radio-from" type=radio name=unitFrom id=IN_radio_from-rem value=rem>
          <span></span>
        </label>

      </fieldset>

    </div>


    <div class=frm_fld-result>

      <label class=frm_lbl>
        <span class=lbl_txt>Result</span>
        <input class=IN-toFS type=text readonly>
        <span class="IN_toUnit -visually-hidden">REM</span>
      </label>

      <fieldset class=frm_set>

        <legend class=-visually-hidden>Change result unit to:</legend>

        <label class=IN_lbl-radio>
          <span class=lbl_txt>px</span>
          <input class=IN_radio-to type=radio name=unitTo id=IN_radio_to-px value=px>
          <span></span>
        </label>

        <label class=IN_lbl-radio>
          <span class=lbl_txt>%</span>
          <input class=IN_radio-to type=radio name=unitTo id=IN_radio_to-percent value=percent>
          <span></span>
        </label>

        <label class=IN_lbl-radio>
          <span class=lbl_txt>pt</span>
          <input class=IN_radio-to type=radio name=unitTo id=IN_radio_to-pt value=pt>
          <span></span>
        </label>

        <label class=IN_lbl-radio>
          <span class=lbl_txt>rem</span>
          <input class=IN_radio-to type=radio name=unitTo id=IN_radio_to-rem value=rem checked>
          <span></span>
        </label>

      </fieldset>

    </div>

    <div style=text-align:right>
      [<span id=svg_from></span> = <span id=svg_to></span>]
      &mdash; Version 4.1
    </div>
  </form>
  
  <p>Try typing into the Convert field. See how the Result is updated live, as you type, but only while the Convert value is valid. Test starting with a period (.) or lead zero, or a zero after a period.</p>
  
  <p>If I've done it correctly, it should be impossible to enter invalid characters, or break the calculation.</p>

</main>

<!-- Footer codepen include -->
[[[https://codepen.io/2kool2/pen/mKeeGM]]]
              
            
!

CSS

              
                /* Generic styling */
*, *::after, *::before {box-sizing: inherit;}
body {
  --color: hsl(269, 19%, 30%);
  --bgColor: hsla(32,100%,85%,.35);
  --linkColor: hsla(214, 71%, 47%, 1);
  --linkColorUnderline: hsla(214, 71%, 47%, .5);
  --linkColorHover: hsla(214, 100%, 35%, 1);
  --linkBgHover: hsla(214, 100%, 85%, 1);
  --linkFocus: hsla(214, 71%, 80%, 0.3);
  --focusOutline: hsla(214, 71%, 85%, 1);

  --textShadow: #fff;
  --boxShadow: hsla(0,0%,0%,.3);
  --bgColorForm: hsla(120,50%,50%,.075);

  font-family: sans-serif;
  text-rendering: optimizeLegibility;
  margin: 1rem;
  line-height: 1.5;
  padding-bottom: 2rem;
  background-attachment: fixed;
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.4'/%3E%3C/filter%3E%3C/defs%3E%3Crect filter='url(%23a)' opacity='.3' width='100%25' height='100%25'/%3E%3C/svg%3E");

  min-height: 100%;
  color: var(--color);
  background-color: var(--bgColor);
}
main {
  max-width: 34rem;
  margin: 0 auto;
}
h1 {
  font-weight: 100;
  margin-top: 2rem;
  margin-bottom: 1rem;
  text-align: center;
}
h1 + p {
  font-size: 1.35rem;
  font-weight: 100;
  text-align: center;
}
a:link,
a:visited {
  color: var(--linkColor);
  text-decoration-color: var(--linkColorUnderline);
  outline: hsla(214, 71%, 80%, 0); solid .25rem;
  transition:
    color .3s ease-out,
    background-color .3s ease-out,
    outline-color .3s ease-out;
}
a:hover,
a:focus {
  color: var(--linkColorHover);
  outline: var(--linkBgHover) solid .25rem;
  background-color: var(--linkBgHover);
}

.-visually-hidden {
    position: absolute !important;
    clip: rect(1px,1px,1px,1px);
    padding: 0 !important;
    border: 0 !important;
    height: 1px !important;
    width: 1px !important;
    overflow: hidden;
}
.-nowrap {
    white-space: nowrap;
}

/* Not trying to redo my sites layout but a little neatness doesn't hurt */

.frm {
  background-color: var(--bgColorForm);
  border: 2px dashed #f90;
  padding: .6rem 1rem 1rem;
  max-width: 40rem;
  margin: 0 auto;
  line-height: 2;
}
.IN-fromFS {
  margin-right: 1rem;
}
.IN-toFS {
  margin-right: 1rem;
}
.frm_lbl > .lbl_txt {
  display: inline-block;
  min-width: 4rem;
}
.frm_set {
  border: 0;
}

[class^="IN_radio"] {
  margin-right: 1rem;
}

input[type="text"]:invalid + .validity:after {
  color: red;
  content: '✖';
}

input[type="text"]:valid + .validity:after {
  color: green;
  content: '✓';
}

.frm_set {
  display: inline-block;
}
              
            
!

JS

              
                var supportsES6 = (function() {
  // https://gist.github.com/bendc/d7f3dbc83d0f65ca0433caf90378cd95
  try {
    new Function('(a = 0) => a');
    return true;
  }
  catch (err) {
    return false;
  }
}());




var Convert_Font_Sizes = (function (window, document, supportsES6) {

  'use strict';
  if (!supportsES6) return;


  const toUnit = document.querySelector('.IN_toUnit');
  const radiosFrom = document.querySelectorAll('.IN_radio-from');
  const radiosTo = document.querySelectorAll('.IN_radio-to');
  

  const allowNumericAndControlsKeysOnly = e => {
    
    // function prevents default action on unwanted key pressed.

    // Exit this function to allow specific control keys. 
    // Unfortunately, no paste (CMD V || CTRL V) allowed as it complicates error checking beyond project scope.
    if (
      (e.which === 65 && e.ctrlKey) ||  // CTRL A
      (e.which === 65 && e.metaKey) ||  // CMD A
      (e.which === 67 && e.ctrlKey) ||  // CTRL C
      (e.which === 67 && e.metaKey) ||  // CMD C
      (e.which > 36 && e.which < 41) || // ARROW KEYS
      (e.which === 9 && e.shiftKey) ||  // SHIFT TAB
       e.which === 8 || // TAB
       e.which === 9 || // BACKSPACE DEL
       e.which === 13   // RETURN
    ) return;

    // Only 0-9, and a single instance of . allowed, otherwise the keypress is ignored
    if (!(
      (e.which > 47 && e.which < 58) || // 0-9
      (e.which === 190 && e.target.value.indexOf('.') === -1) // Only 1 period
    )) {
      e.preventDefault();
    }
  };


  // Limit to four rounded decimal places.
  const sanitize = value => Math.round(value * 10000) / 10000;


  const setValue = (value, prop, valueWhenIllegal) => {
    value = sanitize(value);
    if (isNaN(value) || value === 0)  value = valueWhenIllegal;
    inputs[prop].set(value); // string
  };


  const setInputEvents = prop => {

    const obj = inputs[prop].obj;

    obj.addEventListener('focus', e => {
      obj.addEventListener('keydown', allowNumericAndControlsKeysOnly);
    });

    obj.addEventListener('blur', e => {
      obj.removeEventListener('keydown', allowNumericAndControlsKeysOnly);
      setValue(e.target.value, prop, 1);
    });

    obj.addEventListener('change', e => {
      setValue(e.target.value, prop, 1);
    });

    obj.addEventListener('input', e => {

      // Automatically updates valid values as they are typed

      const v = e.target.value;
      const lc = v.substr(v.length - 1);

      // Do not update values which detract from typing numbers
      // No update when:
      if (
        v.length > 0 &&   // Empty string
        v != 0 &&         // Equals zero (purposefully loose)
        lc !== '.' &&     // Trailing period
        !(lc === '0' && e.target.value.indexOf('.') !== -1) // Zero after a period
      ) {
        setValue(e.target.value, prop, 1);
      }

    });

    inputs[prop].set(inputs[prop].value);
  };


  const setInputValues = (prop, value) => {
    inputs[prop].obj.value = value;
    inputs[prop].value = value;
    updateResult();
  };


  const setRadioEvents = (radios, prop) => {
    for (const rad of radios) {
      rad.addEventListener('click', e => {
        inputs[prop].set(e.target.value);
      });
    }
  };


  const setRadioValues = (prop, value) => {
    inputs[prop].previousValue = inputs[prop].value;
    inputs[prop].value = value;
    if (toUnit) toUnit.textContent = inputs.unitsTo.value;
    updateResult();
  };


  const inputs = {

    rootFS : {
        obj : document.querySelector('.IN-rootFS'),
        value : 16,
        init : _ => setInputEvents('rootFS'),
        set : value => setInputValues('rootFS', value)
    },

    fromFS : {
        obj : document.querySelector('.IN-fromFS'),
        value : 16,
        init :  _ => setInputEvents('fromFS'),
        set : value => setInputValues('fromFS', value)
    },

    toFS : {
        obj : document.querySelector('.IN-toFS'),
        value : 1,
        init : _ => {},
        set : value => {
          // Prevents infinite recursion
          if (value !== parseFloat(inputs.toFS.obj.value)) {
            setInputValues('toFS', value);
          }
        }
    },

    unitsFrom : {
        value : 'px',
        previousValue : 'px',
        init : _ => setRadioEvents(radiosFrom, 'unitsFrom'),
        set : value => setRadioValues('unitsFrom', value)
    },

    unitsTo : {
        value : 'rem',
        previousValue : 'rem',
        init : _ => setRadioEvents(radiosTo, 'unitsTo'),
        set : value => setRadioValues('unitsTo', value)
    }

  };


  const updateResult = _ => {

    const from = inputs.fromFS.value;
    const root = inputs.rootFS.value;

    // Convert From value into pixels
    let pix = 0;
    switch(inputs.unitsFrom.value) {

      case 'px' :
        pix = from;
        break;
      case 'pt' :
        pix = from * 96 / 72;
        break;
      case 'percent' :
        pix = from * root / 100;
        break;
      case 'rem' :
        pix = from * root;
        break;

    }

    // Sets Result value
    switch (inputs.unitsTo.value) {

      case 'px' :
        pix = pix;
        break;
      case 'pt' :
        pix = pix * 72 / 96;
        break;
      case 'percent' :
        pix = pix * 100 / root;
        break;
      case 'rem' :
        pix = pix / root;
        break;
    }

    setValue(pix, 'toFS', 1);

  };

  // Open input settings and for each primary, run init

  const initialise = _ => {

    // Object.keys(inputs).forEach(key => inputs[key].init());

    Object.values(inputs).forEach(prop => prop.init());

    // Required to reset CSS ticks on page refresh
    const checked = document.querySelectorAll('[class^="IN_radio"]:checked');
    for (const chkd of checked) chkd.click();

  };

  initialise();

}(window, document, supportsES6));






// (Simulated) Update SVG calculator values (purely for the visual)

var Update_Svg_Calculator = (function (window, document, supportsES6) {

  'use strict';
  if (!supportsES6) return;

  const root_fs = document.querySelector('.IN-rootFS');
  const from_fs = document.querySelector('.IN-fromFS');
  const to_fs = document.querySelector('.IN-toFS');
  if (!(root_fs && from_fs && to_fs)) return;

  const radios = document.querySelectorAll('[class^="IN_radio-"]');
  if (!radios) return;

  // Limit to three rounded decimal places.
  const sanitize = value => Math.round(value * 1000) / 1000;

  const setSVG = _ => {

    const value = {
      from : from_fs.value,
      to : to_fs.value
    }

    const set = key => {

      const txtObj = document.getElementById('svg_' + key);
      if (!txtObj) return;

      const selector = `.IN_radio-${key}:checked`;
      const unitObj = document.querySelector(selector);
      if (!unitObj) return;

      const unit = unitObj.value === 'percent' ? '%' : unitObj.value;

      txtObj.textContent = sanitize(value[key]) + unit;
    }

    Object.keys(value).forEach(key => set(key));

  };


  const init = _ => {

    root_fs.addEventListener('input', setSVG);
    from_fs.addEventListener('input', setSVG);
    to_fs.addEventListener('input', setSVG);

    for (const rad of radios) {
      rad.addEventListener('click', setSVG);
    }

    setSVG();

  };


  init();

}(window, document, supportsES6));
              
            
!
999px

Console