diff --git a/combobox.html b/combobox.html
new file mode 100644
index 000000000..0d97698b0
--- /dev/null
+++ b/combobox.html
@@ -0,0 +1,170 @@
+
+
+
+
+ iD
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/css/app.css b/css/app.css
index 4c149843d..2be1b819b 100644
--- a/css/app.css
+++ b/css/app.css
@@ -1267,3 +1267,46 @@ a.success-action {
.icon.icon-pre-text { margin-right: 0px;}
.save .label, .apply .label { display: block;}
}
+
+
+
+
+
+div.combobox {
+ width:155px;
+ z-index: 9999;
+ display: none;
+ box-shadow: 0 5px 10px 0 rgba(0,0,0,.2);
+ margin-top: -1px;
+ background: white;
+ max-height: 180px;
+ overflow: auto;
+ border: 1px solid #ccc;
+}
+
+div.combobox a {
+ height: 25px;
+ line-height: 25px;
+ cursor: pointer;
+ display: block;
+ border-top:1px solid #ccc;
+ background-color: #fff;
+ padding:1px 4px;
+ white-space: nowrap;
+}
+
+div.combobox a:hover,
+div.combobox a.selected {
+ background: #e1e8ff;
+ color: #154dff;
+}
+
+div.combobox a:first-child {
+ border-top: 0;
+}
+
+div.combobox-carat {
+ cursor: pointer;
+ padding:0 5px;
+ vertical-align:middle;
+}
diff --git a/js/lib/d3.combobox.js b/js/lib/d3.combobox.js
new file mode 100644
index 000000000..165456563
--- /dev/null
+++ b/js/lib/d3.combobox.js
@@ -0,0 +1,147 @@
+d3.combobox = function() {
+ var event = d3.dispatch('accept'),
+ autohighlight = false,
+ autofilter = false,
+ input,
+ container,
+ data;
+
+ var typeahead = function(selection) {
+ var hidden, idx = autohighlight ? 0 : -1;
+
+ var rect = selection.select('input').node().getBoundingClientRect();
+
+ input = selection.select('input');
+
+ container = selection
+ .insert('div', ':first-child')
+ .attr('class', 'combobox')
+ .style({
+ position: 'absolute',
+ display: 'none',
+ left: '0px',
+ width: rect.width + 'px',
+ top: rect.height + 'px'
+ });
+
+ carat = selection
+ .insert('div', ':first-child')
+ .attr('class', 'combobox-carat')
+ .text('+')
+ .style({
+ position: 'absolute',
+ left: (rect.width - 20) + 'px',
+ top: '0px'
+ })
+ .on('click', function() {
+ update();
+ show();
+ });
+
+ selection
+ .on('keyup.typeahead', key);
+
+ hidden = false;
+
+ function hide() {
+ idx = autohighlight ? 0 : -1;
+ hidden = true;
+ }
+
+ function show() {
+ container.style('display', 'block');
+ }
+
+ function slowHide() {
+ if (autohighlight && container.select('a.selected').node()) {
+ select(container.select('a.selected').datum());
+ event.accept();
+ }
+ window.setTimeout(hide, 150);
+ }
+
+ selection
+ .on('focus.typeahead', show)
+ .on('blur.typeahead', slowHide);
+
+ function key() {
+ var len = container.selectAll('a').data().length;
+ if (d3.event.keyCode === 40) {
+ idx = Math.min(idx + 1, len - 1);
+ return highlight();
+ } else if (d3.event.keyCode === 38) {
+ idx = Math.max(idx - 1, 0);
+ return highlight();
+ } else if (d3.event.keyCode === 13) {
+ if (container.select('a.selected').node()) {
+ select(container.select('a.selected').datum());
+ }
+ event.accept();
+ hide();
+ } else {
+ update();
+ }
+ }
+
+ function highlight() {
+ container
+ .selectAll('a')
+ .classed('selected', function(d, i) { return i == idx; });
+ }
+
+ function update() {
+
+ function run(data) {
+ container.style('display', function() {
+ return data.length ? 'block' : 'none';
+ });
+
+ var options = container
+ .selectAll('a')
+ .data(data, function(d) { return d.value; });
+
+ options.enter()
+ .append('a')
+ .text(function(d) { return d.value; })
+ .attr('title', function(d) { return d.title; })
+ .on('click', select);
+
+ options.exit().remove();
+
+ options
+ .classed('selected', function(d, i) { return i == idx; });
+ }
+
+ if (typeof data === 'function') data(selection, run);
+ else run(data);
+ }
+
+ function select(d) {
+ input
+ .property('value', d.value)
+ .trigger('change');
+ container.style('display', 'none');
+ }
+
+ };
+
+ typeahead.data = function(_) {
+ if (!arguments.length) return data;
+ data = _;
+ return typeahead;
+ };
+
+ typeahead.autofilter = function(_) {
+ if (!arguments.length) return autofilter;
+ autofilter = _;
+ return typeahead;
+ };
+
+ typeahead.autohighlight = function(_) {
+ if (!arguments.length) return autohighlight;
+ autohighlight = _;
+ return typeahead;
+ };
+
+ return d3.rebind(typeahead, event, 'on');
+};