99import { BlockSvg } from './block_svg.js' ;
1010import * as clipboard from './clipboard.js' ;
1111import * as eventUtils from './events/utils.js' ;
12+ import { getFocusManager } from './focus_manager.js' ;
1213import { Gesture } from './gesture.js' ;
1314import {
1415 ICopyable ,
@@ -72,7 +73,9 @@ export function registerDelete() {
7273 focused != null &&
7374 isIDeletable ( focused ) &&
7475 focused . isDeletable ( ) &&
75- ! Gesture . inProgress ( )
76+ ! Gesture . inProgress ( ) &&
77+ // Don't delete the block if a field editor is open
78+ ! getFocusManager ( ) . ephemeralFocusTaken ( )
7679 ) ;
7780 } ,
7881 callback ( workspace , e , shortcut , scope ) {
@@ -101,7 +104,7 @@ let copyWorkspace: WorkspaceSvg | null = null;
101104let copyCoords : Coordinate | null = null ;
102105
103106/**
104- * Determine if a focusable node can be copied using cut or copy .
107+ * Determine if a focusable node can be copied.
105108 *
106109 * Unfortunately the ICopyable interface doesn't include an isCopyable
107110 * method, so we must use some other criteria to make the decision.
@@ -110,8 +113,8 @@ let copyCoords: Coordinate | null = null;
110113 * - It must be an ICopyable.
111114 * - So that a pasted copy can be manipluated and/or disposed of, it
112115 * must be both an IDraggable and an IDeletable.
113- * - Additionally, both .isMovable () and .isDeletable () must return
114- * true (i.e., can currently be moved and deleted).
116+ * - Additionally, both .isOwnMovable () and .isOwnDeletable () must return
117+ * true (i.e., the copy could be moved and deleted).
115118 *
116119 * TODO(#9098): Revise these criteria. The latter criteria prevents
117120 * shadow blocks from being copied; additionally, there are likely to
@@ -124,6 +127,40 @@ let copyCoords: Coordinate | null = null;
124127function isCopyable (
125128 focused : IFocusableNode ,
126129) : focused is ICopyable < ICopyData > & IDeletable & IDraggable {
130+ if ( ! ( focused instanceof BlockSvg ) ) return false ;
131+ return (
132+ isICopyable ( focused ) &&
133+ isIDeletable ( focused ) &&
134+ focused . isOwnDeletable ( ) &&
135+ isDraggable ( focused ) &&
136+ focused . isOwnMovable ( )
137+ ) ;
138+ }
139+
140+ /**
141+ * Determine if a focusable node can be cut.
142+ *
143+ * Unfortunately the ICopyable interface doesn't include an isCuttable
144+ * method, so we must use some other criteria to make the decision.
145+ * Specifically,
146+ *
147+ * - It must be an ICopyable.
148+ * - So that a pasted copy can be manipluated and/or disposed of, it
149+ * must be both an IDraggable and an IDeletable.
150+ * - Additionally, both .isMovable() and .isDeletable() must return
151+ * true (i.e., can currently be moved and deleted). This is the main
152+ * difference with isCopyable.
153+ *
154+ * TODO(#9098): Revise these criteria. The latter criteria prevents
155+ * shadow blocks from being copied; additionally, there are likely to
156+ * be other circumstances were it is desirable to allow movable /
157+ * copyable copies of a currently-unmovable / -copyable block to be
158+ * made.
159+ *
160+ * @param focused The focused object.
161+ */
162+ function isCuttable ( focused : IFocusableNode ) : boolean {
163+ if ( ! ( focused instanceof BlockSvg ) ) return false ;
127164 return (
128165 isICopyable ( focused ) &&
129166 isIDeletable ( focused ) &&
@@ -148,28 +185,45 @@ export function registerCopy() {
148185 name : names . COPY ,
149186 preconditionFn ( workspace , scope ) {
150187 const focused = scope . focusedNode ;
188+ if ( ! ( focused instanceof BlockSvg ) ) return false ;
189+
190+ const targetWorkspace = workspace . isFlyout
191+ ? workspace . targetWorkspace
192+ : workspace ;
151193 return (
152- ! workspace . isReadOnly ( ) &&
153- ! Gesture . inProgress ( ) &&
154194 ! ! focused &&
195+ ! ! targetWorkspace &&
196+ ! targetWorkspace . isReadOnly ( ) &&
197+ ! targetWorkspace . isDragging ( ) &&
198+ ! getFocusManager ( ) . ephemeralFocusTaken ( ) &&
155199 isCopyable ( focused )
156200 ) ;
157201 } ,
158202 callback ( workspace , e , shortcut , scope ) {
159203 // Prevent the default copy behavior, which may beep or otherwise indicate
160204 // an error due to the lack of a selection.
161205 e . preventDefault ( ) ;
162- workspace . hideChaff ( ) ;
206+
163207 const focused = scope . focusedNode ;
164- if ( ! focused || ! isICopyable ( focused ) ) return false ;
165- copyData = focused . toCopyData ( ) ;
166- copyWorkspace =
208+ if ( ! focused || ! isCopyable ( focused ) ) return false ;
209+ let targetWorkspace : WorkspaceSvg | null =
167210 focused . workspace instanceof WorkspaceSvg
168211 ? focused . workspace
169212 : workspace ;
170- copyCoords = isDraggable ( focused )
171- ? focused . getRelativeToSurfaceXY ( )
172- : null ;
213+ targetWorkspace = targetWorkspace . isFlyout
214+ ? targetWorkspace . targetWorkspace
215+ : targetWorkspace ;
216+ if ( ! targetWorkspace ) return false ;
217+
218+ if ( ! focused . workspace . isFlyout ) {
219+ targetWorkspace . hideChaff ( ) ;
220+ }
221+ copyData = focused . toCopyData ( ) ;
222+ copyWorkspace = targetWorkspace ;
223+ copyCoords =
224+ isDraggable ( focused ) && focused . workspace == targetWorkspace
225+ ? focused . getRelativeToSurfaceXY ( )
226+ : null ;
173227 return ! ! copyData ;
174228 } ,
175229 keyCodes : [ ctrlC , metaC ] ,
@@ -193,13 +247,11 @@ export function registerCut() {
193247 preconditionFn ( workspace , scope ) {
194248 const focused = scope . focusedNode ;
195249 return (
196- ! workspace . isReadOnly ( ) &&
197- ! Gesture . inProgress ( ) &&
198250 ! ! focused &&
199- isCopyable ( focused ) &&
200- // Extra criteria for cut (not just copy):
201- ! focused . workspace . isFlyout &&
202- focused . isDeletable ( )
251+ ! workspace . isReadOnly ( ) &&
252+ ! workspace . isDragging ( ) &&
253+ ! getFocusManager ( ) . ephemeralFocusTaken ( ) &&
254+ isCuttable ( focused )
203255 ) ;
204256 } ,
205257 callback ( workspace , e , shortcut , scope ) {
@@ -246,7 +298,16 @@ export function registerPaste() {
246298 const pasteShortcut : KeyboardShortcut = {
247299 name : names . PASTE ,
248300 preconditionFn ( workspace ) {
249- return ! workspace . isReadOnly ( ) && ! Gesture . inProgress ( ) ;
301+ const targetWorkspace = workspace . isFlyout
302+ ? workspace . targetWorkspace
303+ : workspace ;
304+ return (
305+ ! ! copyData &&
306+ ! ! targetWorkspace &&
307+ ! targetWorkspace . isReadOnly ( ) &&
308+ ! targetWorkspace . isDragging ( ) &&
309+ ! getFocusManager ( ) . ephemeralFocusTaken ( )
310+ ) ;
250311 } ,
251312 callback ( workspace : WorkspaceSvg , e : Event ) {
252313 if ( ! copyData || ! copyWorkspace ) return false ;
@@ -305,7 +366,11 @@ export function registerUndo() {
305366 const undoShortcut : KeyboardShortcut = {
306367 name : names . UNDO ,
307368 preconditionFn ( workspace ) {
308- return ! workspace . isReadOnly ( ) && ! Gesture . inProgress ( ) ;
369+ return (
370+ ! workspace . isReadOnly ( ) &&
371+ ! Gesture . inProgress ( ) &&
372+ ! getFocusManager ( ) . ephemeralFocusTaken ( )
373+ ) ;
309374 } ,
310375 callback ( workspace , e ) {
311376 // 'z' for undo 'Z' is for redo.
@@ -340,7 +405,11 @@ export function registerRedo() {
340405 const redoShortcut : KeyboardShortcut = {
341406 name : names . REDO ,
342407 preconditionFn ( workspace ) {
343- return ! Gesture . inProgress ( ) && ! workspace . isReadOnly ( ) ;
408+ return (
409+ ! Gesture . inProgress ( ) &&
410+ ! workspace . isReadOnly ( ) &&
411+ ! getFocusManager ( ) . ephemeralFocusTaken ( )
412+ ) ;
344413 } ,
345414 callback ( workspace , e ) {
346415 // 'z' for undo 'Z' is for redo.
0 commit comments