@joewinke/jatui 0.1.25 → 0.1.27

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 +686 -0
  110. package/dist/components/SearchDropdown.svelte.d.ts +40 -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,351 @@
1
+ /**
2
+ * Keyboard-rail navigation primitive — action + composable.
3
+ *
4
+ * A drawer / form is a vertical sequence of "rungs". Focus starts in the first
5
+ * field. Up / Down (and j / k when NOT typing) step between rungs. Left / Right
6
+ * acts on the focused rung (cycle a value, switch a tab, open a picker). Escape
7
+ * pops focus out of a field back to the container, then fires `onEscape`.
8
+ *
9
+ * Rungs are ordinary HTML elements tagged with a `data-rail-id` attribute (or
10
+ * whatever attribute `attr` specifies). The consumer supplies an `order` array
11
+ * of ids that defines traversal; ids whose element is not currently rendered
12
+ * in the container are skipped at move time.
13
+ *
14
+ * ─── CSS contract ─────────────────────────────────────────────────────────────
15
+ *
16
+ * The controller applies two classes to rung host elements. Style them in your
17
+ * stylesheet (or in a parent scope):
18
+ *
19
+ * .rail-focus — persistent: applied to the currently active rung host. Use
20
+ * `:focus-visible` / `:has(:focus-visible)` inside the rule so
21
+ * only keyboard-driven focus shows a visible ring.
22
+ *
23
+ * .rail-cycled — transient (~280ms): applied when Left / Right changes a
24
+ * rung's value via `rail.bump(host)`. Use for a brief pulse
25
+ * that confirms the value changed.
26
+ *
27
+ * ─── Usage — action form (simplest) ──────────────────────────────────────────
28
+ *
29
+ * <div use:railNav={{
30
+ * order: () => ['title', 'type', 'priority'],
31
+ * onAction: (id, dir, host) => cycleField(id, dir),
32
+ * onEscape: () => closeDrawer(),
33
+ * }}>
34
+ * <input data-rail-id="title" />
35
+ * <div data-rail-id="type" tabindex="-1">…</div>
36
+ * <div data-rail-id="priority" tabindex="-1">…</div>
37
+ * </div>
38
+ *
39
+ * ─── Usage — composable form ──────────────────────────────────────────────────
40
+ *
41
+ * const rail = createRailNav({
42
+ * container: panelEl,
43
+ * order: () => currentOrder,
44
+ * onAction: (id, dir, host) => { rail.bump(host); cycleField(id, dir); },
45
+ * onEscape: () => closeDrawer(),
46
+ * });
47
+ *
48
+ * // Wire both listeners on the container:
49
+ * panelEl.addEventListener('keydown', (e) => rail.handleKeydown(e));
50
+ * panelEl.addEventListener('focusin', (e) => rail.handleFocusin(e));
51
+ *
52
+ * ─── How to wire a drawer to railNav ──────────────────────────────────────────
53
+ *
54
+ * 1. Tag each rung host with `data-rail-id`:
55
+ * <div data-rail-id="type" tabindex="-1">…</div>
56
+ *
57
+ * 2. Apply the action (or composable) to the drawer panel:
58
+ * <div bind:this={panel} use:railNav={{ order, onAction, onEscape }} …>
59
+ *
60
+ * 3. Handle Left/Right in `onAction` and call `rail.bump(host)` on change:
61
+ * onAction: (id, dir, host) => { formData[id] = cycle(opts[id], …, dir); rail.bump(host); }
62
+ */
63
+ var __assign = (this && this.__assign) || function () {
64
+ __assign = Object.assign || function(t) {
65
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
66
+ s = arguments[i];
67
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
68
+ t[p] = s[p];
69
+ }
70
+ return t;
71
+ };
72
+ return __assign.apply(this, arguments);
73
+ };
74
+ // ─── Exported helpers ──────────────────────────────────────────────────────────
75
+ /**
76
+ * Cycle through `list`, returning the item `dir` steps from `current`.
77
+ * Wraps around. Returns `current` unchanged when the list is empty.
78
+ *
79
+ * @example
80
+ * cycle(['bug', 'feature', 'task'], 'feature', 1) // → 'task'
81
+ * cycle(['bug', 'feature', 'task'], 'bug', -1) // → 'task'
82
+ * cycle(['a', 'b'], 'z', 1) // → 'a' (not found → start)
83
+ */
84
+ export function cycle(list, current, dir) {
85
+ if (list.length === 0)
86
+ return current;
87
+ var i = list.indexOf(current);
88
+ // When `current` is not in the list, treat it as if we're at position -1
89
+ // so that `dir=1` lands at index 0 (the first item) and `dir=-1` lands at
90
+ // the last item — matching the "not found → start from the beginning" idiom.
91
+ var base = i === -1 ? -1 : i;
92
+ return list[(((base + dir) % list.length) + list.length) % list.length];
93
+ }
94
+ // ─── Internals ─────────────────────────────────────────────────────────────────
95
+ var BUMP_DURATION_MS = 280;
96
+ var RAIL_FOCUS_CLASS = 'rail-focus';
97
+ var RAIL_CYCLED_CLASS = 'rail-cycled';
98
+ function resolveOrder(opts) {
99
+ return typeof opts.order === 'function' ? opts.order() : opts.order;
100
+ }
101
+ function resolveAttr(opts) {
102
+ var _a;
103
+ return (_a = opts.attr) !== null && _a !== void 0 ? _a : 'data-rail-id';
104
+ }
105
+ /** Return all currently-rendered rung hosts in the rail order. */
106
+ function resolveHosts(optsRef) {
107
+ var container = optsRef.current.container;
108
+ if (!container)
109
+ return [];
110
+ var attr = resolveAttr(optsRef.current);
111
+ var order = resolveOrder(optsRef.current);
112
+ return order
113
+ .map(function (id) { return container.querySelector("[".concat(attr, "=\"").concat(id, "\"]")); })
114
+ .filter(function (el) { return !!el; });
115
+ }
116
+ /**
117
+ * Focus the most appropriate control inside a rung host:
118
+ *
119
+ * 1. If the host itself is directly focusable (input / select / textarea /
120
+ * button / [contenteditable] / [tabindex]), focus it.
121
+ * 2. Otherwise search for a real field before a trigger button. We use an
122
+ * explicit selector for real fields first because `querySelector` resolves
123
+ * by document order — a label's `<button>` can appear before the field it
124
+ * accompanies (e.g. a voice-mic button before a contenteditable).
125
+ * 3. Fall back to any `.sd-trigger,button` (SearchDropdown trigger etc.).
126
+ * 4. Last resort: focus the host itself (requires tabindex on the host).
127
+ */
128
+ function focusRailHost(host) {
129
+ var _a;
130
+ if (host.matches('input,select,textarea,button,[contenteditable="true"],[tabindex]')) {
131
+ host.focus();
132
+ return;
133
+ }
134
+ var inner = (_a = host.querySelector('[contenteditable="true"],input,select,textarea')) !== null && _a !== void 0 ? _a : host.querySelector('.sd-trigger,button');
135
+ (inner !== null && inner !== void 0 ? inner : host).focus();
136
+ }
137
+ function makeController(optsRef) {
138
+ var _currentRailId = '';
139
+ var _lastFocusedHost = null;
140
+ function applyFocusClass(host) {
141
+ if (_lastFocusedHost && _lastFocusedHost !== host) {
142
+ _lastFocusedHost.classList.remove(RAIL_FOCUS_CLASS);
143
+ }
144
+ if (host)
145
+ host.classList.add(RAIL_FOCUS_CLASS);
146
+ _lastFocusedHost = host;
147
+ }
148
+ function bump(host) {
149
+ if (!host)
150
+ return;
151
+ host.classList.remove(RAIL_CYCLED_CLASS);
152
+ void host.offsetWidth; // reflow so the animation restarts on rapid presses
153
+ host.classList.add(RAIL_CYCLED_CLASS);
154
+ setTimeout(function () { return host.classList.remove(RAIL_CYCLED_CLASS); }, BUMP_DURATION_MS);
155
+ }
156
+ function focus(id) {
157
+ var container = optsRef.current.container;
158
+ if (!container)
159
+ return;
160
+ var attr = resolveAttr(optsRef.current);
161
+ var host = container.querySelector("[".concat(attr, "=\"").concat(id, "\"]"));
162
+ if (host) {
163
+ focusRailHost(host);
164
+ applyFocusClass(host);
165
+ _currentRailId = id;
166
+ }
167
+ else {
168
+ container.focus();
169
+ }
170
+ }
171
+ function moveRung(dir) {
172
+ var _a;
173
+ var hosts = resolveHosts(optsRef);
174
+ if (hosts.length === 0)
175
+ return;
176
+ var attr = resolveAttr(optsRef.current);
177
+ var ids = hosts.map(function (h) { return h.getAttribute(attr); });
178
+ // Anchor on the actually-focused rung first. Fall back to the last tracked
179
+ // rung (covers focus parked on the container after an Escape pop-out).
180
+ var active = document.activeElement;
181
+ var activeFocused = (_a = (active === null || active === void 0 ? void 0 : active.closest("[".concat(attr, "]")))) === null || _a === void 0 ? void 0 : _a.getAttribute(attr);
182
+ var fromId = activeFocused && ids.includes(activeFocused) ? activeFocused : _currentRailId;
183
+ var pos = fromId ? ids.indexOf(fromId) : -1;
184
+ if (pos === -1)
185
+ pos = dir === 1 ? -1 : hosts.length;
186
+ var next = (((pos + dir) % hosts.length) + hosts.length) % hosts.length;
187
+ focusRailHost(hosts[next]);
188
+ applyFocusClass(hosts[next]);
189
+ _currentRailId = ids[next];
190
+ }
191
+ function refresh() {
192
+ // Re-derive hosts; re-apply focus class if the current rung is still rendered
193
+ // (DOM may have been reconstructed by Svelte's reactive updates).
194
+ var hosts = resolveHosts(optsRef);
195
+ var attr = resolveAttr(optsRef.current);
196
+ for (var _i = 0, hosts_1 = hosts; _i < hosts_1.length; _i++) {
197
+ var h = hosts_1[_i];
198
+ if (h.getAttribute(attr) === _currentRailId) {
199
+ applyFocusClass(h);
200
+ return;
201
+ }
202
+ }
203
+ }
204
+ function handleFocusin(event) {
205
+ var _a;
206
+ var attr = resolveAttr(optsRef.current);
207
+ var order = resolveOrder(optsRef.current);
208
+ var host = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest("[".concat(attr, "]"));
209
+ var id = host === null || host === void 0 ? void 0 : host.getAttribute(attr);
210
+ // Only track rungs that are in the rail order. Tabbing into an off-rail
211
+ // control (e.g. a description textarea) leaves the last real rung intact
212
+ // so Up / Down can resume from it after the user tabs back out.
213
+ if (id && order.includes(id)) {
214
+ _currentRailId = id;
215
+ applyFocusClass(host);
216
+ }
217
+ }
218
+ function handleKeydown(event) {
219
+ var _a, _b, _c, _d, _e, _f, _g, _h;
220
+ var opts = optsRef.current;
221
+ var el = document.activeElement;
222
+ var tag = el === null || el === void 0 ? void 0 : el.tagName;
223
+ var isTextField = !!el &&
224
+ (tag === 'INPUT' || tag === 'TEXTAREA' || el.isContentEditable === true);
225
+ var isMultiline = !!el && (tag === 'TEXTAREA' || el.isContentEditable === true);
226
+ var hasMod = event.metaKey || event.ctrlKey || event.altKey;
227
+ // Modal trap — stop non-text keystrokes from bubbling to window-level page
228
+ // listeners (e.g., a listNav attached to `window`). Text-field keystrokes
229
+ // propagate normally so typing in the drawer doesn't interfere.
230
+ var trap = opts.trap !== false;
231
+ if (trap && !isTextField)
232
+ event.stopPropagation();
233
+ // Escape cascade:
234
+ // 1. popover open → close popover (e.g. project dropdown)
235
+ // 2. focus inside a descendant control → pop back to container
236
+ // 3. focus already on the container (or just popped) → call onEscape
237
+ if (event.key === 'Escape' && !hasMod) {
238
+ var popoverOpen_1 = (_b = (_a = opts.isPopoverOpen) === null || _a === void 0 ? void 0 : _a.call(opts)) !== null && _b !== void 0 ? _b : false;
239
+ if (popoverOpen_1) {
240
+ event.preventDefault();
241
+ (_c = opts.onPopoverEscape) === null || _c === void 0 ? void 0 : _c.call(opts);
242
+ return true;
243
+ }
244
+ var container = opts.container;
245
+ if (container && el && el !== container && container.contains(el)) {
246
+ event.preventDefault();
247
+ container.focus();
248
+ return true;
249
+ }
250
+ event.preventDefault();
251
+ (_d = opts.onEscape) === null || _d === void 0 ? void 0 : _d.call(opts);
252
+ return true;
253
+ }
254
+ // Rail motion — suppressed when a popover is open or a modifier is held.
255
+ var popoverOpen = (_f = (_e = opts.isPopoverOpen) === null || _e === void 0 ? void 0 : _e.call(opts)) !== null && _f !== void 0 ? _f : false;
256
+ if (!hasMod && !popoverOpen) {
257
+ // Vertical: ArrowDown / j step down; ArrowUp / k step up.
258
+ // j / k are blocked in ANY text field; arrows are blocked only inside
259
+ // multiline fields so the caret can still move line-by-line.
260
+ var goDown = event.key === 'ArrowDown' || event.key === 'j';
261
+ var goUp = event.key === 'ArrowUp' || event.key === 'k';
262
+ if (goDown || goUp) {
263
+ var isVimKey = event.key === 'j' || event.key === 'k';
264
+ var blocked = isVimKey ? isTextField : isMultiline;
265
+ if (!blocked) {
266
+ event.preventDefault();
267
+ moveRung(goDown ? 1 : -1);
268
+ return true;
269
+ }
270
+ }
271
+ // Horizontal: ArrowLeft / ArrowRight dispatch to `onAction`.
272
+ // Skipped inside text fields so caret-left / caret-right keep working.
273
+ if ((event.key === 'ArrowLeft' || event.key === 'ArrowRight') &&
274
+ !isTextField) {
275
+ event.preventDefault();
276
+ var attr = resolveAttr(opts);
277
+ // Prefer the element that actually has DOM focus, then fall back to
278
+ // the last tracked rung (focus parked on the container after pop-out).
279
+ var focusedHost = (_g = (el === null || el === void 0 ? void 0 : el.closest("[".concat(attr, "]")))) !== null && _g !== void 0 ? _g : (opts.container && _currentRailId
280
+ ? opts.container.querySelector("[".concat(attr, "=\"").concat(_currentRailId, "\"]"))
281
+ : null);
282
+ if (focusedHost && opts.onAction) {
283
+ var rungId = (_h = focusedHost.getAttribute(attr)) !== null && _h !== void 0 ? _h : '';
284
+ opts.onAction(rungId, event.key === 'ArrowRight' ? 1 : -1, focusedHost);
285
+ }
286
+ return true;
287
+ }
288
+ }
289
+ return false;
290
+ }
291
+ return {
292
+ handleKeydown: handleKeydown,
293
+ handleFocusin: handleFocusin,
294
+ moveRung: moveRung,
295
+ focus: focus,
296
+ refresh: refresh,
297
+ currentId: function () { return _currentRailId; },
298
+ bump: bump,
299
+ };
300
+ }
301
+ // ─── Composable ────────────────────────────────────────────────────────────────
302
+ /**
303
+ * Imperative composable — use when you already own keydown / focusin handlers
304
+ * and want `railNav` to decide whether to consume the event.
305
+ *
306
+ * You **must** supply `options.container` and wire both events:
307
+ *
308
+ * const rail = createRailNav({ container: panel, order, onAction, onEscape });
309
+ * panel.addEventListener('keydown', (e) => rail.handleKeydown(e));
310
+ * panel.addEventListener('focusin', (e) => rail.handleFocusin(e));
311
+ */
312
+ export function createRailNav(options) {
313
+ var ref = { current: options };
314
+ return makeController(ref);
315
+ }
316
+ // ─── Action ────────────────────────────────────────────────────────────────────
317
+ /**
318
+ * Svelte action — attaches `keydown` and `focusin` listeners to `node` and
319
+ * handles rung stepping, horizontal action dispatch, and Escape cascade.
320
+ *
321
+ * @example
322
+ * <div use:railNav={{ order, onAction, onEscape }}>
323
+ * <input data-rail-id="title" />
324
+ * <div data-rail-id="type" tabindex="-1">…</div>
325
+ * <div data-rail-id="priority" tabindex="-1">…</div>
326
+ * </div>
327
+ */
328
+ export var railNav = function (node, initial) {
329
+ if (initial === void 0) { initial = { order: [] }; }
330
+ var ref = { current: __assign(__assign({}, initial), { container: node }) };
331
+ var controller = makeController(ref);
332
+ function onKeydown(e) {
333
+ controller.handleKeydown(e);
334
+ }
335
+ function onFocusin(e) {
336
+ controller.handleFocusin(e);
337
+ }
338
+ node.addEventListener('keydown', onKeydown);
339
+ node.addEventListener('focusin', onFocusin);
340
+ return {
341
+ update: function (next) {
342
+ if (next === void 0) { next = { order: [] }; }
343
+ ref.current = __assign(__assign({}, next), { container: node });
344
+ controller.refresh();
345
+ },
346
+ destroy: function () {
347
+ node.removeEventListener('keydown', onKeydown);
348
+ node.removeEventListener('focusin', onFocusin);
349
+ },
350
+ };
351
+ };
@@ -0,0 +1,108 @@
1
+ <script lang="ts">
2
+ import type { Annotation, AnnotationThread } from '../types/annotation'
3
+
4
+ interface Props {
5
+ annotations: Annotation[]
6
+ activeAnnotationId?: string | null
7
+ annotationMode?: boolean
8
+ showResolved?: boolean
9
+ onAnnotationClick?: (annotation: Annotation) => void
10
+ onImageClick?: (x: number, y: number) => void
11
+ }
12
+
13
+ let {
14
+ annotations = [],
15
+ activeAnnotationId = null,
16
+ annotationMode = false,
17
+ showResolved = false,
18
+ onAnnotationClick,
19
+ onImageClick,
20
+ }: Props = $props()
21
+
22
+ // Group into threads (only show root pins)
23
+ const threads = $derived.by(() => {
24
+ const roots = annotations.filter((a) => !a.parent_annotation_id)
25
+ return roots
26
+ .filter((r) => showResolved || !r.resolved)
27
+ .map((root) => ({
28
+ root,
29
+ replies: annotations.filter((a) => a.parent_annotation_id === root.id),
30
+ }))
31
+ })
32
+
33
+ function handleOverlayClick(e: MouseEvent) {
34
+ if (!annotationMode || !onImageClick) return
35
+ const target = e.currentTarget as HTMLElement
36
+ const rect = target.getBoundingClientRect()
37
+ const x = ((e.clientX - rect.left) / rect.width) * 100
38
+ const y = ((e.clientY - rect.top) / rect.height) * 100
39
+ onImageClick(Math.round(x * 1000) / 1000, Math.round(y * 1000) / 1000)
40
+ }
41
+ </script>
42
+
43
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
44
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
45
+ <div
46
+ class="absolute inset-0 z-10"
47
+ class:cursor-crosshair={annotationMode}
48
+ class:pointer-events-none={!annotationMode && threads.length === 0}
49
+ onclick={handleOverlayClick}
50
+ >
51
+ {#each threads as thread (thread.root.id)}
52
+ {@const isActive = activeAnnotationId === thread.root.id}
53
+ {@const replyCount = thread.replies.length}
54
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
55
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
56
+ <div
57
+ class="absolute pointer-events-auto"
58
+ style="left: {thread.root.x_percent}%; top: {thread.root.y_percent}%; transform: translate(-50%, -100%);"
59
+ >
60
+ <button
61
+ class="relative group flex flex-col items-center"
62
+ onclick={(e) => { e.stopPropagation(); onAnnotationClick?.(thread.root) }}
63
+ title={thread.root.content.slice(0, 80)}
64
+ >
65
+ <!-- Pin icon -->
66
+ <div
67
+ class="w-7 h-7 rounded-full flex items-center justify-center shadow-lg border-2 transition-all
68
+ {isActive
69
+ ? 'bg-primary border-primary text-primary-content scale-125'
70
+ : thread.root.resolved
71
+ ? 'bg-success/80 border-success/60 text-success-content'
72
+ : 'bg-warning border-warning/80 text-warning-content hover:scale-110'}"
73
+ >
74
+ {#if thread.root.resolved}
75
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
76
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7" />
77
+ </svg>
78
+ {:else}
79
+ <svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24">
80
+ <path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z" />
81
+ </svg>
82
+ {/if}
83
+ </div>
84
+
85
+ <!-- Reply count badge -->
86
+ {#if replyCount > 0}
87
+ <span class="absolute -top-1 -right-1 min-w-[16px] h-4 px-1 rounded-full bg-base-content text-base-100 text-[10px] font-bold flex items-center justify-center">
88
+ {replyCount}
89
+ </span>
90
+ {/if}
91
+
92
+ <!-- Pin tail -->
93
+ <div
94
+ class="w-0.5 h-2 -mt-0.5 {isActive ? 'bg-primary' : thread.root.resolved ? 'bg-success/60' : 'bg-warning/80'}"
95
+ ></div>
96
+ </button>
97
+ </div>
98
+ {/each}
99
+
100
+ <!-- Placement indicator when in annotation mode -->
101
+ {#if annotationMode}
102
+ <div class="absolute inset-0 border-2 border-dashed border-primary/30 rounded pointer-events-none">
103
+ <div class="absolute top-2 left-1/2 -translate-x-1/2 bg-primary/90 text-primary-content text-xs px-3 py-1 rounded-full shadow whitespace-nowrap">
104
+ Click to place annotation
105
+ </div>
106
+ </div>
107
+ {/if}
108
+ </div>
@@ -0,0 +1,12 @@
1
+ import type { Annotation } from '../types/annotation';
2
+ interface Props {
3
+ annotations: Annotation[];
4
+ activeAnnotationId?: string | null;
5
+ annotationMode?: boolean;
6
+ showResolved?: boolean;
7
+ onAnnotationClick?: (annotation: Annotation) => void;
8
+ onImageClick?: (x: number, y: number) => void;
9
+ }
10
+ declare const AnnotationLayer: import("svelte").Component<Props, {}, "">;
11
+ type AnnotationLayer = ReturnType<typeof AnnotationLayer>;
12
+ export default AnnotationLayer;