「まともに動きゃしない」から「微妙に・・・」。(途中経過)

tabindex を渡り歩くのに、treeWalker を使い。
教えて頂いたのに使いこなせていないものを思い出し、てんこ盛り。
すご〜〜〜〜く、長考しています。
2011-01-27T10:50:00 ドラッグ&ドロップで親要素が同じなら、コピーと移動が取り合えず可能


「対応表」これが難題。「ひらめき★」がない!(涙)
className は、使わなかった。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML+ARIA 1.0//EN"
  "http://www.w3.org/WAI/ARIA/schemata/html4-aria-1.dtd">
<html lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <meta http-equiv="Content-Script-Type" content="text/javascript">
  <meta http-equiv="Content-Style-Type" content="text/css">
  
  <title>WAI ARIA のドラッグ&ドロップの実装</title>
  
  <style type="text/css">
  
  * [aria-grabbed="false"] { /*ドラッグ対象可能*/
    border: 3px #fdd solid !important;
  }

  * [aria-grabbed="false"]:focus { /*ドラッグ対象にフォーカスが当たったら*/
    border: 3px #f00 outset !important;
  }
  
  * [aria-grabbed="true"] { /*ドラッグ対象に選ばれている状態*/
    border: 3px #f0f dotted !important;
  }

  * [aria-grabbed="true"]:focus { /*ドラッグ対象に選ばれている状態で尚且つフォーカスがあたっている*/
    border: 3px #f0f inset !important;
  }
  
  * [aria-dropeffect]:focus { /*ドロップにフォーカスが当たっている*/
    border: 3px #00f inset !important;
  }
  
  ul.box {
    background :#eff;
    botder : 3px #dff solid;
    border : 3px #cff solid;
    height : 120px;
    margin : 0;
    padding: 0;
  }
  ul.box li {
    width : 100px;
    height : 100px;
    border : 3px #eee solid;
    display : block;
    float: left;
    font-size: 70%;
    margin: 3px;
  }
  ul.box li img {
    width: 40px;
    height: 40px;
  }
  pre:focus {
    background:#fdf;
  }
  </style>
<head>

<body>
  <h1>WAI ARIA のキーボードによるドラッグ&ドロップの実装</h1>
  <h2>動作サンプルの説明</h2>
  <ul>
    <li><em>ドラッグするには、</em> [Tab]:項目移動 / [Spe] :選択or解除 / [CTRL]+[Spc]: 複数選択 / [SHIFT]+[Spc]:範囲選択
    <li><em>ドロップするには、</em> [CTRL]+([M] || [Enter]) :決定 / [Esc]: 選択範囲解除
  </ul>
  <h2>ここから下のボックスに数字を移動します</h2>
  
  <ul class="box" aria-dropeffect="none">
    <li aria-grabbed="false" aria-controls="move" id="check01">
      <img src="./img/0.gif" alt="0" aria-grabbed=""><br>
      <p>文字だけ</p>
    </li>
    
    <li aria-grabbed="false" aria-controls="move" id="check02">
      <img src="./img/1.gif" alt="1"><br>
      <p aria-grabbed="">絵のみ</p>
    </li>
    
    <li aria-grabbed="false" aria-controls="move" id="check03">
      <img src="./img/2.gif" alt="2"><br>
      <p aria-grabbed="false">?文字と絵</p>
    </li>

    <li draggable="true"  aria-controls="move">
      <p>絵のみ</p>
    
    </li>
    
    
  </ul>
  
  <hr>
  
  <ul class="box" aria-dropeffect="none">
  
  </ul>
<input type="text" size="40" id="v" value="">


<script type="application/javascript;version=1.8">  

