From d58c39126f6e1754e29b6f3bbc01f0f6a3ea2581 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Mon, 02 Jun 2014 10:35:12 -0400
Subject: [PATCH] Some more improvemements on content structure, text representation and keyboard navigation within the mail view

---
 program/js/list.js |   86 +++++++++++++++++++++++-------------------
 1 files changed, 47 insertions(+), 39 deletions(-)

diff --git a/program/js/list.js b/program/js/list.js
index fa37353..65d4a92 100644
--- a/program/js/list.js
+++ b/program/js/list.js
@@ -51,7 +51,7 @@
   this.rowcount = 0;
   this.colcount = 0;
 
-  this.subject_col = -1;
+  this.subject_col = 0;
   this.modkey = 0;
   this.multiselect = false;
   this.multiexpand = false;
@@ -111,15 +111,9 @@
     if (this.keyboard) {
       rcube_event.add_listener({event:'keydown', object:this, method:'key_press'});
 
-      // install a link element to receive focus.
-      // this helps to maintain the natural tab order when moving focus with keyboard
-      this.focus_elem = $('<a>')
-        .attr('tabindex', '0')
-        .attr('style', 'display:block; width:1px; height:1px; line-height:1px; overflow:hidden; position:fixed; top:-1000px')
-        .html('Select List')
-        .insertAfter(this.list)
-        .on('focus', function(e){ me.focus(e); })
-        .on('blur', function(e){ me.blur(e); });
+      // allow the table element to receive focus.
+      $(this.list).attr('tabindex', '0')
+        .on('focus', function(e){ me.focus(e); });
     }
   }
 
@@ -213,6 +207,7 @@
   if (!this.fixed_header) {
     this.fixed_header = $('<table>')
       .attr('class', this.list.className + ' fixedcopy')
+      .attr('role', 'presentation')
       .css({ position:'fixed' })
       .append(clone)
       .append('<tbody></tbody>');
@@ -279,11 +274,10 @@
 
   this.rows = {};
   this.rowcount = 0;
+  this.last_selected = 0;
 
   if (sel)
     this.clear_selection();
-  else
-    this.last_selected = 0;
 
   // reset scroll position (in Opera)
   if (this.frame)
@@ -392,23 +386,21 @@
   if (this.focused)
     return;
 
-  var n, id;
   this.focused = true;
-
-  for (n in this.selection) {
-    id = this.selection[n];
-    if (this.rows[id] && this.rows[id].obj) {
-      $(this.rows[id].obj).addClass('selected').removeClass('unfocused');
-    }
-  }
 
   if (e)
     rcube_event.cancel(e);
 
+  var focus_elem = null;
+
+  if (this.last_selected && this.rows[this.last_selected]) {
+    focus_elem = $(this.rows[this.last_selected].obj).find(this.col_tagname()).eq(this.subject_col).attr('tabindex', '0');
+  }
+
   // Un-focus already focused elements (#1487123, #1487316, #1488600, #1488620)
-  if (this.focus_elem) {
+  if (focus_elem && focus_elem.length) {
     // We now fix this by explicitly assigning focus to a dedicated link element
-    this.focus_noscroll(this.focus_elem);
+    this.focus_noscroll(focus_elem);
   }
   else {
     // It looks that window.focus() does the job for all browsers, but not Firefox (#1489058)
@@ -416,7 +408,7 @@
     window.focus();
   }
 
-  $(this.list).addClass('focus');
+  $(this.list).addClass('focus').removeAttr('tabindex');
 
   // set internal focus pointer to first row
   if (!this.last_selected)
@@ -429,16 +421,18 @@
  */
 blur: function(e)
 {
-  var n, id;
   this.focused = false;
-  for (n in this.selection) {
-    id = this.selection[n];
-    if (this.rows[id] && this.rows[id].obj) {
-      $(this.rows[id].obj).removeClass('selected focused').addClass('unfocused');
-    }
+
+  // avoid the table getting focus right again
+  var me = this;
+  setTimeout(function(){
+    $(me.list).removeClass('focus').attr('tabindex', '0');
+  }, 20);
+
+  if (this.last_selected && this.rows[this.last_selected]) {
+    $(this.rows[this.last_selected].obj)
+      .find(this.col_tagname()).eq(this.subject_col).removeAttr('tabindex');
   }
-  
-  $(this.list).removeClass('focus');
 },
 
 /**
@@ -929,16 +923,22 @@
   if (this.selection.join(',') != select_before)
     this.triggerEvent('select');
 
-  if (this.last_selected != 0 && this.rows[this.last_selected])
-    $(this.rows[this.last_selected].obj).removeClass('focused');
+  if (this.last_selected != 0 && this.rows[this.last_selected]) {
+    $(this.rows[this.last_selected].obj).removeClass('focused')
+      .find(this.col_tagname()).eq(this.subject_col).removeAttr('tabindex');
+  }
 
   // unselect if toggleselect is active and the same row was clicked again
   if (this.toggleselect && this.last_selected == id) {
     this.clear_selection();
     id = null;
   }
-  else
+  else {
     $(this.rows[id].obj).addClass('focused');
+    // set cursor focus to link inside selected row
+    if (this.focused)
+      this.focus_noscroll($(this.rows[id].obj).find(this.col_tagname()).eq(this.subject_col).attr('tabindex', '0'));
+  }
 
   if (!this.selection.length)
     this.shift_start = null;
@@ -1086,7 +1086,7 @@
       this.highlight_row(n, true, true);
     }
     else {
-      $(this.rows[n].obj).removeClass('selected').removeClass('unfocused');
+      $(this.rows[n].obj).removeClass('selected').removeAttr('aria-selected');
     }
   }
 
@@ -1143,7 +1143,7 @@
   else {
     for (n in this.selection)
       if (this.rows[this.selection[n]]) {
-        $(this.rows[this.selection[n]].obj).removeClass('selected').removeClass('unfocused');
+        $(this.rows[this.selection[n]].obj).removeClass('selected').removeAttr('aria-selected');
       }
 
     this.selection = [];
@@ -1206,13 +1206,13 @@
     if (this.selection.length > 1 || !this.in_selection(id)) {
       this.clear_selection(null, true);
       this.selection[0] = id;
-      $(this.rows[id].obj).addClass('selected');
+      $(this.rows[id].obj).addClass('selected').attr('aria-selected', 'true');
     }
   }
   else {
     if (!this.in_selection(id)) { // select row
       this.selection.push(id);
-      $(this.rows[id].obj).addClass('selected');
+      $(this.rows[id].obj).addClass('selected').attr('aria-selected', 'true');
       if (!norecur && !this.rows[id].expanded)
         this.highlight_children(id, true);
     }
@@ -1222,7 +1222,7 @@
         a_post = this.selection.slice(p+1, this.selection.length);
 
       this.selection = a_pre.concat(a_post);
-      $(this.rows[id].obj).removeClass('selected').removeClass('unfocused');
+      $(this.rows[id].obj).removeClass('selected').removeAttr('aria-selected');
       if (!norecur && !this.rows[id].expanded)
         this.highlight_children(id, false);
     }
@@ -1302,6 +1302,14 @@
 
       return rcube_event.cancel(e);
 
+    case 9: // Tab
+      this.blur();
+      break;
+
+    case 13: // Enter
+      if (!this.selection.length)
+        this.select_row(this.last_selected, mod_key, false);
+
     default:
       this.key_pressed = keyCode;
       this.modkey = mod_key;

--
Gitblit v1.9.1