diff --git a/ui (gen4)/lib/widget/browse2.js b/ui (gen4)/lib/widget/browse2.js
index d2c0add4..04ecfce9 100755
--- a/ui (gen4)/lib/widget/browse2.js
+++ b/ui (gen4)/lib/widget/browse2.js
@@ -396,7 +396,8 @@ object.mixinFlat(function(){}, {
.on('focus',
function(){
e.value = this.pathArray
- this.renderItem(e) },
+ e.update()
+ },
tag)
return make },
@@ -442,7 +443,7 @@ object.mixinFlat(function(){}, {
e.value = focused.doc
|| focused.alt
|| ' '
- this.renderItem(e) },
+ e.update() },
tag)
return make },
@@ -512,22 +513,22 @@ var BaseItemPrototype = {
// multiple ways to get and index...
get pathArray(){
- var p = this.parent
- while(p.parent instanceof BaseBrowser){
- p = p.parent }
- return p ?
- p.pathOf(this)
+ var r = (this.parent || {}).root
+ return r ?
+ r.pathOf(this)
: undefined },
get path(){
- return this.pathArray.join('/') },
+ return (this.pathArray || []).join('/') },
+ get index(){
+ var r = (this.parent || {}).root
+ return r ?
+ r.indexOf(this)
+ : undefined },
- // XXX local update/render...
- // XXX should we use these in the main render???
update: function(){
-
- // XXX
-
+ this.parent
+ && this.parent.render(this)
return this
},
@@ -3177,10 +3178,8 @@ var BaseBrowserPrototype = {
renderer.renderFinalize(null, items, null, context)
// nested context -> return item list...
: items } },
- /*/
+ //*/
- // XXX EXPERIMENTAL....
- //
__renderer__: TextRenderer,
// XXX need:
// - section rendering... (DONE)
@@ -3195,8 +3194,12 @@ var BaseBrowserPrototype = {
// XXX args parsing...
// XXX
var args = [...arguments]
- // XXX
- var list
+
+ // XXX add support for item queries...
+ var list = (args[0] instanceof BaseItem || args[0] instanceof Array) ?
+ [args.shift()].flat()
+ : null
+
var filter
// NOTE: these only apply to the 'items' section...
@@ -3336,12 +3339,13 @@ var BaseBrowserPrototype = {
}, options))
// finalize render...
.run(function(){
- return (!options.nonFinalized && render.root === that) ?
+ return (list == null
+ && !options.nonFinalized
+ && render.root === that) ?
render.finalize(this instanceof Array ?
{[section]: this}
- : this, options)
+ : this, options)
: this }) },
- //*/
// Events...
@@ -3998,21 +4002,6 @@ var HTMLItemPrototype = {
this.dom ?
this.elem.replaceWith(value)
: (this.dom = value)},
-
- // XXX EXPERIMENTAL...
- // XXX should we use these in the main render???
- update: function(){
- return object
- .parent(HTMLItemPrototype.update, this).call(this, ...arguments)
- .run(function(){
- var parent = this.parent
-
- // XXX needs i and context...
- // XXX see issue with .elem above...
- this.elem = parent.renderItem(this, 0, {})
-
- // XXX handle children...
- }) },
}
var HTMLItem =
@@ -4136,9 +4125,6 @@ var updateElemClass = function(action, cls, handler){
// XXX needs testing...
// - partial rendering...
// - local re-rendering...
-// - problems:
-// - re-rendering loses focus...
-// XXX doc...
var HTMLRenderer =
module.HTMLRenderer = {
__proto__: BaseRenderer,
@@ -4486,7 +4472,7 @@ module.HTMLRenderer = {
.forEach(function(key){
// XXX should we break or warn???
if(key in button_keys){
- throw new Error(`renderItem(..): button key already used: ${key}`) }
+ throw new Error(`.elem(..): button key already used: ${key}`) }
button_keys[keyboard.joinKey(keyboard.normalizeKey(key))] = button })
// keep focus on the item containing the button -- i.e. if
// we tab out of the item focus the item we get to...
@@ -4642,7 +4628,6 @@ module.HTMLRenderer = {
return render
},
- // XXX BUG: focus is lost here for some reason...
finalize: function(sections, options){
var dialog = this.root
@@ -4683,11 +4668,6 @@ module.HTMLRenderer = {
dialog.dom = d
// set the scroll offset...
- // XXX does not work correctly for all cases yet...
- // XXX move this to a seporate method -- need to trigger this
- // on render that can affect scroll position, e.g. partial
- // render...
- // XXX need to trigger the setup for this from .render(..) itself...
if(this.scroll_offset){
var ref = dialog.focused || dialog.pagetop
var scrolled = ref.dom.offsetParent
@@ -4699,7 +4679,7 @@ module.HTMLRenderer = {
}
// keep focus where it is...
- var focused = this.focused
+ var focused = dialog.focused
focused
&& focused.elem
// XXX this will trigger the focus event...
@@ -5118,622 +5098,6 @@ var HTMLBrowserPrototype = {
navigator.clipboard.writeText(text || this.path) },
- // Renderers (DOM)...
- //
- // XXX add a render mode where we replace specific element's .dom...
- // ...this could be done via:
- // .renderUpdate(..) -- like .renderFinalize(..) but replaces nodes...
- // needs more thought...
- // ...another idea would be to make the render flow "internal", i.e. each
- // item renders and places itself item "packing" is done internally
- // rather than via outer render combinators...
- // - create/get root context
- // - container -> create/clone context + append/replace in parent context
- // - item -> create/clone + append/replace self in parent context
- // - ...
- // - update root context
- // this approach should make the individual renderers self-contained, i.e.
- // rendering a single item would update it in the main tree... (needs thought)
- //
- //
- // Prepare context for maintaining scroll offset...
- //
- // XXX need a counterpart to this to finalize the context on any render...
- // ...or a way to tell .render(..) to render / re-render only
- // specific items...
- renderContext: function(context){
- context = context || {}
-
- // prepare for maintaining the scroll position...
- // XXX need to do this pre any .render*(..) call...
- // ...something like:
- // this.getRenderContext(context)
- // should do the trick...
- // another way to go might be a context object, but that seems to be
- // complicating things...
- var ref = context.scroll_reference =
- context.scroll_reference
- || this.focused
- || this.pagetop
- context.scroll_offset =
- context.scroll_offset
- || ((ref && ref.dom && ref.dom.offsetTop) ?
- ref.dom.offsetTop - ref.dom.offsetParent.scrollTop
- : null)
-
- //context.scroll_offset && console.log('renderContext:', context.scroll_offset)
-
- return context
- },
- //
- // This also does:
- // - save the rendered state to .dom
- // - wrap a list of nodes (nested list) in a div
- // - setup event handling
- // - init state...
- //
- // Format:
- // if list of items passed:
- //
- //
- // ...
- //
- // or same as .renderList(..)
- //
- // XXX set scroll offset... should it be here?
- // ...would be nice to universally wrap any render operation
- // in a context getter/setter that would track the root call and
- // trigger an postRender event...
- // how can we Identify the root call??
- // ...the traditional way would be a stack -- pop last elem
- // means we are done...
- // ways to go with this:
- // - renderer wrapper/generator
- // - a 1:1 item-based render mechanic with single entry point...
- // ...this may be a problem as some of the renderers
- // can call others... (???)
- renderFinalize: function(header, items, footer, context){
- var that = this
- var context = this.renderContext(context)
-
- var d = this.renderList(header, items, footer, context)
- var options = context.options || this.options || {}
-
- // wrap the list (nested list) of nodes in a div...
- if(d instanceof Array){
- var c = document.createElement('div')
- d.classList.add('focusable')
- d.forEach(function(e){
- c.appendChild(e) })
- d = c
- }
- d.setAttribute('tabindex', '0')
-
- // Setup basic event handlers...
- // keyboard...
- // NOTE: we are not doing:
- // d.addEventListener('keydown', this.keyPress.bind(this))
- // because we are abstracting the user from DOM events and
- // directly passing them parsed keys...
- d.addEventListener('keydown', function(evt){
- that.keyPress(that.keyboard.event2key(evt)) })
- // focus...
- d.addEventListener('click',
- function(e){
- e.stopPropagation()
- d.focus() })
- /* XXX this messes up the scrollbar...
- d.addEventListener('focus',
- function(){
- that.focused
- && that.focused.elem.focus() })
- //*/
-
-
- // XXX should this be done here or in .render(..)???
- this.dom = d
-
- // set the scroll offset...
- // XXX does not work correctly for all cases yet...
- // XXX move this to a seporate method -- need to trigger this
- // on render that can affect scroll position, e.g. partial
- // render...
- // XXX need to trigger the setup for this from .render(..) itself...
- if(context.scroll_offset){
- var ref = this.focused || this.pagetop
- var scrolled = ref.dom.offsetParent
- //scrolled.scrollTop =
- // ref.elem.offsetTop - scrolled.scrollTop - context.scroll_offset
- scrolled
- && (scrolled.scrollTop =
- ref.elem.offsetTop - scrolled.scrollTop - context.scroll_offset)
- }
-
- // keep focus where it is...
- var focused = this.focused
- focused
- && focused.elem
- // XXX this will trigger the focus event...
- // ...can we do this without triggering new events???
- .focus()
-
- return this.dom
- },
- //
- // Foramt:
- //
- //
- renderList: function(header, items, footer, context){
- var that = this
- var context = this.renderContext(context)
- var options = context.options || this.options || {}
-
- // dialog (container)...
- var dialog = document.createElement('div')
- dialog.classList.add('browse-widget')
- dialog.setAttribute('tabindex', '0')
- // HACK?: prevent dialog from grabbing focus from item...
- dialog.addEventListener('mousedown',
- function(evt){ evt.stopPropagation() })
-
- // header...
- header
- && !options.hideListHeader
- && dialog.appendChild(this.renderListHeader(header, context))
-
- // list...
- var list = document.createElement('div')
- list.classList.add('list', 'v-block', 'items')
- // prevent scrollbar from grabbing focus...
- list.addEventListener('mousedown',
- function(evt){ evt.stopPropagation() })
- items
- .forEach(function(item){
- list.appendChild(item instanceof Array ?
- that.renderGroup(item)
- : item) })
- dialog.appendChild(list)
-
- // footer...
- footer
- && !options.hideListFooter
- && dialog.appendChild(this.renderListFooter(footer, context))
-
- return dialog
- },
- //
- // Foramt:
- //
- //
- //
- //
- //
- // XXX populate this...
- // XXX make this an item???
- renderListHeader: function(items, context){
- var elem = this.renderList(null, items, null, context).firstChild
- // XXX should we replace 'list' or add 'header'
- elem.classList.replace('items', 'header')
- return elem },
- renderListFooter: function(items, context){
- var elem = this.renderList(null, items, null, context).firstChild
- // XXX should we replace 'list' or add 'footer'
- elem.classList.replace('items', 'footer')
- return elem },
- //
- // Format:
- //
- //
- // ...
- //
- //
- // ...
- //
- //
- renderNested: function(header, children, item, context){
- var that = this
- var context = this.renderContext(context)
- 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.itemLocalEvents || [])
- .forEach(function(evt){
- e.addEventListener(evt, stopPropagation) })
-
- // header...
- header
- && e.appendChild(header)
-
- // items...
- children instanceof Node ?
- e.appendChild(children)
- : children instanceof Array ?
- children
- .forEach(function(item){
- e.appendChild(item) })
- : null
-
- item.dom = e
-
- return e
- },
- // NOTE: this is the similar to .renderItem(..)
- renderNestedHeader: function(item, i, context){
- var that = this
- return this.renderItem(item, i, context)
- // update dom...
- .run(function(){
- this.classList.add('sub-list-header', 'traversable')
- item.collapsed
- && this.classList.add('collapsed')
- }) },
- //
- // Format:
- //
- //
- // ...
- //
- //
- renderGroup: function(items, context){
- var context = this.renderContext(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:
- //
- //
- //
value_a
- //
value_b
- // ...
- //
- //
- //
button_a_html
- //
button_b_html
- // ...
- //
- //
- // NOTE: DOM events trigger Browser events but not the other way
- // around. It is not recommended to use DOM events directly.
- //
- // XXX need to figure out an intuitive behavior of focus + disable/enable...
- // ...do we skip disabled elements?
- // ...can a disabled item be focused?
- // ...how do we collapse/expand a disabled root?
- // ...what do we focus when toggleing disabled?
- // XXX handle .options.focusDisabledItems correctly...
- // - tabindex -- DONE
- // - ???
- // XXX show button global/local keys...
- renderItem: function(item, i, context){
- var that = this
- var context = this.renderContext(context)
- var options = (context || {}).options || this.options || {}
- if(options.hidden && !options.renderHidden){
- return null
- }
- var section = item.section || options.section
-
- // helpers...
- // XXX we need to more carefully test the value to avoid name clashes...
- var resolveValue = function(value, context, exec_context){
- var htmlhandler = typeof(value) == typeof('str') ?
- that.parseStringHandler(value, exec_context)
- : null
- return value instanceof Function ?
- value.call(that, item)
- : htmlhandler
- && htmlhandler.action in context
- && context[htmlhandler.action] instanceof Function ?
- context[htmlhandler.action]
- .call(that, item, ...htmlhandler.arguments)
- : value }
- var setDOMValue = function(target, value){
- value instanceof HTMLElement ?
- target.appendChild(value)
- : (typeof(jQuery) != 'undefined' && value instanceof jQuery) ?
- value.appendTo(target)
- : (target.innerHTML = value)
- return target }
- var doTextKeys = function(text, doKey){
- return text.replace(/\$\w/g,
- function(k){
- // forget the '$'...
- k = k[1]
- return (doKey && doKey(k)) ?
- `${k}`
- : k }) }
-
- // special-case: item.html...
- if(item.html){
- // NOTE: this is a bit of a cheat, but it saves us from either
- // parsing or restricting the format...
- var tmp = document.createElement('div')
- tmp.innerHTML = item.html
- var elem = item.dom = tmp.firstElementChild
- 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 = item.text
-
- // classes...
- elem.classList.add(...['item']
- // user classes...
- .concat((item['class'] || item.cls || [])
- // parse space-separated class strings...
- .run(function(){
- return this instanceof Array ?
- this
- : this.split(/\s+/g) }))
- // special classes...
- .concat(
- (options.shorthandItemClasses || [])
- .filter(function(cls){
- return !!item[cls] })))
-
- // attrs...
- ;(item.disabled && !options.focusDisabledItems)
- || elem.setAttribute('tabindex', '0')
- Object.entries(item.attrs || {})
- // shorthand attrs...
- .concat((options.shorthandItemAttrs || [])
- .map(function(key){
- return [key, item[key]] }))
- .forEach(function([key, value]){
- value !== undefined
- && elem.setAttribute(key, value) })
- ;(item.value == null
- || item.value instanceof Object)
- || elem.setAttribute('value', item.text)
- ;(item.value == null
- || item.value instanceof Object
- || item.alt != item.text)
- && elem.setAttribute('alt', item.alt)
-
- // values...
- text != null
- && (item.value instanceof Array ?
- item.value
- : [item.value])
- // handle $keys and other stuff...
- // NOTE: the actual key setup is done in .__preRender__(..)
- // see that for more info...
- .map(function(v){
- // handle key-shortcuts $K...
- v = typeof(v) == typeof('str') ?
- doTextKeys(v,
- function(k){
- return (item.keys || [])
- .includes(that.keyboard.normalizeKey(k)) })
- : v
-
- var value = document.createElement('span')
- value.classList.add('text')
-
- // set the value...
- setDOMValue(value,
- resolveValue(v, that))
-
- elem.appendChild(value)
- })
-
- // system events...
- elem.addEventListener('click',
- function(evt){
- evt.stopPropagation()
- // NOTE: if an item is disabled we retain its expand/collapse
- // functionality...
- // XXX revise...
- item.disabled ?
- that.toggleCollapse(item)
- : that.open(item, text, elem) })
- elem.addEventListener('focus',
- function(){
- // NOTE: we do not retrigger focus on an item if it's
- // already focused...
- that.focused !== item
- // only trigger focus on gettable items...
- // ...i.e. items in the main section excluding headers
- // and footers...
- && that.focus(item) })
- elem.addEventListener('contextmenu',
- function(evt){
- evt.preventDefault()
- that.menu(item) })
- // user events...
- Object.entries(item.events || {})
- // shorthand DOM events...
- .concat((options.shorthandItemEvents || [])
- .map(function(evt){
- return [evt, item[evt]] }))
- // setup the handlers...
- .forEach(function([evt, handler]){
- handler
- && elem.addEventListener(evt, handler.bind(that)) })
-
- // buttons...
- var button_keys = {}
- // XXX migrate button inheritance...
- var buttons = (item.buttons
- || (section == 'header'
- && (options.headerButtons || []))
- || (section == 'footer'
- && (options.footerButtons || []))
- || options.itemButtons
- || [])
- // resolve buttons from library...
- .map(function(button){
- return button instanceof Array ?
- button
- // XXX reference the actual make(..) and not Items...
- : Items.buttons[button] instanceof Function ?
- [Items.buttons[button].call(that, item)].flat()
- : Items.buttons[button] || button })
- // NOTE: keep the order unsurprising -- first defined, first from left...
- .reverse()
- var stopPropagation = function(evt){ evt.stopPropagation() }
- buttons
- .forEach(function([html, handler, ...rest]){
- var force = (rest[0] === true
- || rest[0] === false
- || rest[0] instanceof Function) ?
- rest.shift()
- : undefined
- var metadata = rest.shift() || {}
-
- // metadata...
- var cls = metadata.cls || []
- cls = cls instanceof Function ?
- cls.call(that, item)
- : cls
- cls = cls instanceof Array ?
- cls
- : cls.split(/\s+/g)
- var alt = metadata.alt
- alt = alt instanceof Function ?
- alt.call(that, item)
- : alt
- var keys = metadata.keys
-
- var button = document.createElement('div')
- button.classList.add('button', ...cls)
- alt
- && button.setAttribute('alt', alt)
-
- // button content...
- var text_keys = []
- var v = resolveValue(html, Items.buttons, {item})
- setDOMValue(button,
- typeof(v) == typeof('str') ?
- doTextKeys(v,
- function(k){
- k = that.keyboard.normalizeKey(k)
- return options.disableButtonSortcuts ?
- false
- : !text_keys.includes(k)
- && text_keys.push(k) })
- : v)
- keys = text_keys.length > 0 ?
- (keys || []).concat(text_keys)
- : keys
-
- // non-disabled button...
- if(force instanceof Function ?
- force.call(that, item)
- : (force || !item.disabled) ){
- button.setAttribute('tabindex', '0')
- // events to keep in buttons...
- ;(options.buttonLocalEvents || options.itemLocalEvents || [])
- .forEach(function(evt){
- button.addEventListener(evt, stopPropagation) })
- // button keys...
- keys && !options.disableButtonSortcuts
- && (keys instanceof Array ? keys : [keys])
- .forEach(function(key){
- // XXX should we break or warn???
- if(key in button_keys){
- throw new Error(`renderItem(..): button key already used: ${key}`) }
- button_keys[keyboard.joinKey(keyboard.normalizeKey(key))] = button })
- // keep focus on the item containing the button -- i.e. if
- // we tab out of the item focus the item we get to...
- button.addEventListener('focus', function(){
- item.focused
- // only focus items in the main section,
- // outside of headers and footers...
- || that.focus(item)
- && button.focus() })
- // main button action (click/enter)...
- // XXX should there be a secondary action (i.e. shift-enter)???
- if(handler){
- var func = handler instanceof Function ?
- handler
- // string handler -> that.(item)
- : function(evt, ...args){
- var a = that.parseStringHandler(
- handler,
- // button handler arg namespace...
- {
- event: evt,
- item: item,
- // NOTE: if we are not focusing
- // on button click this may
- // be different from item...
- focused: that.focused,
- button: html,
- })
- that[a.action](...a.arguments) }
-
- // handle clicks and keyboard...
- button.addEventListener('click', func.bind(that))
- // NOTE: we only trigger buttons on Enter and do
- // not care about other keys...
- button.addEventListener('keydown',
- function(evt){
- var k = keyboard.event2key(evt)
- if(k.includes('Enter')){
- event.stopPropagation()
- func.call(that, evt, item) } }) }
- }
-
- elem.appendChild(button)
- })
-
- // button shortcut keys...
- Object.keys(button_keys).length > 0
- && elem.addEventListener('keydown',
- function(evt){
- var k = keyboard.joinKey(keyboard.event2key(evt))
- if(k in button_keys){
- evt.preventDefault()
- evt.stopPropagation()
- button_keys[k].focus()
- // XXX should this be optional???
- button_keys[k].click() } })
-
- item.dom = elem
- // XXX for some reason this messes up navigation...
- // to reproduce:
- // - select element with children
- // - press right
- // -> blur current elem
- // -> next elem not selected...
- //item.elem = elem
-
- return elem
- },
-
-
// Events extensions...
//
// NOTE: this will also kill any user-set keys for disabled/hidden items...