SVGHMI: added dropdown selection highlighting and fixed scrolling so that it doesn't miss any entry while jumping from one page to the other. svghmi
authorEdouard Tisserant
Thu, 17 Dec 2020 19:31:00 +0100
branchsvghmi
changeset 3092 96ffd8b1b016
parent 3091 f475f39713aa
child 3093 9986e691c2ee
SVGHMI: added dropdown selection highlighting and fixed scrolling so that it doesn't miss any entry while jumping from one page to the other.
svghmi/gen_index_xhtml.xslt
svghmi/widget_dropdown.ysl2
tests/svghmi/svghmi_0@svghmi/svghmi.svg
--- a/svghmi/gen_index_xhtml.xslt	Wed Dec 16 15:44:24 2020 +0100
+++ b/svghmi/gen_index_xhtml.xslt	Thu Dec 17 19:31:00 2020 +0100
@@ -3084,6 +3084,10 @@
 </xsl:text>
     <xsl:text>            this.box_bbox = this.box_elt.getBBox()
 </xsl:text>
+    <xsl:text>            this.highlight_bbox = this.highlight_elt.getBBox()
+</xsl:text>
+    <xsl:text>            this.highlight_elt.style.visibility = "hidden";
+</xsl:text>
     <xsl:text>
 </xsl:text>
     <xsl:text>            // Compute margins
@@ -3228,7 +3232,7 @@
 </xsl:text>
     <xsl:text>                    let backup = first.getAttribute("dy");
 </xsl:text>
-    <xsl:text>                    // apply lift asr a dy added too first span (y attrib stays)
+    <xsl:text>                    // apply lift as a dy added too first span (y attrib stays)
 </xsl:text>
     <xsl:text>                    first.setAttribute("dy", "-"+String((this.lift+1)*1.1)+"em");
 </xsl:text>
@@ -3306,6 +3310,8 @@
 </xsl:text>
     <xsl:text>            this.reset_box();
 </xsl:text>
+    <xsl:text>            this.reset_highlight();
+</xsl:text>
     <xsl:text>            // Put the button back in place
 </xsl:text>
     <xsl:text>            this.element.appendChild(this.button_elt);
@@ -3326,289 +3332,359 @@
 </xsl:text>
     <xsl:text>            let txt = this.text_elt;
 </xsl:text>
