@@ -90,12 +90,11 @@ export class ActionMenu {
9090 * @param workspace The workspace.
9191 */
9292 private openActionMenu ( workspace : WorkspaceSvg ) : boolean {
93- let menuOptions : Array <
94- | ContextMenuRegistry . ContextMenuOption
95- | ContextMenuRegistry . LegacyContextMenuOption
96- > = [ ] ;
9793 let rtl : boolean ;
9894
95+ // TODO: Pass this through the precondition and callback instead of making it up.
96+ const menuOpenEvent = new KeyboardEvent ( 'keydown' ) ;
97+
9998 const cursor = workspace . getCursor ( ) ;
10099 if ( ! cursor ) throw new Error ( 'workspace has no cursor' ) ;
101100 const node = cursor . getCurNode ( ) ;
@@ -104,33 +103,31 @@ export class ActionMenu {
104103 switch ( nodeType ) {
105104 case ASTNode . types . BLOCK : {
106105 const block = node . getLocation ( ) as BlockSvg ;
107- rtl = block . RTL ;
108- // Reimplement BlockSvg.prototype.generateContextMenu as that
109- // method is protected.
110- if ( ! workspace . options . readOnly && block . contextMenu ) {
111- menuOptions = ContextMenuRegistry . registry . getContextMenuOptions (
112- ContextMenuRegistry . ScopeType . BLOCK ,
113- { block} ,
114- ) ;
115-
116- // Allow the block to add or modify menuOptions.
117- block . customContextMenu ?.( menuOptions ) ;
118- }
119- // End reimplement.
106+ block . showContextMenu ( menuOpenEvent ) ;
120107 break ;
121108 }
122109
123110 // case Blockly.ASTNode.types.INPUT:
124111 case ASTNode . types . NEXT :
125112 case ASTNode . types . PREVIOUS :
126113 case ASTNode . types . INPUT : {
127- const connection = node . getLocation ( ) as Connection ;
114+ const connection = node . getLocation ( ) as RenderedConnection ;
128115 rtl = connection . getSourceBlock ( ) . RTL ;
129116
130117 // Slightly hacky: get insert action from registry. Hacky
131118 // because registry typings don't include {connection: ...} as
132119 // a possible kind of scope.
133- this . addConnectionItems ( connection , menuOptions ) ;
120+ const menuOptions = this . addConnectionItems ( connection , menuOpenEvent ) ;
121+ // If no valid options, don't show a menu
122+ if ( ! menuOptions ?. length ) return true ;
123+ const location = this . calculateLocationForConnectionMenu ( connection ) ;
124+ ContextMenu . show ( menuOpenEvent , menuOptions , rtl , workspace , location ) ;
125+ break ;
126+ }
127+
128+ case ASTNode . types . WORKSPACE : {
129+ const workspace = node . getLocation ( ) as WorkspaceSvg ;
130+ workspace . showContextMenu ( menuOpenEvent ) ;
134131 break ;
135132 }
136133
@@ -139,9 +136,6 @@ export class ActionMenu {
139136 return false ;
140137 }
141138
142- if ( ! menuOptions ?. length ) return true ;
143- const fakeEvent = this . fakeEventForNode ( node ) ;
144- ContextMenu . show ( fakeEvent , menuOptions , rtl , workspace ) ;
145139 setTimeout ( ( ) => {
146140 WidgetDiv . getDiv ( )
147141 ?. querySelector ( '.blocklyMenu' )
@@ -163,15 +157,13 @@ export class ActionMenu {
163157 * Add menu items for a context menu on a connection scope.
164158 *
165159 * @param connection The connection on which the menu is shown.
166- * @param menuOptions The list of options, which may be modified by this method .
160+ * @param menuOpenEvent The event that opened this context menu .
167161 */
168- private addConnectionItems (
169- connection : Connection ,
170- menuOptions : Array <
162+ private addConnectionItems ( connection : Connection , menuOpenEvent : Event ) {
163+ const menuOptions : Array <
171164 | ContextMenuRegistry . ContextMenuOption
172165 | ContextMenuRegistry . LegacyContextMenuOption
173- > ,
174- ) {
166+ > = [ ] ;
175167 const possibleOptions = [
176168 this . getContextMenuAction ( 'insert' ) ,
177169 this . getContextMenuAction ( 'blockPasteFromContextMenu' ) ,
@@ -183,7 +175,7 @@ export class ActionMenu {
183175 } as unknown as ContextMenuRegistry . Scope ;
184176
185177 for ( const option of possibleOptions ) {
186- const precondition = option . preconditionFn ?.( scope ) ;
178+ const precondition = option . preconditionFn ?.( scope , menuOpenEvent ) ;
187179 if ( precondition === 'hidden' ) continue ;
188180 const displayText =
189181 ( typeof option . displayText === 'function'
@@ -219,36 +211,14 @@ export class ActionMenu {
219211 return item ;
220212 }
221213
222- /**
223- * Create a fake PointerEvent for opening the action menu for the
224- * given ASTNode.
225- *
226- * @param node The node to open the action menu for.
227- * @returns A synthetic pointerdown PointerEvent.
228- */
229- private fakeEventForNode ( node : ASTNode ) : PointerEvent {
230- switch ( node . getType ( ) ) {
231- case ASTNode . types . BLOCK :
232- return this . fakeEventForBlock ( node . getLocation ( ) as BlockSvg ) ;
233- case ASTNode . types . NEXT :
234- case ASTNode . types . PREVIOUS :
235- case ASTNode . types . INPUT :
236- return this . fakeEventForConnectionNode (
237- node . getLocation ( ) as RenderedConnection ,
238- ) ;
239- default :
240- throw new TypeError ( 'unhandled node type' ) ;
241- }
242- }
243-
244214 /**
245215 * Create a fake PointerEvent for opening the action menu on the specified
246216 * block.
247217 *
248218 * @param block The block to open the action menu for.
249- * @returns A synthetic pointerdown PointerEvent.
219+ * @returns screen coordinates of where to show a menu for a block
250220 */
251- private fakeEventForBlock ( block : BlockSvg ) {
221+ private calculateLocationOfBlock ( block : BlockSvg ) : BlocklyUtils . Coordinate {
252222 // Get the location of the top-left corner of the block in
253223 // screen coordinates.
254224 const blockCoords = BlocklyUtils . svgMath . wsToScreenCoordinates (
@@ -264,16 +234,12 @@ export class ActionMenu {
264234 ?. getSvgRoot ( )
265235 ?. getBoundingClientRect ( ) ;
266236
267- const clientY =
237+ const y =
268238 fieldBoundingClientRect && fieldBoundingClientRect . height
269239 ? fieldBoundingClientRect . y + fieldBoundingClientRect . height
270240 : blockCoords . y + block . height ;
271241
272- // Create a fake event for the action menu code to work from.
273- return new PointerEvent ( 'pointerdown' , {
274- clientX : blockCoords . x + 5 ,
275- clientY : clientY + 5 ,
276- } ) ;
242+ return new BlocklyUtils . Coordinate ( blockCoords . x + 5 , y + 5 ) ;
277243 }
278244
279245 /**
@@ -284,17 +250,17 @@ export class ActionMenu {
284250 * context menu for the source block.
285251 *
286252 * @param connection The node to open the action menu for.
287- * @returns A synthetic pointerdown PointerEvent .
253+ * @returns Screen coordinates of where to show menu for a connection node .
288254 */
289- private fakeEventForConnectionNode (
255+ private calculateLocationForConnectionMenu (
290256 connection : RenderedConnection ,
291- ) : PointerEvent {
257+ ) : BlocklyUtils . Coordinate {
292258 const block = connection . getSourceBlock ( ) as BlockSvg ;
293259 const workspace = block . workspace as WorkspaceSvg ;
294260
295261 if ( typeof connection . x !== 'number' ) {
296262 // No coordinates for connection? Fall back to the parent block.
297- return this . fakeEventForBlock ( block ) ;
263+ return this . calculateLocationOfBlock ( block ) ;
298264 }
299265 const connectionWSCoords = new BlocklyUtils . Coordinate (
300266 connection . x ,
@@ -304,9 +270,6 @@ export class ActionMenu {
304270 workspace ,
305271 connectionWSCoords ,
306272 ) ;
307- return new PointerEvent ( 'pointerdown' , {
308- clientX : connectionScreenCoords . x + 5 ,
309- clientY : connectionScreenCoords . y + 5 ,
310- } ) ;
273+ return connectionScreenCoords . translate ( 5 , 5 ) ;
311274 }
312275}
0 commit comments