')
				.addClass('dir')
				.click(function(){
					that
						.update(cur.slice(0, -1)) 
						.select('"'+cur.pop()+'"')
				})
				.text(e))
		})
		// add current selction indicator...
		p.append($('
')
			.addClass('dir cur')
			// XXX add a filter mode...
			.click(function(){
				that.startFilter()
				//that.update(path.concat($(this).text())) 
			}))
		// fill the children list...
		this.list(path)
			.forEach(function(e){
				l.append($('
')
					.click(function(){
						if(!$(this).hasClass('disabled')){
							that.update(that.path.concat([$(this).text()])) 
						}
					})
					.text(e))
			})
		return this
	},
	// internal actions...
	// XXX pattern modes:
	// 		- lazy match
	// 			abc		-> *abc*		-> ^.*abc.*$
	// 			ab cd	-> *ab*cd*		-> ^.*ab.*cd.*$
	// 		- glob
	// 		- regex
	// XXX sort:
	// 		- as-is
	// 		- best match
	filter: function(pattern, non_matched, sort){
		var that = this
		var browser = this.dom
		// show all...
		if(pattern == null || pattern.trim() == '*'){
			browser.find('.filtered-out')
				.removeClass('filtered-out')
			// clear the highlighing...
			browser.find('.list b')
				.replaceWith(function() { return this.innerHTML })
		// basic filter...
		} else {
			var l = browser.find('.list>div:not(disabled)')
			l.each(function(i, e){
				e = $(e)
				var t = e.text()
				var i = t.search(pattern)
				if(i < 0){
					e
						.addClass('filtered-out')
						.removeClass('selected')
				} else {
					e.html(t.replace(pattern, pattern.bold()))
						.removeClass('filtered-out')
				}
			})
		}
		return this
	},
	// XXX start search/filter...
	// 		- set content editable
	// 		- triger filterig on modified
	// 		- disable nav in favor of editing
	// 		- enter/blur to exit edit mode
	// 		- esc to cancel and reset
	// XXX BUG: when starting with '/' key the '/' gets appended to the
	// 		field...
	// XXX make this a toggler...
	startFilter: function(){
		var range = document.createRange()
		var selection = window.getSelection()
		var that = this
		var e = this.dom.find('.path .dir.cur')
			.text('')
			.attr('contenteditable', true)
			.keyup(function(){
				that.filter($(this).text())
			})
			.focus()
		// place the cursor...
		range.setStart(e[0], 0)
		range.collapse(true)
		// XXX
		selection.removeAllRanges()
		selection.addRange(range)
		return this
	},
	stopFilter: function(){
		this.filter('*')
		this.dom.find('.path .dir.cur')
			.text('')
			.removeAttr('contenteditable')
		this
			.focus()
		return this
	},
	get filtering(){
		return this.dom.find('.path .dir.cur[contenteditable]').length > 0 
	},
	toggleFilterMode: function(){
		this.dom.toggleClass('show-filtered-out')
		return this
	},
	// Select a list element...
	//
	//	Get selected element if it exists, otherwise select and return 
	//	the first...
	//	.select()
	//		-> elem
	//
	//	Get selected element if it exists, null otherwise...
	//	.select('!')
	//		-> elem
	//		-> $()
	//
	//	Select first/last child
	//	.select('first')
	//	.select('last')
	//		-> elem
	//
	//	Select previous/next child
	//	.select('prev')
	//	.select('next')
	//		-> elem
	//
	//	Deselect
	//	.select('none')
	//		-> elem
	//
	//	Select element by sequence number
	//	NOTE: negative numbers count from the tail.
	//	NOTE: overflowing selects the first/last element.
	//	.select()
	//		-> elem
	//
	//	Select element by its text...
	//	NOTE: if text matches one of the reserved commands above use 
	//		quotes to escape it...
	//	.select('')
	//	.select("''")
	//	.select('""')
	//		-> elem
	//
	//	Select element via a regular expression...
	//	.select()
	//		-> elem
	//
	//	.select()
	//		-> elem
	//
	// This will return a jQuery object.
	//
	// NOTE: if multiple matches occur this will select the first.
	//
	//
	// XXX revise return values...
	// XXX Q: should this trigger a "select" event???
	// XXX on string/regexp mismatch this will select the first, is this correct???
	select: function(elem, filtering){
		var pattern = '.list div:not(.disabled):not(.filtered-out)'
		var browser = this.dom
		var elems = browser.find(pattern)
		filtering = filtering == null ? this.filtering : filtering
		if(elems.length == 0){
			return $()
		}
		elem = elem == 0 ? 'first' : elem
		elem = elem || this.select('!')
		// if none selected get the first...
		elem = elem.length == 0 ? 'first' : elem
		// first/last...
		if(elem == 'first' || elem == 'last'){
			return this.select(elems[elem](), filtering)
		
		// prev/next...
		} else if(elem == 'prev' || elem == 'next'){
			var to = this.select('!', filtering)[elem + 'All'](pattern).first()
			if(to.length == 0){
				return this.select(elem == 'prev' ? 'last' : 'first', filtering)
			}
			this.select('none', filtering)
			return this.select(to, filtering)
		// deselect...
		} else if(elem == 'none'){
			if(!filtering){
				browser.find('.path .dir.cur').empty()
			}
			return elems
				.filter('.selected')
				.removeClass('selected')
		// strict...
		} else if(elem == '!'){
			return elems.filter('.selected')
		// number...
		// NOTE: on overflow this will get the first/last element...
		} else if(typeof(elem) == typeof(123)){
			return this.select($(elems.slice(elem)[0] || elems.slice(-1)[0] ), filtering)
		// string...
		// XXX on mismatch this will select the first, is this correct???
		} else if(typeof(elem) == typeof('str')){
			if(/^'.*'$|^".*"$/.test(elem.trim())){
				elem = elem.trim().slice(1, -1)
			}
			return this.select(browser.find(pattern)
					.filter(function(i, e){
						return $(e).text() == elem
					}), filtering)
		// regexp...
		// XXX on mismatch this will select the first, is this correct???
		} else if(elem.constructor === RegExp){
			return this.select(browser.find(pattern)
					.filter(function(i, e){
						return elem.test($(e).text())
					}), filtering)
		// element...
		} else {
			elem = $(elem).first()
			if(elem.length == 0){
				this.select(null, filtering)
			} else {
				this.select('none', filtering)
				if(!filtering){
					browser.find('.path .dir.cur').text(elem.text())
				}
				return elem.addClass('selected')
			}
		}
	},
	// Select next element...
	next: function(elem){
		if(elem != null){
			this.select(elem)
		}
		this.select('next')
		return this
	},
	// Select previous element...
	prev: function(elem){
		if(elem != null){
			this.select(elem)
		}
		this.select('prev')
		return this
	},
	// Push an element to path / go down one level...
	push: function(elem){
		var browser = this.dom 
		var elem = this.select(elem || '!')
		// nothing selected, select first and exit...
		if(elem.length == 0){
			this.select()
			return this
		}
		var path = this.path
		path.push(elem.text())
		// if not traversable call the action...
		if(this.isTraversable != null 
				&& (this.isTraversable !== false
					|| ! this.isTraversable(path))){
			return this.action(path)
		}
		this.path = path
		this.select()
		return this
	},
	// Pop an element off the path / go up one level...
	pop: function(){
		var browser = this.dom
		var path = this.path
		var dir = path.pop()
		this.update(path)
		this.select('"'+dir+'"')
		return this
	},
	focus: function(){
		this.dom.focus()
		return this
	},
	// XXX think about the API...
	// XXX trigger an "open" event...
	action: function(){
		var elem = this.select('!')
		// nothing selected, select first and exit...
		if(elem.length == 0){
			this.select()
			return this
		}
		var path = this.path
		path.push(elem.text())
		var res = this.open(path)
		return res
	},
	// extension methods...
	// XXX this is wrong...
	// 		...need to actually open something...
	open: function(path){ 
		path = path || this.path
		var m = this.options.list
		return m ? m.call(this, path) : path
	},
	list: function(path){
		path = path || this.path
		var m = this.options.list
		return m ? m.call(this, path) : []
	},
	isTraversable: null,
	// XXX need to get a container....
	// XXX setup instance events...
	__init__: function(parent, options){
		options = options || {}
		// merge options...
		var opts = Object.create(this.options)
		Object.keys(options).forEach(function(n){ opts[n] = options[n] })
		options = this.options = opts
		// build the dom...
		var dom = this.dom = this.constructor.make(options)
		// add keyboard handler...
		dom.keydown(
			keyboard.makeKeyboardHandler(
				this.keyboard,
				options.logKeys,
				this))
		// attach to parent...
		if(parent != null){
			parent.append(dom)
		}
		// load the initial state...
		this.update(this.path)
	},
}
/*
var Browser = 
//module.Browser = 
object.makeConstructor('Browser', 
		BrowserClassPrototype, 
		BrowserPrototype)
*/
/**********************************************************************
* vim:set ts=4 sw=4 :                                                */