(function (view) {
  var doc = view.document;

  /*
  var grabbed = {
    default_selector : ''
    selector : 'aria-grabbed',
    enable   : function (e) { e.setAttribute (this.selector, 'true') },
    disable  : function (e) { e.setAttribute (this.selector, 'false') },
    state    : function (e) { e.getAttribute (this.selector) }
  };
  */

  var getParentGrabbed = function (n) { // 親要素のドラッグ対象要素を返す
    return n ? n === doc ? null: n.getAttribute ('aria-grabbed') ? n: arguments.callee (n.parentNode): null;
  };

  var drag = function (current, list) { // ドロップ処理
    var ptName = current.nodeName;

    list.forEach (function (obj) {
      var effect = obj.effect;
      var parent = obj.element.parentNode;
      var node = obj.element;
      var text;
      
      switch (effect) {
      case 'copy' :
        if (ptName === parent.nodeName) {
          current.appendChild (node);
        }
        else if ('value' in current) {
          current.value = node.textContent;
        }
        break;;
      case 'move' :
        if (ptName === parent.nodeName) {
          current.appendChild (node.cloneNode (true));
          obj.element.parentNode.removeChild (obj.element);
        }
        else if ('value' in current) {
          current.value = node.textContent;
          node.value = '';
        }
        break;
      
      case 'href' :
        if ('href' in current) {
          current.href = node.href;
        }
        else if ('value' in current) {
          current.value = node.href;
        }
        break;
      }

    });

    drop.clear (true);
  };


  var drop = function (current) {
    var selected = doc.querySelectorAll ('* [aria-grabbed="true"]');
    var list;
    
    if (selected.length) {
      list = Array.reduce (selected, function (a, e) {
        a.push ({ element: e, effect: e.getAttribute ('aria-controls') || 'copy' });
        return a;
      }, []);

      return drag (current, list);
    }
  };
  

  drop.clear = function (mode) {
    var last;
    
    Array.forEach (
      doc.querySelectorAll ('* [aria-grabbed="true"]'),
      function (e) { e.setAttribute ('aria-grabbed', 'false'); }
    );
    
    if (mode) {
      last = doc.querySelector ('* [aria-grabbed][aria-posinset="last"]');
      if (last)
        last.removeAttribute ('aria-posinset');
    }
  };


  drop.add = function (target, add, range) {
    var last = doc.querySelector ('* [aria-grabbed][aria-posinset="last"]') ||
               doc.querySelector ('* [aria-grabbed="false"]');

    if (range) {
      this.clear ();

      getTabindexRange (target, last).
        forEach (function (e) { e.setAttribute ('aria-grabbed', 'true'); });

      return;
    }
    else if (add);
    else if (last !== target)
      this.clear ();

    target.setAttribute (
      'aria-grabbed', String ('true' !== target.getAttribute ('aria-grabbed')));
    
    if (last)
      last.removeAttribute ('aria-posinset');

    target.setAttribute ('aria-posinset', 'last');
  };


//_____________


  // tabIndex の移動に、TreeWalker を利用
  var tabindex =
    (function (hasTabindex) {
       return doc.createTreeWalker (
         doc, NodeFilter.SHOW_ELEMENT, hasTabindex, false);
     })(function (n) {
          return (n.hasAttribute ('tabindex'))
            ? (n.getAttribute ('aria-grabbed') || n.getAttribute ('aria-dropeffect')) // aria-grabbed="" はパス
              ? NodeFilter.FILTER_ACCEPT
              : NodeFilter.FILTER_SKIP
            : NodeFilter.FILTER_SKIP;
        });

  // 次の tabindex へ
  moveTabindex = function (current, direction) {
    tabindex.currentNode = current;
    current = (direction) ? tabindex.previousNode (): tabindex.nextNode ();
    
    if (current)
      current.focus ();

    return current;
  };


  // start〜end までの
  getTabindexRange = function (start, end) {
    var result = [start];
    var e;
    
    if (start === end || ! end)
      return result;
    
    if (start.compareDocumentPosition (end) & 2) { // 前後を入れ替えるか?
      result[0] = end; end = start;
    }

    tabindex.currentNode = result[0];

    while (e = tabindex.nextNode ()) {
      if ('' !== e.getAttribute ('aria-grabbed'))
        result.push (e);
      
      if (e === end)
        return result;
    }
    throw new Error ();
  };

//_____________


  // img 要素と href属性を持つ a 要素に aria-grabbed, tabIndex属性をつける
  Array.slice (doc.querySelectorAll ('img, a[href], * [draggable="true"]')).
    filter (function (e) { return ! e.hasAttribute ('aria-grabbed'); }).
      forEach (function (e) { e.setAttribute ('aria-grabbed', 'false'); });
  
  Array.slice (doc.querySelectorAll ('* [aria-dropeffect], * [aria-grabbed]')).
    filter (function (e) { return ! e.hasAttribute ('tabindex'); }).
      forEach (function (e) { e.setAttribute ('tabindex', '-1'); });

  Array.slice (doc.querySelectorAll ('input[type="text"], textarea')).
    forEach (function (e) { e.setAttribute ('aria-dropeffect', 'none'); });

//_____________

  var onKeydown =
    function (evt) {
      var e = evt.target;
      var g = getParentGrabbed (e);
      var d = ('none' === e.getAttribute ('aria-dropeffect'));
      var key = evt.keyCode;
      
      switch (key) {
      case 9 :
        return moveTabindex (e, evt.shiftKey) ? evt.preventDefault (): null;
      case 77 : case 13 :
        return d && evt.ctrlKey && drop (e);
      case 27 :
        return (g || d) ? drop.clear (): null;
      case 32 :
        return g ? drop.add (g, evt.ctrlKey, evt.shiftKey): null;
      }
    };


  var onFocus =
    function (evt) { let(g = getParentGrabbed (evt.target)) g ? g.focus (): null};

  
  document.addEventListener ('keydown', onKeydown, false);
  document.addEventListener ('focus', onFocus, true);

})(this);


</script>