@workerdeck/ui 0.23.0 → 1.1.0
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/build/{SessionPanel-DUt2VzXG.mjs → SessionPanel-I5QSLLb0.mjs} +2266 -3459
- package/build/SessionPanel-I5QSLLb0.mjs.map +1 -0
- package/build/SessionPanel-yNO60Tzw.d.mts +151 -0
- package/build/format-C_lpFq0l.d.mts +16 -0
- package/build/format.d.mts +8 -76
- package/build/format.mjs +2 -2
- package/build/index.d.mts +291 -1695
- package/build/index.mjs +6 -301
- package/build/index.mjs.map +1 -1
- package/build/scoped.css +32 -23
- package/build/{status-BE-zg88x.mjs → status-Dm9INVTt.mjs} +17 -88
- package/build/status-Dm9INVTt.mjs.map +1 -0
- package/build/workspace.d.mts +8 -187
- package/build/workspace.mjs +13 -144
- package/build/workspace.mjs.map +1 -1
- package/package.json +24 -24
- package/src/components/agent/CodeEditor.tsx +31 -103
- package/src/components/agent/Composer.tsx +114 -333
- package/src/components/agent/ContextDialog.tsx +20 -41
- package/src/components/agent/ContextRing.tsx +6 -28
- package/src/components/agent/Conversation.tsx +21 -33
- package/src/components/agent/EditorTabs.tsx +39 -67
- package/src/components/agent/EngineIcon.tsx +29 -72
- package/src/components/agent/EntryIcon.tsx +16 -0
- package/src/components/agent/FileCard.tsx +10 -16
- package/src/components/agent/FileTree.tsx +52 -125
- package/src/components/agent/FileViewer.tsx +30 -60
- package/src/components/agent/HostFilesDialog.tsx +40 -69
- package/src/components/agent/Loader.tsx +3 -24
- package/src/components/agent/McpDialog.tsx +64 -137
- package/src/components/agent/Message.tsx +2 -21
- package/src/components/agent/ModelSelect.tsx +33 -62
- package/src/components/agent/PermissionModeSelect.tsx +21 -79
- package/src/components/agent/PermissionPrompt.tsx +59 -63
- package/src/components/agent/ProjectIcon.tsx +228 -93
- package/src/components/agent/PromptTokenText.tsx +10 -16
- package/src/components/agent/QuestionPrompt.tsx +43 -64
- package/src/components/agent/Reasoning.tsx +13 -16
- package/src/components/agent/Response.tsx +3 -9
- package/src/components/agent/Scrubber.tsx +31 -80
- package/src/components/agent/SessionBrowser.tsx +64 -243
- package/src/components/agent/SessionEmptyState.tsx +10 -40
- package/src/components/agent/SessionInfoDialog.tsx +46 -78
- package/src/components/agent/SessionItem.tsx +53 -320
- package/src/components/agent/SessionList.tsx +23 -39
- package/src/components/agent/SessionPanel.tsx +315 -996
- package/src/components/agent/SessionStatusIcon.tsx +14 -28
- package/src/components/agent/SessionSteps.tsx +34 -187
- package/src/components/agent/SessionWorkspace.tsx +104 -219
- package/src/components/agent/SkillsDialog.tsx +35 -80
- package/src/components/agent/StatusBar.tsx +51 -136
- package/src/components/agent/SubagentStrip.tsx +12 -77
- package/src/components/agent/ToolCallCard.tsx +94 -135
- package/src/components/agent/Transcript.tsx +103 -1288
- package/src/components/agent/TranscriptItemView.tsx +155 -0
- package/src/components/agent/TranscriptRows.tsx +418 -0
- package/src/components/agent/UsageDialog.tsx +7 -29
- package/src/components/agent/UsageMeters.tsx +18 -73
- package/src/components/agent/pulse.tsx +9 -32
- package/src/components/agent/scrubber-marks.ts +33 -103
- package/src/components/agent/status.ts +1 -5
- package/src/components/agent/tool-result-fetch.tsx +3 -25
- package/src/components/agent/tool-result-image.tsx +32 -111
- package/src/components/agent/transcript-rows.ts +41 -118
- package/src/components/agent/transcript-variant.tsx +3 -80
- package/src/components/agent/use-height-epoch.ts +10 -24
- package/src/components/agent/use-path-links.ts +45 -63
- package/src/components/agent/use-subagent-frame.ts +106 -0
- package/src/components/agent/use-transcript-jumps.ts +49 -107
- package/src/components/prompt-area/animated-placeholder.tsx +6 -5
- package/src/components/prompt-area/clipboard-helpers.ts +26 -21
- package/src/components/prompt-area/cursor-helpers.ts +79 -37
- package/src/components/prompt-area/dom-helpers.ts +97 -69
- package/src/components/prompt-area/file-strip.tsx +90 -80
- package/src/components/prompt-area/html-to-markdown.ts +67 -46
- package/src/components/prompt-area/image-strip.tsx +8 -18
- package/src/components/prompt-area/index.ts +2 -15
- package/src/components/prompt-area/prompt-area-engine.ts +100 -115
- package/src/components/prompt-area/prompt-area-list-ops.ts +66 -59
- package/src/components/prompt-area/prompt-area.tsx +31 -38
- package/src/components/prompt-area/remove-button.tsx +3 -9
- package/src/components/prompt-area/segment-helpers.ts +4 -12
- package/src/components/prompt-area/trigger-popover.tsx +15 -27
- package/src/components/prompt-area/trigger-presets.ts +0 -10
- package/src/components/prompt-area/types.ts +3 -8
- package/src/components/prompt-area/use-chip-editing.ts +305 -0
- package/src/components/prompt-area/use-markdown-mode.ts +7 -17
- package/src/components/prompt-area/use-prompt-area-events.ts +80 -86
- package/src/components/prompt-area/use-prompt-area-keydown.ts +598 -0
- package/src/components/prompt-area/use-prompt-area-state.ts +5 -23
- package/src/components/prompt-area/use-prompt-area.ts +170 -896
- package/src/components/prompt-area/use-trigger-search.ts +21 -19
- package/src/components/terminal/PermissionPrompt.tsx +56 -59
- package/src/components/terminal/QuestionPrompt.tsx +28 -79
- package/src/components/terminal/StatusLine.tsx +13 -63
- package/src/components/terminal/TerminalTranscript.tsx +44 -160
- package/src/components/terminal/affordances.tsx +68 -98
- package/src/components/terminal/blocks.ts +20 -156
- package/src/components/terminal/diff.tsx +19 -67
- package/src/components/terminal/height.ts +163 -313
- package/src/components/terminal/image-box.ts +0 -44
- package/src/components/terminal/items.tsx +150 -305
- package/src/components/terminal/markdown.tsx +36 -95
- package/src/components/terminal/press.tsx +26 -53
- package/src/components/terminal/prompt.tsx +40 -170
- package/src/components/terminal/result-preview.ts +8 -56
- package/src/components/terminal/row.tsx +11 -98
- package/src/components/terminal/scrubber.tsx +108 -401
- package/src/components/terminal/surface.tsx +6 -54
- package/src/components/terminal/todos.ts +66 -0
- package/src/components/terminal/tool-run.ts +21 -192
- package/src/components/ui/AlertDialog.tsx +8 -23
- package/src/components/ui/Badge.tsx +18 -29
- package/src/components/ui/Button.tsx +7 -15
- package/src/components/ui/Card.tsx +2 -13
- package/src/components/ui/CodeBlock.tsx +10 -37
- package/src/components/ui/CopyButton.tsx +10 -23
- package/src/components/ui/Dialog.tsx +23 -36
- package/src/components/ui/Empty.tsx +6 -29
- package/src/components/ui/Input.tsx +14 -16
- package/src/components/ui/Menu.tsx +6 -18
- package/src/components/ui/PortalScope.tsx +1 -21
- package/src/components/ui/ProgressRing.tsx +8 -19
- package/src/components/ui/Select.tsx +18 -31
- package/src/components/ui/Sonner.tsx +1 -2
- package/src/components/ui/Spinner.tsx +1 -1
- package/src/components/ui/Splitter.tsx +30 -64
- package/src/components/ui/Textarea.tsx +2 -5
- package/src/components/ui/Tooltip.tsx +3 -13
- package/src/format.ts +0 -9
- package/src/index.ts +16 -113
- package/src/lib/clipboard.ts +8 -28
- package/src/lib/css.ts +8 -0
- package/src/lib/format.ts +101 -86
- package/src/lib/plan-request.ts +16 -0
- package/src/lib/status.ts +45 -95
- package/src/lib/tool-icon.ts +39 -47
- package/src/lib/utils.ts +1 -3
- package/src/styles/terminal.css +81 -49
- package/src/styles/theme.css +96 -32
- package/src/workspace.ts +1 -24
- package/build/SessionPanel-BobynUo4.d.mts +0 -669
- package/build/SessionPanel-DUt2VzXG.mjs.map +0 -1
- package/build/format-ljc3lKpA.d.mts +0 -59
- package/build/status-BE-zg88x.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useCallback, useRef } from 'react'
|
|
3
|
+
import { useCallback, useMemo, useRef } from 'react'
|
|
4
4
|
import type { Segment, ChipSegment, TriggerConfig } from './types.ts'
|
|
5
5
|
import { resolveTriggersInSegments } from './prompt-area-engine.ts'
|
|
6
6
|
import { normalizeEditorDOM, safeJsonStringify, getSelectionRange } from './dom-helpers.ts'
|
|
@@ -11,15 +11,7 @@ import {
|
|
|
11
11
|
insertSegmentsAtCursor,
|
|
12
12
|
} from './clipboard-helpers.ts'
|
|
13
13
|
import { htmlToMarkdown } from './html-to-markdown.ts'
|
|
14
|
-
import {
|
|
15
|
-
normalizeListPrefixText,
|
|
16
|
-
renumberOrderedListLines,
|
|
17
|
-
hasOrderedListRun,
|
|
18
|
-
} from './prompt-area-list-ops.ts'
|
|
19
|
-
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
// Types
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
14
|
+
import { normalizeListPrefixText, renumberOrderedListLines, hasOrderedListRun } from './prompt-area-list-ops.ts'
|
|
23
15
|
|
|
24
16
|
type EventHandlerDeps = {
|
|
25
17
|
editorRef: React.RefObject<HTMLDivElement | null>
|
|
@@ -56,10 +48,6 @@ type PromptAreaEventHandlers = {
|
|
|
56
48
|
isComposing: React.RefObject<boolean>
|
|
57
49
|
}
|
|
58
50
|
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
// Undo/Redo Stack
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
|
|
63
51
|
const MAX_UNDO_HISTORY = 100
|
|
64
52
|
|
|
65
53
|
/** Delay before dismissing trigger on blur, so popover clicks register first */
|
|
@@ -70,10 +58,6 @@ type UndoState = {
|
|
|
70
58
|
redoStack: Segment[][]
|
|
71
59
|
}
|
|
72
60
|
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
74
|
-
// Hook
|
|
75
|
-
// ---------------------------------------------------------------------------
|
|
76
|
-
|
|
77
61
|
/**
|
|
78
62
|
* Encapsulates all edge-case event handlers for the prompt area component:
|
|
79
63
|
* paste, copy, cut, drag/drop, IME composition, blur, and undo/redo.
|
|
@@ -99,14 +83,12 @@ export function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHand
|
|
|
99
83
|
|
|
100
84
|
const isComposing = useRef(false)
|
|
101
85
|
|
|
102
|
-
// -----------------------------------------------------------------------
|
|
103
86
|
// Undo/redo stack (MAX_UNDO_HISTORY entries; clears redo on new push).
|
|
104
87
|
// Invariants: stacks live in refs (not useState) so pushUndo /
|
|
105
88
|
// resetUndoHistory / handleKeyDownForUndoRedo keep stable identity.
|
|
106
89
|
// Destabilizing this would re-create handleInput / handleKeyDown /
|
|
107
90
|
// imperative handle on every render and silently regress IME + debounced
|
|
108
91
|
// undo in the parent hook.
|
|
109
|
-
// -----------------------------------------------------------------------
|
|
110
92
|
const undoState = useRef<UndoState>({ undoStack: [], redoStack: [] })
|
|
111
93
|
|
|
112
94
|
const pushUndo = useCallback((segments: Segment[]) => {
|
|
@@ -115,7 +97,6 @@ export function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHand
|
|
|
115
97
|
if (state.undoStack.length > MAX_UNDO_HISTORY) {
|
|
116
98
|
state.undoStack.shift()
|
|
117
99
|
}
|
|
118
|
-
// Clear redo stack on new change
|
|
119
100
|
state.redoStack = []
|
|
120
101
|
}, [])
|
|
121
102
|
|
|
@@ -123,22 +104,23 @@ export function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHand
|
|
|
123
104
|
undoState.current = { undoStack: [], redoStack: [] }
|
|
124
105
|
}, [])
|
|
125
106
|
|
|
126
|
-
// -----------------------------------------------------------------------
|
|
127
107
|
// Paste: strip HTML, insert plain text only
|
|
128
|
-
// -----------------------------------------------------------------------
|
|
129
108
|
|
|
130
109
|
const handlePaste = useCallback(
|
|
131
110
|
(e: React.ClipboardEvent<HTMLDivElement>) => {
|
|
132
111
|
// Let a consumer take over the paste entirely (e.g. divert large text or
|
|
133
112
|
// arbitrary files to an upload pipeline) by calling preventDefault().
|
|
134
113
|
onRawPaste?.(e)
|
|
135
|
-
if (e.defaultPrevented)
|
|
114
|
+
if (e.defaultPrevented) {
|
|
115
|
+
return
|
|
116
|
+
}
|
|
136
117
|
e.preventDefault()
|
|
137
118
|
|
|
138
119
|
const editor = editorRef.current
|
|
139
|
-
if (!editor)
|
|
120
|
+
if (!editor) {
|
|
121
|
+
return
|
|
122
|
+
}
|
|
140
123
|
|
|
141
|
-
// Check for image files in clipboard before processing text
|
|
142
124
|
// Some browsers/OSes provide pasted images via `items` instead of `files` (e.g. screenshots)
|
|
143
125
|
const imageFile =
|
|
144
126
|
Array.from(e.clipboardData.files).find((f) => f.type.startsWith('image/')) ??
|
|
@@ -151,28 +133,25 @@ export function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHand
|
|
|
151
133
|
return
|
|
152
134
|
}
|
|
153
135
|
|
|
154
|
-
// Record undo snapshot
|
|
155
136
|
const currentSegments = readSegmentsFromDOM()
|
|
156
137
|
pushUndo(currentSegments)
|
|
157
138
|
|
|
158
|
-
// Check for internal segment data (copy/paste within the editor)
|
|
159
139
|
const segmentJson = e.clipboardData.getData('text/prompt-area-segments')
|
|
160
140
|
if (segmentJson) {
|
|
161
141
|
const parsed = parseSegmentsFromClipboard(segmentJson)
|
|
162
142
|
if (parsed && parsed.length > 0) {
|
|
163
|
-
// Insert the copied segments at cursor position
|
|
164
143
|
const range = getSelectionRange()
|
|
165
|
-
if (!range)
|
|
144
|
+
if (!range) {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
166
147
|
|
|
167
148
|
range.deleteContents()
|
|
168
149
|
|
|
169
|
-
// Merge pasted segments into current segments at cursor position
|
|
170
150
|
const beforePaste = readSegmentsFromDOM()
|
|
171
151
|
const merged = insertSegmentsAtCursor(beforePaste, parsed, editor)
|
|
172
152
|
onChange(merged)
|
|
173
153
|
renderSegmentsToDOM(merged)
|
|
174
154
|
|
|
175
|
-
// Notify: internal paste with chip data preserved
|
|
176
155
|
onPasteCallback?.({ segments: merged, source: 'internal' })
|
|
177
156
|
for (const seg of parsed) {
|
|
178
157
|
if (seg.type === 'chip') {
|
|
@@ -212,8 +191,12 @@ export function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHand
|
|
|
212
191
|
}
|
|
213
192
|
}
|
|
214
193
|
}
|
|
215
|
-
if (!text)
|
|
216
|
-
|
|
194
|
+
if (!text) {
|
|
195
|
+
text = e.clipboardData.getData('text/plain')
|
|
196
|
+
}
|
|
197
|
+
if (!text) {
|
|
198
|
+
return
|
|
199
|
+
}
|
|
217
200
|
|
|
218
201
|
// Normalize pasted list markers ("- " → "•") so pasted bullets match
|
|
219
202
|
// typed input. Applies to both the HTML→markdown and plain-text paths.
|
|
@@ -230,9 +213,10 @@ export function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHand
|
|
|
230
213
|
text = renumberOrderedListLines(text).text
|
|
231
214
|
}
|
|
232
215
|
|
|
233
|
-
// Insert plain text at cursor position using Selection API
|
|
234
216
|
const range = getSelectionRange()
|
|
235
|
-
if (!range)
|
|
217
|
+
if (!range) {
|
|
218
|
+
return
|
|
219
|
+
}
|
|
236
220
|
|
|
237
221
|
range.deleteContents()
|
|
238
222
|
|
|
@@ -251,13 +235,11 @@ export function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHand
|
|
|
251
235
|
|
|
252
236
|
range.insertNode(fragment)
|
|
253
237
|
|
|
254
|
-
// Move cursor to end of pasted content
|
|
255
238
|
range.collapse(false)
|
|
256
239
|
const sel = window.getSelection()
|
|
257
240
|
sel?.removeAllRanges()
|
|
258
241
|
sel?.addRange(range)
|
|
259
242
|
|
|
260
|
-
// Normalize DOM, sync model, detect triggers
|
|
261
243
|
normalizeEditorDOM(editor)
|
|
262
244
|
const newSegments = readSegmentsFromDOM()
|
|
263
245
|
|
|
@@ -268,16 +250,11 @@ export function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHand
|
|
|
268
250
|
onChange(resolvedSegments)
|
|
269
251
|
renderSegmentsToDOM(resolvedSegments)
|
|
270
252
|
|
|
271
|
-
// Notify about auto-resolved chips from pasted text
|
|
272
253
|
for (const seg of resolvedSegments) {
|
|
273
254
|
if (
|
|
274
255
|
seg.type === 'chip' &&
|
|
275
256
|
!newSegments.some(
|
|
276
|
-
(s) =>
|
|
277
|
-
s.type === 'chip' &&
|
|
278
|
-
s.trigger === seg.trigger &&
|
|
279
|
-
s.value === seg.value &&
|
|
280
|
-
s.displayText === seg.displayText,
|
|
257
|
+
(s) => s.type === 'chip' && s.trigger === seg.trigger && s.value === seg.value && s.displayText === seg.displayText,
|
|
281
258
|
)
|
|
282
259
|
) {
|
|
283
260
|
onChipAdd?.(seg)
|
|
@@ -307,23 +284,21 @@ export function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHand
|
|
|
307
284
|
],
|
|
308
285
|
)
|
|
309
286
|
|
|
310
|
-
// -----------------------------------------------------------------------
|
|
311
287
|
// Copy: serialize chips into plain text
|
|
312
|
-
// -----------------------------------------------------------------------
|
|
313
288
|
|
|
314
289
|
const handleCopy = useCallback((e: React.ClipboardEvent<HTMLDivElement>) => {
|
|
315
290
|
e.preventDefault()
|
|
316
291
|
|
|
317
292
|
const range = getSelectionRange()
|
|
318
|
-
if (!range)
|
|
293
|
+
if (!range) {
|
|
294
|
+
return
|
|
295
|
+
}
|
|
319
296
|
|
|
320
297
|
const fragment = range.cloneContents()
|
|
321
298
|
|
|
322
|
-
// Walk fragment and serialize, converting chips to their text representation
|
|
323
299
|
const plainText = serializeFragmentToPlainText(fragment)
|
|
324
300
|
e.clipboardData.setData('text/plain', plainText)
|
|
325
301
|
|
|
326
|
-
// Also serialize chip segments as JSON for internal paste
|
|
327
302
|
const fragmentSegments = serializeFragmentToSegments(fragment)
|
|
328
303
|
const hasChips = fragmentSegments.some((s) => s.type === 'chip')
|
|
329
304
|
if (hasChips) {
|
|
@@ -334,18 +309,16 @@ export function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHand
|
|
|
334
309
|
}
|
|
335
310
|
}, [])
|
|
336
311
|
|
|
337
|
-
// -----------------------------------------------------------------------
|
|
338
312
|
// Cut: copy + delete
|
|
339
|
-
// -----------------------------------------------------------------------
|
|
340
313
|
|
|
341
314
|
const handleCut = useCallback(
|
|
342
315
|
(e: React.ClipboardEvent<HTMLDivElement>) => {
|
|
343
|
-
// First, do the copy
|
|
344
316
|
handleCopy(e)
|
|
345
317
|
|
|
346
|
-
// Then delete the selection
|
|
347
318
|
const range = getSelectionRange()
|
|
348
|
-
if (!range)
|
|
319
|
+
if (!range) {
|
|
320
|
+
return
|
|
321
|
+
}
|
|
349
322
|
|
|
350
323
|
const currentSegments = readSegmentsFromDOM()
|
|
351
324
|
pushUndo(currentSegments)
|
|
@@ -364,9 +337,7 @@ export function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHand
|
|
|
364
337
|
[handleCopy, editorRef, readSegmentsFromDOM, onChange, pushUndo, runTriggerDetection],
|
|
365
338
|
)
|
|
366
339
|
|
|
367
|
-
// -----------------------------------------------------------------------
|
|
368
340
|
// Drag & Drop: prevent to avoid unpredictable DOM mutations
|
|
369
|
-
// -----------------------------------------------------------------------
|
|
370
341
|
|
|
371
342
|
const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
|
372
343
|
e.preventDefault()
|
|
@@ -376,9 +347,7 @@ export function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHand
|
|
|
376
347
|
e.preventDefault()
|
|
377
348
|
}, [])
|
|
378
349
|
|
|
379
|
-
// -----------------------------------------------------------------------
|
|
380
350
|
// IME Composition: track state, defer trigger detection
|
|
381
|
-
// -----------------------------------------------------------------------
|
|
382
351
|
|
|
383
352
|
const handleCompositionStart = useCallback(() => {
|
|
384
353
|
isComposing.current = true
|
|
@@ -386,46 +355,50 @@ export function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHand
|
|
|
386
355
|
|
|
387
356
|
const handleCompositionEnd = useCallback(() => {
|
|
388
357
|
isComposing.current = false
|
|
389
|
-
// Run trigger detection after composition ends
|
|
390
358
|
runTriggerDetection()
|
|
391
359
|
}, [runTriggerDetection])
|
|
392
360
|
|
|
393
|
-
// -----------------------------------------------------------------------
|
|
394
361
|
// Blur: dismiss trigger dropdown with delay (so popover clicks work)
|
|
395
|
-
// -----------------------------------------------------------------------
|
|
396
362
|
|
|
397
363
|
const handleBlur = useCallback(() => {
|
|
398
364
|
setTimeout(() => {
|
|
399
365
|
const editor = editorRef.current
|
|
400
|
-
if (!editor)
|
|
366
|
+
if (!editor) {
|
|
367
|
+
return
|
|
368
|
+
}
|
|
401
369
|
|
|
402
370
|
// Only dismiss if focus didn't move to an element within the editor container
|
|
403
371
|
const activeEl = document.activeElement
|
|
404
|
-
if (activeEl && editor.parentElement?.contains(activeEl))
|
|
372
|
+
if (activeEl && editor.parentElement?.contains(activeEl)) {
|
|
373
|
+
return
|
|
374
|
+
}
|
|
405
375
|
|
|
406
376
|
dismissTrigger()
|
|
407
377
|
}, BLUR_DELAY_MS)
|
|
408
378
|
}, [editorRef, dismissTrigger])
|
|
409
379
|
|
|
410
|
-
// -----------------------------------------------------------------------
|
|
411
380
|
// Undo/Redo: intercept Ctrl+Z / Ctrl+Shift+Z
|
|
412
|
-
// -----------------------------------------------------------------------
|
|
413
381
|
|
|
414
382
|
const handleKeyDownForUndoRedo = useCallback(
|
|
415
383
|
(e: React.KeyboardEvent<HTMLDivElement>): boolean => {
|
|
416
384
|
const isMeta = e.metaKey || e.ctrlKey
|
|
417
385
|
|
|
418
|
-
if (!isMeta || e.key !== 'z')
|
|
386
|
+
if (!isMeta || e.key !== 'z') {
|
|
387
|
+
return false
|
|
388
|
+
}
|
|
419
389
|
|
|
420
390
|
e.preventDefault()
|
|
421
391
|
const state = undoState.current
|
|
422
392
|
|
|
423
393
|
if (e.shiftKey) {
|
|
424
|
-
|
|
425
|
-
|
|
394
|
+
if (state.redoStack.length === 0) {
|
|
395
|
+
return true
|
|
396
|
+
}
|
|
426
397
|
|
|
427
398
|
const segments = state.redoStack.pop()
|
|
428
|
-
if (!segments)
|
|
399
|
+
if (!segments) {
|
|
400
|
+
return true
|
|
401
|
+
}
|
|
429
402
|
|
|
430
403
|
const current = readSegmentsFromDOM()
|
|
431
404
|
state.undoStack.push(current)
|
|
@@ -434,11 +407,14 @@ export function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHand
|
|
|
434
407
|
renderSegmentsToDOM(segments)
|
|
435
408
|
onRedo?.(segments)
|
|
436
409
|
} else {
|
|
437
|
-
|
|
438
|
-
|
|
410
|
+
if (state.undoStack.length === 0) {
|
|
411
|
+
return true
|
|
412
|
+
}
|
|
439
413
|
|
|
440
414
|
const segments = state.undoStack.pop()
|
|
441
|
-
if (!segments)
|
|
415
|
+
if (!segments) {
|
|
416
|
+
return true
|
|
417
|
+
}
|
|
442
418
|
|
|
443
419
|
const current = readSegmentsFromDOM()
|
|
444
420
|
state.redoStack.push(current)
|
|
@@ -453,18 +429,36 @@ export function usePromptAreaEvents(deps: EventHandlerDeps): PromptAreaEventHand
|
|
|
453
429
|
[readSegmentsFromDOM, onChange, renderSegmentsToDOM, onUndo, onRedo],
|
|
454
430
|
)
|
|
455
431
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
432
|
+
// Memoized so the container is only as unstable as its members: the parent
|
|
433
|
+
// hook keys handleInput and the imperative handle on this object, and a fresh
|
|
434
|
+
// literal per render would re-create both on every render.
|
|
435
|
+
return useMemo(
|
|
436
|
+
() => ({
|
|
437
|
+
handlePaste,
|
|
438
|
+
handleCopy,
|
|
439
|
+
handleCut,
|
|
440
|
+
handleDrop,
|
|
441
|
+
handleDragOver,
|
|
442
|
+
handleCompositionStart,
|
|
443
|
+
handleCompositionEnd,
|
|
444
|
+
handleBlur,
|
|
445
|
+
handleKeyDownForUndoRedo,
|
|
446
|
+
pushUndo,
|
|
447
|
+
resetUndoHistory,
|
|
448
|
+
isComposing,
|
|
449
|
+
}),
|
|
450
|
+
[
|
|
451
|
+
handlePaste,
|
|
452
|
+
handleCopy,
|
|
453
|
+
handleCut,
|
|
454
|
+
handleDrop,
|
|
455
|
+
handleDragOver,
|
|
456
|
+
handleCompositionStart,
|
|
457
|
+
handleCompositionEnd,
|
|
458
|
+
handleBlur,
|
|
459
|
+
handleKeyDownForUndoRedo,
|
|
460
|
+
pushUndo,
|
|
461
|
+
resetUndoHistory,
|
|
462
|
+
],
|
|
463
|
+
)
|
|
470
464
|
}
|