focus and scroll into view now seems OK, not smooth though...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2023-10-23 14:37:14 +03:00
parent 90cdc0c3c7
commit b2a3173f8d
4 changed files with 64 additions and 26 deletions

View File

@ -7,7 +7,7 @@
:root { :root {
--font-size: 5mm; --font-size: 5mm;
--outline-padding: 5rem; --outline-padding: 7rem;
--item-indent: 2rem; --item-indent: 2rem;
--item-padding-ratio: 0.2; --item-padding-ratio: 0.2;
@ -54,8 +54,6 @@
display: block; display: block;
position: relative; position: relative;
width: calc(100% - var(--button-size) - var(--outline-padding) * 2);
padding: 1em var(--outline-padding); padding: 1em var(--outline-padding);
padding-bottom: 1.2em padding-bottom: 1.2em
} }

View File

@ -720,6 +720,7 @@ var Outline = {
// .get('visible') // .get('visible')
// .get('editable') // .get('editable')
// .get('selected') // .get('selected')
// .get('viewport')
// .get('top') // .get('top')
// -> <nodes> // -> <nodes>
// //
@ -739,7 +740,7 @@ var Outline = {
if(node == 'top'){ if(node == 'top'){
return [...outline.children] } return [...outline.children] }
// groups defaulting to .outline as base... // groups defaulting to .outline as base...
if(['all', 'visible', 'editable', 'selected'].includes(node)){ if(['all', 'visible', 'editable', 'selected', 'viewport'].includes(node)){
return this.get(outline, node) } return this.get(outline, node) }
// groups defaulting to .focused as base... // groups defaulting to .focused as base...
if(['parent', 'next', 'prev', 'children', 'siblings'].includes(node)){ if(['parent', 'next', 'prev', 'children', 'siblings'].includes(node)){
@ -806,6 +807,11 @@ var Outline = {
[...node.querySelectorAll('.block')] [...node.querySelectorAll('.block')]
.filter(function(e){ .filter(function(e){
return e.offsetParent != null }) return e.offsetParent != null })
: offset == 'viewport' ?
[...node.querySelectorAll('.block')]
.filter(function(e){
return e.offsetParent != null
&& e.querySelector('.code').visibleInViewport() })
: offset == 'editable' ? : offset == 'editable' ?
[...node.querySelectorAll('.block>.code')] [...node.querySelectorAll('.block>.code')]
: offset == 'selected' ? : offset == 'selected' ?
@ -840,13 +846,20 @@ var Outline = {
focus: function(node='focused', offset){ focus: function(node='focused', offset){
var elem = this.get(...arguments) var elem = this.get(...arguments)
?? this.get(0) ?? this.get(0)
elem if(elem){
&& elem.focus() elem.focus({preventScroll: true})
;(elem.classList.contains('code') ?
elem
: elem.querySelector('.code'))
.scrollIntoView({
block: 'nearest',
//behavior: 'smooth',
}) }
return elem }, return elem },
edit: function(node='focused', offset){ edit: function(node='focused', offset){
var elem = this.get(...arguments) var elem = this.get(...arguments)
if(elem.nodeName != 'TEXTAREA'){ if(!elem.classList.contains('code')){
elem = elem.querySelector('textarea') } elem = elem.querySelector('.code') }
elem?.focus() elem?.focus()
return elem }, return elem },
@ -1027,7 +1040,8 @@ var Outline = {
this.get(elem, 'prev') this.get(elem, 'prev')
: this.get(elem, 'next') } : this.get(elem, 'next') }
elem?.remove() elem?.remove()
next?.focus() next
&& this.focus(next)
this.__change__() this.__change__()
return this }, return this },
clear: function(){ clear: function(){
@ -1042,7 +1056,6 @@ var Outline = {
var stack = this.__crop_stack ??= [] var stack = this.__crop_stack ??= []
stack.push([this.json(), this.path()]) stack.push([this.json(), this.path()])
this.load(this.data()) this.load(this.data())
.focus()
return this }, return this },
// XXX use JSON API... // XXX use JSON API...
uncrop: function(){ uncrop: function(){
@ -1058,7 +1071,6 @@ var Outline = {
return res[i].children }, state) return res[i].children }, state)
.splice(path.at(-1), 1, ...this.json()) .splice(path.at(-1), 1, ...this.json())
this.load(state) this.load(state)
.focus()
return this }, return this },
// block render... // block render...
@ -1339,6 +1351,8 @@ var Outline = {
// update sizes of all the textareas (transparent)... // update sizes of all the textareas (transparent)...
for(var e of [...this.outline.querySelectorAll('textarea')]){ for(var e of [...this.outline.querySelectorAll('textarea')]){
e.updateSize() } e.updateSize() }
// restore focus...
this.focus()
return this }, return this },
sync: function(){ sync: function(){
@ -1485,7 +1499,6 @@ var Outline = {
this.edit( this.edit(
this.Block('next')) } }, this.Block('next')) } },
Enter: function(evt){ Enter: function(evt){
var edited = this.get('edited') var edited = this.get('edited')
// edit -> split text... // edit -> split text...
if(edited){ if(edited){
@ -1636,6 +1649,20 @@ var Outline = {
if(elem.classList.contains('block')){ if(elem.classList.contains('block')){
elem.querySelector('.code').focus() } elem.querySelector('.code').focus() }
// focus viewport...
// XXX this does not work because by this point there is
// no focused element...
if(elem === outline){
var cur = that.get()
var viewport = that.get('viewport')
if(!viewport.includes(cur)){
var visible = that.get('visible')
var i = visible.indexOf(cur)
var v = visible.indexOf(viewport[0])
i < v ?
that.focus(viewport[0])
: that.focus(viewport.at(-1)) } }
that.runPlugins('__click__', evt, that, elem) }) that.runPlugins('__click__', evt, that, elem) })
// keyboard handling... // keyboard handling...
outline.addEventListener('keydown', outline.addEventListener('keydown',
@ -1666,26 +1693,20 @@ var Outline = {
return } return }
// handle focus... // handle focus...
for(var e of [...that.dom.querySelectorAll('.focused')]){ if(elem !== that.outline){
e.classList.remove('focused') } for(var e of [...that.dom.querySelectorAll('.focused')]){
that.get('focused')?.classList?.add('focused') e.classList.remove('focused') }
that.get('focused')?.classList?.add('focused') }
// textarea... // textarea...
if(elem.classList.contains('code')){ if(elem.classList.contains('code')){
elem.updateSize() } elem.updateSize() }
/*/ scroll...
that.get(node).querySelector('view')
?.scrollIntoView({
block: 'nearest',
behavior: 'smooth',
})
//*/
// XXX do we need this??? // XXX do we need this???
that.runPlugins('__focusin__', evt, that, elem) }) that.runPlugins('__focusin__', evt, that, elem) })
outline.addEventListener('focusout', outline.addEventListener('focusout',
function(evt){ function(evt){
var elem = evt.target var elem = evt.target
// update code...
if(elem.classList.contains('code')){ if(elem.classList.contains('code')){
var block = elem.parentElement var block = elem.parentElement
// clean out attrs... // clean out attrs...
@ -1713,7 +1734,7 @@ var Outline = {
focus_textarea = document.activeElement.nodeName == 'TEXTAREA' } focus_textarea = document.activeElement.nodeName == 'TEXTAREA' }
var refocusNode = function(){ var refocusNode = function(){
focus_textarea ? focus_textarea ?
editor.get().querySelector('textarea').focus() editor.get().querySelector('.code').focus()
: editor.focus() : editor.focus()
focus_textarea = undefined } focus_textarea = undefined }
// cache the focused node type before focus changes... // cache the focused node type before focus changes...

View File

@ -4,6 +4,26 @@
* *
**********************************************************************/ **********************************************************************/
Element.prototype.visibleInViewport = function(partial=false){
var { top, left, bottom, right } = this.getBoundingClientRect()
var { innerHeight, innerWidth } = window
return partial
? ((top > 0
&& top < innerHeight)
|| (bottom > 0
&& bottom < innerHeight))
&& ((left > 0
&& left < innerWidth)
|| (right > 0
&& right < innerWidth))
: (top >= 0
&& left >= 0
&& bottom <= innerHeight
&& right <= innerWidth) }
//---------------------------------------------------------------------
HTMLTextAreaElement.prototype.updateSize = function(){ HTMLTextAreaElement.prototype.updateSize = function(){
this.style.height = '' this.style.height = ''
this.style.height = this.scrollHeight + 'px' this.style.height = this.scrollHeight + 'px'

View File

@ -46,7 +46,6 @@ var setup = function(){
- ## Bugs: - ## Bugs:
focused:: true focused:: true
- BUG: editor: FF seems to update the style every other key press -- should be live... - BUG: editor: FF seems to update the style every other key press -- should be live...
- BUG: scrolling into view needs tuning...
- BUG: mobile browsers behave quite chaotically ignoring parts of the styling... - BUG: mobile browsers behave quite chaotically ignoring parts of the styling...
- -
- ## ToDo: - ## ToDo:
@ -318,7 +317,7 @@ var setup = function(){
- Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text - Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text Lots of text
- </textarea> - </textarea>
<!-- outline --> <!-- outline -->
<div class="outline"></div> <div class="outline" tabindex="0"></div>
<!-- toolbar (optional) --> <!-- toolbar (optional) -->
<!--div class="toolbar"> <!--div class="toolbar">
<button onclick="editor.deindent().focus()">&lt;</button> <button onclick="editor.deindent().focus()">&lt;</button>