@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,421 @@
1
+ <script module lang="ts">
2
+ export type SearchDropdownOption = {
3
+ value: string;
4
+ label: string;
5
+ icon?: string;
6
+ image?: string; // URL for avatar/thumbnail image
7
+ };
8
+
9
+ export type SearchDropdownGroup = {
10
+ label: string;
11
+ options: SearchDropdownOption[];
12
+ };
13
+ </script>
14
+
15
+ <script lang="ts">
16
+ import { tick } from 'svelte';
17
+ import { slide } from 'svelte/transition';
18
+
19
+ import type { Snippet } from 'svelte';
20
+
21
+ let {
22
+ value = '',
23
+ groups = [],
24
+ placeholder = 'Filter...',
25
+ disabled = false,
26
+ displayValue,
27
+ colorFn,
28
+ footer,
29
+ onChange,
30
+ }: {
31
+ value: string;
32
+ groups: SearchDropdownGroup[];
33
+ placeholder?: string;
34
+ disabled?: boolean;
35
+ displayValue?: string;
36
+ colorFn?: (value: string) => string | undefined;
37
+ footer?: Snippet;
38
+ onChange: (value: string) => void;
39
+ } = $props();
40
+
41
+ let open = $state(false);
42
+ let searchQuery = $state('');
43
+ let searchInput = $state<HTMLInputElement | undefined>(undefined);
44
+ let containerRef: HTMLDivElement | undefined;
45
+
46
+ // Find the currently selected option across all groups
47
+ const selectedOption = $derived.by(() => {
48
+ for (const group of groups) {
49
+ const found = group.options.find(o => o.value === value);
50
+ if (found) return found;
51
+ }
52
+ return null;
53
+ });
54
+
55
+ const triggerLabel = $derived(displayValue || selectedOption?.label || value || placeholder);
56
+ const triggerIcon = $derived(selectedOption?.icon || '');
57
+ const triggerImage = $derived(selectedOption?.image || '');
58
+ const activeColor = $derived(colorFn ? colorFn(value) : undefined);
59
+
60
+ const filteredGroups = $derived.by(() => {
61
+ if (!searchQuery.trim()) return groups;
62
+ const q = searchQuery.toLowerCase();
63
+ const result: SearchDropdownGroup[] = [];
64
+ for (const group of groups) {
65
+ const filtered = group.options.filter(o =>
66
+ o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q) || (o.icon && o.icon.toLowerCase().includes(q))
67
+ );
68
+ if (filtered.length > 0) result.push({ label: group.label, options: filtered });
69
+ }
70
+ return result;
71
+ });
72
+
73
+ // Generate a deterministic hue from a string for initials avatars
74
+ function nameHue(s: string): number {
75
+ let h = 0;
76
+ for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) & 0xffff;
77
+ return h % 360;
78
+ }
79
+
80
+ function select(optionValue: string) {
81
+ open = false;
82
+ searchQuery = '';
83
+ onChange(optionValue);
84
+ }
85
+
86
+ function handleClickOutside(e: MouseEvent) {
87
+ if (containerRef && !containerRef.contains(e.target as Node)) {
88
+ open = false;
89
+ searchQuery = '';
90
+ }
91
+ }
92
+
93
+ $effect(() => {
94
+ if (open) {
95
+ document.addEventListener('mousedown', handleClickOutside);
96
+ tick().then(() => searchInput?.focus());
97
+ return () => document.removeEventListener('mousedown', handleClickOutside);
98
+ }
99
+ });
100
+ </script>
101
+
102
+ <div class="search-dropdown" bind:this={containerRef}>
103
+ <button
104
+ type="button"
105
+ class="sd-trigger"
106
+ class:sd-disabled={disabled}
107
+ style={activeColor ? `color: ${activeColor};` : ''}
108
+ onclick={() => { if (!disabled) open = !open; }}
109
+ {disabled}
110
+ >
111
+ <span class="sd-trigger-label">
112
+ {#if triggerImage}
113
+ <img src={triggerImage} alt="" class="sd-avatar" />
114
+ {:else if triggerIcon}
115
+ <span class="sd-trigger-icon">{triggerIcon}</span>
116
+ {:else if selectedOption && selectedOption.value}
117
+ <span class="sd-avatar sd-avatar-initials" style="background:oklch(0.55 0.15 {nameHue(selectedOption.label)})">
118
+ {selectedOption.label.charAt(0).toUpperCase()}
119
+ </span>
120
+ {/if}
121
+ <span class="truncate">{triggerLabel}</span>
122
+ </span>
123
+ <svg class="sd-chevron" class:sd-chevron-open={open} fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
124
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
125
+ </svg>
126
+ </button>
127
+
128
+ {#if open}
129
+ <div
130
+ class="sd-panel"
131
+ transition:slide={{ duration: 120 }}
132
+ >
133
+ <!-- Search input -->
134
+ <div class="sd-search">
135
+ <div class="sd-search-inner">
136
+ <svg class="sd-search-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
137
+ <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
138
+ </svg>
139
+ <input
140
+ bind:this={searchInput}
141
+ bind:value={searchQuery}
142
+ onkeydown={(e) => {
143
+ if (e.key === 'Escape') { e.stopPropagation(); open = false; searchQuery = ''; }
144
+ }}
145
+ type="text"
146
+ {placeholder}
147
+ class="sd-search-input"
148
+ autocomplete="off"
149
+ />
150
+ {#if searchQuery}
151
+ <button type="button" onclick={() => { searchQuery = ''; searchInput?.focus(); }} class="sd-search-clear" aria-label="Clear search">
152
+ <svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
153
+ </button>
154
+ {/if}
155
+ </div>
156
+ </div>
157
+
158
+ <!-- Options list -->
159
+ <ul class="sd-list">
160
+ {#if filteredGroups.length > 0}
161
+ {#each filteredGroups as group}
162
+ <li class="sd-group-label">
163
+ <span>{group.label}</span>
164
+ </li>
165
+ {#each group.options as option}
166
+ <li>
167
+ <button
168
+ type="button"
169
+ onclick={() => select(option.value)}
170
+ class="sd-option"
171
+ class:sd-option-selected={value === option.value}
172
+ style={colorFn && colorFn(option.value) ? `color: ${colorFn(option.value)};` : ''}
173
+ >
174
+ {#if option.image}
175
+ <img src={option.image} alt="" class="sd-avatar" />
176
+ {:else if option.icon}
177
+ <span class="sd-option-icon">{option.icon}</span>
178
+ {:else if option.value}
179
+ <span class="sd-avatar sd-avatar-initials" style="background:oklch(0.55 0.15 {nameHue(option.label)})">
180
+ {option.label.charAt(0).toUpperCase()}
181
+ </span>
182
+ {/if}
183
+ <span class="truncate">{option.label}</span>
184
+ {#if value === option.value}
185
+ <svg class="sd-check" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /></svg>
186
+ {/if}
187
+ </button>
188
+ </li>
189
+ {/each}
190
+ {/each}
191
+ {:else}
192
+ <li class="sd-empty">
193
+ <svg class="sd-empty-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
194
+ <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 15.803 7.5 7.5 0 0015.803 15.803z" />
195
+ </svg>
196
+ No matches for "{searchQuery}"
197
+ </li>
198
+ {/if}
199
+ </ul>
200
+ {#if footer}
201
+ <div class="sd-footer">
202
+ {@render footer()}
203
+ </div>
204
+ {/if}
205
+ </div>
206
+ {/if}
207
+ </div>
208
+
209
+ <style>
210
+ .search-dropdown {
211
+ position: relative;
212
+ --sd-bg: var(--sd-color-bg, var(--color-base-100, Canvas));
213
+ --sd-bg-hover: var(--sd-color-bg-hover, var(--color-base-200, color-mix(in oklch, Canvas 90%, CanvasText)));
214
+ --sd-bg-selected: var(--sd-color-bg-selected, var(--color-base-300, color-mix(in oklch, Canvas 80%, CanvasText)));
215
+ --sd-border: var(--sd-color-border, color-mix(in oklch, var(--color-base-content, CanvasText) 20%, transparent));
216
+ --sd-text: var(--sd-color-text, var(--color-base-content, CanvasText));
217
+ --sd-text-muted: var(--sd-color-text-muted, color-mix(in oklch, var(--color-base-content, CanvasText) 50%, transparent));
218
+ --sd-text-label: var(--sd-color-text-label, color-mix(in oklch, var(--color-base-content, CanvasText) 45%, transparent));
219
+ --sd-accent: var(--sd-color-accent, var(--color-primary, LinkText));
220
+ --sd-success: var(--sd-color-success, var(--color-success, green));
221
+ }
222
+
223
+ /* Trigger button */
224
+ .sd-trigger {
225
+ width: 100%;
226
+ padding: 0.25rem 0.5rem;
227
+ border-radius: var(--rounded-btn, 0.5rem);
228
+ font-family: inherit;
229
+ font-size: inherit;
230
+ text-align: left;
231
+ display: flex;
232
+ align-items: center;
233
+ justify-content: space-between;
234
+ gap: 0.375rem;
235
+ transition: background 0.15s, border-color 0.15s;
236
+ min-height: 2rem;
237
+ cursor: pointer;
238
+ background: var(--sd-bg);
239
+ border: 1px solid var(--sd-border);
240
+ color: var(--sd-text);
241
+ }
242
+ .sd-trigger:hover:not(:disabled) {
243
+ background: var(--sd-bg-hover);
244
+ }
245
+ .sd-trigger:disabled, .sd-disabled {
246
+ opacity: 0.5;
247
+ cursor: not-allowed;
248
+ }
249
+
250
+ .sd-trigger-label {
251
+ display: flex;
252
+ align-items: center;
253
+ gap: 0.375rem;
254
+ min-width: 0;
255
+ }
256
+ .sd-trigger-icon {
257
+ flex-shrink: 0;
258
+ font-size: 0.75rem;
259
+ }
260
+ .sd-avatar {
261
+ width: 1.25rem;
262
+ height: 1.25rem;
263
+ border-radius: 50%;
264
+ object-fit: cover;
265
+ flex-shrink: 0;
266
+ }
267
+ .sd-avatar-initials {
268
+ display: inline-flex;
269
+ align-items: center;
270
+ justify-content: center;
271
+ font-size: 0.6rem;
272
+ font-weight: 700;
273
+ color: oklch(1 0 0);
274
+ line-height: 1;
275
+ }
276
+
277
+ .sd-chevron {
278
+ width: 0.75rem;
279
+ height: 0.75rem;
280
+ flex-shrink: 0;
281
+ transition: transform 0.15s;
282
+ color: var(--sd-text-muted);
283
+ }
284
+ .sd-chevron-open {
285
+ transform: rotate(180deg);
286
+ }
287
+
288
+ /* Dropdown panel */
289
+ .sd-panel {
290
+ position: absolute;
291
+ z-index: 50;
292
+ margin-top: 0.25rem;
293
+ width: 100%;
294
+ min-width: 12rem;
295
+ border-radius: var(--rounded-box, 0.5rem);
296
+ overflow: hidden;
297
+ box-shadow: 0 4px 24px oklch(0 0 0 / 0.15);
298
+ background: var(--sd-bg);
299
+ border: 1px solid var(--sd-border);
300
+ }
301
+
302
+ /* Search section */
303
+ .sd-search {
304
+ padding: 0.375rem 0.625rem;
305
+ border-bottom: 1px solid var(--sd-border);
306
+ }
307
+ .sd-search-inner {
308
+ display: flex;
309
+ align-items: center;
310
+ gap: 0.375rem;
311
+ }
312
+ .sd-search-icon {
313
+ width: 0.75rem;
314
+ height: 0.75rem;
315
+ flex-shrink: 0;
316
+ color: var(--sd-text-muted);
317
+ }
318
+ .sd-search-input {
319
+ width: 100%;
320
+ background: transparent;
321
+ font-size: inherit;
322
+ font-family: inherit;
323
+ color: var(--sd-text);
324
+ border: none;
325
+ outline: none;
326
+ }
327
+ .sd-search-input::placeholder {
328
+ color: var(--sd-text-muted);
329
+ }
330
+ .sd-search-clear {
331
+ color: var(--sd-text-muted);
332
+ background: none;
333
+ border: none;
334
+ cursor: pointer;
335
+ padding: 0;
336
+ display: flex;
337
+ }
338
+ .sd-search-clear:hover {
339
+ opacity: 0.8;
340
+ }
341
+
342
+ /* Options list */
343
+ .sd-list {
344
+ padding: 0.125rem 0;
345
+ max-height: 280px;
346
+ overflow-y: auto;
347
+ list-style: none;
348
+ margin: 0;
349
+ }
350
+
351
+ .sd-group-label {
352
+ padding: 0.375rem 0.75rem 0.125rem;
353
+ }
354
+ .sd-group-label span {
355
+ font-size: 0.8125rem;
356
+ font-family: inherit;
357
+ font-weight: 400;
358
+ letter-spacing: 0.005em;
359
+ color: var(--sd-text-label);
360
+ }
361
+
362
+ .sd-option {
363
+ width: 100%;
364
+ padding: 0.375rem 0.75rem;
365
+ display: flex;
366
+ align-items: center;
367
+ gap: 0.5rem;
368
+ text-align: left;
369
+ font-size: inherit;
370
+ font-family: inherit;
371
+ transition: background 0.1s;
372
+ cursor: pointer;
373
+ background: transparent;
374
+ border: none;
375
+ color: var(--sd-text);
376
+ }
377
+ .sd-option:hover {
378
+ background: var(--sd-bg-hover);
379
+ }
380
+ .sd-option-selected {
381
+ background: var(--sd-bg-selected);
382
+ font-weight: 600;
383
+ }
384
+
385
+ .sd-option-icon {
386
+ flex-shrink: 0;
387
+ width: 1.25rem;
388
+ text-align: center;
389
+ font-size: 0.75rem;
390
+ }
391
+
392
+ .sd-check {
393
+ width: 0.75rem;
394
+ height: 0.75rem;
395
+ flex-shrink: 0;
396
+ margin-left: auto;
397
+ color: var(--sd-success);
398
+ }
399
+
400
+ .sd-footer {
401
+ border-top: 1px solid var(--sd-border);
402
+ }
403
+
404
+ .sd-empty {
405
+ padding: 1rem 0.75rem;
406
+ text-align: center;
407
+ font-size: inherit;
408
+ font-family: inherit;
409
+ color: var(--sd-text-muted);
410
+ display: flex;
411
+ flex-direction: column;
412
+ align-items: center;
413
+ gap: 0.375rem;
414
+ }
415
+
416
+ .sd-empty-icon {
417
+ width: 1.25rem;
418
+ height: 1.25rem;
419
+ opacity: 0.35;
420
+ }
421
+ </style>
@@ -0,0 +1,24 @@
1
+ export type SearchDropdownOption = {
2
+ value: string;
3
+ label: string;
4
+ icon?: string;
5
+ image?: string;
6
+ };
7
+ export type SearchDropdownGroup = {
8
+ label: string;
9
+ options: SearchDropdownOption[];
10
+ };
11
+ import type { Snippet } from 'svelte';
12
+ type $$ComponentProps = {
13
+ value: string;
14
+ groups: SearchDropdownGroup[];
15
+ placeholder?: string;
16
+ disabled?: boolean;
17
+ displayValue?: string;
18
+ colorFn?: (value: string) => string | undefined;
19
+ footer?: Snippet;
20
+ onChange: (value: string) => void;
21
+ };
22
+ declare const SearchDropdown: import("svelte").Component<$$ComponentProps, {}, "">;
23
+ type SearchDropdown = ReturnType<typeof SearchDropdown>;
24
+ export default SearchDropdown;
@@ -0,0 +1,237 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SelectInput Component
4
+ *
5
+ * Dropdown select with searchable mode, grouped options, and validation.
6
+ * Converted from Svelte 4 to Svelte 5 runes.
7
+ */
8
+
9
+ import { onMount } from 'svelte';
10
+
11
+ export interface SelectOption {
12
+ value: string | number;
13
+ label: string;
14
+ disabled?: boolean;
15
+ group?: string;
16
+ }
17
+
18
+ interface Props {
19
+ label: string;
20
+ value?: string | number;
21
+ options?: SelectOption[];
22
+ placeholder?: string;
23
+ required?: boolean;
24
+ disabled?: boolean;
25
+ searchable?: boolean;
26
+ error?: string;
27
+ helpText?: string;
28
+ size?: 'sm' | 'md' | 'lg';
29
+ class?: string;
30
+ id?: string;
31
+ name?: string;
32
+ clearSearchOnSelect?: boolean;
33
+ onchange?: (value: string | number, option: SelectOption | undefined) => void;
34
+ }
35
+
36
+ let {
37
+ label,
38
+ value = $bindable(''),
39
+ options = [],
40
+ placeholder = 'Select an option',
41
+ required = false,
42
+ disabled = false,
43
+ searchable = false,
44
+ error = '',
45
+ helpText = '',
46
+ size = 'md',
47
+ class: className = '',
48
+ id = `select-input-${Math.random().toString(36).substr(2, 9)}`,
49
+ name = '',
50
+ clearSearchOnSelect = true,
51
+ onchange
52
+ }: Props = $props();
53
+
54
+ let searchTerm = $state('');
55
+ let isOpen = $state(false);
56
+ let selectElement: HTMLElement | undefined = $state();
57
+
58
+ const sizeClass = $derived({ sm: 'select-sm', md: '', lg: 'select-lg' }[size]);
59
+
60
+ const selectClass = $derived(
61
+ ['select select-bordered w-full', sizeClass, error ? 'select-error' : '', disabled ? 'select-disabled' : '', className]
62
+ .filter(Boolean)
63
+ .join(' ')
64
+ );
65
+
66
+ const filteredOptions = $derived(
67
+ searchable && searchTerm
68
+ ? options.filter((o) => o.label.toLowerCase().includes(searchTerm.toLowerCase()))
69
+ : options
70
+ );
71
+
72
+ const selectedOption = $derived(options.find((o) => o.value === value));
73
+
74
+ const groupedOptions = $derived(
75
+ filteredOptions.reduce(
76
+ (acc, option) => {
77
+ const group = option.group || 'default';
78
+ if (!acc[group]) acc[group] = [];
79
+ acc[group].push(option);
80
+ return acc;
81
+ },
82
+ {} as Record<string, SelectOption[]>
83
+ )
84
+ );
85
+
86
+ function handleSelect(option: SelectOption) {
87
+ if (option.disabled) return;
88
+ value = option.value;
89
+ onchange?.(value, option);
90
+ if (clearSearchOnSelect) searchTerm = '';
91
+ isOpen = false;
92
+ }
93
+
94
+ function handleKeydown(event: KeyboardEvent) {
95
+ if (event.key === 'Escape') {
96
+ isOpen = false;
97
+ searchTerm = '';
98
+ }
99
+ }
100
+
101
+ function handleClickOutside(event: Event) {
102
+ if (selectElement && !selectElement.contains(event.target as Node)) {
103
+ isOpen = false;
104
+ searchTerm = '';
105
+ }
106
+ }
107
+
108
+ function handleNativeChange(e: Event) {
109
+ const target = e.currentTarget as HTMLSelectElement;
110
+ const option = options.find((opt) => opt.value.toString() === target.value);
111
+ onchange?.(value!, option);
112
+ }
113
+
114
+ onMount(() => {
115
+ document.addEventListener('click', handleClickOutside);
116
+ return () => document.removeEventListener('click', handleClickOutside);
117
+ });
118
+
119
+ // Error-state shake (transitions.dev). Shakes the whole control wrapper when
120
+ // `error` becomes set. See TextInput for the rationale.
121
+ let hadError = false;
122
+ $effect(() => {
123
+ const hasError = !!error;
124
+ if (hasError && !hadError && selectElement) {
125
+ const reduce =
126
+ typeof window !== 'undefined' &&
127
+ window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
128
+ if (!reduce) {
129
+ selectElement.classList.remove('t-input-shake');
130
+ void selectElement.offsetWidth;
131
+ selectElement.classList.add('t-input-shake');
132
+ }
133
+ }
134
+ hadError = hasError;
135
+ });
136
+ </script>
137
+
138
+ <div
139
+ class="form-control w-full"
140
+ bind:this={selectElement}
141
+ onanimationend={() => selectElement?.classList.remove('t-input-shake')}
142
+ >
143
+ <label for={id} class="label">
144
+ <span class="text-[0.8125rem] font-medium text-base-content/85">
145
+ {label}
146
+ {#if required}
147
+ <span class="text-error ml-1">*</span>
148
+ {/if}
149
+ </span>
150
+ </label>
151
+
152
+ {#if searchable}
153
+ <div class="dropdown w-full {isOpen ? 'dropdown-open' : ''}">
154
+ <div tabindex="0" role="button" class="btn btn-outline w-full justify-between {sizeClass}">
155
+ <input
156
+ {id}
157
+ {name}
158
+ type="text"
159
+ placeholder={selectedOption?.label || placeholder}
160
+ bind:value={searchTerm}
161
+ onfocus={() => (isOpen = true)}
162
+ onkeydown={handleKeydown}
163
+ class="input input-ghost w-full p-0 h-auto min-h-0 focus:outline-none"
164
+ {disabled}
165
+ {required}
166
+ aria-describedby={error ? `${id}-error` : helpText ? `${id}-help` : undefined}
167
+ aria-invalid={error ? 'true' : 'false'}
168
+ />
169
+ <svg class="w-4 h-4 opacity-70" fill="none" stroke="currentColor" viewBox="0 0 24 24">
170
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
171
+ </svg>
172
+ </div>
173
+
174
+ {#if isOpen}
175
+ <div class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-full max-h-60 overflow-auto">
176
+ {#each Object.entries(groupedOptions) as [groupName, groupOpts]}
177
+ {#if groupName !== 'default'}
178
+ <div class="menu-title"><span>{groupName}</span></div>
179
+ {/if}
180
+ {#each groupOpts as option}
181
+ <li>
182
+ <button
183
+ type="button"
184
+ class="text-left {option.disabled ? 'disabled' : ''}"
185
+ class:active={option.value === value}
186
+ onclick={() => handleSelect(option)}
187
+ disabled={option.disabled}
188
+ >
189
+ {option.label}
190
+ </button>
191
+ </li>
192
+ {/each}
193
+ {/each}
194
+ </div>
195
+ {/if}
196
+ </div>
197
+ {:else}
198
+ <select
199
+ {id}
200
+ {name}
201
+ {required}
202
+ {disabled}
203
+ bind:value
204
+ class={selectClass}
205
+ aria-describedby={error ? `${id}-error` : helpText ? `${id}-help` : undefined}
206
+ aria-invalid={error ? 'true' : 'false'}
207
+ onchange={handleNativeChange}
208
+ >
209
+ {#if placeholder}
210
+ <option value="" disabled>{placeholder}</option>
211
+ {/if}
212
+ {#each Object.entries(groupedOptions) as [groupName, groupOpts]}
213
+ {#if groupName !== 'default'}
214
+ <optgroup label={groupName}>
215
+ {#each groupOpts as option}
216
+ <option value={option.value} disabled={option.disabled}>{option.label}</option>
217
+ {/each}
218
+ </optgroup>
219
+ {:else}
220
+ {#each groupOpts as option}
221
+ <option value={option.value} disabled={option.disabled}>{option.label}</option>
222
+ {/each}
223
+ {/if}
224
+ {/each}
225
+ </select>
226
+ {/if}
227
+
228
+ {#if error}
229
+ <div class="label">
230
+ <span id="{id}-error" class="text-[0.8125rem] text-error" role="alert">{error}</span>
231
+ </div>
232
+ {:else if helpText}
233
+ <div class="label">
234
+ <span id="{id}-help" class="text-[0.8125rem] text-base-content/45">{helpText}</span>
235
+ </div>
236
+ {/if}
237
+ </div>
@@ -0,0 +1,26 @@
1
+ export interface SelectOption {
2
+ value: string | number;
3
+ label: string;
4
+ disabled?: boolean;
5
+ group?: string;
6
+ }
7
+ interface Props {
8
+ label: string;
9
+ value?: string | number;
10
+ options?: SelectOption[];
11
+ placeholder?: string;
12
+ required?: boolean;
13
+ disabled?: boolean;
14
+ searchable?: boolean;
15
+ error?: string;
16
+ helpText?: string;
17
+ size?: 'sm' | 'md' | 'lg';
18
+ class?: string;
19
+ id?: string;
20
+ name?: string;
21
+ clearSearchOnSelect?: boolean;
22
+ onchange?: (value: string | number, option: SelectOption | undefined) => void;
23
+ }
24
+ declare const SelectInput: import("svelte").Component<Props, {}, "value">;
25
+ type SelectInput = ReturnType<typeof SelectInput>;
26
+ export default SelectInput;