diff --git a/blockly_uncompressed.js b/blockly_uncompressed.js index cdb9c4057..3a2f3b23e 100644 --- a/blockly_uncompressed.js +++ b/blockly_uncompressed.js @@ -21,24 +21,23 @@ this.BLOCKLY_DIR = (function(root) { this.BLOCKLY_BOOT = function(root) { // Execute after Closure has loaded. -goog.addDependency('../../core/block.js', ['Blockly.Block'], ['Blockly.ASTNode', 'Blockly.Blocks', 'Blockly.Connection', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.BlockCreate', 'Blockly.Events.BlockDelete', 'Blockly.Events.BlockMove', 'Blockly.Extensions', 'Blockly.Input', 'Blockly.Tooltip', 'Blockly.Workspace', 'Blockly.fieldRegistry', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.deprecation', 'Blockly.utils.object', 'Blockly.utils.string'], {'lang': 'es5'}); +goog.addDependency('../../core/block.js', ['Blockly.Block'], ['Blockly.ASTNode', 'Blockly.Blocks', 'Blockly.Connection', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.BlockCreate', 'Blockly.Events.BlockDelete', 'Blockly.Events.BlockMove', 'Blockly.Extensions', 'Blockly.Input', 'Blockly.Tooltip', 'Blockly.Workspace', 'Blockly.constants', 'Blockly.fieldRegistry', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.deprecation', 'Blockly.utils.object', 'Blockly.utils.string'], {'lang': 'es5'}); goog.addDependency('../../core/block_animations.js', ['Blockly.blockAnimations'], ['Blockly.utils.Svg', 'Blockly.utils.dom'], {}); goog.addDependency('../../core/block_drag_surface.js', ['Blockly.BlockDragSurfaceSvg'], ['Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {}); -goog.addDependency('../../core/block_dragger.js', ['Blockly.BlockDragger'], ['Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Events.Ui', 'Blockly.InsertionMarkerManager', 'Blockly.blockAnimations', 'Blockly.utils.Coordinate', 'Blockly.utils.dom'], {}); +goog.addDependency('../../core/block_dragger.js', ['Blockly.BlockDragger'], ['Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Events.Ui', 'Blockly.InsertionMarkerManager', 'Blockly.blockAnimations', 'Blockly.constants', 'Blockly.utils.Coordinate', 'Blockly.utils.dom'], {}); goog.addDependency('../../core/block_events.js', ['Blockly.Events.BlockBase', 'Blockly.Events.BlockChange', 'Blockly.Events.BlockCreate', 'Blockly.Events.BlockDelete', 'Blockly.Events.BlockMove', 'Blockly.Events.Change', 'Blockly.Events.Create', 'Blockly.Events.Delete', 'Blockly.Events.Move'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.registry', 'Blockly.utils.Coordinate', 'Blockly.utils.object', 'Blockly.utils.xml'], {}); -goog.addDependency('../../core/block_svg.js', ['Blockly.BlockSvg'], ['Blockly.ASTNode', 'Blockly.Block', 'Blockly.ContextMenu', 'Blockly.ContextMenuRegistry', 'Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Events.Ui', 'Blockly.Msg', 'Blockly.RenderedConnection', 'Blockly.TabNavigateCursor', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.blockAnimations', 'Blockly.blockRendering.IPathObject', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.object'], {}); -goog.addDependency('../../core/blockly.js', ['Blockly'], ['Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Procedures', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.Variables', 'Blockly.WidgetDiv', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.constants', 'Blockly.inject', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.colour'], {}); +goog.addDependency('../../core/block_svg.js', ['Blockly.BlockSvg'], ['Blockly.ASTNode', 'Blockly.Block', 'Blockly.ContextMenu', 'Blockly.ContextMenuRegistry', 'Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Events.Ui', 'Blockly.Msg', 'Blockly.RenderedConnection', 'Blockly.TabNavigateCursor', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.blockAnimations', 'Blockly.blockRendering.IPathObject', 'Blockly.constants', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.object'], {}); +goog.addDependency('../../core/blockly.js', ['Blockly'], ['Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Procedures', 'Blockly.ShortcutRegistry', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.Variables', 'Blockly.WidgetDiv', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.constants', 'Blockly.inject', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.colour'], {}); goog.addDependency('../../core/blocks.js', ['Blockly.Blocks'], [], {}); goog.addDependency('../../core/bubble.js', ['Blockly.Bubble'], ['Blockly.Scrollbar', 'Blockly.Touch', 'Blockly.Workspace', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.userAgent'], {}); -goog.addDependency('../../core/bubble_dragger.js', ['Blockly.BubbleDragger'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.CommentMove', 'Blockly.utils', 'Blockly.utils.Coordinate'], {}); +goog.addDependency('../../core/bubble_dragger.js', ['Blockly.BubbleDragger'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.CommentMove', 'Blockly.constants', 'Blockly.utils', 'Blockly.utils.Coordinate'], {}); goog.addDependency('../../core/comment.js', ['Blockly.Comment'], ['Blockly.Bubble', 'Blockly.Css', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.Ui', 'Blockly.Icon', 'Blockly.Warning', 'Blockly.utils.Svg', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {}); -goog.addDependency('../../core/components/component.js', ['Blockly.Component', 'Blockly.Component.Error'], ['Blockly.utils.IdGenerator', 'Blockly.utils.dom', 'Blockly.utils.style'], {}); -goog.addDependency('../../core/connection.js', ['Blockly.Connection'], ['Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Xml', 'Blockly.utils.deprecation'], {}); -goog.addDependency('../../core/connection_checker.js', ['Blockly.ConnectionChecker'], ['Blockly.registry'], {}); -goog.addDependency('../../core/connection_db.js', ['Blockly.ConnectionDB'], ['Blockly.RenderedConnection'], {}); +goog.addDependency('../../core/connection.js', ['Blockly.Connection'], ['Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Xml', 'Blockly.constants', 'Blockly.utils.deprecation'], {}); +goog.addDependency('../../core/connection_checker.js', ['Blockly.ConnectionChecker'], ['Blockly.constants', 'Blockly.registry'], {}); +goog.addDependency('../../core/connection_db.js', ['Blockly.ConnectionDB'], ['Blockly.RenderedConnection', 'Blockly.constants'], {}); goog.addDependency('../../core/constants.js', ['Blockly.constants'], [], {}); -goog.addDependency('../../core/contextmenu.js', ['Blockly.ContextMenu'], ['Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Menu', 'Blockly.MenuItem', 'Blockly.Msg', 'Blockly.Xml', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.dom', 'Blockly.utils.userAgent'], {}); -goog.addDependency('../../core/contextmenu_items.js', ['Blockly.ContextMenuItems'], [], {'lang': 'es5'}); +goog.addDependency('../../core/contextmenu.js', ['Blockly.ContextMenu'], ['Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Menu', 'Blockly.MenuItem', 'Blockly.Msg', 'Blockly.Xml', 'Blockly.constants', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.dom', 'Blockly.utils.userAgent'], {}); +goog.addDependency('../../core/contextmenu_items.js', ['Blockly.ContextMenuItems'], ['Blockly.constants'], {'lang': 'es5'}); goog.addDependency('../../core/contextmenu_registry.js', ['Blockly.ContextMenuRegistry'], ['Blockly.ContextMenuItems'], {'lang': 'es5'}); goog.addDependency('../../core/css.js', ['Blockly.Css'], [], {'lang': 'es5'}); goog.addDependency('../../core/dropdowndiv.js', ['Blockly.DropDownDiv'], ['Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.style'], {}); @@ -57,19 +56,19 @@ goog.addDependency('../../core/field_multilineinput.js', ['Blockly.FieldMultilin goog.addDependency('../../core/field_number.js', ['Blockly.FieldNumber'], ['Blockly.FieldTextInput', 'Blockly.fieldRegistry', 'Blockly.utils.aria', 'Blockly.utils.object'], {}); goog.addDependency('../../core/field_registry.js', ['Blockly.fieldRegistry'], ['Blockly.registry'], {}); goog.addDependency('../../core/field_textinput.js', ['Blockly.FieldTextInput'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.Msg', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.KeyCodes', 'Blockly.utils.Size', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {}); -goog.addDependency('../../core/field_variable.js', ['Blockly.FieldVariable'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.FieldDropdown', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Variables', 'Blockly.Xml', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.object'], {}); +goog.addDependency('../../core/field_variable.js', ['Blockly.FieldVariable'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.FieldDropdown', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Variables', 'Blockly.Xml', 'Blockly.constants', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.object'], {}); goog.addDependency('../../core/flyout_base.js', ['Blockly.Flyout'], ['Blockly.Block', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.VarCreate', 'Blockly.FlyoutCursor', 'Blockly.Gesture', 'Blockly.Marker', 'Blockly.Scrollbar', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {}); goog.addDependency('../../core/flyout_button.js', ['Blockly.FlyoutButton'], ['Blockly.Css', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es5'}); goog.addDependency('../../core/flyout_dragger.js', ['Blockly.FlyoutDragger'], ['Blockly.WorkspaceDragger', 'Blockly.utils.object'], {}); -goog.addDependency('../../core/flyout_horizontal.js', ['Blockly.HorizontalFlyout'], ['Blockly.Block', 'Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.WidgetDiv', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.object'], {}); -goog.addDependency('../../core/flyout_vertical.js', ['Blockly.VerticalFlyout'], ['Blockly.Block', 'Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.WidgetDiv', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {}); -goog.addDependency('../../core/generator.js', ['Blockly.Generator'], ['Blockly.Block'], {}); +goog.addDependency('../../core/flyout_horizontal.js', ['Blockly.HorizontalFlyout'], ['Blockly.Block', 'Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.WidgetDiv', 'Blockly.constants', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.object'], {}); +goog.addDependency('../../core/flyout_vertical.js', ['Blockly.VerticalFlyout'], ['Blockly.Block', 'Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.WidgetDiv', 'Blockly.constants', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {}); +goog.addDependency('../../core/generator.js', ['Blockly.Generator'], ['Blockly.Block', 'Blockly.constants'], {}); goog.addDependency('../../core/gesture.js', ['Blockly.Gesture'], ['Blockly.ASTNode', 'Blockly.BlockDragger', 'Blockly.BubbleDragger', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.FlyoutDragger', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.WorkspaceDragger', 'Blockly.blockAnimations', 'Blockly.constants', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate'], {}); goog.addDependency('../../core/grid.js', ['Blockly.Grid'], ['Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.userAgent'], {}); goog.addDependency('../../core/icon.js', ['Blockly.Icon'], ['Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {}); -goog.addDependency('../../core/inject.js', ['Blockly.inject'], ['Blockly.BlockDragSurfaceSvg', 'Blockly.Component', 'Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.Events', 'Blockly.Grid', 'Blockly.Msg', 'Blockly.Options', 'Blockly.ScrollbarPair', 'Blockly.Tooltip', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.WorkspaceSvg', 'Blockly.user.keyMap', 'Blockly.utils', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.userAgent'], {}); -goog.addDependency('../../core/input.js', ['Blockly.Input'], ['Blockly.Connection', 'Blockly.FieldLabel'], {}); -goog.addDependency('../../core/insertion_marker_manager.js', ['Blockly.InsertionMarkerManager'], ['Blockly.Events', 'Blockly.blockAnimations'], {'lang': 'es5'}); +goog.addDependency('../../core/inject.js', ['Blockly.inject'], ['Blockly.BlockDragSurfaceSvg', 'Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.Events', 'Blockly.Grid', 'Blockly.Msg', 'Blockly.Options', 'Blockly.ScrollbarPair', 'Blockly.Tooltip', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.WorkspaceSvg', 'Blockly.utils', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.userAgent'], {}); +goog.addDependency('../../core/input.js', ['Blockly.Input'], ['Blockly.Connection', 'Blockly.FieldLabel', 'Blockly.constants'], {}); +goog.addDependency('../../core/insertion_marker_manager.js', ['Blockly.InsertionMarkerManager'], ['Blockly.Events', 'Blockly.blockAnimations', 'Blockly.constants'], {'lang': 'es5'}); goog.addDependency('../../core/interfaces/i_accessibility.js', ['Blockly.IASTNodeLocation', 'Blockly.IASTNodeLocationSvg', 'Blockly.IASTNodeLocationWithBlock', 'Blockly.IBlocklyActionable'], [], {}); goog.addDependency('../../core/interfaces/i_bounded_element.js', ['Blockly.IBoundedElement'], [], {}); goog.addDependency('../../core/interfaces/i_bubble.js', ['Blockly.IBubble'], [], {}); @@ -86,39 +85,37 @@ goog.addDependency('../../core/interfaces/i_selectable.js', ['Blockly.ISelectabl goog.addDependency('../../core/interfaces/i_styleable.js', ['Blockly.IStyleable'], [], {}); goog.addDependency('../../core/interfaces/i_toolbox.js', ['Blockly.IToolbox'], [], {}); goog.addDependency('../../core/interfaces/i_toolbox_item.js', ['Blockly.ICollapsibleToolboxItem', 'Blockly.ISelectableToolboxItem', 'Blockly.IToolboxItem'], [], {}); -goog.addDependency('../../core/keyboard_nav/action.js', ['Blockly.Action'], [], {}); -goog.addDependency('../../core/keyboard_nav/ast_node.js', ['Blockly.ASTNode'], ['Blockly.utils.Coordinate'], {'lang': 'es5'}); +goog.addDependency('../../core/keyboard_nav/ast_node.js', ['Blockly.ASTNode'], ['Blockly.constants', 'Blockly.utils.Coordinate'], {'lang': 'es5'}); goog.addDependency('../../core/keyboard_nav/basic_cursor.js', ['Blockly.BasicCursor'], ['Blockly.ASTNode', 'Blockly.Cursor'], {'lang': 'es5'}); -goog.addDependency('../../core/keyboard_nav/cursor.js', ['Blockly.Cursor'], ['Blockly.ASTNode', 'Blockly.Action', 'Blockly.Marker', 'Blockly.navigation', 'Blockly.utils.object'], {'lang': 'es5'}); +goog.addDependency('../../core/keyboard_nav/cursor.js', ['Blockly.Cursor'], ['Blockly.ASTNode', 'Blockly.Marker', 'Blockly.navigation', 'Blockly.utils.object'], {'lang': 'es5'}); goog.addDependency('../../core/keyboard_nav/flyout_cursor.js', ['Blockly.FlyoutCursor'], ['Blockly.Cursor', 'Blockly.navigation', 'Blockly.utils.object'], {'lang': 'es5'}); -goog.addDependency('../../core/keyboard_nav/key_map.js', ['Blockly.user.keyMap'], ['Blockly.utils.KeyCodes', 'Blockly.utils.object'], {}); goog.addDependency('../../core/keyboard_nav/marker.js', ['Blockly.Marker'], ['Blockly.ASTNode', 'Blockly.navigation'], {}); -goog.addDependency('../../core/keyboard_nav/navigation.js', ['Blockly.navigation'], ['Blockly.ASTNode', 'Blockly.Action', 'Blockly.user.keyMap', 'Blockly.utils.Coordinate'], {}); +goog.addDependency('../../core/keyboard_nav/navigation.js', ['Blockly.navigation'], ['Blockly.ASTNode', 'Blockly.constants', 'Blockly.utils.Coordinate'], {}); goog.addDependency('../../core/keyboard_nav/tab_navigate_cursor.js', ['Blockly.TabNavigateCursor'], ['Blockly.ASTNode', 'Blockly.BasicCursor', 'Blockly.utils.object'], {}); goog.addDependency('../../core/marker_manager.js', ['Blockly.MarkerManager'], ['Blockly.Cursor', 'Blockly.Marker'], {}); goog.addDependency('../../core/menu.js', ['Blockly.Menu'], ['Blockly.utils.Coordinate', 'Blockly.utils.KeyCodes', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.style'], {}); goog.addDependency('../../core/menuitem.js', ['Blockly.MenuItem'], ['Blockly.utils.IdGenerator', 'Blockly.utils.aria', 'Blockly.utils.dom'], {}); goog.addDependency('../../core/msg.js', ['Blockly.Msg'], ['Blockly.utils.global'], {}); goog.addDependency('../../core/mutator.js', ['Blockly.Mutator'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.Ui', 'Blockly.Icon', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.global', 'Blockly.utils.object', 'Blockly.utils.toolbox', 'Blockly.utils.xml'], {}); -goog.addDependency('../../core/names.js', ['Blockly.Names'], ['Blockly.Msg'], {}); -goog.addDependency('../../core/options.js', ['Blockly.Options'], ['Blockly.Theme', 'Blockly.Themes.Classic', 'Blockly.Xml', 'Blockly.registry', 'Blockly.user.keyMap', 'Blockly.utils.IdGenerator', 'Blockly.utils.Metrics', 'Blockly.utils.toolbox', 'Blockly.utils.userAgent'], {}); +goog.addDependency('../../core/names.js', ['Blockly.Names'], ['Blockly.Msg', 'Blockly.constants'], {}); +goog.addDependency('../../core/options.js', ['Blockly.Options'], ['Blockly.Theme', 'Blockly.Themes.Classic', 'Blockly.Xml', 'Blockly.registry', 'Blockly.utils.IdGenerator', 'Blockly.utils.Metrics', 'Blockly.utils.toolbox', 'Blockly.utils.userAgent'], {}); goog.addDependency('../../core/procedures.js', ['Blockly.Procedures'], ['Blockly.Blocks', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.Msg', 'Blockly.Names', 'Blockly.Workspace', 'Blockly.Xml', 'Blockly.constants', 'Blockly.utils.xml'], {}); goog.addDependency('../../core/registry.js', ['Blockly.registry'], [], {}); -goog.addDependency('../../core/rendered_connection.js', ['Blockly.RenderedConnection'], ['Blockly.Connection', 'Blockly.Events', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.object'], {}); +goog.addDependency('../../core/rendered_connection.js', ['Blockly.RenderedConnection'], ['Blockly.Connection', 'Blockly.Events', 'Blockly.constants', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.object'], {}); goog.addDependency('../../core/renderers/common/block_rendering.js', ['Blockly.blockRendering'], ['Blockly.registry', 'Blockly.utils.object'], {}); -goog.addDependency('../../core/renderers/common/constants.js', ['Blockly.blockRendering.ConstantProvider'], ['Blockly.utils', 'Blockly.utils.Svg', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.svgPaths', 'Blockly.utils.userAgent'], {'lang': 'es5'}); -goog.addDependency('../../core/renderers/common/debugger.js', ['Blockly.blockRendering.Debug'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es5'}); +goog.addDependency('../../core/renderers/common/constants.js', ['Blockly.blockRendering.ConstantProvider'], ['Blockly.constants', 'Blockly.utils', 'Blockly.utils.Svg', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.svgPaths', 'Blockly.utils.userAgent'], {'lang': 'es5'}); +goog.addDependency('../../core/renderers/common/debugger.js', ['Blockly.blockRendering.Debug'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.constants', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es5'}); goog.addDependency('../../core/renderers/common/drawer.js', ['Blockly.blockRendering.Drawer'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.utils.svgPaths'], {}); goog.addDependency('../../core/renderers/common/i_path_object.js', ['Blockly.blockRendering.IPathObject'], [], {}); -goog.addDependency('../../core/renderers/common/info.js', ['Blockly.blockRendering.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.Hat', 'Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.InlineInput', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RoundCorner', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.SquareCorner', 'Blockly.blockRendering.StatementInput', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types'], {}); -goog.addDependency('../../core/renderers/common/marker_svg.js', ['Blockly.blockRendering.MarkerSvg'], ['Blockly.ASTNode', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {}); +goog.addDependency('../../core/renderers/common/info.js', ['Blockly.blockRendering.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.Hat', 'Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.InlineInput', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RoundCorner', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.SquareCorner', 'Blockly.blockRendering.StatementInput', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.constants'], {}); +goog.addDependency('../../core/renderers/common/marker_svg.js', ['Blockly.blockRendering.MarkerSvg'], ['Blockly.ASTNode', 'Blockly.constants', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {}); goog.addDependency('../../core/renderers/common/path_object.js', ['Blockly.blockRendering.PathObject'], ['Blockly.Theme', 'Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.IPathObject', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {}); -goog.addDependency('../../core/renderers/common/renderer.js', ['Blockly.blockRendering.Renderer'], ['Blockly.InsertionMarkerManager', 'Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.Drawer', 'Blockly.blockRendering.IPathObject', 'Blockly.blockRendering.MarkerSvg', 'Blockly.blockRendering.PathObject', 'Blockly.blockRendering.RenderInfo'], {}); +goog.addDependency('../../core/renderers/common/renderer.js', ['Blockly.blockRendering.Renderer'], ['Blockly.InsertionMarkerManager', 'Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.Drawer', 'Blockly.blockRendering.IPathObject', 'Blockly.blockRendering.MarkerSvg', 'Blockly.blockRendering.PathObject', 'Blockly.blockRendering.RenderInfo', 'Blockly.constants'], {}); goog.addDependency('../../core/renderers/geras/constants.js', ['Blockly.geras.ConstantProvider'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.utils.object'], {'lang': 'es5'}); goog.addDependency('../../core/renderers/geras/drawer.js', ['Blockly.geras.Drawer'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.Drawer', 'Blockly.geras.Highlighter', 'Blockly.geras.RenderInfo', 'Blockly.utils.object', 'Blockly.utils.svgPaths'], {}); goog.addDependency('../../core/renderers/geras/highlight_constants.js', ['Blockly.geras.HighlightConstantProvider'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.utils.svgPaths'], {'lang': 'es5'}); goog.addDependency('../../core/renderers/geras/highlighter.js', ['Blockly.geras.Highlighter'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.utils.svgPaths'], {}); -goog.addDependency('../../core/renderers/geras/info.js', ['Blockly.geras', 'Blockly.geras.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Types', 'Blockly.geras.InlineInput', 'Blockly.geras.StatementInput', 'Blockly.utils.object'], {}); +goog.addDependency('../../core/renderers/geras/info.js', ['Blockly.geras', 'Blockly.geras.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Types', 'Blockly.constants', 'Blockly.geras.InlineInput', 'Blockly.geras.StatementInput', 'Blockly.utils.object'], {}); goog.addDependency('../../core/renderers/geras/measurables/inputs.js', ['Blockly.geras.InlineInput', 'Blockly.geras.StatementInput'], ['Blockly.utils.object'], {}); goog.addDependency('../../core/renderers/geras/path_object.js', ['Blockly.geras.PathObject'], ['Blockly.Theme', 'Blockly.blockRendering.PathObject', 'Blockly.geras.ConstantProvider', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object'], {}); goog.addDependency('../../core/renderers/geras/renderer.js', ['Blockly.geras.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.geras.ConstantProvider', 'Blockly.geras.Drawer', 'Blockly.geras.HighlightConstantProvider', 'Blockly.geras.PathObject', 'Blockly.geras.RenderInfo', 'Blockly.utils.object'], {}); @@ -134,17 +131,19 @@ goog.addDependency('../../core/renderers/minimalist/info.js', ['Blockly.minimali goog.addDependency('../../core/renderers/minimalist/renderer.js', ['Blockly.minimalist.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.minimalist.ConstantProvider', 'Blockly.minimalist.Drawer', 'Blockly.minimalist.RenderInfo', 'Blockly.utils.object'], {}); goog.addDependency('../../core/renderers/thrasos/info.js', ['Blockly.thrasos', 'Blockly.thrasos.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.InlineInput', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.StatementInput', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {}); goog.addDependency('../../core/renderers/thrasos/renderer.js', ['Blockly.thrasos.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.thrasos.RenderInfo', 'Blockly.utils.object'], {}); -goog.addDependency('../../core/renderers/zelos/constants.js', ['Blockly.zelos.ConstantProvider'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.svgPaths'], {'lang': 'es5'}); +goog.addDependency('../../core/renderers/zelos/constants.js', ['Blockly.zelos.ConstantProvider'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.constants', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.svgPaths'], {'lang': 'es5'}); goog.addDependency('../../core/renderers/zelos/drawer.js', ['Blockly.zelos.Drawer'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.Drawer', 'Blockly.blockRendering.Types', 'Blockly.utils.object', 'Blockly.zelos.RenderInfo'], {}); -goog.addDependency('../../core/renderers/zelos/info.js', ['Blockly.zelos', 'Blockly.zelos.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.InlineInput', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.RoundCorner', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.SquareCorner', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.utils.object', 'Blockly.zelos.BottomRow', 'Blockly.zelos.RightConnectionShape', 'Blockly.zelos.StatementInput', 'Blockly.zelos.TopRow'], {}); +goog.addDependency('../../core/renderers/zelos/info.js', ['Blockly.zelos', 'Blockly.zelos.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.InlineInput', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.RoundCorner', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.SquareCorner', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.constants', 'Blockly.utils.object', 'Blockly.zelos.BottomRow', 'Blockly.zelos.RightConnectionShape', 'Blockly.zelos.StatementInput', 'Blockly.zelos.TopRow'], {}); goog.addDependency('../../core/renderers/zelos/marker_svg.js', ['Blockly.zelos.MarkerSvg'], ['Blockly.blockRendering.MarkerSvg', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {}); goog.addDependency('../../core/renderers/zelos/measurables/inputs.js', ['Blockly.zelos.StatementInput'], ['Blockly.blockRendering.StatementInput', 'Blockly.utils.object'], {}); goog.addDependency('../../core/renderers/zelos/measurables/row_elements.js', ['Blockly.zelos.RightConnectionShape'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {}); goog.addDependency('../../core/renderers/zelos/measurables/rows.js', ['Blockly.zelos.BottomRow', 'Blockly.zelos.TopRow'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow', 'Blockly.utils.object'], {}); goog.addDependency('../../core/renderers/zelos/path_object.js', ['Blockly.zelos.PathObject'], ['Blockly.blockRendering.PathObject', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.zelos.ConstantProvider'], {}); -goog.addDependency('../../core/renderers/zelos/renderer.js', ['Blockly.zelos.Renderer'], ['Blockly.InsertionMarkerManager', 'Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.utils.object', 'Blockly.zelos.ConstantProvider', 'Blockly.zelos.Drawer', 'Blockly.zelos.MarkerSvg', 'Blockly.zelos.PathObject', 'Blockly.zelos.RenderInfo'], {}); +goog.addDependency('../../core/renderers/zelos/renderer.js', ['Blockly.zelos.Renderer'], ['Blockly.InsertionMarkerManager', 'Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.constants', 'Blockly.utils.object', 'Blockly.zelos.ConstantProvider', 'Blockly.zelos.Drawer', 'Blockly.zelos.MarkerSvg', 'Blockly.zelos.PathObject', 'Blockly.zelos.RenderInfo'], {}); goog.addDependency('../../core/requires.js', ['Blockly.requires'], ['Blockly', 'Blockly.Comment', 'Blockly.FieldAngle', 'Blockly.FieldCheckbox', 'Blockly.FieldColour', 'Blockly.FieldDropdown', 'Blockly.FieldImage', 'Blockly.FieldLabelSerializable', 'Blockly.FieldMultilineInput', 'Blockly.FieldNumber', 'Blockly.FieldTextInput', 'Blockly.FieldVariable', 'Blockly.FlyoutButton', 'Blockly.Generator', 'Blockly.HorizontalFlyout', 'Blockly.Mutator', 'Blockly.Themes.Classic', 'Blockly.Themes.Dark', 'Blockly.Themes.Deuteranopia', 'Blockly.Themes.HighContrast', 'Blockly.Themes.Tritanopia', 'Blockly.Toolbox', 'Blockly.Trashcan', 'Blockly.VariablesDynamic', 'Blockly.VerticalFlyout', 'Blockly.Warning', 'Blockly.ZoomControls', 'Blockly.geras.Renderer', 'Blockly.thrasos.Renderer', 'Blockly.zelos.Renderer'], {}); goog.addDependency('../../core/scrollbar.js', ['Blockly.Scrollbar', 'Blockly.ScrollbarPair'], ['Blockly.Touch', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Metrics', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {}); +goog.addDependency('../../core/shortcut_items.js', ['Blockly.ShortcutItems'], ['Blockly.utils.KeyCodes'], {}); +goog.addDependency('../../core/shortcut_registry.js', ['Blockly.ShortcutRegistry'], ['Blockly.ShortcutItems', 'Blockly.navigation', 'Blockly.utils.object'], {}); goog.addDependency('../../core/theme.js', ['Blockly.Theme'], ['Blockly.registry', 'Blockly.utils', 'Blockly.utils.colour', 'Blockly.utils.object'], {}); goog.addDependency('../../core/theme/classic.js', ['Blockly.Themes.Classic'], ['Blockly.Theme'], {}); goog.addDependency('../../core/theme/dark.js', ['Blockly.Themes.Dark'], ['Blockly.Theme'], {}); @@ -157,12 +156,12 @@ goog.addDependency('../../core/theme_manager.js', ['Blockly.ThemeManager'], ['Bl goog.addDependency('../../core/toolbox/category.js', ['Blockly.ToolboxCategory'], ['Blockly.ToolboxItem', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es5'}); goog.addDependency('../../core/toolbox/collapsible_category.js', ['Blockly.CollapsibleToolboxCategory'], ['Blockly.ToolboxCategory', 'Blockly.ToolboxItem', 'Blockly.ToolboxSeparator', 'Blockly.registry', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {}); goog.addDependency('../../core/toolbox/separator.js', ['Blockly.ToolboxSeparator'], ['Blockly.ToolboxItem', 'Blockly.registry', 'Blockly.utils.dom'], {'lang': 'es5'}); -goog.addDependency('../../core/toolbox/toolbox.js', ['Blockly.Toolbox'], ['Blockly.CollapsibleToolboxCategory', 'Blockly.Css', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Touch', 'Blockly.navigation', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es5'}); +goog.addDependency('../../core/toolbox/toolbox.js', ['Blockly.Toolbox'], ['Blockly.CollapsibleToolboxCategory', 'Blockly.Css', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Touch', 'Blockly.constants', 'Blockly.navigation', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es5'}); goog.addDependency('../../core/toolbox/toolbox_item.js', ['Blockly.ToolboxItem'], [], {}); goog.addDependency('../../core/tooltip.js', ['Blockly.Tooltip'], ['Blockly.utils.string'], {}); -goog.addDependency('../../core/touch.js', ['Blockly.Touch'], ['Blockly.utils', 'Blockly.utils.global', 'Blockly.utils.string'], {}); +goog.addDependency('../../core/touch.js', ['Blockly.Touch'], ['Blockly.constants', 'Blockly.utils', 'Blockly.utils.global', 'Blockly.utils.string'], {}); goog.addDependency('../../core/touch_gesture.js', ['Blockly.TouchGesture'], ['Blockly.Gesture', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.object'], {}); -goog.addDependency('../../core/trashcan.js', ['Blockly.Trashcan'], ['Blockly.Scrollbar', 'Blockly.Xml', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {}); +goog.addDependency('../../core/trashcan.js', ['Blockly.Trashcan'], ['Blockly.Scrollbar', 'Blockly.Xml', 'Blockly.constants', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {}); goog.addDependency('../../core/ui_events.js', ['Blockly.Events.Ui'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.registry', 'Blockly.utils.object'], {}); goog.addDependency('../../core/utils.js', ['Blockly.utils'], ['Blockly.Msg', 'Blockly.constants', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.colour', 'Blockly.utils.global', 'Blockly.utils.string', 'Blockly.utils.style', 'Blockly.utils.userAgent'], {}); goog.addDependency('../../core/utils/aria.js', ['Blockly.utils.aria'], [], {}); @@ -182,18 +181,18 @@ goog.addDependency('../../core/utils/string.js', ['Blockly.utils.string'], [], { goog.addDependency('../../core/utils/style.js', ['Blockly.utils.style'], ['Blockly.utils.Coordinate', 'Blockly.utils.Size'], {}); goog.addDependency('../../core/utils/svg.js', ['Blockly.utils.Svg'], [], {}); goog.addDependency('../../core/utils/svg_paths.js', ['Blockly.utils.svgPaths'], [], {}); -goog.addDependency('../../core/utils/toolbox.js', ['Blockly.utils.toolbox'], [], {}); +goog.addDependency('../../core/utils/toolbox.js', ['Blockly.utils.toolbox'], ['Blockly.constants'], {}); goog.addDependency('../../core/utils/useragent.js', ['Blockly.utils.userAgent'], ['Blockly.utils.global'], {}); goog.addDependency('../../core/utils/xml.js', ['Blockly.utils.xml'], [], {}); goog.addDependency('../../core/variable_events.js', ['Blockly.Events.VarBase', 'Blockly.Events.VarCreate', 'Blockly.Events.VarDelete', 'Blockly.Events.VarRename'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.registry', 'Blockly.utils.object'], {}); goog.addDependency('../../core/variable_map.js', ['Blockly.VariableMap'], ['Blockly.Events', 'Blockly.Events.VarDelete', 'Blockly.Events.VarRename', 'Blockly.Msg', 'Blockly.utils', 'Blockly.utils.object'], {}); goog.addDependency('../../core/variable_model.js', ['Blockly.VariableModel'], ['Blockly.Events', 'Blockly.Events.VarCreate', 'Blockly.utils'], {}); -goog.addDependency('../../core/variables.js', ['Blockly.Variables'], ['Blockly.Blocks', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Xml', 'Blockly.utils', 'Blockly.utils.xml'], {}); +goog.addDependency('../../core/variables.js', ['Blockly.Variables'], ['Blockly.Blocks', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Xml', 'Blockly.constants', 'Blockly.utils', 'Blockly.utils.xml'], {}); goog.addDependency('../../core/variables_dynamic.js', ['Blockly.VariablesDynamic'], ['Blockly.Blocks', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Variables', 'Blockly.utils.xml'], {}); goog.addDependency('../../core/warning.js', ['Blockly.Warning'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Icon', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object'], {}); goog.addDependency('../../core/widgetdiv.js', ['Blockly.WidgetDiv'], ['Blockly.utils.style'], {}); goog.addDependency('../../core/workspace.js', ['Blockly.Workspace'], ['Blockly.ConnectionChecker', 'Blockly.Events', 'Blockly.Options', 'Blockly.VariableMap', 'Blockly.utils', 'Blockly.utils.math'], {}); -goog.addDependency('../../core/workspace_audio.js', ['Blockly.WorkspaceAudio'], ['Blockly.utils', 'Blockly.utils.global', 'Blockly.utils.userAgent'], {'lang': 'es5'}); +goog.addDependency('../../core/workspace_audio.js', ['Blockly.WorkspaceAudio'], ['Blockly.constants', 'Blockly.utils', 'Blockly.utils.global', 'Blockly.utils.userAgent'], {'lang': 'es5'}); goog.addDependency('../../core/workspace_comment.js', ['Blockly.WorkspaceComment'], ['Blockly.Events', 'Blockly.Events.CommentChange', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.xml'], {}); goog.addDependency('../../core/workspace_comment_render_svg.js', ['Blockly.WorkspaceCommentSvg.render'], ['Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {}); goog.addDependency('../../core/workspace_comment_svg.js', ['Blockly.WorkspaceCommentSvg'], ['Blockly.Css', 'Blockly.Events', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove', 'Blockly.Events.Ui', 'Blockly.WorkspaceComment', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object'], {}); @@ -202,8 +201,8 @@ goog.addDependency('../../core/workspace_dragger.js', ['Blockly.WorkspaceDragger goog.addDependency('../../core/workspace_events.js', ['Blockly.Events.FinishedLoading'], ['Blockly.Events', 'Blockly.Events.Ui', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es5'}); goog.addDependency('../../core/workspace_svg.js', ['Blockly.WorkspaceSvg'], ['Blockly.BlockSvg', 'Blockly.ConnectionDB', 'Blockly.ContextMenuRegistry', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Gesture', 'Blockly.Grid', 'Blockly.MarkerManager', 'Blockly.Msg', 'Blockly.Options', 'Blockly.ThemeManager', 'Blockly.Themes.Classic', 'Blockly.TouchGesture', 'Blockly.Workspace', 'Blockly.WorkspaceAudio', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.constants', 'Blockly.navigation', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Metrics', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {}); goog.addDependency('../../core/ws_comment_events.js', ['Blockly.Events.CommentBase', 'Blockly.Events.CommentChange', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.registry', 'Blockly.utils.Coordinate', 'Blockly.utils.object', 'Blockly.utils.xml'], {}); -goog.addDependency('../../core/xml.js', ['Blockly.Xml'], ['Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.FinishedLoading', 'Blockly.Events.VarCreate', 'Blockly.utils', 'Blockly.utils.dom', 'Blockly.utils.global', 'Blockly.utils.xml'], {}); -goog.addDependency('../../core/zoom_controls.js', ['Blockly.ZoomControls'], ['Blockly.Css', 'Blockly.Scrollbar', 'Blockly.Touch', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es5'}); +goog.addDependency('../../core/xml.js', ['Blockly.Xml'], ['Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.FinishedLoading', 'Blockly.Events.VarCreate', 'Blockly.constants', 'Blockly.utils', 'Blockly.utils.dom', 'Blockly.utils.global', 'Blockly.utils.xml'], {}); +goog.addDependency('../../core/zoom_controls.js', ['Blockly.ZoomControls'], ['Blockly.Css', 'Blockly.Scrollbar', 'Blockly.Touch', 'Blockly.constants', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es5'}); goog.addDependency("base.js", [], []); // Load Blockly. diff --git a/core/block_svg.js b/core/block_svg.js index e34b75df7..1b0b370c2 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -680,10 +680,11 @@ Blockly.BlockSvg.prototype.tab = function(start, forward) { var tabCursor = new Blockly.TabNavigateCursor(); tabCursor.setCurNode(Blockly.ASTNode.createFieldNode(start)); var currentNode = tabCursor.getCurNode(); - var action = forward ? - Blockly.navigation.ACTION_NEXT : Blockly.navigation.ACTION_PREVIOUS; + var actionName = forward ? + Blockly.navigation.actionNames.NEXT : Blockly.navigation.actionNames.PREVIOUS; - tabCursor.onBlocklyAction(action); + tabCursor.onBlocklyAction( + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ ({name: actionName})); var nextNode = tabCursor.getCurNode(); if (nextNode && nextNode !== currentNode) { diff --git a/core/blockly.js b/core/blockly.js index 1c7025c30..c48d57b83 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -20,8 +20,8 @@ goog.require('Blockly.constants'); goog.require('Blockly.Events'); goog.require('Blockly.Events.Ui'); goog.require('Blockly.inject'); -goog.require('Blockly.navigation'); goog.require('Blockly.Procedures'); +goog.require('Blockly.ShortcutRegistry'); goog.require('Blockly.Tooltip'); goog.require('Blockly.Touch'); goog.require('Blockly.utils'); @@ -177,89 +177,18 @@ Blockly.onKeyDown = function(e) { // hidden. return; } + Blockly.ShortcutRegistry.registry.onKeyDown(mainWorkspace, e); +}; - if (mainWorkspace.options.readOnly) { - // When in read only mode handle key actions for keyboard navigation. - Blockly.navigation.onKeyPress(e); - return; - } - - var deleteBlock = false; - if (e.keyCode == Blockly.utils.KeyCodes.ESC) { - // Pressing esc closes the context menu. - Blockly.hideChaff(); - Blockly.navigation.onBlocklyAction(Blockly.navigation.ACTION_EXIT); - } else if (!Blockly.Gesture.inProgress() && Blockly.navigation.onKeyPress(e)) { - // If the keyboard or field handled the key press return. - return; - } else if (e.keyCode == Blockly.utils.KeyCodes.BACKSPACE || - e.keyCode == Blockly.utils.KeyCodes.DELETE) { - // Delete or backspace. - // Stop the browser from going back to the previous page. - // Do this first to prevent an error in the delete code from resulting in - // data loss. - e.preventDefault(); - // Don't delete while dragging. Jeez. - if (Blockly.Gesture.inProgress()) { - return; - } - if (Blockly.selected && Blockly.selected.isDeletable()) { - deleteBlock = true; - } - } else if (e.altKey || e.ctrlKey || e.metaKey) { - // Don't use meta keys during drags. - if (Blockly.Gesture.inProgress()) { - return; - } - if (Blockly.selected && - Blockly.selected.isDeletable() && Blockly.selected.isMovable()) { - // Don't allow copying immovable or undeletable blocks. The next step - // would be to paste, which would create additional undeletable/immovable - // blocks on the workspace. - if (e.keyCode == Blockly.utils.KeyCodes.C) { - // 'c' for copy. - Blockly.hideChaff(); - Blockly.copy_(Blockly.selected); - } else if (e.keyCode == Blockly.utils.KeyCodes.X && - !Blockly.selected.workspace.isFlyout) { - // 'x' for cut, but not in a flyout. - // Don't even copy the selected item in the flyout. - Blockly.copy_(Blockly.selected); - deleteBlock = true; - } - } - if (e.keyCode == Blockly.utils.KeyCodes.V) { - // 'v' for paste. - if (Blockly.clipboardXml_) { - // Pasting always pastes to the main workspace, even if the copy - // started in a flyout workspace. - var workspace = Blockly.clipboardSource_; - if (workspace.isFlyout) { - workspace = workspace.targetWorkspace; - } - if (Blockly.clipboardTypeCounts_ && - workspace.isCapacityAvailable(Blockly.clipboardTypeCounts_)) { - Blockly.Events.setGroup(true); - workspace.paste(Blockly.clipboardXml_); - Blockly.Events.setGroup(false); - } - } - } else if (e.keyCode == Blockly.utils.KeyCodes.Z) { - // 'z' for undo 'Z' is for redo. - Blockly.hideChaff(); - mainWorkspace.undo(e.shiftKey); - } else if (e.ctrlKey && e.keyCode == Blockly.utils.KeyCodes.Y) { - // Ctrl-y is redo in Windows. Command-y is never valid on Macs. - Blockly.hideChaff(); - mainWorkspace.undo(true); - } - } - // Common code for delete and cut. - // Don't delete in the flyout. - if (deleteBlock && !Blockly.selected.workspace.isFlyout) { +/** + * Delete the given block. + * @param {!Blockly.BlockSvg} selected The block to delete. + * @package + */ +Blockly.deleteBlock = function(selected) { + if (!selected.workspace.isFlyout) { Blockly.Events.setGroup(true); Blockly.hideChaff(); - var selected = /** @type {!Blockly.BlockSvg} */ (Blockly.selected); selected.dispose(/* heal */ true, true); Blockly.Events.setGroup(false); } @@ -268,9 +197,9 @@ Blockly.onKeyDown = function(e) { /** * Copy a block or workspace comment onto the local clipboard. * @param {!Blockly.ICopyable} toCopy Block or Workspace Comment to be copied. - * @private + * @package */ -Blockly.copy_ = function(toCopy) { +Blockly.copy = function(toCopy) { var data = toCopy.toCopyData(); if (data) { Blockly.clipboardXml_ = data.xml; @@ -279,6 +208,31 @@ Blockly.copy_ = function(toCopy) { } }; +/** + * Paste a block or workspace comment on to the main workspace. + * @return {boolean} True if the paste was successful, false otherwise. + * @package + */ +Blockly.paste = function() { + if (!Blockly.clipboardXml_) { + return false; + } + // Pasting always pastes to the main workspace, even if the copy + // started in a flyout workspace. + var workspace = Blockly.clipboardSource_; + if (workspace.isFlyout) { + workspace = workspace.targetWorkspace; + } + if (Blockly.clipboardTypeCounts_ && + workspace.isCapacityAvailable(Blockly.clipboardTypeCounts_)) { + Blockly.Events.setGroup(true); + workspace.paste(Blockly.clipboardXml_); + Blockly.Events.setGroup(false); + return true; + } + return false; +}; + /** * Duplicate this block and its children, or a workspace comment. * @param {!Blockly.ICopyable} toDuplicate Block or Workspace Comment to be @@ -291,7 +245,7 @@ Blockly.duplicate = function(toDuplicate) { var clipboardSource = Blockly.clipboardSource_; // Create a duplicate via a copy/paste operation. - Blockly.copy_(toDuplicate); + Blockly.copy(toDuplicate); toDuplicate.workspace.paste(Blockly.clipboardXml_); // Restore the clipboard. diff --git a/core/field.js b/core/field.js index c70cc8b9a..40bd48b2f 100644 --- a/core/field.js +++ b/core/field.js @@ -32,6 +32,7 @@ goog.requireType('Blockly.IASTNodeLocationSvg'); goog.requireType('Blockly.IASTNodeLocationWithBlock'); goog.requireType('Blockly.IBlocklyActionable'); goog.requireType('Blockly.IRegistrable'); +goog.requireType('Blockly.ShortcutRegistry'); /** @@ -1107,7 +1108,7 @@ Blockly.Field.prototype.isTabNavigable = function() { /** * Handles the given action. * This is only triggered when keyboard accessibility mode is enabled. - * @param {!Blockly.Action} _action The action to be handled. + * @param {!Blockly.ShortcutRegistry.KeyboardShortcut} _action The action to be handled. * @return {boolean} True if the field handled the action, false otherwise. * @package */ diff --git a/core/field_colour.js b/core/field_colour.js index fbef49384..6a316237c 100644 --- a/core/field_colour.js +++ b/core/field_colour.js @@ -27,6 +27,7 @@ goog.require('Blockly.utils.KeyCodes'); goog.require('Blockly.utils.object'); goog.require('Blockly.utils.Size'); +goog.requireType('Blockly.ShortcutRegistry'); /** * Class for a colour input field. @@ -384,24 +385,27 @@ Blockly.FieldColour.prototype.onKeyDown_ = function(e) { /** * Handles the given action. * This is only triggered when keyboard accessibility mode is enabled. - * @param {!Blockly.Action} action The action to be handled. + * @param {!Blockly.ShortcutRegistry.KeyboardShortcut} action The action to be handled. * @return {boolean} True if the field handled the action, false otherwise. * @package */ Blockly.FieldColour.prototype.onBlocklyAction = function(action) { if (this.picker_) { - if (action === Blockly.navigation.ACTION_PREVIOUS) { - this.moveHighlightBy_(0, -1); - return true; - } else if (action === Blockly.navigation.ACTION_NEXT) { - this.moveHighlightBy_(0, 1); - return true; - } else if (action === Blockly.navigation.ACTION_OUT) { - this.moveHighlightBy_(-1, 0); - return true; - } else if (action === Blockly.navigation.ACTION_IN) { - this.moveHighlightBy_(1, 0); - return true; + switch (action.name) { + case Blockly.navigation.actionNames.PREVIOUS: + this.moveHighlightBy_(0, -1); + return true; + case Blockly.navigation.actionNames.NEXT: + this.moveHighlightBy_(0, 1); + return true; + case Blockly.navigation.actionNames.OUT: + this.moveHighlightBy_(-1, 0); + return true; + case Blockly.navigation.actionNames.IN: + this.moveHighlightBy_(1, 0); + return true; + default: + return false; } } return Blockly.FieldColour.superClass_.onBlocklyAction.call(this, action); diff --git a/core/field_dropdown.js b/core/field_dropdown.js index 474b15281..acc2ab291 100644 --- a/core/field_dropdown.js +++ b/core/field_dropdown.js @@ -31,6 +31,8 @@ goog.require('Blockly.utils.string'); goog.require('Blockly.utils.Svg'); goog.require('Blockly.utils.userAgent'); +goog.requireType('Blockly.ShortcutRegistry'); + /** * Class for an editable dropdown field. @@ -739,18 +741,21 @@ Blockly.FieldDropdown.validateOptions_ = function(options) { /** * Handles the given action. * This is only triggered when keyboard accessibility mode is enabled. - * @param {!Blockly.Action} action The action to be handled. + * @param {!Blockly.ShortcutRegistry.KeyboardShortcut} action The action to be handled. * @return {boolean} True if the field handled the action, false otherwise. * @package */ Blockly.FieldDropdown.prototype.onBlocklyAction = function(action) { if (this.menu_) { - if (action === Blockly.navigation.ACTION_PREVIOUS) { - this.menu_.highlightPrevious(); - return true; - } else if (action === Blockly.navigation.ACTION_NEXT) { - this.menu_.highlightNext(); - return true; + switch (action.name) { + case Blockly.navigation.actionNames.PREVIOUS: + this.menu_.highlightPrevious(); + return true; + case Blockly.navigation.actionNames.NEXT: + this.menu_.highlightNext(); + return true; + default: + return false; } } return Blockly.FieldDropdown.superClass_.onBlocklyAction.call(this, action); diff --git a/core/flyout_base.js b/core/flyout_base.js index 9e33f98cb..c87b7686a 100644 --- a/core/flyout_base.js +++ b/core/flyout_base.js @@ -34,6 +34,7 @@ goog.require('Blockly.Xml'); goog.requireType('Blockly.IBlocklyActionable'); goog.requireType('Blockly.IDeleteArea'); goog.requireType('Blockly.IFlyout'); +goog.requireType('Blockly.ShortcutRegistry'); goog.requireType('Blockly.utils.Metrics'); @@ -997,7 +998,7 @@ Blockly.Flyout.prototype.placeNewBlock_ = function(oldBlock) { /** * Handles the given action. * This is only triggered when keyboard accessibility mode is enabled. - * @param {!Blockly.Action} action The action to be handled. + * @param {!Blockly.ShortcutRegistry.KeyboardShortcut} action The action to be handled. * @return {boolean} True if the flyout handled the action, false otherwise. * @package */ diff --git a/core/inject.js b/core/inject.js index a67ab2418..812ddcd4f 100644 --- a/core/inject.js +++ b/core/inject.js @@ -21,7 +21,6 @@ goog.require('Blockly.Msg'); goog.require('Blockly.Options'); goog.require('Blockly.ScrollbarPair'); goog.require('Blockly.Tooltip'); -goog.require('Blockly.user.keyMap'); goog.require('Blockly.utils'); goog.require('Blockly.utils.dom'); goog.require('Blockly.utils.Svg'); @@ -68,7 +67,6 @@ Blockly.inject = function(container, opt_options) { var workspace = Blockly.createMainWorkspace_(svg, options, blockDragSurface, workspaceDragSurface); - Blockly.user.keyMap.setKeyMap(options.keyMap); Blockly.init_(workspace); diff --git a/core/interfaces/i_accessibility.js b/core/interfaces/i_accessibility.js index c31461b6d..d1275500d 100644 --- a/core/interfaces/i_accessibility.js +++ b/core/interfaces/i_accessibility.js @@ -15,6 +15,7 @@ goog.provide('Blockly.IASTNodeLocation'); goog.provide('Blockly.IASTNodeLocationSvg'); goog.provide('Blockly.IASTNodeLocationWithBlock'); goog.provide('Blockly.IBlocklyActionable'); +goog.requireType('Blockly.ShortcutRegistry'); /** * An AST node location interface. @@ -66,7 +67,7 @@ Blockly.IBlocklyActionable = function() {}; /** * Handles the given action. - * @param {!Blockly.Action} action The action to be handled. + * @param {!Blockly.ShortcutRegistry.KeyboardShortcut} action The action to be handled. * @return {boolean} True if the action has been handled, false otherwise. */ Blockly.IBlocklyActionable.prototype.onBlocklyAction; diff --git a/core/keyboard_nav/action.js b/core/keyboard_nav/action.js deleted file mode 100644 index 348049bf0..000000000 --- a/core/keyboard_nav/action.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview The class representing an action. - * Used primarily for keyboard navigation. - */ -'use strict'; - -goog.provide('Blockly.Action'); - - -/** - * Class for a single action. - * An action describes user intent. (ex go to next or go to previous) - * @param {string} name The name of the action. - * @param {string} desc The description of the action. - * @constructor - */ -Blockly.Action = function(name, desc) { - this.name = name; - this.desc = desc; -}; diff --git a/core/keyboard_nav/cursor.js b/core/keyboard_nav/cursor.js index ab5aa9218..8a2a3e97a 100644 --- a/core/keyboard_nav/cursor.js +++ b/core/keyboard_nav/cursor.js @@ -13,13 +13,13 @@ goog.provide('Blockly.Cursor'); -goog.require('Blockly.Action'); goog.require('Blockly.ASTNode'); goog.require('Blockly.Marker'); goog.require('Blockly.navigation'); goog.require('Blockly.utils.object'); goog.requireType('Blockly.IBlocklyActionable'); +goog.requireType('Blockly.ShortcutRegistry'); /** @@ -140,7 +140,7 @@ Blockly.Cursor.prototype.out = function() { /** * Handles the given action. * This is only triggered when keyboard navigation is enabled. - * @param {!Blockly.Action} action The action to be handled. + * @param {!Blockly.ShortcutRegistry.KeyboardShortcut} action The action to be handled. * @return {boolean} True if the action has been handled, false otherwise. */ Blockly.Cursor.prototype.onBlocklyAction = function(action) { diff --git a/core/keyboard_nav/flyout_cursor.js b/core/keyboard_nav/flyout_cursor.js index abe7a80a3..3db0a5257 100644 --- a/core/keyboard_nav/flyout_cursor.js +++ b/core/keyboard_nav/flyout_cursor.js @@ -17,6 +17,8 @@ goog.require('Blockly.Cursor'); goog.require('Blockly.navigation'); goog.require('Blockly.utils.object'); +goog.requireType('Blockly.ShortcutRegistry'); + /** * Class for a flyout cursor. @@ -32,7 +34,7 @@ Blockly.utils.object.inherits(Blockly.FlyoutCursor, Blockly.Cursor); /** * Handles the given action. * This is only triggered when keyboard navigation is enabled. - * @param {!Blockly.Action} action The action to be handled. + * @param {!Blockly.ShortcutRegistry.KeyboardShortcut} action The action to be handled. * @return {boolean} True if the action has been handled, false otherwise. * @override */ diff --git a/core/keyboard_nav/key_map.js b/core/keyboard_nav/key_map.js deleted file mode 100644 index 9ae9bc1b0..000000000 --- a/core/keyboard_nav/key_map.js +++ /dev/null @@ -1,190 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview The namespace used to keep track of keyboard actions and the - * key codes used to execute those actions. - * This is used primarily for keyboard navigation. - */ -'use strict'; - -goog.provide('Blockly.user.keyMap'); - -// TODO: Fix circular dependency. -// goog.require('Blockly.navigation'); -goog.require('Blockly.utils.KeyCodes'); -goog.require('Blockly.utils.object'); - - -/** - * Holds the serialized key to key action mapping. - * @type {!Object} - * @private - */ -Blockly.user.keyMap.map_ = {}; - -/** - * Object holding valid modifiers. - * @enum {string} - */ -Blockly.user.keyMap.modifierKeys = { - SHIFT: 'Shift', - CONTROL: 'Control', - ALT: 'Alt', - META: 'Meta' -}; - -/** - * Update the key map to contain the new action. - * @param {string} keyCode The key code serialized by the serializeKeyEvent. - * @param {!Blockly.Action} action The action to be executed when the keys - * corresponding to the serialized key code is pressed. - */ -Blockly.user.keyMap.setActionForKey = function(keyCode, action) { - var oldKey = Blockly.user.keyMap.getKeyByAction(action); - // If the action already exists in the key map remove it and add the new mapping. - if (oldKey) { - delete Blockly.user.keyMap.map_[oldKey]; - } - Blockly.user.keyMap.map_[keyCode] = action; -}; - -/** - * Creates a new key map. - * @param {!Object} keyMap The object holding the key - * to action mapping. - */ -Blockly.user.keyMap.setKeyMap = function(keyMap) { - Blockly.user.keyMap.map_ = keyMap; -}; - -/** - * Gets the current key map. - * @return {Object} The object holding the key to - * action mapping. - */ -Blockly.user.keyMap.getKeyMap = function() { - var map = {}; - Blockly.utils.object.mixin(map, Blockly.user.keyMap.map_); - return map; -}; - -/** - * Get the action by the serialized key code. - * @param {string} keyCode The serialized key code. - * @return {Blockly.Action|undefined} The action holding the function to - * call when the given keyCode is used or undefined if no action exists. - */ -Blockly.user.keyMap.getActionByKeyCode = function(keyCode) { - return Blockly.user.keyMap.map_[keyCode]; -}; - -/** - * Get the serialized key that corresponds to the action. - * @param {!Blockly.Action} action The action for which we want to get - * the key. - * @return {?string} The serialized key or null if the action does not have - * a key mapping. - */ -Blockly.user.keyMap.getKeyByAction = function(action) { - var keys = Object.keys(Blockly.user.keyMap.map_); - for (var i = 0, key; (key = keys[i]); i++) { - if (Blockly.user.keyMap.map_[key].name === action.name) { - return key; - } - } - return null; -}; - -/** - * Serialize the key event. - * @param {!KeyboardEvent} e A key up event holding the key code. - * @return {string} A string containing the serialized key event. - * @package - */ -Blockly.user.keyMap.serializeKeyEvent = function(e) { - var modifiers = Blockly.utils.object.values(Blockly.user.keyMap.modifierKeys); - var key = ''; - for (var i = 0, keyName; (keyName = modifiers[i]); i++) { - if (e.getModifierState(keyName)) { - key += keyName; - } - } - key += e.keyCode; - return key; -}; - -/** - * Checks whether any of the given modifiers are not valid. - * @param {!Array.} modifiers List of modifiers to be used with the key. - * @param {!Array.} validModifiers List of modifiers we support. - * @throws {Error} if the modifier is not in the valid modifiers list. - * @private - */ -Blockly.user.keyMap.checkModifiers_ = function(modifiers, validModifiers) { - for (var i = 0, modifier; (modifier = modifiers[i]); i++) { - if (validModifiers.indexOf(modifier) < 0) { - throw Error(modifier + ' is not a valid modifier key.'); - } - } -}; - -/** - * Create the serialized key code that will be used in the key map. - * @param {number} keyCode Number code representing the key. - * @param {!Array.} modifiers List of modifiers to be used with the key. - * All valid modifiers can be found in the Blockly.user.keyMap.modifierKeys. - * @return {string} The serialized key code for the given modifiers and key. - */ -Blockly.user.keyMap.createSerializedKey = function(keyCode, modifiers) { - var key = ''; - var validModifiers = Blockly.utils.object.values(Blockly.user.keyMap.modifierKeys); - Blockly.user.keyMap.checkModifiers_(modifiers, validModifiers); - for (var i = 0, validModifier; (validModifier = validModifiers[i]); i++) { - if (modifiers.indexOf(validModifier) > -1) { - key += validModifier; - } - } - key += keyCode; - return key; -}; - -/** - * Creates the default key map. - * @return {!Object} An object holding the default key - * to action mapping. - */ -Blockly.user.keyMap.createDefaultKeyMap = function() { - var map = {}; - var controlK = Blockly.user.keyMap.createSerializedKey( - Blockly.utils.KeyCodes.K, [Blockly.user.keyMap.modifierKeys.CONTROL, - Blockly.user.keyMap.modifierKeys.SHIFT]); - var shiftW = Blockly.user.keyMap.createSerializedKey( - Blockly.utils.KeyCodes.W, [Blockly.user.keyMap.modifierKeys.SHIFT]); - var shiftA = Blockly.user.keyMap.createSerializedKey( - Blockly.utils.KeyCodes.A, [Blockly.user.keyMap.modifierKeys.SHIFT]); - var shiftS = Blockly.user.keyMap.createSerializedKey( - Blockly.utils.KeyCodes.S, [Blockly.user.keyMap.modifierKeys.SHIFT]); - var shiftD = Blockly.user.keyMap.createSerializedKey( - Blockly.utils.KeyCodes.D, [Blockly.user.keyMap.modifierKeys.SHIFT]); - - map[Blockly.utils.KeyCodes.W] = Blockly.navigation.ACTION_PREVIOUS; - map[Blockly.utils.KeyCodes.A] = Blockly.navigation.ACTION_OUT; - map[Blockly.utils.KeyCodes.S] = Blockly.navigation.ACTION_NEXT; - map[Blockly.utils.KeyCodes.D] = Blockly.navigation.ACTION_IN; - map[Blockly.utils.KeyCodes.I] = Blockly.navigation.ACTION_INSERT; - map[Blockly.utils.KeyCodes.ENTER] = Blockly.navigation.ACTION_MARK; - map[Blockly.utils.KeyCodes.X] = Blockly.navigation.ACTION_DISCONNECT; - map[Blockly.utils.KeyCodes.T] = Blockly.navigation.ACTION_TOOLBOX; - map[Blockly.utils.KeyCodes.E] = Blockly.navigation.ACTION_EXIT; - map[Blockly.utils.KeyCodes.ESC] = Blockly.navigation.ACTION_EXIT; - map[controlK] = Blockly.navigation.ACTION_TOGGLE_KEYBOARD_NAV; - map[shiftW] = Blockly.navigation.ACTION_MOVE_WS_CURSOR_UP; - map[shiftA] = Blockly.navigation.ACTION_MOVE_WS_CURSOR_LEFT; - map[shiftS] = Blockly.navigation.ACTION_MOVE_WS_CURSOR_DOWN; - map[shiftD] = Blockly.navigation.ACTION_MOVE_WS_CURSOR_RIGHT; - return map; -}; diff --git a/core/keyboard_nav/navigation.js b/core/keyboard_nav/navigation.js index 8736acbda..774544b09 100644 --- a/core/keyboard_nav/navigation.js +++ b/core/keyboard_nav/navigation.js @@ -13,11 +13,9 @@ goog.provide('Blockly.navigation'); -goog.require('Blockly.Action'); goog.require('Blockly.ASTNode'); goog.require('Blockly.constants'); goog.require('Blockly.utils.Coordinate'); -goog.require('Blockly.user.keyMap'); /** @@ -113,6 +111,7 @@ Blockly.navigation.getMarker = function() { /** * Get the workspace that is being navigated. * @return {!Blockly.WorkspaceSvg} The workspace being navigated. + * TODO: Remove this in favor or using passed in workspaces. */ Blockly.navigation.getNavigationWorkspace = function() { return /** @type {!Blockly.WorkspaceSvg} */ (Blockly.getMainWorkspace()); @@ -121,10 +120,11 @@ Blockly.navigation.getNavigationWorkspace = function() { /** * If a toolbox exists, set the navigation state to toolbox and select the first * category in the toolbox. + * @param {!Blockly.WorkspaceSvg} workspace The main workspace. * @private */ -Blockly.navigation.focusToolbox_ = function() { - var toolbox = Blockly.navigation.getNavigationWorkspace().getToolbox(); +Blockly.navigation.focusToolbox_ = function(workspace) { + var toolbox = workspace.getToolbox(); if (toolbox) { Blockly.navigation.currentState_ = Blockly.navigation.STATE_TOOLBOX; Blockly.navigation.resetFlyout_(false /* shouldHide */); @@ -133,26 +133,19 @@ Blockly.navigation.focusToolbox_ = function() { Blockly.navigation.markAtCursor_(); } if (!toolbox.getSelectedItem()) { - // Find the first item that is selectable. - var toolboxItems = toolbox.getToolboxItems(); - for (var i = 0, toolboxItem; (toolboxItem = toolboxItems[i]); i++) { - if (toolboxItem.isSelectable()) { - toolbox.selectItemByPosition(i); - break; - } - } + toolbox.selectItemByPosition(0); } } }; /** * Change focus to the flyout. + * @param {!Blockly.WorkspaceSvg} workspace The main workspace. * @private */ -Blockly.navigation.focusFlyout_ = function() { +Blockly.navigation.focusFlyout_ = function(workspace) { var topBlock = null; Blockly.navigation.currentState_ = Blockly.navigation.STATE_FLYOUT; - var workspace = Blockly.navigation.getNavigationWorkspace(); var toolbox = workspace.getToolbox(); var flyout = toolbox ? toolbox.getFlyout() : workspace.getFlyout(); @@ -173,11 +166,11 @@ Blockly.navigation.focusFlyout_ = function() { /** * Finds where the cursor should go on the workspace. This is either the top * block or a set position on the workspace. + * @param {!Blockly.WorkspaceSvg} workspace The main workspace. * @private */ -Blockly.navigation.focusWorkspace_ = function() { +Blockly.navigation.focusWorkspace_ = function(workspace) { Blockly.hideChaff(); - var workspace = Blockly.navigation.getNavigationWorkspace(); var cursor = workspace.getCursor(); var reset = !!workspace.getToolbox(); var topBlocks = workspace.getTopBlocks(true); @@ -218,9 +211,9 @@ Blockly.navigation.getFlyoutCursor_ = function() { * If there is a marked connection try connecting the block from the flyout to * that connection. If no connection has been marked then inserting will place * it on the workspace. + * @param {!Blockly.WorkspaceSvg} workspace The main workspace. */ -Blockly.navigation.insertFromFlyout = function() { - var workspace = Blockly.navigation.getNavigationWorkspace(); +Blockly.navigation.insertFromFlyout = function(workspace) { var flyout = workspace.getFlyout(); if (!flyout || !flyout.isVisible()) { Blockly.navigation.warn_('Trying to insert from the flyout when the flyout does not ' + @@ -248,7 +241,7 @@ Blockly.navigation.insertFromFlyout = function() { Blockly.navigation.warn_('Something went wrong while inserting a block from the flyout.'); } - Blockly.navigation.focusWorkspace_(); + Blockly.navigation.focusWorkspace_(workspace); workspace.getCursor().setCurNode(Blockly.ASTNode.createTopNode(newBlock)); Blockly.navigation.removeMark_(); }; @@ -569,10 +562,10 @@ Blockly.navigation.insertBlock = function(block, destConnection) { * Disconnect the connection that the cursor is pointing to, and bump blocks. * This is a no-op if the connection cannot be broken or if the cursor is not * pointing to a connection. + * @param {!Blockly.WorkspaceSvg} workspace The main workspace. * @private */ -Blockly.navigation.disconnectBlocks_ = function() { - var workspace = Blockly.navigation.getNavigationWorkspace(); +Blockly.navigation.disconnectBlocks_ = function(workspace) { var curNode = workspace.getCursor().getCurNode(); if (!curNode.isConnection()) { Blockly.navigation.log_('Cannot disconnect blocks when the cursor is not on a connection'); @@ -697,7 +690,7 @@ Blockly.navigation.enableKeyboardAccessibility = function() { var workspace = Blockly.navigation.getNavigationWorkspace(); if (!workspace.keyboardAccessibilityMode) { workspace.keyboardAccessibilityMode = true; - Blockly.navigation.focusWorkspace_(); + Blockly.navigation.focusWorkspace_(workspace); } }; @@ -762,140 +755,15 @@ Blockly.navigation.error_ = function(msg) { /** Handle Key Press */ /** ***************** */ -/** - * Handler for all the keyboard navigation events. - * @param {!KeyboardEvent} e The keyboard event. - * @return {boolean} True if the key was handled false otherwise. - */ -Blockly.navigation.onKeyPress = function(e) { - var key = Blockly.user.keyMap.serializeKeyEvent(e); - var action = Blockly.user.keyMap.getActionByKeyCode(key); - - if (action) { - return Blockly.navigation.onBlocklyAction(action); - } - return false; -}; - -/** - * Decides which actions to handle depending on keyboard navigation and readonly - * states. - * @param {!Blockly.Action} action The current action. - * @return {boolean} True if the action has been handled, false otherwise. - */ -Blockly.navigation.onBlocklyAction = function(action) { - var workspace = Blockly.navigation.getNavigationWorkspace(); - var readOnly = workspace.options.readOnly; - var actionHandled = false; - - if (workspace.keyboardAccessibilityMode) { - if (!readOnly) { - actionHandled = Blockly.navigation.handleActions_(action); - // If in readonly mode only handle valid actions. - } else if (Blockly.navigation.READONLY_ACTION_LIST.indexOf(action) > -1) { - actionHandled = Blockly.navigation.handleActions_(action); - } - // If not in accessibility mode only handle turning on keyboard navigation. - } else if (action.name === Blockly.navigation.actionNames.TOGGLE_KEYBOARD_NAV) { - Blockly.navigation.enableKeyboardAccessibility(); - actionHandled = true; - } - return actionHandled; -}; - -/** - * Handles the action or dispatches to the appropriate action handler. - * @param {!Blockly.Action} action The action to handle. - * @return {boolean} True if the action has been handled, false otherwise. - * @private - */ -Blockly.navigation.handleActions_ = function(action) { - if (action.name == Blockly.navigation.actionNames.TOOLBOX || - Blockly.navigation.currentState_ == Blockly.navigation.STATE_TOOLBOX) { - return Blockly.navigation.toolboxOnAction_(action); - } else if (action.name == Blockly.navigation.actionNames.TOGGLE_KEYBOARD_NAV) { - Blockly.navigation.disableKeyboardAccessibility(); - return true; - } if (Blockly.navigation.currentState_ == Blockly.navigation.STATE_WS) { - return Blockly.navigation.workspaceOnAction_(action); - } else if (Blockly.navigation.currentState_ == Blockly.navigation.STATE_FLYOUT) { - return Blockly.navigation.flyoutOnAction_(action); - } - return false; -}; - -/** - * Handles the given action for the flyout. - * @param {!Blockly.Action} action The action to handle. - * @return {boolean} True if the action has been handled, false otherwise. - * @private - */ -Blockly.navigation.flyoutOnAction_ = function(action) { - var workspace = Blockly.navigation.getNavigationWorkspace(); - var toolbox = workspace.getToolbox(); - var flyout = toolbox ? toolbox.getFlyout() : workspace.getFlyout(); - - if (flyout && flyout.onBlocklyAction(action)) { - return true; - } - - switch (action.name) { - case Blockly.navigation.actionNames.OUT: - Blockly.navigation.focusToolbox_(); - return true; - case Blockly.navigation.actionNames.MARK: - Blockly.navigation.insertFromFlyout(); - return true; - case Blockly.navigation.actionNames.EXIT: - Blockly.navigation.focusWorkspace_(); - return true; - default: - return false; - } -}; - -/** - * Handles the given action for the toolbox. - * @param {!Blockly.Action} action The action to handle. - * @return {boolean} True if the action has been handled, false otherwise. - * @private - */ -Blockly.navigation.toolboxOnAction_ = function(action) { - var workspace = Blockly.navigation.getNavigationWorkspace(); - var toolbox = workspace.getToolbox(); - var handled = toolbox && typeof toolbox.onBlocklyAction == 'function' ? - toolbox.onBlocklyAction(action) : false; - - if (handled) { - return true; - } - - if (action.name === Blockly.navigation.actionNames.TOOLBOX) { - if (!workspace.getToolbox()) { - Blockly.navigation.focusFlyout_(); - } else { - Blockly.navigation.focusToolbox_(); - } - return true; - } else if (action.name === Blockly.navigation.actionNames.IN) { - Blockly.navigation.focusFlyout_(); - return true; - } else if (action.name === Blockly.navigation.actionNames.EXIT) { - Blockly.navigation.focusWorkspace_(); - return true; - } - return false; -}; - /** * Move the workspace cursor in the given direction. + * @param {!Blockly.WorkspaceSvg} workspace The workspace the cursor is on. * @param {number} xDirection -1 to move cursor left. 1 to move cursor right. * @param {number} yDirection -1 to move cursor up. 1 to move cursor down. * @return {boolean} True if the current node is a workspace, false otherwise. * @private */ -Blockly.navigation.moveWSCursor_ = function(xDirection, yDirection) { - var workspace = Blockly.navigation.getNavigationWorkspace(); +Blockly.navigation.moveWSCursor_ = function(workspace, xDirection, yDirection) { var cursor = workspace.getCursor(); var curNode = workspace.getCursor().getCurNode(); @@ -912,46 +780,14 @@ Blockly.navigation.moveWSCursor_ = function(xDirection, yDirection) { return true; }; -/** - * Handles the given action for the workspace. - * @param {!Blockly.Action} action The action to handle. - * @return {boolean} True if the action has been handled, false otherwise. - * @private - */ -Blockly.navigation.workspaceOnAction_ = function(action) { - var workspace = Blockly.navigation.getNavigationWorkspace(); - if (workspace.getCursor().onBlocklyAction(action)) { - return true; - } - switch (action.name) { - case Blockly.navigation.actionNames.INSERT: - Blockly.navigation.modify_(); - return true; - case Blockly.navigation.actionNames.MARK: - Blockly.navigation.handleEnterForWS_(); - return true; - case Blockly.navigation.actionNames.DISCONNECT: - Blockly.navigation.disconnectBlocks_(); - return true; - case Blockly.navigation.actionNames.MOVE_WS_CURSOR_UP: - return Blockly.navigation.moveWSCursor_(0, -1); - case Blockly.navigation.actionNames.MOVE_WS_CURSOR_DOWN: - return Blockly.navigation.moveWSCursor_(0, 1); - case Blockly.navigation.actionNames.MOVE_WS_CURSOR_LEFT: - return Blockly.navigation.moveWSCursor_(-1, 0); - case Blockly.navigation.actionNames.MOVE_WS_CURSOR_RIGHT: - return Blockly.navigation.moveWSCursor_(1, 0); - default: - return false; - } -}; - /** * Handles hitting the enter key on the workspace. + * @param {!Blockly.WorkspaceSvg} workspace The workspace the enter event + * originated from. * @private */ -Blockly.navigation.handleEnterForWS_ = function() { - var cursor = Blockly.navigation.getNavigationWorkspace().getCursor(); +Blockly.navigation.handleEnterForWS_ = function(workspace) { + var cursor = workspace.getCursor(); var curNode = cursor.getCurNode(); var nodeType = curNode.getType(); if (nodeType == Blockly.ASTNode.types.FIELD) { @@ -966,127 +802,423 @@ Blockly.navigation.handleEnterForWS_ = function() { } }; -/** ******************* */ -/** Navigation Actions */ -/** ******************* */ +/** ***************** */ +/** Register Items */ +/** ***************** */ /** - * The previous action. - * @type {!Blockly.Action} + * Keyboard shortcut to go to the previous location when in keyboard navigation + * mode. */ -Blockly.navigation.ACTION_PREVIOUS = new Blockly.Action( - Blockly.navigation.actionNames.PREVIOUS, 'Go to the previous location.'); +Blockly.navigation.registerPrevious = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var previousShortcut = { + name: Blockly.navigation.actionNames.PREVIOUS, + preconditionFn: function(workspace) { + return workspace.keyboardAccessibilityMode; + }, + callback: function(workspace, e, action) { + var toolbox = workspace.getToolbox(); + switch (Blockly.navigation.currentState_) { + case Blockly.navigation.STATE_WS: + return workspace.getCursor().onBlocklyAction(action); + case Blockly.navigation.STATE_FLYOUT: + var flyout = toolbox ? toolbox.getFlyout() : workspace.getFlyout(); + return !!(flyout && flyout.onBlocklyAction(action)); + case Blockly.navigation.STATE_TOOLBOX: + return toolbox && typeof toolbox.onBlocklyAction == 'function' ? + toolbox.onBlocklyAction(action) : + false; + default: + return false; + } + } + }; + + Blockly.ShortcutRegistry.registry.register(previousShortcut); + Blockly.ShortcutRegistry.registry.addKeyMapping( + Blockly.utils.KeyCodes.W, previousShortcut.name); +}; /** - * The out action. - * @type {!Blockly.Action} + * Keyboard shortcut to go to the out location when in keyboard navigation + * mode. */ -Blockly.navigation.ACTION_OUT = new Blockly.Action( - Blockly.navigation.actionNames.OUT, - 'Go to the parent of the current location.'); +Blockly.navigation.registerOut = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var outShortcut = { + name: Blockly.navigation.actionNames.OUT, + preconditionFn: function(workspace) { + return workspace.keyboardAccessibilityMode; + }, + callback: function(workspace, e, action) { + switch (Blockly.navigation.currentState_) { + case Blockly.navigation.STATE_WS: + return workspace.getCursor().onBlocklyAction(action); + case Blockly.navigation.STATE_FLYOUT: + Blockly.navigation.focusToolbox_(workspace); + return true; + case Blockly.navigation.STATE_TOOLBOX: + var toolbox = workspace.getToolbox(); + return toolbox && typeof toolbox.onBlocklyAction == 'function' ? + toolbox.onBlocklyAction(action) : + false; + default: + return false; + } + } + }; + + Blockly.ShortcutRegistry.registry.register(outShortcut); + Blockly.ShortcutRegistry.registry.addKeyMapping( + Blockly.utils.KeyCodes.A, outShortcut.name); +}; /** - * The next action. - * @type {!Blockly.Action} + * Keyboard shortcut to go to the next location when in keyboard navigation + * mode. */ -Blockly.navigation.ACTION_NEXT = new Blockly.Action( - Blockly.navigation.actionNames.NEXT, 'Go to the next location.'); +Blockly.navigation.registerNext = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var nextShortcut = { + name: Blockly.navigation.actionNames.NEXT, + preconditionFn: function(workspace) { + return workspace.keyboardAccessibilityMode; + }, + callback: function(workspace, e, action) { + var toolbox = workspace.getToolbox(); + switch (Blockly.navigation.currentState_) { + case Blockly.navigation.STATE_WS: + return workspace.getCursor().onBlocklyAction(action); + case Blockly.navigation.STATE_FLYOUT: + var flyout = toolbox ? toolbox.getFlyout() : workspace.getFlyout(); + return !!(flyout && flyout.onBlocklyAction(action)); + case Blockly.navigation.STATE_TOOLBOX: + return toolbox && typeof toolbox.onBlocklyAction == 'function' ? + toolbox.onBlocklyAction(action) : + false; + default: + return false; + } + } + }; + + Blockly.ShortcutRegistry.registry.register(nextShortcut); + Blockly.ShortcutRegistry.registry.addKeyMapping( + Blockly.utils.KeyCodes.S, nextShortcut.name); +}; /** - * The in action. - * @type {!Blockly.Action} + * Keyboard shortcut to go to the in location when in keyboard navigation mode. */ -Blockly.navigation.ACTION_IN = new Blockly.Action( - Blockly.navigation.actionNames.IN, - 'Go to the first child of the current location.'); +Blockly.navigation.registerIn = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var inShortcut = { + name: Blockly.navigation.actionNames.IN, + preconditionFn: function(workspace) { + return workspace.keyboardAccessibilityMode; + }, + callback: function(workspace, e, action) { + switch (Blockly.navigation.currentState_) { + case Blockly.navigation.STATE_WS: + return workspace.getCursor().onBlocklyAction(action); + case Blockly.navigation.STATE_TOOLBOX: + var toolbox = workspace.getToolbox(); + var isHandled = + toolbox && typeof toolbox.onBlocklyAction == 'function' ? + toolbox.onBlocklyAction(action) : + false; + if (!isHandled) { + Blockly.navigation.focusFlyout_(workspace); + } + return true; + default: + return false; + } + } + }; + + Blockly.ShortcutRegistry.registry.register(inShortcut); + Blockly.ShortcutRegistry.registry.addKeyMapping( + Blockly.utils.KeyCodes.D, inShortcut.name); +}; /** - * The action to try to insert a block. - * @type {!Blockly.Action} + * Keyboard shortcut to connect a block to a marked location when in keyboard + * navigation mode. */ -Blockly.navigation.ACTION_INSERT = new Blockly.Action( - Blockly.navigation.actionNames.INSERT, - 'Connect the current location to the marked location.'); +Blockly.navigation.registerInsert = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var insertShortcut = { + name: Blockly.navigation.actionNames.INSERT, + preconditionFn: function(workspace) { + return workspace.keyboardAccessibilityMode && !workspace.options.readOnly; + }, + callback: function() { + switch (Blockly.navigation.currentState_) { + case Blockly.navigation.STATE_WS: + return Blockly.navigation.modify_(); + default: + return false; + } + } + }; + + Blockly.ShortcutRegistry.registry.register(insertShortcut); + Blockly.ShortcutRegistry.registry.addKeyMapping( + Blockly.utils.KeyCodes.I, insertShortcut.name); +}; + +/** Keyboard shortcut to mark a location when in keyboard navigation mode. */ +Blockly.navigation.registerMark = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var markShortcut = { + name: Blockly.navigation.actionNames.MARK, + preconditionFn: function(workspace) { + return workspace.keyboardAccessibilityMode && !workspace.options.readOnly; + }, + callback: function(workspace) { + switch (Blockly.navigation.currentState_) { + case Blockly.navigation.STATE_WS: + Blockly.navigation.handleEnterForWS_(workspace); + return true; + case Blockly.navigation.STATE_FLYOUT: + Blockly.navigation.insertFromFlyout(workspace); + return true; + default: + return false; + } + } + }; + + Blockly.ShortcutRegistry.registry.register(markShortcut); + Blockly.ShortcutRegistry.registry.addKeyMapping( + Blockly.utils.KeyCodes.ENTER, markShortcut.name); +}; /** - * The action to mark a certain location. - * @type {!Blockly.Action} + * Keyboard shortcut to disconnect two blocks when in keyboard navigation mode. */ -Blockly.navigation.ACTION_MARK = new Blockly.Action( - Blockly.navigation.actionNames.MARK, 'Mark the current location.'); +Blockly.navigation.registerDisconnect = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var disconnectShortcut = { + name: Blockly.navigation.actionNames.DISCONNECT, + preconditionFn: function(workspace) { + return workspace.keyboardAccessibilityMode && !workspace.options.readOnly; + }, + callback: function(workspace) { + switch (Blockly.navigation.currentState_) { + case Blockly.navigation.STATE_WS: + Blockly.navigation.disconnectBlocks_(workspace); + return true; + default: + return false; + } + } + }; + + Blockly.ShortcutRegistry.registry.register(disconnectShortcut); + Blockly.ShortcutRegistry.registry.addKeyMapping( + Blockly.utils.KeyCodes.X, disconnectShortcut.name); +}; /** - * The action to disconnect a block. - * @type {!Blockly.Action} + * Keyboard shortcut to focus on the toolbox when in keyboard navigation mode. */ -Blockly.navigation.ACTION_DISCONNECT = new Blockly.Action( - Blockly.navigation.actionNames.DISCONNECT, - 'Disconnect the block at the current location from its parent.'); +Blockly.navigation.registerToolboxFocus = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var focusToolboxShortcut = { + name: Blockly.navigation.actionNames.TOOLBOX, + preconditionFn: function(workspace) { + return workspace.keyboardAccessibilityMode && !workspace.options.readOnly; + }, + callback: function(workspace) { + switch (Blockly.navigation.currentState_) { + case Blockly.navigation.STATE_WS: + if (!workspace.getToolbox()) { + Blockly.navigation.focusFlyout_(workspace); + } else { + Blockly.navigation.focusToolbox_(workspace); + } + return true; + default: + return false; + } + } + }; + + Blockly.ShortcutRegistry.registry.register(focusToolboxShortcut); + Blockly.ShortcutRegistry.registry.addKeyMapping( + Blockly.utils.KeyCodes.T, focusToolboxShortcut.name); +}; /** - * The action to open the toolbox. - * @type {!Blockly.Action} + * Keyboard shortcut to exit the current location and focus on the workspace + * when in keyboard navigation mode. */ -Blockly.navigation.ACTION_TOOLBOX = new Blockly.Action( - Blockly.navigation.actionNames.TOOLBOX, 'Open the toolbox.'); +Blockly.navigation.registerExit = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var exitShortcut = { + name: Blockly.navigation.actionNames.EXIT, + preconditionFn: function(workspace) { + return workspace.keyboardAccessibilityMode; + }, + callback: function(workspace) { + switch (Blockly.navigation.currentState_) { + case Blockly.navigation.STATE_FLYOUT: + Blockly.navigation.focusWorkspace_(workspace); + return true; + case Blockly.navigation.STATE_TOOLBOX: + Blockly.navigation.focusWorkspace_(workspace); + return true; + default: + return false; + } + } + }; + + Blockly.ShortcutRegistry.registry.register(exitShortcut, true); + Blockly.ShortcutRegistry.registry.addKeyMapping( + Blockly.utils.KeyCodes.ESC, exitShortcut.name, true); + Blockly.ShortcutRegistry.registry.addKeyMapping( + Blockly.utils.KeyCodes.E, exitShortcut.name, true); +}; + +/** Keyboard shortcut to turn keyboard navigation on or off. */ +Blockly.navigation.registerToggleKeyboardNav = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var toggleKeyboardNavShortcut = { + name: Blockly.navigation.actionNames.TOGGLE_KEYBOARD_NAV, + callback: function(workspace) { + if (workspace.keyboardAccessibilityMode) { + Blockly.navigation.disableKeyboardAccessibility(); + } else { + Blockly.navigation.enableKeyboardAccessibility(); + } + return true; + } + }; + + Blockly.ShortcutRegistry.registry.register(toggleKeyboardNavShortcut); + var ctrlShiftK = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.K, + [Blockly.utils.KeyCodes.CTRL, Blockly.utils.KeyCodes.SHIFT]); + Blockly.ShortcutRegistry.registry.addKeyMapping( + ctrlShiftK, toggleKeyboardNavShortcut.name); +}; /** - * The action to exit the toolbox or flyout. - * @type {!Blockly.Action} + * Keyboard shortcut to move the cursor on the workspace to the left when in + * keyboard navigation mode. */ -Blockly.navigation.ACTION_EXIT = new Blockly.Action( - Blockly.navigation.actionNames.EXIT, - 'Close the current modal, such as a toolbox or field editor.'); +Blockly.navigation.registerWorkspaceMoveLeft = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var wsMoveLeftShortcut = { + name: Blockly.navigation.actionNames.MOVE_WS_CURSOR_LEFT, + preconditionFn: function(workspace) { + return workspace.keyboardAccessibilityMode && !workspace.options.readOnly; + }, + callback: function(workspace) { + return Blockly.navigation.moveWSCursor_(workspace, -1, 0); + } + }; + + Blockly.ShortcutRegistry.registry.register(wsMoveLeftShortcut); + var shiftA = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.A, [Blockly.utils.KeyCodes.SHIFT]); + Blockly.ShortcutRegistry.registry.addKeyMapping( + shiftA, wsMoveLeftShortcut.name); +}; /** - * The action to toggle keyboard navigation mode on and off. - * @type {!Blockly.Action} + * Keyboard shortcut to move the cursor on the workspace to the right when in + * keyboard navigation mode. */ -Blockly.navigation.ACTION_TOGGLE_KEYBOARD_NAV = new Blockly.Action( - Blockly.navigation.actionNames.TOGGLE_KEYBOARD_NAV, - 'Turns on and off keyboard navigation.'); +Blockly.navigation.registerWorkspaceMoveRight = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var wsMoveRightShortcut = { + name: Blockly.navigation.actionNames.MOVE_WS_CURSOR_RIGHT, + preconditionFn: function(workspace) { + return workspace.keyboardAccessibilityMode && !workspace.options.readOnly; + }, + callback: function(workspace) { + return Blockly.navigation.moveWSCursor_(workspace, 1, 0); + } + }; + + Blockly.ShortcutRegistry.registry.register(wsMoveRightShortcut); + var shiftD = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.D, [Blockly.utils.KeyCodes.SHIFT]); + Blockly.ShortcutRegistry.registry.addKeyMapping( + shiftD, wsMoveRightShortcut.name); +}; /** - * The action to move the cursor to the left on a workspace. - * @type {!Blockly.Action} + * Keyboard shortcut to move the cursor on the workspace up when in keyboard + * navigation mode. */ -Blockly.navigation.ACTION_MOVE_WS_CURSOR_LEFT = new Blockly.Action( - Blockly.navigation.actionNames.MOVE_WS_CURSOR_LEFT, - 'Move the workspace cursor to the lefts.'); +Blockly.navigation.registerWorkspaceMoveUp = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var wsMoveUpShortcut = { + name: Blockly.navigation.actionNames.MOVE_WS_CURSOR_UP, + preconditionFn: function(workspace) { + return workspace.keyboardAccessibilityMode && !workspace.options.readOnly; + }, + callback: function(workspace) { + return Blockly.navigation.moveWSCursor_(workspace, 0, -1); + } + }; + + Blockly.ShortcutRegistry.registry.register(wsMoveUpShortcut); + var shiftW = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.W, [Blockly.utils.KeyCodes.SHIFT]); + Blockly.ShortcutRegistry.registry.addKeyMapping( + shiftW, wsMoveUpShortcut.name); +}; /** - * The action to move the cursor to the right on a workspace. - * @type {!Blockly.Action} + * Keyboard shortcut to move the cursor on the workspace down when in keyboard + * navigation mode. */ -Blockly.navigation.ACTION_MOVE_WS_CURSOR_RIGHT = new Blockly.Action( - Blockly.navigation.actionNames.MOVE_WS_CURSOR_RIGHT, - 'Move the workspace cursor to the right.'); +Blockly.navigation.registerWorkspaceMoveDown = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var wsMoveDownShortcut = { + name: Blockly.navigation.actionNames.MOVE_WS_CURSOR_DOWN, + preconditionFn: function(workspace) { + return workspace.keyboardAccessibilityMode && !workspace.options.readOnly; + }, + callback: function(workspace) { + return Blockly.navigation.moveWSCursor_(workspace, 0, 1); + } + }; + + Blockly.ShortcutRegistry.registry.register(wsMoveDownShortcut); + var shiftW = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.S, [Blockly.utils.KeyCodes.SHIFT]); + Blockly.ShortcutRegistry.registry.addKeyMapping( + shiftW, wsMoveDownShortcut.name); +}; /** - * The action to move the cursor up on a workspace. - * @type {!Blockly.Action} + * Registers all default keyboard shortcut items for keyboard navigation. This + * should be called once per instance of KeyboardShortcutRegistry. + * @package */ -Blockly.navigation.ACTION_MOVE_WS_CURSOR_UP = new Blockly.Action( - Blockly.navigation.actionNames.MOVE_WS_CURSOR_UP, - 'Move the workspace cursor up.'); +Blockly.navigation.registerNavigationShortcuts = function() { + Blockly.navigation.registerIn(); + Blockly.navigation.registerNext(); + Blockly.navigation.registerOut(); + Blockly.navigation.registerPrevious(); -/** - * The action to move the cursor down on a workspace. - * @type {!Blockly.Action} - */ -Blockly.navigation.ACTION_MOVE_WS_CURSOR_DOWN = new Blockly.Action( - Blockly.navigation.actionNames.MOVE_WS_CURSOR_DOWN, - 'Move the workspace cursor down.'); + Blockly.navigation.registerWorkspaceMoveDown(); + Blockly.navigation.registerWorkspaceMoveLeft(); + Blockly.navigation.registerWorkspaceMoveRight(); + Blockly.navigation.registerWorkspaceMoveUp(); - -/** - * List of actions that can be performed in read only mode. - * @type {!Array.} - */ -Blockly.navigation.READONLY_ACTION_LIST = [ - Blockly.navigation.ACTION_PREVIOUS, - Blockly.navigation.ACTION_OUT, - Blockly.navigation.ACTION_IN, - Blockly.navigation.ACTION_NEXT, - Blockly.navigation.ACTION_TOGGLE_KEYBOARD_NAV -]; + Blockly.navigation.registerDisconnect(); + Blockly.navigation.registerExit(); + Blockly.navigation.registerInsert(); + Blockly.navigation.registerMark(); + Blockly.navigation.registerToggleKeyboardNav(); + Blockly.navigation.registerToolboxFocus(); +}; diff --git a/core/options.js b/core/options.js index 4262125ac..a968a8698 100644 --- a/core/options.js +++ b/core/options.js @@ -15,7 +15,6 @@ goog.provide('Blockly.Options'); goog.require('Blockly.Theme'); goog.require('Blockly.Themes.Classic'); goog.require('Blockly.registry'); -goog.require('Blockly.user.keyMap'); goog.require('Blockly.utils.IdGenerator'); goog.require('Blockly.utils.Metrics'); goog.require('Blockly.utils.toolbox'); @@ -108,8 +107,6 @@ Blockly.Options = function(options) { } else { var oneBasedIndex = !!options['oneBasedIndex']; } - var keyMap = options['keyMap'] || Blockly.user.keyMap.createDefaultKeyMap(); - var renderer = options['renderer'] || 'geras'; var plugins = options['plugins'] || {}; @@ -158,8 +155,6 @@ Blockly.Options = function(options) { this.toolboxPosition = toolboxPosition; /** @type {!Blockly.Theme} */ this.theme = Blockly.Options.parseThemeOptions_(options); - /** @type {!Object} */ - this.keyMap = keyMap; /** @type {string} */ this.renderer = renderer; /** @type {?Object} */ diff --git a/core/shortcut_items.js b/core/shortcut_items.js new file mode 100644 index 000000000..301dec52f --- /dev/null +++ b/core/shortcut_items.js @@ -0,0 +1,263 @@ +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Registers default keyboard shortcuts. + * @author aschmiedt@google.com (Abby Schmiedt) + */ +'use strict'; + +/** + * @name Blockly.ShortcutItems + * @namespace + */ +goog.provide('Blockly.ShortcutItems'); + +goog.require('Blockly.utils.KeyCodes'); + + +/** + * Object holding the names of the default shortcut items. + * @enum {string} + */ +Blockly.ShortcutItems.names = { + ESCAPE: 'escape', + DELETE: 'delete', + COPY: 'copy', + CUT: 'cut', + PASTE: 'paste', + UNDO: 'undo', + REDO: 'redo' +}; + +/** Keyboard shortcut to hide chaff on escape. */ +Blockly.ShortcutItems.registerEscape = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var escapeAction = { + name: Blockly.ShortcutItems.names.ESCAPE, + preconditionFn: function(workspace) { + return !workspace.options.readOnly; + }, + callback: function() { + Blockly.hideChaff(); + return true; + } + }; + Blockly.ShortcutRegistry.registry.register(escapeAction); + Blockly.ShortcutRegistry.registry.addKeyMapping( + Blockly.utils.KeyCodes.ESC, escapeAction.name); +}; + +/** Keyboard shortcut to delete a block on delete or backspace */ +Blockly.ShortcutItems.registerDelete = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var deleteShortcut = { + name: Blockly.ShortcutItems.names.DELETE, + preconditionFn: function(workspace) { + return !workspace.options.readOnly && + Blockly.selected && + Blockly.selected.isDeletable(); + }, + callback: function(workspace, e) { + // Delete or backspace. + // Stop the browser from going back to the previous page. + // Do this first to prevent an error in the delete code from resulting in + // data loss. + e.preventDefault(); + // Don't delete while dragging. Jeez. + if (Blockly.Gesture.inProgress()) { + return false; + } + Blockly.deleteBlock(/** @type {!Blockly.BlockSvg} */ (Blockly.selected)); + return true; + } + }; + Blockly.ShortcutRegistry.registry.register(deleteShortcut); + Blockly.ShortcutRegistry.registry.addKeyMapping( + Blockly.utils.KeyCodes.DELETE, deleteShortcut.name); + Blockly.ShortcutRegistry.registry.addKeyMapping( + Blockly.utils.KeyCodes.BACKSPACE, deleteShortcut.name); +}; + +/** Keyboard shortcut to copy a block on ctrl+c, cmd+c, or alt+c. */ +Blockly.ShortcutItems.registerCopy = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var copyShortcut = { + name: Blockly.ShortcutItems.names.COPY, + preconditionFn: function(workspace) { + return !workspace.options.readOnly && + !Blockly.Gesture.inProgress() && + Blockly.selected && + Blockly.selected.isDeletable() && + Blockly.selected.isMovable(); + }, + callback: function() { + Blockly.hideChaff(); + Blockly.copy(/** @type {!Blockly.ICopyable} */ (Blockly.selected)); + return true; + } + }; + Blockly.ShortcutRegistry.registry.register(copyShortcut); + + var ctrlC = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.C, [Blockly.utils.KeyCodes.CTRL]); + Blockly.ShortcutRegistry.registry.addKeyMapping(ctrlC, copyShortcut.name); + + var altC = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.C, [Blockly.utils.KeyCodes.ALT]); + Blockly.ShortcutRegistry.registry.addKeyMapping(altC, copyShortcut.name); + + var metaC = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.C, [Blockly.utils.KeyCodes.META]); + Blockly.ShortcutRegistry.registry.addKeyMapping(metaC, copyShortcut.name); +}; + +/** Keyboard shortcut to copy and delete a block on ctrl+x, cmd+x, or alt+x. */ +Blockly.ShortcutItems.registerCut = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var cutShortcut = { + name: Blockly.ShortcutItems.names.CUT, + preconditionFn: function(workspace) { + return !workspace.options.readOnly && + !Blockly.Gesture.inProgress() && + Blockly.selected && + Blockly.selected.isDeletable() && + Blockly.selected.isMovable() && + !Blockly.selected.workspace.isFlyout; + }, + callback: function() { + Blockly.copy(/** @type {!Blockly.ICopyable} */ (Blockly.selected)); + Blockly.deleteBlock(/** @type {!Blockly.BlockSvg} */ (Blockly.selected)); + return true; + } + }; + + Blockly.ShortcutRegistry.registry.register(cutShortcut); + + var ctrlX = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.X, [Blockly.utils.KeyCodes.CTRL]); + Blockly.ShortcutRegistry.registry.addKeyMapping(ctrlX, cutShortcut.name); + + var altX = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.X, [Blockly.utils.KeyCodes.ALT]); + Blockly.ShortcutRegistry.registry.addKeyMapping(altX, cutShortcut.name); + + var metaX = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.X, [Blockly.utils.KeyCodes.META]); + Blockly.ShortcutRegistry.registry.addKeyMapping(metaX, cutShortcut.name); +}; + +/** Keyboard shortcut to paste a block on ctrl+v, cmd+v, or alt+v. */ +Blockly.ShortcutItems.registerPaste = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var pasteShortcut = { + name: Blockly.ShortcutItems.names.PASTE, + preconditionFn: function(workspace) { + return !workspace.options.readOnly && !Blockly.Gesture.inProgress(); + }, + callback: function() { + return Blockly.paste(); + } + }; + + Blockly.ShortcutRegistry.registry.register(pasteShortcut); + + var ctrlV = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.V, [Blockly.utils.KeyCodes.CTRL]); + Blockly.ShortcutRegistry.registry.addKeyMapping(ctrlV, pasteShortcut.name); + + var altV = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.V, [Blockly.utils.KeyCodes.ALT]); + Blockly.ShortcutRegistry.registry.addKeyMapping(altV, pasteShortcut.name); + + var metaV = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.V, [Blockly.utils.KeyCodes.META]); + Blockly.ShortcutRegistry.registry.addKeyMapping(metaV, pasteShortcut.name); +}; + +/** Keyboard shortcut to undo the previous action on ctrl+z, cmd+z, or alt+z. */ +Blockly.ShortcutItems.registerUndo = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var undoShortcut = { + name: Blockly.ShortcutItems.names.UNDO, + preconditionFn: function(workspace) { + return !workspace.options.readOnly && + !Blockly.Gesture.inProgress(); + }, + callback: function(workspace) { + // 'z' for undo 'Z' is for redo. + Blockly.hideChaff(); + workspace.undo(false); + return true; + } + }; + Blockly.ShortcutRegistry.registry.register(undoShortcut); + + var ctrlZ = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.Z, [Blockly.utils.KeyCodes.CTRL]); + Blockly.ShortcutRegistry.registry.addKeyMapping(ctrlZ, undoShortcut.name); + + var altZ = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.Z, [Blockly.utils.KeyCodes.ALT]); + Blockly.ShortcutRegistry.registry.addKeyMapping(altZ, undoShortcut.name); + + var metaZ = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.Z, [Blockly.utils.KeyCodes.META]); + Blockly.ShortcutRegistry.registry.addKeyMapping(metaZ, undoShortcut.name); +}; + +/** Keyboard shortcut to redo the previous action on ctrl+shift+z, cmd+shift+z, or alt+shift+z. */ +Blockly.ShortcutItems.registerRedo = function() { + /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ + var redoShortcut = { + name: Blockly.ShortcutItems.names.REDO, + preconditionFn: function(workspace) { + return !Blockly.Gesture.inProgress() && !workspace.options.readOnly; + }, + callback: function(workspace) { + // 'z' for undo 'Z' is for redo. + Blockly.hideChaff(); + workspace.undo(true); + return true; + } + }; + Blockly.ShortcutRegistry.registry.register(redoShortcut); + + var ctrlShiftZ = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.Z, [Blockly.utils.KeyCodes.SHIFT, + Blockly.utils.KeyCodes.CTRL]); + Blockly.ShortcutRegistry.registry.addKeyMapping(ctrlShiftZ, redoShortcut.name); + + var altShiftZ = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.Z, [Blockly.utils.KeyCodes.SHIFT, + Blockly.utils.KeyCodes.ALT]); + Blockly.ShortcutRegistry.registry.addKeyMapping(altShiftZ, redoShortcut.name); + + var metaShiftZ = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.Z, [Blockly.utils.KeyCodes.SHIFT, + Blockly.utils.KeyCodes.META]); + Blockly.ShortcutRegistry.registry.addKeyMapping(metaShiftZ, redoShortcut.name); + + // Ctrl-y is redo in Windows. Command-y is never valid on Macs. + var ctrlY = Blockly.ShortcutRegistry.registry.createSerializedKey( + Blockly.utils.KeyCodes.Y, [Blockly.utils.KeyCodes.CTRL]); + Blockly.ShortcutRegistry.registry.addKeyMapping(ctrlY, redoShortcut.name); +}; + +/** + * Registers all default keyboard shortcut item. This should be called once per instance of + * KeyboardShortcutRegistry. + * @package + */ +Blockly.ShortcutItems.registerDefaultShortcuts = function() { + Blockly.ShortcutItems.registerEscape(); + Blockly.ShortcutItems.registerDelete(); + Blockly.ShortcutItems.registerCopy(); + Blockly.ShortcutItems.registerCut(); + Blockly.ShortcutItems.registerPaste(); + Blockly.ShortcutItems.registerUndo(); + Blockly.ShortcutItems.registerRedo(); +}; diff --git a/core/shortcut_registry.js b/core/shortcut_registry.js new file mode 100644 index 000000000..f5b978915 --- /dev/null +++ b/core/shortcut_registry.js @@ -0,0 +1,347 @@ +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview The namespace used to keep track of keyboard shortcuts and the + * key codes used to execute those shortcuts. + * @author aschmiedt@google.com (Abby Schmiedt) + */ +'use strict'; + +goog.provide('Blockly.ShortcutRegistry'); + +goog.require('Blockly.navigation'); +goog.require('Blockly.ShortcutItems'); +goog.require('Blockly.utils.object'); + + +/** + * Class for the registry of keyboard shortcuts. This is intended to be a + * singleton. You should not create a new instance, and only access this class + * from Blockly.ShortcutRegistry.registry. + * @constructor + */ +Blockly.ShortcutRegistry = function() { + // Singleton instance should be registered once. + Blockly.ShortcutRegistry.registry = this; + + /** + * Registry of all keyboard shortcuts, keyed by name of shortcut. + * @type {!Object} + * @private + */ + this.registry_ = Object.create(null); + + /** + * Map of key codes to an array of shortcut names. + * @type {!Object>} + * @private + */ + this.keyMap_ = Object.create(null); + + Blockly.ShortcutItems.registerDefaultShortcuts(); + Blockly.navigation.registerNavigationShortcuts(); +}; + +/** + * Enum of valid modifiers. + * @enum {!Blockly.utils.KeyCodes} + */ +Blockly.ShortcutRegistry.modifierKeys = { + Shift: Blockly.utils.KeyCodes.SHIFT, + Control: Blockly.utils.KeyCodes.CTRL, + Alt: Blockly.utils.KeyCodes.ALT, + Meta: Blockly.utils.KeyCodes.META +}; + +/** + * A keyboard shortcut. + * @typedef {{ + * callback: ((function(!Blockly.Workspace, Event, + * !Blockly.ShortcutRegistry.KeyboardShortcut):boolean)|undefined), + * name: string, + * preconditionFn: ((function(!Blockly.Workspace):boolean)|undefined), + * metadata: (Object|undefined) + * }} + */ +Blockly.ShortcutRegistry.KeyboardShortcut; + +/** + * Registers a keyboard shortcut. + * @param {!Blockly.ShortcutRegistry.KeyboardShortcut} shortcut The + * shortcut for this key code. + * @param {boolean=} opt_allowOverrides True to prevent a warning when + * overriding an already registered item. + * @throws {Error} if a shortcut with the same name already exists. + * @public + */ +Blockly.ShortcutRegistry.prototype.register = function( + shortcut, opt_allowOverrides) { + var registeredShortcut = this.registry_[shortcut.name]; + if (registeredShortcut && !opt_allowOverrides) { + throw new Error( + 'Shortcut with name "' + shortcut.name + '" already exists.'); + } + this.registry_[shortcut.name] = shortcut; +}; + +/** + * Unregisters a keyboard shortcut registered with the given key code. This will + * also remove any key mappings that reference this shortcut. + * @param {string} shortcutName The name of the shortcut to unregister. + * @return {boolean} True if an item was unregistered, false otherwise. + * @public + */ +Blockly.ShortcutRegistry.prototype.unregister = function(shortcutName) { + var shortcut = this.registry_[shortcutName]; + + if (!shortcut) { + console.warn( + 'Keyboard shortcut with name "' + shortcutName + '" not found.'); + return false; + } + + // Remove all key mappings with this shortcut. + for (var keyCode in this.keyMap_) { + this.removeKeyMapping(keyCode, shortcutName, true); + } + + delete this.registry_[shortcutName]; + return true; +}; + +/** + * Adds a mapping between a keycode and a keyboard shortcut. + * @param {string} keyCode The key code for the keyboard shortcut. If + * registering a key code with a modifier (ex: ctrl+c) use + * Blockly.ShortcutRegistry.registry.createSerializedKey; + * @param {string} shortcutName The name of the shortcut to execute when the + * given keycode is pressed. + * @param {boolean=} opt_allowCollision True to prevent an error when adding a + * shortcut to a key that is already mapped to a shortcut. + * @throws {Error} if the given key code is already mapped to a shortcut. + * @public + */ +Blockly.ShortcutRegistry.prototype.addKeyMapping = function( + keyCode, shortcutName, opt_allowCollision) { + var shortcutNames = this.keyMap_[keyCode]; + if (shortcutNames && !opt_allowCollision) { + throw new Error( + 'Shortcut with name "' + shortcutName + '" collides with shortcuts ' + + shortcutNames.toString()); + } else if (shortcutNames && opt_allowCollision) { + shortcutNames.unshift(shortcutName); + } else { + this.keyMap_[keyCode] = [shortcutName]; + } +}; + +/** + * Removes a mapping between a keycode and a keyboard shortcut. + * @param {string} keyCode The key code for the keyboard shortcut. If + * registering a key code with a modifier (ex: ctrl+c) use + * Blockly.ShortcutRegistry.registry.createSerializedKey; + * @param {string} shortcutName The name of the shortcut to execute when the + * given keycode is pressed. + * @param {boolean=} opt_quiet True to not console warn when there is no + * shortcut to remove. + * @return {boolean} True if a key mapping was removed, false otherwise. + * @public + */ +Blockly.ShortcutRegistry.prototype.removeKeyMapping = function( + keyCode, shortcutName, opt_quiet) { + var shortcutNames = this.keyMap_[keyCode]; + + if (!shortcutNames && !opt_quiet) { + console.warn( + 'No keyboard shortcut with name "' + shortcutName + + '" registered with key code "' + keyCode + '"'); + return false; + } + + var shortcutIdx = shortcutNames.indexOf(shortcutName); + if (shortcutIdx > -1) { + shortcutNames.splice(shortcutIdx, 1); + if (shortcutNames.length == 0) { + delete this.keyMap_[keyCode]; + } + return true; + } else if (!opt_quiet) { + console.warn( + 'No keyboard shortcut with name "' + shortcutName + + '" registered with key code "' + keyCode + '"'); + return false; + } + return false; +}; + +/** + * Sets the key map. Setting the key map will override any default key mappings. + * @param {!Object>} keyMap The object with key code to + * shortcut names. + * @public + */ +Blockly.ShortcutRegistry.prototype.setKeyMap = function(keyMap) { + this.keyMap_ = keyMap; +}; + +/** + * Gets the current key map. + * @return {!Object>} + * The object holding key codes to Blockly.ShortcutRegistry.KeyboardShortcut. + * @public + */ +Blockly.ShortcutRegistry.prototype.getKeyMap = function() { + return Blockly.utils.object.deepMerge(Object.create(null), this.keyMap_); +}; + +/** + * Gets the registry of keyboard shortcuts. + * @return {!Object} + * The registry of keyboard shortcuts. + * @public + */ +Blockly.ShortcutRegistry.prototype.getRegistry = function() { + return Blockly.utils.object.deepMerge(Object.create(null), this.registry_); +}; + +/** + * Handles key down events. + * @param {!Blockly.Workspace} workspace The main workspace where the event was + * captured. + * @param {!Event} e The key down event. + * @return {boolean} True if the event was handled, false otherwise. + * @public + */ +Blockly.ShortcutRegistry.prototype.onKeyDown = function(workspace, e) { + var key = this.serializeKeyEvent_(e); + var shortcutNames = this.getKeyboardShortcuts(key); + if (!shortcutNames) { + return false; + } + for (var i = 0, shortcutName; (shortcutName = shortcutNames[i]); i++) { + var shortcut = this.registry_[shortcutName]; + if (!shortcut.preconditionFn || shortcut.preconditionFn(workspace)) { + // If the key has been handled, stop processing shortcuts. + if (shortcut.callback && shortcut.callback(workspace, e, shortcut)) { + return true; + } + } + } + return false; +}; + +/** + * Gets the shortcuts registered to the given key code. + * @param {string} keyCode The serialized key code. + * @return {!Array|undefined} The list of shortcuts to call when the + * given keyCode is used. Undefined if no shortcuts exist. + * @public + */ +Blockly.ShortcutRegistry.prototype.getKeyboardShortcuts = function( + keyCode) { + return this.keyMap_[keyCode] || []; +}; + +/** + * Gets the serialized key codes that the shortcut with the given name is + * registered under. + * @param {string} shortcutName The name of the shortcut. + * @return {!Array} An array with all the key codes the shortcut is + * registered under. + * @public + */ +Blockly.ShortcutRegistry.prototype.getKeyCodeByShortcutName = function( + shortcutName) { + var keys = []; + for (var keyCode in this.keyMap_) { + var shortcuts = this.keyMap_[keyCode]; + var shortcutIdx = shortcuts.indexOf(shortcutName); + if (shortcutIdx > -1) { + keys.push(keyCode); + } + } + return keys; +}; + +/** + * Serializes a key event. + * @param {!Event} e A key down event. + * @return {string} The serialized key code for the given event. + * @private + */ +Blockly.ShortcutRegistry.prototype.serializeKeyEvent_ = function(e) { + var serializedKey = ''; + for (var modifier in Blockly.ShortcutRegistry.modifierKeys) { + if (e.getModifierState(modifier)) { + if (serializedKey != '') { + serializedKey += '+'; + } + serializedKey += modifier; + } + } + if (serializedKey != '' && e.keyCode) { + serializedKey = serializedKey + '+' + e.keyCode; + } else if (e.keyCode) { + serializedKey = e.keyCode.toString(); + } + return serializedKey; +}; + +/** + * Checks whether any of the given modifiers are not valid. + * @param {!Array} modifiers List of modifiers to be used with the key. + * @throws {Error} if the modifier is not in the valid modifiers list. + * @private + */ +Blockly.ShortcutRegistry.prototype.checkModifiers_ = function( + modifiers) { + var validModifiers = Blockly.utils.object.values( + Blockly.ShortcutRegistry.modifierKeys); + for (var i = 0, modifier; (modifier = modifiers[i]); i++) { + if (validModifiers.indexOf(modifier) < 0) { + throw new Error(modifier + ' is not a valid modifier key.'); + } + } +}; + +/** + * Creates the serialized key code that will be used in the key map. + * @param {number} keyCode Number code representing the key. + * @param {?Array.} modifiers List of modifier key codes to be used with + * the key. All valid modifiers can be found in the + * Blockly.ShortcutRegistry.modifierKeys. + * @return {string} The serialized key code for the given modifiers and key. + * @public + */ +Blockly.ShortcutRegistry.prototype.createSerializedKey = function( + keyCode, modifiers) { + var serializedKey = ''; + + if (modifiers) { + this.checkModifiers_(modifiers); + for (var modifier in Blockly.ShortcutRegistry.modifierKeys) { + var modifierKeyCode = + Blockly.ShortcutRegistry.modifierKeys[modifier]; + if (modifiers.indexOf(modifierKeyCode) > -1) { + if (serializedKey != '') { + serializedKey += '+'; + } + serializedKey += modifier; + } + } + } + + if (serializedKey != '' && keyCode) { + serializedKey = serializedKey + '+' + keyCode; + } else if (keyCode) { + serializedKey = keyCode.toString(); + } + return serializedKey; +}; + +// Creates and assigns the singleton instance. +new Blockly.ShortcutRegistry(); diff --git a/core/toolbox/toolbox.js b/core/toolbox/toolbox.js index db713bf51..bf82cb8c1 100644 --- a/core/toolbox/toolbox.js +++ b/core/toolbox/toolbox.js @@ -26,7 +26,6 @@ goog.require('Blockly.utils.dom'); goog.require('Blockly.utils.Rect'); goog.require('Blockly.utils.toolbox'); -goog.requireType('Blockly.Action'); goog.requireType('Blockly.IBlocklyActionable'); goog.requireType('Blockly.ICollapsibleToolboxItem'); goog.requireType('Blockly.IDeleteArea'); @@ -35,6 +34,7 @@ goog.requireType('Blockly.ISelectableToolboxItem'); goog.requireType('Blockly.IStyleable'); goog.requireType('Blockly.IToolbox'); goog.requireType('Blockly.IToolboxItem'); +goog.requireType('Blockly.ShortcutRegistry'); goog.requireType('Blockly.WorkspaceSvg'); @@ -816,7 +816,7 @@ Blockly.Toolbox.prototype.fireSelectEvent_ = function(oldItem, newItem) { /** * Handles the given Blockly action on a toolbox. * This is only triggered when keyboard accessibility mode is enabled. - * @param {!Blockly.Action} action The action to be handled. + * @param {!Blockly.ShortcutRegistry.KeyboardShortcut} action The action to be handled. * @return {boolean} True if the field handled the action, false otherwise. * @package */ diff --git a/demos/keyboard_nav/index.html b/demos/keyboard_nav/index.html index 53408c57e..b4f601bca 100644 --- a/demos/keyboard_nav/index.html +++ b/demos/keyboard_nav/index.html @@ -1,570 +1,11 @@ - - Blockly Demo: Keyboard Navigation - - - - - - + -

Blockly > - Demos > Keyboard Navigation

- -

Keyboard Navigation is our first step towards an accessible Blockly.
- For more information on how the default keyboard navigation works please see - the documentation. -
-
- Pre Order Traversal
- Feel free to just play around in accessibility mode or hit the button below to see the demo. - The demo uses preorder tree traversal - as an alternative way to navigate the blocks, - connections, and fields on the workspace.

- - Cursors
- The cursor controls how the user navigates the blocks, inputs, fields and connections on a workspace. - This demo shows three different cursors:
- Default Cursor: Explained in documentation.
- Basic Cursor: Uses pre order traversal to allow users to navigate - through everything using only the previous and next command.
- Line Cursor: We tried to make this cursor mimic a text editor. Navigating - up and down will take the cursor to the next and previous "line" of code. - Navigating in and out will move the cursor through all the fields and inputs - in that "line" of code. -

- -

- - - - - - - - -

- -
-
- -
- - - - - - - + +

This demo is in the process of moving to a repository!

+ diff --git a/demos/keyboard_nav/line_cursor.js b/demos/keyboard_nav/line_cursor.js deleted file mode 100644 index d9cb80689..000000000 --- a/demos/keyboard_nav/line_cursor.js +++ /dev/null @@ -1,169 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview The class representing a line cursor. - * A line cursor traverses the blocks as if they were - * lines of code in a text editor. - * Previous and next go up and down lines. In and out go - * through the elements in a line. - * @author aschmiedt@google.com (Abby Schmiedt) - */ -'use strict'; - - -/** - * Class for a line cursor. - * This will allow the user to get to all nodes in the AST by hitting next or - * previous. - * @constructor - * @extends {Blockly.BasicCursor} - */ -Blockly.LineCursor = function() { - Blockly.LineCursor.superClass_.constructor.call(this); -}; -Blockly.utils.object.inherits(Blockly.LineCursor, Blockly.BasicCursor); - - -/** - * Find the next node in the pre order traversal. - * @return {Blockly.ASTNode} The next node, or null if the current node is - * not set or there is no next value. - * @override - */ -Blockly.LineCursor.prototype.next = function() { - var curNode = this.getCurNode(); - if (!curNode) { - return null; - } - var newNode = this.getNextNode_(curNode, this.validLineNode_); - - // Skip the input or next value if there is a connected block. - if (newNode && (newNode.getType() == Blockly.ASTNode.types.INPUT || - newNode.getType() == Blockly.ASTNode.types.NEXT) && - newNode.getLocation().targetBlock()) { - newNode = this.getNextNode_(newNode, this.validLineNode_); - } - if (newNode) { - this.setCurNode(newNode); - } - return newNode; -}; - -/** - * For a basic cursor we only have the ability to go next and previous, so - * in will also allow the user to get to the next node in the pre order traversal. - * @return {Blockly.ASTNode} The next node, or null if the current node is - * not set or there is no next value. - * @override - */ -Blockly.LineCursor.prototype.in = function() { - var curNode = this.getCurNode(); - if (!curNode) { - return null; - } - var newNode = this.getNextNode_(curNode, this.validInLineNode_); - - if (newNode) { - this.setCurNode(newNode); - } - return newNode; -}; - -/** - * Find the previous node in the pre order traversal. - * @return {Blockly.ASTNode} The previous node, or null if the current node - * is not set or there is no previous value. - * @override - */ -Blockly.LineCursor.prototype.prev = function() { - var curNode = this.getCurNode(); - if (!curNode) { - return null; - } - var newNode = this.getPreviousNode_(curNode, this.validLineNode_); - - if (newNode && (newNode.getType() == Blockly.ASTNode.types.INPUT || - newNode.getType() == Blockly.ASTNode.types.NEXT) && - newNode.getLocation().targetBlock()) { - newNode = this.getPreviousNode_(newNode, this.validLineNode_); - } - - if (newNode) { - this.setCurNode(newNode); - } - return newNode; -}; - -/** - * For a basic cursor we only have the ability to go next and previous, so - * out will allow the user to get to the previous node in the pre order traversal. - * @return {Blockly.ASTNode} The previous node, or null if the current node is - * not set or there is no previous value. - * @override - */ -Blockly.LineCursor.prototype.out = function() { - var curNode = this.getCurNode(); - if (!curNode) { - return null; - } - var newNode = this.getPreviousNode_(curNode, this.validInLineNode_); - - if (newNode) { - this.setCurNode(newNode); - } - return newNode; - -}; - -/** - * Meant to traverse by lines of code. This is blocks, statement inputs and - * next connections. - * @param {Blockly.ASTNode} node The AST node to check whether it is valid. - * @return {boolean} True if the node should be visited, false otherwise. - * @private - */ -Blockly.LineCursor.prototype.validLineNode_ = function(node) { - if (!node) { - return false; - } - var isValid = false; - var location = node.getLocation(); - var type = node && node.getType(); - if (type == Blockly.ASTNode.types.BLOCK) { - if (location.outputConnection === null) { - isValid = true; - } - } else if (type == Blockly.ASTNode.types.INPUT && - location.type == Blockly.NEXT_STATEMENT) { - isValid = true; - } else if (type == Blockly.ASTNode.types.NEXT) { - isValid = true; - } - return isValid; -}; - -/** - * Meant to traverse within a block. These are fields and input values. - * @param {Blockly.ASTNode} node The AST node to check whether it is valid. - * @return {boolean} True if the node should be visited, false otherwise. - * @private - */ -Blockly.LineCursor.prototype.validInLineNode_ = function(node) { - if (!node) { - return false; - } - var isValid = false; - var location = node.getLocation(); - var type = node && node.getType(); - if (type == Blockly.ASTNode.types.FIELD) { - isValid = true; - } else if (type == Blockly.ASTNode.types.INPUT && - location.type == Blockly.INPUT_VALUE) { - isValid = true; - } - return isValid; -}; diff --git a/tests/mocha/index.html b/tests/mocha/index.html index 2ee094490..36fc09426 100644 --- a/tests/mocha/index.html +++ b/tests/mocha/index.html @@ -81,7 +81,7 @@ - + diff --git a/tests/mocha/key_map_test.js b/tests/mocha/key_map_test.js deleted file mode 100644 index 301a2a28b..000000000 --- a/tests/mocha/key_map_test.js +++ /dev/null @@ -1,97 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -suite('Key Map Tests', function() { - setup(function() { - sharedTestSetup.call(this); - Blockly.user.keyMap.setKeyMap(Blockly.user.keyMap.createDefaultKeyMap()); - }); - teardown(function() { - sharedTestTeardown.call(this); - }); - - test('Test adding a new action to key map', function() { - var newAction = new Blockly.Action('test_action', 'test', function(){ - return "test"; - }); - Blockly.user.keyMap.setActionForKey('65', newAction); - chai.assert.equal(Blockly.user.keyMap.map_['65'].name, 'test_action'); - }); - - test('Test giving an old action a new key', function() { - Blockly.user.keyMap.setActionForKey(Blockly.utils.KeyCodes.F, - Blockly.navigation.ACTION_PREVIOUS); - chai.assert.isUndefined(Blockly.user.keyMap.map_[Blockly.utils.KeyCodes.W]); - chai.assert.equal(Blockly.user.keyMap.map_[Blockly.utils.KeyCodes.F], - Blockly.navigation.ACTION_PREVIOUS); - }); - - test('Test get key by action defined', function() { - var key = Blockly.user.keyMap.getKeyByAction(Blockly.navigation.ACTION_PREVIOUS); - chai.assert.equal(key, Blockly.utils.KeyCodes.W); - }); - - test('Test get key by action not defined', function() { - var key = Blockly.user.keyMap.getKeyByAction(new Blockly.Action('something')); - chai.assert.notExists(key); - }); - - test('Test set key map', function() { - var testKeyMap = Blockly.user.keyMap.createDefaultKeyMap(); - testKeyMap['randomKey'] = new Blockly.Action('test','',null); - Blockly.user.keyMap.setKeyMap(testKeyMap); - chai.assert.equal(Blockly.user.keyMap.map_['randomKey'].name, 'test'); - }); - - test('Test get key map returns a clone', function() { - var keyMap = Blockly.user.keyMap.getKeyMap(); - keyMap['randomKey'] = new Blockly.Action('test', '', null); - chai.assert.isUndefined(Blockly.user.keyMap.map_['randomKey']); - }); - - test('Test serialize key code with modifiers', function() { - var mockEvent = { - getModifierState: function(){ - return true; - }, - keyCode: 65 - }; - var serializedKey = Blockly.user.keyMap.serializeKeyEvent(mockEvent); - chai.assert.equal(serializedKey, 'ShiftControlAltMeta65'); - }); - - test('Test serialize key code without modifiers', function() { - var mockEvent = { - getModifierState: function(){ - return false; - }, - keyCode: 65 - }; - var serializedKey = Blockly.user.keyMap.serializeKeyEvent(mockEvent); - chai.assert.equal(serializedKey, '65'); - }); - - test('Test modifiers in reverse order', function() { - var testKey = Blockly.user.keyMap.createSerializedKey( - Blockly.utils.KeyCodes.K, [Blockly.user.keyMap.modifierKeys.CONTROL, - Blockly.user.keyMap.modifierKeys.SHIFT]); - Blockly.user.keyMap.setActionForKey(testKey, new Blockly.Action('test', '', null)); - var action = Blockly.user.keyMap.getActionByKeyCode('ShiftControl75'); - chai.assert.isNotNull(action); - chai.assert.equal(action.name, 'test'); - }); - - test('Test report invalid modifiers', function() { - var shouldThrow = function() { - Blockly.user.keyMap.createSerializedKey(Blockly.utils.KeyCodes.K, ['s', - Blockly.user.keyMap.modifierKeys.SHIFT]); - }; - chai.assert.throws(shouldThrow, Error, 's is not a valid modifier key.'); - }); - - - teardown(function() {}); -}); diff --git a/tests/mocha/keydown_test.js b/tests/mocha/keydown_test.js index d41517c30..d989d9614 100644 --- a/tests/mocha/keydown_test.js +++ b/tests/mocha/keydown_test.js @@ -93,7 +93,7 @@ suite('Key Down', function() { suite('Copy', function() { setup(function() { setSelectedBlock(this.workspace); - this.copySpy = sinon.spy(Blockly, 'copy_'); + this.copySpy = sinon.spy(Blockly, 'copy'); this.hideChaffSpy = sinon.spy(Blockly, 'hideChaff'); }); var testCases = [ diff --git a/tests/mocha/navigation_test.js b/tests/mocha/navigation_test.js index 9e63cbf45..9e3e31901 100644 --- a/tests/mocha/navigation_test.js +++ b/tests/mocha/navigation_test.js @@ -12,9 +12,10 @@ suite('Navigation', function() { - function createNavigationWorkspace(enableKeyboardNav) { + function createNavigationWorkspace(enableKeyboardNav, readOnly) { var toolbox = document.getElementById('toolbox-categories'); - var workspace = Blockly.inject('blocklyDiv', {toolbox: toolbox}); + var workspace = + Blockly.inject('blocklyDiv', {toolbox: toolbox, readOnly: readOnly}); if (enableKeyboardNav) { Blockly.navigation.enableKeyboardAccessibility(); Blockly.navigation.currentState_ = Blockly.navigation.STATE_WS; @@ -45,64 +46,88 @@ suite('Navigation', function() { ] }]); this.workspace = createNavigationWorkspace(true); - Blockly.navigation.focusToolbox_(); - this.mockEvent = { - getModifierState: function() { - return false; - } - }; + Blockly.navigation.focusToolbox_(this.workspace); }); teardown(function() { workspaceTeardown.call(this, this.workspace); }); - function testToolboxSelectMethodCalled(ws, mockEvent, keyCode, selectMethodName) { - mockEvent.keyCode = keyCode; - var toolbox = ws.getToolbox(); - toolbox.selectedItem_ = toolbox.contents_[0]; - var selectNextStub = sinon.stub(toolbox, selectMethodName); - Blockly.navigation.onKeyPress(mockEvent); - sinon.assert.called(selectNextStub); - } + var testCases = [ + [ + 'Calls toolbox selectNext_', + createKeyDownEvent(Blockly.utils.KeyCodes.S, 'NotAField'), 'selectNext_' + ], + [ + 'Calls toolbox selectPrevious_', + createKeyDownEvent(Blockly.utils.KeyCodes.W, 'NotAField'), + 'selectPrevious_' + ], + [ + 'Calls toolbox selectParent_', + createKeyDownEvent(Blockly.utils.KeyCodes.D, 'NotAField'), + 'selectChild_' + ], + [ + 'Calls toolbox selectChild_', + createKeyDownEvent(Blockly.utils.KeyCodes.A, 'NotAField'), + 'selectParent_' + ] + ]; - test('Calls toolbox selectNext_', function() { - testToolboxSelectMethodCalled(this.workspace, this.mockEvent, Blockly.utils.KeyCodes.S, 'selectNext_'); - }); - test('Calls toolbox selectPrevious_', function() { - testToolboxSelectMethodCalled(this.workspace, this.mockEvent, Blockly.utils.KeyCodes.W, 'selectPrevious_'); - }); - test('Calls toolbox selectParent_', function() { - testToolboxSelectMethodCalled(this.workspace, this.mockEvent, Blockly.utils.KeyCodes.D, 'selectChild_'); - }); - test('Calls toolbox selectChild_', function() { - testToolboxSelectMethodCalled(this.workspace, this.mockEvent, Blockly.utils.KeyCodes.A, 'selectParent_'); + testCases.forEach(function(testCase) { + var testCaseName = testCase[0]; + var mockEvent = testCase[1]; + var stubName = testCase[2]; + test(testCaseName, function() { + var toolbox = this.workspace.getToolbox(); + var selectStub = sinon.stub(toolbox, stubName); + toolbox.selectedItem_ = toolbox.contents_[0]; + Blockly.onKeyDown(mockEvent); + sinon.assert.called(selectStub); + }); }); test('Go to flyout', function() { - this.mockEvent.keyCode = Blockly.utils.KeyCodes.D; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); - chai.assert.equal(Blockly.navigation.currentState_, - Blockly.navigation.STATE_FLYOUT); - var flyoutCursor = Blockly.navigation.getFlyoutCursor_(); + var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.D, 'NotAField'); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + Blockly.onKeyDown(mockEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + chai.assert.equal( + Blockly.navigation.currentState_, Blockly.navigation.STATE_FLYOUT); + + var flyoutCursor = Blockly.navigation.getFlyoutCursor_(); chai.assert.equal(flyoutCursor.getCurNode().getLocation().getFieldValue("TEXT"), "FirstCategory-FirstBlock"); }); test('Focuses workspace from toolbox (e)', function() { Blockly.navigation.currentState_ = Blockly.navigation.STATE_TOOLBOX; - this.mockEvent.keyCode = Blockly.utils.KeyCodes.E; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); - chai.assert.equal(Blockly.navigation.currentState_, - Blockly.navigation.STATE_WS); + var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.E, 'NotAField'); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + + Blockly.onKeyDown(mockEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + chai.assert.equal( + Blockly.navigation.currentState_, Blockly.navigation.STATE_WS); }); test('Focuses workspace from toolbox (escape)', function() { Blockly.navigation.currentState_ = Blockly.navigation.STATE_TOOLBOX; - this.mockEvent.keyCode = Blockly.utils.KeyCodes.E; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); - chai.assert.equal(Blockly.navigation.currentState_, - Blockly.navigation.STATE_WS); + var mockEvent = + createKeyDownEvent(Blockly.utils.KeyCodes.ESC, 'NotAField'); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + + Blockly.onKeyDown(mockEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + chai.assert.equal( + Blockly.navigation.currentState_, Blockly.navigation.STATE_WS); }); // More tests: // - nested categories @@ -124,14 +149,8 @@ suite('Navigation', function() { ] }]); this.workspace = createNavigationWorkspace(true); - Blockly.mainWorkspace = this.workspace; - Blockly.navigation.focusToolbox_(); - Blockly.navigation.focusFlyout_(); - this.mockEvent = { - getModifierState: function() { - return false; - } - }; + Blockly.navigation.focusToolbox_(this.workspace); + Blockly.navigation.focusFlyout_(this.workspace); }); teardown(function() { @@ -140,10 +159,15 @@ suite('Navigation', function() { // Should be a no-op test('Previous at beginning', function() { - this.mockEvent.keyCode = Blockly.utils.KeyCodes.W; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); - chai.assert.equal(Blockly.navigation.currentState_, - Blockly.navigation.STATE_FLYOUT); + var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.W, 'NotAField'); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + + Blockly.onKeyDown(mockEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + chai.assert.equal( + Blockly.navigation.currentState_, Blockly.navigation.STATE_FLYOUT); chai.assert.equal(Blockly.navigation.getFlyoutCursor_().getCurNode().getLocation().getFieldValue("TEXT"), "FirstCategory-FirstBlock"); }); @@ -155,45 +179,72 @@ suite('Navigation', function() { var flyoutBlock = Blockly.navigation.getFlyoutCursor_().getCurNode().getLocation(); chai.assert.equal(flyoutBlock.getFieldValue("TEXT"), "FirstCategory-SecondBlock"); - this.mockEvent.keyCode = Blockly.utils.KeyCodes.W; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); - chai.assert.equal(Blockly.navigation.currentState_, - Blockly.navigation.STATE_FLYOUT); + var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.W, 'NotAField'); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + + Blockly.onKeyDown(mockEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + chai.assert.equal( + Blockly.navigation.currentState_, Blockly.navigation.STATE_FLYOUT); flyoutBlock = Blockly.navigation.getFlyoutCursor_().getCurNode().getLocation(); chai.assert.equal(flyoutBlock.getFieldValue("TEXT"), "FirstCategory-FirstBlock"); }); test('Next', function() { - this.mockEvent.keyCode = Blockly.utils.KeyCodes.S; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); - chai.assert.equal(Blockly.navigation.currentState_, - Blockly.navigation.STATE_FLYOUT); + var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.S, 'NotAField'); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + + Blockly.onKeyDown(mockEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + chai.assert.equal( + Blockly.navigation.currentState_, Blockly.navigation.STATE_FLYOUT); var flyoutBlock = Blockly.navigation.getFlyoutCursor_().getCurNode().getLocation(); chai.assert.equal(flyoutBlock.getFieldValue("TEXT"), "FirstCategory-SecondBlock"); }); test('Out', function() { - this.mockEvent.keyCode = Blockly.utils.KeyCodes.A; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); - chai.assert.equal(Blockly.navigation.currentState_, - Blockly.navigation.STATE_TOOLBOX); + var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.A, 'NotAField'); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + + Blockly.onKeyDown(mockEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + chai.assert.equal( + Blockly.navigation.currentState_, Blockly.navigation.STATE_TOOLBOX); }); - test('MARK', function() { - this.mockEvent.keyCode = Blockly.utils.KeyCodes.ENTER; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); - chai.assert.equal(Blockly.navigation.currentState_, - Blockly.navigation.STATE_WS); + test('Mark', function() { + var mockEvent = + createKeyDownEvent(Blockly.utils.KeyCodes.ENTER, 'NotAField'); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + + Blockly.onKeyDown(mockEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + chai.assert.equal( + Blockly.navigation.currentState_, Blockly.navigation.STATE_WS); chai.assert.equal(this.workspace.getTopBlocks().length, 1); }); - test('EXIT', function() { - this.mockEvent.keyCode = Blockly.utils.KeyCodes.ESC; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); - chai.assert.equal(Blockly.navigation.currentState_, - Blockly.navigation.STATE_WS); + test('Exit', function() { + var mockEvent = + createKeyDownEvent(Blockly.utils.KeyCodes.ESC, 'NotAField'); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + + Blockly.onKeyDown(mockEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + chai.assert.equal( + Blockly.navigation.currentState_, Blockly.navigation.STATE_WS); }); }); @@ -216,12 +267,6 @@ suite('Navigation', function() { }]); this.workspace = createNavigationWorkspace(true); this.basicBlock = this.workspace.newBlock('basic_block'); - this.firstCategory_ = this.workspace.getToolbox().contents_[0]; - this.mockEvent = { - getModifierState: function() { - return false; - } - }; }); teardown(function() { @@ -229,77 +274,106 @@ suite('Navigation', function() { }); test('Previous', function() { - sinon.spy(this.workspace.getCursor(), 'prev'); - this.mockEvent.keyCode = Blockly.utils.KeyCodes.W; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); - sinon.assert.calledOnce(this.workspace.getCursor().prev); - chai.assert.equal(Blockly.navigation.currentState_, - Blockly.navigation.STATE_WS); - this.workspace.getCursor().prev.restore(); + var prevSpy = sinon.spy(this.workspace.getCursor(), 'prev'); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + var wEvent = createKeyDownEvent(Blockly.utils.KeyCodes.W, ''); + + Blockly.onKeyDown(wEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + sinon.assert.calledOnce(prevSpy); + chai.assert.equal( + Blockly.navigation.currentState_, Blockly.navigation.STATE_WS); }); test('Next', function() { - var cursor = this.workspace.getCursor(); - sinon.spy(cursor, 'next'); - this.mockEvent.keyCode = Blockly.utils.KeyCodes.S; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); - sinon.assert.calledOnce(cursor.next); - chai.assert.equal(Blockly.navigation.currentState_, - Blockly.navigation.STATE_WS); - cursor.next.restore(); + var nextSpy = sinon.spy(this.workspace.getCursor(), 'next'); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + var sEvent = createKeyDownEvent(Blockly.utils.KeyCodes.S, ''); + + Blockly.onKeyDown(sEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + sinon.assert.calledOnce(nextSpy); + chai.assert.equal( + Blockly.navigation.currentState_, Blockly.navigation.STATE_WS); }); test('Out', function() { - var cursor = this.workspace.getCursor(); - sinon.spy(cursor, 'out'); - this.mockEvent.keyCode = Blockly.utils.KeyCodes.A; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); - sinon.assert.calledOnce(cursor.out); - chai.assert.equal(Blockly.navigation.currentState_, - Blockly.navigation.STATE_WS); - cursor.out.restore(); + var outSpy = sinon.spy(this.workspace.getCursor(), 'out'); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + var aEvent = createKeyDownEvent(Blockly.utils.KeyCodes.A, ''); + + Blockly.onKeyDown(aEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + sinon.assert.calledOnce(outSpy); + chai.assert.equal( + Blockly.navigation.currentState_, Blockly.navigation.STATE_WS); }); test('In', function() { - var cursor = this.workspace.getCursor(); - sinon.spy(cursor, 'in'); - this.mockEvent.keyCode = Blockly.utils.KeyCodes.D; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); - sinon.assert.calledOnce(cursor.in); - chai.assert.equal(Blockly.navigation.currentState_, - Blockly.navigation.STATE_WS); - cursor.in.restore(); + var inSpy = sinon.spy(this.workspace.getCursor(), 'in'); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + var dEvent = createKeyDownEvent(Blockly.utils.KeyCodes.D, ''); + + Blockly.onKeyDown(dEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + sinon.assert.calledOnce(inSpy); + chai.assert.equal( + Blockly.navigation.currentState_, Blockly.navigation.STATE_WS); }); test('Insert', function() { // Stub modify as we are not testing its behavior, only if it was called. // Otherwise, there is a warning because there is no marked node. - sinon.stub(Blockly.navigation, 'modify_'); - this.mockEvent.keyCode = Blockly.utils.KeyCodes.I; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); - sinon.assert.calledOnce(Blockly.navigation.modify_); - chai.assert.equal(Blockly.navigation.currentState_, - Blockly.navigation.STATE_WS); - Blockly.navigation.modify_.restore(); + var modifyStub = sinon.stub(Blockly.navigation, 'modify_').returns(true); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + var iEvent = createKeyDownEvent(Blockly.utils.KeyCodes.I, ''); + + Blockly.onKeyDown(iEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + sinon.assert.calledOnce(modifyStub); + chai.assert.equal( + Blockly.navigation.currentState_, Blockly.navigation.STATE_WS); }); test('Mark', function() { this.workspace.getCursor().setCurNode( Blockly.ASTNode.createConnectionNode(this.basicBlock.previousConnection)); - this.mockEvent.keyCode = Blockly.utils.KeyCodes.ENTER; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + var enterEvent = createKeyDownEvent(Blockly.utils.KeyCodes.ENTER, ''); + + Blockly.onKeyDown(enterEvent); + var markedNode = this.workspace.getMarker(Blockly.navigation.MARKER_NAME).getCurNode(); + chai.assert.isTrue(keyDownSpy.returned(true)); chai.assert.equal(markedNode.getLocation(), this.basicBlock.previousConnection); - chai.assert.equal(Blockly.navigation.currentState_, - Blockly.navigation.STATE_WS); + chai.assert.equal( + Blockly.navigation.currentState_, Blockly.navigation.STATE_WS); }); test('Toolbox', function() { - this.mockEvent.keyCode = Blockly.utils.KeyCodes.T; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); - chai.assert.equal(this.workspace.getToolbox().getSelectedItem(), this.firstCategory_); - chai.assert.equal(Blockly.navigation.currentState_, - Blockly.navigation.STATE_TOOLBOX); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + var tEvent = createKeyDownEvent(Blockly.utils.KeyCodes.T, ''); + + Blockly.onKeyDown(tEvent); + + var firstCategory = this.workspace.getToolbox().contents_[0]; + chai.assert.isTrue(keyDownSpy.returned(true)); + chai.assert.equal( + this.workspace.getToolbox().getSelectedItem(), firstCategory); + chai.assert.equal( + Blockly.navigation.currentState_, Blockly.navigation.STATE_TOOLBOX); }); }); @@ -325,83 +399,84 @@ suite('Navigation', function() { this.workspace = createNavigationWorkspace(true); this.workspace.getCursor().drawer_ = null; this.basicBlock = this.workspace.newBlock('basic_block'); - Blockly.user.keyMap.setKeyMap(Blockly.user.keyMap.createDefaultKeyMap()); - Blockly.mainWorkspace = this.workspace; - Blockly.getMainWorkspace().keyboardAccessibilityMode = true; - Blockly.navigation.currentState_ = Blockly.navigation.STATE_WS; - - this.mockEvent = { - getModifierState: function() { - return false; - } - }; }); teardown(function() { workspaceTeardown.call(this, this.workspace); }); + + test('Action does not exist', function() { var block = this.workspace.getTopBlocks()[0]; var field = block.inputList[0].fieldRow[0]; - sinon.spy(field, 'onBlocklyAction'); + var fieldSpy = sinon.spy(field, 'onBlocklyAction'); + var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.N, ''); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); this.workspace.getCursor().setCurNode(Blockly.ASTNode.createFieldNode(field)); - this.mockEvent.keyCode = Blockly.utils.KeyCodes.N; - var isHandled = Blockly.navigation.onKeyPress(this.mockEvent); - chai.assert.isFalse(isHandled); - sinon.assert.notCalled(field.onBlocklyAction); - field.onBlocklyAction.restore(); + Blockly.onKeyDown(mockEvent); + + chai.assert.isFalse(keyDownSpy.returned(true)); + sinon.assert.notCalled(fieldSpy); }); test('Action exists - field handles action', function() { var block = this.workspace.getTopBlocks()[0]; var field = block.inputList[0].fieldRow[0]; - - sinon.stub(field, 'onBlocklyAction').returns(true); + var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.A, ''); + var fieldSpy = sinon.stub(field, 'onBlocklyAction').returns(true); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); this.workspace.getCursor().setCurNode(Blockly.ASTNode.createFieldNode(field)); - var isHandled = Blockly.navigation.onBlocklyAction(Blockly.navigation.ACTION_OUT); - chai.assert.isTrue(isHandled); - sinon.assert.calledOnce(field.onBlocklyAction); + Blockly.onKeyDown(mockEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + sinon.assert.calledOnce(fieldSpy); - field.onBlocklyAction.restore(); }); test('Action exists - field does not handle action', function() { var block = this.workspace.getTopBlocks()[0]; var field = block.inputList[0].fieldRow[0]; - sinon.spy(field, 'onBlocklyAction'); + var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.A, ''); + var fieldSpy = sinon.spy(field, 'onBlocklyAction'); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); this.workspace.getCursor().setCurNode(Blockly.ASTNode.createFieldNode(field)); - this.mockEvent.keyCode = Blockly.utils.KeyCodes.A; - var isHandled = Blockly.navigation.onBlocklyAction(Blockly.navigation.ACTION_OUT); - chai.assert.isTrue(isHandled); - sinon.assert.calledOnce(field.onBlocklyAction); + Blockly.onKeyDown(mockEvent); - field.onBlocklyAction.restore(); + chai.assert.isTrue(keyDownSpy.returned(true)); + sinon.assert.calledOnce(fieldSpy); }); test('Toggle Action Off', function() { - this.mockEvent.keyCode = 'ShiftControl75'; - sinon.spy(Blockly.navigation, 'onBlocklyAction'); - Blockly.getMainWorkspace().keyboardAccessibilityMode = true; + var mockEvent = createKeyDownEvent( + Blockly.utils.KeyCodes.K, '', + [Blockly.utils.KeyCodes.SHIFT, Blockly.utils.KeyCodes.CTRL]); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + this.workspace.keyboardAccessibilityMode = true; - var isHandled = Blockly.navigation.onKeyPress(this.mockEvent); - chai.assert.isTrue(isHandled); - sinon.assert.calledOnce(Blockly.navigation.onBlocklyAction); - chai.assert.isFalse(Blockly.getMainWorkspace().keyboardAccessibilityMode); - Blockly.navigation.onBlocklyAction.restore(); + Blockly.onKeyDown(mockEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + chai.assert.isFalse(this.workspace.keyboardAccessibilityMode); }); test('Toggle Action On', function() { - this.mockEvent.keyCode = 'ShiftControl75'; - sinon.stub(Blockly.navigation, 'focusWorkspace_'); - Blockly.getMainWorkspace().keyboardAccessibilityMode = false; + var mockEvent = createKeyDownEvent( + Blockly.utils.KeyCodes.K, '', + [Blockly.utils.KeyCodes.SHIFT, Blockly.utils.KeyCodes.CTRL]); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + this.workspace.keyboardAccessibilityMode = false; - var isHandled = Blockly.navigation.onKeyPress(this.mockEvent); - chai.assert.isTrue(isHandled); - sinon.assert.calledOnce(Blockly.navigation.focusWorkspace_); - chai.assert.isTrue(Blockly.getMainWorkspace().keyboardAccessibilityMode); - Blockly.navigation.focusWorkspace_.restore(); + Blockly.onKeyDown(mockEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); + chai.assert.isTrue(this.workspace.keyboardAccessibilityMode); }); suite('Test key press in read only mode', function() { @@ -431,19 +506,12 @@ suite('Navigation', function() { "tooltip": "", "helpUrl": "" }]); - this.workspace = Blockly.inject('blocklyDiv', {readOnly: true}); + this.workspace = createNavigationWorkspace(true, true); Blockly.mainWorkspace = this.workspace; this.workspace.getCursor().drawer_ = null; - Blockly.getMainWorkspace().keyboardAccessibilityMode = true; - Blockly.navigation.currentState_ = Blockly.navigation.STATE_WS; this.fieldBlock1 = this.workspace.newBlock('field_block'); - this.mockEvent = { - getModifierState: function() { - return false; - } - }; }); teardown(function() { @@ -452,24 +520,39 @@ suite('Navigation', function() { test('Perform valid action for read only', function() { var astNode = Blockly.ASTNode.createBlockNode(this.fieldBlock1); + var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.S, ''); this.workspace.getCursor().setCurNode(astNode); - this.mockEvent.keyCode = Blockly.utils.KeyCodes.S; - chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent)); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + + Blockly.onKeyDown(mockEvent); + + chai.assert.isTrue(keyDownSpy.returned(true)); }); test('Perform invalid action for read only', function() { var astNode = Blockly.ASTNode.createBlockNode(this.fieldBlock1); + var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.I, ''); this.workspace.getCursor().setCurNode(astNode); - this.mockEvent.keyCode = Blockly.utils.KeyCodes.I; - chai.assert.isFalse(Blockly.navigation.onKeyPress(this.mockEvent)); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + + Blockly.onKeyDown(mockEvent); + + chai.assert.isTrue(keyDownSpy.returned(false)); }); test('Try to perform action on a field', function() { var field = this.fieldBlock1.inputList[0].fieldRow[0]; var astNode = Blockly.ASTNode.createFieldNode(field); + var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.ENTER, ''); this.workspace.getCursor().setCurNode(astNode); - this.mockEvent.keyCode = Blockly.utils.KeyCodes.ENTER; - chai.assert.isFalse(Blockly.navigation.onKeyPress(this.mockEvent)); + var keyDownSpy = + sinon.spy(Blockly.ShortcutRegistry.registry, 'onKeyDown'); + + Blockly.onKeyDown(mockEvent); + + chai.assert.isTrue(keyDownSpy.returned(false)); }); }); }); @@ -508,9 +591,9 @@ suite('Navigation', function() { var prevNode = Blockly.ASTNode.createConnectionNode(previousConnection); this.workspace.getMarker(Blockly.navigation.MARKER_NAME).setCurNode(prevNode); - Blockly.navigation.focusToolbox_(); - Blockly.navigation.focusFlyout_(); - Blockly.navigation.insertFromFlyout(); + Blockly.navigation.focusToolbox_(this.workspace); + Blockly.navigation.focusFlyout_(this.workspace); + Blockly.navigation.insertFromFlyout(this.workspace); var insertedBlock = this.basicBlock.previousConnection.targetBlock(); @@ -520,9 +603,9 @@ suite('Navigation', function() { }); test('Insert Block from flyout without marking a connection', function() { - Blockly.navigation.focusToolbox_(); - Blockly.navigation.focusFlyout_(); - Blockly.navigation.insertFromFlyout(); + Blockly.navigation.focusToolbox_(this.workspace); + Blockly.navigation.focusFlyout_(this.workspace); + Blockly.navigation.insertFromFlyout(this.workspace); var numBlocks = this.workspace.getTopBlocks().length; diff --git a/tests/mocha/shortcut_registry_test.js b/tests/mocha/shortcut_registry_test.js new file mode 100644 index 000000000..7465e623c --- /dev/null +++ b/tests/mocha/shortcut_registry_test.js @@ -0,0 +1,355 @@ +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +suite('Keyboard Shortcut Registry Test', function() { + setup(function() { + sharedTestSetup.call(this); + this.registry = new Blockly.ShortcutRegistry(); + }); + teardown(function() { + sharedTestTeardown.call(this); + }); + + suite('Registering', function() { + test('Registering a shortcut', function() { + var testShortcut = {'name': 'test_shortcut'}; + this.registry.register(testShortcut, true); + var shortcut = this.registry.registry_['test_shortcut']; + chai.assert.equal(shortcut.name, 'test_shortcut'); + }); + test('Registers shortcut with same name', function() { + var registry = this.registry; + var testShortcut = {'name': 'test_shortcut'}; + + registry.registry_['test_shortcut'] = [testShortcut]; + + var shouldThrow = function() { + registry.register(testShortcut); + }; + chai.assert.throws( + shouldThrow, Error, + 'Shortcut with name "test_shortcut" already exists.'); + }); + test( + 'Registers shortcut with same name opt_allowOverrides=true', + function() { + var registry = this.registry; + var testShortcut = {'name': 'test_shortcut'}; + var otherShortcut = { + 'name': 'test_shortcut', + 'callback': function() {} + }; + + registry.registry_['test_shortcut'] = [testShortcut]; + + var shouldNotThrow = function() { + registry.register(otherShortcut, true); + }; + chai.assert.doesNotThrow(shouldNotThrow); + chai.assert.exists(registry.registry_['test_shortcut'].callback); + }); + }); + + suite('Unregistering', function() { + test('Unregistering a shortcut', function() { + var testShortcut = {'name': 'test_shortcut'}; + this.registry.registry_['test'] = [testShortcut]; + chai.assert.isOk(this.registry.registry_['test']); + this.registry.unregister('test', 'test_shortcut'); + chai.assert.isUndefined(this.registry.registry_['test']); + }); + test('Unregistering a nonexistent shortcut', function() { + var consoleStub = sinon.stub(console, 'warn'); + chai.assert.isUndefined(this.registry.registry_['test']); + + var registry = this.registry; + chai.assert.isFalse(registry.unregister('test', 'test_shortcut')); + sinon.assert.calledOnceWithExactly(consoleStub, 'Keyboard shortcut with name "test" not found.'); + }); + test('Unregistering a shortcut with key mappings', function() { + var testShortcut = {'name': 'test_shortcut'}; + this.registry.keyMap_['keyCode'] = ['test_shortcut']; + this.registry.registry_['test_shortcut'] = testShortcut; + + this.registry.unregister('test_shortcut'); + + var shortcut = this.registry.registry_['test']; + var keyMappings = this.registry.keyMap_['keyCode']; + chai.assert.isUndefined(shortcut); + chai.assert.isUndefined(keyMappings); + }); + test('Unregistering a shortcut with colliding key mappings', function() { + var testShortcut = {'name': 'test_shortcut'}; + this.registry.keyMap_['keyCode'] = ['test_shortcut', 'other_shortcutt']; + this.registry.registry_['test_shortcut'] = testShortcut; + + this.registry.unregister('test_shortcut'); + + var shortcut = this.registry.registry_['test']; + var keyMappings = this.registry.keyMap_['keyCode']; + chai.assert.lengthOf(keyMappings, 1); + chai.assert.isUndefined(shortcut); + }); + }); + + suite('addKeyMapping', function() { + test('Adds a key mapping', function() { + this.registry.registry_['test_shortcut'] = {'name': 'test_shortcut'}; + + this.registry.addKeyMapping('keyCode', 'test_shortcut'); + + var shortcutNames = this.registry.keyMap_['keyCode']; + chai.assert.lengthOf(shortcutNames, 1); + chai.assert.equal(shortcutNames[0], 'test_shortcut'); + }); + test('Adds a colliding key mapping - opt_allowCollision=true', function() { + this.registry.registry_['test_shortcut'] = {'name': 'test_shortcut'}; + this.registry.keyMap_['keyCode'] = ['test_shortcut_2']; + + this.registry.addKeyMapping('keyCode', 'test_shortcut', true); + + var shortcutNames = this.registry.keyMap_['keyCode']; + chai.assert.lengthOf(shortcutNames, 2); + chai.assert.equal(shortcutNames[0], 'test_shortcut'); + chai.assert.equal(shortcutNames[1], 'test_shortcut_2'); + }); + test('Adds a colliding key mapping - opt_allowCollision=false', function() { + this.registry.registry_['test_shortcut'] = {'name': 'test_shortcut'}; + this.registry.keyMap_['keyCode'] = ['test_shortcut_2']; + + var registry = this.registry; + var shouldThrow = function() { + registry.addKeyMapping('keyCode', 'test_shortcut'); + }; + chai.assert.throws( + shouldThrow, Error, + 'Shortcut with name "test_shortcut" collides with shortcuts test_shortcut_2'); + }); + }); + + suite('removeKeyMapping', function() { + test('Removes a key mapping', function() { + this.registry.registry_['test_shortcut'] = {'name': 'test_shortcut'}; + this.registry.keyMap_['keyCode'] = ['test_shortcut', 'test_shortcut_2']; + + var isRemoved = + this.registry.removeKeyMapping('keyCode', 'test_shortcut'); + + var shortcutNames = this.registry.keyMap_['keyCode']; + chai.assert.lengthOf(shortcutNames, 1); + chai.assert.equal(shortcutNames[0], 'test_shortcut_2'); + chai.assert.isTrue(isRemoved); + }); + test('Removes last key mapping for a key', function() { + this.registry.registry_['test_shortcut'] = {'name': 'test_shortcut'}; + this.registry.keyMap_['keyCode'] = ['test_shortcut']; + + this.registry.removeKeyMapping('keyCode', 'test_shortcut'); + + var shortcutNames = this.registry.keyMap_['keyCode']; + chai.assert.isUndefined(shortcutNames); + }); + test('Removes a key map that does not exist opt_quiet=false', function() { + var consoleStub = sinon.stub(console, 'warn'); + this.registry.keyMap_['keyCode'] = ['test_shortcut_2']; + + var isRemoved = + this.registry.removeKeyMapping('keyCode', 'test_shortcut'); + + chai.assert.isFalse(isRemoved); + sinon.assert.calledOnceWithExactly( + consoleStub, + 'No keyboard shortcut with name "test_shortcut" registered with key code "keyCode"'); + }); + test( + 'Removes a key map that does not exist from empty key mapping opt_quiet=false', + function() { + var consoleStub = sinon.stub(console, 'warn'); + + var isRemoved = + this.registry.removeKeyMapping('keyCode', 'test_shortcut'); + + chai.assert.isFalse(isRemoved); + sinon.assert.calledOnceWithExactly( + consoleStub, + 'No keyboard shortcut with name "test_shortcut" registered with key code "keyCode"'); + }); + }); + + suite('Setters/Getters', function() { + test('Sets the key map', function() { + this.registry.setKeyMap({'keyCode': ['test_shortcut']}); + chai.assert.lengthOf(Object.keys(this.registry.keyMap_), 1); + chai.assert.equal(this.registry.keyMap_['keyCode'][0], 'test_shortcut'); + }); + test('Gets a copy of the key map', function() { + this.registry.keyMap_['keyCode'] = ['a']; + var keyMapCopy = this.registry.getKeyMap(); + keyMapCopy['keyCode'] = ['b']; + chai.assert.equal(this.registry.keyMap_['keyCode'][0], 'a'); + }); + test('Gets a copy of the registry', function() { + this.registry.registry_['shortcutName'] = {'name': 'shortcutName'}; + var registrycopy = this.registry.getRegistry(); + registrycopy['shortcutName']['name'] = 'shortcutName1'; + chai.assert.equal( + this.registry.registry_['shortcutName']['name'], 'shortcutName'); + }); + test('Gets keyboard shortcuts from a key code', function() { + this.registry.keyMap_['keyCode'] = ['shortcutName']; + var shortcutNames = this.registry.getKeyboardShortcuts('keyCode'); + chai.assert.equal(shortcutNames[0], 'shortcutName'); + }); + test('Gets keycodes by shortcut name', function() { + this.registry.keyMap_['keyCode'] = ['shortcutName']; + this.registry.keyMap_['keyCode1'] = ['shortcutName']; + var shortcutNames = + this.registry.getKeyCodeByShortcutName('shortcutName'); + chai.assert.lengthOf(shortcutNames, 2); + chai.assert.equal(shortcutNames[0], 'keyCode'); + chai.assert.equal(shortcutNames[1], 'keyCode1'); + }); + }); + + suite('onKeyDown', function() { + function addShortcut(registry, shortcut, keyCode, returns) { + registry.register(shortcut, true); + registry.addKeyMapping(keyCode, shortcut.name, true); + return sinon.stub(shortcut, 'callback').returns(returns); + } + + setup(function() { + this.testShortcut = { + 'name': 'test_shortcut', + 'callback': function() { + return true; + }, + 'precondition': function() { + return true; + } + }; + this.callBackStub = + addShortcut(this.registry, this.testShortcut, 'keyCode', true); + }); + test('Execute a shortcut from event', function() { + var event = createKeyDownEvent('keyCode', ''); + chai.assert.isTrue(this.registry.onKeyDown(this.workspace, event)); + sinon.assert.calledOnce(this.callBackStub); + }); + test('No shortcut executed from event', function() { + var event = createKeyDownEvent('nonExistentKeyCode', ''); + chai.assert.isFalse(this.registry.onKeyDown(this.workspace, event)); + }); + test('No precondition available - execute callback', function() { + delete this.testShortcut['precondition']; + var event = createKeyDownEvent('keyCode', ''); + chai.assert.isTrue(this.registry.onKeyDown(this.workspace, event)); + sinon.assert.calledOnce(this.callBackStub); + }); + test('Execute all shortcuts in list', function() { + var event = createKeyDownEvent('keyCode', ''); + var testShortcut2 = { + 'name': 'test_shortcut_2', + 'callback': function() { + return false; + }, + 'precondition': function() { + return false; + } + }; + var testShortcut2Stub = + addShortcut(this.registry, testShortcut2, 'keyCode', false); + chai.assert.isTrue(this.registry.onKeyDown(this.workspace, event)); + sinon.assert.calledOnce(testShortcut2Stub); + sinon.assert.calledOnce(this.callBackStub); + }); + test('Stop executing shortcut when event is handled', function() { + var event = createKeyDownEvent('keyCode', ''); + var testShortcut2 = { + 'name': 'test_shortcut_2', + 'callback': function() { + return false; + }, + 'precondition': function() { + return false; + } + }; + var testShortcut2Stub = + addShortcut(this.registry, testShortcut2, 'keyCode', true); + chai.assert.isTrue(this.registry.onKeyDown(this.workspace, event)); + sinon.assert.calledOnce(testShortcut2Stub); + sinon.assert.notCalled(this.callBackStub); + }); + }); + + suite('createSerializedKey', function() { + test('Serialize key', function() { + var serializedKey = + this.registry.createSerializedKey(Blockly.utils.KeyCodes.A); + chai.assert.equal(serializedKey, '65'); + }); + + test('Serialize key code and modifier', function() { + var serializedKey = this.registry.createSerializedKey( + Blockly.utils.KeyCodes.A, [Blockly.utils.KeyCodes.CTRL]); + chai.assert.equal(serializedKey, 'Control+65'); + }); + test('Serialize only a modifier', function() { + var serializedKey = this.registry.createSerializedKey( + null, [Blockly.utils.KeyCodes.CTRL]); + chai.assert.equal(serializedKey, 'Control'); + }); + test('Serialize multiple modifiers', function() { + var serializedKey = this.registry.createSerializedKey( + null, [Blockly.utils.KeyCodes.CTRL, Blockly.utils.KeyCodes.SHIFT]); + chai.assert.equal(serializedKey, 'Shift+Control'); + }); + test('Order of modifiers should result in same serialized key', function() { + var serializedKey = this.registry.createSerializedKey( + null, [Blockly.utils.KeyCodes.CTRL, Blockly.utils.KeyCodes.SHIFT]); + chai.assert.equal(serializedKey, 'Shift+Control'); + var serializedKeyNewOrder = this.registry.createSerializedKey( + null, [Blockly.utils.KeyCodes.SHIFT, Blockly.utils.KeyCodes.CTRL]); + chai.assert.equal(serializedKeyNewOrder, 'Shift+Control'); + }); + }); + + suite('serializeKeyEvent', function() { + test('Serialize key', function() { + var mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.A, ''); + var serializedKey = this.registry.serializeKeyEvent_(mockEvent); + chai.assert.equal(serializedKey, '65'); + }); + test('Serialize key code and modifier', function() { + var mockEvent = createKeyDownEvent( + Blockly.utils.KeyCodes.A, '', [Blockly.utils.KeyCodes.CTRL]); + var serializedKey = this.registry.serializeKeyEvent_(mockEvent); + chai.assert.equal(serializedKey, 'Control+65'); + }); + test('Serialize only a modifier', function() { + var mockEvent = + createKeyDownEvent(null, '', [Blockly.utils.KeyCodes.CTRL]); + var serializedKey = this.registry.serializeKeyEvent_(mockEvent); + chai.assert.equal(serializedKey, 'Control'); + }); + test('Serialize multiple modifiers', function() { + var mockEvent = createKeyDownEvent( + null, '', + [Blockly.utils.KeyCodes.CTRL, Blockly.utils.KeyCodes.SHIFT]); + var serializedKey = this.registry.serializeKeyEvent_(mockEvent); + chai.assert.equal(serializedKey, 'Shift+Control'); + }); + test('Throw error when incorrect modifier', function() { + var registry = this.registry; + var shouldThrow = function() { + registry.createSerializedKey(Blockly.utils.KeyCodes.K, ['s']); + }; + chai.assert.throws(shouldThrow, Error, 's is not a valid modifier key.'); + }); + }); + + teardown(function() {}); +}); diff --git a/tests/mocha/test_helpers.js b/tests/mocha/test_helpers.js index 14ffd18c2..7ee94233b 100644 --- a/tests/mocha/test_helpers.js +++ b/tests/mocha/test_helpers.js @@ -576,10 +576,17 @@ function dispatchPointerEvent(target, type, properties) { function createKeyDownEvent(keyCode, type, modifiers) { var event = { keyCode: keyCode, - target: { - type: type - }, - getModifierState: function() { + target: {type: type}, + getModifierState: function(name) { + if (name == 'Shift' && this.shiftKey) { + return true; + } else if (name == 'Control' && this.ctrlKey) { + return true; + } else if (name == 'Meta' && this.metaKey) { + return true; + } else if (name == 'Alt' && this.altKey) { + return true; + } return false; }, preventDefault: function() {}