Keyboard shortcuts (#4421)

* Adds shortcut registry and removes action and key map (#4398)

* Adds Shortcut tests and refactored navigation tests (#4412)

* Adds shortcut items (#4408)

* Add shortcuts for navigation (#4409)

* Add final keyboard shortcut cleanup (#4413)
This commit is contained in:
alschmiedt
2020-11-02 13:30:05 -08:00
committed by GitHub
parent 4364bdb18c
commit f1498e7f07
26 changed files with 1782 additions and 1675 deletions

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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
*/

View File

@@ -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);

View File

@@ -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);

View File

@@ -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
*/

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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) {

View File

@@ -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
*/

View File

@@ -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<string, Blockly.Action>}
* @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<string, Blockly.Action>} 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<string,Blockly.Action>} 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.<string>} modifiers List of modifiers to be used with the key.
* @param {!Array.<string>} 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.<string>} 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<string,Blockly.Action>} 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;
};

View File

@@ -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.Action>}
*/
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();
};

View File

@@ -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<string,Blockly.Action>} */
this.keyMap = keyMap;
/** @type {string} */
this.renderer = renderer;
/** @type {?Object} */

263
core/shortcut_items.js Normal file
View File

@@ -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();
};

347
core/shortcut_registry.js Normal file
View File

@@ -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<string, !Blockly.ShortcutRegistry.KeyboardShortcut>}
* @private
*/
this.registry_ = Object.create(null);
/**
* Map of key codes to an array of shortcut names.
* @type {!Object<string, !Array<string>>}
* @private
*/
this.keyMap_ = Object.create(null);
Blockly.ShortcutItems.registerDefaultShortcuts();
Blockly.navigation.registerNavigationShortcuts();
};
/**
* Enum of valid modifiers.
* @enum {!Blockly.utils.KeyCodes<number>}
*/
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<string, !Array<string>>} 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<string,!Array<!Blockly.ShortcutRegistry.KeyboardShortcut>>}
* 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<string, !Blockly.ShortcutRegistry.KeyboardShortcut>}
* 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<string>|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<string>} 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<string>} 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.<string>} 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();

View File

@@ -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
*/

View File

