from __future__ import annotations from dataclasses import dataclass from html import escape import gradio as gr @dataclass(frozen=True) class EmojiPaletteItem: emoji: str label: str description: str EMOJI_PALETTE_CSS = """ .emoji-palette { max-width: 100%; } .emoji-palette-grid { display: flex; gap: 3px; flex-wrap: wrap; align-items: flex-start; max-height: 124px; overflow-y: auto; } .emoji-palette-button { flex: 0 0 28px !important; min-width: 28px !important; max-width: 28px !important; } .emoji-palette-button button { width: 28px !important; min-width: 28px !important; height: 28px !important; min-height: 28px !important; border-radius: 4px; font-size: 17px; line-height: 1; padding: 0 !important; } button.emoji-palette-button { width: 28px; min-width: 28px; height: 28px; min-height: 28px; border-radius: 4px; font-size: 17px; line-height: 1; padding: 0; cursor: pointer; } """ EMOJI_PALETTE_ITEMS: tuple[EmojiPaletteItem, ...] = ( EmojiPaletteItem("๐Ÿ‘‚", "ๅ›ใ", "่€ณๅ…ƒใฎ้Ÿณ"), EmojiPaletteItem("๐Ÿ˜ฎโ€๐Ÿ’จ", "ๅๆฏ", "ๆบœๆฏใ€ๅฏๆฏ"), EmojiPaletteItem("โธ๏ธ", "้–“", "ๆฒˆ้ป™"), EmojiPaletteItem("๐Ÿคญ", "็ฌ‘ใ„", "ใใ™ใใ™ใ€ๅซใฟ็ฌ‘ใ„"), EmojiPaletteItem("๐Ÿฅต", "ๅ–˜ใŽ", "ใ†ใ‚ใๅฃฐใ€ๅ”ธใ‚Šๅฃฐ"), EmojiPaletteItem("๐Ÿ“ข", "ใ‚จใ‚ณใƒผ", "ใƒชใƒใƒผใƒ–"), EmojiPaletteItem("๐Ÿ˜", "ใ‹ใ‚‰ใ‹ใ†", "็”˜ใˆใ‚‹ใ‚ˆใ†ใซ"), EmojiPaletteItem("๐Ÿฅบ", "้œ‡ใˆๅฃฐ", "่‡ชไฟกใชใ•ใ’ใซ"), EmojiPaletteItem("๐ŸŒฌ๏ธ", "ๆฏๅˆ‡ใ‚Œ", "่’ใ„ๆฏ้ฃใ„ใ€ๅ‘ผๅธ้Ÿณ"), EmojiPaletteItem("๐Ÿ˜ฎ", "ๆฏใ‚’ใฎใ‚€", "Gasp"), EmojiPaletteItem("๐Ÿ‘…", "่ˆใ‚ใ‚‹้Ÿณ", "ๅ’€ๅšผ้Ÿณใ€ๆฐด้Ÿณ"), EmojiPaletteItem("๐Ÿ’‹", "ใƒชใƒƒใƒ—ใƒŽใ‚คใ‚บ", "Lip smack"), EmojiPaletteItem("๐Ÿซถ", "ๅ„ชใ—ใ", "Tenderly"), EmojiPaletteItem("๐Ÿ˜ญ", "ๆณฃใๅฃฐ", "ๅ—šๅ’ฝใ€ๆ‚ฒใ—ใฟ"), EmojiPaletteItem("๐Ÿ˜ฑ", "ๆ‚ฒ้ณด", "ๅซใณใ€็ตถๅซ"), EmojiPaletteItem("๐Ÿ˜ช", "็œ ใใ†", "ๆฐ—ใ ใ‚‹ใ’ใซ"), EmojiPaletteItem("๐Ÿ˜ด", "ๅฏ่จ€", "ใ„ใณใ"), EmojiPaletteItem("โฉ", "ๆ—ฉๅฃ", "ไธ€ๆฐ—ใซใ€ๆ€ฅใ„ใง"), EmojiPaletteItem("๐Ÿ“ž", "้›ป่ฉฑ่ถŠใ—", "ใ‚นใƒ”ใƒผใ‚ซใƒผ่ถŠใ—"), EmojiPaletteItem("๐Ÿข", "ใ‚†ใฃใใ‚Š", "Slowly"), EmojiPaletteItem("๐Ÿฅค", "้ฃฒใฟ่พผใ‚€", "ๅ”พใ‚’้ฃฒใ‚€้Ÿณ"), EmojiPaletteItem("๐Ÿคง", "ๅ’ณใƒป้ผป", "ๅ’ณใ่พผใฟใ€้ผปใ™ใ™ใ‚Š"), EmojiPaletteItem("๐Ÿ˜’", "่ˆŒๆ‰“ใก", "Tutting"), EmojiPaletteItem("๐Ÿ˜ฐ", "ๆ…Œใฆใ‚‹", "ๅ‹•ๆบใ€็ทŠๅผตใ€ใฉใ‚‚ใ‚Š"), EmojiPaletteItem("๐Ÿ˜†", "ๅ–œใณ", "ๅฌ‰ใ—ใใ†ใซ"), EmojiPaletteItem("๐Ÿ’ฅ", "ๅ‹ขใ„ใ‚ˆใ", "ๅŠ›ๅผทใ„ๅ‹ขใ„"), EmojiPaletteItem("๐Ÿ˜ ", "ๆ€’ใ‚Š", "ไธๆบ€ใ’ใ€ๆ‹—ใญใ‚‹"), EmojiPaletteItem("๐Ÿ˜ฒ", "้ฉšใ", "ๆ„Ÿๅ˜†"), EmojiPaletteItem("๐Ÿฅฑ", "ใ‚ใใณ", "Yawn"), EmojiPaletteItem("๐Ÿ˜–", "่‹ฆใ—ใ’", "Agonizingly"), EmojiPaletteItem("๐Ÿ˜Ÿ", "ๅฟƒ้…", "ไธๅฎ‰ใใ†ใซ"), EmojiPaletteItem("๐Ÿซฃ", "็…งใ‚Œ", "ๆฅใšใ‹ใ—ใใ†ใซ"), EmojiPaletteItem("๐Ÿ™„", "ๅ‘†ใ‚Œ", "Exasperatedly"), EmojiPaletteItem("๐Ÿ˜Š", "ๆฅฝใ—ใ’", "ๅฌ‰ใ—ใใ†ใซ"), EmojiPaletteItem("๐Ÿ˜Ž", "ๅพ—ๆ„ใ’", "่‡ชไฟกใ‚ใ‚Šใ’ใซ"), EmojiPaletteItem("๐Ÿ‘Œ", "็›ธๆงŒ", "้ ทใ้Ÿณ"), EmojiPaletteItem("๐Ÿ™", "ๆ‡‡้ก˜", "ใŠ้ก˜ใ„ใ™ใ‚‹ใ‚ˆใ†ใซ"), EmojiPaletteItem("๐Ÿฅด", "้…”ใ†", "Drunkenly"), EmojiPaletteItem("๐ŸŽต", "้ผปๆญŒ", "Humming"), EmojiPaletteItem("๐Ÿค", "ๅฃใ‚’ๅกžใ", "Muffled"), EmojiPaletteItem("๐Ÿ˜Œ", "ๅฎ‰ๅ ต", "ๆบ€่ถณใ’ใซ"), EmojiPaletteItem("๐Ÿค”", "็–‘ๅ•", "Questioning"), EmojiPaletteItem("๐Ÿ’ช", "ๅŠ›ๅผทใ", "ๅŠ›ใ‚’่พผใ‚ใฆ"), EmojiPaletteItem("๐Ÿ‘ƒ", "ๅ—…ใ้Ÿณ", "ๅŒ‚ใ„ใ‚’ๅ—…ใ้Ÿณ"), EmojiPaletteItem("๐Ÿ“–", "ๆœ—่ชญ", "ใƒŠใƒฌใƒผใ‚ทใƒงใƒณ"), ) _INSERT_EMOJI_ON_POINTER_DOWN = ( "event.preventDefault();" "const root=this.closest('[data-irodori-emoji-palette]');" "const input=root?document.querySelector(root.dataset.target):null;" "if(!input)return;" "const emoji=this.dataset.emoji;" "const text=input.value||'';" "const focused=document.activeElement===input;" "const start=focused&&typeof input.selectionStart==='number'?input.selectionStart:text.length;" "const end=focused&&typeof input.selectionEnd==='number'?input.selectionEnd:text.length;" "const next=text.slice(0,start)+emoji+text.slice(end);" "const caret=start+emoji.length;" "input.value=next;" "input.focus({preventScroll:true});" "input.setSelectionRange(caret,caret);" "input.dispatchEvent(new Event('input',{bubbles:true}));" "input.dispatchEvent(new Event('change',{bubbles:true}));" ) def _textbox_selector(textbox: gr.Textbox) -> str: elem_id = getattr(textbox, "elem_id", None) root_selector = f"#{elem_id}" if elem_id else f"#component-{textbox._id}" return f"{root_selector} textarea, {root_selector} input:not([type='hidden'])" def _emoji_palette_html(textbox: gr.Textbox) -> str: target = escape(_textbox_selector(textbox), quote=True) handler = escape(_INSERT_EMOJI_ON_POINTER_DOWN, quote=True) buttons = [] for item in EMOJI_PALETTE_ITEMS: emoji = escape(item.emoji, quote=True) title = escape(f"{item.label}: {item.description}", quote=True) buttons.append( '" ) return ( '
' f"{''.join(buttons)}
" ) def build_emoji_palette(textbox: gr.Textbox, *, open: bool = True) -> None: with gr.Accordion("Emoji Palette", open=open, elem_classes=["emoji-palette"]): gr.HTML(_emoji_palette_html(textbox))