Switching to a proper emojii picker

This commit is contained in:
Dan 2026-02-21 19:57:47 +00:00
parent 3757454f3a
commit 15d98fe19a
4 changed files with 46 additions and 81 deletions

View file

@ -0,0 +1 @@
Emojii picker element - https://github.com/nolanlawson/emoji-picker-element

View file

@ -57,21 +57,11 @@
role="button"
tabindex="0"
aria-label="Current emoji — click to change"
aria-expanded="false"
>
<span id="emoji-show">💬</span>
</div>
<input
type="text"
id="emoji-input"
class="emoji-picker-input"
placeholder="Type or paste an emoji…"
autocomplete="off"
autocorrect="off"
autocapitalize="none"
spellcheck="false"
aria-label="Emoji input"
/>
<span class="emoji-hint" id="emoji-hint">Click to change emoji</span>
<emoji-picker id="emoji-picker"></emoji-picker>
</div>
<!-- Status text -->
@ -269,6 +259,7 @@
</div>
</div>
<script type="module" src="https://cdn.jsdelivr.net/npm/emoji-picker-element@^1/index.js"></script>
<script src="script.js"></script>
</body>
</html>

View file

@ -6,9 +6,7 @@ let settings = { username: "", apiKey: "", theme: "auto", mastodonPost: false };
const $ = (id) => document.getElementById(id);
const emojiDisplay = $("emoji-display");
const emojiShow = $("emoji-show");
const emojiInput = $("emoji-input");
const emojiSection = emojiDisplay.closest(".emoji-section");
const emojiHint = $("emoji-hint");
const statusText = $("status-text");
const statusUrl = $("status-url");
const charCount = $("char-count");
@ -31,6 +29,11 @@ function applyTheme(theme) {
} else {
document.documentElement.dataset.theme = theme;
}
const picker = document.getElementById("emoji-picker");
if (picker) {
picker.classList.remove("light", "dark");
if (theme !== "auto") picker.classList.add(theme);
}
}
function updateThemeToggle(theme) {
@ -135,65 +138,43 @@ eyeBtn.addEventListener("click", () => {
});
// ── Emoji picker ─────────────────────────────────────────────────────────────
function extractFirstEmoji(str) {
if (!str) return null;
if (typeof Intl?.Segmenter === "function") {
const seg = new Intl.Segmenter(undefined, { granularity: "grapheme" });
for (const { segment } of seg.segment(str)) {
if (/\p{Emoji}/u.test(segment) && segment.trim() !== "") return segment;
}
return null;
}
const m = str.match(/\p{Emoji_Presentation}[\p{Emoji}\u{FE0F}\u{20E3}]*/u);
return m ? m[0] : null;
}
// Detect platform for hint text
const isMac =
/Mac/.test(navigator.userAgent) && !/iPhone|iPad/.test(navigator.userAgent);
const osHint = isMac
? " · or ⌃⌘Space"
: navigator.userAgent.includes("Win")
? " · or Win+."
: "";
const emojiPicker = $("emoji-picker");
function openEmojiPicker() {
emojiSection.classList.add("picking");
emojiInput.value = "";
emojiInput.focus();
emojiHint.textContent = `Type, paste, or use your emoji keyboard${osHint}`;
emojiDisplay.setAttribute("aria-expanded", "true");
}
function closeEmojiPicker() {
emojiSection.classList.remove("picking");
emojiHint.textContent = "Click to change emoji";
emojiDisplay.setAttribute("aria-expanded", "false");
}
emojiDisplay.addEventListener("click", openEmojiPicker);
emojiDisplay.addEventListener("click", () => {
if (emojiSection.classList.contains("picking")) closeEmojiPicker();
else openEmojiPicker();
});
emojiDisplay.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
openEmojiPicker();
if (emojiSection.classList.contains("picking")) closeEmojiPicker();
else openEmojiPicker();
}
});
emojiInput.addEventListener("input", () => {
const found = extractFirstEmoji(emojiInput.value);
if (found) {
emoji = found;
emojiShow.textContent = found;
emojiPicker.addEventListener("emoji-click", (e) => {
emoji = e.detail.unicode;
emojiShow.textContent = emoji;
closeEmojiPicker();
});
document.addEventListener("click", (e) => {
if (emojiSection.classList.contains("picking") && !emojiSection.contains(e.target)) {
closeEmojiPicker();
}
});
emojiInput.addEventListener("blur", () => {
// Small delay so a click on the emoji-display doesn't flicker
setTimeout(() => {
if (document.activeElement !== emojiInput) closeEmojiPicker();
}, 150);
});
// ── Keyboard shortcuts ────────────────────────────────────────────────────────
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {

View file

@ -199,6 +199,7 @@ main {
flex-direction: column;
align-items: center;
gap: 10px;
position: relative;
}
.emoji-display {
@ -229,39 +230,30 @@ main {
background: var(--accent-dim);
}
/* Visible emoji text input — slides in when picking */
.emoji-picker-input {
width: 200px;
text-align: center;
font-size: 1.5rem;
/* collapse when hidden */
max-height: 0;
padding-top: 0;
padding-bottom: 0;
overflow: hidden;
emoji-picker {
opacity: 0;
visibility: hidden;
pointer-events: none;
border-color: transparent;
transition:
max-height 0.2s ease,
padding 0.18s ease,
opacity 0.15s ease,
border-color 0.15s ease;
position: absolute;
top: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
z-index: 20;
width: min(340px, calc(100vw - 40px));
border-radius: var(--r);
--background: var(--surface);
--border-color: var(--border);
--indicator-color: var(--accent);
--input-border-color: var(--border);
--input-font-color: var(--text);
--input-placeholder-color: var(--muted);
--outline-color: var(--accent);
}
.emoji-section.picking .emoji-picker-input {
max-height: 60px;
padding-top: 9px;
padding-bottom: 9px;
.emoji-section.picking emoji-picker {
opacity: 1;
pointer-events: all;
border-color: var(--accent);
}
.emoji-hint {
font-size: 0.8rem;
color: var(--muted);
transition: opacity 0.15s;
visibility: visible;
pointer-events: auto;
}
/* ── Form fields ───────────────────────────────────────────────────────── */