hosted-fields/internal/polyfills/simple-placeholder-shim.js

'use strict';

/**
 * standalone placeholder shim
 * ===========================
 * There are [a bunch of placeholder polyfills](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-browser-Polyfills#web-forms--input-placeholder) out there, but this one is different because:
 * - it has no dependencies
 * - it doesn't change the `value`
 * - it works with weirdly-positioned elements (like `position: fixed`)
 * - it can be manually triggered
 * - the placeholder can be styled
 * - it's made for IE8 and IE9 and no other browsers
 */

var CSS_PROPERTIES_TO_STEAL = [
  'border-width',
  'font',
  'fontFamily',
  'fontSize',
  'fontSizeAdjust',
  'fontStretch',
  'fontStyle',
  'fontVariant',
  'fontVariantAlternates',
  'fontVariantCaps',
  'fontVariantEastAsian',
  'fontVariantLigatures',
  'fontVariantNumeric',
  'fontWeight',
  'lineHeight',
  'padding',
  'textAlign',
  'textShadow'
];

var hasAddedGlobalStyles = false;

// the big kahuna
function placeholderShim(inputEl) {
  var placeholderEl, displayShow;
  var testInput = document.createElement('input');
  var isNativelySupported = testInput.placeholder !== void 0; // eslint-disable-line no-void

  if (isNativelySupported) {
    // This browser supports placeholders natively, so do nothing.
    return {};
  }

  addGlobalStyles();

  placeholderEl = createPlaceholderFor(inputEl);
  inputEl.parentNode.appendChild(placeholderEl);

  displayShow = getStyle(inputEl, 'display') || 'block';
  function update(force) {
    var shouldShow = force == null ? !inputEl.value : force;

    placeholderEl.style.display = shouldShow ? displayShow : 'none';
  }

  update();

  if (inputEl.attachEvent) {
    inputEl.attachEvent('onfocus', function () { update(false); });
    inputEl.attachEvent('onblur', function () { update(); });
    placeholderEl.attachEvent('onclick', function () { inputEl.focus(); });
  }

  return new Placeholder();
}

function Placeholder() {}

// TODO
extend(Placeholder.prototype, {
  redraw: noop,
  destroy: noop
});

// the private methods

function noop() {}

function extend(dest, source) {
  var prop;

  for (prop in source) {
    if (source.hasOwnProperty(prop) && source[prop] != null) {
      dest[prop] = source[prop];
    }
  }
}

function addGlobalStyles() {
  var sheet, head, style;

  if (hasAddedGlobalStyles) { return; }

  sheet = document.styleSheets && document.styleSheets[0];

  if (!sheet) {
    head = document.head || document.getElementsByTagName('head')[0];
    style = document.createElement('style');
    head.appendChild(style);
    sheet = style.sheet;
  }

  sheet.addRule('.placeholder-shim', 'color: #999;', 0);

  hasAddedGlobalStyles = true;
}

function createPlaceholderFor(el) {
  var result = document.createElement('div');

  updateParentPositionFor(el);
  addProperties(result);
  stealStyles(el, result);
  addStyles(result);
  stealPlaceholder(el, result);
  return result;
}

function addProperties(result) {
  result.setAttribute('unselectable', 'on');
}

function stealStyles(src, dest) {
  var newStyles = {
    width: src.offsetWidth + 'px',
    height: src.offsetHeight + 'px'
  };
  var i, property, zIndex, borderTop, borderLeft;

  for (i = 0; i < CSS_PROPERTIES_TO_STEAL.length; i++) {
    property = CSS_PROPERTIES_TO_STEAL[i];
    newStyles[property] = getStyle(src, property);
  }

  zIndex = getStyle(src, 'zIndex');
  newStyles.zIndex = typeof zIndex === 'number' ? zIndex + 1 : 999;

  if (getStyle(src, 'position') === 'fixed') {
    extend(newStyles, {
      position: 'fixed',
      margin: getStyle(src, 'margin'),
      top: getStyle(src, 'top'),
      left: getStyle(src, 'left'),
      bottom: getStyle(src, 'bottom'),
      right: getStyle(src, 'right')
    });
  } else {
    borderTop = parseFloat(getStyle(src, 'borderTopWidth')) || 0;
    borderLeft = parseFloat(getStyle(src, 'borderLeftWidth')) || 0;

    extend(newStyles, {
      position: 'absolute',
      top: src.offsetTop + borderTop + 'px',
      left: src.offsetLeft + borderLeft + 'px'
    });
  }

  extend(dest.style, newStyles);
}

function addStyles(dest) {
  dest.className = 'placeholder-shim';

  extend(dest.style, {
    boxSizing: 'border-box',
    borderColor: 'transparent',
    overflow: 'hidden',
    whiteSpace: 'nowrap'
  });
}

function updateParentPositionFor(el) {
  var parent = el.offsetParent;

  if (parent && getStyle(parent, 'position') === 'static') {
    parent.style.position = 'relative';
  }
}

function stealPlaceholder(src, dest) {
  var result = src.getAttribute('placeholder') ||
    src.attributes.placeholder && src.attributes.placeholder.nodeValue ||
      '';

  result = result.replace(/&/g, '&amp;')
  .replace(/</g, '&lt;')
  .replace(/>/g, '&gt;');
  dest.innerHTML = result;
}

function getStyle(elem, prop) {
  if (elem.currentStyle) {
    return elem.currentStyle[prop];
  } else if (window.getComputedStyle && elem instanceof HTMLElement) {
    return document.defaultView.getComputedStyle(elem, null)[prop];
  } else if (prop in elem.style) {
    return elem.style[prop];
  }
  return null;
}

module.exports = placeholderShim;