+    <xsl:text>            let original_text_y = this.text_bbox.y;
+</xsl:text>
+    <xsl:text>            let highlight = this.highlight_elt;
+</xsl:text>
+    <xsl:text>            let original_h_y = this.highlight_bbox.y;
+</xsl:text>
+    <xsl:text>            let clickable = highlight.cloneNode();
+</xsl:text>
+    <xsl:text>            let yoffset = span.getBBox().y - original_text_y;
+</xsl:text>
+    <xsl:text>            clickable.y.baseVal.value = original_h_y + yoffset;
+</xsl:text>
+    <xsl:text>            clickable.style.pointerEvents = "bounding-box";
+</xsl:text>
+    <xsl:text>            //clickable.style.visibility = "hidden";
+</xsl:text>
+    <xsl:text>            //clickable.onclick = () =&gt; alert("love JS");
+</xsl:text>
+    <xsl:text>            clickable.onclick = func;
+</xsl:text>
+    <xsl:text>            this.element.appendChild(clickable);
+</xsl:text>
+    <xsl:text>            this.clickables.push(clickable)
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        reset_clickables() {
+</xsl:text>
+    <xsl:text>            while(this.clickables.length){
+</xsl:text>
+    <xsl:text>                this.element.removeChild(this.clickables.pop());
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        // Set text content when content is smaller than menu (no scrolling)
+</xsl:text>
+    <xsl:text>        set_complete_text(){
+</xsl:text>
+    <xsl:text>            let spans = this.text_elt.children;
+</xsl:text>
+    <xsl:text>            let c = 0;
+</xsl:text>
+    <xsl:text>            for(let item of this.content){
+</xsl:text>
+    <xsl:text>                let span=spans[c];
+</xsl:text>
+    <xsl:text>                span.textContent = item;
+</xsl:text>
+    <xsl:text>                let sel = c;
+</xsl:text>
+    <xsl:text>                this.make_clickable(span, (evt) =&gt; this.bound_on_selection_click(sel));
+</xsl:text>
+    <xsl:text>                c++;
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        // Move partial view :
+</xsl:text>
+    <xsl:text>        // false : upward, lower value
+</xsl:text>
+    <xsl:text>        // true  : downward, higher value
+</xsl:text>
+    <xsl:text>        scroll(forward){
+</xsl:text>
+    <xsl:text>            let contentlength = this.content.length;
+</xsl:text>
+    <xsl:text>            let spans = this.text_elt.children;
+</xsl:text>
+    <xsl:text>            let spanslength = spans.length;
+</xsl:text>
+    <xsl:text>            // reduce accounted menu size according to prsence of scroll buttons
+</xsl:text>
+    <xsl:text>            // since we scroll there is necessarly one button
+</xsl:text>
+    <xsl:text>            spanslength--;
+</xsl:text>
+    <xsl:text>            if(forward){
+</xsl:text>
+    <xsl:text>                // reduce accounted menu size because of back button
+</xsl:text>
+    <xsl:text>                if(this.menu_offset != 0) spanslength--;
+</xsl:text>
+    <xsl:text>                this.menu_offset = Math.min(
+</xsl:text>
+    <xsl:text>                    contentlength - spans.length + 1,
+</xsl:text>
+    <xsl:text>                    this.menu_offset + spanslength);
+</xsl:text>
+    <xsl:text>            }else{
+</xsl:text>
+    <xsl:text>                if(this.menu_offset - spanslength &gt; 0) spanslength--;
+</xsl:text>
+    <xsl:text>                this.menu_offset = Math.max(
+</xsl:text>
+    <xsl:text>                    0,
+</xsl:text>
+    <xsl:text>                    this.menu_offset - spanslength);
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>            if(this.menu_offset == 1)
+</xsl:text>
+    <xsl:text>                this.menu_offset = 0;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>            this.reset_highlight();
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>            this.reset_clickables();
+</xsl:text>
+    <xsl:text>            this.set_partial_text();
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>            this.highlight_selection();
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        // Setup partial view text content
+</xsl:text>
+    <xsl:text>        // with jumps at first and last entry when appropriate
+</xsl:text>
+    <xsl:text>        set_partial_text(){
+</xsl:text>
+    <xsl:text>            let spans = this.text_elt.children;
+</xsl:text>
+    <xsl:text>            let contentlength = this.content.length;
+</xsl:text>
+    <xsl:text>            let spanslength = spans.length;
+</xsl:text>
+    <xsl:text>            let i = this.menu_offset, c = 0;
+</xsl:text>
+    <xsl:text>            let m = this.box_bbox;
+</xsl:text>
+    <xsl:text>            while(c &lt; spanslength){
+</xsl:text>
+    <xsl:text>                let span=spans[c];
+</xsl:text>
+    <xsl:text>                let onclickfunc;
+</xsl:text>
+    <xsl:text>                // backward jump only present if not exactly at start
+</xsl:text>
+    <xsl:text>                if(c == 0 &amp;&amp; i != 0){
+</xsl:text>
+    <xsl:text>                    span.textContent = "&#x25B2;";
+</xsl:text>
+    <xsl:text>                    onclickfunc = this.bound_on_backward_click;
+</xsl:text>
+    <xsl:text>                    let o = span.getBBox();
+</xsl:text>
+    <xsl:text>                    span.setAttribute("dx", (m.width - o.width)/2);
+</xsl:text>
+    <xsl:text>                // presence of forward jump when not right at the end
+</xsl:text>
+    <xsl:text>                }else if(c == spanslength-1 &amp;&amp; i &lt; contentlength - 1){
+</xsl:text>
+    <xsl:text>                    span.textContent = "&#x25BC;";
+</xsl:text>
+    <xsl:text>                    onclickfunc = this.bound_on_forward_click;
+</xsl:text>
+    <xsl:text>                    let o = span.getBBox();
+</xsl:text>
+    <xsl:text>                    span.setAttribute("dx", (m.width - o.width)/2);
+</xsl:text>
+    <xsl:text>                // otherwise normal content
+</xsl:text>
+    <xsl:text>                }else{
+</xsl:text>
+    <xsl:text>                    span.textContent = this.content[i];
+</xsl:text>
+    <xsl:text>                    let sel = i;
+</xsl:text>
+    <xsl:text>                    onclickfunc = (evt) =&gt; this.bound_on_selection_click(sel);
+</xsl:text>
+    <xsl:text>                    span.removeAttribute("dx");
+</xsl:text>
+    <xsl:text>                    i++;
+</xsl:text>
+    <xsl:text>                }
+</xsl:text>
+    <xsl:text>                this.make_clickable(span, onclickfunc);
+</xsl:text>
+    <xsl:text>                c++;
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        open(){
+</xsl:text>
+    <xsl:text>            let length = this.content.length;
+</xsl:text>
+    <xsl:text>            // systematically reset text, to strip eventual whitespace spans
+</xsl:text>
+    <xsl:text>            this.reset_text();
+</xsl:text>
+    <xsl:text>            // grow as much as needed or possible
+</xsl:text>
+    <xsl:text>            let slots = this.grow_text(length);
+</xsl:text>
+    <xsl:text>            // Depending on final size
+</xsl:text>
+    <xsl:text>            if(slots == length) {
+</xsl:text>
+    <xsl:text>                // show all at once
+</xsl:text>
+    <xsl:text>                this.set_complete_text();
+</xsl:text>
+    <xsl:text>            } else {
+</xsl:text>
+    <xsl:text>                // eventualy align menu to current selection, compensating for lift
+</xsl:text>
+    <xsl:text>                let offset = this.last_selection - this.lift;
+</xsl:text>
+    <xsl:text>                if(offset &gt; 0)
+</xsl:text>
+    <xsl:text>                    this.menu_offset = Math.min(offset + 1, length - slots + 1);
+</xsl:text>
+    <xsl:text>                else
+</xsl:text>
+    <xsl:text>                    this.menu_offset = 0;
+</xsl:text>
+    <xsl:text>                // show surrounding values
+</xsl:text>
+    <xsl:text>                this.set_partial_text();
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>            // Now that text size is known, we can set the box around it
+</xsl:text>
+    <xsl:text>            this.adjust_box_to_text();
+</xsl:text>
+    <xsl:text>            // Take button out until menu closed
+</xsl:text>
+    <xsl:text>            this.element.removeChild(this.button_elt);
+</xsl:text>
+    <xsl:text>            // Rise widget to top by moving it to last position among siblings
+</xsl:text>
+    <xsl:text>            this.element.parentNode.appendChild(this.element.parentNode.removeChild(this.element));
+</xsl:text>
+    <xsl:text>            // disable interaction with background
+</xsl:text>
+    <xsl:text>            svg_root.addEventListener("pointerdown", numb_event, true);
+</xsl:text>
+    <xsl:text>            svg_root.addEventListener("pointerup", numb_event, true);
+</xsl:text>
+    <xsl:text>            svg_root.addEventListener("click", this.bound_close_on_click_elsewhere, true);
+</xsl:text>
+    <xsl:text>            this.highlight_selection();
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>            // mark as open
+</xsl:text>
+    <xsl:text>            this.opened = true;
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        // Put text element in normalized state
+</xsl:text>
+    <xsl:text>        reset_text(){
+</xsl:text>
+    <xsl:text>            let txt = this.text_elt;
+</xsl:text>
     <xsl:text>            let first = txt.firstElementChild;
 </xsl:text>
+    <xsl:text>            // remove attribute eventually added to first text line while opening
+</xsl:text>
+    <xsl:text>            first.onclick = null;
+</xsl:text>
+    <xsl:text>            first.removeAttribute("dy");
+</xsl:text>
+    <xsl:text>            first.removeAttribute("dx");
+</xsl:text>
+    <xsl:text>            // keep only the first line of text
+</xsl:text>
+    <xsl:text>            for(let span of Array.from(txt.children).slice(1)){
+</xsl:text>
+    <xsl:text>                txt.removeChild(span)
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        // Put rectangle element in saved original state
+</xsl:text>
+    <xsl:text>        reset_box(){
+</xsl:text>
+    <xsl:text>            let m = this.box_bbox;
+</xsl:text>
+    <xsl:text>            let b = this.box_elt;
+</xsl:text>
+    <xsl:text>            b.x.baseVal.value = m.x;
+</xsl:text>
+    <xsl:text>            b.y.baseVal.value = m.y;
+</xsl:text>
+    <xsl:text>            b.width.baseVal.value = m.width;
+</xsl:text>
+    <xsl:text>            b.height.baseVal.value = m.height;
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        highlight_selection(){
+</xsl:text>
+    <xsl:text>            let highlighted_row = this.last_selection - this.menu_offset;
+</xsl:text>
+    <xsl:text>            if(highlighted_row &lt; 0) return;
+</xsl:text>
+    <xsl:text>            let spans = this.text_elt.children;
+</xsl:text>
+    <xsl:text>            let spanslength = spans.length;
+</xsl:text>
+    <xsl:text>            let contentlength = this.content.length;
+</xsl:text>
+    <xsl:text>            if(this.menu_offset != 0) {
+</xsl:text>
+    <xsl:text>                spanslength--;
+</xsl:text>
+    <xsl:text>                highlighted_row++;
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>            if(this.menu_offset + spanslength &lt; contentlength - 1) spanslength--;
+</xsl:text>
+    <xsl:text>            if(highlighted_row &gt; spanslength) return;
+</xsl:text>
     <xsl:text>            let original_text_y = this.text_bbox.y;
 </xsl:text>
     <xsl:text>            let highlight = this.highlight_elt;
 </xsl:text>
-    <xsl:text>            let original_h_y = highlight.getBBox().y;
-</xsl:text>
-    <xsl:text>            let clickable = highlight.cloneNode();
+    <xsl:text>            let span = spans[highlighted_row];
 </xsl:text>
     <xsl:text>            let yoffset = span.getBBox().y - original_text_y;
 </xsl:text>
-    <xsl:text>            clickable.setAttribute("y", original_h_y + yoffset); 
-</xsl:text>
-    <xsl:text>            clickable.style.pointerEvents = "bounding-box";
-</xsl:text>
-    <xsl:text>            clickable.style.visibility = "hidden";
-</xsl:text>
-    <xsl:text>            //clickable.onclick = () =&gt; alert("love JS");
-</xsl:text>
-    <xsl:text>            clickable.onclick = func;
-</xsl:text>
-    <xsl:text>            this.element.appendChild(clickable);
-</xsl:text>
-    <xsl:text>            this.clickables.push(clickable)
-</xsl:text>
-    <xsl:text>        }
-</xsl:text>
-    <xsl:text>        reset_clickables() {
-</xsl:text>
-    <xsl:text>            while(this.clickables.length){
-</xsl:text>
-    <xsl:text>                this.element.removeChild(this.clickables.pop());
-</xsl:text>
-    <xsl:text>            }
-</xsl:text>
-    <xsl:text>        }
-</xsl:text>
-    <xsl:text>        // Set text content when content is smaller than menu (no scrolling)
-</xsl:text>
-    <xsl:text>        set_complete_text(){
-</xsl:text>
-    <xsl:text>            let spans = this.text_elt.children;
-</xsl:text>
-    <xsl:text>            let c = 0;
-</xsl:text>
-    <xsl:text>            for(let item of this.content){
-</xsl:text>
-    <xsl:text>                let span=spans[c];
-</xsl:text>
-    <xsl:text>                span.textContent = item;
-</xsl:text>
-    <xsl:text>                let sel = c;
-</xsl:text>
-    <xsl:text>                this.make_clickable(span, (evt) =&gt; this.bound_on_selection_click(sel));
-</xsl:text>
-    <xsl:text>                c++;
-</xsl:text>
-    <xsl:text>            }
-</xsl:text>
-    <xsl:text>        }
-</xsl:text>
-    <xsl:text>        // Move partial view :
-</xsl:text>
-    <xsl:text>        // false : upward, lower value
-</xsl:text>
-    <xsl:text>        // true  : downward, higher value
-</xsl:text>
-    <xsl:text>        scroll(forward){
-</xsl:text>
-    <xsl:text>            let contentlength = this.content.length;
-</xsl:text>
-    <xsl:text>            let spans = this.text_elt.children;
-</xsl:text>
-    <xsl:text>            let spanslength = spans.length;
-</xsl:text>
-    <xsl:text>            // reduce accounted menu size according to jumps
-</xsl:text>
-    <xsl:text>            if(this.menu_offset != 0) spanslength--;
-</xsl:text>
-    <xsl:text>            if(this.menu_offset &lt; contentlength - 1) spanslength--;
-</xsl:text>
-    <xsl:text>            if(forward){
-</xsl:text>
-    <xsl:text>                this.menu_offset = Math.min(
-</xsl:text>
-    <xsl:text>                    contentlength - spans.length + 1,
-</xsl:text>
-    <xsl:text>                    this.menu_offset + spanslength);
-</xsl:text>
-    <xsl:text>            }else{
-</xsl:text>
-    <xsl:text>                this.menu_offset = Math.max(
-</xsl:text>
-    <xsl:text>                    0,
-</xsl:text>
-    <xsl:text>                    this.menu_offset - spanslength);
-</xsl:text>
-    <xsl:text>            }
-</xsl:text>
-    <xsl:text>            this.reset_clickables();
-</xsl:text>
-    <xsl:text>            this.set_partial_text();
-</xsl:text>
-    <xsl:text>        }
-</xsl:text>
-    <xsl:text>        // Setup partial view text content
-</xsl:text>
-    <xsl:text>        // with jumps at first and last entry when appropriate
-</xsl:text>
-    <xsl:text>        set_partial_text(){
-</xsl:text>
-    <xsl:text>            let spans = this.text_elt.children;
-</xsl:text>
-    <xsl:text>            let contentlength = this.content.length;
-</xsl:text>
-    <xsl:text>            let spanslength = spans.length;
-</xsl:text>
-    <xsl:text>            let i = this.menu_offset, c = 0;
-</xsl:text>
-    <xsl:text>            let m = this.box_bbox;
-</xsl:text>
-    <xsl:text>            while(c &lt; spanslength){
-</xsl:text>
-    <xsl:text>                let span=spans[c];
-</xsl:text>
-    <xsl:text>                let onclickfunc;
-</xsl:text>
-    <xsl:text>                // backward jump only present if not exactly at start
-</xsl:text>
-    <xsl:text>                if(c == 0 &amp;&amp; i != 0){
-</xsl:text>
-    <xsl:text>                    span.textContent = "&#x25B2;";
-</xsl:text>
-    <xsl:text>                    onclickfunc = this.bound_on_backward_click;
-</xsl:text>
-    <xsl:text>                    let o = span.getBBox();
-</xsl:text>
-    <xsl:text>                    span.setAttribute("dx", (m.width - o.width)/2);
-</xsl:text>
-    <xsl:text>                // presence of forward jump when not right at the end
-</xsl:text>
-    <xsl:text>                }else if(c == spanslength-1 &amp;&amp; i &lt; contentlength - 1){
-</xsl:text>
-    <xsl:text>                    span.textContent = "&#x25BC;";
-</xsl:text>
-    <xsl:text>                    onclickfunc = this.bound_on_forward_click;
-</xsl:text>
-    <xsl:text>                    let o = span.getBBox();
-</xsl:text>
-    <xsl:text>                    span.setAttribute("dx", (m.width - o.width)/2);
-</xsl:text>
-    <xsl:text>                // otherwise normal content
-</xsl:text>
-    <xsl:text>                }else{
-</xsl:text>
-    <xsl:text>                    span.textContent = this.content[i];
-</xsl:text>
-    <xsl:text>                    let sel = i;
-</xsl:text>
-    <xsl:text>                    onclickfunc = (evt) =&gt; this.bound_on_selection_click(sel);
-</xsl:text>
-    <xsl:text>                    span.removeAttribute("dx");
-</xsl:text>
-    <xsl:text>                    i++;
-</xsl:text>
-    <xsl:text>                }
-</xsl:text>
-    <xsl:text>                this.make_clickable(span, onclickfunc);
-</xsl:text>
-    <xsl:text>                c++;
-</xsl:text>
-    <xsl:text>            }
-</xsl:text>
-    <xsl:text>        }
-</xsl:text>
-    <xsl:text>        open(){
-</xsl:text>
-    <xsl:text>            let length = this.content.length;
-</xsl:text>
-    <xsl:text>            // systematically reset text, to strip eventual whitespace spans
-</xsl:text>
-    <xsl:text>            this.reset_text();
-</xsl:text>
-    <xsl:text>            // grow as much as needed or possible
-</xsl:text>
-    <xsl:text>            let slots = this.grow_text(length);
-</xsl:text>
-    <xsl:text>            // Depending on final size
-</xsl:text>
-    <xsl:text>            if(slots == length) {
-</xsl:text>
-    <xsl:text>                // show all at once
-</xsl:text>
-    <xsl:text>                this.set_complete_text();
-</xsl:text>
-    <xsl:text>            } else {
-</xsl:text>
-    <xsl:text>                // eventualy align menu to current selection, compensating for lift
-</xsl:text>
-    <xsl:text>                let offset = this.last_selection - this.lift;
-</xsl:text>
-    <xsl:text>                if(offset &gt; 0)
-</xsl:text>
-    <xsl:text>                    this.menu_offset = Math.min(offset + 1, length - slots + 1);
-</xsl:text>
-    <xsl:text>                else
-</xsl:text>
-    <xsl:text>                    this.menu_offset = 0;
-</xsl:text>
-    <xsl:text>                // show surrounding values
-</xsl:text>
-    <xsl:text>                this.set_partial_text();
-</xsl:text>
-    <xsl:text>            }
-</xsl:text>
-    <xsl:text>            // Now that text size is known, we can set the box around it
-</xsl:text>
-    <xsl:text>            this.adjust_box_to_text();
-</xsl:text>
-    <xsl:text>            // Take button out until menu closed
-</xsl:text>
-    <xsl:text>            this.element.removeChild(this.button_elt);
-</xsl:text>
-    <xsl:text>            // Rise widget to top by moving it to last position among siblings
-</xsl:text>
-    <xsl:text>            this.element.parentNode.appendChild(this.element.parentNode.removeChild(this.element));
-</xsl:text>
-    <xsl:text>            // disable interaction with background
-</xsl:text>
-    <xsl:text>            svg_root.addEventListener("pointerdown", numb_event, true);
-</xsl:text>
-    <xsl:text>            svg_root.addEventListener("pointerup", numb_event, true);
-</xsl:text>
-    <xsl:text>            svg_root.addEventListener("click", this.bound_close_on_click_elsewhere, true);
-</xsl:text>
-    <xsl:text>            // mark as open
-</xsl:text>
-    <xsl:text>            this.opened = true;
-</xsl:text>
-    <xsl:text>        }
-</xsl:text>
-    <xsl:text>        // Put text element in normalized state
-</xsl:text>
-    <xsl:text>        reset_text(){
-</xsl:text>
-    <xsl:text>            let txt = this.text_elt;
-</xsl:text>
-    <xsl:text>            let first = txt.firstElementChild;
-</xsl:text>
-    <xsl:text>            // remove attribute eventually added to first text line while opening
-</xsl:text>
-    <xsl:text>            first.onclick = null;
-</xsl:text>
-    <xsl:text>            first.removeAttribute("dy");
-</xsl:text>
-    <xsl:text>            first.removeAttribute("dx");
-</xsl:text>
-    <xsl:text>            // keep only the first line of text
-</xsl:text>
-    <xsl:text>            for(let span of Array.from(txt.children).slice(1)){
-</xsl:text>
-    <xsl:text>                txt.removeChild(span)
-</xsl:text>
-    <xsl:text>            }
-</xsl:text>
-    <xsl:text>        }
-</xsl:text>
-    <xsl:text>        // Put rectangle element in saved original state
-</xsl:text>
-    <xsl:text>        reset_box(){
-</xsl:text>
-    <xsl:text>            let m = this.box_bbox;
-</xsl:text>
-    <xsl:text>            let b = this.box_elt;
-</xsl:text>
-    <xsl:text>            b.x.baseVal.value = m.x;
-</xsl:text>
-    <xsl:text>            b.y.baseVal.value = m.y;
-</xsl:text>
-    <xsl:text>            b.width.baseVal.value = m.width;
-</xsl:text>
-    <xsl:text>            b.height.baseVal.value = m.height;
+    <xsl:text>            highlight.y.baseVal.value = this.highlight_bbox.y + yoffset;
+</xsl:text>
+    <xsl:text>            highlight.style.visibility = "visible";
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        reset_highlight(){
+</xsl:text>
+    <xsl:text>            let highlight = this.highlight_elt;
+</xsl:text>
+    <xsl:text>            highlight.y.baseVal.value = this.highlight_bbox.y;
+</xsl:text>
+    <xsl:text>            highlight.style.visibility = "hidden";
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
--- a/svghmi/widget_dropdown.ysl2	Wed Dec 16 15:44:24 2020 +0100
+++ b/svghmi/widget_dropdown.ysl2	Thu Dec 17 19:31:00 2020 +0100
@@ -13,6 +13,8 @@
             this.button_elt.onclick = this.on_button_click.bind(this);
             // Save original size of rectangle
             this.box_bbox = this.box_elt.getBBox()
+            this.highlight_bbox = this.highlight_elt.getBBox()
+            this.highlight_elt.style.visibility = "hidden";
 
             // Compute margins
             this.text_bbox = this.text_elt.getBBox();
@@ -85,7 +87,7 @@
                 if(rect.bottom > bounds.bottom){
                     // in case of overflow at the bottom, lift up one row
                     let backup = first.getAttribute("dy");
-                    // apply lift asr a dy added too first span (y attrib stays)
+                    // apply lift as a dy added too first span (y attrib stays)
                     first.setAttribute("dy", "-"+String((this.lift+1)*1.1)+"em");
                     rect = txt.getBoundingClientRect();
                     if(rect.top > bounds.top){
@@ -124,6 +126,7 @@
             this.reset_text();
             this.reset_clickables();
             this.reset_box();
+            this.reset_highlight();
             // Put the button back in place
             this.element.appendChild(this.button_elt);
             // Mark as closed (to allow dispatch)
@@ -134,15 +137,14 @@
         // Make item (text span) clickable by overlaying a rectangle on top of it
         make_clickable(span, func) {
             let txt = this.text_elt;
-            let first = txt.firstElementChild;
             let original_text_y = this.text_bbox.y;
             let highlight = this.highlight_elt;
-            let original_h_y = highlight.getBBox().y;
+            let original_h_y = this.highlight_bbox.y;
             let clickable = highlight.cloneNode();
             let yoffset = span.getBBox().y - original_text_y;
-            clickable.setAttribute("y", original_h_y + yoffset); 
+            clickable.y.baseVal.value = original_h_y + yoffset;
             clickable.style.pointerEvents = "bounding-box";
-            clickable.style.visibility = "hidden";
+            //clickable.style.visibility = "hidden";
             //clickable.onclick = () => alert("love JS");
             clickable.onclick = func;
             this.element.appendChild(clickable);
@@ -172,20 +174,33 @@
             let contentlength = this.content.length;
             let spans = this.text_elt.children;
             let spanslength = spans.length;
-            // reduce accounted menu size according to jumps
-            if(this.menu_offset != 0) spanslength--;
-            if(this.menu_offset < contentlength - 1) spanslength--;
+            // reduce accounted menu size according to prsence of scroll buttons
+            // since we scroll there is necessarly one button
+            spanslength--;
             if(forward){
+                // reduce accounted menu size because of back button
+                // in current view
+                if(this.menu_offset > 0) spanslength--;
                 this.menu_offset = Math.min(
                     contentlength - spans.length + 1,
                     this.menu_offset + spanslength);
             }else{
+                // reduce accounted menu size because of back button
+                // in view once scrolled
+                if(this.menu_offset - spanslength > 0) spanslength--;
                 this.menu_offset = Math.max(
                     0,
                     this.menu_offset - spanslength);
             }
+            if(this.menu_offset == 1)
+                this.menu_offset = 0;
+
+            this.reset_highlight();
+
             this.reset_clickables();
             this.set_partial_text();
+
+            this.highlight_selection();
         }
         // Setup partial view text content
         // with jumps at first and last entry when appropriate
@@ -252,6 +267,8 @@
             svg_root.addEventListener("pointerdown", numb_event, true);
             svg_root.addEventListener("pointerup", numb_event, true);
             svg_root.addEventListener("click", this.bound_close_on_click_elsewhere, true);
+            this.highlight_selection();
+
             // mark as open
             this.opened = true;
         }
@@ -277,6 +294,30 @@
             b.width.baseVal.value = m.width;
             b.height.baseVal.value = m.height;
         }
+        highlight_selection(){
+            let highlighted_row = this.last_selection - this.menu_offset;
+            if(highlighted_row < 0) return;
+            let spans = this.text_elt.children;
+            let spanslength = spans.length;
+            let contentlength = this.content.length;
+            if(this.menu_offset != 0) {
+                spanslength--;
+                highlighted_row++;
+            }
+            if(this.menu_offset + spanslength < contentlength - 1) spanslength--;
+            if(highlighted_row > spanslength) return;
+            let original_text_y = this.text_bbox.y;
+            let highlight = this.highlight_elt;
+            let span = spans[highlighted_row];
+            let yoffset = span.getBBox().y - original_text_y;
+            highlight.y.baseVal.value = this.highlight_bbox.y + yoffset;
+            highlight.style.visibility = "visible";
+        }
+        reset_highlight(){
+            let highlight = this.highlight_elt;
+            highlight.y.baseVal.value = this.highlight_bbox.y;
+            highlight.style.visibility = "hidden";
+        }
         // Use margin and text size to compute box size
         adjust_box_to_text(){
             let [lmargin, tmargin] = this.margins;
--- a/tests/svghmi/svghmi_0@svghmi/svghmi.svg	Wed Dec 16 15:44:24 2020 +0100
+++ b/tests/svghmi/svghmi_0@svghmi/svghmi.svg	Thu Dec 17 19:31:00 2020 +0100
@@ -197,12 +197,12 @@
      inkscape:pageopacity="0"
      inkscape:pageshadow="2"
      inkscape:document-units="px"
-     inkscape:current-layer="g14237"
+     inkscape:current-layer="hmi0"
      showgrid="false"
      units="px"
      inkscape:zoom="0.54565796"
-     inkscape:cx="147.6698"
-     inkscape:cy="180.09341"
+     inkscape:cx="-623.8758"
+     inkscape:cy="172.76281"
      inkscape:window-width="1600"
      inkscape:window-height="836"
      inkscape:window-x="0"
@@ -2394,7 +2394,7 @@
   </g>
   <g
      id="g14237"
-     inkscape:label="HMI:DropDown:1:2:3:4:5:6:7:8:9:10@/SELECTION"
+     inkscape:label="HMI:DropDown:1:2:3:4:5:6:7:8:9:10:11:12:13:14:15:16:17:18:19:20:21:22:23:24:25:26:27@/SELECTION"
      transform="matrix(0.81491208,0,0,0.81491208,243.6641,-510.30491)"
      style="stroke-width:0.35083869">
     <rect