Shopping Cart

Shopping Cart

Turn on a screen reader to experience this example in action.

Content updated dynamically

dummy link
Shopping cart, total dollars
Thumbnail Item Name Unit price Quantity Actions Extras Total
Cart Total: $

HTML Source Code

<p>Content updated dynamically</p>
  <a href="#">dummy link</a>
<div id="cart">
    <table class="list" tabindex="-1">
      <caption class="clipped">
        <span>Shopping cart, total </span>
        <span class="total"></span>
        <span> dollars</span>
      </caption>
      <thead>
        <tr>
          <th scope="col">Thumbnail</th>
          <th scope="col">Item Name</th>
          <th scope="col">Unit price</th>
          <th id="qty-header" scope="col">Quantity</th>
          <th scope="col">Actions</th>
          <th scope="col">Extras</th>
          <th scope="col">Total</th>
        </tr>
      </thead>
      <tbody></tbody>
    </table>

    <div class="summary">
      <span id="cart-total">
        <span>Cart Total: $</span><span class="total"></span>
      </span>
    </div>

    <div id="cart-log" class="clipped" aria-live="polite" aria-atomic="true"></div>
  </div>

JavaScript Source Code

This code was combined from three separate files cart.js, item.js, and index.js

(function outer(modules, cache, entries){

  /**
   * Global
   */

  var global = (function(){ return this; })();

  /**
   * Require `name`.
   *
   * @param {String} name
   * @param {Boolean} jumped
   * @api public
   */

  function require(name, jumped){
    if (cache[name]) return cache[name].exports;
    if (modules[name]) return call(name, require);
    throw new Error('cannot find module "' + name + '"');
  }

  /**
   * Call module `id` and cache it.
   *
   * @param {Number} id
   * @param {Function} require
   * @return {Function}
   * @api private
   */

  function call(id, require){
    var m = cache[id] = { exports: {} };
    var mod = modules[id];
    var name = mod[2];
    var fn = mod[0];

    fn.call(m.exports, function(req){
      var dep = modules[id][1][req];
      return require(dep ? dep : req);
    }, m, m.exports, outer, modules, cache, entries);

    // expose as `name`.
    if (name) cache[name] = cache[id];

    return cache[id].exports;
  }

  /**
   * Require all entries exposing them on global if needed.
   */

  for (var id in entries) {
    if (entries[id]) {
      global[entries[id]] = require(id);
    } else {
      require(id);
    }
  }

  /**
   * Duo flag.
   */

  require.duo = true;

  /**
   * Expose cache.
   */

  require.cache = cache;

  /**
   * Expose modules
   */

  require.modules = modules;

  /**
   * Return newest require.
   */

   return require;
})({
1: [function(require, module, exports) {

/**
 * Module dependencies.
 */

var data = require('./data.json');
var Item = require('./item');
var Cart = require('./cart');

/**
 * Create the cart, and add all the items.
 */

var cart = new Cart();

while (data.items.length) {
  var _name = data.items.shift();
  addItem(_name);
}

cart.updateTotal();

/**
 * Creates a new `Item#` and appends it to `Cart#`.
 *
 * @param {String} name
 * @api private
 */

function addItem(name) {
  new Item(name)
  .initialize()
  .appendTo(cart)
  .on('updated', function () {
    cart.updateTotal();
  })
  .on('removed', function () {
    cart.updateTotal();
    cart.focusWrapper();
  })
  .on('log', function (data) {
    cart.log(data.msg);
  });
}

}, {"./data.json":2,"./item":3,"./cart":4}],
2: [function(require, module, exports) {
module.exports = {
  "items": [
    "Ray Gun",
    "Leonidas",
    "Beef Jerky",
    "Amplifier",
    "Cheesecake"
  ]
};
}, {}],
3: [function(require, module, exports) {

/**
 * Module dependencies.
 */

var itemHtml = require('./item.html');
var classes = require('component/classes');
var domify = require('component/domify');
var events = require('component/events');
var Emitter = require('component/emitter');
var closest = require('component/closest');
var Confirmation = require('component/confirmation');
var focusTrap = require('stephenmathieson/focus-trap');
var fmt = require('yields/fmt');

/**
 * Extend `fmt` to format currency values.
 */

fmt.c = function (n) {
  return Number(n || 0).toFixed(2);
};

/**
 * Expose `Item`.
 */

module.exports = Item;

/**
 * Create a new `Item`.
 *
 * @param {String} name
 * @api public
 */

function Item(name) {
  this.el = domify(itemHtml);
  this.name = name;
  this.price = 9.99;
  this.quantity = 1;
  this.warrantyCost = 4.99;
  this.giftwrapCost = 2.99;
  this.nameEl = this.el.querySelector('.name');
  this.thumb = this.el.querySelector('.thumb img');
  this.priceEl = this.el.querySelector('.price .dollars');
  this.quantityEl = this.el.querySelector('.quantity input');
  this.extrasEl = this.el.querySelector('.extras');
  this.totalEl = this.el.querySelector('td.total .dollars');
  this.events = events(this.el, this);
  this.events.bind('change .quantity input', 'updateTotal');
  this.events.bind('click button.warranty', 'toggleExtra');
  this.events.bind('click button.giftwrap', 'toggleExtra');
  this.events.bind('click button.remove', 'remove');
}

/**
 * Mixin `Emitter`.
 */

Emitter(Item.prototype);

/**
 * Sets up the Item. Invoke prior to `#appendTo`.
 *
 * @return {Item}
 * @api public
 */

Item.prototype.initialize = function () {
  var altStr = 'Thumbnail image of ';
  this.nameEl.innerHTML = this.name;
  this.thumb.setAttribute('alt', altStr + this.name);
  this.priceEl.innerHTML = this.price;
  this.quantityEl.value = this.quantity;
  this.totalPrice = this.totalEl.innerHTML =
    Number(this.price * this.quantity).toFixed(2);
  return this;
};

/**
 * Appends the Item to a given `cart`.
 *
 * @param {Cart} cart
 * @return {Item}
 * @api public
 */

Item.prototype.appendTo = function (cart) {
  cart.listEl.appendChild(this.el);
  cart.items.push(this);
  return this;
};

/**
 * Updates `Item#totalPrice` and `Item#totalEl.innerHTML`
 * using available item info.
 *
 * @return {Item}
 * @api public
 */

Item.prototype.updateTotal = function () {
  var qty = this.quantity = this.quantityEl.value;
  var warranty = this.extrasEl.querySelector('.warranty.active')
    ? this.warrantyCost * qty : 0;
  var giftwrap = this.extrasEl.querySelector('.giftwrap.active')
    ? this.giftwrapCost * qty : 0;

  this.totalPrice = (this.price * this.quantityEl.value)
    + warranty
    + giftwrap;

  this.totalEl.innerHTML = Number(this.totalPrice).toFixed(2);
  this.emit('updated');
  this.log(fmt('Item total: %c dollars', this.totalPrice));

  return this;
};

/**
 * Handles clicks on "action" items which toggle "extras".
 *
 * @param {Event} e
 * @return {Item}
 * @api public
 */

Item.prototype.toggleExtra = function (e) {
  var button = closest(e.target, 'button', true);
  var extraName = button.className;
  var extra = this.extrasEl.querySelector('.' + extraName);
  var action = button.querySelector('.action');
  var origState = action.innerHTML;
  var extraMsg;

  // Toggle "active" class to display/hide the "extra"
  classes(extra).toggle('active');

  // Toggle the trigger button's text
  if (origState == 'Add') {
    action.innerHTML = 'Remove';
    extraMsg = extraName + ' added. ';
  } else {
    action.innerHTML = 'Add';
    extraMsg = extraName + ' removed. ';
  }

  this.updateTotal();
  this.log(extraMsg + fmt('Item total: %c dollars', this.totalPrice));

  return this;
};

/**
 * Handles clicks on the "Remove Item" button.
 *
 * Triggers a confirmation prompt before
 * removing the item.
 *
 * @api public
 */

Item.prototype.remove = function () {
  var self = this;
  var trigger = self.el.querySelector('button.remove');

  new Confirmation('Do you really want to remove ' + self.name + ' from your cart?')
  .ok('Yes')
  .cancel('No')
  .fixed()

  .on('show', function () {
    focusTrap(this.el.querySelector('.confirmation-actions'));
    this.el.setAttribute('role', 'alertdialog');
  })

  .on('ok', function () {
    self.totalPrice = 0;
    self.el.parentNode.removeChild(self.el);
    self.emit('removed');
    self.log(self.name + ' removed from cart');
    trigger.focus();
  })

  .on('cancel', function () {
    trigger.focus();
  })

  .show();
};


Item.prototype.log = function (msg) {
  this.emit('log', {
    msg: '<span>' + msg + '</span>'
  });
};

}, {"./item.html":5,"component/classes":6,"component/domify":7,"component/events":8,"component/emitter":9,"component/closest":10,"component/confirmation":11,"stephenmathieson/focus-trap":12,"yields/fmt":13}],
5: [function(require, module, exports) {
module.exports = '\n<tr class="item">\n\n  <td class="thumb">\n    <img src="https://dequeuniversity.com/assets/js/aria/custom-form-controls/shopping-cart/sf/box.png">\n  </td>\n\n  <th class="name" scope="row"></th>\n\n  <td class="price">\n    <span class="dollars"></span>\n    <span class="clipped"> dollars</span>\n  </td>\n\n  <td class="quantity">\n    <input type="number" min="0" aria-labelledby="qty-header">\n  </td>\n\n  <td class="actions">\n    <ul>\n      <li>\n        <button aria-controls="cart-total" class="warranty">\n          <span class="action">Add</span>\n          <span> Warranty</span>\n        </button>\n      </li>\n      <li>\n        <button aria-controls="cart-total" class="giftwrap">\n          <span class="action">Add</span>\n          <span> Gift Wrapping</span>\n        </button>\n      </li>\n      <li>\n        <button aria-controls="cart-total" class="remove">Remove Item</button>\n      </li>\n    </ul>\n  </td>\n\n  <td class="extras">\n    <ul>\n      <li class="warranty">Warranty ($4.99)</li>\n      <li class="giftwrap">Gift Wrapping ($2.99)</li>\n    </ul>\n  </td>\n\n  <td class="total">\n    <span class="dollars"></span>\n    <span class="clipped"> dollars</span>\n  </td>\n\n</tr>';
}, {}],
6: [function(require, module, exports) {
/**
 * Module dependencies.
 */

var index = require('indexof');

/**
 * Whitespace regexp.
 */

var re = /\s+/;

/**
 * toString reference.
 */

var toString = Object.prototype.toString;

/**
 * Wrap `el` in a `ClassList`.
 *
 * @param {Element} el
 * @return {ClassList}
 * @api public
 */

module.exports = function(el){
  return new ClassList(el);
};

/**
 * Initialize a new ClassList for `el`.
 *
 * @param {Element} el
 * @api private
 */

function ClassList(el) {
  if (!el || !el.nodeType) {
    throw new Error('A DOM element reference is required');
  }
  this.el = el;
  this.list = el.classList;
}

/**
 * Add class `name` if not already present.
 *
 * @param {String} name
 * @return {ClassList}
 * @api public
 */

ClassList.prototype.add = function(name){
  // classList
  if (this.list) {
    this.list.add(name);
    return this;
  }

  // fallback
  var arr = this.array();
  var i = index(arr, name);
  if (!~i) arr.push(name);
  this.el.className = arr.join(' ');
  return this;
};

/**
 * Remove class `name` when present, or
 * pass a regular expression to remove
 * any which match.
 *
 * @param {String|RegExp} name
 * @return {ClassList}
 * @api public
 */

ClassList.prototype.remove = function(name){
  if ('[object RegExp]' == toString.call(name)) {
    return this.removeMatching(name);
  }

  // classList
  if (this.list) {
    this.list.remove(name);
    return this;
  }

  // fallback
  var arr = this.array();
  var i = index(arr, name);
  if (~i) arr.splice(i, 1);
  this.el.className = arr.join(' ');
  return this;
};

/**
 * Remove all classes matching `re`.
 *
 * @param {RegExp} re
 * @return {ClassList}
 * @api private
 */

ClassList.prototype.removeMatching = function(re){
  var arr = this.array();
  for (var i = 0; i < arr.length; i++) {
    if (re.test(arr[i])) {
      this.remove(arr[i]);
    }
  }
  return this;
};

/**
 * Toggle class `name`, can force state via `force`.
 *
 * For browsers that support classList, but do not support `force` yet,
 * the mistake will be detected and corrected.
 *
 * @param {String} name
 * @param {Boolean} force
 * @return {ClassList}
 * @api public
 */

ClassList.prototype.toggle = function(name, force){
  // classList
  if (this.list) {
    if ("undefined" !== typeof force) {
      if (force !== this.list.toggle(name, force)) {
        this.list.toggle(name); // toggle again to correct
      }
    } else {
      this.list.toggle(name);
    }
    return this;
  }

  // fallback
  if ("undefined" !== typeof force) {
    if (!force) {
      this.remove(name);
    } else {
      this.add(name);
    }
  } else {
    if (this.has(name)) {
      this.remove(name);
    } else {
      this.add(name);
    }
  }

  return this;
};

/**
 * Return an array of classes.
 *
 * @return {Array}
 * @api public
 */

ClassList.prototype.array = function(){
  var str = this.el.className.replace(/^\s+|\s+$/g, '');
  var arr = str.split(re);
  if ('' === arr[0]) arr.shift();
  return arr;
};

/**
 * Check if class `name` is present.
 *
 * @param {String} name
 * @return {ClassList}
 * @api public
 */

ClassList.prototype.has =
ClassList.prototype.contains = function(name){
  return this.list
    ? this.list.contains(name)
    : !! ~index(this.array(), name);
};

}, {"indexof":14}],
14: [function(require, module, exports) {
module.exports = function(arr, obj){
  if (arr.indexOf) return arr.indexOf(obj);
  for (var i = 0; i < arr.length; ++i) {
    if (arr[i] === obj) return i;
  }
  return -1;
};
}, {}],
7: [function(require, module, exports) {

/**
 * Expose `parse`.
 */

module.exports = parse;

/**
 * Tests for browser support.
 */

var div = document.createElement('div');
// Setup
div.innerHTML = '  <link/><table></table><a href="/a">a</a><input type="checkbox"/>';
// Make sure that link elements get serialized correctly by innerHTML
// This requires a wrapper element in IE
var innerHTMLBug = !div.getElementsByTagName('link').length;
div = undefined;

/**
 * Wrap map from jquery.
 */

var map = {
  legend: [1, '<fieldset>', '</fieldset>'],
  tr: [2, '<table><tbody>', '</tbody></table>'],
  col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
  // for script/link/style tags to work in IE6-8, you have to wrap
  // in a div with a non-whitespace character in front, ha!
  _default: innerHTMLBug ? [1, 'X<div>', '</div>'] : [0, '', '']
};

map.td =
map.th = [3, '<table><tbody><tr>', '</tr></tbody></table>'];

map.option =
map.optgroup = [1, '<select multiple="multiple">', '</select>'];

map.thead =
map.tbody =
map.colgroup =
map.caption =
map.tfoot = [1, '<table>', '</table>'];

map.text =
map.circle =
map.ellipse =
map.line =
map.path =
map.polygon =
map.polyline =
map.rect = [1, '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">','</svg>'];

/**
 * Parse `html` and return a DOM Node instance, which could be a TextNode,
 * HTML DOM Node of some kind (<div> for example), or a DocumentFragment
 * instance, depending on the contents of the `html` string.
 *
 * @param {String} html - HTML string to "domify"
 * @param {Document} doc - The `document` instance to create the Node for
 * @return {DOMNode} the TextNode, DOM Node, or DocumentFragment instance
 * @api private
 */

function parse(html, doc) {
  if ('string' != typeof html) throw new TypeError('String expected');

  // default to the global `document` object
  if (!doc) doc = document;

  // tag name
  var m = /<([\w:]+)/.exec(html);
  if (!m) return doc.createTextNode(html);

  html = html.replace(/^\s+|\s+$/g, ''); // Remove leading/trailing whitespace

  var tag = m[1];

  // body support
  if (tag == 'body') {
    var el = doc.createElement('html');
    el.innerHTML = html;
    return el.removeChild(el.lastChild);
  }

  // wrap map
  var wrap = map[tag] || map._default;
  var depth = wrap[0];
  var prefix = wrap[1];
  var suffix = wrap[2];
  var el = doc.createElement('div');
  el.innerHTML = prefix + html + suffix;
  while (depth--) el = el.lastChild;

  // one element
  if (el.firstChild == el.lastChild) {
    return el.removeChild(el.firstChild);
  }

  // several elements
  var fragment = doc.createDocumentFragment();
  while (el.firstChild) {
    fragment.appendChild(el.removeChild(el.firstChild));
  }

  return fragment;
}

}, {}],
8: [function(require, module, exports) {

/**
 * Module dependencies.
 */

var events = require('event');
var delegate = require('delegate');

/**
 * Expose `Events`.
 */

module.exports = Events;

/**
 * Initialize an `Events` with the given
 * `el` object which events will be bound to,
 * and the `obj` which will receive method calls.
 *
 * @param {Object} el
 * @param {Object} obj
 * @api public
 */

function Events(el, obj) {
  if (!(this instanceof Events)) return new Events(el, obj);
  if (!el) throw new Error('element required');
  if (!obj) throw new Error('object required');
  this.el = el;
  this.obj = obj;
  this._events = {};
}

/**
 * Subscription helper.
 */

Events.prototype.sub = function(event, method, cb){
  this._events[event] = this._events[event] || {};
  this._events[event][method] = cb;
};

/**
 * Bind to `event` with optional `method` name.
 * When `method` is undefined it becomes `event`
 * with the "on" prefix.
 *
 * Examples:
 *
 *  Direct event handling:
 *
 *    events.bind('click') // implies "onclick"
 *    events.bind('click', 'remove')
 *    events.bind('click', 'sort', 'asc')
 *
 *  Delegated event handling:
 *
 *    events.bind('click li > a')
 *    events.bind('click li > a', 'remove')
 *    events.bind('click a.sort-ascending', 'sort', 'asc')
 *    events.bind('click a.sort-descending', 'sort', 'desc')
 *
 * @param {String} event
 * @param {String|function} [method]
 * @return {Function} callback
 * @api public
 */

Events.prototype.bind = function(event, method){
  var e = parse(event);
  var el = this.el;
  var obj = this.obj;
  var name = e.name;
  var method = method || 'on' + name;
  var args = [].slice.call(arguments, 2);

  // callback
  function cb(){
    var a = [].slice.call(arguments).concat(args);
    obj[method].apply(obj, a);
  }

  // bind
  if (e.selector) {
    cb = delegate.bind(el, e.selector, name, cb);
  } else {
    events.bind(el, name, cb);
  }

  // subscription for unbinding
  this.sub(name, method, cb);

  return cb;
};

/**
 * Unbind a single binding, all bindings for `event`,
 * or all bindings within the manager.
 *
 * Examples:
 *
 *  Unbind direct handlers:
 *
 *     events.unbind('click', 'remove')
 *     events.unbind('click')
 *     events.unbind()
 *
 * Unbind delegate handlers:
 *
 *     events.unbind('click', 'remove')
 *     events.unbind('click')
 *     events.unbind()
 *
 * @param {String|Function} [event]
 * @param {String|Function} [method]
 * @api public
 */

Events.prototype.unbind = function(event, method){
  if (0 === arguments.length) return this.unbindAll();
  if (1 === arguments.length) return this.unbindAllOf(event);

  // no bindings for this event
  var bindings = this._events[event];
  if (!bindings) return;

  // no bindings for this method
  var cb = bindings[method];
  if (!cb) return;

  events.unbind(this.el, event, cb);
};

/**
 * Unbind all events.
 *
 * @api private
 */

Events.prototype.unbindAll = function(){
  for (var event in this._events) {
    this.unbindAllOf(event);
  }
};

/**
 * Unbind all events for `event`.
 *
 * @param {String} event
 * @api private
 */

Events.prototype.unbindAllOf = function(event){
  var bindings = this._events[event];
  if (!bindings) return;

  for (var method in bindings) {
    this.unbind(event, method);
  }
};

/**
 * Parse `event`.
 *
 * @param {String} event
 * @return {Object}
 * @api private
 */

function parse(event) {
  var parts = event.split(/ +/);
  return {
    name: parts.shift(),
    selector: parts.join(' ')
  }
}

}, {"event":15,"delegate":16}],
15: [function(require, module, exports) {
var bind = window.addEventListener ? 'addEventListener' : 'attachEvent',
    unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent',
    prefix = bind !== 'addEventListener' ? 'on' : '';

/**
 * Bind `el` event `type` to `fn`.
 *
 * @param {Element} el
 * @param {String} type
 * @param {Function} fn
 * @param {Boolean} capture
 * @return {Function}
 * @api public
 */

exports.bind = function(el, type, fn, capture){
  el[bind](prefix + type, fn, capture || false);
  return fn;
};

/**
 * Unbind `el` event `type`'s callback `fn`.
 *
 * @param {Element} el
 * @param {String} type
 * @param {Function} fn
 * @param {Boolean} capture
 * @return {Function}
 * @api public
 */

exports.unbind = function(el, type, fn, capture){
  el[unbind](prefix + type, fn, capture || false);
  return fn;
};
}, {}],
16: [function(require, module, exports) {
/**
 * Module dependencies.
 */

var closest = require('closest')
  , event = require('event');

/**
 * Delegate event `type` to `selector`
 * and invoke `fn(e)`. A callback function
 * is returned which may be passed to `.unbind()`.
 *
 * @param {Element} el
 * @param {String} selector
 * @param {String} type
 * @param {Function} fn
 * @param {Boolean} capture
 * @return {Function}
 * @api public
 */

exports.bind = function(el, selector, type, fn, capture){
  return event.bind(el, type, function(e){
    var target = e.target || e.srcElement;
    e.delegateTarget = closest(target, selector, true, el);
    if (e.delegateTarget) fn.call(el, e);
  }, capture);
};

/**
 * Unbind event `type`'s callback `fn`.
 *
 * @param {Element} el
 * @param {String} type
 * @param {Function} fn
 * @param {Boolean} capture
 * @api public
 */

exports.unbind = function(el, type, fn, capture){
  event.unbind(el, type, fn, capture);
};

}, {"closest":10,"event":15}],
10: [function(require, module, exports) {
var matches = require('matches-selector')

module.exports = function (element, selector, checkYoSelf, root) {
  element = checkYoSelf ? {parentNode: element} : element

  root = root || document

  // Make sure `element !== document` and `element != null`
  // otherwise we get an illegal invocation
  while ((element = element.parentNode) && element !== document) {
    if (matches(element, selector))
      return element
    // After `matches` on the edge case that
    // the selector matches the root
    // (when the root is not the document)
    if (element === root)
      return
  }
}

}, {"matches-selector":17}],
17: [function(require, module, exports) {
/**
 * Module dependencies.
 */

var query = require('query');

/**
 * Element prototype.
 */

var proto = Element.prototype;

/**
 * Vendor function.
 */

var vendor = proto.matches
  || proto.webkitMatchesSelector
  || proto.mozMatchesSelector
  || proto.msMatchesSelector
  || proto.oMatchesSelector;

/**
 * Expose `match()`.
 */

module.exports = match;

/**
 * Match `el` to `selector`.
 *
 * @param {Element} el
 * @param {String} selector
 * @return {Boolean}
 * @api public
 */

function match(el, selector) {
  if (!el || el.nodeType !== 1) return false;
  if (vendor) return vendor.call(el, selector);
  var nodes = query.all(selector, el.parentNode);
  for (var i = 0; i < nodes.length; ++i) {
    if (nodes[i] == el) return true;
  }
  return false;
}

}, {"query":18}],
18: [function(require, module, exports) {
function one(selector, el) {
  return el.querySelector(selector);
}

exports = module.exports = function(selector, el){
  el = el || document;
  return one(selector, el);
};

exports.all = function(selector, el){
  el = el || document;
  return el.querySelectorAll(selector);
};

exports.engine = function(obj){
  if (!obj.one) throw new Error('.one callback required');
  if (!obj.all) throw new Error('.all callback required');
  one = obj.one;
  exports.all = obj.all;
  return exports;
};

}, {}],
9: [function(require, module, exports) {

/**
 * Expose `Emitter`.
 */

module.exports = Emitter;

/**
 * Initialize a new `Emitter`.
 *
 * @api public
 */

function Emitter(obj) {
  if (obj) return mixin(obj);
};

/**
 * Mixin the emitter properties.
 *
 * @param {Object} obj
 * @return {Object}
 * @api private
 */

function mixin(obj) {
  for (var key in Emitter.prototype) {
    obj[key] = Emitter.prototype[key];
  }
  return obj;
}

/**
 * Listen on the given `event` with `fn`.
 *
 * @param {String} event
 * @param {Function} fn
 * @return {Emitter}
 * @api public
 */

Emitter.prototype.on =
Emitter.prototype.addEventListener = function(event, fn){
  this._callbacks = this._callbacks || {};
  (this._callbacks[event] = this._callbacks[event] || [])
    .push(fn);
  return this;
};

/**
 * Adds an `event` listener that will be invoked a single
 * time then automatically removed.
 *
 * @param {String} event
 * @param {Function} fn
 * @return {Emitter}
 * @api public
 */

Emitter.prototype.once = function(event, fn){
  var self = this;
  this._callbacks = this._callbacks || {};

  function on() {
    self.off(event, on);
    fn.apply(this, arguments);
  }

  on.fn = fn;
  this.on(event, on);
  return this;
};

/**
 * Remove the given callback for `event` or all
 * registered callbacks.
 *
 * @param {String} event
 * @param {Function} fn
 * @return {Emitter}
 * @api public
 */

Emitter.prototype.off =
Emitter.prototype.removeListener =
Emitter.prototype.removeAllListeners =
Emitter.prototype.removeEventListener = function(event, fn){
  this._callbacks = this._callbacks || {};

  // all
  if (0 == arguments.length) {
    this._callbacks = {};
    return this;
  }

  // specific event
  var callbacks = this._callbacks[event];
  if (!callbacks) return this;

  // remove all handlers
  if (1 == arguments.length) {
    delete this._callbacks[event];
    return this;
  }

  // remove specific handler
  var cb;
  for (var i = 0; i < callbacks.length; i++) {
    cb = callbacks[i];
    if (cb === fn || cb.fn === fn) {
      callbacks.splice(i, 1);
      break;
    }
  }
  return this;
};

/**
 * Emit `event` with the given args.
 *
 * @param {String} event
 * @param {Mixed} ...
 * @return {Emitter}
 */

Emitter.prototype.emit = function(event){
  this._callbacks = this._callbacks || {};
  var args = [].slice.call(arguments, 1)
    , callbacks = this._callbacks[event];

  if (callbacks) {
    callbacks = callbacks.slice(0);
    for (var i = 0, len = callbacks.length; i < len; ++i) {
      callbacks[i].apply(this, args);
    }
  }

  return this;
};

/**
 * Return array of callbacks for `event`.
 *
 * @param {String} event
 * @return {Array}
 * @api public
 */

Emitter.prototype.listeners = function(event){
  this._callbacks = this._callbacks || {};
  return this._callbacks[event] || [];
};

/**
 * Check if this emitter has `event` handlers.
 *
 * @param {String} event
 * @return {Boolean}
 * @api public
 */

Emitter.prototype.hasListeners = function(event){
  return !! this.listeners(event).length;
};

}, {}],
11: [function(require, module, exports) {
/**
 * Module dependencies.
 */

var Dialog = require('dialog').Dialog
  , events = require('event')
  , q = require('query')
  , html = require('./confirmation')
  , inherit = require('inherit');

/**
 * Expose `confirm()`.
 */

exports = module.exports = confirm;

/**
 * Expose `Confirmation`.
 */

exports.Confirmation = Confirmation;

/**
 * Return a new `Confirmation` with the given
 * (optional) `title` and `msg`.
 *
 * @param {String} title or msg
 * @param {String} msg
 * @return {Confirmation}
 * @api public
 */

function confirm(title, msg) {
  switch (arguments.length) {
    case 2:
      return new Confirmation({ title: title, message: msg });
    case 1:
      return new Confirmation({ message: title });
  }
}

/**
 * Initialize a new `Confirmation` dialog.
 *
 * @param {Object} options
 * @api public
 */

function Confirmation(options) {
  Dialog.call(this, options);
  this.focus('cancel');
}

/**
 * Inherits from `Dialog.prototype`.
 */

inherit(Confirmation, Dialog);

/**
 * Focus `type`, either "ok" or "cancel".
 *
 * @param {String} type
 * @return {Confirmation}
 * @api public
 */

Confirmation.prototype.focus = function(type){
  this._focus = type;
  return this;
};

/**
 * Change "cancel" button `text`.
 *
 * @param {String} text
 * @return {Confirmation}
 * @api public
 */

Confirmation.prototype.cancel = function(text){
  q('.cancel', this.el).innerHTML = text;
  return this;
};

/**
 * Change "ok" button `text`.
 *
 * @param {String} text
 * @return {Confirmation}
 * @api public
 */

Confirmation.prototype.ok = function(text){
  q('.ok', this.el).innerHTML = text;
  return this;
};

/**
 * Show the confirmation dialog and invoke `fn(ok)`.
 *
 * @param {Function} fn
 * @return {Confirmation} for chaining
 * @api public
 */

Confirmation.prototype.show = function(fn){
  Dialog.prototype.show.call(this);
  q('.' + this._focus, this.el).focus();
  this.callback = fn || function(){};
  return this;
};

/**
 * Render with the given `options`.
 *
 * Emits "cancel" event.
 * Emits "ok" event.
 *
 * @param {Object} options
 * @api public
 */

Confirmation.prototype.render = function(options){
  var self = this;
  Dialog.prototype.render.call(this, options);

  this._classes.add('confirmation');
  this.el.insertAdjacentHTML('beforeend', html);

  this.on('close', function(){
    self.emit('cancel');
    self.callback(false);
  });

  this.on('escape', function(){
    self.emit('cancel');
    self.callback(false);
  });

  events.bind(q('.cancel', this.el), 'click', function(e){
    e.preventDefault();
    self.emit('cancel');
    self.callback(false);
    self.hide();
  });

  events.bind(q('.ok', this.el), 'click', function(e){
    e.preventDefault();
    self.emit('ok');
    self.callback(true);
    self.hide();
  });
};

}, {"dialog":19,"event":15,"query":18,"./confirmation":20,"inherit":21}],
19: [function(require, module, exports) {

/**
 * Module dependencies.
 */

var Emitter = require('emitter')
  , overlay = require('overlay')
  , domify = require('domify')
  , events = require('event')
  , classes = require('classes')
  , query = require('query');

/**
 * Active dialog.
 */

var active;

/**
 * Expose `dialog()`.
 */

exports = module.exports = dialog;

/**
 * Expose `Dialog`.
 */

exports.Dialog = Dialog;

/**
 * Return a new `Dialog` with the given
 * (optional) `title` and `msg`.
 *
 * @param {String} title or msg
 * @param {String} msg
 * @return {Dialog}
 * @api public
 */

function dialog(title, msg){
  switch (arguments.length) {
    case 2:
      return new Dialog({ title: title, message: msg });
    case 1:
      return new Dialog({ message: title });
  }
};

/**
 * Initialize a new `Dialog`.
 *
 * Options:
 *
 *    - `title` dialog title
 *    - `message` a message to display
 *
 * Emits:
 *
 *    - `show` when visible
 *    - `hide` when hidden
 *
 * @param {Object} options
 * @api public
 */

function Dialog(options) {
  Emitter.call(this);
  options = options || {};
  this.template = require('./template.html');
  this.el = domify(this.template);
  this._classes = classes(this.el);
  this.render(options);
  if (active && !active.hiding) active.hide();
  if (exports.effect) this.effect(exports.effect);
  this.on('escape', this.hide.bind(this));
  active = this;
};

/**
 * Inherit from `Emitter.prototype`.
 */

Dialog.prototype = new Emitter;

/**
 * Render with the given `options`.
 *
 * @param {Object} options
 * @api public
 */

Dialog.prototype.render = function(options){
  var self = this
    , el = self.el
    , title = options.title
    , titleEl = query('.title', el)
    , pEl = query('p', el)
    , msg = options.message;

  events.bind(query('.close', el), 'click', function (ev) {
    ev.preventDefault();
    self.emit('close');
    self.hide();
  });

  if (titleEl) {
    if (!title) {
      titleEl.parentNode.removeChild(titleEl);
    } else {
      titleEl.textContent = title;
    }
  }

  // message
  if ('string' == typeof msg) {
    pEl.textContent = msg;
  } else if (msg) {
    pEl.parentNode.insertBefore(msg.el || msg, pEl);
    pEl.parentNode.removeChild(pEl);
  }
};

/**
 * Enable the dialog close link.
 *
 * @return {Dialog} for chaining
 * @api public
 */

Dialog.prototype.closable = function(){
  return this.addClass('closable');
};

/**
 * Add class `name`.
 *
 * @param {String} name
 * @return {Dialog}
 * @api public
 */

Dialog.prototype.addClass = function(name){
  this._classes.add(name);
  return this;
};

/**
 * Set the effect to `type`.
 *
 * @param {String} type
 * @return {Dialog} for chaining
 * @api public
 */

Dialog.prototype.effect = function(type){
  this._effect = type;
  this.addClass(type);
  return this;
};

/**
 * Make it modal!
 *
 * @return {Dialog} for chaining
 * @api public
 */

Dialog.prototype.modal = function(){
  this._overlay = overlay();
  return this;
};

/**
 * Add an overlay.
 *
 * @return {Dialog} for chaining
 * @api public
 */

Dialog.prototype.overlay = function(opts){
  var self = this;
  opts = opts || { closable: true };
  var o = overlay(opts);
  o.on('hide', function(){
    self._overlay = null;
    self.hide();
  });
  this._overlay = o;
  return this;
};

/**
 * Close the dialog when the escape key is pressed.
 *
 * @api public
 */

Dialog.prototype.escapable = function(){
  var self = this;
  // Save reference to remove listener later
  self._escKeyCallback = self._escKeyCallback || function (e) {
    e.which = e.which || e.keyCode;
    if (27 !== e.which) return;
    self.emit('escape');
  };
  events.bind(document, 'keydown', self._escKeyCallback);
  return this;
};

/**
 * Fixed dialogs position can be manipulated through CSS.
 *
 * @return {Dialog} for chaining
 * @api public
 */

Dialog.prototype.fixed = function(){
  this._fixed = true;
  return this;
}

/**
 * Show the dialog.
 *
 * Emits "show" event.
 *
 * @return {Dialog} for chaining
 * @api public
 */

Dialog.prototype.show = function(){
  var overlay = this._overlay;
  var self = this;

  // overlay
  if (overlay) {
    overlay.show();
    this._classes.add('modal');
  }

  // escape
  if (!overlay || overlay.closable) this.escapable();

  // position
  document.body.appendChild(this.el);
  if (!this._fixed) {
    setTimeout(function() {
      self.el.style.marginLeft = -(self.el.offsetWidth / 2) + 'px'
    }, 0);
  }
  this._classes.remove('hide');
  this.emit('show');
  return this;
};

/**
 * Hide the overlay.
 *
 * @api private
 */

Dialog.prototype.hideOverlay = function(){
  if (!this._overlay) return;
  this._overlay.remove();
  this._overlay = null;
};

/**
 * Hide the dialog with optional delay of `ms`,
 * otherwise the dialog is removed immediately.
 *
 * Emits "hide" event.
 *
 * @return {Number} ms
 * @return {Dialog} for chaining
 * @api public
 */

Dialog.prototype.hide = function(ms){
  var self = this;

  if (self._escKeyCallback) {
    events.unbind(document, 'keydown', self._escKeyCallback);
  }

  // prevent thrashing - this isn't used
  self.hiding = true;

  // duration
  if (ms) {
    setTimeout(function(){
      self.hide();
    }, ms);
    return self;
  }

  // hide / remove
  self._classes.add('hide');
  if (self._effect) {
    setTimeout(function(){
      self.remove();
    }, 500);
  } else {
    self.remove();
  }

  // overlay
  self.hideOverlay();

  return self;
};
/**
 * Hide the dialog without potential animation.
 *
 * @return {Dialog} for chaining
 * @api public
 */

Dialog.prototype.remove = function(){
  if (this.el.parentNode) {
    this.emit('hide');
    this.el.parentNode.removeChild(this.el);
  }
  return this;
};

}, {"emitter":9,"overlay":22,"domify":7,"event":15,"classes":23,"query":18,"./template.html":24}],
22: [function(require, module, exports) {

/**
 * Module dependencies.
 */

var Emitter = require('emitter');
var tmpl = require('./template.html');
var domify = require('domify');
var event = require('event');
var classes = require('classes');

/**
 * Expose `overlay()`.
 */

exports = module.exports = overlay;

/**
 * Expose `Overlay`.
 */

exports.Overlay = Overlay;

/**
 * Return a new `Overlay` with the given `options`.
 *
 * @param {Object|Element} options
 * @return {Overlay}
 * @api public
 */

function overlay(options){
  options = options || {};

  // element
  if (options.nodeName) {
    options = { target: options };
  }

  return new Overlay(options);
}

/**
 * Initialize a new `Overlay`.
 *
 * @param {Object} options
 * @api public
 */

function Overlay(options) {
  Emitter.call(this);
  options = options || {};
  this.target = options.target || document.body;
  this.closable = options.closable;
  this.el = domify(tmpl);
  this.target.appendChild(this.el);
  if (this.closable) {
	event.bind(this.el, 'click', this.hide.bind(this));
    classes(this.el).add('closable');
  }
}

/**
 * Mixin emitter.
 */

Emitter(Overlay.prototype);

/**
 * Show the overlay.
 *
 * Emits "show" event.
 *
 * @return {Overlay}
 * @api public
 */

Overlay.prototype.show = function(){
  this.emit('show');
  classes(this.el).remove('hidden');
  return this;
};

/**
 * Hide the overlay.
 *
 * Emits "hide" event.
 *
 * @return {Overlay}
 * @api public
 */

Overlay.prototype.hide = function(){
  this.emit('hide');
  return this.remove();
};

/**
 * Hide the overlay without emitting "hide".
 *
 * Emits "close" event.
 *
 * @return {Overlay}
 * @api public
 */

Overlay.prototype.remove = function(){
  var self = this;
  this.emit('close');
  classes(this.el).add('hidden');
  setTimeout(function(){
    self.target.removeChild(self.el);
  }, 350);
  return this;
};


}, {"emitter":25,"./template.html":26,"domify":27,"event":28,"classes":23}],
25: [function(require, module, exports) {

/**
 * Expose `Emitter`.
 */

module.exports = Emitter;

/**
 * Initialize a new `Emitter`.
 *
 * @api public
 */

function Emitter(obj) {
  if (obj) return mixin(obj);
};

/**
 * Mixin the emitter properties.
 *
 * @param {Object} obj
 * @return {Object}
 * @api private
 */

function mixin(obj) {
  for (var key in Emitter.prototype) {
    obj[key] = Emitter.prototype[key];
  }
  return obj;
}

/**
 * Listen on the given `event` with `fn`.
 *
 * @param {String} event
 * @param {Function} fn
 * @return {Emitter}
 * @api public
 */

Emitter.prototype.on =
Emitter.prototype.addEventListener = function(event, fn){
  this._callbacks = this._callbacks || {};
  (this._callbacks[event] = this._callbacks[event] || [])
    .push(fn);
  return this;
};

/**
 * Adds an `event` listener that will be invoked a single
 * time then automatically removed.
 *
 * @param {String} event
 * @param {Function} fn
 * @return {Emitter}
 * @api public
 */

Emitter.prototype.once = function(event, fn){
  var self = this;
  this._callbacks = this._callbacks || {};

  function on() {
    self.off(event, on);
    fn.apply(this, arguments);
  }

  on.fn = fn;
  this.on(event, on);
  return this;
};

/**
 * Remove the given callback for `event` or all
 * registered callbacks.
 *
 * @param {String} event
 * @param {Function} fn
 * @return {Emitter}
 * @api public
 */

Emitter.prototype.off =
Emitter.prototype.removeListener =
Emitter.prototype.removeAllListeners =
Emitter.prototype.removeEventListener = function(event, fn){
  this._callbacks = this._callbacks || {};

  // all
  if (0 == arguments.length) {
    this._callbacks = {};
    return this;
  }

  // specific event
  var callbacks = this._callbacks[event];
  if (!callbacks) return this;

  // remove all handlers
  if (1 == arguments.length) {
    delete this._callbacks[event];
    return this;
  }

  // remove specific handler
  var cb;
  for (var i = 0; i < callbacks.length; i++) {
    cb = callbacks[i];
    if (cb === fn || cb.fn === fn) {
      callbacks.splice(i, 1);
      break;
    }
  }
  return this;
};

/**
 * Emit `event` with the given args.
 *
 * @param {String} event
 * @param {Mixed} ...
 * @return {Emitter}
 */

Emitter.prototype.emit = function(event){
  this._callbacks = this._callbacks || {};
  var args = [].slice.call(arguments, 1)
    , callbacks = this._callbacks[event];

  if (callbacks) {
    callbacks = callbacks.slice(0);
    for (var i = 0, len = callbacks.length; i < len; ++i) {
      callbacks[i].apply(this, args);
    }
  }

  return this;
};

/**
 * Return array of callbacks for `event`.
 *
 * @param {String} event
 * @return {Array}
 * @api public
 */

Emitter.prototype.listeners = function(event){
  this._callbacks = this._callbacks || {};
  return this._callbacks[event] || [];
};

/**
 * Check if this emitter has `event` handlers.
 *
 * @param {String} event
 * @return {Boolean}
 * @api public
 */

Emitter.prototype.hasListeners = function(event){
  return !! this.listeners(event).length;
};

}, {}],
26: [function(require, module, exports) {
module.exports = '<div class="overlay hidden"></div>\n';
}, {}],
27: [function(require, module, exports) {

/**
 * Expose `parse`.
 */

module.exports = parse;

/**
 * Wrap map from jquery.
 */

var map = {
  legend: [1, '<fieldset>', '</fieldset>'],
  tr: [2, '<table><tbody>', '</tbody></table>'],
  col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
  _default: [0, '', '']
};

map.td =
map.th = [3, '<table><tbody><tr>', '</tr></tbody></table>'];

map.option =
map.optgroup = [1, '<select multiple="multiple">', '</select>'];

map.thead =
map.tbody =
map.colgroup =
map.caption =
map.tfoot = [1, '<table>', '</table>'];

map.text =
map.circle =
map.ellipse =
map.line =
map.path =
map.polygon =
map.polyline =
map.rect = [1, '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">','</svg>'];

/**
 * Parse `html` and return the children.
 *
 * @param {String} html
 * @return {Array}
 * @api private
 */

function parse(html) {
  if ('string' != typeof html) throw new TypeError('String expected');
  
  // tag name
  var m = /<([\w:]+)/.exec(html);
  if (!m) return document.createTextNode(html);

  html = html.replace(/^\s+|\s+$/g, ''); // Remove leading/trailing whitespace

  var tag = m[1];

  // body support
  if (tag == 'body') {
    var el = document.createElement('html');
    el.innerHTML = html;
    return el.removeChild(el.lastChild);
  }

  // wrap map
  var wrap = map[tag] || map._default;
  var depth = wrap[0];
  var prefix = wrap[1];
  var suffix = wrap[2];
  var el = document.createElement('div');
  el.innerHTML = prefix + html + suffix;
  while (depth--) el = el.lastChild;

  // one element
  if (el.firstChild == el.lastChild) {
    return el.removeChild(el.firstChild);
  }

  // several elements
  var fragment = document.createDocumentFragment();
  while (el.firstChild) {
    fragment.appendChild(el.removeChild(el.firstChild));
  }

  return fragment;
}

}, {}],
28: [function(require, module, exports) {
var bind = window.addEventListener ? 'addEventListener' : 'attachEvent',
    unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent',
    prefix = bind !== 'addEventListener' ? 'on' : '';

/**
 * Bind `el` event `type` to `fn`.
 *
 * @param {Element} el
 * @param {String} type
 * @param {Function} fn
 * @param {Boolean} capture
 * @return {Function}
 * @api public
 */

exports.bind = function(el, type, fn, capture){
  el[bind](prefix + type, fn, capture || false);
  return fn;
};

/**
 * Unbind `el` event `type`'s callback `fn`.
 *
 * @param {Element} el
 * @param {String} type
 * @param {Function} fn
 * @param {Boolean} capture
 * @return {Function}
 * @api public
 */

exports.unbind = function(el, type, fn, capture){
  el[unbind](prefix + type, fn, capture || false);
  return fn;
};
}, {}],
23: [function(require, module, exports) {
/**
 * Module dependencies.
 */

var index = require('indexof');

/**
 * Whitespace regexp.
 */

var re = /\s+/;

/**
 * toString reference.
 */

var toString = Object.prototype.toString;

/**
 * Wrap `el` in a `ClassList`.
 *
 * @param {Element} el
 * @return {ClassList}
 * @api public
 */

module.exports = function(el){
  return new ClassList(el);
};

/**
 * Initialize a new ClassList for `el`.
 *
 * @param {Element} el
 * @api private
 */

function ClassList(el) {
  if (!el) throw new Error('A DOM element reference is required');
  this.el = el;
  this.list = el.classList;
}

/**
 * Add class `name` if not already present.
 *
 * @param {String} name
 * @return {ClassList}
 * @api public
 */

ClassList.prototype.add = function(name){
  // classList
  if (this.list) {
    this.list.add(name);
    return this;
  }

  // fallback
  var arr = this.array();
  var i = index(arr, name);
  if (!~i) arr.push(name);
  this.el.className = arr.join(' ');
  return this;
};

/**
 * Remove class `name` when present, or
 * pass a regular expression to remove
 * any which match.
 *
 * @param {String|RegExp} name
 * @return {ClassList}
 * @api public
 */

ClassList.prototype.remove = function(name){
  if ('[object RegExp]' == toString.call(name)) {
    return this.removeMatching(name);
  }

  // classList
  if (this.list) {
    this.list.remove(name);
    return this;
  }

  // fallback
  var arr = this.array();
  var i = index(arr, name);
  if (~i) arr.splice(i, 1);
  this.el.className = arr.join(' ');
  return this;
};

/**
 * Remove all classes matching `re`.
 *
 * @param {RegExp} re
 * @return {ClassList}
 * @api private
 */

ClassList.prototype.removeMatching = function(re){
  var arr = this.array();
  for (var i = 0; i < arr.length; i++) {
    if (re.test(arr[i])) {
      this.remove(arr[i]);
    }
  }
  return this;
};

/**
 * Toggle class `name`, can force state via `force`.
 *
 * For browsers that support classList, but do not support `force` yet,
 * the mistake will be detected and corrected.
 *
 * @param {String} name
 * @param {Boolean} force
 * @return {ClassList}
 * @api public
 */

ClassList.prototype.toggle = function(name, force){
  // classList
  if (this.list) {
    if ("undefined" !== typeof force) {
      if (force !== this.list.toggle(name, force)) {
        this.list.toggle(name); // toggle again to correct
      }
    } else {
      this.list.toggle(name);
    }
    return this;
  }

  // fallback
  if ("undefined" !== typeof force) {
    if (!force) {
      this.remove(name);
    } else {
      this.add(name);
    }
  } else {
    if (this.has(name)) {
      this.remove(name);
    } else {
      this.add(name);
    }
  }

  return this;
};

/**
 * Return an array of classes.
 *
 * @return {Array}
 * @api public
 */

ClassList.prototype.array = function(){
  var str = this.el.className.replace(/^\s+|\s+$/g, '');
  var arr = str.split(re);
  if ('' === arr[0]) arr.shift();
  return arr;
};

/**
 * Check if class `name` is present.
 *
 * @param {String} name
 * @return {ClassList}
 * @api public
 */

ClassList.prototype.has =
ClassList.prototype.contains = function(name){
  return this.list
    ? this.list.contains(name)
    : !! ~index(this.array(), name);
};

}, {"indexof":14}],
24: [function(require, module, exports) {
module.exports = '<div class="dialog hide">\n  <div class="content">\n    <span class="title">Title</span>\n    <a href="#" class="close">&times;<em>close</em></a>\n    <div class="body">\n      <p>Message</p>\n    </div>\n  </div>\n</div>\n';
}, {}],
20: [function(require, module, exports) {
module.exports = '<div class="confirmation-actions">\n  <button class="cancel">Cancel</button>\n  <button class="ok main">Ok</button>\n</div>';
}, {}],
21: [function(require, module, exports) {

module.exports = function(a, b){
  var fn = function(){};
  fn.prototype = b.prototype;
  a.prototype = new fn;
  a.prototype.constructor = a;
};
}, {}],
12: [function(require, module, exports) {

var focusable = require('focusable-elements');
var ev = require('event');
var normalize = require('normalize');

/**
 * Expose `trapFocus`
 */

module.exports = focusTrap;

/**
 * Trap focus within `container`
 *
 * @api public
 * @param {HTMLElement} container
 * @return {Function} keyboard listener
 */

function focusTrap(container) {
  var elements = focusable(container);
  var first = elements[0];
  var last = elements[elements.length - 1];

  return ev.bind(container, 'keydown', function (keyboardEvent) {
    keyboardEvent = normalize(keyboardEvent);

    if (9 !== keyboardEvent.which) return;

    if (keyboardEvent.shiftKey) {
      if (first === keyboardEvent.target) {
        keyboardEvent.preventDefault();
        last.focus();
      }
    } else {
      if (last === keyboardEvent.target) {
        keyboardEvent.preventDefault();
        first.focus();
      }
    }
  });
}

}, {"focusable-elements":29,"event":15,"normalize":30}],
29: [function(require, module, exports) {

var isFocusable = require('is-focusable');
var descendants = require('descendants');

module.exports = elements;

/**
 * Get all focusable elements within `container`
 *
 * @api public
 * @param {HTMLElement} container
 * @return {Array}
 */

function elements(container) {
  var children = descendants(container);
  var focusable = [];
  for (var i = 0, len = children.length; i < len; i++) {
    if (isFocusable(children[i])) focusable.push(children[i]);
  }
  return focusable;
}

}, {"is-focusable":31,"descendants":32}],
31: [function(require, module, exports) {

var selector = /input|select|textarea|button/i;

module.exports = isFocusable;

/**
 * Check if the given `element` can receive focus
 *
 * @api public
 * @param {HTMLElement} element
 * @return {Boolean}
 */

function isFocusable(element) {
  // tabindex
  if (element.hasAttribute('tabindex')) {
    var tabindex = element.getAttribute('tabindex');
    if (!isNaN(tabindex)) {
      return true;
    }
  }

  // natively focusable, but only when enabled
  var name = element.nodeName;
  if (selector.test(name)) {
    return element.type.toLowerCase() !== 'hidden'
        && !element.disabled;
  }

  // anchors must have an href
  if (name === 'A') {
    return !!element.href;
  }

  return false;
}

}, {}],
32: [function(require, module, exports) {

/**
 * Convert the given `nodes` to an Array, filtering
 * out text and comment nodes
 *
 * @api private
 * @param {NodeList} nodes
 * @return {Array}
 */
function array(nodes) {
  var arr = [];

  for (var i = 0, len = nodes.length; i < len; i++) {
    var type = nodes[i].nodeType;
    type !== 8 && type !== 3 && arr.push(nodes[i]);
  }

  return arr;
}

/**
 * Get an Array of descendants from `element`
 *
 * @api public
 * @param {HTMLElement} element
 * @param {Boolean} [direct]
 * @return {Array}
 */
exports = module.exports = function (element, direct) {
  var decendants = direct
    ? element.childNodes
    : element.getElementsByTagName('*');

  return array(decendants);
};

}, {}],
30: [function(require, module, exports) {

/**
 * Normalize the events provided to `fn`
 *
 * @api public
 * @param {Function|Event} fn
 * @return {Function|Event}
 */

exports = module.exports = function (fn) {
  // handle functions which are passed an event
  if (typeof fn === 'function') {
    return function (event) {
      event = exports.normalize(event);
      fn.call(this, event);
    };
  }

  // just normalize the event
  return exports.normalize(fn);
};

/**
 * Normalize the given `event`
 *
 * @api private
 * @param {Event} event
 * @return {Event}
 */

exports.normalize = function (event) {
  event = event || window.event;

  event.target = event.target || event.srcElement;

  event.which = event.which ||  event.keyCode || event.charCode;

  event.preventDefault = event.preventDefault || function () {
    this.returnValue = false;
  };

  event.stopPropagation = event.stopPropagation || function () {
    this.cancelBubble = true;
  };

  return event;
};

}, {}],
13: [function(require, module, exports) {

/**
 * toString.
 */

var toString = window.JSON
  ? JSON.stringify
  : function(_){ return String(_); };

/**
 * Export `fmt`
 */

module.exports = fmt;

/**
 * Formatters
 */

fmt.o = toString;
fmt.s = String;
fmt.d = parseInt;

/**
 * Format the given `str`.
 *
 * @param {String} str
 * @param {...} args
 * @return {String}
 * @api public
 */

function fmt(str){
  var args = [].slice.call(arguments, 1);
  var j = 0;

  return str.replace(/%([a-z])/gi, function(_, f){
    return fmt[f]
      ? fmt[f](args[j++])
      : _ + f;
  });
}

}, {}],
4: [function(require, module, exports) {

/**
 * Module dependencies.
 */

var Emitter = require('component/emitter');
var events = require('component/events');

/**
 * Expose `Cart`.
 */

module.exports = Cart;

/**
 * Create a new `Cart`.
 *
 * @api public
 */

function Cart() {
  this.el = document.querySelector('#cart');
  this.items = [];
  this.listEl = this.el.querySelector('.list tbody');
  this.totalEl = this.el.querySelector('#cart-total .total');
  this.logEl = this.el.querySelector('#cart-log');
  this.events = events(this.el, this);
}

/**
 * Mixin `Emitter`.
 */

Emitter(Cart.prototype);

/**
 * Update the text content of the cart's total.
 *
 * @return {Cart}
 * @api public
 */

Cart.prototype.updateTotal = function () {
  var total = 0;
  this.items.forEach(function (item) {
    total += parseFloat(item.totalPrice);
  });
  this.totalEl.innerHTML = Number(total).toFixed(2);

  // Update the total in the table caption as well
  var captionTotal = this.el.querySelector('caption .total');
  captionTotal.innerHTML = Number(total).toFixed(2);

  return this;
};

/**
 * Focuses the outermost wrapper of the shopping cart.
 *
 * @return {Cart}
 * @api public
 */

Cart.prototype.focusWrapper = function () {
  var wrapper = this.el.querySelector('.list');
  wrapper.focus();
  return this;
};

/**
 * Assertively announces a given `msg`.
 *
 * @param {String} msg
 * @return {Cart}
 * @api public
 */

Cart.prototype.log = function (msg) {
  msg = [msg, '<span>Cart Total: ' + this.totalEl.innerHTML + ' dollars</span>'].join(' ');
  this.logEl.innerHTML = msg;
  return this;
};

}, {"component/emitter":9,"component/events":8}]}, {}, {"1":""})

CSS Source Code

:focus {
  outline: 2px solid;
}

.clipped {
  clip: rect(1px, 1px, 1px, 1px);
  position: absolute;
  overflow: hidden;
  width: 0;
}

.dollars::before {
  content: '$';
}

#cart .list {
  width: 100%;
  padding: 0;
  list-style: none;
  border-bottom: 1px dotted #ccc;
}

#cart .item {
  padding: 10px 0;
  border-top: 1px dotted #ccc;
}

#cart th, #cart td {
  padding: 0 10px;
}

#cart table > tbody > tr:nth-child(even) {
  background-color: #f6f6f6;
  /* background-color: #f9f9f9; */
}

#cart .thumb {
  width: 100px;
  padding: 10px;
}

#cart .quantity input {
  width: 50px;
  padding: 0 0 0 5px;
}

#cart .quantity input:focus, #cart .quantity input:hover {
  border: 1px #333 solid !important;
  outline: 2px solid #467310 !important;
  outline-offset: 1.5px;
}

#cart .actions {
  padding: 10px;
  width: 250px;
}

#cart .actions ul,
#cart .extras ul {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: .9em;
}

#cart .actions button {
  background-color: inherit;
  border: none;
  text-decoration: underline;
  border: none!important;
}

#cart .actions button:hover,
#cart .actions button:focus {
  color: blue;
}

#cart .extras {
  width: 150px;
}

#cart .extras li {
  display: none;
}

#cart .extras li.active {
  display: block;
}

#cart td.total {
  width: 80px;
}

#cart .summary::after {
  clear: both;
  content: '';
  display: table;
}

#cart-total {
  float: right;
  padding: 20px;
  font-size: 1.2em;
  font-weight: 600;
}

.confirmation {
  box-shadow: 0px 0px 8px #ccc;
  background-color: #fff;
  border: 1px solid #ccc;
  padding: 20px;
  height: 150px;
  width: 400px;
  position: fixed;
  top: 20vh;
  left: calc(50vw - 200px);
}

.confirmation .close {
  display: none;
}

.confirmation-actions {
  position: absolute;
  right: 20px;
  bottom: 20px;
}

Copy and Paste Full Page Example