working on DOM navigation...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2019-06-04 02:55:53 +03:00
parent 14e711400a
commit 878c866f3f
4 changed files with 173 additions and 29 deletions

View File

@ -23,6 +23,7 @@ var collections = require('features/collections')
/*********************************************************************/ /*********************************************************************/
// XXX might be a good idea to normalize key spec values here...
var GLOBAL_KEYBOARD = var GLOBAL_KEYBOARD =
module.GLOBAL_KEYBOARD = { module.GLOBAL_KEYBOARD = {
// NOTE: the order of sections is important, it determines in what // NOTE: the order of sections is important, it determines in what
@ -417,6 +418,7 @@ module.GLOBAL_KEYBOARD = {
// marking... // marking...
M: 'toggleMark', M: 'toggleMark',
Ins: 'toggleMark',
ctrl_A: 'markRibbon!', ctrl_A: 'markRibbon!',
ctrl_shift_A: 'markLoaded!', ctrl_shift_A: 'markLoaded!',
ctrl_D: 'unmarkRibbon!', ctrl_D: 'unmarkRibbon!',

View File

@ -511,6 +511,8 @@ var KeyboardPrototype = {
return this.__keyboard instanceof Function ? return this.__keyboard instanceof Function ?
this.__keyboard() this.__keyboard()
: this.__keyboard }, : this.__keyboard },
// XXX might be a good idea to normalize the value here...
// ...i.e. normalize key specs as they are input by humans...
set keyboard(value){ set keyboard(value){
if(this.__keyboard instanceof Function){ if(this.__keyboard instanceof Function){
this.__keyboard(value) this.__keyboard(value)
@ -898,6 +900,8 @@ var KeyboardPrototype = {
// - shifted keys first // - shifted keys first
// - modifiers are skipped in order, left to right // - modifiers are skipped in order, left to right
// XXX carefully revise key search order... // XXX carefully revise key search order...
// XXX should we normalize what's in the bindings????
// ...currently we will match 'Ins' but not 'insert'
var keyCombinations = function(key, shift_key, remove_single_keys){ var keyCombinations = function(key, shift_key, remove_single_keys){
if(key.length <= 1){ if(key.length <= 1){
//return shift_key ? [key, shift_key] : [key] //return shift_key ? [key, shift_key] : [key]

View File

@ -3451,6 +3451,11 @@ var BrowserPrototype = {
// NOTE: when no element is selected, 'next' will select the // NOTE: when no element is selected, 'next' will select the
// first, while 'prev' the last element's // first, while 'prev' the last element's
// //
// Navigate to element above/below current element...
// .navigate('up')
// .navigate('down')
// -> elem
//
// Deselect element... // Deselect element...
// .navigate('none') // .navigate('none')
// -> elem // -> elem

View File

@ -1689,6 +1689,11 @@ var BaseBrowserPrototype = {
// -> item // -> item
// -> undefined // -> undefined
// //
// Get parent element relative to focused...
// .get('parent'[, func][, options])
// -> item
// -> undefined
//
// Get first item matching pattern... // Get first item matching pattern...
// .get(pattern[, func][, options]) // .get(pattern[, func][, options])
// -> item // -> item
@ -1756,12 +1761,57 @@ var BaseBrowserPrototype = {
b.length > offset b.length > offset
&& b.shift() }, && b.shift() },
options) options)
// get parent element...
: pattern == 'parent' ?
this.parentOf()
// base case -> get first match... // base case -> get first match...
: this.search(pattern, : this.search(pattern,
function(elem, i, path, stop){ function(elem, i, path, stop){
stop([func(elem, i, path)]) }, stop([func(elem, i, path)]) },
options) ].flat()[0] }, options) ].flat()[0] },
//
// Get parent of .focused
// .parentOf()
// .parentOf('focused'[, ..])
// -> parent
// -> this
// -> undefined
//
// Get parent of elem
// .parentOf(elem[, ..])
// -> parent
// -> this
// -> undefined
//
//
// Return values:
// - element - actual parent element
// - this - input element is at root of browser
// - undefined - element not found
//
//
// NOTE: this is signature compatible with .get(..) see that for more
// docs...
//
// XXX should this be a part of .get(..)???
parentOf: function(item, options){
var that = this
item = item || this.focused
var fargs = [...arguments].slice(1)
var args = fargs[0] instanceof Function ?
fargs.slice(1)
: fargs
return item ?
this.get(item,
function(e, i, p){
return p.length > 1 ?
that.get(p.slice(0, -1), ...fargs)
: that },
...args)
: undefined },
// Sublist map functions... // Sublist map functions...
// XXX this does not include inlined sections, should it??? // XXX this does not include inlined sections, should it???
@ -2153,6 +2203,8 @@ var BaseBrowserPrototype = {
// of actual rendering should lay on the renderer methods... // of actual rendering should lay on the renderer methods...
// NOTE: currently options and context are distinguished only via // NOTE: currently options and context are distinguished only via
// the .options attribute... // the .options attribute...
//
// XXX use partial render for things like search....
render: function(options, renderer, context){ render: function(options, renderer, context){
context = context || {} context = context || {}
renderer = renderer || this renderer = renderer || this
@ -2723,6 +2775,7 @@ var BrowserPrototype = {
}, },
}, },
// XXX STUB...
__keyboard_config: { __keyboard_config: {
General: { General: {
pattern: '*', pattern: '*',
@ -2733,13 +2786,14 @@ var BrowserPrototype = {
Down: 'next', Down: 'next',
// XXX use left/right... // XXX use left/right...
Left: 'collapse', Left: 'left',
Right: 'expand', Right: 'right',
Enter: 'open', Enter: 'open',
}, },
}, },
//__keyboard_config: null, //__keyboard_config: null,
get keybindings(){ get keybindings(){
return this.__keyboard_config }, return this.__keyboard_config },
@ -2790,9 +2844,11 @@ var BrowserPrototype = {
// Element renderers... // Element renderers...
// //
// This does tow additional things: // This also does:
// - save the rendered state to .dom // - save the rendered state to .dom
// - wrap a list of nodes (nested list) in a div // - wrap a list of nodes (nested list) in a div
// - setup event handling
// - init state...
// //
// Format: // Format:
// if list of items passed: // if list of items passed:
@ -2813,16 +2869,26 @@ var BrowserPrototype = {
c.appendChild(e) }) c.appendChild(e) })
d = c d = c
} }
d.setAttribute('tabindex', '0') d.setAttribute('tabindex', '0')
// XXX // XXX
d.addEventListener('keydown', d.addEventListener('keydown',
keyboard.makeKeyboardHandler(this.keyboard, keyboard.makePausableKeyboardHandler(this.keyboard,
function(){ console.log('KEY:', ...arguments) },//null, function(){ console.log('KEY:', ...arguments) },//null,
this)) this))
this.dom = d this.dom = d
// keep focus where it is...
var focused = this.focused
focused
&& (focused.dom.classList.contains('list') ?
focused.dom.querySelector('.item')
: focused.dom)
// XXX this will trigger the focus event...
// ...can we do this without triggering new events???
.focus()
return this.dom return this.dom
}, },
// //
@ -3057,7 +3123,11 @@ var BrowserPrototype = {
//elem.addEventListener('tap', //elem.addEventListener('tap',
// function(){ $(elem).trigger('open', [text, item, elem]) }) // function(){ $(elem).trigger('open', [text, item, elem]) })
elem.addEventListener('focus', elem.addEventListener('focus',
function(){ that.focus(item) }) function(){
// do not retrigger focus on an item if it's already focused...
// XXX do we handle focus after blur???
that.focused !== item
&& that.focus(item) })
// user events... // user events...
Object.entries(item.events || {}) Object.entries(item.events || {})
// shorthand events... // shorthand events...
@ -3099,38 +3169,101 @@ var BrowserPrototype = {
}, },
// Custom events... // Custom events handlers...
// 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...
__focus__: function(evt, elem){ __focus__: function(evt, elem){
elem.dom.classList.contains('list') ? ;(elem.dom.classList.contains('list') ?
elem.dom.querySelector('.item').focus() elem.dom.querySelector('.item')
: elem.dom.focus() }, : elem.dom)
__select__: function(){}, .focus() },
__deselect__: function(){},
__expand__: function(){ // XXX add support for pixel offset...
this.focused // XXX
&& this.focus(this.focused) }, get: function(pattern){
__collapse__: function(){ var p = pattern
this.focused pattern = arguments[0] =
&& this.focus(this.focused) }, // DOM element...
// XXX should we also check for content???
pattern instanceof HTMLElement ?
function(e){ return e.dom === p }
// jQuery object...
// XXX should we also check for content???
: (typeof(jQuery) != 'undefined' && pattern instanceof jQuery) ?
function(e){ return p.is(e.dom) }
: pattern
return pattern == 'pagetop' ?
// XXX
false
: pattern == 'pagebottom' ?
// XXX
false
// call parent...
: object.parent(BrowserPrototype.get, this).call(this, ...arguments) },
// Navigation... // Navigation...
// //
// hold key repeat on first/last elements...
next: function(){
object.parent(BrowserPrototype.next, this).call(this, ...arguments)
// hold repeat at last element...
this.focused === this.get('last')
&& this.keyboard.pauseRepeat
&& this.keyboard.pauseRepeat() },
prev: function(){
object.parent(BrowserPrototype.prev, this).call(this, ...arguments)
// hold repeat at first element...
this.focused === this.get('first')
&& this.keyboard.pauseRepeat
&& this.keyboard.pauseRepeat() },
// XXX focus element above/below...
up: function(){}, up: function(){},
down: function(){}, down: function(){},
left: function(){}, // XXX check if there are elements to the left...
right: function(){}, left: function(){
var focused = this.focused
var p
if(!focused){
return this.prev() }
// collapsable -> collapse...
;(focused.children && !focused.collapsed) ?
this.collapse()
// on a nested level -> go up one level...
: (p = this.parentOf()) && p !== this ?
this.focus(p)
// prev...
: this.prev()
},
// XXX check if there are elements to the right...
right: function(){
var focused = this.focused
if(!focused){
return this.next() }
focused.collapsed ?
this
.expand()
.next()
: this.next() },
//next: function(){}, // navigation relative to page...
//prev: function(){}, pageTop: function(){
this.focus(this.get('pagetop')) },
pageBottom: function(){
this.focus(this.get('pagebottom')) },
// XXX
pageUp: function(){
var ref = this.get('pagetop')
// XXX get element closest to pageHeight above top...
var target = null
this.scrollTo(target)
},
// XXX should we scroll to the bottom elem (current behavior) or to the one after it???
pageDown: function(){
this.scrollTo(this.get('pagebottom')) },
//collapse: function(){},
// XXX scroll... // XXX scroll...
scrollTo: function(elem){
},
} }