mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 01:01:39 -05:00
180 lines
4.4 KiB
JavaScript
180 lines
4.4 KiB
JavaScript
import { Controller } from "@hotwired/stimulus"
|
|
import { Picker } from "emoji-mart"
|
|
|
|
// Emoji Picker Controller
|
|
// Based on RailsBlocks pattern: https://railsblocks.com/docs/emoji-picker
|
|
export default class extends Controller {
|
|
static targets = ["input", "button", "pickerContainer"]
|
|
static values = {
|
|
autoSubmit: { type: Boolean, default: true }
|
|
}
|
|
|
|
connect() {
|
|
this.picker = null
|
|
this.setupKeyboardListeners()
|
|
}
|
|
|
|
disconnect() {
|
|
this.removePicker()
|
|
this.removeKeyboardListeners()
|
|
}
|
|
|
|
toggle(event) {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
|
|
if (this.pickerContainerTarget.classList.contains("hidden")) {
|
|
this.open()
|
|
} else {
|
|
this.close()
|
|
}
|
|
}
|
|
|
|
open() {
|
|
if (!this.picker) {
|
|
this.createPicker()
|
|
}
|
|
|
|
this.pickerContainerTarget.classList.remove("hidden")
|
|
this.setupOutsideClickListener()
|
|
}
|
|
|
|
close() {
|
|
this.pickerContainerTarget.classList.add("hidden")
|
|
this.removeOutsideClickListener()
|
|
}
|
|
|
|
createPicker() {
|
|
this.picker = new Picker({
|
|
onEmojiSelect: this.onEmojiSelect.bind(this),
|
|
theme: this.getTheme(),
|
|
previewPosition: "none",
|
|
skinTonePosition: "search",
|
|
maxFrequentRows: 2,
|
|
perLine: 8,
|
|
navPosition: "bottom",
|
|
categories: [
|
|
"frequent",
|
|
"people",
|
|
"nature",
|
|
"foods",
|
|
"activity",
|
|
"places",
|
|
"objects",
|
|
"symbols",
|
|
"flags"
|
|
]
|
|
})
|
|
|
|
this.pickerContainerTarget.appendChild(this.picker)
|
|
}
|
|
|
|
onEmojiSelect(emoji) {
|
|
if (!emoji || !emoji.native) return
|
|
|
|
// Update input value
|
|
this.inputTarget.value = emoji.native
|
|
|
|
// Update button to show selected emoji
|
|
if (this.hasButtonTarget) {
|
|
// Find the display element (could be a span or the button itself)
|
|
const display = this.buttonTarget.querySelector('[data-emoji-picker-display]') || this.buttonTarget
|
|
display.textContent = emoji.native
|
|
}
|
|
|
|
// Close picker
|
|
this.close()
|
|
|
|
// Auto-submit if enabled
|
|
if (this.autoSubmitValue) {
|
|
this.submitForm()
|
|
}
|
|
|
|
// Dispatch custom event for advanced use cases
|
|
this.dispatch("select", { detail: { emoji: emoji.native } })
|
|
}
|
|
|
|
submitForm() {
|
|
const form = this.element.closest("form")
|
|
if (form && !form.requestSubmit) {
|
|
// Fallback for older browsers
|
|
form.submit()
|
|
} else if (form) {
|
|
form.requestSubmit()
|
|
}
|
|
}
|
|
|
|
clearEmoji(event) {
|
|
event?.preventDefault()
|
|
this.inputTarget.value = ""
|
|
|
|
if (this.hasButtonTarget) {
|
|
const display = this.buttonTarget.querySelector('[data-emoji-picker-display]') || this.buttonTarget
|
|
// Reset to default emoji or icon
|
|
const defaultIcon = this.buttonTarget.dataset.defaultIcon || "😀"
|
|
display.textContent = defaultIcon
|
|
}
|
|
|
|
this.dispatch("clear")
|
|
}
|
|
|
|
getTheme() {
|
|
// Detect dark mode from document
|
|
if (document.documentElement.getAttribute('data-theme') === 'dark' ||
|
|
document.documentElement.classList.contains('dark')) {
|
|
return 'dark'
|
|
}
|
|
return 'light'
|
|
}
|
|
|
|
setupKeyboardListeners() {
|
|
this.handleKeydown = this.handleKeydown.bind(this)
|
|
document.addEventListener("keydown", this.handleKeydown)
|
|
}
|
|
|
|
removeKeyboardListeners() {
|
|
document.removeEventListener("keydown", this.handleKeydown)
|
|
}
|
|
|
|
handleKeydown(event) {
|
|
// Close on Escape
|
|
if (event.key === "Escape" && !this.pickerContainerTarget.classList.contains("hidden")) {
|
|
this.close()
|
|
}
|
|
|
|
// Clear on Delete/Backspace (when picker is open)
|
|
if ((event.key === "Delete" || event.key === "Backspace") &&
|
|
!this.pickerContainerTarget.classList.contains("hidden") &&
|
|
event.target === this.inputTarget) {
|
|
event.preventDefault()
|
|
this.clearEmoji()
|
|
}
|
|
}
|
|
|
|
setupOutsideClickListener() {
|
|
this.handleOutsideClick = this.handleOutsideClick.bind(this)
|
|
// Use setTimeout to avoid immediate triggering from the toggle click
|
|
setTimeout(() => {
|
|
document.addEventListener("click", this.handleOutsideClick)
|
|
}, 0)
|
|
}
|
|
|
|
removeOutsideClickListener() {
|
|
if (this.handleOutsideClick) {
|
|
document.removeEventListener("click", this.handleOutsideClick)
|
|
}
|
|
}
|
|
|
|
handleOutsideClick(event) {
|
|
if (!this.element.contains(event.target)) {
|
|
this.close()
|
|
}
|
|
}
|
|
|
|
removePicker() {
|
|
if (this.picker && this.picker.remove) {
|
|
this.picker.remove()
|
|
}
|
|
this.picker = null
|
|
}
|
|
}
|