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'); +};