One of my Bulk Attachment Download users suggested I make it work for grid view as well as list view, and since this was entirely sensible and something I should have looked at sooner, off I went. Most of the work was relatively straightforward, but I hit a bump when trying to respond to users selecting attachments for download.
Although I could get a jQuery function to react to a click event on the image, nothing would happen when clicking on the check / uncheck icon at the top right of each image.

The problem came from an event.stopPropagation();
in this section of WordPress’s media handling:
/**
* Add the model if it isn't in the selection, if it is in the selection,
* remove it.
*
* @param {[type]} event [description]
* @return {[type]} [description]
*/
checkClickHandler: function ( event ) {
var selection = this.options.selection;
if ( ! selection ) {
return;
}
event.stopPropagation(); // <--- AHA!!!
if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
selection.remove( this.model );
// Move focus back to the attachment tile (from the check).
this.$el.focus();
} else {
selection.add( this.model );
}
// Trigger an action button update.
this.controller.trigger( 'selection:toggle' );
}
Luckily the same section of code has the first part of the answer – the very last line:
this.controller.trigger( 'selection:toggle' );
Simple, I thought – just use that event to fire whatever I needed to. Except not quite. Using something like $( 'body' ).on( 'click', function() { // Do stuff } );
won’t work. Instead, and for reasons that I am not sure I fully understand yet, the event listener must be set to the wp.media.frame
object. Thus for anyone else struggling with this, the answer is:
if ( 'undefined' != typeof( wp.media.frame ) ) {
wp.media.frame.on('selection:toggle', function() {
// Do stuff
} );
}