Skip to content

Instantly share code, notes, and snippets.

@alpaylan
Created October 1, 2025 00:42
Show Gist options
  • Select an option

  • Save alpaylan/9c3e80a1f7ff83bb860c4dd9e74b84bf to your computer and use it in GitHub Desktop.

Select an option

Save alpaylan/9c3e80a1f7ff83bb860c4dd9e74b84bf to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<meta charset="utf-8">
<title>Autocomplete</title>
<body>
<input id="autocomplete" type="text">
<div id="suggestions" style="border: 1px solid #ccc; display: none;"></div>
</body>
<script>
let suggestions = null;
const unicodeList = fetch("https://raw.githubusercontent.com/muan/emojilib/refs/heads/main/dist/emoji-en-US.json").then(r => r.json()).then(data => {
const list = [];
for (const [key, value] of Object.entries(data)) {
list.push({ emoji: key, keys: value });
}
return list;
});
console.log(unicodeList);
function getTokens(val) {
const tokens = [];
let tokenStart = 0;
for (let i = 0; i < val.length; i++) {
if (val[i] === " ") {
if (i > tokenStart) // avoid empty tokens
{
tokens.push({ text: val.slice(tokenStart, i), start: tokenStart, end: i });
}
tokenStart = i + 1; // skip the space
}
}
// push the last token
tokens.push({ text: val.slice(tokenStart), start: tokenStart, end: val.length });
return tokens;
}
function getCaretToken(tokens, caret) {
for (const token of tokens) {
if (caret >= token.start && caret <= token.end) {
return token;
}
}
return null;
}
function getUnicodeSuggestions(token, caret) {
if (!token) return null;
const tokenCaret = caret - token.start;
if (tokenCaret < 1) return null; // need at least ":x"
if (token.text[0] !== ":") return null; // not a unicode token
const query = token.text.slice(1, tokenCaret).toLowerCase();
if (query.length === 0) return null; // need at least ":x"
// filter the unicode list
const suggestions = unicodeList.then(list => list.filter(u => u.keys.some(k => k.toLowerCase().includes(query)) || u.emoji === query).slice(0, 10));
return suggestions;
}
const textInput = document.getElementById("autocomplete");
textInput.addEventListener("input", (e) => {
const val = e.target.value;
const caret = e.target.selectionStart;
// tokenize val
const tokens = getTokens(val);
// find the token that the caret is in
const token = getCaretToken(tokens, caret);
// check if the token is a unicode token
if (token.text[token.start] !== ":") {
suggestions = null;
}
suggestions = getUnicodeSuggestions(token, caret);
if (suggestions !== null) {
suggestions.then(s => {
renderSuggestions(s);
});
} else {
console.log("no suggestions");
}
})
function renderSuggestions(suggestions) {
const suggestionsDiv = document.getElementById("suggestions");
if (suggestions.length === 0) {
suggestionsDiv.style.display = "none";
suggestionsDiv.innerHTML = "";
return;
}
suggestionsDiv.style.display = "block";
suggestionsDiv.innerHTML = "";
for (const suggestion of suggestions) {
const div = document.createElement("div");
div.textContent = `${suggestion.emoji} - ${suggestion.keys.join(", ")}`;
div.style.padding = "4px";
div.style.cursor = "pointer";
div.addEventListener("click", () => {
insertSuggestion(suggestion);
suggestionsDiv.style.display = "none";
suggestionsDiv.innerHTML = "";
});
suggestionsDiv.appendChild(div);
}
}
function insertSuggestion(suggestion) {
const textInput = document.getElementById("autocomplete");
const val = textInput.value;
const caret = textInput.selectionStart;
const tokens = getTokens(val);
const token = getCaretToken(tokens, caret);
if (!token) return;
const beforeToken = val.slice(0, token.start);
const afterToken = val.slice(token.end);
const newVal = beforeToken + suggestion.emoji + afterToken;
textInput.value = newVal;
const newCaretPos = beforeToken.length + suggestion.emoji.length;
textInput.setSelectionRange(newCaretPos, newCaretPos);
textInput.focus();
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment