Skip to content

Instantly share code, notes, and snippets.

@bruce965
Created June 20, 2015 20:20
Show Gist options
  • Save bruce965/fb62ca736050aea771b5 to your computer and use it in GitHub Desktop.
Save bruce965/fb62ca736050aea771b5 to your computer and use it in GitHub Desktop.
Confine focus inside a limited set of elements
/// <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