@joewinke/jatui 0.1.24 → 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,296 @@
1
+ <script lang="ts">
2
+ /**
3
+ * KeyboardShortcutsOverlay — press `?` to toggle a modal listing the
4
+ * keyboard shortcuts available on the current page. Press `?` again or
5
+ * `Escape` to dismiss. Input/contenteditable fields and Ctrl/Meta/Alt
6
+ * combos are ignored so the shortcut never interferes with typing.
7
+ *
8
+ * Usage:
9
+ *
10
+ * <KeyboardShortcutsOverlay shortcuts={[
11
+ * { key: 'j / ↓', description: 'Focus next item' },
12
+ * { key: 'k / ↑', description: 'Focus previous item' },
13
+ * { key: 'Enter', description: 'Open detail' },
14
+ * ]} />
15
+ */
16
+
17
+ export interface KeyboardShortcut {
18
+ key: string;
19
+ description: string;
20
+ }
21
+
22
+ export interface SectionEntry {
23
+ key: string;
24
+ description: string;
25
+ /** Optional oklch color to tint the key badge — used for legend entries */
26
+ color?: string;
27
+ }
28
+
29
+ export interface ShortcutSection {
30
+ title: string;
31
+ shortcuts: SectionEntry[];
32
+ }
33
+
34
+ let {
35
+ shortcuts = [],
36
+ sections,
37
+ title = 'Keyboard Shortcuts',
38
+ open = $bindable(false)
39
+ }: {
40
+ shortcuts?: KeyboardShortcut[];
41
+ /**
42
+ * Optional grouped sections (e.g. mode-specific shortcut tables for
43
+ * /inbox: list / detail / compose). When provided, takes
44
+ * precedence over the flat `shortcuts` prop.
45
+ */
46
+ sections?: ShortcutSection[];
47
+ title?: string;
48
+ open?: boolean;
49
+ } = $props();
50
+
51
+ let isOpen = $state(open);
52
+
53
+ $effect(() => {
54
+ isOpen = open;
55
+ });
56
+ $effect(() => {
57
+ open = isOpen;
58
+ });
59
+
60
+ function isTypingTarget(target: EventTarget | null): boolean {
61
+ if (!(target instanceof HTMLElement)) return false;
62
+ const tag = target.tagName;
63
+ if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true;
64
+ return target.isContentEditable;
65
+ }
66
+
67
+ function onKeydown(e: KeyboardEvent) {
68
+ if (e.ctrlKey || e.metaKey || e.altKey) return;
69
+
70
+ if (e.key === '?') {
71
+ // `?` is always Shift+/. Don't trigger while typing — would swallow the char.
72
+ if (isTypingTarget(e.target)) return;
73
+ e.preventDefault();
74
+ isOpen = !isOpen;
75
+ return;
76
+ }
77
+
78
+ if (isOpen && e.key === 'Escape') {
79
+ e.preventDefault();
80
+ isOpen = false;
81
+ }
82
+ }
83
+
84
+ $effect(() => {
85
+ window.addEventListener('keydown', onKeydown);
86
+ return () => window.removeEventListener('keydown', onKeydown);
87
+ });
88
+ </script>
89
+
90
+ {#if isOpen}
91
+ <div
92
+ class="kso-backdrop"
93
+ role="presentation"
94
+ onclick={() => (isOpen = false)}
95
+ >
96
+ <div
97
+ class="kso-panel animate-scale-in"
98
+ role="dialog"
99
+ aria-modal="true"
100
+ aria-label={title}
101
+ onclick={(e) => e.stopPropagation()}
102
+ onkeydown={(e) => e.stopPropagation()}
103
+ tabindex="-1"
104
+ >
105
+ <div class="kso-header">
106
+ <h2 class="kso-title">{title}</h2>
107
+ <button
108
+ type="button"
109
+ class="kso-close"
110
+ onclick={() => (isOpen = false)}
111
+ aria-label="Close keyboard shortcuts"
112
+ >
113
+ ×
114
+ </button>
115
+ </div>
116
+ <div class="kso-list">
117
+ {#if sections && sections.length > 0}
118
+ {#each sections as section, sIdx (section.title + '::' + sIdx)}
119
+ <div class="kso-section-title">{section.title}</div>
120
+ {#each section.shortcuts as { key, description, color }, i (section.title + '::' + key + '::' + i)}
121
+ <div class="kso-row">
122
+ <kbd
123
+ class="kso-key"
124
+ style={color
125
+ ? `color:${color};border-color:color-mix(in oklch,${color} 50%,oklch(0.35 0.02 250));background:color-mix(in oklch,${color} 12%,oklch(0.25 0.02 250))`
126
+ : ''}
127
+ >
128
+ {key}
129
+ </kbd>
130
+ <span class="kso-description">{description}</span>
131
+ </div>
132
+ {/each}
133
+ {/each}
134
+ {:else}
135
+ {#each shortcuts as { key, description }, i (key + '::' + i)}
136
+ <div class="kso-row">
137
+ <kbd class="kso-key">{key}</kbd>
138
+ <span class="kso-description">{description}</span>
139
+ </div>
140
+ {/each}
141
+ {/if}
142
+ <div class="kso-row kso-row-meta">
143
+ <kbd class="kso-key">?</kbd>
144
+ <span class="kso-description">Toggle this overlay</span>
145
+ </div>
146
+ <div class="kso-row kso-row-meta">
147
+ <kbd class="kso-key">Esc</kbd>
148
+ <span class="kso-description">Close overlay</span>
149
+ </div>
150
+ </div>
151
+ </div>
152
+ </div>
153
+ {/if}
154
+
155
+ <style>
156
+ .kso-backdrop {
157
+ position: fixed;
158
+ inset: 0;
159
+ background: oklch(0 0 0 / 0.6);
160
+ backdrop-filter: blur(2px);
161
+ z-index: 60;
162
+ display: flex;
163
+ align-items: center;
164
+ justify-content: center;
165
+ padding: 1.5rem;
166
+ animation: kso-fade 0.15s ease-out;
167
+ }
168
+
169
+ .kso-panel {
170
+ background: oklch(0.18 0.02 250);
171
+ border: 1px solid oklch(0.30 0.03 250);
172
+ border-radius: 0.75rem;
173
+ box-shadow: 0 20px 60px oklch(0 0 0 / 0.6);
174
+ max-width: 34rem;
175
+ width: 100%;
176
+ max-height: 80vh;
177
+ overflow: hidden;
178
+ display: flex;
179
+ flex-direction: column;
180
+ }
181
+
182
+ .kso-header {
183
+ display: flex;
184
+ align-items: center;
185
+ justify-content: space-between;
186
+ padding: 0.9rem 1.25rem;
187
+ border-bottom: 1px solid oklch(0.25 0.02 250);
188
+ flex-shrink: 0;
189
+ }
190
+
191
+ .kso-title {
192
+ font-size: 0.95rem;
193
+ font-weight: 600;
194
+ color: oklch(0.92 0.02 250);
195
+ margin: 0;
196
+ letter-spacing: 0.01em;
197
+ }
198
+
199
+ .kso-close {
200
+ background: transparent;
201
+ border: none;
202
+ color: oklch(0.65 0.02 250);
203
+ font-size: 1.5rem;
204
+ line-height: 1;
205
+ cursor: pointer;
206
+ padding: 0.15rem 0.5rem;
207
+ border-radius: 0.35rem;
208
+ transition:
209
+ background 0.12s ease,
210
+ color 0.12s ease;
211
+ }
212
+
213
+ .kso-close:hover {
214
+ color: oklch(0.94 0.02 250);
215
+ background: oklch(0.25 0.02 250);
216
+ }
217
+
218
+ .kso-list {
219
+ padding: 0.75rem 1.25rem 1.25rem;
220
+ display: flex;
221
+ flex-direction: column;
222
+ gap: 0.3rem;
223
+ overflow-y: auto;
224
+ }
225
+
226
+ .kso-row {
227
+ display: flex;
228
+ align-items: center;
229
+ gap: 1rem;
230
+ padding: 0.4rem 0.25rem;
231
+ }
232
+
233
+ .kso-section-title {
234
+ font-size: 0.7rem;
235
+ text-transform: uppercase;
236
+ letter-spacing: 0.08em;
237
+ color: oklch(0.62 0.05 240);
238
+ padding: 0.55rem 0.25rem 0.2rem;
239
+ border-bottom: 1px solid oklch(0.25 0.02 250);
240
+ margin-top: 0.25rem;
241
+ }
242
+
243
+ .kso-section-title:first-of-type {
244
+ margin-top: 0;
245
+ padding-top: 0.1rem;
246
+ }
247
+
248
+ .kso-row-meta {
249
+ opacity: 0.7;
250
+ border-top: 1px dashed oklch(0.25 0.02 250);
251
+ margin-top: 0.25rem;
252
+ padding-top: 0.5rem;
253
+ }
254
+
255
+ .kso-row-meta:not(:first-of-type) {
256
+ border-top: none;
257
+ margin-top: 0;
258
+ }
259
+
260
+ .kso-key {
261
+ font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
262
+ font-size: 0.78rem;
263
+ background: oklch(0.25 0.02 250);
264
+ border: 1px solid oklch(0.35 0.02 250);
265
+ border-bottom-width: 2px;
266
+ border-radius: 0.35rem;
267
+ padding: 0.18rem 0.55rem;
268
+ min-width: 3.25rem;
269
+ text-align: center;
270
+ color: oklch(0.93 0.02 250);
271
+ flex-shrink: 0;
272
+ white-space: nowrap;
273
+ }
274
+
275
+ .kso-description {
276
+ font-size: 0.85rem;
277
+ color: oklch(0.78 0.02 250);
278
+ line-height: 1.35;
279
+ }
280
+
281
+ @keyframes kso-fade {
282
+ from {
283
+ opacity: 0;
284
+ }
285
+ to {
286
+ opacity: 1;
287
+ }
288
+ }
289
+
290
+ @media (prefers-reduced-motion: reduce) {
291
+ .kso-backdrop,
292
+ .kso-panel {
293
+ animation: none !important;
294
+ }
295
+ }
296
+ </style>
@@ -0,0 +1,42 @@
1
+ /**
2
+ * KeyboardShortcutsOverlay — press `?` to toggle a modal listing the
3
+ * keyboard shortcuts available on the current page. Press `?` again or
4
+ * `Escape` to dismiss. Input/contenteditable fields and Ctrl/Meta/Alt
5
+ * combos are ignored so the shortcut never interferes with typing.
6
+ *
7
+ * Usage:
8
+ *
9
+ * <KeyboardShortcutsOverlay shortcuts={[
10
+ * { key: 'j / ↓', description: 'Focus next item' },
11
+ * { key: 'k / ↑', description: 'Focus previous item' },
12
+ * { key: 'Enter', description: 'Open detail' },
13
+ * ]} />
14
+ */
15
+ export interface KeyboardShortcut {
16
+ key: string;
17
+ description: string;
18
+ }
19
+ export interface SectionEntry {
20
+ key: string;
21
+ description: string;
22
+ /** Optional oklch color to tint the key badge — used for legend entries */
23
+ color?: string;
24
+ }
25
+ export interface ShortcutSection {
26
+ title: string;
27
+ shortcuts: SectionEntry[];
28
+ }
29
+ type $$ComponentProps = {
30
+ shortcuts?: KeyboardShortcut[];
31
+ /**
32
+ * Optional grouped sections (e.g. mode-specific shortcut tables for
33
+ * /inbox: list / detail / compose). When provided, takes
34
+ * precedence over the flat `shortcuts` prop.
35
+ */
36
+ sections?: ShortcutSection[];
37
+ title?: string;
38
+ open?: boolean;
39
+ };
40
+ declare const KeyboardShortcutsOverlay: import("svelte").Component<$$ComponentProps, {}, "open">;
41
+ type KeyboardShortcutsOverlay = ReturnType<typeof KeyboardShortcutsOverlay>;
42
+ export default KeyboardShortcutsOverlay;
@@ -0,0 +1,123 @@
1
+ <script lang="ts">
2
+ import { onMount, onDestroy } from "svelte"
3
+
4
+ let {
5
+ src = "",
6
+ alt = "",
7
+ className = "",
8
+ aspectRatio = null,
9
+ fit = "cover",
10
+ preload = false,
11
+ priority = false,
12
+ width = null,
13
+ height = null,
14
+ } = $props<{
15
+ src: string
16
+ alt: string
17
+ className?: string
18
+ aspectRatio?: string | null
19
+ fit?: "cover" | "contain" | "fill"
20
+ preload?: boolean
21
+ priority?: boolean
22
+ width?: number | null
23
+ height?: number | null
24
+ }>()
25
+
26
+ let loaded = $state(false)
27
+ let error = $state(false)
28
+ // svelte-ignore state_referenced_locally
29
+ let shouldLoad = $state(preload || priority)
30
+ let containerRef: HTMLDivElement
31
+
32
+ function startLoadingImage() {
33
+ if (loaded || !src) return
34
+ shouldLoad = true
35
+ }
36
+
37
+ onMount(() => {
38
+ if (priority || preload) {
39
+ startLoadingImage()
40
+ return
41
+ }
42
+
43
+ if (containerRef) {
44
+ const observer = new IntersectionObserver(
45
+ (entries) => {
46
+ for (const entry of entries) {
47
+ if (entry.isIntersecting) {
48
+ startLoadingImage()
49
+ observer.unobserve(entry.target)
50
+ }
51
+ }
52
+ },
53
+ { root: null, rootMargin: "200px", threshold: 0.01 }
54
+ )
55
+ observer.observe(containerRef)
56
+ return () => observer.disconnect()
57
+ }
58
+ })
59
+
60
+ $effect(() => {
61
+ if (src) {
62
+ loaded = false
63
+ error = false
64
+ }
65
+ })
66
+ </script>
67
+
68
+ <div
69
+ bind:this={containerRef}
70
+ class="relative overflow-hidden {className}"
71
+ style={aspectRatio ? `aspect-ratio: ${aspectRatio};` : ""}
72
+ data-src={src}
73
+ >
74
+ {#if !loaded && shouldLoad}
75
+ <div class="skeleton absolute inset-0 w-full h-full"></div>
76
+ {/if}
77
+
78
+ {#if shouldLoad}
79
+ <img
80
+ {src}
81
+ {alt}
82
+ {width}
83
+ {height}
84
+ class="w-full h-full object-{fit} transition-opacity duration-300 {loaded ? 'opacity-100' : 'opacity-0'}"
85
+ onload={(e) => {
86
+ loaded = true
87
+ if (!width || !height) {
88
+ const img = e.target as HTMLImageElement
89
+ width = img.naturalWidth
90
+ height = img.naturalHeight
91
+ }
92
+ }}
93
+ onerror={() => (error = true)}
94
+ loading={priority ? "eager" : "lazy"}
95
+ decoding="async"
96
+ />
97
+ {/if}
98
+
99
+ {#if !shouldLoad}
100
+ <div
101
+ class="w-full h-full bg-base-200"
102
+ style={width && height ? `aspect-ratio: ${width / height};` : ""}
103
+ ></div>
104
+ {/if}
105
+
106
+ {#if error && shouldLoad}
107
+ <div class="absolute inset-0 flex items-center justify-center bg-base-200">
108
+ <div class="flex flex-col items-center text-base-content/70">
109
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mb-2">
110
+ <rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect>
111
+ <circle cx="9" cy="9" r="2"></circle>
112
+ <path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"></path>
113
+ </svg>
114
+ <span class="text-xs">Failed to load image</span>
115
+ </div>
116
+ </div>
117
+ {/if}
118
+ </div>
119
+
120
+ <style>
121
+ img { will-change: opacity; }
122
+ .skeleton { transform: translateZ(0); will-change: opacity; }
123
+ </style>
@@ -0,0 +1,14 @@
1
+ type $$ComponentProps = {
2
+ src: string;
3
+ alt: string;
4
+ className?: string;
5
+ aspectRatio?: string | null;
6
+ fit?: "cover" | "contain" | "fill";
7
+ preload?: boolean;
8
+ priority?: boolean;
9
+ width?: number | null;
10
+ height?: number | null;
11
+ };
12
+ declare const LazyImage: import("svelte").Component<$$ComponentProps, {}, "">;
13
+ type LazyImage = ReturnType<typeof LazyImage>;
14
+ export default LazyImage;