// Book name suggestions for reference/search boxes: type "joh" and get // John, 1 John...; Tab (or tap/click) completes. Only offered while the text // could still be the start of a book name, so keyword searches are unaffected. (function () { var BOOKS = __BOOKS__; var MAX_SHOWN = 8; var list = null; var current = { input: null, matches: [], active: 0, explicit: false }; function norm(text) { return text.toLowerCase().replace(/[^a-z0-9]/g, ""); } function injectStyle() { var style = document.createElement("style"); style.textContent = ".book-suggest{position:absolute;z-index:2000;margin:4px 0 0;padding:4px 0;list-style:none;" + "background:var(--color-bg-secondary,#fff);color:var(--color-text-primary,#111);" + "border:1px solid var(--color-border,#ccc);border-radius:6px;" + "box-shadow:0 6px 18px var(--color-shadow,rgba(0,0,0,.25));font:1rem var(--font-ui,sans-serif)}" + ".book-suggest li{padding:10px 14px;cursor:pointer;min-height:44px;display:flex;align-items:center}" + ".book-suggest li[aria-selected=true]{background:var(--color-accent-primary,#2b5c94);" + "color:var(--color-on-accent,#fff)}" + ".book-suggest .hint{min-height:0;padding:4px 14px;font-size:.8rem;cursor:default;" + "color:var(--color-text-muted,#666);background:none}" + "@media (hover:none){.book-suggest .hint{display:none}}"; document.head.appendChild(style); } function ensureList() { if (list) { return list; } injectStyle(); list = document.createElement("ul"); list.className = "book-suggest"; list.id = "book-suggest-list"; list.setAttribute("role", "listbox"); list.hidden = true; // keep focus in the input while clicking an item list.addEventListener("mousedown", function (event) { event.preventDefault(); }); document.body.appendChild(list); return list; } function close() { if (list) { list.hidden = true; } if (current.input) { current.input.setAttribute("aria-expanded", "false"); current.input.removeAttribute("aria-activedescendant"); } current.matches = []; } function accept(book) { var input = current.input; input.value = book + " "; input.setSelectionRange(input.value.length, input.value.length); close(); input.focus(); } function paint() { var items = list.querySelectorAll("li[role=option]"); for (var i = 0; i < items.length; i++) { var selected = i === current.active; items[i].setAttribute("aria-selected", selected ? "true" : "false"); if (selected) { current.input.setAttribute("aria-activedescendant", items[i].id); } } } function show(input, matches) { var box = input.getBoundingClientRect(); ensureList(); list.textContent = ""; matches.forEach(function (book, i) { var item = document.createElement("li"); item.setAttribute("role", "option"); item.id = "book-suggest-" + i; item.textContent = book; item.addEventListener("click", function () { accept(book); }); list.appendChild(item); }); var hint = document.createElement("li"); hint.className = "hint"; hint.textContent = "Tab to complete"; list.appendChild(hint); list.style.left = box.left + window.pageXOffset + "px"; list.style.top = box.bottom + window.pageYOffset + "px"; list.style.minWidth = Math.min(box.width, 320) + "px"; list.hidden = false; input.setAttribute("aria-expanded", "true"); paint(); } function update(input) { current.input = input; var value = input.value.replace(/^\s+/, ""); // "1 jo", "song of sol": optional leading book number, then letters only var n = /^[1-3]?\s?[A-Za-z ]*$/.test(value) ? norm(value) : ""; var matches = n ? BOOKS.filter(function (b) { return norm(b).indexOf(n) === 0; }) : []; // already typed a whole book name: nothing left to complete if (matches.length === 1 && norm(matches[0]) === n) { matches = []; } matches = matches.slice(0, MAX_SHOWN); current.matches = matches; current.active = 0; current.explicit = false; if (matches.length) { show(input, matches); } else { close(); } } function onKeydown(event) { var open = list && !list.hidden && current.matches.length; if (!open) { return; } var count = current.matches.length; if (event.key === "Tab" && !event.shiftKey) { event.preventDefault(); accept(current.matches[current.active]); } else if (event.key === "ArrowDown" || event.key === "ArrowUp") { event.preventDefault(); current.active = (current.active + (event.key === "ArrowDown" ? 1 : count - 1)) % count; current.explicit = true; paint(); } else if (event.key === "Enter" && current.explicit) { event.preventDefault(); // pick the highlighted book instead of submitting accept(current.matches[current.active]); } else if (event.key === "Escape") { close(); } } function attach(input) { input.setAttribute("autocomplete", "off"); input.setAttribute("aria-autocomplete", "list"); input.setAttribute("aria-controls", "book-suggest-list"); input.addEventListener("input", function () { update(input); }); input.addEventListener("keydown", function (event) { current.input = input; onKeydown(event); }); input.addEventListener("blur", close); } var inputs = document.querySelectorAll("input[data-book-suggest]"); for (var i = 0; i < inputs.length; i++) { attach(inputs[i]); } window.addEventListener("resize", close); })();