mirror of
				https://github.com/flynx/ImageGrid.git
				synced 2025-11-03 21:00:14 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1916 lines
		
	
	
		
			50 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1916 lines
		
	
	
		
			50 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
/**********************************************************************
 | 
						|
* 
 | 
						|
*
 | 
						|
*
 | 
						|
**********************************************************************/
 | 
						|
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
 | 
						|
(function(require){ var module={} // make module AMD/node compatible...
 | 
						|
/*********************************************************************/
 | 
						|
 | 
						|
var toggler = require('../toggler')
 | 
						|
var keyboard = require('../keyboard')
 | 
						|
var object = require('../object')
 | 
						|
var widget = require('./widget')
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/*********************************************************************/
 | 
						|
// Helpers...
 | 
						|
 | 
						|
// Collect a list of literal values and "make(..) calls" into an array...
 | 
						|
//
 | 
						|
//	collectItems(context, items)
 | 
						|
//		-> values
 | 
						|
//
 | 
						|
//
 | 
						|
// items format:
 | 
						|
// 	[
 | 
						|
// 		// explicit value...
 | 
						|
// 		value,
 | 
						|
//
 | 
						|
// 		// literal make call...
 | 
						|
// 		make(..),
 | 
						|
//
 | 
						|
// 		...
 | 
						|
// 	]
 | 
						|
//
 | 
						|
// NOTE: this will remove the made via make(..) items from .items thus the
 | 
						|
// 		caller is responsible for adding them back...
 | 
						|
// NOTE: this uses the make(..) return value to implicitly infer the items
 | 
						|
// 		to collect, thus the items must already be constructed and in 
 | 
						|
// 		the same order as they are present in .items
 | 
						|
// 		...also, considering that this implicitly identifies the items 
 | 
						|
// 		passing the make function without calling it can trick the system
 | 
						|
// 		and lead to unexpected results.
 | 
						|
//
 | 
						|
// XXX would be nice to have a better check/test...
 | 
						|
// 		...this could be done by chaining instances of make instead of 
 | 
						|
// 		returning an actual function, i.e. each make call would return 
 | 
						|
// 		a "new" function that would reference the actual item (.item())
 | 
						|
// 		and the previous item created (.prevItem()), ... etc.
 | 
						|
// 		...this would enable us to uniquely identify the actual items 
 | 
						|
// 		and prevent allot of specific errors...
 | 
						|
var collectItems = function(make, items){
 | 
						|
	var made = items
 | 
						|
		.filter(function(e){
 | 
						|
			return e === make })
 | 
						|
	// constructed item list...
 | 
						|
	// ...remove each instance from .items
 | 
						|
	made = make.items.splice(
 | 
						|
		make.items.length - made.length, 
 | 
						|
		made.length)
 | 
						|
	// get the actual item values...
 | 
						|
	return items
 | 
						|
		.map(function(e){
 | 
						|
			return e === make ?
 | 
						|
				made.shift()
 | 
						|
				// raw item -> make(..)
 | 
						|
				: (make(e) 
 | 
						|
					&& make.items.pop()) }) }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
//---------------------------------------------------------------------
 | 
						|
// XXX general design:
 | 
						|
// 		- each of these can take either a value or a function (constructor)
 | 
						|
//		- the function has access to Items.* and context
 | 
						|
//		- the constructor can be called from two contexts:
 | 
						|
//			- external
 | 
						|
//				called from the module or as a function...
 | 
						|
//				calls the passed constructor (passing context)
 | 
						|
//				builds the container
 | 
						|
//			- nested
 | 
						|
//				called from constructor function...
 | 
						|
//				calls constructor (if applicable)
 | 
						|
//				builds item(s)
 | 
						|
// XXX need a way to pass container constructors (a-la ui-widgets dialog containers)
 | 
						|
// 		- passing through the context (this) makes this more flexible...
 | 
						|
// 		- passing via args fixes the signature which is a good thing...
 | 
						|
//		
 | 
						|
//
 | 
						|
 | 
						|
// XXX
 | 
						|
// XXX can't use Object.assign(..) here as it will not copy props...
 | 
						|
var Items = module.items = function(){}
 | 
						|
 | 
						|
 | 
						|
// placeholders...
 | 
						|
Items.dialog = null
 | 
						|
Items.items = null
 | 
						|
 | 
						|
 | 
						|
// Last item created...
 | 
						|
// XXX not sure about this...
 | 
						|
// XXX should this be a prop???
 | 
						|
Items.last = function(){
 | 
						|
	return (this.items || [])[this.items.length - 1] }
 | 
						|
 | 
						|
 | 
						|
// Focus last created item...
 | 
						|
Items.focus = function(){
 | 
						|
	this.last().current = true }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
// Group a set of items...
 | 
						|
//
 | 
						|
//	.group(make(..), ..)
 | 
						|
//	.group([make(..), ..])
 | 
						|
//		-> make
 | 
						|
//
 | 
						|
//
 | 
						|
// Example:
 | 
						|
// 	make.group(
 | 
						|
// 		make('made item'),
 | 
						|
// 		'literal item',
 | 
						|
// 		...)
 | 
						|
//
 | 
						|
//
 | 
						|
// NOTE: see notes to collectItems(..) for more info...
 | 
						|
//
 | 
						|
// XXX do we need to pass options to groups???
 | 
						|
Items.group = function(...items){
 | 
						|
	var that = this
 | 
						|
	items = items.length == 1 && items[0] instanceof Array ?
 | 
						|
		items[0]
 | 
						|
		: items
 | 
						|
	// replace the items with the group...
 | 
						|
	this.items.splice(this.items.length, 0, ...collectItems(this, items))
 | 
						|
	return this
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
// Place list in a sub-list of item...
 | 
						|
//
 | 
						|
// XXX options???
 | 
						|
Items.nest = function(item, list, options){
 | 
						|
	options = options || {}
 | 
						|
	//options = Object.assign(Object.create(this.options || {}), options || {})
 | 
						|
	options.sublist = list instanceof Array ?
 | 
						|
		collectItems(this, list)
 | 
						|
		: list
 | 
						|
	return this(item, options)
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
//---------------------------------------------------------------------
 | 
						|
// wrappers...
 | 
						|
 | 
						|
Items.Item = function(value, options){}
 | 
						|
Items.Action = function(value, options){}
 | 
						|
Items.Heading = function(value, options){}
 | 
						|
Items.Empty = function(value){}
 | 
						|
Items.Separator = function(value){}
 | 
						|
Items.Spinner = function(value){}
 | 
						|
Items.Selected = function(value){}
 | 
						|
Items.Editable = function(value){}
 | 
						|
Items.ConfirmAction = function(value){}
 | 
						|
 | 
						|
// lists...
 | 
						|
Items.List = function(values){}
 | 
						|
Items.EditableList = function(values){}
 | 
						|
Items.EditablePinnedList = function(values){}
 | 
						|
 | 
						|
// Special list components...
 | 
						|
Items.ListPath = function(){}
 | 
						|
Items.ListTitle = function(){}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
//---------------------------------------------------------------------
 | 
						|
 | 
						|
// Generate an event method...
 | 
						|
//
 | 
						|
//	Trigger an event
 | 
						|
//	.event()
 | 
						|
//	.event(arg, ..)
 | 
						|
//		-> this
 | 
						|
//
 | 
						|
//	Bind an event handler...
 | 
						|
//	.event(func)
 | 
						|
//		-> this
 | 
						|
//
 | 
						|
//
 | 
						|
// XXX should this be simply a shorthand to .trigger(..) ???
 | 
						|
var makeEventMethod = function(event, handler){
 | 
						|
	return function(item){
 | 
						|
		// register handler...
 | 
						|
		if(item instanceof Function){
 | 
						|
			return this.on(event, item) 
 | 
						|
		}
 | 
						|
 | 
						|
		// XXX STUG: event object...
 | 
						|
		// XXX can we generate this in one spot???
 | 
						|
		// 		...currently it is generated here and in .trigger(..)
 | 
						|
		var evt = {
 | 
						|
		 	name: event,
 | 
						|
			// XXX
 | 
						|
			//stopPropagation: function(){
 | 
						|
			//},
 | 
						|
		}
 | 
						|
 | 
						|
		// XXX handle more of the API???
 | 
						|
		handler
 | 
						|
			&& handler.call(this, evt, ...arguments)
 | 
						|
 | 
						|
		// XXX we should get the actual item and pass it on...
 | 
						|
		this.trigger(evt, ...arguments)
 | 
						|
 | 
						|
		return this
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
var callItemEventHandlers = function(item, event, ...args){
 | 
						|
	;(item[event] ?
 | 
						|
			[item[event]]
 | 
						|
			: [])
 | 
						|
		.concat((item.events || {})[event] || [])
 | 
						|
		.forEach(function(handler){
 | 
						|
			// XXX revise call signature...
 | 
						|
			handler.call(item, evt, item, ...args) }) }
 | 
						|
 | 
						|
// XXX use .find(..) instead of .get(..) here....
 | 
						|
var makeItemEventMethod = function(event, handler){
 | 
						|
	return makeEventMethod(event, function(evt, item, ...args){
 | 
						|
		item = item ? 
 | 
						|
			// XXX
 | 
						|
			this.get(item) 
 | 
						|
			: []
 | 
						|
		item = item instanceof Array ? item : [item]
 | 
						|
 | 
						|
		handler
 | 
						|
			&& handler.call(this, evt, item, ...args)
 | 
						|
 | 
						|
		item.forEach(function(item){
 | 
						|
			callItemEventHandlers(item, event) })
 | 
						|
	}) }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
						|
 | 
						|
var BaseBrowserClassPrototype = {
 | 
						|
}
 | 
						|
 | 
						|
// XXX need a way to identify items...
 | 
						|
var BaseBrowserPrototype = {
 | 
						|
	// XXX should we mix item/list options or separate them into sub-objects???
 | 
						|
	options: {
 | 
						|
		noDuplicateValues: false,
 | 
						|
	},
 | 
						|
 | 
						|
	// parent widget object...
 | 
						|
	//
 | 
						|
	// NOTE: this may or may not be a Browser object.
 | 
						|
	parent: null,
 | 
						|
 | 
						|
	//
 | 
						|
	// Format:
 | 
						|
	// 	[
 | 
						|
	// 		<item> | <browser>,
 | 
						|
	// 		...
 | 
						|
	// 	]
 | 
						|
	//
 | 
						|
	// <item> format:
 | 
						|
	// 	{
 | 
						|
	// 		value: ...,
 | 
						|
	//
 | 
						|
	// 		...
 | 
						|
	// 	}
 | 
						|
	//
 | 
						|
	// NOTE: this can't be a map/dict as we need both order manipulation 
 | 
						|
	// 		and nested structures which would overcomplicate things, as 
 | 
						|
	// 		a compromise we use .item_key_index below for item identification.
 | 
						|
	__items: null,
 | 
						|
	get items(){
 | 
						|
		this.__items
 | 
						|
			|| this.make()
 | 
						|
		return this.__items },
 | 
						|
	set items(value){
 | 
						|
		this.__items = value },
 | 
						|
 | 
						|
	//
 | 
						|
	// Format:
 | 
						|
	// 	{
 | 
						|
	// 		<key>: <item>,
 | 
						|
	// 		...
 | 
						|
	// 	}
 | 
						|
	//
 | 
						|
	// NOTE: this will get overwritten each tume .make(..) is called.
 | 
						|
	//
 | 
						|
	// XXX need to maintain this over item add/remove/change...
 | 
						|
	// XXX Q: should we be able to add/remove/change items outside of .__list__(..)???
 | 
						|
	// 		...only some item updates (how .collapsed is handled) make 
 | 
						|
	// 		sense at this time -- need to think about this more 
 | 
						|
	// 		carefully + strictly document the result...
 | 
						|
	// XXX can we make the format here simpler with less level 
 | 
						|
	// 		of indirection??
 | 
						|
	// 		...currently to go down a path we need to:
 | 
						|
	//			this.item_key_index.A.sublist.item_key_index.B.sublist...
 | 
						|
	//		would be nice to be closer to:
 | 
						|
	//			this.A.B...
 | 
						|
	__item_index: null,
 | 
						|
	get item_key_index(){
 | 
						|
		this.__item_index
 | 
						|
			|| this.make()
 | 
						|
		return this.__item_index },
 | 
						|
	set item_key_index(value){
 | 
						|
		this.__item_index = value },
 | 
						|
 | 
						|
 | 
						|
	// XXX what should these return??? (item, id, ...)
 | 
						|
	__focused: undefined,
 | 
						|
	get focused(){
 | 
						|
		return this.__focused && this.__focused.focused ?
 | 
						|
			this.__focused
 | 
						|
			: (this.__focused = this
 | 
						|
				// XXX should we simple bailout when we find an item???
 | 
						|
				.filter(function(e){
 | 
						|
					return e.focused }).shift()) },
 | 
						|
	set focused(value){
 | 
						|
		// XXX
 | 
						|
	},
 | 
						|
 | 
						|
	__selected: null,
 | 
						|
	get selected(){
 | 
						|
		return this.__selected 
 | 
						|
			|| (this.__selected = this
 | 
						|
				.filter(function(e){
 | 
						|
					return e.selected })) },
 | 
						|
	set selected(value){
 | 
						|
		// XXX
 | 
						|
	},
 | 
						|
 | 
						|
 | 
						|
	// Length...
 | 
						|
	//
 | 
						|
	// visible only...
 | 
						|
	get length(){
 | 
						|
		return this.map({skipNested: true}).length
 | 
						|
			+ this.nested()
 | 
						|
				.reduce(function(res, e){ 
 | 
						|
					return e.collapsed ?
 | 
						|
						res + 1
 | 
						|
						: res + e.sublist.length }, 0) },
 | 
						|
	// tree -- ignores .collapsed...
 | 
						|
	get lengthTree(){
 | 
						|
		return this.map({skipNested: true}).length
 | 
						|
			+ this.nested()
 | 
						|
				.reduce(function(res, e){ 
 | 
						|
					return res + e.sublist.length }, 0) },
 | 
						|
	// full -- ignores .collapsed and .noniterable...
 | 
						|
	get lengthAll(){
 | 
						|
		return this.map({skipNested: true, iterateNonIterable: true}).length
 | 
						|
			+ this.nested()
 | 
						|
				.reduce(function(res, e){ 
 | 
						|
					return res + (e.sublist.lengthAll || e.sublist.length) }, 0) },
 | 
						|
 | 
						|
 | 
						|
	// Item list constructor...
 | 
						|
	//
 | 
						|
	// 	.__list__(make, options)
 | 
						|
	// 		-> undefined
 | 
						|
	// 		-> list
 | 
						|
	//
 | 
						|
	//
 | 
						|
	// 	Item constructor:
 | 
						|
	// 		make(value)
 | 
						|
	// 		make(value, options)
 | 
						|
	// 			-> make
 | 
						|
	//
 | 
						|
	//
 | 
						|
	// There are two modes of operation:
 | 
						|
	// 	1) call make(..) to create items
 | 
						|
	// 	2) return a list of items
 | 
						|
	//
 | 
						|
	//
 | 
						|
	// The if make(..) is called at least once the return value is 
 | 
						|
	// ignored (mode #1), otherwise, the returned list is used as the 
 | 
						|
	// .items structure.
 | 
						|
	//
 | 
						|
	//
 | 
						|
	// When calling make(..) (mode #1) the item is built by combining 
 | 
						|
	// the following in order:
 | 
						|
	// 	- original item (.items[key]) if present,
 | 
						|
	// 	- options passed to .make(<options>) method calling .__list__(..),
 | 
						|
	// 	- options passed to make(.., <options>) constructing the item,
 | 
						|
	// 	- {value: <value>} where <value> passed to make(<value>, ..)
 | 
						|
	//
 | 
						|
	// Each of the above will override values of the previous sections.
 | 
						|
	//
 | 
						|
	// The resulting item is stored in:
 | 
						|
	// 	.items
 | 
						|
	// 	.item_key_index (keyed via .id or JSONified .value)
 | 
						|
	//
 | 
						|
	// Each of the above structures is reset on each call to .make(..)
 | 
						|
	//
 | 
						|
	// options format:
 | 
						|
	// 	{
 | 
						|
	// 		id: <string>,
 | 
						|
	// 		value: <string> | <array>,
 | 
						|
	//
 | 
						|
	// 		sublist: <browser> | <array>,
 | 
						|
	//
 | 
						|
	// 		focused: <bool>,
 | 
						|
	// 		selected: <bool>,
 | 
						|
	// 		disabled: <bool>,
 | 
						|
	// 		noniterable: <bool>,
 | 
						|
	//
 | 
						|
	// 		// Set automatically...
 | 
						|
	// 		parent: <browser>,
 | 
						|
	// 		// XXX move this to the appropriate object...
 | 
						|
	// 		dom: <dom>,
 | 
						|
	// 	}
 | 
						|
	//
 | 
						|
	//
 | 
						|
	// Example:
 | 
						|
	// 	XXX
 | 
						|
	//
 | 
						|
	//
 | 
						|
	// In mode #2 XXX
 | 
						|
	//
 | 
						|
	//
 | 
						|
	// NOTE: this is not designed to be called directly...
 | 
						|
	__list__: function(make, options){
 | 
						|
		throw new Error('.__list__(..): Not implemented.') },
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	// XXX need a better key/path API...
 | 
						|
	//
 | 
						|
	__value2key__: function(key){
 | 
						|
		//return JSON.stringify(key)
 | 
						|
		return key instanceof Array ?
 | 
						|
			key.join(' ')
 | 
						|
			: key },
 | 
						|
 | 
						|
	// Key getter/generator...
 | 
						|
	//
 | 
						|
	// XXX should these include the path???
 | 
						|
	// XXX is JSON the best key format???
 | 
						|
	__key__: function(item){
 | 
						|
		return item.id 
 | 
						|
			// value is a browser -> generate an unique id...
 | 
						|
			// XXX identify via structure...
 | 
						|
			|| (item.value instanceof Browser 
 | 
						|
				&& this.__id__())
 | 
						|
			|| this.__value2key__(item.value) },
 | 
						|
 | 
						|
	// ID generator...
 | 
						|
	//
 | 
						|
	// Format:
 | 
						|
	// 	"<date>"
 | 
						|
	// 	"<prefix> <date>"
 | 
						|
	//
 | 
						|
	// XXX do a better id...
 | 
						|
	// 		Ex:
 | 
						|
	//			"abc"
 | 
						|
	// 			"abc (1)"
 | 
						|
	// 			"abc (2)"
 | 
						|
	// XXX not sure about the logic of this, should this take an item as 
 | 
						|
	// 		input and return an id???
 | 
						|
	// 		...should this check for uniqueness???
 | 
						|
	// 		think merging this with any of the actual ID generators would be best...
 | 
						|
	__id__: function(prefix){
 | 
						|
		// id prefix...
 | 
						|
		return (prefix || '') 
 | 
						|
			// separator...
 | 
						|
			+ (prefix ? ' ' : '') 
 | 
						|
			// date...
 | 
						|
			+ Date.now() },
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	// Make .items and .item_key_index...
 | 
						|
	//
 | 
						|
	// 	.make()
 | 
						|
	// 	.make(options)
 | 
						|
	// 		-> this
 | 
						|
	//
 | 
						|
	// The items are constructed by passing a make function to .__list__(..)
 | 
						|
	// which in turn will call this make(..) per item created.
 | 
						|
	//
 | 
						|
	// For more doc on item construction see: .__init__(..)
 | 
						|
	//
 | 
						|
	//
 | 
						|
	// NOTE: each call to this will reset both .items and .item_key_index
 | 
						|
	// NOTE: for items with repeating values there is no way to correctly 
 | 
						|
	// 		identify an item thus no state is maintained between .make(..)
 | 
						|
	// 		calls for such items...
 | 
						|
	//
 | 
						|
	// XXX revise options handling for .__list__(..)
 | 
						|
	make: function(options){
 | 
						|
		options = Object.assign(Object.create(this.options || {}), options || {})
 | 
						|
 | 
						|
		var items = this.items = []
 | 
						|
		var old_index = this.__item_index || {}
 | 
						|
		var new_index = this.__item_index = {} 
 | 
						|
 | 
						|
		// item constructor...
 | 
						|
		//
 | 
						|
		// 	make(value[, options])
 | 
						|
		// 	make(value, func[, options])
 | 
						|
		// 		-> make
 | 
						|
		//
 | 
						|
		var make_called = false
 | 
						|
		var make = function(value, opts){
 | 
						|
			make_called = true
 | 
						|
			var args = [...arguments]
 | 
						|
 | 
						|
			opts = opts || {}
 | 
						|
			// handle: make(.., func, ..)
 | 
						|
			opts = opts instanceof Function ?
 | 
						|
				{open: opts}
 | 
						|
				: opts
 | 
						|
			// handle trailing options...
 | 
						|
			opts = args.length > 2 ?
 | 
						|
				Object.assign({},
 | 
						|
					args.pop(),
 | 
						|
					opts)
 | 
						|
				: opts
 | 
						|
			opts = Object.assign(
 | 
						|
				{},
 | 
						|
				opts, 
 | 
						|
				{value: value})
 | 
						|
 | 
						|
			// item id...
 | 
						|
			var key = this.__key__(opts)
 | 
						|
			var id_changed = (old_index[key] || {}).id_changed
 | 
						|
 | 
						|
			// handle duplicate ids -> err if found...
 | 
						|
			if(opts.id && opts.id in new_index){
 | 
						|
				throw new Error(`make(..): duplicate id "${key}": `
 | 
						|
					+`can't create multiple items with the same key.`) }
 | 
						|
			// handle duplicate keys...
 | 
						|
			// NOTE: we can't reuse an old copy when re-making the list
 | 
						|
			// 		because there is now way to correctly identify an 
 | 
						|
			// 		object when it's id is tweaked (and we can not rely
 | 
						|
			// 		on item order)...
 | 
						|
			// 		...for this reason all "persistent" state for such 
 | 
						|
			// 		an element will be lost when calling .make(..) again
 | 
						|
			// 		and re-making the list...
 | 
						|
			// 		a solution to this would be to manually assign an .id 
 | 
						|
			// 		to such elements in .__list__(..)...
 | 
						|
			// 		XXX can we go around this without requiring the user 
 | 
						|
			// 			to manage ids???
 | 
						|
			var k = key
 | 
						|
			while(k in new_index){
 | 
						|
				// duplicate keys disabled...
 | 
						|
				if(options.noDuplicateValues){
 | 
						|
					throw new Error(`make(..): duplicate key "${key}": `
 | 
						|
						+`can't create multiple items with the same key.`) }
 | 
						|
 | 
						|
				// mark both the current and the first items as id-mutated...
 | 
						|
				opts.id_changed = true
 | 
						|
				new_index[key].id_changed = true
 | 
						|
 | 
						|
				// create a new key...
 | 
						|
				k = this.__id__(key)
 | 
						|
			}
 | 
						|
			key = opts.id = k
 | 
						|
 | 
						|
			// build the item...
 | 
						|
			var item = Object.assign(
 | 
						|
				Object.create(options || {}), 
 | 
						|
				// get the old item values (only for non duplicate items)...
 | 
						|
				id_changed ?
 | 
						|
					{}
 | 
						|
					: old_index[key] || {},
 | 
						|
				// XXX inherit from this...
 | 
						|
				opts,
 | 
						|
				{
 | 
						|
					parent: this,
 | 
						|
				})
 | 
						|
 | 
						|
			// XXX do we need both this and the above ref???
 | 
						|
			item.sublist instanceof Browser
 | 
						|
				&& (item.sublist.parent = this)
 | 
						|
 | 
						|
			// store the item...
 | 
						|
			items.push(item)
 | 
						|
			new_index[key] = item
 | 
						|
 | 
						|
			return make
 | 
						|
		}.bind(this)
 | 
						|
		make.__proto__ = Items
 | 
						|
		make.dialog = this
 | 
						|
		make.items = items
 | 
						|
 | 
						|
		//var res = this.__list__(make)
 | 
						|
		// XXX not sure about this -- options handling...
 | 
						|
		var res = this.__list__(make, 
 | 
						|
			options ? 
 | 
						|
				Object.assign(
 | 
						|
					Object.create(this.options || {}), 
 | 
						|
					options || {}) 
 | 
						|
				: null)
 | 
						|
 | 
						|
		// if make was not called use the .__list__(..) return value...
 | 
						|
		this.items = make_called ? 
 | 
						|
			this.items 
 | 
						|
			: res
 | 
						|
 | 
						|
		return this
 | 
						|
	},
 | 
						|
 | 
						|
 | 
						|
	// Renderers...
 | 
						|
	//
 | 
						|
	// 	.renderList(items, context)
 | 
						|
	// 	.renderNested(header, sublist, item, context)
 | 
						|
	// 	.renderNestedHeader(item, i, context)
 | 
						|
	// 	.renderItem(item, i, context)
 | 
						|
	// 	.renderGroup(items, context)
 | 
						|
	//
 | 
						|
	//
 | 
						|
	renderList: function(items, context){
 | 
						|
		return items },
 | 
						|
	// NOTE: to skip rendering an item/list return null...
 | 
						|
	// XXX should this take an empty sublist???
 | 
						|
	// 		...this would make it simpler to expand/collapse without 
 | 
						|
	// 		re-rendering the whole list...
 | 
						|
	renderNested: function(header, sublist, item, context){
 | 
						|
		return header ? 
 | 
						|
			this.renderGroup([
 | 
						|
				header, 
 | 
						|
				sublist,
 | 
						|
			])
 | 
						|
   			: sublist },
 | 
						|
	renderNestedHeader: function(item, i, context){
 | 
						|
		return this.renderItem(item, i, context) },
 | 
						|
	// NOTE: to skip rendering an item/list return null...
 | 
						|
	renderItem: function(item, i, context){
 | 
						|
		return item },
 | 
						|
	renderGroup: function(items, context){
 | 
						|
		return items },
 | 
						|
 | 
						|
	// Render state...
 | 
						|
	//
 | 
						|
	//	.render()
 | 
						|
	//	.render(options)
 | 
						|
	//	.render(context)
 | 
						|
	//		-> state
 | 
						|
	//
 | 
						|
	//
 | 
						|
	// context format:
 | 
						|
	// 	{
 | 
						|
	// 		root: <root-browser>,
 | 
						|
	// 		options: <options>,
 | 
						|
	// 	}
 | 
						|
	//
 | 
						|
	//
 | 
						|
	// NOTE: currently options and context are distinguished only via 
 | 
						|
	// 		the .options attribute...
 | 
						|
	//
 | 
						|
	// XXX should this use .map(..) internally???
 | 
						|
	// 		...there are two ways to go:
 | 
						|
	// 			- local recursion (as-is now or via .map(.., { .. }))
 | 
						|
	// 			- outer recursion (.map(..))
 | 
						|
	render: function(options){
 | 
						|
		var that = this
 | 
						|
		// XXX Q: should options and context be distinguished only via 
 | 
						|
		// 		the .options attr as is the case now???
 | 
						|
		var context = (options == null || options.options == null) ?
 | 
						|
				{
 | 
						|
					root: this,
 | 
						|
					// NOTE: we are not combining this with .options as nested 
 | 
						|
					// 		lists can have their own unique sets of options 
 | 
						|
					// 		independently of the root list...
 | 
						|
					//options: options || this.options || {},
 | 
						|
					options: Object.assign(Object.create(this.options || {}), options || {}),
 | 
						|
				}
 | 
						|
			: options
 | 
						|
		options = context.options
 | 
						|
 | 
						|
		// render the items...
 | 
						|
		var _render
 | 
						|
		// XXX should we control render parameters (range, start, end, ...)
 | 
						|
		// 		from outside render and pass this info down to nested lists???
 | 
						|
		// 		...if yes how??
 | 
						|
		// 			- options
 | 
						|
		// 			- arg threading
 | 
						|
		var items = this.items
 | 
						|
			.map(_render = function(item, i){
 | 
						|
				return (
 | 
						|
					// group...
 | 
						|
					item instanceof Array ?
 | 
						|
						that.renderGroup(
 | 
						|
							item.map(_render), context)
 | 
						|
					// renderable item...
 | 
						|
					: item.render instanceof Function ?
 | 
						|
						item.render(context) 
 | 
						|
					// renderable value -- embedded list...
 | 
						|
					: (item.value || {}).render instanceof Function ?
 | 
						|
						item.value.render(context) 
 | 
						|
					// .sublist -- nested list...
 | 
						|
					: item.sublist ?
 | 
						|
						that.renderNested(
 | 
						|
							that.renderNestedHeader(item, i, context),
 | 
						|
							// collapsed...
 | 
						|
							(item.collapsed ?
 | 
						|
									null
 | 
						|
							// renderable...
 | 
						|
							:item.sublist.render instanceof Function ?
 | 
						|
								item.sublist.render(context)
 | 
						|
							// list of items...
 | 
						|
							: item.sublist.map(_render)),
 | 
						|
							item, 
 | 
						|
							context)
 | 
						|
					// basic item...
 | 
						|
					: that.renderItem(item, i, context)) }) 
 | 
						|
			.filter(function(e){
 | 
						|
				return e != null })
 | 
						|
 | 
						|
		// determine the render mode...
 | 
						|
		return context.root === this ?
 | 
						|
			// root context -> render list and return this...
 | 
						|
			this.renderList(items, context)
 | 
						|
			// non-root context -> return items as-is...
 | 
						|
			// XXX should this be a list of the return value of a 
 | 
						|
			// 		renderer like .renderNested(..) ???
 | 
						|
			: items
 | 
						|
	},
 | 
						|
 | 
						|
	// Update state (make then render)...
 | 
						|
	//
 | 
						|
	// 	.update()
 | 
						|
	// 		-> state
 | 
						|
	//
 | 
						|
	//
 | 
						|
	// XXX options here are a relatively blunt means of overriding options
 | 
						|
	// 		in the tree...
 | 
						|
	// 		...do we need this???
 | 
						|
	update: function(options){
 | 
						|
		return this
 | 
						|
			.make(options)
 | 
						|
			.render(options) },
 | 
						|
 | 
						|
 | 
						|
	// XXX item API...
 | 
						|
	//
 | 
						|
	/* XXX this is a more complicated version of .get(..) that should be
 | 
						|
	// 		a bit faster on very large lists -- o(1) on direct non-nested
 | 
						|
	// 		indexing vs o(n) for the same case in the new .get(..) 
 | 
						|
	// 		implementation...
 | 
						|
	// XXX add path support...
 | 
						|
	// XXX add literal item support (???)
 | 
						|
	// XXX do not get .subtree elements of a .collapsed item...
 | 
						|
	// XXX skip .noniterable items...
 | 
						|
	get: function(key, options){
 | 
						|
		key = key == null ? 0 : key
 | 
						|
		options = Object.assign(Object.create(this.options || {}), options || {})
 | 
						|
 | 
						|
		// index...
 | 
						|
		if(typeof(key) == typeof(123)){
 | 
						|
 | 
						|
			var items = this.items
 | 
						|
			// XXX cache this (prop?)...
 | 
						|
			var sublists = this.sublists()
 | 
						|
				.map(function(e, i){ 
 | 
						|
					return [e, i] })
 | 
						|
				.compact()
 | 
						|
 | 
						|
			var i = 0
 | 
						|
			var nested = 0
 | 
						|
			var offset = 0
 | 
						|
 | 
						|
			do {
 | 
						|
				var x = key - offset + nested
 | 
						|
				// direct match...
 | 
						|
				// XXX skip .noniterable...
 | 
						|
				if(sublists.length == 0 || x < sublists[0][1]){
 | 
						|
					return items[x]
 | 
						|
				}
 | 
						|
 | 
						|
				// query the sublist...
 | 
						|
				var [sublist, i] = sublists.shift()
 | 
						|
				nested += 1
 | 
						|
 | 
						|
				// inlined...
 | 
						|
				if(sublist.value instanceof Browser){
 | 
						|
					var res = sublist.value.get(x - i, options)
 | 
						|
 | 
						|
				// nested...
 | 
						|
				// XXX support .collapsed...
 | 
						|
				} else { 
 | 
						|
					var res = x - i == 0 ?
 | 
						|
							sublist
 | 
						|
						: sublist.sublist instanceof Browser ?
 | 
						|
							// NOTE: we are decrementing here to compensate 
 | 
						|
							// 		for the header...
 | 
						|
							sublist.sublist.get(x - i - 1, options) 
 | 
						|
						: sublist.sublist[x - i - 1]
 | 
						|
					// account for the header...
 | 
						|
					offset += 1
 | 
						|
				}
 | 
						|
 | 
						|
				if(res !== undefined){
 | 
						|
					return res
 | 
						|
				}
 | 
						|
 | 
						|
				// NOTE: we need to get the full length here rather than
 | 
						|
				// 		the number of iterable elements -> prefer .lengthAll...
 | 
						|
				offset += (sublist.sublist || sublist.value).lengthAll
 | 
						|
					|| (sublist.sublist || sublist.value).length
 | 
						|
 | 
						|
			// NOTE: we do not need an explicit exit here as the first 
 | 
						|
			// 		test will bail us out as soon as sublists are 
 | 
						|
			// 		depleted...
 | 
						|
			} while(true)
 | 
						|
 | 
						|
 | 
						|
		// key...
 | 
						|
		// XXX account for paths...
 | 
						|
		} else {
 | 
						|
			// XXX
 | 
						|
			var k = this.__value2key__(key)
 | 
						|
 | 
						|
			// direct match...
 | 
						|
			if(k in this.item_key_index){
 | 
						|
				return this.item_key_index[k]
 | 
						|
			}
 | 
						|
 | 
						|
			// query nested...
 | 
						|
			var nested = Object.values(this.item_key_index)
 | 
						|
				.filter(function(e){ 
 | 
						|
					return e.sublist instanceof Browser })
 | 
						|
			while(nested.length > 0){
 | 
						|
				var n = nested.shift().sublist
 | 
						|
				var res = n.get(key)
 | 
						|
				if(res !== undefined){
 | 
						|
					// return first match...
 | 
						|
					return res
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return undefined
 | 
						|
	},
 | 
						|
	//*/
 | 
						|
 | 
						|
	// Get item...
 | 
						|
	//
 | 
						|
	// 	.get()
 | 
						|
	// 	.get(id)
 | 
						|
	// 	.get(index)
 | 
						|
	// 	.get(path)
 | 
						|
	// 		-> item
 | 
						|
	// 		-> undefined
 | 
						|
	//
 | 
						|
	//
 | 
						|
	// options format:
 | 
						|
	// 	{
 | 
						|
	// 		ignoreKeywords: <bool>,
 | 
						|
	//
 | 
						|
	// 		// rest of the options are the same as for .map(..)
 | 
						|
	// 		...
 | 
						|
	// 	}
 | 
						|
	//
 | 
						|
	//
 | 
						|
	// XXX this is not too fast for indexing very long lists...
 | 
						|
	// XXX use cache for these -- currently these use .map(..)...
 | 
						|
	// XXX do we need to support negative indexes???
 | 
						|
	get: function(key, options){
 | 
						|
		key = key == null ? 0 : key
 | 
						|
		key = typeof(key) == typeof('str') ?
 | 
						|
			key.split(/[\\\/]/g)
 | 
						|
				.filter(function(e){ return e.length > 0 })
 | 
						|
			: key
 | 
						|
		key = typeof(key) == typeof('str') ?
 | 
						|
			[key]
 | 
						|
			: key
 | 
						|
 | 
						|
		options = Object.assign(Object.create(this.options || {}), options || {})
 | 
						|
		var iterateCollapsed = options.iterateAll || options.iterateCollapsed
 | 
						|
		var ignoreKeywords = options.ignoreKeywords
 | 
						|
 | 
						|
		// keywords...
 | 
						|
		if(!ignoreKeywords){
 | 
						|
			// XXX don't like how this feels...
 | 
						|
			if(key == 'next' || key == 'prev'){
 | 
						|
				var reference = this.focused
 | 
						|
				key = key == 'next' ?
 | 
						|
					(reference ? 
 | 
						|
						this.indexOf(reference) + 1 
 | 
						|
						: 0)
 | 
						|
					: (reference ? 
 | 
						|
						this.indexOf(reference) - 1 
 | 
						|
						: -1)
 | 
						|
			}
 | 
						|
 | 
						|
			if(key == 'first'){
 | 
						|
				var res = this.items[0]
 | 
						|
				return res.value instanceof Browser ?
 | 
						|
					res.value.get(key, options)
 | 
						|
					: res
 | 
						|
 | 
						|
			} else if(key == 'last'){
 | 
						|
				var res = this.items[this.items.length - 1]
 | 
						|
				return res.value instanceof Browser ?
 | 
						|
						res.value.get(key, options)
 | 
						|
					: res.sublist && (!this.collapsed || iterateCollapsed) ?
 | 
						|
						(res.sublist instanceof Browser ?
 | 
						|
							res.sublist.get(key, options)
 | 
						|
							: res.sublist[res.sublist.length - 1])
 | 
						|
					: res
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// get path...
 | 
						|
		if(key instanceof Array){
 | 
						|
			var res = this.item_key_index[key.shift()]
 | 
						|
			return key.length == 0 ?
 | 
						|
				res
 | 
						|
				// nested...
 | 
						|
				: iterateCollapsed || !res.collapsed ?
 | 
						|
					res.sublist.get(key, options) 
 | 
						|
				: undefined }
 | 
						|
 | 
						|
		// get index...
 | 
						|
		// XXX getting an element by index is o(n) and not o(1)...
 | 
						|
		// 		...unless we cache .sublists() not sure if this can be 
 | 
						|
		// 		made better in the general case...
 | 
						|
		// XXX do we need to support negative indexes???
 | 
						|
		var Stop = new Error('.get(..): Result found exception.')
 | 
						|
		var i = 0
 | 
						|
		var res
 | 
						|
		try {
 | 
						|
			this.map(function(e){
 | 
						|
				res = key == i ?
 | 
						|
					e
 | 
						|
					: res
 | 
						|
				if(res){
 | 
						|
					throw Stop }
 | 
						|
				i++
 | 
						|
			}, options)
 | 
						|
		} catch(e){
 | 
						|
			if(e === Stop){
 | 
						|
				return res
 | 
						|
			}
 | 
						|
			throw e
 | 
						|
		}
 | 
						|
 | 
						|
		return res
 | 
						|
	},
 | 
						|
	// XXX move these to a more logical spot...
 | 
						|
	// XXX these are almost identical -- reuse???
 | 
						|
	indexOf: function(item, options){
 | 
						|
		item = typeof(item) == typeof('str') ?
 | 
						|
			item.split(/[\\\/]/g)
 | 
						|
			: item
 | 
						|
 | 
						|
		var Stop = new Error('.indexOf(..): Result found exception.')
 | 
						|
 | 
						|
		var i = 0
 | 
						|
		try{
 | 
						|
			this.map(function(e, p){
 | 
						|
				if(item instanceof Array ? item.cmp(p) : (item === e)){
 | 
						|
					throw Stop }
 | 
						|
				i++
 | 
						|
			}, options)
 | 
						|
 | 
						|
		} catch(e){
 | 
						|
			if(e === Stop){
 | 
						|
				return i
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return -1
 | 
						|
	},
 | 
						|
	pathOf: function(item, options){
 | 
						|
		var Stop = new Error('.pathOf(..): Result found exception.')
 | 
						|
 | 
						|
		var path
 | 
						|
		var i = 0
 | 
						|
		try{
 | 
						|
			this.map(function(e, p){
 | 
						|
				path = p
 | 
						|
				if(typeof(item) == typeof(123) ? item == i : (item === e)){
 | 
						|
					throw Stop }
 | 
						|
				i++
 | 
						|
			}, options)
 | 
						|
 | 
						|
		} catch(e){
 | 
						|
			if(e === Stop){
 | 
						|
				return path
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return undefined
 | 
						|
	},
 | 
						|
 | 
						|
	// Like .get(.., {iterateCollapsed: true}) but will expand all the 
 | 
						|
	// path items to reveal the target...
 | 
						|
	// XXX should this return the item or this???
 | 
						|
	reveal: function(key, options){
 | 
						|
		// get the item...
 | 
						|
		var res = this.get(key, Object.assign({iterateCollapsed: true}, options))
 | 
						|
 | 
						|
		// expand the path up...
 | 
						|
		var cur = res.parent
 | 
						|
		while(cur && cur.parent instanceof Browser){
 | 
						|
			delete (cur.parent.item_key_index[cur.id]
 | 
						|
				|| cur.parent.items
 | 
						|
					.filter(function(e){ 
 | 
						|
						return e.sublist === cur })
 | 
						|
				.shift()).collapsed
 | 
						|
			cur = cur.parent }
 | 
						|
 | 
						|
		// re-render...
 | 
						|
		this.render()
 | 
						|
 | 
						|
		return res
 | 
						|
	},
 | 
						|
 | 
						|
 | 
						|
	//
 | 
						|
	//	.find(id[, options])
 | 
						|
	//	.find(index[, options])
 | 
						|
	//	.find(path[, options])
 | 
						|
	//	.find(func[, options])
 | 
						|
	//		-> list
 | 
						|
	//
 | 
						|
	// XXX add '**' patterns...
 | 
						|
	// XXX should this return item paths???
 | 
						|
	// 		...one way to do this is to return an object instead of a list...
 | 
						|
	find: function(query, options){
 | 
						|
		query = typeof(query) == typeof('str') ?
 | 
						|
			query.split(/[\\\/]/g)
 | 
						|
				.filter(function(e){ return e.length > 0 })
 | 
						|
			: query
 | 
						|
		query = typeof(query) == typeof('str') ?
 | 
						|
			[query]
 | 
						|
			: query
 | 
						|
		query = query instanceof Array ?
 | 
						|
			query
 | 
						|
				.map(function(d){
 | 
						|
					return d == '*' ?
 | 
						|
							d
 | 
						|
						: d.indexOf('*') >= 0 ?
 | 
						|
							new RegExp(d
 | 
						|
								.replace(/\*/g, '.*'))
 | 
						|
						: d})
 | 
						|
			: query
 | 
						|
 | 
						|
		var i = -1
 | 
						|
		return this
 | 
						|
			.filter(function(e, p){
 | 
						|
				i++
 | 
						|
				return (query === e
 | 
						|
					|| (
 | 
						|
						// index...
 | 
						|
						typeof(query) == typeof(123) ?
 | 
						|
							query == i
 | 
						|
						// predicate...
 | 
						|
						: query instanceof Function ?
 | 
						|
							// XXX revise signature...
 | 
						|
							query.call(this, e, p, i, this)
 | 
						|
						// regular expression...
 | 
						|
						: query instanceof RegExp ?
 | 
						|
							query.test(p.join('/'))
 | 
						|
						// direct path comparison...
 | 
						|
						: query instanceof Array ?
 | 
						|
							query.cmp(p)
 | 
						|
							|| (query.length == p.length
 | 
						|
								&& query
 | 
						|
									.filter(function(q, i){
 | 
						|
										return q == '*' 
 | 
						|
											|| (q instanceof RegExp 
 | 
						|
												&& q.test(p[i]))
 | 
						|
											|| q == p[i] })
 | 
						|
									.length == p.length)
 | 
						|
						: false)) }, options) },
 | 
						|
 | 
						|
	// XXX support: up/down/left/right/first/last/next/prev
 | 
						|
	// XXX extend support for screen oriented nav in a subclass...
 | 
						|
	navigate: function(direction){
 | 
						|
		// XXX get then return element...
 | 
						|
	},
 | 
						|
 | 
						|
 | 
						|
	// Extended map...
 | 
						|
	//
 | 
						|
	//	Generic map...
 | 
						|
	//	.map([options])
 | 
						|
	//	.map(func[, options])
 | 
						|
	//		-> items
 | 
						|
	//
 | 
						|
	// options format:
 | 
						|
	// 	{
 | 
						|
	// 		// Iterate ALL items...
 | 
						|
	// 		//
 | 
						|
	// 		// NOTE: this if true overrides all other iteration coverage 
 | 
						|
	// 		//		options... 
 | 
						|
	// 		iterateAll: <bool>,
 | 
						|
	//
 | 
						|
	// 		// If true do not skip items with .noniterable set to true...
 | 
						|
	// 		iterateNonIterable: <bool>,
 | 
						|
	// 		// If true do not skip item.sublist of items with .collapsed 
 | 
						|
	// 		// set to true...
 | 
						|
	// 		iterateCollapsed: <bool>,
 | 
						|
	// 		// If true skip iterating nested items...
 | 
						|
	// 		skipNested: <bool>,
 | 
						|
	//
 | 
						|
	// 		// If true include inlined parent id in path...
 | 
						|
	// 		inlinedPaths: <bool>,
 | 
						|
	// 	}
 | 
						|
	//
 | 
						|
	//
 | 
						|
	// By default this will not iterate items that are:
 | 
						|
	// 	- non-iterable (item.noniterable is true)
 | 
						|
	// 	- collapsed sub-items (item.collapsed is true)
 | 
						|
	// 	
 | 
						|
	//
 | 
						|
	// This extends the Array .map(..) by adding:
 | 
						|
	// 	- ability to run without arguments
 | 
						|
	// 	- support for options
 | 
						|
	//
 | 
						|
	//
 | 
						|
	//
 | 
						|
	// NOTE: a semi-documented signature is also used internally to 
 | 
						|
	// 		generate paths:
 | 
						|
	//			.map(func, path, options)
 | 
						|
	//
 | 
						|
	// XXX this essentially repeats what .render(..) does but in a more 
 | 
						|
	// 		complex way -- can we reuse one or the other and simplify things???
 | 
						|
	// XXX make item access by index lazy... 
 | 
						|
	// 		- index nested stuff and lengths... (.sublist_length)
 | 
						|
	// 		- stop when target reached... (control callback???)
 | 
						|
	// XXX Q: should we have an option to treat groups as elements???
 | 
						|
	map: function(func, options){
 | 
						|
		var that = this
 | 
						|
 | 
						|
		// parse args...
 | 
						|
		var args = [...arguments]
 | 
						|
		func = args[0] instanceof Function ? 
 | 
						|
			args.shift() 
 | 
						|
			: undefined
 | 
						|
		var path = (args[0] instanceof Array 
 | 
						|
				|| typeof(args[0]) == typeof('str')) ?
 | 
						|
			args.shift()
 | 
						|
			: []
 | 
						|
		path = path instanceof Array ? path : [path]
 | 
						|
		var options = args.pop() || {}
 | 
						|
 | 
						|
		// options...
 | 
						|
		options = Object.assign(Object.create(this.options || {}), options || {})
 | 
						|
		var iterateNonIterable = options.iterateAll || options.iterateNonIterable
 | 
						|
		var iterateCollapsed = options.iterateAll || options.iterateCollapsed
 | 
						|
		var skipNested = !options.iterateAll && options.skipNested
 | 
						|
		var reverse = !!options.reverseIteration
 | 
						|
		
 | 
						|
		// NOTE: func is in closure as it will not change within one run 
 | 
						|
		// 		on any level of nesting...
 | 
						|
		var doElem = function(elem, path){
 | 
						|
			return [func ? 
 | 
						|
				func.call(that, elem, path.concat(elem.id), that) 
 | 
						|
				: elem] }
 | 
						|
		var doLevel = function(elem, nested, path){
 | 
						|
			return [ 
 | 
						|
				doElem(elem, path),
 | 
						|
				(!iterateCollapsed && elem.collapsed) ?
 | 
						|
					[]
 | 
						|
					: nested ]
 | 
						|
				// reverse level order...
 | 
						|
				.run(function(){
 | 
						|
					reverse 
 | 
						|
						&& this.reverse() })
 | 
						|
				.flat() }
 | 
						|
		// NOTE: we need to reverse two things:
 | 
						|
		// 		- level order (done here)
 | 
						|
		// 		- linearization order (done below)
 | 
						|
		// XXX can we make this even simpler???
 | 
						|
		var walk = function(path, list){
 | 
						|
			return list 
 | 
						|
				// reverse the items...
 | 
						|
				.run(function(){
 | 
						|
					return reverse ?
 | 
						|
						// NOTE: we .slice() as we do not want to affect 
 | 
						|
						// 		the actual list...
 | 
						|
						this.slice().reverse() 
 | 
						|
						: this })
 | 
						|
				.map(function(elem){
 | 
						|
					return (
 | 
						|
						// item not iterable -> skip...
 | 
						|
						(!iterateNonIterable && elem.noniterable) ?
 | 
						|
							[]
 | 
						|
						// value is Browser (inline)...
 | 
						|
						: elem.value instanceof Browser ?
 | 
						|
							elem.value.map(func, 
 | 
						|
								options.inlinedPaths ?
 | 
						|
									path.concat(elem.id)
 | 
						|
									: path.slice(), 
 | 
						|
								options)	
 | 
						|
						// .sublist is Browser (nested)...
 | 
						|
						: (!skipNested 
 | 
						|
								&& elem.sublist instanceof Browser) ?
 | 
						|
							doLevel(
 | 
						|
								elem,
 | 
						|
								elem.sublist
 | 
						|
									.map(func, path.concat(elem.id), options), 
 | 
						|
								path)
 | 
						|
						// .sublist is Array (nested)...
 | 
						|
						: (!skipNested 
 | 
						|
								&& elem.sublist instanceof Array) ?
 | 
						|
							doLevel(
 | 
						|
								elem,
 | 
						|
								walk(path.concat(elem.id), elem.sublist),
 | 
						|
								path)
 | 
						|
						// normal item...
 | 
						|
						: doElem(elem, path) ) })
 | 
						|
				.flat() }
 | 
						|
 | 
						|
		return walk(path, this.items) 
 | 
						|
	},
 | 
						|
 | 
						|
	// Sublist map functions...
 | 
						|
	// NOTE: there are different from .map(..) in that instead of paths 
 | 
						|
	// 		func(..) will get indexes in the current browser...
 | 
						|
	// NOTE: these will return a sparse array...
 | 
						|
	sublists: function(func, options){
 | 
						|
		var that = this
 | 
						|
		//options = options || {}
 | 
						|
		options = Object.assign(Object.create(this.options || {}), options || {})
 | 
						|
		var skipNested = options.skipNested
 | 
						|
		var skipInlined = options.skipInlined
 | 
						|
 | 
						|
		var res = []
 | 
						|
		this.items
 | 
						|
			.forEach(function(elem, i){
 | 
						|
				if((!skipInlined && elem.value instanceof Browser)
 | 
						|
							|| (!skipNested && elem.sublist)){
 | 
						|
					res[i] = func ?
 | 
						|
						func.call(that, elem, i, that)
 | 
						|
						: elem 
 | 
						|
				} })
 | 
						|
		return res 
 | 
						|
	},
 | 
						|
	nested: function(func){
 | 
						|
		return this.sublists(func, {skipInlined: true}) },
 | 
						|
	inlined: function(func){
 | 
						|
		return this.sublists(func, {skipNested: true}) },
 | 
						|
 | 
						|
	next: function(){},
 | 
						|
	prev: function(){},
 | 
						|
 | 
						|
	// XXX should there return an array or a .constructor(..) instance??
 | 
						|
	forEach: function(func, options){
 | 
						|
		this.map(...arguments)
 | 
						|
		return this },
 | 
						|
	filter: function(func, options){
 | 
						|
		return this.map(function(e, p, b){
 | 
						|
			return func.call(this, e, p, b) ? [e] : [] })
 | 
						|
		.flat() },
 | 
						|
	reduce: function(){},
 | 
						|
 | 
						|
 | 
						|
	// XXX do we need edit ability here? 
 | 
						|
	// 		i.e. .set(..), .remove(..), .sort(..), ...
 | 
						|
	// 		...if we are going to implement editing then we'll need to 
 | 
						|
	// 		callback the user code or update the user state...
 | 
						|
 | 
						|
 | 
						|
	// Events...
 | 
						|
	//
 | 
						|
	// Format:
 | 
						|
	// 	{
 | 
						|
	// 		// XXX add tagged event support...
 | 
						|
	// 		<event-name>: [
 | 
						|
	// 			<handler>,
 | 
						|
	// 			...
 | 
						|
	// 		],
 | 
						|
	// 		...
 | 
						|
	// 	}
 | 
						|
	//
 | 
						|
	// XXX
 | 
						|
	__event_handlers: null,
 | 
						|
 | 
						|
	// generic event infrastructure...
 | 
						|
	// XXX add support for tagged events...
 | 
						|
	// XXX should these be defined on this level or should we use DOM???
 | 
						|
	// XXX add support for item events...
 | 
						|
	// 		e.g. item.focus(..) -> root.focus(..)
 | 
						|
	// XXX also need to design a means for this system to interact both 
 | 
						|
	// 		ways with DOM events...
 | 
						|
	// XXX need to bubble the event up through the nested browsers...
 | 
						|
	on: function(evt, handler){
 | 
						|
		var handlers = this.__event_handlers = this.__event_handlers || {}
 | 
						|
		handlers = handlers[evt] = handlers[evt] || []
 | 
						|
		handlers.push(handler)
 | 
						|
		return this
 | 
						|
	},
 | 
						|
	one: function(evt, handler){
 | 
						|
		var func = function(...args){
 | 
						|
			handler.call(this, ...args)
 | 
						|
			this.off(evt, func)
 | 
						|
		}
 | 
						|
		this.on(evt, func)
 | 
						|
		return this
 | 
						|
	},
 | 
						|
	off: function(evt, handler){
 | 
						|
		// remove all handlers
 | 
						|
		if(handler == '*' || handler == 'all'){
 | 
						|
			delete (this.__event_handlers || {})[evt]
 | 
						|
 | 
						|
		// remove only the specific handler...
 | 
						|
		} else {
 | 
						|
			var handlers = (this.__event_handlers || {})[evt] || []
 | 
						|
			do{
 | 
						|
				var i = handlers.indexOf(handler)
 | 
						|
				i > -1
 | 
						|
					&& handlers.splice(i, 1)
 | 
						|
			} while(i > -1)
 | 
						|
		}
 | 
						|
		return this
 | 
						|
	},
 | 
						|
	trigger: function(evt, ...args){
 | 
						|
		var that = this
 | 
						|
		var stopPropagation = false
 | 
						|
		var evt = typeof(evt) == typeof('str') ?
 | 
						|
			// XXX construct this in one place...
 | 
						|
			// 		...currently it is constructed here and in makeEventMethod(..)
 | 
						|
			{
 | 
						|
				name: evt,
 | 
						|
				stopPropagation: function(){
 | 
						|
					stopPropagation = true },
 | 
						|
			}
 | 
						|
			: evt
 | 
						|
 | 
						|
		// call the main set of handlers...
 | 
						|
		;((this.__event_handlers || {})[evt.name] || [])
 | 
						|
			// prevent .off(..) from affecting the call loop...
 | 
						|
			.slice()
 | 
						|
			.forEach(function(handler){
 | 
						|
				handler.call(that, evt, ...args) })
 | 
						|
 | 
						|
		// trigger the parent's event...
 | 
						|
		!stopPropagation
 | 
						|
			&& this.parent
 | 
						|
			&& this.parent.trigger(evt, ...args)
 | 
						|
 | 
						|
		return this
 | 
						|
	},
 | 
						|
 | 
						|
	// domain events/actions...
 | 
						|
	// XXX need a way to extend these to:
 | 
						|
	// 		- be able to trigger an external (DOM) event...
 | 
						|
	// 		- be able to be triggered from an external (DOM) event...
 | 
						|
	focus: makeItemEventMethod('focus', function(evt, items){
 | 
						|
		// NOTE: if we got multiple matches we care only about the last one...
 | 
						|
		var item = items.pop()
 | 
						|
 | 
						|
		if(!item){
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		// blur .focused...
 | 
						|
		this.focused
 | 
						|
			&& this.blur(this.focused)
 | 
						|
 | 
						|
		item.focused = true
 | 
						|
	}),
 | 
						|
	blur: makeItemEventMethod('blur', function(evt, items){
 | 
						|
		items.forEach(function(item){
 | 
						|
			delete item.focused }) }),
 | 
						|
	// XXX update this.selected in a more granular way...
 | 
						|
	select: makeItemEventMethod('select', function(evt, items){
 | 
						|
		items.forEach(function(item){
 | 
						|
			item.selected = true
 | 
						|
			// XXX update this.selected in a more granular way...
 | 
						|
			delete this.__selected
 | 
						|
		}) }),
 | 
						|
	deselect: makeItemEventMethod('deselect', function(evt, item){
 | 
						|
		items.forEach(function(item){
 | 
						|
			delete item.selected
 | 
						|
			// XXX update this.selected in a more granular way...
 | 
						|
			delete this.__selected
 | 
						|
		}) }),
 | 
						|
 | 
						|
	open: makeItemEventMethod('open', function(evt, item){}),
 | 
						|
	enter: makeItemEventMethod('enter', function(evt, item){}),
 | 
						|
	// XXX can/should we unify these???
 | 
						|
	collapse: makeItemEventMethod('collapse', function(evt, item){}),
 | 
						|
	expand: makeItemEventMethod('expand', function(evt, item){}),
 | 
						|
 | 
						|
	// XXX target can be item or path...
 | 
						|
	load: makeEventMethod('load', function(evt, item){}),
 | 
						|
 | 
						|
	close: makeEventMethod('close', function(evt, reason){}),
 | 
						|
	
 | 
						|
	// XXX should we update on on init....
 | 
						|
	__init__: function(func, options){
 | 
						|
		this.__list__ = func
 | 
						|
		this.options = Object.assign(
 | 
						|
			{}, 
 | 
						|
			this.options || {}, 
 | 
						|
			options || {})
 | 
						|
 | 
						|
		// XXX should this be here or should this be optional???
 | 
						|
		//this.update()
 | 
						|
	},
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
var BaseBrowser = 
 | 
						|
module.BaseBrowser = 
 | 
						|
object.makeConstructor('BaseBrowser', 
 | 
						|
		BaseBrowserClassPrototype, 
 | 
						|
		BaseBrowserPrototype)
 | 
						|
 | 
						|
 | 
						|
 | 
						|
//---------------------------------------------------------------------
 | 
						|
 | 
						|
var BrowserClassPrototype = {
 | 
						|
	__proto__: BaseBrowser,
 | 
						|
}
 | 
						|
 | 
						|
// XXX TODO:
 | 
						|
// 		- need a way to update some stuff on .update() / .make() -- a way 
 | 
						|
// 			to selectively merge the old state with the new...
 | 
						|
// 		- event handler signature -- pass the item + optionally render...
 | 
						|
// 		- keyboard handling...
 | 
						|
// XXX render of nested lists does not affect the parent list(s)...
 | 
						|
// 		...need to render lists and items both as a whole or independently...
 | 
						|
// XXX should this use vanilla DOM or jQuery???
 | 
						|
// XXX add a left button type/option -- expand/collapse and friends...
 | 
						|
var BrowserPrototype = {
 | 
						|
	__proto__: BaseBrowser.prototype,
 | 
						|
 | 
						|
	options: {
 | 
						|
		hideListHeader: false,
 | 
						|
 | 
						|
		renderHidden: false,
 | 
						|
 | 
						|
		localEvents: [
 | 
						|
			// XXX STUB???
 | 
						|
			'click',
 | 
						|
 | 
						|
			// XXX keyboard stuff...
 | 
						|
			// XXX
 | 
						|
 | 
						|
			// XXX custom events...
 | 
						|
			// XXX
 | 
						|
		],
 | 
						|
		//buttonLocalEvents: [
 | 
						|
		//],
 | 
						|
 | 
						|
		// Format:
 | 
						|
		// 	[
 | 
						|
		// 		['html', <handler>],
 | 
						|
		// 		...
 | 
						|
		// 	]
 | 
						|
		itemButtons: [
 | 
						|
		],
 | 
						|
		// XXX need to mix these into the header only...
 | 
						|
		headerItemButtons: [
 | 
						|
		],
 | 
						|
 | 
						|
		// Shorthand elements...
 | 
						|
		//
 | 
						|
		// Format:
 | 
						|
		// 	{
 | 
						|
		// 		<key>: {
 | 
						|
		// 			class: <element-class-str>,
 | 
						|
		// 			html: <element-html-str>,
 | 
						|
		// 		},
 | 
						|
		// 		...
 | 
						|
		// 	}
 | 
						|
		//
 | 
						|
		// If make(..) gets passed <key> it will construct and element
 | 
						|
		// via <element-html-str> with an optional <element-class-str>
 | 
						|
		//
 | 
						|
		// NOTE: .class is optional...
 | 
						|
		// NOTE: set this to null to disable shorthands...
 | 
						|
		// NOTE: currently the options in the template will override 
 | 
						|
		// 		anything explicitly given by item options... (XXX revise)
 | 
						|
		elementShorthand: {
 | 
						|
			'---': {
 | 
						|
				'class': 'separator',
 | 
						|
				'html': '<hr>',
 | 
						|
				noniterable: true,
 | 
						|
			},
 | 
						|
			'...': {
 | 
						|
				'class': 'separator',
 | 
						|
				'html': '<center><div class="loader"/></center>',
 | 
						|
				noniterable: true,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	},
 | 
						|
 | 
						|
	// parent element (optional)...
 | 
						|
	// XXX rename???
 | 
						|
	// 		... should this be .containerDom or .parentDom???
 | 
						|
	get container(){
 | 
						|
		return this.__container 
 | 
						|
			|| (this.__dom ? 
 | 
						|
				this.__dom.parentElement 
 | 
						|
				: undefined) },
 | 
						|
	set container(value){
 | 
						|
		var dom = this.dom
 | 
						|
		this.__container = value
 | 
						|
		// transfer the dom to the new parent...
 | 
						|
		dom && (this.dom = dom)
 | 
						|
	},
 | 
						|
 | 
						|
	// browser dom...
 | 
						|
	get dom(){
 | 
						|
		return this.__dom },
 | 
						|
	set dom(value){
 | 
						|
		this.container 
 | 
						|
			&& (this.__dom ?
 | 
						|
				this.container.replaceChild(value, this.__dom) 
 | 
						|
				: this.container.appendChild(value))
 | 
						|
		this.__dom = value },
 | 
						|
 | 
						|
 | 
						|
	// Element renderers...
 | 
						|
	//
 | 
						|
	// Foramt:
 | 
						|
	// 	<div class="browse-widget" tabindex="0">
 | 
						|
	// 		<!-- header -->
 | 
						|
	// 		...
 | 
						|
	//
 | 
						|
	// 		<!-- list -->
 | 
						|
	// 		<div class="list v-block">
 | 
						|
	// 			<!-- items -->
 | 
						|
	// 			...
 | 
						|
	// 		</div>
 | 
						|
	// 	</div>
 | 
						|
	//
 | 
						|
	// XXX instrument interactions...
 | 
						|
	// XXX register event handlers...
 | 
						|
	renderList: function(items, context){
 | 
						|
		var that = this
 | 
						|
		var options = context.options || this.options
 | 
						|
 | 
						|
		// dialog (container)...
 | 
						|
		var dialog = document.createElement('div')
 | 
						|
		dialog.classList.add('browse-widget')
 | 
						|
		dialog.setAttribute('tabindex', '0')
 | 
						|
 | 
						|
		// header...
 | 
						|
		options.hideListHeader
 | 
						|
			|| dialog.appendChild(this.renderListHeader(context))
 | 
						|
 | 
						|
		// list...
 | 
						|
		var list = document.createElement('div')
 | 
						|
		list.classList.add('list', 'v-block')
 | 
						|
		items
 | 
						|
			.forEach(function(item){
 | 
						|
				list.appendChild(item instanceof Array ? 
 | 
						|
					that.renderGroup(item) 
 | 
						|
					: item) })
 | 
						|
		dialog.appendChild(list)
 | 
						|
 | 
						|
		// XXX event handlers...
 | 
						|
		// XXX
 | 
						|
 | 
						|
		return dialog 
 | 
						|
	},
 | 
						|
	//
 | 
						|
	// Foramt:
 | 
						|
	//	<div class="path v-block">
 | 
						|
	//		<div class="dir" tabindex="0">dir</div>
 | 
						|
	//		...
 | 
						|
	//		<div class="dir cur" tabindex="0">dir</div>
 | 
						|
	//	</div>
 | 
						|
	// 	
 | 
						|
	// XXX populate this...
 | 
						|
	// XXX make this an item???
 | 
						|
	renderListHeader: function(context){
 | 
						|
		var header = document.createElement('div')
 | 
						|
		header.classList.add('path', 'v-block')
 | 
						|
 | 
						|
		// XXX path/search...
 | 
						|
		var dir = document.createElement('div')
 | 
						|
		dir.classList.add('dir', 'cur')
 | 
						|
		dir.setAttribute('tabindex', '0')
 | 
						|
		header.appendChild(dir)
 | 
						|
 | 
						|
		return header
 | 
						|
	},
 | 
						|
	//
 | 
						|
	// Format:
 | 
						|
	// 	<div class="list">
 | 
						|
	// 		<!-- header (optional) -->
 | 
						|
	// 		...
 | 
						|
	//
 | 
						|
	// 		<!-- sublist (optional) -->
 | 
						|
	// 		...
 | 
						|
	// 	</div>
 | 
						|
	//
 | 
						|
	// XXX register event handlers...
 | 
						|
	renderNested: function(header, sublist, item, context){
 | 
						|
		var that = this
 | 
						|
		var options = context.options || this.options
 | 
						|
 | 
						|
		// container...
 | 
						|
		var e = document.createElement('div')
 | 
						|
		e.classList.add('list')
 | 
						|
 | 
						|
		// localize events...
 | 
						|
		var stopPropagation = function(evt){ evt.stopPropagation() }
 | 
						|
		;(options.localEvents || [])
 | 
						|
			.forEach(function(evt){
 | 
						|
				e.addEventListener(evt, stopPropagation) })
 | 
						|
 | 
						|
		// header...
 | 
						|
		header
 | 
						|
			&& e.appendChild(header)
 | 
						|
 | 
						|
		// items...
 | 
						|
		sublist instanceof Node ?
 | 
						|
			e.appendChild(sublist)
 | 
						|
		// XXX should this add the items to a container???
 | 
						|
		: sublist instanceof Array ?
 | 
						|
			sublist
 | 
						|
				.forEach(function(item){
 | 
						|
					e.appendChild(item) })
 | 
						|
		: null
 | 
						|
 | 
						|
		// XXX event handlers... (???)
 | 
						|
		// XXX
 | 
						|
 | 
						|
		item.dom = e
 | 
						|
 | 
						|
		return e
 | 
						|
	},
 | 
						|
	// NOTE: this is the similar to .renderItem(..)
 | 
						|
	// XXX make collapse action overloadable....
 | 
						|
	renderNestedHeader: function(item, i, context){
 | 
						|
		var that = this
 | 
						|
		return this.renderItem(item, i, context)
 | 
						|
			// update dom...
 | 
						|
			.run(function(){
 | 
						|
				// class...
 | 
						|
				// XXX should be done here or in the config???
 | 
						|
				this.classList.add('sub-list-header', 'traversable')
 | 
						|
				item.collapsed
 | 
						|
					&& this.classList.add('collapsed')
 | 
						|
 | 
						|
				// collapse action handler...
 | 
						|
				// XXX make this overloadable...
 | 
						|
				$(this).on('open', function(evt){
 | 
						|
					item.collapsed = !item.collapsed
 | 
						|
					that.render(context)
 | 
						|
				})
 | 
						|
			}) },
 | 
						|
	//
 | 
						|
	// Format:
 | 
						|
	// 	<div class="group">
 | 
						|
	// 		..
 | 
						|
	// 	</div>
 | 
						|
	//
 | 
						|
	// XXX this does not seem to get called by .render(..)...
 | 
						|
	renderGroup: function(items, context){
 | 
						|
		var e = document.createElement('div')
 | 
						|
		e.classList.add('group')
 | 
						|
		items
 | 
						|
			// XXX is this wrong???
 | 
						|
			.flat(Infinity)
 | 
						|
			.forEach(function(item){
 | 
						|
				e.appendChild(item) })
 | 
						|
		return e },
 | 
						|
	//
 | 
						|
	// Format:
 | 
						|
	// 	<div value="value_json" class="item .." tabindex="0" ..>
 | 
						|
	// 		<!-- value -->
 | 
						|
	// 		<div class="text">value_a</div>
 | 
						|
	// 		<div class="text">value_b</div>
 | 
						|
	// 		...
 | 
						|
	//
 | 
						|
	// 		<!-- buttons (optional) -->
 | 
						|
	// 		<div class="button">button_a_html</div>
 | 
						|
	// 		<div class="button">button_b_html</div>
 | 
						|
	// 		...
 | 
						|
	// 	</div>
 | 
						|
	//
 | 
						|
	// XXX add custom events:
 | 
						|
	// 		- open
 | 
						|
	// 		- select
 | 
						|
	// 		- update
 | 
						|
	renderItem: function(item, i, context){
 | 
						|
		var options = context.options || this.options
 | 
						|
		if(options.hidden && !options.renderHidden){
 | 
						|
			return null
 | 
						|
		}
 | 
						|
 | 
						|
		// special-case: item shorthands...
 | 
						|
		if(item.value in options.elementShorthand){
 | 
						|
			// XXX need to merge and not overwrite -- revise...
 | 
						|
			Object.assign(item, options.elementShorthand[item.value])
 | 
						|
 | 
						|
			// NOTE: this is a bit of a cheat, but it saves us from either 
 | 
						|
			// 		parsing or restricting the format...
 | 
						|
			var elem = item.dom = $(item.html)[0]
 | 
						|
			elem.classList.add(
 | 
						|
				...(item['class'] instanceof Array ?
 | 
						|
					item['class']
 | 
						|
					: item['class'].split(/\s+/g)))
 | 
						|
 | 
						|
			return elem 
 | 
						|
		}
 | 
						|
 | 
						|
		// Base DOM...
 | 
						|
		var elem = document.createElement('div')
 | 
						|
		var text = this.__value2key__(item.value || item)
 | 
						|
 | 
						|
		// classes...
 | 
						|
		elem.classList.add(...['item']
 | 
						|
			// user classes...
 | 
						|
			.concat(item['class'] || item.cls || [])
 | 
						|
			// special classes...
 | 
						|
			.concat([
 | 
						|
				'selected',
 | 
						|
				'disabled',
 | 
						|
				'hidden',
 | 
						|
			].filter(function(cls){ 
 | 
						|
				return !!item[cls] })))
 | 
						|
 | 
						|
		// attrs...
 | 
						|
		item.disabled
 | 
						|
			|| elem.setAttribute('tabindex', '0')
 | 
						|
		Object.entries(item.attrs || {})
 | 
						|
			.forEach(function([key, value]){
 | 
						|
				elem.setAttribute(key, value) })
 | 
						|
		elem.setAttribute('value', text)
 | 
						|
 | 
						|
		// values...
 | 
						|
		text
 | 
						|
			&& (item.value instanceof Array ? item.value : [item.value])
 | 
						|
				// XXX handle $keys and other stuff...
 | 
						|
				.map(function(v){
 | 
						|
					var value = document.createElement('span')
 | 
						|
					value.classList.add('text')
 | 
						|
					value.innerHTML = v || item || ''
 | 
						|
					elem.appendChild(value)
 | 
						|
				})
 | 
						|
 | 
						|
		// events...
 | 
						|
		// XXX revise signature...
 | 
						|
		elem.addEventListener('click', 
 | 
						|
			function(){ $(elem).trigger('open', [text, item, elem]) })
 | 
						|
		//elem.addEventListener('tap', function(){ $(elem).trigger('open', [text, item, elem]) })
 | 
						|
		Object.entries(item.events || {})
 | 
						|
			// shorthand events...
 | 
						|
			.concat([
 | 
						|
					'click',
 | 
						|
				].map(function(evt){ 
 | 
						|
					return [evt, item[evt]] }))
 | 
						|
			// setup the handlers...
 | 
						|
			.forEach(function([evt, handler]){
 | 
						|
				handler
 | 
						|
					&& elem.addEventListener(evt, handler) })
 | 
						|
 | 
						|
		// buttons...
 | 
						|
		// XXX migrate the default buttons functionality and button inheritance...
 | 
						|
		var buttons = (item.buttons || options.itemButtons || [])
 | 
						|
			.slice()
 | 
						|
			// NOTE: keep the order unsurprising...
 | 
						|
			.reverse()
 | 
						|
		var stopPropagation = function(evt){ evt.stopPropagation() }
 | 
						|
		buttons
 | 
						|
			.forEach(function([html, handler]){
 | 
						|
				var button = document.createElement('div')
 | 
						|
				button.classList.add('button')
 | 
						|
				button.innerHTML = html
 | 
						|
				if(!item.disabled){
 | 
						|
					button.setAttribute('tabindex', '0')
 | 
						|
					;(options.buttonLocalEvents || options.localEvents || [])
 | 
						|
						.forEach(function(evt){
 | 
						|
							button.addEventListener(evt, stopPropagation) })
 | 
						|
					handler
 | 
						|
						&& button.addEventListener('click', handler)
 | 
						|
				}
 | 
						|
				elem.appendChild(button)
 | 
						|
			})
 | 
						|
		
 | 
						|
		item.dom = elem
 | 
						|
 | 
						|
		return elem 
 | 
						|
	},
 | 
						|
 | 
						|
	// This does tow additional things:
 | 
						|
	// 	- save the rendered state to .dom 
 | 
						|
	// 	- wrap a list of nodes (nested list) in a div
 | 
						|
	render: function(options){
 | 
						|
		var d = object.parent(BrowserPrototype.render, this).call(this, ...arguments)
 | 
						|
 | 
						|
		// wrap the list (nested list) of nodes in a div...
 | 
						|
		if(d instanceof Array){
 | 
						|
			var c = document.createElement('div')
 | 
						|
			d.forEach(function(e){
 | 
						|
				c.appendChild(e) })
 | 
						|
			d = c
 | 
						|
		}
 | 
						|
 | 
						|
		this.dom = d
 | 
						|
		return this.dom
 | 
						|
	},
 | 
						|
 | 
						|
	// Custom events...
 | 
						|
	// XXX do we use jQuery event handling or vanilla?
 | 
						|
	// 		...feels like jQuery here wins as it provides a far simpler
 | 
						|
	// 		API + it's a not time critical area...
 | 
						|
	// 		....another idea is to force the user to use the provided API
 | 
						|
	// 		by not implementing ANY direct functionality in DOM -- I do
 | 
						|
	// 		not like this idea at this point as it violates POLS...
 | 
						|
	//open: function(func){},
 | 
						|
 | 
						|
	//filter: function(){},
 | 
						|
 | 
						|
	//select: function(){},
 | 
						|
	//get: function(){},
 | 
						|
	//focus: function(){},
 | 
						|
 | 
						|
	// Navigation...
 | 
						|
	//
 | 
						|
	up: function(){},
 | 
						|
	down: function(){},
 | 
						|
	left: function(){},
 | 
						|
	right: function(){},
 | 
						|
 | 
						|
	next: function(){},
 | 
						|
	prev: function(){},
 | 
						|
 | 
						|
	collapse: function(){},
 | 
						|
	// XXX scroll...
 | 
						|
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
// XXX should this be a Widget too???
 | 
						|
var Browser = 
 | 
						|
module.Browser = 
 | 
						|
object.makeConstructor('Browser', 
 | 
						|
		BrowserClassPrototype, 
 | 
						|
		BrowserPrototype)
 | 
						|
 | 
						|
 | 
						|
 | 
						|
//---------------------------------------------------------------------
 | 
						|
// Text tree renderer...
 | 
						|
//
 | 
						|
// This is mainly designed for testing.
 | 
						|
//
 | 
						|
// XXX Q: how should the header item and it's sub-list be linked???
 | 
						|
 | 
						|
var TextBrowserClassPrototype = {
 | 
						|
	__proto__: BaseBrowser,
 | 
						|
}
 | 
						|
 | 
						|
var TextBrowserPrototype = {
 | 
						|
	__proto__: BaseBrowser.prototype,
 | 
						|
 | 
						|
	options: {
 | 
						|
		renderIndent: '\t',
 | 
						|
	},
 | 
						|
	
 | 
						|
	// NOTE: we do not need .renderGroup(..) here as a group is not 
 | 
						|
	// 		visible in text...
 | 
						|
	renderList: function(items, options){
 | 
						|
		var that = this
 | 
						|
		return this.renderNested(null, items, null, null, options)
 | 
						|
			.join('\n') },
 | 
						|
	renderItem: function(item, i, options){
 | 
						|
		var value = item.value || item
 | 
						|
		return item.current ?
 | 
						|
			`[ ${value} ]`
 | 
						|
   			: value },
 | 
						|
	renderNested: function(header, sublist, context, item, options){
 | 
						|
		var that = this
 | 
						|
		var nested = sublist 
 | 
						|
			&& sublist
 | 
						|
				.flat()
 | 
						|
				.map(function(e){
 | 
						|
					return e instanceof Array ?
 | 
						|
						e.map(function(e){ 
 | 
						|
							return (that.options.renderIndent || '  ') + e })
 | 
						|
						: e })
 | 
						|
				.flat() 
 | 
						|
		return (
 | 
						|
			// expanded...
 | 
						|
			header && nested ?
 | 
						|
				[
 | 
						|
					'- ' + header,
 | 
						|
					nested,
 | 
						|
				]
 | 
						|
			// collapsed...
 | 
						|
			: header ?
 | 
						|
				[ '+ ' + header ]
 | 
						|
			// headerless...
 | 
						|
			: nested )},
 | 
						|
}
 | 
						|
 | 
						|
var TextBrowser = 
 | 
						|
module.TextBrowser = 
 | 
						|
object.makeConstructor('TextBrowser', 
 | 
						|
		TextBrowserClassPrototype, 
 | 
						|
		TextBrowserPrototype)
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/**********************************************************************
 | 
						|
* vim:set ts=4 sw=4 :                               */ return module })
 |