Created
June 20, 2015 20:20
-
-
Save bruce965/fb62ca736050aea771b5 to your computer and use it in GitHub Desktop.
Confine focus inside a limited set of elements
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <reference path="../../defs/jquery/jquery.d.ts" /> | |
module fg.dom { | |
// get the next element in a JQuery set (returns the first element if 'current' is the last in the set) | |
var nextElement = (set: JQuery, current: Element) => { | |
var index = set.index(current); | |
if (index < set.length - 1) | |
return set.get(index + 1); | |
return set.first().get(0); | |
}; | |
// get the previous element in a JQuery set (returns the last element if 'current' is the first in the set) | |
var prevElement = (set: JQuery, current: Element) => { | |
var index = set.index(current); | |
if (index > 0) | |
return set.get(index - 1); | |
return set.last().get(0); | |
}; | |
/** Confines focus to a limited set of DOM elements and their children. Requires JQuery UI. */ | |
export class FocusConfiner { | |
private lastFocus: Element; | |
private confinementEnabled = false; | |
constructor(private confines: JQuery) { | |
if (confines.find(confines).length) { | |
//console.warn("FocusContainer confines cannot contain other confines."); | |
confines = confines.not(confines.find(confines)); | |
} | |
} | |
/** Start confining focus. */ | |
public confine() { | |
if (this.confinementEnabled) | |
return; | |
this.confinementEnabled = true; | |
$(window).on('blur', this.instanceOnWindowBlur); | |
$(document.body).delegate(':focusable', 'focus', this.instanceOnFocusChanged); | |
this.instanceOnFocusChanged(); // ensure a valid state | |
} | |
/** Stop confining focus. */ | |
public suspend() { | |
if (!this.confinementEnabled) | |
return; | |
this.confinementEnabled = false; | |
$(window).off('blur', this.instanceOnWindowBlur); | |
$(document.body).undelegate(':focusable', 'focus', this.instanceOnFocusChanged); | |
} | |
private instanceOnFocusChanged = () => this.onFocusChanged(); | |
private onFocusChanged() { | |
if (!document.activeElement || document.activeElement === document.body) { | |
this.lastFocus = null; | |
return; // nothing focused, this is a valid state | |
} | |
if (this.confines.has(document.activeElement).length) { | |
this.lastFocus = document.activeElement; | |
return; // we are already in a valid state | |
} | |
// this might be slow if we have a few thousand buttons or links on the page | |
var globalFocusables = $(document.body).find(':focusable'); | |
var focusables = this.confines.find(':focusable'); | |
// focusing forwards | |
if (nextElement(globalFocusables, this.lastFocus) === document.activeElement) | |
this.lastFocus = nextElement(focusables, this.lastFocus); | |
// focusing backwards | |
else if (prevElement(globalFocusables, this.lastFocus) === document.activeElement) | |
this.lastFocus = prevElement(focusables, this.lastFocus); | |
// focus restored from outside window | |
else if (!this.lastFocus) | |
this.lastFocus = focusables.first().get(0); | |
// the user mouse-jumped to a focusable element outside confines | |
//else | |
// this.lastFocus = this.lastFocus; | |
$(this.lastFocus).focus(); | |
} | |
private instanceOnWindowBlur = () => this.onWindowBlur(); | |
private onWindowBlur(): void { | |
this.lastFocus = null; | |
$(document.activeElement).blur(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment