@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,291 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
// Declared in the module script so `import type { DisplaySegment } from
|
|
3
|
+
// './InlineEdit.svelte'` resolves (instance-script exports are not module members).
|
|
4
|
+
/** A display segment for formula-aware rendering */
|
|
5
|
+
export interface DisplaySegment {
|
|
6
|
+
type: 'text' | 'formula';
|
|
7
|
+
display: string;
|
|
8
|
+
tooltip?: string;
|
|
9
|
+
}
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
/**
|
|
14
|
+
* InlineEdit Component
|
|
15
|
+
*
|
|
16
|
+
* A reusable inline editing component for text and textarea inputs.
|
|
17
|
+
* Shows display value when not editing, converts to input on click.
|
|
18
|
+
*
|
|
19
|
+
* Features:
|
|
20
|
+
* - Auto-saves on blur or Enter (Enter only for text type)
|
|
21
|
+
* - Cancels on Escape, reverting to original value
|
|
22
|
+
* - Hover state to indicate editability
|
|
23
|
+
* - Auto-focus when editing starts
|
|
24
|
+
* - Disabled state during saves
|
|
25
|
+
* - Optional formula-aware display: segments with type/display/tooltip
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
interface Props {
|
|
29
|
+
/** Current value */
|
|
30
|
+
value: string;
|
|
31
|
+
/** Callback when value is saved */
|
|
32
|
+
onSave: (newValue: string) => Promise<void> | void;
|
|
33
|
+
/** Input type: 'text' or 'textarea' */
|
|
34
|
+
type?: 'text' | 'textarea';
|
|
35
|
+
/** Placeholder text when empty */
|
|
36
|
+
placeholder?: string;
|
|
37
|
+
/** Disable editing */
|
|
38
|
+
disabled?: boolean;
|
|
39
|
+
/** Additional CSS classes for the container */
|
|
40
|
+
class?: string;
|
|
41
|
+
/** Number of rows for textarea (default: 3) */
|
|
42
|
+
rows?: number;
|
|
43
|
+
/** Show save/cancel buttons instead of auto-save on blur */
|
|
44
|
+
showButtons?: boolean;
|
|
45
|
+
/** Truncate long text with ellipsis (shows full text on hover) */
|
|
46
|
+
truncate?: boolean;
|
|
47
|
+
/** Optional display segments for formula-aware rendering */
|
|
48
|
+
displaySegments?: DisplaySegment[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let {
|
|
52
|
+
value,
|
|
53
|
+
onSave,
|
|
54
|
+
type = 'text',
|
|
55
|
+
placeholder = 'Click to edit...',
|
|
56
|
+
disabled = false,
|
|
57
|
+
class: className = '',
|
|
58
|
+
rows = 3,
|
|
59
|
+
showButtons = false,
|
|
60
|
+
truncate = false,
|
|
61
|
+
displaySegments = []
|
|
62
|
+
}: Props = $props();
|
|
63
|
+
|
|
64
|
+
// --- Formula rendering ---
|
|
65
|
+
let hasFormulas = $derived(displaySegments.length > 0);
|
|
66
|
+
|
|
67
|
+
// Internal state
|
|
68
|
+
let isEditing = $state(false);
|
|
69
|
+
// svelte-ignore state_referenced_locally
|
|
70
|
+
let editValue = $state(value);
|
|
71
|
+
let isSaving = $state(false);
|
|
72
|
+
let inputElement = $state<HTMLInputElement | HTMLTextAreaElement | null>(null);
|
|
73
|
+
|
|
74
|
+
// Sync editValue when value prop changes (only when not editing)
|
|
75
|
+
$effect(() => {
|
|
76
|
+
if (!isEditing) {
|
|
77
|
+
editValue = value;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Start editing
|
|
82
|
+
function startEditing() {
|
|
83
|
+
if (disabled || isSaving) return;
|
|
84
|
+
editValue = value;
|
|
85
|
+
isEditing = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Cancel editing
|
|
89
|
+
function cancelEditing() {
|
|
90
|
+
editValue = value;
|
|
91
|
+
isEditing = false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Save the edited value
|
|
95
|
+
async function saveValue() {
|
|
96
|
+
// Don't save if value hasn't changed
|
|
97
|
+
if (editValue === value) {
|
|
98
|
+
isEditing = false;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
isSaving = true;
|
|
103
|
+
try {
|
|
104
|
+
await onSave(editValue);
|
|
105
|
+
isEditing = false;
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.error('Failed to save:', err);
|
|
108
|
+
// Keep editing mode open on error
|
|
109
|
+
} finally {
|
|
110
|
+
isSaving = false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Handle keydown events
|
|
115
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
116
|
+
if (event.key === 'Escape') {
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
cancelEditing();
|
|
119
|
+
} else if (event.key === 'Enter') {
|
|
120
|
+
if (type === 'text' || event.metaKey || event.ctrlKey) {
|
|
121
|
+
event.preventDefault();
|
|
122
|
+
saveValue();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Handle blur (auto-save when not using buttons)
|
|
128
|
+
function handleBlur() {
|
|
129
|
+
if (!showButtons) {
|
|
130
|
+
saveValue();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Auto-focus action
|
|
135
|
+
function autofocus(node: HTMLElement) {
|
|
136
|
+
requestAnimationFrame(() => {
|
|
137
|
+
node.focus();
|
|
138
|
+
if (node instanceof HTMLInputElement) {
|
|
139
|
+
node.select();
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
import { fade } from 'svelte/transition';
|
|
145
|
+
|
|
146
|
+
// Auto-resize textarea to fit content
|
|
147
|
+
function autoresize(node: HTMLTextAreaElement) {
|
|
148
|
+
function resize() {
|
|
149
|
+
node.style.height = 'auto';
|
|
150
|
+
node.style.height = node.scrollHeight + 'px';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
requestAnimationFrame(resize);
|
|
154
|
+
|
|
155
|
+
node.addEventListener('input', resize);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
destroy() {
|
|
159
|
+
node.removeEventListener('input', resize);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
</script>
|
|
164
|
+
|
|
165
|
+
{#if isEditing}
|
|
166
|
+
<!-- Edit mode -->
|
|
167
|
+
<div class="inline-edit-container w-full {className}" in:fade={{ duration: 120 }}>
|
|
168
|
+
{#if type === 'textarea'}
|
|
169
|
+
<textarea
|
|
170
|
+
bind:this={inputElement}
|
|
171
|
+
bind:value={editValue}
|
|
172
|
+
{placeholder}
|
|
173
|
+
{rows}
|
|
174
|
+
class="textarea textarea-bordered w-full text-[0.9375rem] resize-none overflow-hidden"
|
|
175
|
+
style="min-height: {rows * 1.5}rem;"
|
|
176
|
+
disabled={isSaving}
|
|
177
|
+
onkeydown={handleKeyDown}
|
|
178
|
+
onblur={handleBlur}
|
|
179
|
+
use:autofocus
|
|
180
|
+
use:autoresize
|
|
181
|
+
></textarea>
|
|
182
|
+
{:else}
|
|
183
|
+
<input
|
|
184
|
+
bind:this={inputElement}
|
|
185
|
+
bind:value={editValue}
|
|
186
|
+
type="text"
|
|
187
|
+
{placeholder}
|
|
188
|
+
class="input input-bordered input-sm w-full"
|
|
189
|
+
disabled={isSaving}
|
|
190
|
+
onkeydown={handleKeyDown}
|
|
191
|
+
onblur={handleBlur}
|
|
192
|
+
use:autofocus
|
|
193
|
+
/>
|
|
194
|
+
{/if}
|
|
195
|
+
|
|
196
|
+
{#if showButtons}
|
|
197
|
+
<div class="flex gap-1 mt-1">
|
|
198
|
+
<button
|
|
199
|
+
class="btn btn-xs btn-success"
|
|
200
|
+
onclick={saveValue}
|
|
201
|
+
disabled={isSaving}
|
|
202
|
+
>
|
|
203
|
+
{#if isSaving}
|
|
204
|
+
<span class="loading loading-spinner loading-xs"></span>
|
|
205
|
+
{:else}
|
|
206
|
+
Save
|
|
207
|
+
{/if}
|
|
208
|
+
</button>
|
|
209
|
+
<button
|
|
210
|
+
class="btn btn-xs btn-ghost"
|
|
211
|
+
onclick={cancelEditing}
|
|
212
|
+
disabled={isSaving}
|
|
213
|
+
>
|
|
214
|
+
Cancel
|
|
215
|
+
</button>
|
|
216
|
+
</div>
|
|
217
|
+
{:else if isSaving}
|
|
218
|
+
<div class="absolute right-2 top-1/2 -translate-y-1/2">
|
|
219
|
+
<span class="loading loading-spinner loading-xs"></span>
|
|
220
|
+
</div>
|
|
221
|
+
{/if}
|
|
222
|
+
</div>
|
|
223
|
+
{:else}
|
|
224
|
+
<!-- Display mode -->
|
|
225
|
+
<button
|
|
226
|
+
class="inline-edit-display text-left w-full rounded px-2 py-1 transition-colors {disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer hover:bg-base-200'} {truncate ? 'inline-edit-truncate' : ''} {className}"
|
|
227
|
+
in:fade={{ duration: 120 }}
|
|
228
|
+
onclick={startEditing}
|
|
229
|
+
disabled={disabled}
|
|
230
|
+
type="button"
|
|
231
|
+
title={truncate && value ? value : undefined}
|
|
232
|
+
>
|
|
233
|
+
{#if value}
|
|
234
|
+
{#if hasFormulas}
|
|
235
|
+
<span class={type === 'textarea' ? 'whitespace-pre-wrap' : (truncate ? 'truncate-text' : '')}>
|
|
236
|
+
{#each displaySegments as seg}
|
|
237
|
+
{#if seg.type === 'formula'}
|
|
238
|
+
<span class="inline-fx-result" title={seg.tooltip}>{seg.display}</span>
|
|
239
|
+
{:else}
|
|
240
|
+
{seg.display}
|
|
241
|
+
{/if}
|
|
242
|
+
{/each}
|
|
243
|
+
</span>
|
|
244
|
+
{:else if type === 'textarea'}
|
|
245
|
+
<span class="whitespace-pre-wrap">{value}</span>
|
|
246
|
+
{:else}
|
|
247
|
+
<span class={truncate ? 'truncate-text' : ''}>{value}</span>
|
|
248
|
+
{/if}
|
|
249
|
+
{:else}
|
|
250
|
+
<span class="text-base-content/50 italic">{placeholder}</span>
|
|
251
|
+
{/if}
|
|
252
|
+
</button>
|
|
253
|
+
{/if}
|
|
254
|
+
|
|
255
|
+
<style>
|
|
256
|
+
.inline-edit-container {
|
|
257
|
+
position: relative;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.inline-edit-display {
|
|
261
|
+
min-height: 2rem;
|
|
262
|
+
display: flex;
|
|
263
|
+
align-items: center;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/* Truncation styles */
|
|
267
|
+
.inline-edit-truncate {
|
|
268
|
+
max-width: 100%;
|
|
269
|
+
overflow: hidden;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.truncate-text {
|
|
273
|
+
display: block;
|
|
274
|
+
white-space: nowrap;
|
|
275
|
+
overflow: hidden;
|
|
276
|
+
text-overflow: ellipsis;
|
|
277
|
+
max-width: 100%;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.inline-fx-result {
|
|
281
|
+
display: inline;
|
|
282
|
+
padding: 0 4px;
|
|
283
|
+
border-radius: 3px;
|
|
284
|
+
background: oklch(0.25 0.06 80 / 0.4);
|
|
285
|
+
border: 1px solid oklch(0.35 0.08 80 / 0.3);
|
|
286
|
+
color: oklch(0.82 0.10 80);
|
|
287
|
+
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
288
|
+
font-size: 0.85em;
|
|
289
|
+
cursor: help;
|
|
290
|
+
}
|
|
291
|
+
</style>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** A display segment for formula-aware rendering */
|
|
2
|
+
export interface DisplaySegment {
|
|
3
|
+
type: 'text' | 'formula';
|
|
4
|
+
display: string;
|
|
5
|
+
tooltip?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* InlineEdit Component
|
|
9
|
+
*
|
|
10
|
+
* A reusable inline editing component for text and textarea inputs.
|
|
11
|
+
* Shows display value when not editing, converts to input on click.
|
|
12
|
+
*
|
|
13
|
+
* Features:
|
|
14
|
+
* - Auto-saves on blur or Enter (Enter only for text type)
|
|
15
|
+
* - Cancels on Escape, reverting to original value
|
|
16
|
+
* - Hover state to indicate editability
|
|
17
|
+
* - Auto-focus when editing starts
|
|
18
|
+
* - Disabled state during saves
|
|
19
|
+
* - Optional formula-aware display: segments with type/display/tooltip
|
|
20
|
+
*/
|
|
21
|
+
interface Props {
|
|
22
|
+
/** Current value */
|
|
23
|
+
value: string;
|
|
24
|
+
/** Callback when value is saved */
|
|
25
|
+
onSave: (newValue: string) => Promise<void> | void;
|
|
26
|
+
/** Input type: 'text' or 'textarea' */
|
|
27
|
+
type?: 'text' | 'textarea';
|
|
28
|
+
/** Placeholder text when empty */
|
|
29
|
+
placeholder?: string;
|
|
30
|
+
/** Disable editing */
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
/** Additional CSS classes for the container */
|
|
33
|
+
class?: string;
|
|
34
|
+
/** Number of rows for textarea (default: 3) */
|
|
35
|
+
rows?: number;
|
|
36
|
+
/** Show save/cancel buttons instead of auto-save on blur */
|
|
37
|
+
showButtons?: boolean;
|
|
38
|
+
/** Truncate long text with ellipsis (shows full text on hover) */
|
|
39
|
+
truncate?: boolean;
|
|
40
|
+
/** Optional display segments for formula-aware rendering */
|
|
41
|
+
displaySegments?: DisplaySegment[];
|
|
42
|
+
}
|
|
43
|
+
declare const InlineEdit: import("svelte").Component<Props, {}, "">;
|
|
44
|
+
type InlineEdit = ReturnType<typeof InlineEdit>;
|
|
45
|
+
export default InlineEdit;
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { fly, fade } from 'svelte/transition';
|
|
3
|
+
import { cubicOut } from 'svelte/easing';
|
|
4
|
+
import { tick } from 'svelte';
|
|
5
|
+
import { getPendingInput, submitInput, cancelInput } from '../stores/inputDialog.svelte';
|
|
6
|
+
|
|
7
|
+
const pending = $derived(getPendingInput());
|
|
8
|
+
const opts = $derived(pending?.options ?? null);
|
|
9
|
+
const visible = $derived(pending !== null);
|
|
10
|
+
|
|
11
|
+
let value = $state('');
|
|
12
|
+
let error = $state<string | null>(null);
|
|
13
|
+
let inputRef = $state<HTMLInputElement | null>(null);
|
|
14
|
+
|
|
15
|
+
$effect(() => {
|
|
16
|
+
if (visible && opts) {
|
|
17
|
+
value = '';
|
|
18
|
+
error = null;
|
|
19
|
+
tick().then(() => inputRef?.focus());
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function handleSubmit() {
|
|
24
|
+
if (opts?.validate) {
|
|
25
|
+
const msg = opts.validate(value);
|
|
26
|
+
if (msg !== null) { error = msg; return; }
|
|
27
|
+
}
|
|
28
|
+
error = null;
|
|
29
|
+
submitInput(value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
33
|
+
if (!visible) return;
|
|
34
|
+
if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); cancelInput(); }
|
|
35
|
+
if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); handleSubmit(); }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
$effect(() => {
|
|
39
|
+
window.addEventListener('keydown', handleKeydown, true);
|
|
40
|
+
return () => window.removeEventListener('keydown', handleKeydown, true);
|
|
41
|
+
});
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
{#if visible && opts}
|
|
45
|
+
<!-- Scrim (above ConfirmDialog z-9990/9991) -->
|
|
46
|
+
<div
|
|
47
|
+
class="id-scrim"
|
|
48
|
+
transition:fade={{ duration: 120 }}
|
|
49
|
+
role="presentation"
|
|
50
|
+
onclick={cancelInput}
|
|
51
|
+
></div>
|
|
52
|
+
|
|
53
|
+
<!-- Dialog -->
|
|
54
|
+
<div
|
|
55
|
+
class="id-wrap"
|
|
56
|
+
role="dialog"
|
|
57
|
+
aria-modal="true"
|
|
58
|
+
aria-label="{opts.title}. Press Enter to confirm or Escape to cancel."
|
|
59
|
+
in:fly={{ y: -20, duration: 200, easing: cubicOut }}
|
|
60
|
+
out:fade={{ duration: 120 }}
|
|
61
|
+
>
|
|
62
|
+
<div class="id-box">
|
|
63
|
+
<!-- Icon + title -->
|
|
64
|
+
<div class="id-header">
|
|
65
|
+
<div class="id-icon">
|
|
66
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
67
|
+
<path d="M12 20h9"/>
|
|
68
|
+
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
|
|
69
|
+
</svg>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="id-title-group">
|
|
72
|
+
<h3 class="id-title">{opts.title}</h3>
|
|
73
|
+
{#if opts.body}
|
|
74
|
+
<p class="id-body">{opts.body}</p>
|
|
75
|
+
{/if}
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<!-- Input field -->
|
|
80
|
+
<input
|
|
81
|
+
bind:this={inputRef}
|
|
82
|
+
bind:value
|
|
83
|
+
oninput={() => { if (error) error = null; }}
|
|
84
|
+
type="text"
|
|
85
|
+
class="id-input"
|
|
86
|
+
class:id-input-error={error !== null}
|
|
87
|
+
placeholder={opts.placeholder ?? ''}
|
|
88
|
+
maxlength={opts.maxLength}
|
|
89
|
+
autocomplete="off"
|
|
90
|
+
spellcheck="false"
|
|
91
|
+
/>
|
|
92
|
+
{#if error !== null || opts.maxLength}
|
|
93
|
+
<div class="id-field-footer">
|
|
94
|
+
{#if error !== null}
|
|
95
|
+
<p class="id-error">{error}</p>
|
|
96
|
+
{:else}
|
|
97
|
+
<span></span>
|
|
98
|
+
{/if}
|
|
99
|
+
{#if opts.maxLength}
|
|
100
|
+
<span class="id-char-count">{value.length}/{opts.maxLength}</span>
|
|
101
|
+
{/if}
|
|
102
|
+
</div>
|
|
103
|
+
{/if}
|
|
104
|
+
|
|
105
|
+
<!-- Actions -->
|
|
106
|
+
<div class="id-actions">
|
|
107
|
+
<button
|
|
108
|
+
type="button"
|
|
109
|
+
class="id-btn id-btn-cancel"
|
|
110
|
+
onclick={cancelInput}
|
|
111
|
+
>
|
|
112
|
+
<kbd class="id-kbd">Esc</kbd>
|
|
113
|
+
<span>{opts.cancelLabel ?? 'Cancel'}</span>
|
|
114
|
+
</button>
|
|
115
|
+
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
class="id-btn id-btn-confirm"
|
|
119
|
+
onclick={handleSubmit}
|
|
120
|
+
>
|
|
121
|
+
<kbd class="id-kbd">⏎</kbd>
|
|
122
|
+
<span>{opts.confirmLabel ?? 'OK'}</span>
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
{/if}
|
|
128
|
+
|
|
129
|
+
<style>
|
|
130
|
+
.id-scrim {
|
|
131
|
+
position: fixed;
|
|
132
|
+
inset: 0;
|
|
133
|
+
background: oklch(0 0 0 / 0.45);
|
|
134
|
+
z-index: 9991;
|
|
135
|
+
backdrop-filter: blur(2px);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.id-wrap {
|
|
139
|
+
position: fixed;
|
|
140
|
+
top: 5rem;
|
|
141
|
+
left: 50%;
|
|
142
|
+
transform: translateX(-50%);
|
|
143
|
+
z-index: 9992;
|
|
144
|
+
pointer-events: auto;
|
|
145
|
+
width: max-content;
|
|
146
|
+
max-width: min(92vw, 460px);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.id-box {
|
|
150
|
+
display: flex;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
gap: 0.625rem;
|
|
153
|
+
padding: 1rem 1.125rem 0.625rem;
|
|
154
|
+
background: oklch(0.17 0.02 250 / 0.98);
|
|
155
|
+
border: 1px solid oklch(0.32 0.03 250);
|
|
156
|
+
border-radius: 0.875rem;
|
|
157
|
+
box-shadow:
|
|
158
|
+
0 20px 60px oklch(0 0 0 / 0.55),
|
|
159
|
+
0 0 0 1px oklch(0.28 0.03 250 / 0.5);
|
|
160
|
+
backdrop-filter: blur(16px);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.id-header {
|
|
164
|
+
display: flex;
|
|
165
|
+
align-items: flex-start;
|
|
166
|
+
gap: 0.875rem;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.id-icon {
|
|
170
|
+
flex-shrink: 0;
|
|
171
|
+
width: 2.25rem;
|
|
172
|
+
height: 2.25rem;
|
|
173
|
+
border-radius: 0.5rem;
|
|
174
|
+
display: flex;
|
|
175
|
+
align-items: center;
|
|
176
|
+
justify-content: center;
|
|
177
|
+
background: oklch(0.30 0.06 200 / 0.35);
|
|
178
|
+
border: 1px solid oklch(0.45 0.10 200 / 0.4);
|
|
179
|
+
color: oklch(0.75 0.12 200);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.id-icon svg {
|
|
183
|
+
width: 1.125rem;
|
|
184
|
+
height: 1.125rem;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.id-title-group {
|
|
188
|
+
display: flex;
|
|
189
|
+
flex-direction: column;
|
|
190
|
+
gap: 0.25rem;
|
|
191
|
+
flex: 1;
|
|
192
|
+
min-width: 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.id-title {
|
|
196
|
+
margin: 0;
|
|
197
|
+
font-size: 0.9375rem;
|
|
198
|
+
font-weight: 600;
|
|
199
|
+
color: oklch(0.92 0.04 250);
|
|
200
|
+
line-height: 1.3;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.id-body {
|
|
204
|
+
margin: 0;
|
|
205
|
+
font-size: 0.8rem;
|
|
206
|
+
color: oklch(0.60 0.03 250);
|
|
207
|
+
line-height: 1.5;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.id-input {
|
|
211
|
+
width: 100%;
|
|
212
|
+
padding: 0.4375rem 0.75rem;
|
|
213
|
+
font-size: 0.875rem;
|
|
214
|
+
background: oklch(0.13 0.02 250);
|
|
215
|
+
border: 1px solid oklch(0.32 0.03 250);
|
|
216
|
+
border-radius: 0.5rem;
|
|
217
|
+
color: oklch(0.92 0.04 250);
|
|
218
|
+
outline: none;
|
|
219
|
+
transition: border-color 0.1s ease;
|
|
220
|
+
box-sizing: border-box;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.id-input::placeholder {
|
|
224
|
+
color: oklch(0.45 0.03 250);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.id-input:focus {
|
|
228
|
+
border-color: oklch(0.55 0.12 250);
|
|
229
|
+
box-shadow: 0 0 0 2px oklch(0.55 0.12 250 / 0.2);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.id-input-error,
|
|
233
|
+
.id-input-error:focus {
|
|
234
|
+
border-color: oklch(0.55 0.20 25);
|
|
235
|
+
box-shadow: 0 0 0 2px oklch(0.55 0.20 25 / 0.2);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.id-field-footer {
|
|
239
|
+
display: flex;
|
|
240
|
+
align-items: center;
|
|
241
|
+
justify-content: space-between;
|
|
242
|
+
gap: 0.5rem;
|
|
243
|
+
margin-top: -0.125rem;
|
|
244
|
+
min-height: 1rem;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.id-error {
|
|
248
|
+
margin: 0;
|
|
249
|
+
font-size: 0.75rem;
|
|
250
|
+
color: oklch(0.65 0.18 25);
|
|
251
|
+
line-height: 1.4;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.id-char-count {
|
|
255
|
+
margin-left: auto;
|
|
256
|
+
font-size: 0.75rem;
|
|
257
|
+
color: oklch(0.50 0.03 250);
|
|
258
|
+
font-variant-numeric: tabular-nums;
|
|
259
|
+
white-space: nowrap;
|
|
260
|
+
flex-shrink: 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.id-actions {
|
|
264
|
+
display: flex;
|
|
265
|
+
align-items: center;
|
|
266
|
+
gap: 0.375rem;
|
|
267
|
+
padding-top: 0.125rem;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.id-btn {
|
|
271
|
+
display: inline-flex;
|
|
272
|
+
align-items: center;
|
|
273
|
+
gap: 0.375rem;
|
|
274
|
+
padding: 0.3125rem 0.75rem;
|
|
275
|
+
font-size: 0.8rem;
|
|
276
|
+
font-weight: 500;
|
|
277
|
+
border-radius: 0.5rem;
|
|
278
|
+
border: 1px solid transparent;
|
|
279
|
+
background: transparent;
|
|
280
|
+
color: oklch(0.80 0.04 250);
|
|
281
|
+
cursor: pointer;
|
|
282
|
+
transition: background 0.1s ease, border-color 0.1s ease, color 0.1s ease;
|
|
283
|
+
white-space: nowrap;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.id-btn:hover {
|
|
287
|
+
background: oklch(0.24 0.03 250);
|
|
288
|
+
border-color: oklch(0.32 0.04 250);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.id-btn-cancel:focus-visible {
|
|
292
|
+
outline: 2px solid oklch(0.60 0.10 250);
|
|
293
|
+
outline-offset: 2px;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.id-btn-confirm {
|
|
297
|
+
background: oklch(0.28 0.04 250 / 0.6);
|
|
298
|
+
border-color: oklch(0.40 0.05 250);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.id-btn-confirm:hover {
|
|
302
|
+
background: oklch(0.34 0.06 250);
|
|
303
|
+
border-color: oklch(0.50 0.08 250);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.id-btn-confirm:focus-visible {
|
|
307
|
+
outline: 2px solid oklch(0.60 0.10 250);
|
|
308
|
+
outline-offset: 2px;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.id-kbd {
|
|
312
|
+
font-family: ui-monospace, monospace;
|
|
313
|
+
font-size: 0.65rem;
|
|
314
|
+
padding: 0.0625rem 0.3125rem;
|
|
315
|
+
background: oklch(0.22 0.02 250);
|
|
316
|
+
border: 1px solid oklch(0.30 0.03 250);
|
|
317
|
+
border-bottom-width: 2px;
|
|
318
|
+
border-radius: 0.25rem;
|
|
319
|
+
color: oklch(0.75 0.04 250);
|
|
320
|
+
line-height: 1.6;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
@media (prefers-reduced-motion: reduce) {
|
|
324
|
+
.id-input { transition: none; }
|
|
325
|
+
.id-btn { transition: none; }
|
|
326
|
+
}
|
|
327
|
+
</style>
|