From c098e8e7457076be9ff148d42fc4b074a84c1f67 Mon Sep 17 00:00:00 2001
From: Kevin Jahns <kevin.jahns@rwth-aachen.de>
Date: Sat, 5 May 2018 14:47:59 +0200
Subject: [PATCH] implement scroll-fixer

---
 examples/html-editor/index.js           |  2 +-
 src/Bindings/DomBinding/DomBinding.js   | 12 +-------
 src/Bindings/DomBinding/domObserver.js  |  3 --
 src/Bindings/DomBinding/typeObserver.js | 37 +++++++++++++++++++++++++
 4 files changed, 39 insertions(+), 15 deletions(-)

diff --git a/examples/html-editor/index.js b/examples/html-editor/index.js
index 80623031..a13010a4 100644
--- a/examples/html-editor/index.js
+++ b/examples/html-editor/index.js
@@ -1,7 +1,7 @@
 /* global Y */
 
 window.onload = function () {
-  window.domBinding = new Y.DomBinding(window.yXmlType, document.body)
+  window.domBinding = new Y.DomBinding(window.yXmlType, document.body, { scrollingElement: document.scrollingElement })
 }
 
 let y = new Y('htmleditor', {
diff --git a/src/Bindings/DomBinding/DomBinding.js b/src/Bindings/DomBinding/DomBinding.js
index 1568f08d..b3ecb625 100644
--- a/src/Bindings/DomBinding/DomBinding.js
+++ b/src/Bindings/DomBinding/DomBinding.js
@@ -33,6 +33,7 @@ export default class DomBinding extends Binding {
     this.opts = opts
     opts.document = opts.document || document
     opts.hooks = opts.hooks || {}
+    this.scrollingElement = opts.scrollingElement || null
     /**
      * Maps each DOM element to the type that it is associated with.
      * @type {Map}
@@ -105,17 +106,6 @@ export default class DomBinding extends Binding {
     createAssociation(this, target, type)
   }
 
-  /**
-   * Enables the smart scrolling functionality for a Dom Binding.
-   * This is useful when YXml is bound to a shared editor. When activated,
-   * the viewport will be changed to accommodate remote changes.
-   *
-   * @param {Element} scrollElement The node that is
-   */
-  enableSmartScrolling (scrollElement) {
-    // @TODO: implement smart scrolling
-  }
-
   /**
    * NOTE: currently does not apply filter to existing elements!
    * @param {FilterFunction} filter The filter function to use from now on.
diff --git a/src/Bindings/DomBinding/domObserver.js b/src/Bindings/DomBinding/domObserver.js
index 8304c079..593510a5 100644
--- a/src/Bindings/DomBinding/domObserver.js
+++ b/src/Bindings/DomBinding/domObserver.js
@@ -125,9 +125,6 @@ export default function domObserver (mutations, _document) {
         }
       })
       for (let dom of diffChildren) {
-        if (dom.yOnChildrenChanged !== undefined) {
-          dom.yOnChildrenChanged()
-        }
         const yxml = this.domToType.get(dom)
         applyChangesFromDom(this, dom, yxml, _document)
       }
diff --git a/src/Bindings/DomBinding/typeObserver.js b/src/Bindings/DomBinding/typeObserver.js
index 1417cc4e..6b193159 100644
--- a/src/Bindings/DomBinding/typeObserver.js
+++ b/src/Bindings/DomBinding/typeObserver.js
@@ -1,13 +1,49 @@
+/* global getSelection */
 
 import YXmlText from '../../Types/YXml/YXmlText.js'
 import YXmlHook from '../../Types/YXml/YXmlHook.js'
 import { removeDomChildrenUntilElementFound } from './util.js'
 
+function findScrollReference (scrollingElement) {
+  if (scrollingElement !== null) {
+    let anchor = getSelection().anchorNode
+    if (anchor == null) {
+      let children = scrollingElement.children
+      for (let i = 0; i < children.length; i++) {
+        const elem = children[i]
+        const rect = elem.getBoundingClientRect()
+        if (rect.top >= 0) {
+          return { elem, top: rect.top }
+        }
+      }
+    } else {
+      if (anchor.nodeType === document.TEXT_NODE) {
+        anchor = anchor.parentElement
+      }
+      const top = anchor.getBoundingClientRect().top
+      return { elem: anchor, top: top }
+    }
+  }
+  return null
+}
+
+function fixScroll (scrollingElement, ref) {
+  if (ref !== null) {
+    const { elem, top } = ref
+    const currentTop = elem.getBoundingClientRect().top
+    const newScroll = scrollingElement.scrollTop + currentTop - top
+    if (newScroll >= 0) {
+      scrollingElement.scrollTop = newScroll
+    }
+  }
+}
+
 /**
  * @private
  */
 export default function typeObserver (events) {
   this._mutualExclude(() => {
+    const scrollRef = findScrollReference(this.scrollingElement)
     events.forEach(event => {
       const yxml = event.target
       const dom = this.typeToDom.get(yxml)
@@ -59,5 +95,6 @@ export default function typeObserver (events) {
         }
       }
     })
+    fixScroll(this.scrollingElement, scrollRef)
   })
 }