@joewinke/jatui 0.1.25 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/dist/actions/collapse.d.ts +80 -0
  2. package/dist/actions/collapse.js +134 -0
  3. package/dist/actions/railNav.d.ts +167 -0
  4. package/dist/actions/railNav.js +351 -0
  5. package/dist/components/AnnotationLayer.svelte +108 -0
  6. package/dist/components/AnnotationLayer.svelte.d.ts +12 -0
  7. package/dist/components/AnnotationPanel.svelte +319 -0
  8. package/dist/components/AnnotationPanel.svelte.d.ts +13 -0
  9. package/dist/components/AudioWaveform.svelte +701 -0
  10. package/dist/components/AudioWaveform.svelte.d.ts +19 -0
  11. package/dist/components/AvailabilityModal.svelte +177 -0
  12. package/dist/components/AvailabilityModal.svelte.d.ts +14 -0
  13. package/dist/components/AvatarUpload.svelte +234 -0
  14. package/dist/components/AvatarUpload.svelte.d.ts +23 -0
  15. package/dist/components/Badge.svelte +38 -0
  16. package/dist/components/Badge.svelte.d.ts +14 -0
  17. package/dist/components/BookingForm.svelte +278 -0
  18. package/dist/components/BookingForm.svelte.d.ts +37 -0
  19. package/dist/components/BurndownChart.svelte +778 -0
  20. package/dist/components/BurndownChart.svelte.d.ts +17 -0
  21. package/dist/components/Button.svelte +81 -0
  22. package/dist/components/Button.svelte.d.ts +21 -0
  23. package/dist/components/CalendarPicker.svelte +284 -0
  24. package/dist/components/CalendarPicker.svelte.d.ts +22 -0
  25. package/dist/components/Card.svelte +67 -0
  26. package/dist/components/Card.svelte.d.ts +17 -0
  27. package/dist/components/CharacterCounter.svelte +82 -0
  28. package/dist/components/CharacterCounter.svelte.d.ts +10 -0
  29. package/dist/components/ChipInput.svelte +609 -0
  30. package/dist/components/ChipInput.svelte.d.ts +55 -0
  31. package/dist/components/Collapse.svelte +91 -0
  32. package/dist/components/Collapse.svelte.d.ts +18 -0
  33. package/dist/components/ColorSelector.svelte +167 -0
  34. package/dist/components/ColorSelector.svelte.d.ts +9 -0
  35. package/dist/components/CommentThread.svelte +773 -0
  36. package/dist/components/CommentThread.svelte.d.ts +32 -0
  37. package/dist/components/ConfirmDialog.svelte +348 -0
  38. package/dist/components/ConfirmDialog.svelte.d.ts +3 -0
  39. package/dist/components/ConfirmModal.svelte +142 -0
  40. package/dist/components/ConfirmModal.svelte.d.ts +15 -0
  41. package/dist/components/ContextMenu.svelte +188 -0
  42. package/dist/components/ContextMenu.svelte.d.ts +31 -0
  43. package/dist/components/CountdownTimer.svelte +94 -0
  44. package/dist/components/CountdownTimer.svelte.d.ts +10 -0
  45. package/dist/components/DateRangePicker.svelte +194 -0
  46. package/dist/components/DateRangePicker.svelte.d.ts +10 -0
  47. package/dist/components/Drawer.svelte +143 -0
  48. package/dist/components/Drawer.svelte.d.ts +33 -0
  49. package/dist/components/EmojiPicker.svelte +159 -0
  50. package/dist/components/EmojiPicker.svelte.d.ts +8 -0
  51. package/dist/components/EntityPreviewCard.svelte +104 -0
  52. package/dist/components/EntityPreviewCard.svelte.d.ts +9 -0
  53. package/dist/components/FileDropzone.svelte +493 -0
  54. package/dist/components/FileDropzone.svelte.d.ts +40 -0
  55. package/dist/components/FilePicker.svelte +488 -0
  56. package/dist/components/FilePicker.svelte.d.ts +20 -0
  57. package/dist/components/FileThumbnail.svelte +80 -0
  58. package/dist/components/FileThumbnail.svelte.d.ts +12 -0
  59. package/dist/components/FilterDropdown.svelte +203 -0
  60. package/dist/components/FilterDropdown.svelte.d.ts +48 -0
  61. package/dist/components/FloatingActionBar.svelte +354 -0
  62. package/dist/components/FloatingActionBar.svelte.d.ts +51 -0
  63. package/dist/components/GPSTracker.svelte +202 -0
  64. package/dist/components/GPSTracker.svelte.d.ts +34 -0
  65. package/dist/components/HunkDiffView.svelte +348 -0
  66. package/dist/components/HunkDiffView.svelte.d.ts +9 -0
  67. package/dist/components/ImageLightbox.svelte +274 -0
  68. package/dist/components/ImageLightbox.svelte.d.ts +25 -0
  69. package/dist/components/ImageUpload.svelte +147 -0
  70. package/dist/components/ImageUpload.svelte.d.ts +10 -0
  71. package/dist/components/InlineEdit.svelte +291 -0
  72. package/dist/components/InlineEdit.svelte.d.ts +45 -0
  73. package/dist/components/InputDialog.svelte +327 -0
  74. package/dist/components/InputDialog.svelte.d.ts +3 -0
  75. package/dist/components/KeyboardShortcutsOverlay.svelte +296 -0
  76. package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +42 -0
  77. package/dist/components/LazyImage.svelte +123 -0
  78. package/dist/components/LazyImage.svelte.d.ts +14 -0
  79. package/dist/components/LinkShortener.svelte +274 -0
  80. package/dist/components/LinkShortener.svelte.d.ts +27 -0
  81. package/dist/components/LoadingSpinner.svelte +106 -0
  82. package/dist/components/LoadingSpinner.svelte.d.ts +21 -0
  83. package/dist/components/LocationMap.svelte +186 -0
  84. package/dist/components/LocationMap.svelte.d.ts +11 -0
  85. package/dist/components/MapView.svelte +341 -0
  86. package/dist/components/MapView.svelte.d.ts +20 -0
  87. package/dist/components/MarkupEditor.svelte +485 -0
  88. package/dist/components/MarkupEditor.svelte.d.ts +10 -0
  89. package/dist/components/MarkupOverlay.svelte +55 -0
  90. package/dist/components/MarkupOverlay.svelte.d.ts +8 -0
  91. package/dist/components/MediaWorkbench.svelte +871 -0
  92. package/dist/components/MediaWorkbench.svelte.d.ts +9 -0
  93. package/dist/components/MilestoneCard.svelte +98 -0
  94. package/dist/components/MilestoneCard.svelte.d.ts +16 -0
  95. package/dist/components/MilestoneTimeline.svelte +91 -0
  96. package/dist/components/MilestoneTimeline.svelte.d.ts +15 -0
  97. package/dist/components/Modal.svelte +244 -0
  98. package/dist/components/Modal.svelte.d.ts +35 -0
  99. package/dist/components/PDFViewer.svelte +105 -0
  100. package/dist/components/PDFViewer.svelte.d.ts +9 -0
  101. package/dist/components/PdfThumbnail.svelte +63 -0
  102. package/dist/components/PdfThumbnail.svelte.d.ts +9 -0
  103. package/dist/components/PhoneInput.svelte +212 -0
  104. package/dist/components/PhoneInput.svelte.d.ts +25 -0
  105. package/dist/components/ResizableDivider.svelte +307 -0
  106. package/dist/components/ResizableDivider.svelte.d.ts +26 -0
  107. package/dist/components/ResizablePanel.svelte +305 -0
  108. package/dist/components/ResizablePanel.svelte.d.ts +18 -0
  109. package/dist/components/SearchDropdown.svelte +421 -0
  110. package/dist/components/SearchDropdown.svelte.d.ts +24 -0
  111. package/dist/components/SelectInput.svelte +237 -0
  112. package/dist/components/SelectInput.svelte.d.ts +26 -0
  113. package/dist/components/SidebarUserFooter.svelte +34 -0
  114. package/dist/components/SidebarUserFooter.svelte.d.ts +16 -0
  115. package/dist/components/SignaturePad.svelte +175 -0
  116. package/dist/components/SignaturePad.svelte.d.ts +17 -0
  117. package/dist/components/SmartImageEditor.svelte +720 -0
  118. package/dist/components/SmartImageEditor.svelte.d.ts +24 -0
  119. package/dist/components/SortDropdown.svelte +156 -0
  120. package/dist/components/SortDropdown.svelte.d.ts +40 -0
  121. package/dist/components/Sparkline.svelte +116 -0
  122. package/dist/components/Sparkline.svelte.d.ts +12 -0
  123. package/dist/components/SpeechForm.svelte +114 -0
  124. package/dist/components/SpeechForm.svelte.d.ts +17 -0
  125. package/dist/components/StatusBadge.svelte +157 -0
  126. package/dist/components/StatusBadge.svelte.d.ts +24 -0
  127. package/dist/components/TaskTypeIcon.svelte +54 -0
  128. package/dist/components/TaskTypeIcon.svelte.d.ts +17 -0
  129. package/dist/components/TextArea.svelte +162 -0
  130. package/dist/components/TextArea.svelte.d.ts +30 -0
  131. package/dist/components/TextInput.svelte +131 -0
  132. package/dist/components/TextInput.svelte.d.ts +29 -0
  133. package/dist/components/ThemeSelector.svelte +206 -0
  134. package/dist/components/ThemeSelector.svelte.d.ts +14 -0
  135. package/dist/components/TimeSlotPicker.svelte +162 -0
  136. package/dist/components/TimeSlotPicker.svelte.d.ts +18 -0
  137. package/dist/components/UserAvatar.svelte +72 -0
  138. package/dist/components/UserAvatar.svelte.d.ts +11 -0
  139. package/dist/components/VariablePicker.svelte +170 -0
  140. package/dist/components/VariablePicker.svelte.d.ts +40 -0
  141. package/dist/components/VoicePlayer.svelte +422 -0
  142. package/dist/components/VoicePlayer.svelte.d.ts +18 -0
  143. package/dist/components/linked-columns/LinkedColumns.svelte +526 -0
  144. package/dist/components/linked-columns/LinkedColumns.svelte.d.ts +130 -0
  145. package/dist/components/markup.d.ts +47 -0
  146. package/dist/components/markup.js +201 -0
  147. package/dist/components/messaging/Avatar.svelte +81 -0
  148. package/dist/components/messaging/Avatar.svelte.d.ts +11 -0
  149. package/dist/components/messaging/ChannelInfoModal.svelte +163 -0
  150. package/dist/components/messaging/ChannelInfoModal.svelte.d.ts +18 -0
  151. package/dist/components/messaging/ChannelList.svelte +107 -0
  152. package/dist/components/messaging/ChannelList.svelte.d.ts +11 -0
  153. package/dist/components/messaging/ChannelMemberAvatarStack.svelte +69 -0
  154. package/dist/components/messaging/ChannelMemberAvatarStack.svelte.d.ts +11 -0
  155. package/dist/components/messaging/ChannelMembersModal.svelte +182 -0
  156. package/dist/components/messaging/ChannelMembersModal.svelte.d.ts +16 -0
  157. package/dist/components/messaging/CreateChannelModal.svelte +190 -0
  158. package/dist/components/messaging/CreateChannelModal.svelte.d.ts +14 -0
  159. package/dist/components/messaging/DirectMessageList.svelte +145 -0
  160. package/dist/components/messaging/DirectMessageList.svelte.d.ts +12 -0
  161. package/dist/components/messaging/EmojiSelector.svelte +261 -0
  162. package/dist/components/messaging/EmojiSelector.svelte.d.ts +19 -0
  163. package/dist/components/messaging/MentionAutocomplete.svelte +193 -0
  164. package/dist/components/messaging/MentionAutocomplete.svelte.d.ts +17 -0
  165. package/dist/components/messaging/MessageAttachment.svelte +273 -0
  166. package/dist/components/messaging/MessageAttachment.svelte.d.ts +13 -0
  167. package/dist/components/messaging/MessageAttachmentUpload.svelte +243 -0
  168. package/dist/components/messaging/MessageAttachmentUpload.svelte.d.ts +19 -0
  169. package/dist/components/messaging/MessageInput.svelte +452 -0
  170. package/dist/components/messaging/MessageInput.svelte.d.ts +28 -0
  171. package/dist/components/messaging/MessageItem.svelte +341 -0
  172. package/dist/components/messaging/MessageItem.svelte.d.ts +41 -0
  173. package/dist/components/messaging/MessageThread.svelte +306 -0
  174. package/dist/components/messaging/MessageThread.svelte.d.ts +30 -0
  175. package/dist/components/messaging/NotificationSettingsModal.svelte +234 -0
  176. package/dist/components/messaging/NotificationSettingsModal.svelte.d.ts +13 -0
  177. package/dist/components/messaging/QuotedMessageDisplay.svelte +123 -0
  178. package/dist/components/messaging/QuotedMessageDisplay.svelte.d.ts +10 -0
  179. package/dist/components/messaging/StartDMModal.svelte +100 -0
  180. package/dist/components/messaging/StartDMModal.svelte.d.ts +14 -0
  181. package/dist/components/messaging/ThreadPanel.svelte +153 -0
  182. package/dist/components/messaging/ThreadPanel.svelte.d.ts +16 -0
  183. package/dist/components/pipeline/DESIGN.md +361 -0
  184. package/dist/components/pipeline/Pipeline.svelte +391 -0
  185. package/dist/components/pipeline/Pipeline.svelte.d.ts +28 -0
  186. package/dist/components/pipeline/PipelineCard.svelte +94 -0
  187. package/dist/components/pipeline/PipelineCard.svelte.d.ts +13 -0
  188. package/dist/components/pipeline/PipelineColumn.svelte +163 -0
  189. package/dist/components/pipeline/PipelineColumn.svelte.d.ts +30 -0
  190. package/dist/components/replay/ChapterTimeline.svelte +326 -0
  191. package/dist/components/replay/ChapterTimeline.svelte.d.ts +39 -0
  192. package/dist/components/session-nav/transcriptModel.d.ts +132 -0
  193. package/dist/components/session-nav/transcriptModel.js +285 -0
  194. package/dist/data/emojis.d.ts +18 -0
  195. package/dist/data/emojis.js +1196 -0
  196. package/dist/index.d.ts +142 -0
  197. package/dist/index.js +142 -0
  198. package/dist/stores/confirmDialog.svelte.d.ts +21 -0
  199. package/dist/stores/confirmDialog.svelte.js +31 -0
  200. package/dist/stores/inputDialog.svelte.d.ts +24 -0
  201. package/dist/stores/inputDialog.svelte.js +32 -0
  202. package/dist/styles/rail.css +63 -0
  203. package/dist/types/annotation.d.ts +32 -0
  204. package/dist/types/annotation.js +3 -0
  205. package/dist/types/booking.d.ts +115 -0
  206. package/dist/types/booking.js +24 -0
  207. package/dist/types/comments.d.ts +65 -0
  208. package/dist/types/comments.js +22 -0
  209. package/dist/types/entityPreview.d.ts +42 -0
  210. package/dist/types/entityPreview.js +10 -0
  211. package/dist/types/filePicker.d.ts +15 -0
  212. package/dist/types/filePicker.js +1 -0
  213. package/dist/types/googleMaps.d.ts +51 -0
  214. package/dist/types/maps.d.ts +43 -0
  215. package/dist/types/maps.js +4 -0
  216. package/dist/types/messaging.d.ts +282 -0
  217. package/dist/types/messaging.js +100 -0
  218. package/dist/types/milestone.d.ts +20 -0
  219. package/dist/types/milestone.js +7 -0
  220. package/dist/types/pipeline.d.ts +115 -0
  221. package/dist/types/pipeline.js +15 -0
  222. package/dist/types/smartImageEditor.d.ts +42 -0
  223. package/dist/types/smartImageEditor.js +1 -0
  224. package/dist/types/templateVars.d.ts +34 -0
  225. package/dist/types/templateVars.js +12 -0
  226. package/dist/utils/currency.d.ts +8 -0
  227. package/dist/utils/currency.js +17 -0
  228. package/dist/utils/daisyuiColors.d.ts +37 -0
  229. package/dist/utils/daisyuiColors.js +152 -0
  230. package/dist/utils/dateFormatters.d.ts +69 -0
  231. package/dist/utils/dateFormatters.js +199 -0
  232. package/dist/utils/googleMapsLoader.d.ts +19 -0
  233. package/dist/utils/googleMapsLoader.js +121 -0
  234. package/dist/utils/mentionParser.d.ts +67 -0
  235. package/dist/utils/mentionParser.js +132 -0
  236. package/dist/utils/phone.d.ts +13 -0
  237. package/dist/utils/phone.js +67 -0
  238. package/dist/utils/phoneFormat.d.ts +26 -0
  239. package/dist/utils/phoneFormat.js +69 -0
  240. package/dist/utils/taskUtils.d.ts +37 -0
  241. package/dist/utils/taskUtils.js +71 -0
  242. package/package.json +3 -2
