diff --git a/calc.css b/calc.css index 2c59ba9..c297aa9 100644 --- a/calc.css +++ b/calc.css @@ -461,6 +461,16 @@ tr.nosloop .sloop { .breakdown-first-output td { border-top: 1px solid var(--light); } +/* dropdowns */ +.dropdownWrapper .dropdown .search { + display: none; + width: 100%; + padding-left: 0.4em; + margin-bottom: 0.4em; +} +.dropdownWrapper.open .dropdown .search { + display: block; +} /* about */ .about-content { max-width: 40em; diff --git a/dropdown.js b/dropdown.js index 9754354..ee62cd2 100644 --- a/dropdown.js +++ b/dropdown.js @@ -15,26 +15,35 @@ limitations under the License.*/ let dropdownLocal = d3.local() function toggleDropdown() { - let dropdownNode = dropdownLocal.get(this) + let {dropdownNode, onOpen, onClose} = dropdownLocal.get(this) + let dropdown = d3.select(dropdownNode) let classes = dropdownNode.classList if (classes.contains("open")) { classes.remove("open") + if (onClose) { + onClose(dropdown) + } } else { - let dropdown = d3.select(dropdownNode) let selected = dropdown.select("input:checked + label") dropdown.select(".spacer") .style("width", selected.style("width")) .style("height", selected.style("height")) classes.add("open") + if (onOpen) { + onOpen(dropdown) + } } } // Appends a dropdown to the selection, and returns a selection over the div // for the content of the dropdown. -export function makeDropdown(selector) { +export function makeDropdown(selector, onOpen, onClose) { let dropdown = selector.append("div") .classed("dropdownWrapper", true) - .each(function() { dropdownLocal.set(this, this) }) + .each(function() { + let dropdownNode = this + dropdownLocal.set(this, {dropdownNode, onOpen, onClose}) + }) dropdown.append("div") .classed("clicker", true) .on("click", toggleDropdown) diff --git a/target.js b/target.js index 52d0da5..f5c8826 100644 --- a/target.js +++ b/target.js @@ -49,6 +49,68 @@ function changeRateHandler(target) { } } +function resetSearch(dropdown) { + dropdown.getElementsByClassName("search")[0].value = "" + + // unhide all child nodes + let elems = dropdown.querySelectorAll("label, hr") + for (let elem of elems) { + elem.style.display = "" + } +} + +function searchTargets(event) { + let search = this + let search_text = search.value.toLowerCase().replace(/[^a-z0-9]+/g, "") + let dropdown = d3.select(search.parentNode) + + if (!search_text) { + resetSearch(search.parentNode) + return + } + + // handle enter key press (select target if only one is visible) + if (event.keyCode === 13) { + let labels = dropdown.selectAll("label") + .filter(function() { + return this.style.display !== "none" + }) + // don't do anything if more than one icon is visible + if (labels.size() === 1) { + let input = document.getElementById(labels.attr("for")) + input.checked = true + input.dispatchEvent(new Event("change")) + } + return + } + + // hide non-matching labels & icons + let currentHrHasContent = false + let lastHrWithContent = null + dropdown.selectAll("hr, label").each(function(item) { + if (this.tagName === "HR") { + if (currentHrHasContent) { + this.style.display = "" + lastHrWithContent = this + } else { + this.style.display = "none" + } + currentHrHasContent = false + } else { + let title = item.name.toLowerCase().replace(/-/g, "") + if (title.indexOf(search_text) === -1) { + this.style.display = "none" + } else { + this.style.display = "" + currentHrHasContent = true + } + } + }) + if (!currentHrHasContent && lastHrWithContent !== null) { + lastHrWithContent.style.display = "none" + } +} + let targetCount = 0 let recipeSelectorCount = 0 @@ -73,7 +135,15 @@ export class BuildTarget { .on("click", removeHandler(this)) this.element = element.node() - let dropdown = makeDropdown(element) + let dropdown = makeDropdown( + element, + d => d.select(".search").node().focus(), + d => resetSearch(d.node()), + ) + dropdown.append("input") + .classed("search", true) + .attr("placeholder", "Search") + .on("keyup", searchTargets) let itemSpan = dropdown.selectAll("div") .data(tiers) .join("div")