「まともに動きゃしない」から「微妙に・・・」。(途中経過)
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>