@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.
Files changed (145) hide show
  1. package/build/{SessionPanel-DUt2VzXG.mjs → SessionPanel-I5QSLLb0.mjs} +2266 -3459
  2. package/build/SessionPanel-I5QSLLb0.mjs.map +1 -0
  3. package/build/SessionPanel-yNO60Tzw.d.mts +151 -0
  4. package/build/format-C_lpFq0l.d.mts +16 -0
  5. package/build/format.d.mts +8 -76
  6. package/build/format.mjs +2 -2
  7. package/build/index.d.mts +291 -1695
  8. package/build/index.mjs +6 -301
  9. package/build/index.mjs.map +1 -1
  10. package/build/scoped.css +32 -23
  11. package/build/{status-BE-zg88x.mjs → status-Dm9INVTt.mjs} +17 -88
  12. package/build/status-Dm9INVTt.mjs.map +1 -0
  13. package/build/workspace.d.mts +8 -187
  14. package/build/workspace.mjs +13 -144
  15. package/build/workspace.mjs.map +1 -1
  16. package/package.json +24 -24
  17. package/src/components/agent/CodeEditor.tsx +31 -103
  18. package/src/components/agent/Composer.tsx +114 -333
  19. package/src/components/agent/ContextDialog.tsx +20 -41
  20. package/src/components/agent/ContextRing.tsx +6 -28
  21. package/src/components/agent/Conversation.tsx +21 -33
  22. package/src/components/agent/EditorTabs.tsx +39 -67
  23. package/src/components/agent/EngineIcon.tsx +29 -72
  24. package/src/components/agent/EntryIcon.tsx +16 -0
  25. package/src/components/agent/FileCard.tsx +10 -16
  26. package/src/components/agent/FileTree.tsx +52 -125
  27. package/src/components/agent/FileViewer.tsx +30 -60
  28. package/src/components/agent/HostFilesDialog.tsx +40 -69
  29. package/src/components/agent/Loader.tsx +3 -24
  30. package/src/components/agent/McpDialog.tsx +64 -137
  31. package/src/components/agent/Message.tsx +2 -21
  32. package/src/components/agent/ModelSelect.tsx +33 -62
  33. package/src/components/agent/PermissionModeSelect.tsx +21 -79
  34. package/src/components/agent/PermissionPrompt.tsx +59 -63
  35. package/src/components/agent/ProjectIcon.tsx +228 -93
  36. package/src/components/agent/PromptTokenText.tsx +10 -16
  37. package/src/components/agent/QuestionPrompt.tsx +43 -64
  38. package/src/components/agent/Reasoning.tsx +13 -16
  39. package/src/components/agent/Response.tsx +3 -9
  40. package/src/components/agent/Scrubber.tsx +31 -80
  41. package/src/components/agent/SessionBrowser.tsx +64 -243
  42. package/src/components/agent/SessionEmptyState.tsx +10 -40
  43. package/src/components/agent/SessionInfoDialog.tsx +46 -78
  44. package/src/components/agent/SessionItem.tsx +53 -320
  45. package/src/components/agent/SessionList.tsx +23 -39
  46. package/src/components/agent/SessionPanel.tsx +315 -996
  47. package/src/components/agent/SessionStatusIcon.tsx +14 -28
  48. package/src/components/agent/SessionSteps.tsx +34 -187
  49. package/src/components/agent/SessionWorkspace.tsx +104 -219
  50. package/src/components/agent/SkillsDialog.tsx +35 -80
  51. package/src/components/agent/StatusBar.tsx +51 -136
  52. package/src/components/agent/SubagentStrip.tsx +12 -77
  53. package/src/components/agent/ToolCallCard.tsx +94 -135
  54. package/src/components/agent/Transcript.tsx +103 -1288
  55. package/src/components/agent/TranscriptItemView.tsx +155 -0
  56. package/src/components/agent/TranscriptRows.tsx +418 -0
  57. package/src/components/agent/UsageDialog.tsx +7 -29
  58. package/src/components/agent/UsageMeters.tsx +18 -73
  59. package/src/components/agent/pulse.tsx +9 -32
  60. package/src/components/agent/scrubber-marks.ts +33 -103
  61. package/src/components/agent/status.ts +1 -5
  62. package/src/components/agent/tool-result-fetch.tsx +3 -25
  63. package/src/components/agent/tool-result-image.tsx +32 -111
  64. package/src/components/agent/transcript-rows.ts +41 -118
  65. package/src/components/agent/transcript-variant.tsx +3 -80
  66. package/src/components/agent/use-height-epoch.ts +10 -24
  67. package/src/components/agent/use-path-links.ts +45 -63
  68. package/src/components/agent/use-subagent-frame.ts +106 -0
  69. package/src/components/agent/use-transcript-jumps.ts +49 -107
  70. package/src/components/prompt-area/animated-placeholder.tsx +6 -5
  71. package/src/components/prompt-area/clipboard-helpers.ts +26 -21
  72. package/src/components/prompt-area/cursor-helpers.ts +79 -37
  73. package/src/components/prompt-area/dom-helpers.ts +97 -69
  74. package/src/components/prompt-area/file-strip.tsx +90 -80
  75. package/src/components/prompt-area/html-to-markdown.ts +67 -46
  76. package/src/components/prompt-area/image-strip.tsx +8 -18
  77. package/src/components/prompt-area/index.ts +2 -15
  78. package/src/components/prompt-area/prompt-area-engine.ts +100 -115
  79. package/src/components/prompt-area/prompt-area-list-ops.ts +66 -59
  80. package/src/components/prompt-area/prompt-area.tsx +31 -38
  81. package/src/components/prompt-area/remove-button.tsx +3 -9
  82. package/src/components/prompt-area/segment-helpers.ts +4 -12
  83. package/src/components/prompt-area/trigger-popover.tsx +15 -27
  84. package/src/components/prompt-area/trigger-presets.ts +0 -10
  85. package/src/components/prompt-area/types.ts +3 -8
  86. package/src/components/prompt-area/use-chip-editing.ts +305 -0
  87. package/src/components/prompt-area/use-markdown-mode.ts +7 -17
  88. package/src/components/prompt-area/use-prompt-area-events.ts +80 -86
  89. package/src/components/prompt-area/use-prompt-area-keydown.ts +598 -0
  90. package/src/components/prompt-area/use-prompt-area-state.ts +5 -23
  91. package/src/components/prompt-area/use-prompt-area.ts +170 -896
  92. package/src/components/prompt-area/use-trigger-search.ts +21 -19
  93. package/src/components/terminal/PermissionPrompt.tsx +56 -59
  94. package/src/components/terminal/QuestionPrompt.tsx +28 -79
  95. package/src/components/terminal/StatusLine.tsx +13 -63
  96. package/src/components/terminal/TerminalTranscript.tsx +44 -160
  97. package/src/components/terminal/affordances.tsx +68 -98
  98. package/src/components/terminal/blocks.ts +20 -156
  99. package/src/components/terminal/diff.tsx +19 -67
  100. package/src/components/terminal/height.ts +163 -313
  101. package/src/components/terminal/image-box.ts +0 -44
  102. package/src/components/terminal/items.tsx +150 -305
  103. package/src/components/terminal/markdown.tsx +36 -95
  104. package/src/components/terminal/press.tsx +26 -53
  105. package/src/components/terminal/prompt.tsx +40 -170
  106. package/src/components/terminal/result-preview.ts +8 -56
  107. package/src/components/terminal/row.tsx +11 -98
  108. package/src/components/terminal/scrubber.tsx +108 -401
  109. package/src/components/terminal/surface.tsx +6 -54
  110. package/src/components/terminal/todos.ts +66 -0
  111. package/src/components/terminal/tool-run.ts +21 -192
  112. package/src/components/ui/AlertDialog.tsx +8 -23
  113. package/src/components/ui/Badge.tsx +18 -29
  114. package/src/components/ui/Button.tsx +7 -15
  115. package/src/components/ui/Card.tsx +2 -13
  116. package/src/components/ui/CodeBlock.tsx +10 -37
  117. package/src/components/ui/CopyButton.tsx +10 -23
  118. package/src/components/ui/Dialog.tsx +23 -36
  119. package/src/components/ui/Empty.tsx +6 -29
  120. package/src/components/ui/Input.tsx +14 -16
  121. package/src/components/ui/Menu.tsx +6 -18
  122. package/src/components/ui/PortalScope.tsx +1 -21
  123. package/src/components/ui/ProgressRing.tsx +8 -19
  124. package/src/components/ui/Select.tsx +18 -31
  125. package/src/components/ui/Sonner.tsx +1 -2
  126. package/src/components/ui/Spinner.tsx +1 -1
  127. package/src/components/ui/Splitter.tsx +30 -64
  128. package/src/components/ui/Textarea.tsx +2 -5
  129. package/src/components/ui/Tooltip.tsx +3 -13
  130. package/src/format.ts +0 -9
  131. package/src/index.ts +16 -113
  132. package/src/lib/clipboard.ts +8 -28
  133. package/src/lib/css.ts +8 -0
  134. package/src/lib/format.ts +101 -86
  135. package/src/lib/plan-request.ts +16 -0
  136. package/src/lib/status.ts +45 -95
  137. package/src/lib/tool-icon.ts +39 -47
  138. package/src/lib/utils.ts +1 -3
  139. package/src/styles/terminal.css +81 -49
  140. package/src/styles/theme.css +96 -32
  141. package/src/workspace.ts +1 -24
  142. package/build/SessionPanel-BobynUo4.d.mts +0 -669
  143. package/build/SessionPanel-DUt2VzXG.mjs.map +0 -1
  144. package/build/format-ljc3lKpA.d.mts +0 -59
  145. 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) return
114
+ if (e.defaultPrevented) {
115
+ return
116
+ }
136
117
  e.preventDefault()
137
118
 
138
119
  const editor = editorRef.current
139
- if (!editor) return
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) return
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) text = e.clipboardData.getData('text/plain')
216
- if (!text) return
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) return
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) return
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) return
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) return
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)) return
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') return false
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
- // Redo: Ctrl+Shift+Z
425
- if (state.redoStack.length === 0) return true
394
+ if (state.redoStack.length === 0) {
395
+ return true
396
+ }
426
397
 
427
398
  const segments = state.redoStack.pop()
428
- if (!segments) return true
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
- // Undo: Ctrl+Z
438
- if (state.undoStack.length === 0) return true
410
+ if (state.undoStack.length === 0) {
411
+ return true
412
+ }
439
413
 
440
414
  const segments = state.undoStack.pop()
441
- if (!segments) return true
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
- return {
457
- handlePaste,
458
- handleCopy,
459
- handleCut,
460
- handleDrop,
461
- handleDragOver,
462
- handleCompositionStart,
463
- handleCompositionEnd,
464
- handleBlur,
465
- handleKeyDownForUndoRedo,
466
- pushUndo,
467
- resetUndoHistory,
468
- isComposing,
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
  }