@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.
- package/dist/actions/collapse.d.ts +80 -0
- package/dist/actions/collapse.js +134 -0
- package/dist/actions/railNav.d.ts +167 -0
- package/dist/actions/railNav.js +351 -0
- package/dist/components/AnnotationLayer.svelte +108 -0
- package/dist/components/AnnotationLayer.svelte.d.ts +12 -0
- package/dist/components/AnnotationPanel.svelte +319 -0
- package/dist/components/AnnotationPanel.svelte.d.ts +13 -0
- package/dist/components/AudioWaveform.svelte +701 -0
- package/dist/components/AudioWaveform.svelte.d.ts +19 -0
- package/dist/components/AvailabilityModal.svelte +177 -0
- package/dist/components/AvailabilityModal.svelte.d.ts +14 -0
- package/dist/components/AvatarUpload.svelte +234 -0
- package/dist/components/AvatarUpload.svelte.d.ts +23 -0
- package/dist/components/Badge.svelte +38 -0
- package/dist/components/Badge.svelte.d.ts +14 -0
- package/dist/components/BookingForm.svelte +278 -0
- package/dist/components/BookingForm.svelte.d.ts +37 -0
- package/dist/components/BurndownChart.svelte +778 -0
- package/dist/components/BurndownChart.svelte.d.ts +17 -0
- package/dist/components/Button.svelte +81 -0
- package/dist/components/Button.svelte.d.ts +21 -0
- package/dist/components/CalendarPicker.svelte +284 -0
- package/dist/components/CalendarPicker.svelte.d.ts +22 -0
- package/dist/components/Card.svelte +67 -0
- package/dist/components/Card.svelte.d.ts +17 -0
- package/dist/components/CharacterCounter.svelte +82 -0
- package/dist/components/CharacterCounter.svelte.d.ts +10 -0
- package/dist/components/ChipInput.svelte +609 -0
- package/dist/components/ChipInput.svelte.d.ts +55 -0
- package/dist/components/Collapse.svelte +91 -0
- package/dist/components/Collapse.svelte.d.ts +18 -0
- package/dist/components/ColorSelector.svelte +167 -0
- package/dist/components/ColorSelector.svelte.d.ts +9 -0
- package/dist/components/CommentThread.svelte +773 -0
- package/dist/components/CommentThread.svelte.d.ts +32 -0
- package/dist/components/ConfirmDialog.svelte +348 -0
- package/dist/components/ConfirmDialog.svelte.d.ts +3 -0
- package/dist/components/ConfirmModal.svelte +142 -0
- package/dist/components/ConfirmModal.svelte.d.ts +15 -0
- package/dist/components/ContextMenu.svelte +188 -0
- package/dist/components/ContextMenu.svelte.d.ts +31 -0
- package/dist/components/CountdownTimer.svelte +94 -0
- package/dist/components/CountdownTimer.svelte.d.ts +10 -0
- package/dist/components/DateRangePicker.svelte +194 -0
- package/dist/components/DateRangePicker.svelte.d.ts +10 -0
- package/dist/components/Drawer.svelte +143 -0
- package/dist/components/Drawer.svelte.d.ts +33 -0
- package/dist/components/EmojiPicker.svelte +159 -0
- package/dist/components/EmojiPicker.svelte.d.ts +8 -0
- package/dist/components/EntityPreviewCard.svelte +104 -0
- package/dist/components/EntityPreviewCard.svelte.d.ts +9 -0
- package/dist/components/FileDropzone.svelte +493 -0
- package/dist/components/FileDropzone.svelte.d.ts +40 -0
- package/dist/components/FilePicker.svelte +488 -0
- package/dist/components/FilePicker.svelte.d.ts +20 -0
- package/dist/components/FileThumbnail.svelte +80 -0
- package/dist/components/FileThumbnail.svelte.d.ts +12 -0
- package/dist/components/FilterDropdown.svelte +203 -0
- package/dist/components/FilterDropdown.svelte.d.ts +48 -0
- package/dist/components/FloatingActionBar.svelte +354 -0
- package/dist/components/FloatingActionBar.svelte.d.ts +51 -0
- package/dist/components/GPSTracker.svelte +202 -0
- package/dist/components/GPSTracker.svelte.d.ts +34 -0
- package/dist/components/HunkDiffView.svelte +348 -0
- package/dist/components/HunkDiffView.svelte.d.ts +9 -0
- package/dist/components/ImageLightbox.svelte +274 -0
- package/dist/components/ImageLightbox.svelte.d.ts +25 -0
- package/dist/components/ImageUpload.svelte +147 -0
- package/dist/components/ImageUpload.svelte.d.ts +10 -0
- package/dist/components/InlineEdit.svelte +291 -0
- package/dist/components/InlineEdit.svelte.d.ts +45 -0
- package/dist/components/InputDialog.svelte +327 -0
- package/dist/components/InputDialog.svelte.d.ts +3 -0
- package/dist/components/KeyboardShortcutsOverlay.svelte +296 -0
- package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +42 -0
- package/dist/components/LazyImage.svelte +123 -0
- package/dist/components/LazyImage.svelte.d.ts +14 -0
- package/dist/components/LinkShortener.svelte +274 -0
- package/dist/components/LinkShortener.svelte.d.ts +27 -0
- package/dist/components/LoadingSpinner.svelte +106 -0
- package/dist/components/LoadingSpinner.svelte.d.ts +21 -0
- package/dist/components/LocationMap.svelte +186 -0
- package/dist/components/LocationMap.svelte.d.ts +11 -0
- package/dist/components/MapView.svelte +341 -0
- package/dist/components/MapView.svelte.d.ts +20 -0
- package/dist/components/MarkupEditor.svelte +485 -0
- package/dist/components/MarkupEditor.svelte.d.ts +10 -0
- package/dist/components/MarkupOverlay.svelte +55 -0
- package/dist/components/MarkupOverlay.svelte.d.ts +8 -0
- package/dist/components/MediaWorkbench.svelte +871 -0
- package/dist/components/MediaWorkbench.svelte.d.ts +9 -0
- package/dist/components/MilestoneCard.svelte +98 -0
- package/dist/components/MilestoneCard.svelte.d.ts +16 -0
- package/dist/components/MilestoneTimeline.svelte +91 -0
- package/dist/components/MilestoneTimeline.svelte.d.ts +15 -0
- package/dist/components/Modal.svelte +244 -0
- package/dist/components/Modal.svelte.d.ts +35 -0
- package/dist/components/PDFViewer.svelte +105 -0
- package/dist/components/PDFViewer.svelte.d.ts +9 -0
- package/dist/components/PdfThumbnail.svelte +63 -0
- package/dist/components/PdfThumbnail.svelte.d.ts +9 -0
- package/dist/components/PhoneInput.svelte +212 -0
- package/dist/components/PhoneInput.svelte.d.ts +25 -0
- package/dist/components/ResizableDivider.svelte +307 -0
- package/dist/components/ResizableDivider.svelte.d.ts +26 -0
- package/dist/components/ResizablePanel.svelte +305 -0
- package/dist/components/ResizablePanel.svelte.d.ts +18 -0
- package/dist/components/SearchDropdown.svelte +421 -0
- package/dist/components/SearchDropdown.svelte.d.ts +24 -0
- package/dist/components/SelectInput.svelte +237 -0
- package/dist/components/SelectInput.svelte.d.ts +26 -0
- package/dist/components/SidebarUserFooter.svelte +34 -0
- package/dist/components/SidebarUserFooter.svelte.d.ts +16 -0
- package/dist/components/SignaturePad.svelte +175 -0
- package/dist/components/SignaturePad.svelte.d.ts +17 -0
- package/dist/components/SmartImageEditor.svelte +720 -0
- package/dist/components/SmartImageEditor.svelte.d.ts +24 -0
- package/dist/components/SortDropdown.svelte +156 -0
- package/dist/components/SortDropdown.svelte.d.ts +40 -0
- package/dist/components/Sparkline.svelte +116 -0
- package/dist/components/Sparkline.svelte.d.ts +12 -0
- package/dist/components/SpeechForm.svelte +114 -0
- package/dist/components/SpeechForm.svelte.d.ts +17 -0
- package/dist/components/StatusBadge.svelte +157 -0
- package/dist/components/StatusBadge.svelte.d.ts +24 -0
- package/dist/components/TaskTypeIcon.svelte +54 -0
- package/dist/components/TaskTypeIcon.svelte.d.ts +17 -0
- package/dist/components/TextArea.svelte +162 -0
- package/dist/components/TextArea.svelte.d.ts +30 -0
- package/dist/components/TextInput.svelte +131 -0
- package/dist/components/TextInput.svelte.d.ts +29 -0
- package/dist/components/ThemeSelector.svelte +206 -0
- package/dist/components/ThemeSelector.svelte.d.ts +14 -0
- package/dist/components/TimeSlotPicker.svelte +162 -0
- package/dist/components/TimeSlotPicker.svelte.d.ts +18 -0
- package/dist/components/UserAvatar.svelte +72 -0
- package/dist/components/UserAvatar.svelte.d.ts +11 -0
- package/dist/components/VariablePicker.svelte +170 -0
- package/dist/components/VariablePicker.svelte.d.ts +40 -0
- package/dist/components/VoicePlayer.svelte +422 -0
- package/dist/components/VoicePlayer.svelte.d.ts +18 -0
- package/dist/components/linked-columns/LinkedColumns.svelte +526 -0
- package/dist/components/linked-columns/LinkedColumns.svelte.d.ts +130 -0
- package/dist/components/markup.d.ts +47 -0
- package/dist/components/markup.js +201 -0
- package/dist/components/messaging/Avatar.svelte +81 -0
- package/dist/components/messaging/Avatar.svelte.d.ts +11 -0
- package/dist/components/messaging/ChannelInfoModal.svelte +163 -0
- package/dist/components/messaging/ChannelInfoModal.svelte.d.ts +18 -0
- package/dist/components/messaging/ChannelList.svelte +107 -0
- package/dist/components/messaging/ChannelList.svelte.d.ts +11 -0
- package/dist/components/messaging/ChannelMemberAvatarStack.svelte +69 -0
- package/dist/components/messaging/ChannelMemberAvatarStack.svelte.d.ts +11 -0
- package/dist/components/messaging/ChannelMembersModal.svelte +182 -0
- package/dist/components/messaging/ChannelMembersModal.svelte.d.ts +16 -0
- package/dist/components/messaging/CreateChannelModal.svelte +190 -0
- package/dist/components/messaging/CreateChannelModal.svelte.d.ts +14 -0
- package/dist/components/messaging/DirectMessageList.svelte +145 -0
- package/dist/components/messaging/DirectMessageList.svelte.d.ts +12 -0
- package/dist/components/messaging/EmojiSelector.svelte +261 -0
- package/dist/components/messaging/EmojiSelector.svelte.d.ts +19 -0
- package/dist/components/messaging/MentionAutocomplete.svelte +193 -0
- package/dist/components/messaging/MentionAutocomplete.svelte.d.ts +17 -0
- package/dist/components/messaging/MessageAttachment.svelte +273 -0
- package/dist/components/messaging/MessageAttachment.svelte.d.ts +13 -0
- package/dist/components/messaging/MessageAttachmentUpload.svelte +243 -0
- package/dist/components/messaging/MessageAttachmentUpload.svelte.d.ts +19 -0
- package/dist/components/messaging/MessageInput.svelte +452 -0
- package/dist/components/messaging/MessageInput.svelte.d.ts +28 -0
- package/dist/components/messaging/MessageItem.svelte +341 -0
- package/dist/components/messaging/MessageItem.svelte.d.ts +41 -0
- package/dist/components/messaging/MessageThread.svelte +306 -0
- package/dist/components/messaging/MessageThread.svelte.d.ts +30 -0
- package/dist/components/messaging/NotificationSettingsModal.svelte +234 -0
- package/dist/components/messaging/NotificationSettingsModal.svelte.d.ts +13 -0
- package/dist/components/messaging/QuotedMessageDisplay.svelte +123 -0
- package/dist/components/messaging/QuotedMessageDisplay.svelte.d.ts +10 -0
- package/dist/components/messaging/StartDMModal.svelte +100 -0
- package/dist/components/messaging/StartDMModal.svelte.d.ts +14 -0
- package/dist/components/messaging/ThreadPanel.svelte +153 -0
- package/dist/components/messaging/ThreadPanel.svelte.d.ts +16 -0
- package/dist/components/pipeline/DESIGN.md +361 -0
- package/dist/components/pipeline/Pipeline.svelte +391 -0
- package/dist/components/pipeline/Pipeline.svelte.d.ts +28 -0
- package/dist/components/pipeline/PipelineCard.svelte +94 -0
- package/dist/components/pipeline/PipelineCard.svelte.d.ts +13 -0
- package/dist/components/pipeline/PipelineColumn.svelte +163 -0
- package/dist/components/pipeline/PipelineColumn.svelte.d.ts +30 -0
- package/dist/components/replay/ChapterTimeline.svelte +326 -0
- package/dist/components/replay/ChapterTimeline.svelte.d.ts +39 -0
- package/dist/components/session-nav/transcriptModel.d.ts +132 -0
- package/dist/components/session-nav/transcriptModel.js +285 -0
- package/dist/data/emojis.d.ts +18 -0
- package/dist/data/emojis.js +1196 -0
- package/dist/index.d.ts +142 -0
- package/dist/index.js +142 -0
- package/dist/stores/confirmDialog.svelte.d.ts +21 -0
- package/dist/stores/confirmDialog.svelte.js +31 -0
- package/dist/stores/inputDialog.svelte.d.ts +24 -0
- package/dist/stores/inputDialog.svelte.js +32 -0
- package/dist/styles/rail.css +63 -0
- package/dist/types/annotation.d.ts +32 -0
- package/dist/types/annotation.js +3 -0
- package/dist/types/booking.d.ts +115 -0
- package/dist/types/booking.js +24 -0
- package/dist/types/comments.d.ts +65 -0
- package/dist/types/comments.js +22 -0
- package/dist/types/entityPreview.d.ts +42 -0
- package/dist/types/entityPreview.js +10 -0
- package/dist/types/filePicker.d.ts +15 -0
- package/dist/types/filePicker.js +1 -0
- package/dist/types/googleMaps.d.ts +51 -0
- package/dist/types/maps.d.ts +43 -0
- package/dist/types/maps.js +4 -0
- package/dist/types/messaging.d.ts +282 -0
- package/dist/types/messaging.js +100 -0
- package/dist/types/milestone.d.ts +20 -0
- package/dist/types/milestone.js +7 -0
- package/dist/types/pipeline.d.ts +115 -0
- package/dist/types/pipeline.js +15 -0
- package/dist/types/smartImageEditor.d.ts +42 -0
- package/dist/types/smartImageEditor.js +1 -0
- package/dist/types/templateVars.d.ts +34 -0
- package/dist/types/templateVars.js +12 -0
- package/dist/utils/currency.d.ts +8 -0
- package/dist/utils/currency.js +17 -0
- package/dist/utils/daisyuiColors.d.ts +37 -0
- package/dist/utils/daisyuiColors.js +152 -0
- package/dist/utils/dateFormatters.d.ts +69 -0
- package/dist/utils/dateFormatters.js +199 -0
- package/dist/utils/googleMapsLoader.d.ts +19 -0
- package/dist/utils/googleMapsLoader.js +121 -0
- package/dist/utils/mentionParser.d.ts +67 -0
- package/dist/utils/mentionParser.js +132 -0
- package/dist/utils/phone.d.ts +13 -0
- package/dist/utils/phone.js +67 -0
- package/dist/utils/phoneFormat.d.ts +26 -0
- package/dist/utils/phoneFormat.js +69 -0
- package/dist/utils/taskUtils.d.ts +37 -0
- package/dist/utils/taskUtils.js +71 -0
- 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;
|