Index: src/wp-admin/js/customize-widgets.js
===================================================================
--- src/wp-admin/js/customize-widgets.js	(revision 27985)
+++ src/wp-admin/js/customize-widgets.js	(working copy)
@@ -121,7 +121,7 @@
 		 * @param {jQuery} widget
 		 * @param {String} newForm
 		 */
-		rss: function ( e, widget, newForm ) {
+		rss: function( e, widget, newForm ) {
 			var oldWidgetError = widget.find( '.widget-error:first' ),
 				newWidgetError = $( '<div>' + newForm + '</div>' ).find( '.widget-error:first' );
 
@@ -246,7 +246,7 @@
 			} );
 
 			// Reposition whenever a sidebar's widgets are changed
-			api.each( function ( setting ) {
+			api.each( function( setting ) {
 				if ( 0 === setting.id.indexOf( 'sidebars_widgets[' ) ) {
 					setting.bind( function() {
 						if ( control.container.hasClass( 'expanded' ) ) {
@@ -264,7 +264,7 @@
 		_setupControlToggle: function() {
 			var control = this, close_btn;
 
-			control.container.find( '.widget-top' ).on( 'click', function ( e ) {
+			control.container.find( '.widget-top' ).on( 'click', function( e ) {
 				e.preventDefault();
 				var sidebar_widgets_control = control.getSidebarWidgetsControl();
 				if ( sidebar_widgets_control.is_reordering ) {
@@ -275,7 +275,7 @@
 
 			close_btn = control.container.find( '.widget-control-close' );
 			// @todo Hitting Enter on this link does nothing; will be resolved in core with <http://core.trac.wordpress.org/ticket/26633>
-			close_btn.on( 'click', function ( e ) {
+			close_btn.on( 'click', function( e ) {
 				e.preventDefault();
 				control.collapseForm();
 				control.container.find( '.widget-top .widget-action:first' ).focus(); // keyboard accessibility
@@ -317,7 +317,7 @@
 			 *
 			 * @param {jQuery} li
 			 */
-			select_sidebar_item = function ( li ) {
+			select_sidebar_item = function( li ) {
 				li.siblings( '.selected' ).removeClass( 'selected' );
 				li.addClass( 'selected' );
 				var is_self_sidebar = ( li.data( 'id' ) === control.params.sidebar_id );
@@ -363,7 +363,7 @@
 			 * Handle clicks for up/down/move on the reorder nav
 			 */
 			reorder_nav = control.container.find( '.widget-reorder-nav' );
-			reorder_nav.find( '.move-widget, .move-widget-down, .move-widget-up' ).on( 'click keypress', function ( event ) {
+			reorder_nav.find( '.move-widget, .move-widget-down, .move-widget-up' ).on( 'click keypress', function( event ) {
 				if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
 					return;
 				}
@@ -393,7 +393,7 @@
 			/**
 			 * Handle selecting a sidebar to move to
 			 */
-			control.container.find( '.widget-area-select' ).on( 'click keypress', 'li', function ( e ) {
+			control.container.find( '.widget-area-select' ).on( 'click keypress', 'li', function( e ) {
 				if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
 					return;
 				}
@@ -472,7 +472,7 @@
 			save_btn.val( l10n.saveBtnLabel );
 			save_btn.attr( 'title', l10n.saveBtnTooltip );
 			save_btn.removeClass( 'button-primary' ).addClass( 'button-secondary' );
-			save_btn.on( 'click', function ( e ) {
+			save_btn.on( 'click', function( e ) {
 				e.preventDefault();
 				control.updateWidget( { disable_form: true } );
 			} );
@@ -491,7 +491,7 @@
 			} );
 
 			// Handle widgets that support live previews
-			widget_content.on( 'change input propertychange', ':input', function ( e ) {
+			widget_content.on( 'change input propertychange', ':input', function( e ) {
 				if ( control.live_update_mode ) {
 					if ( e.type === 'change' ) {
 						control.updateWidget();
@@ -505,21 +505,21 @@
 			control.setting.previewer.channel.bind( 'synced', function() {
 				control.container.removeClass( 'previewer-loading' );
 			} );
-			api.Widgets.Previewer.bind( 'widget-updated', function ( updated_widget_id ) {
+			api.Widgets.Previewer.bind( 'widget-updated', function( updated_widget_id ) {
 				if ( updated_widget_id === control.params.widget_id ) {
 					control.container.removeClass( 'previewer-loading' );
 				}
 			} );
 
 			// Update widget control to indicate whether it is currently rendered (cf. Widget Visibility)
-			api.Widgets.Previewer.bind( 'rendered-widgets', function ( rendered_widgets ) {
+			api.Widgets.Previewer.bind( 'rendered-widgets', function( rendered_widgets ) {
 				var is_rendered = !! rendered_widgets[control.params.widget_id];
 				control.container.toggleClass( 'widget-rendered', is_rendered );
 			} );
 
 			form_update_event_handler = api.Widgets.formSyncHandlers[ control.params.widget_id_base ];
 			if ( form_update_event_handler ) {
-				$( document ).on( 'widget-synced', function ( e, widget_el ) {
+				$( document ).on( 'widget-synced', function( e, widget_el ) {
 					if ( widget_root.is( widget_el ) ) {
 						form_update_event_handler.apply( document, arguments );
 					}
@@ -538,7 +538,7 @@
 			// Configure remove button
 			remove_btn = control.container.find( 'a.widget-control-remove' );
 			// @todo Hitting Enter on this link does nothing; will be resolved in core with <http://core.trac.wordpress.org/ticket/26633>
-			remove_btn.on( 'click', function ( e ) {
+			remove_btn.on( 'click', function( e ) {
 				e.preventDefault();
 
 				// Find an adjacent element to add focus to when this widget goes away
@@ -592,7 +592,7 @@
 		 * @returns {jQuery} inputs
 		 * @private
 		 */
-		_getInputs: function ( container ) {
+		_getInputs: function( container ) {
 			return $( container ).find( ':input[name]' );
 		},
 
@@ -604,8 +604,8 @@
 		 * @returns {string}
 		 * @private
 		 */
-		_getInputsSignature: function ( inputs ) {
-			var inputs_signatures = _( inputs ).map( function ( input ) {
+		_getInputsSignature: function( inputs ) {
+			var inputs_signatures = _( inputs ).map( function( input ) {
 				input = $( input );
 				var signature_parts;
 				if ( input.is( ':checkbox, :radio' ) ) {
@@ -625,7 +625,7 @@
 		 * @returns {string}
 		 * @private
 		 */
-		_getInputStatePropertyName: function ( input ) {
+		_getInputStatePropertyName: function( input ) {
 			input = $( input );
 			if ( input.is( ':radio, :checkbox' ) ) {
 				return 'checked';
@@ -661,7 +661,7 @@
 		 * @param {Function|null} [args.complete=null]  Function which is called when the request finishes. Context is bound to the control. First argument is any error. Following arguments are for success.
 		 * @param {Boolean} [args.ignore_active_element=false] Whether or not updating a field will be deferred if focus is still on the element.
 		 */
-		updateWidget: function ( args ) {
+		updateWidget: function( args ) {
 			var control = this,
 				instance_override,
 				complete_callback,
@@ -726,7 +726,7 @@
 			}
 			data += '&' + widget_content.find( '~ :input' ).serialize();
 
-			jqxhr = $.post( wp.ajax.settings.url, data, function ( r ) {
+			jqxhr = $.post( wp.ajax.settings.url, data, function( r ) {
 				var message,
 					sanitized_form,
 					sanitized_inputs,
@@ -763,7 +763,7 @@
 
 					// Sync sanitized field states to existing fields if they are aligned
 					if ( has_same_inputs_in_response && control.live_update_mode ) {
-						inputs.each( function ( i ) {
+						inputs.each( function( i ) {
 							var input = $( this ),
 								sanitized_input = $( sanitized_inputs[i] ),
 								property = control._getInputStatePropertyName( this ),
@@ -827,7 +827,7 @@
 					}
 				}
 			} );
-			jqxhr.fail( function ( jqXHR, textStatus ) {
+			jqxhr.fail( function( jqXHR, textStatus ) {
 				if ( complete_callback ) {
 					complete_callback.call( control, textStatus );
 				}
@@ -872,7 +872,7 @@
 		 *
 		 * @param {boolean|undefined} [do_expand] If not supplied, will be inverse of current visibility
 		 */
-		toggleForm: function ( do_expand ) {
+		toggleForm: function( do_expand ) {
 			var control = this, widget, inside, complete;
 
 			widget = control.container.find( 'div.widget:first' );
@@ -888,7 +888,7 @@
 
 			if ( do_expand ) {
 				// Close all other widget controls before expanding this one
-				api.control.each( function ( other_control ) {
+				api.control.each( function( other_control ) {
 					if ( control.params.type === other_control.params.type && control !== other_control ) {
 						other_control.collapseForm();
 					}
@@ -974,7 +974,7 @@
 		 *
 		 * @param {Number} offset 1|-1
 		 */
-		_moveWidgetByOne: function ( offset ) {
+		_moveWidgetByOne: function( offset ) {
 			var control = this,
 				i,
 				sidebar_widgets_setting,
@@ -997,7 +997,7 @@
 		 *
 		 * @param {Boolean} [toggle]
 		 */
-		toggleWidgetMoveArea: function ( toggle ) {
+		toggleWidgetMoveArea: function( toggle ) {
 			var control = this, move_widget_area;
 			move_widget_area = control.container.find( '.move-widget-area' );
 			if ( typeof toggle === 'undefined' ) {
@@ -1074,12 +1074,12 @@
 					removed_widget_ids = _( old_widget_ids ).difference( new_widget_ids );
 
 				// Filter out any persistent widget_ids for widgets which have been deactivated
-				new_widget_ids = _( new_widget_ids ).filter( function ( new_widget_id ) {
+				new_widget_ids = _( new_widget_ids ).filter( function( new_widget_id ) {
 					var parsed_widget_id = parse_widget_id( new_widget_id );
 					return !! api.Widgets.availableWidgets.findWhere( { id_base: parsed_widget_id.id_base } );
 				} );
 
-				widget_form_controls = _( new_widget_ids ).map( function ( widget_id ) {
+				widget_form_controls = _( new_widget_ids ).map( function( widget_id ) {
 					var widget_form_control = api.Widgets.getWidgetFormControlForWidget( widget_id );
 					if ( ! widget_form_control ) {
 						widget_form_control = control.addWidget( widget_id );
@@ -1088,7 +1088,7 @@
 				} );
 
 				// Sort widget controls to their new positions
-				widget_form_controls.sort( function ( a, b ) {
+				widget_form_controls.sort( function( a, b ) {
 					var a_index = _.indexOf( new_widget_ids, a.params.widget_id ),
 						b_index = _.indexOf( new_widget_ids, b.params.widget_id );
 					if ( a_index === b_index ) {
@@ -1109,12 +1109,12 @@
 				control._applyCardinalOrderClassNames();
 
 				// If the widget was dragged into the sidebar, make sure the sidebar_id param is updated
-				_( widget_form_controls ).each( function ( widget_form_control ) {
+				_( widget_form_controls ).each( function( widget_form_control ) {
 					widget_form_control.params.sidebar_id = control.params.sidebar_id;
 				} );
 
 				// Cleanup after widget removal
-				_( removed_widget_ids ).each( function ( removed_widget_id ) {
+				_( removed_widget_ids ).each( function( removed_widget_id ) {
 
 					// Using setTimeout so that when moving a widget to another sidebar, the other sidebars_widgets settings get a chance to update
 					setTimeout( function() {
@@ -1126,7 +1126,7 @@
 							widget;
 
 						// Check if the widget is in another sidebar
-						api.each( function ( other_setting ) {
+						api.each( function( other_setting ) {
 							if ( other_setting.id === control.setting.id || 0 !== other_setting.id.indexOf( 'sidebars_widgets[' ) || other_setting.id === 'sidebars_widgets[wp_inactive_widgets]' ) {
 								return;
 							}
@@ -1178,13 +1178,13 @@
 			} );
 
 			// Update the model with whether or not the sidebar is rendered
-			api.Widgets.Previewer.bind( 'rendered-sidebars', function ( rendered_sidebars ) {
+			api.Widgets.Previewer.bind( 'rendered-sidebars', function( rendered_sidebars ) {
 				var is_rendered = !! rendered_sidebars[control.params.sidebar_id];
 				registered_sidebar.set( 'is_rendered', is_rendered );
 			} );
 
 			// Show the sidebar section when it becomes visible
-			registered_sidebar.on( 'change:is_rendered', function ( ) {
+			registered_sidebar.on( 'change:is_rendered', function( ) {
 				var section_selector = '#accordion-section-sidebar-widgets-' + this.get( 'id' ), section;
 				section = $( section_selector );
 				if ( this.get( 'is_rendered' ) ) {
@@ -1219,7 +1219,7 @@
 				connectWith: '.accordion-section-content:has(.customize-control-sidebar_widgets)',
 				update: function() {
 					var widget_container_ids = control.section_content.sortable( 'toArray' ), widget_ids;
-					widget_ids = $.map( widget_container_ids, function ( widget_container_id ) {
+					widget_ids = $.map( widget_container_ids, function( widget_container_id ) {
 						return $( '#' + widget_container_id ).find( ':input[name=widget-id]' ).val();
 					} );
 					control.setting( widget_ids );
@@ -1306,7 +1306,7 @@
 		 *
 		 * @param {Boolean} toggle to enable/disable reordering
 		 */
-		toggleReordering: function ( toggle ) {
+		toggleReordering: function( toggle ) {
 			var control = this;
 			toggle = Boolean( toggle );
 			if ( toggle === control.section_content.hasClass( 'reordering' ) ) {
@@ -1317,7 +1317,7 @@
 			control.section_content.toggleClass( 'reordering', toggle );
 
 			if ( toggle ) {
-				_( control.getWidgetFormControls() ).each( function ( form_control ) {
+				_( control.getWidgetFormControls() ).each( function( form_control ) {
 					form_control.collapseForm();
 				} );
 			}
@@ -1329,7 +1329,7 @@
 		getWidgetFormControls: function() {
 			var control = this, form_controls;
 
-			form_controls = _( control.setting() ).map( function ( widget_id ) {
+			form_controls = _( control.setting() ).map( function( widget_id ) {
 				var setting_id = widget_id_to_setting_id( widget_id ),
 					form_control = api.control( setting_id );
 
@@ -1345,7 +1345,7 @@
 		 * @param {string} widget_id or an id_base for adding a previously non-existing widget
 		 * @returns {object} widget_form control instance
 		 */
-		addWidget: function ( widget_id ) {
+		addWidget: function( widget_id ) {
 			var control = this,
 				control_html,
 				widget_el,
@@ -1377,7 +1377,7 @@
 
 			control_html = $( '#widget-tpl-' + widget.get( 'id' ) ).html();
 			if ( widget.get( 'is_multi' ) ) {
-				control_html = control_html.replace( /<[^<>]+>/g, function ( m ) {
+				control_html = control_html.replace( /<[^<>]+>/g, function( m ) {
 					return m.replace( /__i__|%i%/g, widget_number );
 				} );
 			} else {
@@ -1435,7 +1435,7 @@
 			api.control.add( setting_id, widget_form_control );
 
 			// Make sure widget is removed from the other sidebars
-			api.each( function ( other_setting ) {
+			api.each( function( other_setting ) {
 				if ( other_setting.id === control.setting.id ) {
 					return;
 				}
@@ -1462,7 +1462,7 @@
 					widget_form_control.expandForm();
 					widget_form_control.updateWidget( {
 						instance: widget_form_control.setting(),
-						complete: function ( error ) {
+						complete: function( error ) {
 							if ( error ) {
 								throw error;
 							}
@@ -1488,7 +1488,9 @@
 
 	api.bind( 'ready', function() {
 		// Set up the widgets panel
-		api.Widgets.availableWidgetsPanel.setup();
+		api.Widgets.availableWidgetsPanel = new api.Widgets.AvailableWidgetsPanelView({
+			collection: api.Widgets.availableWidgets
+		});
 
 		// Highlight widget control
 		api.Widgets.Previewer.bind( 'highlight-widget-control', api.Widgets.highlightWidgetFormControl );
@@ -1540,10 +1542,10 @@
 	 * @param {string} widget_id
 	 * @return {object|null}
 	 */
-	api.Widgets.getSidebarWidgetControlContainingWidget = function ( widget_id ) {
+	api.Widgets.getSidebarWidgetControlContainingWidget = function( widget_id ) {
 		var found_control = null;
 		// @todo this can use widget_id_to_setting_id(), then pass into wp.customize.control( x ).getSidebarWidgetsControl()
-		api.control.each( function ( control ) {
+		api.control.each( function( control ) {
 			if ( control.params.type === 'sidebar_widgets' && -1 !== _.indexOf( control.setting(), widget_id ) ) {
 				found_control = control;
 			}
@@ -1557,10 +1559,10 @@
 	 * @param {string} widget_id
 	 * @return {object|null}
 	 */
-	api.Widgets.getWidgetFormControlForWidget = function ( widget_id ) {
+	api.Widgets.getWidgetFormControlForWidget = function( widget_id ) {
 		var found_control = null;
 		// @todo We can just use widget_id_to_setting_id() here
-		api.control.each( function ( control ) {
+		api.control.each( function( control ) {
 			if ( control.params.type === 'widget_form' && control.params.widget_id === widget_id ) {
 				found_control = control;
 			}
@@ -1569,213 +1571,214 @@
 		return found_control;
 	};
 
-	/**
-	 * Available Widgets Panel
-	 */
-	api.Widgets.availableWidgetsPanel = {
-		active_sidebar_widgets_control: null,
-		selected_widget_tpl: null,
-		container: null,
-		filter_input: null,
+	api.Widgets.AvailableWidgetsPanelView = wp.Backbone.View.extend({
 
-		/**
-		 * Set up event listeners
-		 */
-		setup: function() {
-			var panel = this;
+		el: '#available-widgets',
 
-			panel.container = $( '#available-widgets' );
-			panel.filter_input = $( '#available-widgets-filter' ).find( 'input' );
+		events: {
+			'input #widgets-search': 'search',
+			'keyup #widgets-search': 'search',
+			'change #widgets-search': 'search',
+			'search #widgets-search': 'search',
+			'focus .widget-tpl' : 'focus',
+			'click .widget-tpl' : '_submit',
+			'keypress .widget-tpl' : '_submit',
+			'keydown' : 'keyboardAccessible'
+		},
 
-			api.Widgets.availableWidgets.on( 'change update', panel.update_available_widgets_list );
-			panel.update_available_widgets_list();
+		selected: null,
+		currentSidebarControl: null,
+		$search: null,
 
+		initialize: function() {
+			var self = this;
+
+			this.$search = $( '#widgets-search' );
+
+			this.listenTo( this.collection, 'update', this.updateList );
+
+			this.updateList();
+
 			// If the available widgets panel is open and the customize controls are
 			// interacted with (i.e. available widgets panel is blurred) then close the
 			// available widgets panel.
-			$( '#customize-controls' ).on( 'click keydown', function ( e ) {
-				var is_add_new_widget_btn = $( e.target ).is( '.add-new-widget, .add-new-widget *' );
-				if ( $( 'body' ).hasClass( 'adding-widget' ) && ! is_add_new_widget_btn ) {
-					panel.close();
+			$( '#customize-controls' ).on( 'click keydown', function( e ) {
+				var isAddNewBtn = $( e.target ).is( '.add-new-widget, .add-new-widget *' );
+				if ( $( 'body' ).hasClass( 'adding-widget' ) && ! isAddNewBtn ) {
+					self.close();
 				}
 			} );
 
 			// Close the panel if the URL in the preview changes
-			api.Widgets.Previewer.bind( 'url', function() {
-				panel.close();
-			} );
+			api.Widgets.Previewer.bind( 'url', this.close );
+		},
 
-			// Submit a selection when clicked or keypressed
-			panel.container.find( '.widget-tpl' ).on( 'click keypress', function( event ) {
+		search: function( event ) {
+			var firstVisible;
 
-				// Only proceed with keypress if it is Enter or Spacebar
-				if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
-					return;
-				}
+			this.collection.doSearch( event.target.value );
 
-				panel.submit( this );
-			} );
+			// Remove a widget from being selected if it is no longer visible
+			if ( this.selected && ! this.selected.is( ':visible' ) ) {
+				this.selected.removeClass( 'selected' );
+				this.selected = null;
+			}
 
-			panel.filter_input.on( 'input keyup change', function( event ) {
-				var first_visible_widget;
+			// If a widget was selected but the filter value has been cleared out, clear selection
+			if ( this.selected && ! event.target.value ) {
+				this.selected.removeClass( 'selected' );
+				this.selected = null;
+			}
 
-				api.Widgets.availableWidgets.doSearch( event.target.value );
-
-				// Remove a widget from being selected if it is no longer visible
-				if ( panel.selected_widget_tpl && ! panel.selected_widget_tpl.is( ':visible' ) ) {
-					panel.selected_widget_tpl.removeClass( 'selected' );
-					panel.selected_widget_tpl = null;
+			// If a filter has been entered and a widget hasn't been selected, select the first one shown
+			if ( ! this.selected && event.target.value ) {
+				firstVisible = this.$el.find( '> .widget-tpl:visible:first' );
+				if ( firstVisible.length ) {
+					this.select( firstVisible );
 				}
+			}
+		},
 
-				// If a widget was selected but the filter value has been cleared out, clear selection
-				if ( panel.selected_widget_tpl && ! event.target.value ) {
-					panel.selected_widget_tpl.removeClass( 'selected' );
-					panel.selected_widget_tpl = null;
-				}
+		updateList: function() {
+			// First hide all widgets...
+			this.$el.find( '.widget-tpl' ).hide();
 
-				// If a filter has been entered and a widget hasn't been selected, select the first one shown
-				if ( ! panel.selected_widget_tpl &&  event.target.value ) {
-					first_visible_widget = panel.container.find( '> .widget-tpl:visible:first' );
-					if ( first_visible_widget.length ) {
-						panel.select( first_visible_widget );
-					}
+			// ..and then show only available widgets which could be filtered
+			this.collection.each( function( widget ) {
+				var widgetTpl = $( '#widget-tpl-' + widget.id );
+				widgetTpl.toggle( ! widget.get( 'is_disabled' ) );
+				if ( widget.get( 'is_disabled' ) && widgetTpl.is( this.selected ) ) {
+					this.selected = null;
 				}
 			} );
+		},
 
-			// Select a widget when it is focused on
-			panel.container.find( ' > .widget-tpl' ).on( 'focus', function() {
-				panel.select( this );
-			} );
+		select: function( widgetTpl ) {
+			this.selected = $( widgetTpl );
+			this.selected.siblings( '.widget-tpl' ).removeClass( 'selected' );
+			this.selected.addClass( 'selected' );
+		},
 
-			panel.container.on( 'keydown', function ( event ) {
-				var is_enter = ( event.which === 13 ),
-					is_esc = ( event.which === 27 ),
-					is_down = ( event.which === 40 ),
-					is_up = ( event.which === 38 ),
-					selected_widget_tpl = null,
-					first_visible_widget = panel.container.find( '> .widget-tpl:visible:first' ),
-					last_visible_widget = panel.container.find( '> .widget-tpl:visible:last' ),
-					is_input_focused = $( event.target ).is( panel.filter_input );
-
-				if ( is_down || is_up ) {
-					if ( is_down ) {
-						if ( is_input_focused ) {
-							selected_widget_tpl = first_visible_widget;
-						} else if ( panel.selected_widget_tpl && panel.selected_widget_tpl.nextAll( '.widget-tpl:visible' ).length !== 0 ) {
-							selected_widget_tpl = panel.selected_widget_tpl.nextAll( '.widget-tpl:visible:first' );
-						}
-					} else if ( is_up ) {
-						if ( is_input_focused ) {
-							selected_widget_tpl = last_visible_widget;
-						} else if ( panel.selected_widget_tpl && panel.selected_widget_tpl.prevAll( '.widget-tpl:visible' ).length !== 0 ) {
-							selected_widget_tpl = panel.selected_widget_tpl.prevAll( '.widget-tpl:visible:first' );
-						}
-					}
-					panel.select( selected_widget_tpl );
-					if ( selected_widget_tpl ) {
-						selected_widget_tpl.focus();
-					} else {
-						panel.filter_input.focus();
-					}
-					return;
-				}
-
-				// If enter pressed but nothing entered, don't do anything
-				if ( is_enter && ! panel.filter_input.val() ) {
-					return;
-				}
-
-				if ( is_enter ) {
-					panel.submit();
-				} else if ( is_esc ) {
-					panel.close( { return_focus: true } );
-				}
-			} );
+		focus: function( event ) {
+			this.select( $( event.currentTarget ) );
 		},
 
-		/**
-		 * Updates widgets list.
-		 */
-		update_available_widgets_list: function() {
-			var panel = api.Widgets.availableWidgetsPanel;
+		_submit: function( event ) {
+			// Only proceed with keypress if it is Enter or Spacebar
+			if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
+				return;
+			}
 
-			// First hide all widgets...
-			panel.container.find( '.widget-tpl' ).hide();
-
-			// ..and then show only available widgets which could be filtered
-			api.Widgets.availableWidgets.each( function ( widget ) {
-				var widget_tpl = $( '#widget-tpl-' + widget.id );
-				widget_tpl.toggle( ! widget.get( 'is_disabled' ) );
-				if ( widget.get( 'is_disabled' ) && widget_tpl.is( panel.selected_widget_tpl ) ) {
-					panel.selected_widget_tpl = null;
-				}
-			} );
+			this.submit( $( event.currentTarget ) );
 		},
 
-		/**
-		 * @param widget_tpl
-		 */
-		select: function ( widget_tpl ) {
-			var panel = this;
-			panel.selected_widget_tpl = $( widget_tpl );
-			panel.selected_widget_tpl.siblings( '.widget-tpl' ).removeClass( 'selected' );
-			panel.selected_widget_tpl.addClass( 'selected' );
-		},
+		submit: function( widgetTpl ) {
+			var widgetId, widget;
 
-		submit: function ( widget_tpl ) {
-			var panel = this, widget_id, widget;
-			if ( ! widget_tpl ) {
-				widget_tpl = panel.selected_widget_tpl;
+			if ( ! widgetTpl ) {
+				widgetTpl = this.selected;
 			}
-			if ( ! widget_tpl || ! panel.active_sidebar_widgets_control ) {
+
+			if ( ! widgetTpl || ! this.currentSidebarControl ) {
 				return;
 			}
-			panel.select( widget_tpl );
 
-			widget_id = $( panel.selected_widget_tpl ).data( 'widget-id' );
-			widget = api.Widgets.availableWidgets.findWhere( {id: widget_id} );
+			this.select( widgetTpl );
+
+			widgetId = $( this.selected ).data( 'widget-id' );
+			widget = this.collection.findWhere( { id: widgetId } );
 			if ( ! widget ) {
-				throw new Error( 'Widget unexpectedly not found.' );
+				return;
 			}
-			panel.active_sidebar_widgets_control.addWidget( widget.get( 'id_base' ) );
-			panel.close();
+
+			this.currentSidebarControl.addWidget( widget.get( 'id_base' ) );
+
+			this.close();
 		},
 
-		/**
-		 * @param sidebars_widgets_control
-		 */
-		open: function ( sidebars_widgets_control ) {
-			var panel = this;
-			panel.active_sidebar_widgets_control = sidebars_widgets_control;
+		open: function( sidebarControl ) {
+			this.currentSidebarControl = sidebarControl;
 
 			// Wide widget controls appear over the preview, and so they need to be collapsed when the panel opens
-			_( sidebars_widgets_control.getWidgetFormControls() ).each( function ( control ) {
+			_( this.currentSidebarControl.getWidgetFormControls() ).each( function( control ) {
 				if ( control.params.is_wide ) {
 					control.collapseForm();
 				}
 			} );
 
 			$( 'body' ).addClass( 'adding-widget' );
-			panel.container.find( '.widget-tpl' ).removeClass( 'selected' );
-			api.Widgets.availableWidgets.doSearch( '' );
-			panel.filter_input.focus();
+
+			this.$el.find( '.selected' ).removeClass( 'selected' );
+
+			// Reset search
+			this.collection.doSearch( '' );
+
+			this.$search.focus();
 		},
 
-		/**
-		 * Hide the panel
-		 */
-		close: function ( options ) {
-			var panel = this;
+		close: function( options ) {
 			options = options || {};
-			if ( options.return_focus && panel.active_sidebar_widgets_control ) {
-				panel.active_sidebar_widgets_control.container.find( '.add-new-widget' ).focus();
+
+			if ( options.returnFocus && this.currentSidebarControl ) {
+				this.currentSidebarControl.container.find( '.add-new-widget' ).focus();
 			}
-			panel.active_sidebar_widgets_control = null;
-			panel.selected_widget_tpl = null;
+
+			this.currentSidebarControl = null;
+			this.selected = null;
+
 			$( 'body' ).removeClass( 'adding-widget' );
-			panel.filter_input.val( '' );
+
+			this.$search.val( '' );
+		},
+
+		keyboardAccessible: function( event ) {
+			var isEnter = ( event.which === 13 ),
+				isEsc = ( event.which === 27 ),
+				isDown = ( event.which === 40 ),
+				isUp = ( event.which === 38 ),
+				selected = null,
+				firstVisible = this.$el.find( '> .widget-tpl:visible:first' ),
+				lastVisible = this.$el.find( '> .widget-tpl:visible:last' ),
+				isSearchFocused = $( event.target ).is( this.$search );
+
+			if ( isDown || isUp ) {
+				if ( isDown ) {
+					if ( isSearchFocused ) {
+						selected = firstVisible;
+					} else if ( this.selected && this.selected.nextAll( '.widget-tpl:visible' ).length !== 0 ) {
+						selected = this.selected.nextAll( '.widget-tpl:visible:first' );
+					}
+				} else if ( isUp ) {
+					if ( isSearchFocused ) {
+						selected = lastVisible;
+					} else if ( this.selected && this.selected.prevAll( '.widget-tpl:visible' ).length !== 0 ) {
+						selected = this.selected.prevAll( '.widget-tpl:visible:first' );
+					}
+				}
+
+				this.select( selected );
+
+				if ( selected ) {
+					selected.focus();
+				} else {
+					this.$search.focus();
+				}
+
+				return;
+			}
+
+			// If enter pressed but nothing entered, don't do anything
+			if ( isEnter && ! this.$search.val() ) {
+				return;
+			}
+
+			if ( isEnter ) {
+				this.submit();
+			} else if ( isEsc ) {
+				this.close( { returnFocus: true } );
+			}
 		}
-	};
+	});
 
 	/**
 	 * @param {String} widget_id
