@@ -104,7 +104,7 @@ let copyWorkspace: WorkspaceSvg | null = null;
104104let copyCoords : Coordinate | null = null ;
105105
106106/**
107- * Determine if a focusable node can be copied using cut or copy .
107+ * Determine if a focusable node can be copied.
108108 *
109109 * Unfortunately the ICopyable interface doesn't include an isCopyable
110110 * method, so we must use some other criteria to make the decision.
@@ -113,8 +113,8 @@ let copyCoords: Coordinate | null = null;
113113 * - It must be an ICopyable.
114114 * - So that a pasted copy can be manipluated and/or disposed of, it
115115 * must be both an IDraggable and an IDeletable.
116- * - Additionally, both .isMovable () and .isDeletable () must return
117- * 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).
118118 *
119119 * TODO(#9098): Revise these criteria. The latter criteria prevents
120120 * shadow blocks from being copied; additionally, there are likely to
@@ -127,6 +127,40 @@ let copyCoords: Coordinate | null = null;
127127function isCopyable (
128128 focused : IFocusableNode ,
129129) : 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 ;
130164 return (
131165 isICopyable ( focused ) &&
132166 isIDeletable ( focused ) &&
@@ -151,29 +185,45 @@ export function registerCopy() {
151185 name : names . COPY ,
152186 preconditionFn ( workspace , scope ) {
153187 const focused = scope . focusedNode ;
188+ if ( ! ( focused instanceof BlockSvg ) ) return false ;
189+
190+ const targetWorkspace = workspace . isFlyout
191+ ? workspace . targetWorkspace
192+ : workspace ;
154193 return (
155- ! workspace . isReadOnly ( ) &&
156- ! Gesture . inProgress ( ) &&
157194 ! ! focused &&
158- isCopyable ( focused ) &&
159- ! getFocusManager ( ) . ephemeralFocusTaken ( )
195+ ! ! targetWorkspace &&
196+ ! targetWorkspace . isReadOnly ( ) &&
197+ ! targetWorkspace . isDragging ( ) &&
198+ ! getFocusManager ( ) . ephemeralFocusTaken ( ) &&
199+ isCopyable ( focused )
160200 ) ;
161201 } ,
162202 callback ( workspace , e , shortcut , scope ) {
163203 // Prevent the default copy behavior, which may beep or otherwise indicate
164204 // an error due to the lack of a selection.
165205 e . preventDefault ( ) ;
166- workspace . hideChaff ( ) ;
206+
167207 const focused = scope . focusedNode ;
168- if ( ! focused || ! isICopyable ( focused ) ) return false ;
169- copyData = focused . toCopyData ( ) ;
170- copyWorkspace =
208+ if ( ! focused || ! isCopyable ( focused ) ) return false ;
209+ let targetWorkspace : WorkspaceSvg | null =
171210 focused . workspace instanceof WorkspaceSvg
172211 ? focused . workspace
173212 : workspace ;
174- copyCoords = isDraggable ( focused )
175- ? focused . getRelativeToSurfaceXY ( )
176- : 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 ;
177227 return ! ! copyData ;
178228 } ,
179229 keyCodes : [ ctrlC , metaC ] ,
@@ -197,14 +247,11 @@ export function registerCut() {
197247 preconditionFn ( workspace , scope ) {
198248 const focused = scope . focusedNode ;
199249 return (
200- ! workspace . isReadOnly ( ) &&
201- ! Gesture . inProgress ( ) &&
202250 ! ! focused &&
203- isCopyable ( focused ) &&
204- // Extra criteria for cut (not just copy):
205- ! focused . workspace . isFlyout &&
206- focused . isDeletable ( ) &&
207- ! getFocusManager ( ) . ephemeralFocusTaken ( )
251+ ! workspace . isReadOnly ( ) &&
252+ ! workspace . isDragging ( ) &&
253+ ! getFocusManager ( ) . ephemeralFocusTaken ( ) &&
254+ isCuttable ( focused )
208255 ) ;
209256 } ,
210257 callback ( workspace , e , shortcut , scope ) {
@@ -251,9 +298,14 @@ export function registerPaste() {
251298 const pasteShortcut : KeyboardShortcut = {
252299 name : names . PASTE ,
253300 preconditionFn ( workspace ) {
301+ const targetWorkspace = workspace . isFlyout
302+ ? workspace . targetWorkspace
303+ : workspace ;
254304 return (
255- ! workspace . isReadOnly ( ) &&
256- ! Gesture . inProgress ( ) &&
305+ ! ! copyData &&
306+ ! ! targetWorkspace &&
307+ ! targetWorkspace . isReadOnly ( ) &&
308+ ! targetWorkspace . isDragging ( ) &&
257309 ! getFocusManager ( ) . ephemeralFocusTaken ( )
258310 ) ;
259311 } ,
0 commit comments