add focus controller, manages keyboard focus across multiple embedded decks

This commit is contained in:
Hakim El Hattab 2020-05-11 09:15:02 +02:00
parent 57107ebe4c
commit 664beff715
6 changed files with 125 additions and 6 deletions

4
dist/reveal.esm.js vendored

File diff suppressed because one or more lines are too long

4
dist/reveal.js vendored

File diff suppressed because one or more lines are too long

View File

@ -57,6 +57,15 @@
</div> </div>
</div> </div>
<style>
.reveal {
border: 4px solid #ccc;
}
.reveal.focused {
border-color: #94b5ff;
}
</style>
<script src="../dist/reveal.js"></script> <script src="../dist/reveal.js"></script>
<script src="../dist/plugin/highlight.js"></script> <script src="../dist/plugin/highlight.js"></script>
<script src="../dist/plugin/markdown.js"></script> <script src="../dist/plugin/markdown.js"></script>
@ -65,7 +74,8 @@
let deck1 = new Reveal( document.querySelector( '.deck1' ), { let deck1 = new Reveal( document.querySelector( '.deck1' ), {
embedded: true, embedded: true,
keyboard: false, progress: false,
keyboardCondition: 'focused',
plugins: [ RevealHighlight ] plugins: [ RevealHighlight ]
} ); } );
deck1.on( 'slidechanged', () => { deck1.on( 'slidechanged', () => {
@ -75,7 +85,8 @@
let deck2 = new Reveal( document.querySelector( '.deck2' ), { let deck2 = new Reveal( document.querySelector( '.deck2' ), {
embedded: true, embedded: true,
keyboard: true, progress: false,
keyboardCondition: 'focused',
plugins: [ RevealMarkdown, RevealMath ] plugins: [ RevealMarkdown, RevealMath ]
} ); } );
deck2.initialize().then( () => { deck2.initialize().then( () => {

95
js/controllers/focus.js Normal file
View File

@ -0,0 +1,95 @@
/**
* Manages focus when a presentation is embedded. This
* helps us only capture keyboard from the presentation
* a user is currently interacting with in a page where
* multiple presentations are embedded.
*/
const STATE_FOCUS = 'focus';
const STATE_BLUR = 'blur';
export default class Focus {
constructor( Reveal ) {
this.Reveal = Reveal;
this.onRevealPointerDown = this.onRevealPointerDown.bind( this );
this.onDocumentPointerDown = this.onDocumentPointerDown.bind( this );
}
/**
* Called when the reveal.js config is updated.
*/
configure( config, oldConfig ) {
if( config.embedded ) {
this.blur();
}
else {
this.focus();
this.unbind();
}
}
bind() {
if( this.Reveal.getConfig().embedded ) {
this.Reveal.getRevealElement().addEventListener( 'pointerdown', this.onRevealPointerDown, false );
}
}
unbind() {
this.Reveal.getRevealElement().removeEventListener( 'pointerdown', this.onRevealPointerDown, false );
document.removeEventListener( 'pointerdown', this.onDocumentPointerDown, false );
}
focus() {
if( this.state !== STATE_FOCUS ) {
this.Reveal.getRevealElement().classList.add( 'focused' );
document.addEventListener( 'pointerdown', this.onDocumentPointerDown, false );
}
this.state = STATE_FOCUS;
}
blur() {
if( this.state !== STATE_BLUR ) {
this.Reveal.getRevealElement().classList.remove( 'focused' );
document.removeEventListener( 'pointerdown', this.onDocumentPointerDown, false );
}
this.state = STATE_BLUR;
}
isFocused() {
return this.state === STATE_FOCUS;
}
onRevealPointerDown( event ) {
this.focus();
}
onDocumentPointerDown( event ) {
let revealElement = event.target.closest( '.reveal' );
if( !revealElement || revealElement !== this.Reveal.getRevealElement() ) {
this.blur();
}
}
}

View File

@ -151,6 +151,12 @@ export default class Keyboard {
return true; return true;
} }
// If keyboardCondition is set, only capture keyboard events
// for embedded decks when they are focused
if( config.keyboardCondition === 'focused' && !this.Reveal.isFocused() ) {
return true;
}
// Shorthand // Shorthand
let keyCode = event.keyCode; let keyCode = event.keyCode;

View File

@ -12,6 +12,7 @@ import Pointer from './controllers/pointer.js'
import Plugins from './controllers/plugins.js' import Plugins from './controllers/plugins.js'
import Print from './controllers/print.js' import Print from './controllers/print.js'
import Touch from './controllers/touch.js' import Touch from './controllers/touch.js'
import Focus from './controllers/focus.js'
import Notes from './controllers/notes.js' import Notes from './controllers/notes.js'
import Playback from './components/playback.js' import Playback from './components/playback.js'
import defaultConfig from './config.js' import defaultConfig from './config.js'
@ -111,6 +112,7 @@ export default function( revealElement, options ) {
pointer = new Pointer( Reveal ), pointer = new Pointer( Reveal ),
plugins = new Plugins( Reveal ), plugins = new Plugins( Reveal ),
print = new Print( Reveal ), print = new Print( Reveal ),
focus = new Focus( Reveal ),
touch = new Touch( Reveal ), touch = new Touch( Reveal ),
notes = new Notes( Reveal ); notes = new Notes( Reveal );
@ -464,6 +466,7 @@ export default function( revealElement, options ) {
} }
notes.configure( config, oldConfig ); notes.configure( config, oldConfig );
focus.configure( config, oldConfig );
pointer.configure( config, oldConfig ); pointer.configure( config, oldConfig );
controls.configure( config, oldConfig ); controls.configure( config, oldConfig );
progress.configure( config, oldConfig ); progress.configure( config, oldConfig );
@ -489,6 +492,7 @@ export default function( revealElement, options ) {
if( config.progress ) progress.bind(); if( config.progress ) progress.bind();
if( config.respondToHashChanges ) location.bind(); if( config.respondToHashChanges ) location.bind();
controls.bind(); controls.bind();
focus.bind();
dom.slides.addEventListener( 'transitionend', onTransitionEnd, false ); dom.slides.addEventListener( 'transitionend', onTransitionEnd, false );
dom.pauseOverlay.addEventListener( 'click', resume, false ); dom.pauseOverlay.addEventListener( 'click', resume, false );
@ -507,6 +511,7 @@ export default function( revealElement, options ) {
eventsAreBound = false; eventsAreBound = false;
touch.unbind(); touch.unbind();
focus.unbind();
keyboard.unbind(); keyboard.unbind();
controls.unbind(); controls.unbind();
progress.unbind(); progress.unbind();
@ -2438,6 +2443,7 @@ export default function( revealElement, options ) {
isAutoSliding, isAutoSliding,
isSpeakerNotes: notes.isSpeakerNotesWindow.bind( notes ), isSpeakerNotes: notes.isSpeakerNotesWindow.bind( notes ),
isOverview: overview.isActive.bind( overview ), isOverview: overview.isActive.bind( overview ),
isFocused: focus.isFocused.bind( focus ),
isPrintingPDF: print.isPrintingPDF.bind( print ), isPrintingPDF: print.isPrintingPDF.bind( print ),
// Checks if reveal.js has been loaded and is ready for use // Checks if reveal.js has been loaded and is ready for use
@ -2548,6 +2554,7 @@ export default function( revealElement, options ) {
// Controllers // Controllers
print, print,
focus,
progress, progress,
controls, controls,
location, location,