@@ -1,570 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Blockly Demo: Keyboard Navigation</title>
<script src="../../blockly_compressed.js"></script>
<script src="../../blocks_compressed.js"></script>
<script src="../../javascript_compressed.js"></script>
<script src="../../msg/js/en.js"></script>
<script src="line_cursor.js"></script>
<style>
body {
background-color: #fff;
font-family: sans-serif;
}
h1 {
font-weight: normal;
font-size: 140%;
}
.wrapper {
display: flex;
}
#keyboard_nav {
background-color: #ededed;
border: 1px solid black;
padding: 1em;
}
#keyboard_announce {
font-size: 1.5em;
font-weight: 500;
text-align: center;
}
#keyboard_mappings {
font-size: 1.3em;
font-weight: 400;
}
label {
margin-right: .5em;
min-width: 100px;
}
div[data-actionname] {
display: flex;
width: 100%;
}
select {
font-size: .8em;
}
</style>
<meta http-equiv="Refresh" content="0; url=https://github.com/google/blockly-samples">
</head>
<body>
<h1><a href="https://developers.google.com/blockly/">Blockly</a> &gt;
<a href="../index.html">Demos</a> &gt; Keyboard Navigation</h1>
<p>Keyboard Navigation is our first step towards an accessible Blockly.<br />
For more information on how the default keyboard navigation works please see
the <a href="https://developers.google.com/blockly/guides/configure/web/keyboard-nav">documentation</a>.
<br />
<br />
<b>Pre Order Traversal</b><br />
Feel free to just play around in accessibility mode or hit the button below to see the demo.
The demo uses <a href="https://en.wikipedia.org/wiki/Tree_traversal#Pre-order_(NLR)">preorder tree traversal</a>
as an alternative way to navigate the blocks,
connections, and fields on the workspace.<br /><br />
<b>Cursors</b><br />
The cursor controls how the user navigates the blocks, inputs, fields and connections on a workspace.
This demo shows three different cursors:<br />
<b>Default Cursor:</b> Explained in <a href="https://developers.google.com/blockly/guides/configure/web/keyboard-nav">documentation</a>.<br />
<b>Basic Cursor:</b> Uses pre order traversal to allow users to navigate
through everything using only the previous and next command.<br />
<b>Line Cursor:</b> 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.
</p>
<p>
<label for="accessibilityModeCheck">Enable Accessibility Mode:</label>
<input type="checkbox" onclick="toggleAccessibilityMode(this.checked)" id="accessibilityModeCheck">
<select id="cursorChanger" name="cursor" onchange="changeCursor(this.value)">
<option value="default">Default Cursor</option>
<option value="basic">Basic Cursor</option>
<option value="line">Line Cursor</option>
</select>
<button onclick="preOrderDemo()">Start Pre-order Demo</button>
<button onclick="stopDemo()">Stop Pre-order Demo</button>
<label for="displayKeyMappings">Open Key Mappings:</label>
<input type="checkbox" onclick="toggleDisplayKeyMappings(this.checked)" id="displayKeyMappings">
</p>
<div class="wrapper">
<div id="blocklyDiv" style="height: 480px; width: 600px;"></div>
<div id="keyboard_nav" style="display:none">
<p id="keyboard_announce" aria-live="assertive">Set key mappings below</p>
<form id="keyboard_mappings"></form>
</div>
</div>
<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox" style="display: none">
<category name="Logic" colour="%{BKY_LOGIC_HUE}">
<block type="controls_if"></block>
<block type="logic_compare"></block>
<block type="logic_operation"></block>
<block type="logic_negate"></block>
<block type="logic_boolean"></block>
</category>
<category name="Loops" colour="%{BKY_LOOPS_HUE}">
<block type="controls_repeat_ext">
<value name="TIMES">
<block type="math_number">
<field name="NUM">10</field>
</block>
</value>
</block>
<block type="controls_whileUntil"></block>
</category>
<category name="Math" colour="%{BKY_MATH_HUE}">
<block type="math_number">
<field name="NUM">123</field>
</block>
<block type="math_arithmetic"></block>
<block type="math_single"></block>
</category>
<category name="Text" colour="%{BKY_TEXTS_HUE}">
<block type="text"></block>
<block type="text_length"></block>
<block type="text_print"></block>
</category>
</xml>
<xml xmlns="https://developers.google.com/blockly/xml" id="startBlocks" style="display: none">
<variables>
<variable id="~GNXm@Z(wclI]t3zTf.g">list</variable>
<variable id="8]s[S+Gy+%k7HoFup])m">item</variable>
</variables>
<block type="controls_if" x="37" y="162">
<value name="IF0">
<block type="logic_compare">
<field name="OP">EQ</field>
<value name="A">
<block type="math_arithmetic">
<field name="OP">ADD</field>
<value name="A">
<shadow type="math_number">
<field name="NUM">1</field>
</shadow>
</value>
<value name="B">
<shadow type="math_number">
<field name="NUM">1</field>
</shadow>
</value>
</block>
</value>
<value name="B">
<block type="math_single">
<field name="OP">ROOT</field>
<value name="NUM">
<shadow type="math_number">
<field name="NUM">9</field>
</shadow>
<block type="math_number">
<field name="NUM">123</field>
</block>
</value>
</block>
</value>
</block>
</value>
<statement name="DO0">
<block type="lists_setIndex">
<mutation at="true"></mutation>
<field name="MODE">SET</field>
<field name="WHERE">FROM_START</field>
<value name="LIST">
<block type="variables_get">
<field name="VAR" id="~GNXm@Z(wclI]t3zTf.g">list</field>
</block>
</value>
<next>
<block type="text_append">
<field name="VAR" id="8]s[S+Gy+%k7HoFup])m">item</field>
<value name="TEXT">
<shadow type="text">
<field name="TEXT"></field>
</shadow>
</value>
</block>
</next>
</block>
</statement>
<next>
<block type="controls_repeat_ext">
<value name="TIMES">
<shadow type="math_number">
<field name="NUM">10</field>
</shadow>
</value>
</block>
</next>
</block>
</xml>
<script>
var demoWorkspace = Blockly.inject('blocklyDiv',
{media: '../../media/',
toolbox: document.getElementById('toolbox')});
Blockly.Xml.domToWorkspace(document.getElementById('startBlocks'),
demoWorkspace);
var timeout;
var actions = [
Blockly.navigation.ACTION_PREVIOUS,
Blockly.navigation.ACTION_OUT,
Blockly.navigation.ACTION_NEXT,
Blockly.navigation.ACTION_IN,
Blockly.navigation.ACTION_INSERT,
Blockly.navigation.ACTION_MARK,
Blockly.navigation.ACTION_DISCONNECT,
Blockly.navigation.ACTION_TOOLBOX,
Blockly.navigation.ACTION_EXIT,
Blockly.navigation.ACTION_MOVE_WS_CURSOR_UP,
Blockly.navigation.ACTION_MOVE_WS_CURSOR_LEFT,
Blockly.navigation.ACTION_MOVE_WS_CURSOR_DOWN,
Blockly.navigation.ACTION_MOVE_WS_CURSOR_RIGHT
];
createKeyMappingList(actions);
/**
* Shows the next node in the tree traversal every second.
* @package
*/
function demo() {
var doNext = function() {
var markerManager = Blockly.getMainWorkspace().getMarkerManager();
var nextNode = markerManager.getCursor().next();
if (nextNode) {
timeout = setTimeout(doNext, 1000);
}
}
doNext();
}
/**
* Stop the running demo.
* @package
*/
function stopDemo() {
clearTimeout(timeout);
document.getElementById('accessibilityModeCheck').disabled = false;
};
/**
* Sets up accessibility mode and change the cursor to basic cursor so that
* the demo can successfully run.
* @package
*/
function preOrderDemo() {
changeCursor('basic');
document.getElementById('accessibilityModeCheck').disabled = true;
setTimeout(demo, 1000);
}
/**
* Turn on/off accessibility mode depending on the state.
* @param {boolean} state True to turn on accessibility mode, false otherwise.
* @package
*/
function toggleAccessibilityMode(state) {
if (state) {
Blockly.navigation.enableKeyboardAccessibility();
} else {
Blockly.navigation.disableKeyboardAccessibility();
}
}
/**
* Change the type of the cursor and set to the location of the old cursor.
* Changing the cursor changes how a user navigates the blocks on the workspace.
* @param {string} cursorType The type of the cursor.
* @package
*/
function changeCursor(cursorType) {
Blockly.navigation.enableKeyboardAccessibility();
document.getElementById('accessibilityModeCheck').checked = true;
document.getElementById('cursorChanger').value = cursorType;
var markerManager = Blockly.getMainWorkspace().getMarkerManager();
var oldCurNode = markerManager.getCursor().getCurNode();
if (cursorType === "basic") {
Blockly.ASTNode.NAVIGATE_ALL_FIELDS = false;
markerManager.setCursor(new Blockly.BasicCursor());
} else if (cursorType === "line") {
Blockly.ASTNode.NAVIGATE_ALL_FIELDS = true;
markerManager.setCursor(new Blockly.LineCursor());
} else {
Blockly.ASTNode.NAVIGATE_ALL_FIELDS = false;
markerManager.setCursor(new Blockly.Cursor());
}
if (oldCurNode) {
markerManager.getCursor().setCurNode(oldCurNode);
}
document.activeElement.blur();
}
// Start key mapping demo functions
/**
* Save the current key map in session storage.
* @package
*/
function saveKeyMap() {
var currentMap = Blockly.user.keyMap.getKeyMap();
if (sessionStorage) {
sessionStorage.setItem('keyMap', JSON.stringify(currentMap));
}
}
/**
* Set the key map to the map from session storage.
* @package
*/
function restoreKeyMap() {
var defaultMap = Blockly.user.keyMap.getKeyMap();
var stringifiedMap = sessionStorage.getItem('keyMap');
var restoredMap = {};
if (sessionStorage && stringifiedMap) {
var keyMap = JSON.parse(stringifiedMap);
var keys = Object.keys(keyMap);
for (var i = 0, key; key = keys[i]; i++) {
restoredMap[key] = Object.assign(new Blockly.Action, keyMap[key]);
}
Blockly.user.keyMap.setKeyMap(restoredMap);
}
}
/**
* Given the three dropdowns create the serialized key that will be stored
* in the key map.
* @param {Array.<Element>} selectDivs The three dropdown divs that display
* the key combination.
* @package
*/
function serializeKey(selectDivs) {
var modifiers = Blockly.utils.object.values(Blockly.user.keyMap.modifierKeys);
var newModifiers = [];
var newKeyCode = '';
var keyValue = selectDivs[2].value;
// Get the new modifiers from the first two dropdowns.
for (var i = 0; i < 2; i++) {
var selectDiv = selectDivs[i];
var key = selectDiv.value;
if (key !== 'None') {
newModifiers.push(key);
}
}
// Get the key code from the last dropdown.
if (keyValue !== 'None') {
if (keyValue === 'Escape') {
newKeyCode = Blockly.utils.KeyCodes.ESC;
} else if (keyValue === 'Enter') {
newKeyCode = Blockly.utils.KeyCodes.ENTER;
} else {
newKeyCode = keyValue.toUpperCase().charCodeAt(0);
}
}
return Blockly.user.keyMap.createSerializedKey(newKeyCode, newModifiers);
}
/**
* Set all dropdowns for that action to none.
* We clear dropdowns when a user chooses the same key combination for a
* second action.
* @param {Blockly.Action} action The action that we want to clear the
* dropdowns for.
* @package
*/
function clearDropdown(action) {
var actionDiv = document.querySelectorAll('[data-actionname='+ action.name +']')[0];
var selectDivs = actionDiv.getElementsByTagName('select');
for (var i = 0, selectDiv; selectDiv = selectDivs[i]; i++) {
selectDiv.value = 'None';
}
}
/**
* Given the three dropdowns create a human readable string so the screen reader
* can read it out.
* @param {Array.<Element>} selectDivs The three dropdown divs that display
* the key combination.
* @package
*/
function getReadableKey(selectDivs) {
var readableKey = '';
for (var i = 0, selectDiv; selectDiv = selectDivs[i]; i++) {
if (selectDiv.value !== 'None') {
readableKey += selectDiv.value + ' ';
}
}
return readableKey;
}
/**
* Update the key in the key map when the user selects a new value in one of the
* dropdowns.
* @param {Event} e The event dispatched from changing a dropdown.
* @package
*/
function updateKey(e) {
var keyboardAnnouncerText = '';
var actionDiv = e.srcElement.parentElement;
var action = actionDiv.action;
var selectDivs = actionDiv.getElementsByTagName('select');
var key = serializeKey(selectDivs);
var oldAction = Blockly.user.keyMap.getActionByKeyCode(key);
if (oldAction) {
keyboardAnnouncerText += oldAction.name + ' action key was overwritten. \n';
clearDropdown(oldAction);
}
keyboardAnnouncerText += action.name + ' key was set to ' + getReadableKey(selectDivs);
document.getElementById('keyboard_announce').innerText = keyboardAnnouncerText;
Blockly.user.keyMap.setActionForKey(key, action);
saveKeyMap();
document.activeElement.blur();
}
/**
* Set the key to be the correct value from the key map.
* @param {string} actionKey The serialized key for a given action.
* @param {Element} keyDropdown The dropdown that displays the primary key.
* @package
*/
function setKeyDropdown(actionKey, keyDropdown) {
// Strip off any modifier to just get the key code.
var keyCode = actionKey.match(/\d+/)[0];
var keyValue = String.fromCharCode(keyCode);
if (parseInt(keyCode) === Blockly.utils.KeyCodes.ESC) {
keyValue = 'Escape';
} else if (parseInt(keyCode) === Blockly.utils.KeyCodes.ENTER) {
keyValue = 'Enter';
}
keyDropdown.value = keyValue;
}
/**
* Set the modifiers to be the correct value from the key map.
* @param {string} actionKey The key code holding the modifiers and key.
* @param {Array.<Element>} modifierDropdowns A list of dropdowns for
* the modifier values.
* @package
*/
function setModifiers(actionKey, modifierDropdowns) {
var modifiers = Blockly.utils.object.values(Blockly.user.keyMap.modifierKeys);
for (var i = 0; i < 2; i++) {
var modifierDropdown = modifierDropdowns[i];
for (var j = 0, modifier; modifier = modifiers[j]; j++) {
if (actionKey.indexOf(modifier) > -1) {
modifierDropdown.value = modifier;
actionKey = actionKey.replace(modifier, '');
break;
}
}
}
}
/**
* Set the dropdowns to display the correct combination of modifiers and
* keys for the action key.
* @param {Blockly.Action} action The Blockly action.
* @param {Element} actionDiv The div holding the dropdowns and label for the
* given action.
* @param {string} actionKey The key corresponding to the given action.
* @package
*/
function setDropdowns(action, actionDiv, actionKey) {
var selectDivs = actionDiv.getElementsByTagName('select');
if (actionKey) {
setModifiers(actionKey, selectDivs);
setKeyDropdown(actionKey, selectDivs[selectDivs.length - 1]);
} else {
clearDropdown(action);
}
}
/**
* Create a dropdown with the given list of possible keys.
* @param {Blockly.Action} action The Blockly action.
* @param {Element} actionDiv The div holding the dropdowns and labels for
* a given action.
* @param {Array.<string>} keys The list of keys to add to the dropdown.
* @package
*/
function createDropdown(action, actionDiv, keys) {
var select = document.createElement('select');
select.addEventListener('change', updateKey);
select.setAttribute('aria-labelledby', action.name + '_label');
for (var i = 0, key; key = keys[i]; i++) {
select.options.add(new Option(key, key));
}
actionDiv.appendChild(select);
}
/**
* Create two dropdowns that display possible modifiers and a single dropdown
* displaying a list of keys.
* @param {Blockly.Action} action The Blockly action.
* @param {string} actionKey The key corresponding to the given action.
* @param {Element} actionDiv The div holding the dropdowns and label for the
* given action.
* @package
*/
function createDropdowns(action, actionKey, actionDiv) {
var modifiers = ['None'].concat(Blockly.utils.object.values(Blockly.user.keyMap.modifierKeys));
var keys = ['None', 'Enter', 'Escape'].concat("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".split(''));
createDropdown(action, actionDiv, modifiers);
createDropdown(action, actionDiv, modifiers);
createDropdown(action, actionDiv, keys);
setDropdowns(action, actionDiv, actionKey);
}
/**
* For each action create a row of 3 dropdowns and an action label. Update
* the dropdowns to reflect the value in the key map.
* @param {Array.<Blockly.Action>} actions List of blockly actions.
* @package
*/
function createKeyMappingList(actions) {
// Update the key map to reflect the key map saved in session storage.
restoreKeyMap();
var keyMapDiv = document.getElementById('keyboard_mappings');
for (var i = 0, action; action = actions[i]; i++) {
var actionDiv = document.createElement('div');
actionDiv.setAttribute('data-actionname', action.name);
actionDiv.action = action;
var labelDiv = document.createElement('label');
labelDiv.innerText = action.name;
labelDiv.setAttribute('id', action.name + '_label');
actionDiv.appendChild(labelDiv);
keyMapDiv.appendChild(actionDiv);
var actionKey = Blockly.user.keyMap.getKeyByAction(action);
createDropdowns(action, actionKey, actionDiv);
}
}
/**
* Hide/show the key map panel.
* @param {boolean} state The state of the checkbox. True if checked, false
* otherwise.
* @package
*/
function toggleDisplayKeyMappings(state) {
if (state) {
document.getElementById('keyboard_nav').style.display = 'block';
} else {
document.getElementById('keyboard_nav').style.display = 'none';
}
}
// End key mapping demo functions
</script>
<body>
<p>This demo is in the process of moving to a repository!</p>
</body>
</body>
</html>

View File

@@ -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;
};

View File

@@ -81,7 +81,7 @@
<script src="input_test.js"></script>
<script src="insertion_marker_test.js"></script>
<script src="json_test.js"></script>
<script src="key_map_test.js"></script>
<script src="shortcut_registry_test.js"></script>
<script src="keydown_test.js"></script>
<script src="logic_ternary_test.js"></script>
<script src="metrics_test.js"></script>

View File

@@ -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() {});
});

View File

@@ -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 = [

View File

@@ -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;

View File

@@ -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() {});
});

View File

@@ -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() {}