@@ -0,0 +1,261 @@
1
+ <!--
2
+ EmojiSelector Component
3
+
4
+ Emoji picker with categories, search, skin tones, and custom emoji support.
5
+ Custom emojis and preferences are loaded via optional callbacks.
6
+ -->
7
+
8
+ <script lang="ts">
9
+ import { onMount, onDestroy } from 'svelte'
10
+ import type {
11
+ EmojiSelection,
12
+ EmojiCategory,
13
+ SkinTone,
14
+ StandardEmoji,
15
+ CustomEmoji,
16
+ EmojiPreferences,
17
+ EmojiCallbacks,
18
+ } from '../../types/messaging'
19
+ import { EMOJI_CATEGORIES, SKIN_TONES } from '../../types/messaging'
20
+
21
+ interface Props {
22
+ visible: boolean
23
+ position: { x: number; y: number }
24
+ onselect: (selection: EmojiSelection) => void
25
+ onclose: () => void
26
+ emojis?: StandardEmoji[]
27
+ customEmojis?: CustomEmoji[]
28
+ preferences?: EmojiPreferences
29
+ callbacks?: EmojiCallbacks
30
+ searchPlaceholder?: string
31
+ maxRecent?: number
32
+ }
33
+
34
+ let {
35
+ visible = false,
36
+ position = { x: 0, y: 0 },
37
+ onselect,
38
+ onclose,
39
+ emojis = [],
40
+ customEmojis = [],
41
+ preferences,
42
+ callbacks,
43
+ searchPlaceholder = 'Search emojis...',
44
+ maxRecent = 24,
45
+ }: Props = $props()
46
+
47
+ let searchQuery = $state('')
48
+ let activeCategory: EmojiCategory = $state('smileys_people')
49
+ // svelte-ignore state_referenced_locally
50
+ let selectedSkinTone: SkinTone = $state(preferences?.skinTone || 'default')
51
+ let hoveredEmoji = $state<{ emoji: string; name: string; shortcode: string } | null>(null)
52
+ let loading = $state(false)
53
+ let selectorElement = $state<HTMLElement | null>(null)
54
+ let searchInput = $state<HTMLInputElement | null>(null)
55
+
56
+ // Category icons using emoji characters
57
+ const categoryEmojis: Record<string, string> = {
58
+ search: '🔍',
59
+ frequently_used: '🕐',
60
+ smileys_people: '😀',
61
+ animals_nature: '🐻',
62
+ food_drink: '🍎',
63
+ travel_places: '🏠',
64
+ activities: '⚽',
65
+ objects: '🔨',
66
+ symbols: '❤️',
67
+ flags: '🏳️',
68
+ custom: '🎨',
69
+ }
70
+
71
+ // Filter emojis by category
72
+ function getEmojisByCategory(category: EmojiCategory): StandardEmoji[] {
73
+ return emojis.filter((e) => e.category === category)
74
+ }
75
+
76
+ // Search emojis
77
+ function searchEmojis(query: string): StandardEmoji[] {
78
+ const q = query.toLowerCase()
79
+ return emojis.filter(
80
+ (e) =>
81
+ e.name.toLowerCase().includes(q) ||
82
+ e.shortcode.toLowerCase().includes(q) ||
83
+ e.keywords.some((k) => k.toLowerCase().includes(q)),
84
+ )
85
+ }
86
+
87
+ const displayEmojis = $derived(
88
+ searchQuery ? searchEmojis(searchQuery) : getEmojisByCategory(activeCategory),
89
+ )
90
+
91
+ function handleSelect(emoji: StandardEmoji) {
92
+ const selection: EmojiSelection = {
93
+ type: 'standard',
94
+ unicode: emoji.unicode,
95
+ name: emoji.name,
96
+ shortcode: emoji.shortcode,
97
+ skinTone: selectedSkinTone,
98
+ }
99
+ onselect(selection)
100
+ }
101
+
102
+ function handleCustomSelect(custom: CustomEmoji) {
103
+ const selection: EmojiSelection = {
104
+ type: 'custom',
105
+ customEmojiId: custom.id,
106
+ name: custom.displayName,
107
+ shortcode: custom.name,
108
+ }
109
+ onselect(selection)
110
+ }
111
+
112
+ // Close on click outside
113
+ function handleClickOutside(event: MouseEvent) {
114
+ if (selectorElement && !selectorElement.contains(event.target as Node)) {
115
+ onclose()
116
+ }
117
+ }
118
+
119
+ // Close on escape
120
+ function handleKeydown(event: KeyboardEvent) {
121
+ if (event.key === 'Escape') onclose()
122
+ }
123
+
124
+ // Load custom emojis and preferences from callbacks on mount
125
+ $effect(() => {
126
+ if (visible && callbacks) {
127
+ loading = true
128
+ Promise.all([
129
+ callbacks.loadCustomEmojis?.().catch(() => []),
130
+ callbacks.loadPreferences?.().catch(() => null),
131
+ ]).then(([loadedCustom, loadedPrefs]) => {
132
+ if (loadedCustom?.length) customEmojis = loadedCustom
133
+ if (loadedPrefs) {
134
+ selectedSkinTone = loadedPrefs.skinTone || 'default'
135
+ }
136
+ loading = false
137
+ })
138
+ }
139
+ })
140
+
141
+ $effect(() => {
142
+ if (visible) {
143
+ document.addEventListener('click', handleClickOutside)
144
+ document.addEventListener('keydown', handleKeydown)
145
+ // Focus search on open
146
+ setTimeout(() => searchInput?.focus(), 100)
147
+ return () => {
148
+ document.removeEventListener('click', handleClickOutside)
149
+ document.removeEventListener('keydown', handleKeydown)
150
+ }
151
+ }
152
+ })
153
+
154
+ // Visible categories (skip search and custom if no custom emojis)
155
+ const visibleCategories = $derived(
156
+ EMOJI_CATEGORIES.filter((cat) => {
157
+ if (cat.id === 'search') return false
158
+ if (cat.id === 'custom' && customEmojis.length === 0) return false
159
+ if (cat.id === 'frequently_used') return false // TODO: track frequent
160
+ return true
161
+ }),
162
+ )
163
+ </script>
164
+
165
+ {#if visible}
166
+ <div
167
+ bind:this={selectorElement}
168
+ class="fixed z-50 bg-base-200 border border-base-300 rounded-box w-88 flex flex-col"
169
+ style="left: {position.x}px; top: {position.y}px; max-height: 420px;"
170
+ >
171
+ <!-- Search -->
172
+ <div class="p-2 border-b border-base-300">
173
+ <input
174
+ bind:this={searchInput}
175
+ bind:value={searchQuery}
176
+ type="text"
177
+ placeholder={searchPlaceholder}
178
+ class="input input-sm input-bordered w-full"
179
+ />
180
+ </div>
181
+
182
+ <!-- Category tabs -->
183
+ {#if !searchQuery}
184
+ <div class="flex items-center gap-0.5 px-2 py-1 border-b border-base-300 overflow-x-auto">
185
+ {#each visibleCategories as cat}
186
+ <button
187
+ class="btn btn-ghost btn-xs btn-square {activeCategory === cat.id ? 'bg-primary/10 text-primary' : ''}"
188
+ onclick={() => (activeCategory = cat.id)}
189
+ title={cat.name}
190
+ >
191
+ {categoryEmojis[cat.id] || '❓'}
192
+ </button>
193
+ {/each}
194
+ </div>
195
+ {/if}
196
+
197
+ <!-- Emoji grid -->
198
+ <div class="flex-1 overflow-y-auto p-2 min-h-[200px]">
199
+ {#if loading}
200
+ <div class="flex items-center justify-center h-full">
201
+ <span class="loading loading-spinner loading-sm"></span>
202
+ </div>
203
+ {:else if searchQuery && displayEmojis.length === 0}
204
+ <div class="flex items-center justify-center h-full text-base-content/50 text-sm">
205
+ No emojis found
206
+ </div>
207
+ {:else if activeCategory === 'custom' && !searchQuery}
208
+ <!-- Custom emojis grid -->
209
+ <div class="grid grid-cols-8 gap-1">
210
+ {#each customEmojis as custom}
211
+ <button
212
+ class="w-8 h-8 flex items-center justify-center rounded hover:bg-base-200 transition-colors text-lg"
213
+ onclick={() => handleCustomSelect(custom)}
214
+ onmouseenter={() => (hoveredEmoji = { emoji: '🎨', name: custom.displayName, shortcode: custom.name })}
215
+ onmouseleave={() => (hoveredEmoji = null)}
216
+ title={custom.displayName}
217
+ >
218
+ 🎨
219
+ </button>
220
+ {/each}
221
+ </div>
222
+ {:else}
223
+ <div class="grid grid-cols-8 gap-1">
224
+ {#each displayEmojis as emoji}
225
+ <button
226
+ class="w-8 h-8 flex items-center justify-center rounded hover:bg-base-200 transition-colors text-lg"
227
+ onclick={() => handleSelect(emoji)}
228
+ onmouseenter={() => (hoveredEmoji = { emoji: emoji.unicode, name: emoji.name, shortcode: emoji.shortcode })}
229
+ onmouseleave={() => (hoveredEmoji = null)}
230
+ title="{emoji.name} :{emoji.shortcode}:"
231
+ >
232
+ {emoji.unicode}
233
+ </button>
234
+ {/each}
235
+ </div>
236
+ {/if}
237
+ </div>
238
+
239
+ <!-- Skin tone selector -->
240
+ <div class="flex items-center justify-between px-2 py-1 border-t border-base-300">
241
+ <div class="flex items-center gap-1">
242
+ {#each SKIN_TONES as tone}
243
+ <button
244
+ class="w-6 h-6 flex items-center justify-center rounded text-sm {selectedSkinTone === tone.id ? 'bg-primary/20 ring-1 ring-primary' : 'hover:bg-base-200'}"
245
+ onclick={() => (selectedSkinTone = tone.id)}
246
+ title={tone.name}
247
+ >
248
+ {tone.preview}
249
+ </button>
250
+ {/each}
251
+ </div>
252
+
253
+ <!-- Hovered emoji info -->
254
+ {#if hoveredEmoji}
255
+ <div class="text-xs text-base-content/60 truncate max-w-[150px]">
256
+ {hoveredEmoji.emoji} :{hoveredEmoji.shortcode}:
257
+ </div>
258
+ {/if}
259
+ </div>
260
+ </div>
261
+ {/if}
@@ -0,0 +1,19 @@
1
+ import type { EmojiSelection, StandardEmoji, CustomEmoji, EmojiPreferences, EmojiCallbacks } from '../../types/messaging';
2
+ interface Props {
3
+ visible: boolean;
4
+ position: {
5
+ x: number;
6
+ y: number;
7
+ };
8
+ onselect: (selection: EmojiSelection) => void;
9
+ onclose: () => void;
10
+ emojis?: StandardEmoji[];
11
+ customEmojis?: CustomEmoji[];
12
+ preferences?: EmojiPreferences;
13
+ callbacks?: EmojiCallbacks;
14
+ searchPlaceholder?: string;
15
+ maxRecent?: number;
16
+ }
17
+ declare const EmojiSelector: import("svelte").Component<Props, {}, "">;
18
+ type EmojiSelector = ReturnType<typeof EmojiSelector>;
19
+ export default EmojiSelector;
@@ -0,0 +1,193 @@
1
+ <!--
2
+ MentionAutocomplete Component
3
+
4
+ Shows search results for @-mention autocomplete.
5
+ Receives search results via callback — no direct API calls.
6
+ -->
7
+
8
+ <script lang="ts">
9
+ import { onMount, onDestroy } from 'svelte'
10
+ import type { MentionItem } from '../../types/messaging'
11
+
12
+ interface Props {
13
+ visible: boolean
14
+ query: string
15
+ position: { x: number; y: number }
16
+ onsearch: (query: string) => Promise<MentionItem[]>
17
+ onselect?: (data: { result: MentionItem }) => void
18
+ onclose?: () => void
19
+ }
20
+
21
+ let { visible, query, position, onsearch, onselect, onclose }: Props = $props()
22
+
23
+ let results = $state<MentionItem[]>([])
24
+ let loading = $state(false)
25
+ let selectedIndex = $state(0)
26
+ let dropdownElement = $state<HTMLElement | undefined>(undefined)
27
+ let searchTimeout: ReturnType<typeof setTimeout>
28
+
29
+ // Debounced search
30
+ $effect(() => {
31
+ if (visible && query && query.length >= 2) {
32
+ clearTimeout(searchTimeout)
33
+ searchTimeout = setTimeout(async () => {
34
+ loading = true
35
+ try {
36
+ results = await onsearch(query)
37
+ selectedIndex = 0
38
+ } catch {
39
+ results = []
40
+ } finally {
41
+ loading = false
42
+ }
43
+ }, 300)
44
+ } else if (!visible || query.length < 2) {
45
+ results = []
46
+ }
47
+
48
+ return () => clearTimeout(searchTimeout)
49
+ })
50
+
51
+ function handleKeyDown(event: KeyboardEvent) {
52
+ if (!visible || results.length === 0) return
53
+
54
+ switch (event.key) {
55
+ case 'ArrowDown':
56
+ event.preventDefault()
57
+ selectedIndex = Math.min(selectedIndex + 1, results.length - 1)
58
+ break
59
+ case 'ArrowUp':
60
+ event.preventDefault()
61
+ selectedIndex = Math.max(selectedIndex - 1, 0)
62
+ break
63
+ case 'Enter':
64
+ case 'Tab':
65
+ event.preventDefault()
66
+ if (results[selectedIndex]) {
67
+ onselect?.({ result: results[selectedIndex] })
68
+ }
69
+ break
70
+ case 'Escape':
71
+ event.preventDefault()
72
+ onclose?.()
73
+ break
74
+ }
75
+ }
76
+
77
+ function handleSelect(result: MentionItem) {
78
+ onselect?.({ result })
79
+ }
80
+
81
+ function handleClickOutside(event: MouseEvent) {
82
+ const target = event.target as Node
83
+ if (dropdownElement?.contains(target)) return
84
+ if (
85
+ target instanceof HTMLTextAreaElement ||
86
+ target instanceof HTMLInputElement ||
87
+ (target as Element).closest?.('[contenteditable]')
88
+ ) {
89
+ return
90
+ }
91
+ onclose?.()
92
+ }
93
+
94
+ onMount(() => {
95
+ document.addEventListener('keydown', handleKeyDown)
96
+ })
97
+
98
+ onDestroy(() => {
99
+ document.removeEventListener('keydown', handleKeyDown)
100
+ document.removeEventListener('click', handleClickOutside)
101
+ })
102
+
103
+ $effect(() => {
104
+ if (visible) {
105
+ const timeoutId = setTimeout(() => {
106
+ document.addEventListener('click', handleClickOutside)
107
+ }, 100)
108
+ return () => {
109
+ clearTimeout(timeoutId)
110
+ document.removeEventListener('click', handleClickOutside)
111
+ }
112
+ } else {
113
+ document.removeEventListener('click', handleClickOutside)
114
+ }
115
+ })
116
+
117
+ let dropdownStyle = $state('')
118
+
119
+ $effect(() => {
120
+ if (!visible) {
121
+ dropdownStyle = 'display: none;'
122
+ return
123
+ }
124
+
125
+ const maxHeight = 400
126
+ const itemHeight = 60
127
+ const footerHeight = results.length > 0 ? 40 : 0
128
+ const estimatedHeight = Math.min(
129
+ maxHeight,
130
+ Math.max(120, results.length * itemHeight + footerHeight + 20),
131
+ )
132
+
133
+ let top = position.y - estimatedHeight - 24
134
+ const left = position.x
135
+
136
+ if (top < 10) {
137
+ top = position.y + 20
138
+ }
139
+
140
+ dropdownStyle = `position: fixed; top: ${top}px; left: ${left}px; z-index: 1000; max-height: ${maxHeight}px; width: 320px;`
141
+ })
142
+ </script>
143
+
144
+ {#if visible}
145
+ <div
146
+ bind:this={dropdownElement}
147
+ class="bg-base-200 border border-base-300 rounded-box overflow-y-auto overflow-x-hidden flex flex-col"
148
+ style={dropdownStyle}
149
+ >
150
+ {#if loading}
151
+ <div class="p-4 flex items-center justify-center">
152
+ <span class="loading loading-spinner loading-sm mr-2"></span>
153
+ <span class="text-sm text-base-content/70">Searching...</span>
154
+ </div>
155
+ {:else if results.length === 0}
156
+ <div class="p-4 text-center">
157
+ <span class="text-sm text-base-content/50">
158
+ {query.length < 2 ? 'Type to search...' : `No results found for "${query}"`}
159
+ </span>
160
+ </div>
161
+ {:else}
162
+ {#each results as result, index}
163
+ <button
164
+ type="button"
165
+ class="flex items-center gap-3 p-4 text-left hover:bg-base-200 transition-colors w-full border-b border-base-200 last:border-b-0 min-h-[60px] flex-shrink-0
166
+ {selectedIndex === index ? 'bg-primary/10 text-primary' : 'text-base-content'}"
167
+ onclick={() => handleSelect(result)}
168
+ >
169
+ <div class="flex-1 min-w-0">
170
+ <div class="font-medium text-sm truncate">{result.name}</div>
171
+ {#if result.secondary}
172
+ <div class="text-xs text-base-content/60 truncate">{result.secondary}</div>
173
+ {/if}
174
+ </div>
175
+
176
+ <div class="flex-shrink-0">
177
+ <span class="badge badge-ghost badge-xs">{result.type}</span>
178
+ </div>
179
+ </button>
180
+ {/each}
181
+ {/if}
182
+
183
+ {#if results.length > 0}
184
+ <div class="px-3 py-2 border-t border-base-300 bg-base-50 flex-shrink-0">
185
+ <div class="text-xs text-base-content/50 flex justify-between">
186
+ <span>↑↓ Navigate</span>
187
+ <span>Tab/Enter Select</span>
188
+ <span>Esc Close</span>
189
+ </div>
190
+ </div>
191
+ {/if}
192
+ </div>
193
+ {/if}
@@ -0,0 +1,17 @@
1
+ import type { MentionItem } from '../../types/messaging';
2
+ interface Props {
3
+ visible: boolean;
4
+ query: string;
5
+ position: {
6
+ x: number;
7
+ y: number;
8
+ };
9
+ onsearch: (query: string) => Promise<MentionItem[]>;
10
+ onselect?: (data: {
11
+ result: MentionItem;
12
+ }) => void;
13
+ onclose?: () => void;
14
+ }
15
+ declare const MentionAutocomplete: import("svelte").Component<Props, {}, "">;
16
+ type MentionAutocomplete = ReturnType<typeof MentionAutocomplete>;
17
+ export default MentionAutocomplete;