From f9f931a6d0101b2688254728950ea32e71e7e2cf Mon Sep 17 00:00:00 2001 From: dtbuild Date: Thu, 31 Oct 2024 17:20:14 +0000 Subject: [PATCH] 83295cd30fd68d26b7ce900d387a32ffc0976a50 New: `-init select.keys` option - enabled row navigation and selection by keyboard of a DataTable. Also includes supporting methods, types and example. 4ec3a6891012f01409e740c68221473f80c78266 Merge branch 'master' of github.com:DataTables/Select Sync to source repo @4ec3a6891012f01409e740c68221473f80c78266 --- datatables.json | 2 +- js/dataTables.select.js | 165 +++++++++++++++++++++++++++++++++++ js/dataTables.select.min.js | 2 +- js/dataTables.select.min.mjs | 2 +- js/dataTables.select.mjs | 165 +++++++++++++++++++++++++++++++++++ types/types.d.ts | 32 ++++++- 6 files changed, 363 insertions(+), 5 deletions(-) diff --git a/datatables.json b/datatables.json index dd6a9eb..4575862 100644 --- a/datatables.json +++ b/datatables.json @@ -11,5 +11,5 @@ ], "src-repo": "http://github.com/DataTables/Select", "last-tag": "2.1.0", - "last-sync": "e880efbe6582516f7eece94926a5de6814e7729b" + "last-sync": "4ec3a6891012f01409e740c68221473f80c78266" } \ No newline at end of file diff --git a/js/dataTables.select.js b/js/dataTables.select.js index 7f7adda..929243a 100644 --- a/js/dataTables.select.js +++ b/js/dataTables.select.js @@ -132,6 +132,7 @@ DataTable.select.init = function (dt) { var className = 'selected'; var headerCheckbox = true; var setStyle = false; + var keys = false; ctx._select = { infoEls: [] @@ -187,6 +188,10 @@ DataTable.select.init = function (dt) { if (opts.selectable !== undefined) { selectable = opts.selectable; } + + if (opts.keys !== undefined) { + keys = opts.keys; + } } dt.select.selector(selector); @@ -195,6 +200,7 @@ DataTable.select.init = function (dt) { dt.select.blurable(blurable); dt.select.toggleable(toggleable); dt.select.info(info); + dt.select.keys(keys); dt.select.selectable(selectable); ctx._select.className = className; @@ -690,6 +696,137 @@ function initCheckboxHeader( dt, headerCheckbox ) { }); } +function keysSet(dt) { + var ctx = dt.settings()[0]; + var flag = ctx._select.keys; + + if (flag) { + // Need a tabindex of the `tr` elements to make them focusable by the browser + $(dt.rows({page: 'current'}).nodes()).attr('tabindex', 0); + + dt.on('draw.dts-keys', function () { + $(dt.rows({page: 'current'}).nodes()).attr('tabindex', 0); + }); + + // Listen on document for tab, up and down + $(document).on('keydown.dts-keys', function (e) { + var key = e.keyCode; + var active = document.activeElement; + + // Can't use e.key as it wasn't widely supported until 2017 + // 9 Tab + // 32 Space + // 38 ArrowUp + // 40 ArrowDown + if (! [9, 32, 38, 40].includes(key)) { + return; + } + + var nodes = dt.rows({page: 'current'}).nodes().toArray(); + var idx = nodes.indexOf(active); + var preventDefault = true; + + // Only take an action if a row has focus + if (idx === -1) { + return; + } + + if (key === 9) { + // Tab focus change + if (e.shift === false && idx === nodes.length - 1) { + keysPageDown(dt); + } + else if (e.shift === true && idx === 0) { + keysPageUp(dt); + } + else { + // Browser will do it for us + preventDefault = false; + } + } + else if (key === 32) { + // Row selection / deselection + var row = dt.row(active); + + if (row.selected()) { + row.deselect(); + } + else { + row.select(); + } + } + else if (key === 38) { + // Move up + if (idx > 0) { + nodes[idx-1].focus(); + } + else { + keysPageUp(dt); + } + } + else { + // Move down + if (idx < nodes.length -1) { + nodes[idx+1].focus(); + } + else { + keysPageDown(dt); + } + } + + if (preventDefault) { + e.preventDefault(); + } + }); + } + else { + // Stop the rows from being able to gain focus + $(dt.rows().nodes()).removeAttr('tabindex'); + + // Nuke events + dt.off('draw.dts-keys'); + $(document).off('keydown.dts-keys'); + } +} + +/** + * Change to the next page and focus on the first row + * + * @param {DataTable.Api} dt DataTable instance + */ +function keysPageDown(dt) { + // Is there another page to turn to? + var info = dt.page.info(); + + if (info.page < info.pages - 1) { + dt + .one('draw', function () { + dt.row(':first-child').node().focus(); + }) + .page('next') + .draw(false); + } +} + +/** + * Change to the previous page and focus on the last row + * + * @param {DataTable.Api} dt DataTable instance + */ +function keysPageUp(dt) { + // Is there another page to turn to? + var info = dt.page.info(); + + if (info.page > 0) { + dt + .one('draw', function () { + dt.row(':last-child').node().focus(); + }) + .page('previous') + .draw(false); + } +} + /** * Determine the counts used to define the header checkbox's state * @@ -1143,6 +1280,18 @@ apiRegister('select.items()', function (items) { }); }); +apiRegister('select.keys()', function (flag) { + if (flag === undefined) { + return this.context[0]._select.keys; + } + + return this.iterator('table', function (ctx) { + ctx._select.keys = flag; + + keysSet(new DataTable.Api(ctx)); + }); +}); + // Takes effect from the _next_ selection. None disables future selection, but // does not clear the current selection. Use the `deselect` methods for that apiRegister('select.style()', function (style) { @@ -1306,6 +1455,22 @@ apiRegister('row().selected()', function () { return false; }); +apiRegister('row().focus()', function () { + var ctx = this.context[0]; + + if (ctx && this.length && ctx.aoData[this[0]] && ctx.aoData[this[0]].nTr) { + ctx.aoData[this[0]].nTr.focus(); + } +}); + +apiRegister('row().blur()', function () { + var ctx = this.context[0]; + + if (ctx && this.length && ctx.aoData[this[0]] && ctx.aoData[this[0]].nTr) { + ctx.aoData[this[0]].nTr.blur(); + } +}); + apiRegisterPlural('columns().select()', 'column().select()', function (select) { var api = this; diff --git a/js/dataTables.select.min.js b/js/dataTables.select.min.js index 7f78eb9..3014bb7 100644 --- a/js/dataTables.select.min.js +++ b/js/dataTables.select.min.js @@ -1,4 +1,4 @@ /*! Select for DataTables 2.1.0 * © SpryMedia Ltd - datatables.net/license/mit */ -!function(l){var s,c;"function"==typeof define&&define.amd?define(["jquery","datatables.net"],function(e){return l(e,window,document)}):"object"==typeof exports?(s=require("jquery"),c=function(e,t){t.fn.dataTable||require("datatables.net")(e,t)},"undefined"==typeof window?module.exports=function(e,t){return e=e||window,t=t||s(e),c(e,t),l(t,e,e.document)}:(c(window,s),module.exports=l(s,window,window.document))):l(jQuery,window,document)}(function(m,i,e){"use strict";var p=m.fn.dataTable;function r(n,e,t){function l(t,l){ls.indexOf(l)&&(e=l,l=t,t=e),!1);return s.filter(function(e){return e===t&&(c=!0),e===l?!(c=!1):c})}var c,t=n.cells({selected:!0}).any()||t?(c=l(t.column,e.column),s(t.row,e.row)):(c=l(0,e.column),s(0,e.row)),t=n.cells(t,c).flatten();n.cells(e,{selected:!0}).any()?n.cells(t).deselect():n.cells(t).select()}function v(e){var t=p.select.classes.checkbox;return e?t.replace(/ /g,"."):t}function c(e){var t=e.settings()[0]._select.selector;m(e.table().container()).off("mousedown.dtSelect",t).off("mouseup.dtSelect",t).off("click.dtSelect",t),m("body").off("click.dtSelect"+_(e.table().node()))}function n(o){var a,t=m(o.table().container()),l=o.settings()[0],s=l._select.selector;t.on("mousedown.dtSelect",s,function(e){(e.shiftKey||e.metaKey||e.ctrlKey)&&t.css("-moz-user-select","none").one("selectstart.dtSelect",s,function(){return!1}),i.getSelection&&(a=i.getSelection())}).on("mouseup.dtSelect",s,function(){t.css("-moz-user-select","")}).on("click.dtSelect",s,function(e){var t,l=o.select.items();if(a){var s=i.getSelection();if((!s.anchorNode||m(s.anchorNode).closest("table")[0]===o.table().node())&&s!==a)return}var c,s=o.settings()[0],n=o.table().container();m(e.target).closest("div.dt-container")[0]==n&&(n=o.cell(m(e.target).closest("td, th"))).any()&&(c=m.Event("user-select.dt"),u(o,c,[l,n,e]),c.isDefaultPrevented()||(c=n.index(),"row"===l?(t=c.row,h(e,o,s,"row",t)):"column"===l?(t=n.index().column,h(e,o,s,"column",t)):"cell"===l&&(t=n.index(),h(e,o,s,"cell",t)),s._select_lastCell=c))}),m("body").on("click.dtSelect"+_(o.table().node()),function(e){var t;!l._select.blurable||m(e.target).parents().filter(o.table().container()).length||0===m(e.target).parents("html").length||m(e.target).parents("div.DTE").length||(t=m.Event("select-blur.dt"),u(o,t,[e.target,e]),t.isDefaultPrevented())||f(l,!0)})}function u(e,t,l,s){s&&!e.flatten().length||("string"==typeof t&&(t+=".dt"),l.unshift(e),m(e.table().node()).trigger(t,l))}function b(e){return e.mRender&&"selectCheckbox"===e.mRender._name}function s(s,e){var t,l,c,n,o;"api"!==s.select.style()&&!1!==s.select.info()&&(o=s.settings()[0]._select_set.length||s.rows({selected:!0}).count(),t=s.columns({selected:!0}).count(),l=s.cells({selected:!0}).count(),c=function(e,t,l){e.append(m('').append(s.i18n("select."+t+"s",{_:"%d "+t+"s selected",0:"",1:"1 "+t+" selected"},l)))},e=m(e),c(n=m(''),"row",o),c(n,"column",t),c(n,"cell",l),(o=e.children("span.select-info")).length&&o.remove(),""!==n.text())&&e.append(n)}function o(o){var r,a=new p.Api(o);o._select_init=!0,o._select_set=[],o.aoRowCreatedCallback.push(function(e,t,l){var s,c,n=o.aoData[l],l=a.row(l).id();for((n._select_selected||"undefined"!==l&&o._select_set.includes(l))&&(n._select_selected=!0,m(e).addClass(o._select.className).find("input."+v(!0)).prop("checked",!0)),s=0,c=o.aoColumns.length;s").attr({class:v(!0),type:"checkbox","aria-label":c.i18n("select.aria.headerCheckbox")||"Select all rows"}).appendTo(t).on("change",function(){this.checked?("select-page"==n?c.rows({page:"current"}):c.rows({search:"applied"})).select():("select-page"==n?c.rows({page:"current",selected:!0}):c.rows({selected:!0})).deselect()}).on("click",function(e){e.stopPropagation()}),c.on("draw select deselect",function(e,t,l){"row"!==l&&l||((l=function(e,t){var l=e.settings()[0],s=l._select.selectable,c=0,n=("select-page"==t?e.rows({page:"current",selected:!0}):e.rows({selected:!0})).count(),o=("select-page"==t?e.rows({page:"current",selected:!0}):e.rows({search:"applied",selected:!0})).count();if(s)for(var a=("select-page"==t?e.rows({page:"current"}):e.rows({search:"applied"})).indexes(),i=0;i").attr({"aria-label":o,class:v(),name:r?r(l):null,type:"checkbox",value:i?i(l):null,checked:n}).on("input",function(e){e.preventDefault(),this.checked=m(this).closest("tr").hasClass("selected")})[0]}var i=e?p.util.get(e):null,r=t?p.util.get(t):null;return l._name="selectCheckbox",l},p.ext.order["select-checkbox"]=function(t,e){return this.api().column(e,{order:"index"}).nodes().map(function(e){return"row"===t._select.items?m(e).parent().hasClass(t._select.className).toString():"cell"===t._select.items&&m(e).hasClass(t._select.className).toString()})},m.fn.DataTable.select=p.select,m(e).on("i18n.dt.dtSelect preInit.dt.dtSelect",function(e,t){"dt"===e.namespace&&p.select.init(new p.Api(t))}),p}); \ No newline at end of file +!function(s){var l,c;"function"==typeof define&&define.amd?define(["jquery","datatables.net"],function(e){return s(e,window,document)}):"object"==typeof exports?(l=require("jquery"),c=function(e,t){t.fn.dataTable||require("datatables.net")(e,t)},"undefined"==typeof window?module.exports=function(e,t){return e=e||window,t=t||l(e),c(e,t),s(t,e,e.document)}:(c(window,l),module.exports=s(l,window,window.document))):s(jQuery,window,document)}(function(m,i,a){"use strict";var v=m.fn.dataTable;function r(n,e,t){function s(t,s){sl.indexOf(s)&&(e=s,s=t,t=e),!1);return l.filter(function(e){return e===t&&(c=!0),e===s?!(c=!1):c})}var c,t=n.cells({selected:!0}).any()||t?(c=s(t.column,e.column),l(t.row,e.row)):(c=s(0,e.column),l(0,e.row)),t=n.cells(t,c).flatten();n.cells(e,{selected:!0}).any()?n.cells(t).deselect():n.cells(t).select()}function w(e){var t=v.select.classes.checkbox;return e?t.replace(/ /g,"."):t}function c(e){var t=e.settings()[0]._select.selector;m(e.table().container()).off("mousedown.dtSelect",t).off("mouseup.dtSelect",t).off("click.dtSelect",t),m("body").off("click.dtSelect"+b(e.table().node()))}function n(o){var a,t=m(o.table().container()),s=o.settings()[0],l=s._select.selector;t.on("mousedown.dtSelect",l,function(e){(e.shiftKey||e.metaKey||e.ctrlKey)&&t.css("-moz-user-select","none").one("selectstart.dtSelect",l,function(){return!1}),i.getSelection&&(a=i.getSelection())}).on("mouseup.dtSelect",l,function(){t.css("-moz-user-select","")}).on("click.dtSelect",l,function(e){var t,s=o.select.items();if(a){var l=i.getSelection();if((!l.anchorNode||m(l.anchorNode).closest("table")[0]===o.table().node())&&l!==a)return}var c,l=o.settings()[0],n=o.table().container();m(e.target).closest("div.dt-container")[0]==n&&(n=o.cell(m(e.target).closest("td, th"))).any()&&(c=m.Event("user-select.dt"),u(o,c,[s,n,e]),c.isDefaultPrevented()||(c=n.index(),"row"===s?(t=c.row,p(e,o,l,"row",t)):"column"===s?(t=n.index().column,p(e,o,l,"column",t)):"cell"===s&&(t=n.index(),p(e,o,l,"cell",t)),l._select_lastCell=c))}),m("body").on("click.dtSelect"+b(o.table().node()),function(e){var t;!s._select.blurable||m(e.target).parents().filter(o.table().container()).length||0===m(e.target).parents("html").length||m(e.target).parents("div.DTE").length||(t=m.Event("select-blur.dt"),u(o,t,[e.target,e]),t.isDefaultPrevented())||_(s,!0)})}function u(e,t,s,l){l&&!e.flatten().length||("string"==typeof t&&(t+=".dt"),s.unshift(e),m(e.table().node()).trigger(t,s))}function g(e){return e.mRender&&"selectCheckbox"===e.mRender._name}function l(l,e){var t,s,c,n,o;"api"!==l.select.style()&&!1!==l.select.info()&&(o=l.settings()[0]._select_set.length||l.rows({selected:!0}).count(),t=l.columns({selected:!0}).count(),s=l.cells({selected:!0}).count(),c=function(e,t,s){e.append(m('').append(l.i18n("select."+t+"s",{_:"%d "+t+"s selected",0:"",1:"1 "+t+" selected"},s)))},e=m(e),c(n=m(''),"row",o),c(n,"column",t),c(n,"cell",s),(o=e.children("span.select-info")).length&&o.remove(),""!==n.text())&&e.append(n)}function d(e){var t=e.page.info();t.page").attr({class:w(!0),type:"checkbox","aria-label":c.i18n("select.aria.headerCheckbox")||"Select all rows"}).appendTo(t).on("change",function(){this.checked?("select-page"==n?c.rows({page:"current"}):c.rows({search:"applied"})).select():("select-page"==n?c.rows({page:"current",selected:!0}):c.rows({selected:!0})).deselect()}).on("click",function(e){e.stopPropagation()}),c.on("draw select deselect",function(e,t,s){"row"!==s&&s||((s=function(e,t){var s=e.settings()[0],l=s._select.selectable,c=0,n=("select-page"==t?e.rows({page:"current",selected:!0}):e.rows({selected:!0})).count(),o=("select-page"==t?e.rows({page:"current",selected:!0}):e.rows({search:"applied",selected:!0})).count();if(l)for(var a=("select-page"==t?e.rows({page:"current"}):e.rows({search:"applied"})).indexes(),i=0;i").attr({"aria-label":o,class:w(),name:r?r(s):null,type:"checkbox",value:i?i(s):null,checked:n}).on("input",function(e){e.preventDefault(),this.checked=m(this).closest("tr").hasClass("selected")})[0]}var i=e?v.util.get(e):null,r=t?v.util.get(t):null;return s._name="selectCheckbox",s},v.ext.order["select-checkbox"]=function(t,e){return this.api().column(e,{order:"index"}).nodes().map(function(e){return"row"===t._select.items?m(e).parent().hasClass(t._select.className).toString():"cell"===t._select.items&&m(e).hasClass(t._select.className).toString()})},m.fn.DataTable.select=v.select,m(a).on("i18n.dt.dtSelect preInit.dt.dtSelect",function(e,t){"dt"===e.namespace&&v.select.init(new v.Api(t))}),v}); \ No newline at end of file diff --git a/js/dataTables.select.min.mjs b/js/dataTables.select.min.mjs index f0f4054..08f5a33 100644 --- a/js/dataTables.select.min.mjs +++ b/js/dataTables.select.min.mjs @@ -1,4 +1,4 @@ /*! Select for DataTables 2.1.0 * © SpryMedia Ltd - datatables.net/license/mit */ -import jQuery from"jquery";import DataTable from"datatables.net";let $=jQuery;function cellRange(n,e,t){function l(t,l){ls.indexOf(l)&&(e=l,l=t,t=e),!1);return s.filter(function(e){return e===t&&(c=!0),e===l?!(c=!1):c})}var c,t=n.cells({selected:!0}).any()||t?(c=l(t.column,e.column),s(t.row,e.row)):(c=l(0,e.column),s(0,e.row)),t=n.cells(t,c).flatten();n.cells(e,{selected:!0}).any()?n.cells(t).deselect():n.cells(t).select()}function checkboxClass(e){var t=DataTable.select.classes.checkbox;return e?t.replace(/ /g,"."):t}function disableMouseSelection(e){var t=e.settings()[0]._select.selector;$(e.table().container()).off("mousedown.dtSelect",t).off("mouseup.dtSelect",t).off("click.dtSelect",t),$("body").off("click.dtSelect"+_safeId(e.table().node()))}function enableMouseSelection(a){var o,t=$(a.table().container()),l=a.settings()[0],s=l._select.selector;t.on("mousedown.dtSelect",s,function(e){(e.shiftKey||e.metaKey||e.ctrlKey)&&t.css("-moz-user-select","none").one("selectstart.dtSelect",s,function(){return!1}),window.getSelection&&(o=window.getSelection())}).on("mouseup.dtSelect",s,function(){t.css("-moz-user-select","")}).on("click.dtSelect",s,function(e){var t,l=a.select.items();if(o){var s=window.getSelection();if((!s.anchorNode||$(s.anchorNode).closest("table")[0]===a.table().node())&&s!==o)return}var c,s=a.settings()[0],n=a.table().container();$(e.target).closest("div.dt-container")[0]==n&&(n=a.cell($(e.target).closest("td, th"))).any()&&(c=$.Event("user-select.dt"),eventTrigger(a,c,[l,n,e]),c.isDefaultPrevented()||(c=n.index(),"row"===l?(t=c.row,typeSelect(e,a,s,"row",t)):"column"===l?(t=n.index().column,typeSelect(e,a,s,"column",t)):"cell"===l&&(t=n.index(),typeSelect(e,a,s,"cell",t)),s._select_lastCell=c))}),$("body").on("click.dtSelect"+_safeId(a.table().node()),function(e){var t;!l._select.blurable||$(e.target).parents().filter(a.table().container()).length||0===$(e.target).parents("html").length||$(e.target).parents("div.DTE").length||(t=$.Event("select-blur.dt"),eventTrigger(a,t,[e.target,e]),t.isDefaultPrevented())||clear(l,!0)})}function eventTrigger(e,t,l,s){s&&!e.flatten().length||("string"==typeof t&&(t+=".dt"),l.unshift(e),$(e.table().node()).trigger(t,l))}function isCheckboxColumn(e){return e.mRender&&"selectCheckbox"===e.mRender._name}function info(s,e){var t,l,c,n,a;"api"!==s.select.style()&&!1!==s.select.info()&&(a=s.settings()[0]._select_set.length||s.rows({selected:!0}).count(),t=s.columns({selected:!0}).count(),l=s.cells({selected:!0}).count(),c=function(e,t,l){e.append($('').append(s.i18n("select."+t+"s",{_:"%d "+t+"s selected",0:"",1:"1 "+t+" selected"},l)))},e=$(e),c(n=$(''),"row",a),c(n,"column",t),c(n,"cell",l),(a=e.children("span.select-info")).length&&a.remove(),""!==n.text())&&e.append(n)}function initCheckboxHeader(c,n){var l=c.settings()[0].aoColumns;c.columns().iterator("column",function(e,t){var s;isCheckboxColumn(l[t])&&(t=c.column(t).header(),$("input",t).length||(s=$("").attr({class:checkboxClass(!0),type:"checkbox","aria-label":c.i18n("select.aria.headerCheckbox")||"Select all rows"}).appendTo(t).on("change",function(){this.checked?("select-page"==n?c.rows({page:"current"}):c.rows({search:"applied"})).select():("select-page"==n?c.rows({page:"current",selected:!0}):c.rows({selected:!0})).deselect()}).on("click",function(e){e.stopPropagation()}),c.on("draw select deselect",function(e,t,l){"row"!==l&&l||((l=headerCheckboxState(c,n)).search&&l.search<=l.count&&l.search===l.available?s.prop("checked",!0).prop("indeterminate",!1):0===l.search&&0===l.count?s.prop("checked",!1).prop("indeterminate",!1):s.prop("checked",!1).prop("indeterminate",!0))})))})}function headerCheckboxState(e,t){var l=e.settings()[0],s=l._select.selectable,c=0,n=("select-page"==t?e.rows({page:"current",selected:!0}):e.rows({selected:!0})).count(),a=("select-page"==t?e.rows({page:"current",selected:!0}):e.rows({search:"applied",selected:!0})).count();if(s)for(var o=("select-page"==t?e.rows({page:"current"}):e.rows({search:"applied"})).indexes(),i=0;i").attr({"aria-label":a,class:checkboxClass(),name:r?r(l):null,type:"checkbox",value:i?i(l):null,checked:n}).on("input",function(e){e.preventDefault(),this.checked=$(this).closest("tr").hasClass("selected")})[0]}var i=e?DataTable.util.get(e):null,r=t?DataTable.util.get(t):null;return l._name="selectCheckbox",l},DataTable.ext.order["select-checkbox"]=function(t,e){return this.api().column(e,{order:"index"}).nodes().map(function(e){return"row"===t._select.items?$(e).parent().hasClass(t._select.className).toString():"cell"===t._select.items&&$(e).hasClass(t._select.className).toString()})},$.fn.DataTable.select=DataTable.select,$(document).on("i18n.dt.dtSelect preInit.dt.dtSelect",function(e,t){"dt"===e.namespace&&DataTable.select.init(new DataTable.Api(t))});export default DataTable; \ No newline at end of file +import jQuery from"jquery";import DataTable from"datatables.net";let $=jQuery;function cellRange(n,e,t){function l(t,l){ls.indexOf(l)&&(e=l,l=t,t=e),!1);return s.filter(function(e){return e===t&&(c=!0),e===l?!(c=!1):c})}var c,t=n.cells({selected:!0}).any()||t?(c=l(t.column,e.column),s(t.row,e.row)):(c=l(0,e.column),s(0,e.row)),t=n.cells(t,c).flatten();n.cells(e,{selected:!0}).any()?n.cells(t).deselect():n.cells(t).select()}function checkboxClass(e){var t=DataTable.select.classes.checkbox;return e?t.replace(/ /g,"."):t}function disableMouseSelection(e){var t=e.settings()[0]._select.selector;$(e.table().container()).off("mousedown.dtSelect",t).off("mouseup.dtSelect",t).off("click.dtSelect",t),$("body").off("click.dtSelect"+_safeId(e.table().node()))}function enableMouseSelection(a){var o,t=$(a.table().container()),l=a.settings()[0],s=l._select.selector;t.on("mousedown.dtSelect",s,function(e){(e.shiftKey||e.metaKey||e.ctrlKey)&&t.css("-moz-user-select","none").one("selectstart.dtSelect",s,function(){return!1}),window.getSelection&&(o=window.getSelection())}).on("mouseup.dtSelect",s,function(){t.css("-moz-user-select","")}).on("click.dtSelect",s,function(e){var t,l=a.select.items();if(o){var s=window.getSelection();if((!s.anchorNode||$(s.anchorNode).closest("table")[0]===a.table().node())&&s!==o)return}var c,s=a.settings()[0],n=a.table().container();$(e.target).closest("div.dt-container")[0]==n&&(n=a.cell($(e.target).closest("td, th"))).any()&&(c=$.Event("user-select.dt"),eventTrigger(a,c,[l,n,e]),c.isDefaultPrevented()||(c=n.index(),"row"===l?(t=c.row,typeSelect(e,a,s,"row",t)):"column"===l?(t=n.index().column,typeSelect(e,a,s,"column",t)):"cell"===l&&(t=n.index(),typeSelect(e,a,s,"cell",t)),s._select_lastCell=c))}),$("body").on("click.dtSelect"+_safeId(a.table().node()),function(e){var t;!l._select.blurable||$(e.target).parents().filter(a.table().container()).length||0===$(e.target).parents("html").length||$(e.target).parents("div.DTE").length||(t=$.Event("select-blur.dt"),eventTrigger(a,t,[e.target,e]),t.isDefaultPrevented())||clear(l,!0)})}function eventTrigger(e,t,l,s){s&&!e.flatten().length||("string"==typeof t&&(t+=".dt"),l.unshift(e),$(e.table().node()).trigger(t,l))}function isCheckboxColumn(e){return e.mRender&&"selectCheckbox"===e.mRender._name}function info(s,e){var t,l,c,n,a;"api"!==s.select.style()&&!1!==s.select.info()&&(a=s.settings()[0]._select_set.length||s.rows({selected:!0}).count(),t=s.columns({selected:!0}).count(),l=s.cells({selected:!0}).count(),c=function(e,t,l){e.append($('').append(s.i18n("select."+t+"s",{_:"%d "+t+"s selected",0:"",1:"1 "+t+" selected"},l)))},e=$(e),c(n=$(''),"row",a),c(n,"column",t),c(n,"cell",l),(a=e.children("span.select-info")).length&&a.remove(),""!==n.text())&&e.append(n)}function initCheckboxHeader(c,n){var l=c.settings()[0].aoColumns;c.columns().iterator("column",function(e,t){var s;isCheckboxColumn(l[t])&&(t=c.column(t).header(),$("input",t).length||(s=$("").attr({class:checkboxClass(!0),type:"checkbox","aria-label":c.i18n("select.aria.headerCheckbox")||"Select all rows"}).appendTo(t).on("change",function(){this.checked?("select-page"==n?c.rows({page:"current"}):c.rows({search:"applied"})).select():("select-page"==n?c.rows({page:"current",selected:!0}):c.rows({selected:!0})).deselect()}).on("click",function(e){e.stopPropagation()}),c.on("draw select deselect",function(e,t,l){"row"!==l&&l||((l=headerCheckboxState(c,n)).search&&l.search<=l.count&&l.search===l.available?s.prop("checked",!0).prop("indeterminate",!1):0===l.search&&0===l.count?s.prop("checked",!1).prop("indeterminate",!1):s.prop("checked",!1).prop("indeterminate",!0))})))})}function keysSet(a){a.settings()[0]._select.keys?($(a.rows({page:"current"}).nodes()).attr("tabindex",0),a.on("draw.dts-keys",function(){$(a.rows({page:"current"}).nodes()).attr("tabindex",0)}),$(document).on("keydown.dts-keys",function(e){var t,l,s,c=e.keyCode,n=document.activeElement;[9,32,38,40].includes(c)&&(s=!0,-1!==(l=(t=a.rows({page:"current"}).nodes().toArray()).indexOf(n)))&&(9===c?!1===e.shift&&l===t.length-1?keysPageDown(a):!0===e.shift&&0===l?keysPageUp(a):s=!1:32===c?(n=a.row(n)).selected()?n.deselect():n.select():38===c?0").attr({"aria-label":a,class:checkboxClass(),name:r?r(l):null,type:"checkbox",value:i?i(l):null,checked:n}).on("input",function(e){e.preventDefault(),this.checked=$(this).closest("tr").hasClass("selected")})[0]}var i=e?DataTable.util.get(e):null,r=t?DataTable.util.get(t):null;return l._name="selectCheckbox",l},DataTable.ext.order["select-checkbox"]=function(t,e){return this.api().column(e,{order:"index"}).nodes().map(function(e){return"row"===t._select.items?$(e).parent().hasClass(t._select.className).toString():"cell"===t._select.items&&$(e).hasClass(t._select.className).toString()})},$.fn.DataTable.select=DataTable.select,$(document).on("i18n.dt.dtSelect preInit.dt.dtSelect",function(e,t){"dt"===e.namespace&&DataTable.select.init(new DataTable.Api(t))});export default DataTable; \ No newline at end of file diff --git a/js/dataTables.select.mjs b/js/dataTables.select.mjs index 130ee47..7c0c898 100644 --- a/js/dataTables.select.mjs +++ b/js/dataTables.select.mjs @@ -92,6 +92,7 @@ DataTable.select.init = function (dt) { var className = 'selected'; var headerCheckbox = true; var setStyle = false; + var keys = false; ctx._select = { infoEls: [] @@ -147,6 +148,10 @@ DataTable.select.init = function (dt) { if (opts.selectable !== undefined) { selectable = opts.selectable; } + + if (opts.keys !== undefined) { + keys = opts.keys; + } } dt.select.selector(selector); @@ -155,6 +160,7 @@ DataTable.select.init = function (dt) { dt.select.blurable(blurable); dt.select.toggleable(toggleable); dt.select.info(info); + dt.select.keys(keys); dt.select.selectable(selectable); ctx._select.className = className; @@ -650,6 +656,137 @@ function initCheckboxHeader( dt, headerCheckbox ) { }); } +function keysSet(dt) { + var ctx = dt.settings()[0]; + var flag = ctx._select.keys; + + if (flag) { + // Need a tabindex of the `tr` elements to make them focusable by the browser + $(dt.rows({page: 'current'}).nodes()).attr('tabindex', 0); + + dt.on('draw.dts-keys', function () { + $(dt.rows({page: 'current'}).nodes()).attr('tabindex', 0); + }); + + // Listen on document for tab, up and down + $(document).on('keydown.dts-keys', function (e) { + var key = e.keyCode; + var active = document.activeElement; + + // Can't use e.key as it wasn't widely supported until 2017 + // 9 Tab + // 32 Space + // 38 ArrowUp + // 40 ArrowDown + if (! [9, 32, 38, 40].includes(key)) { + return; + } + + var nodes = dt.rows({page: 'current'}).nodes().toArray(); + var idx = nodes.indexOf(active); + var preventDefault = true; + + // Only take an action if a row has focus + if (idx === -1) { + return; + } + + if (key === 9) { + // Tab focus change + if (e.shift === false && idx === nodes.length - 1) { + keysPageDown(dt); + } + else if (e.shift === true && idx === 0) { + keysPageUp(dt); + } + else { + // Browser will do it for us + preventDefault = false; + } + } + else if (key === 32) { + // Row selection / deselection + var row = dt.row(active); + + if (row.selected()) { + row.deselect(); + } + else { + row.select(); + } + } + else if (key === 38) { + // Move up + if (idx > 0) { + nodes[idx-1].focus(); + } + else { + keysPageUp(dt); + } + } + else { + // Move down + if (idx < nodes.length -1) { + nodes[idx+1].focus(); + } + else { + keysPageDown(dt); + } + } + + if (preventDefault) { + e.preventDefault(); + } + }); + } + else { + // Stop the rows from being able to gain focus + $(dt.rows().nodes()).removeAttr('tabindex'); + + // Nuke events + dt.off('draw.dts-keys'); + $(document).off('keydown.dts-keys'); + } +} + +/** + * Change to the next page and focus on the first row + * + * @param {DataTable.Api} dt DataTable instance + */ +function keysPageDown(dt) { + // Is there another page to turn to? + var info = dt.page.info(); + + if (info.page < info.pages - 1) { + dt + .one('draw', function () { + dt.row(':first-child').node().focus(); + }) + .page('next') + .draw(false); + } +} + +/** + * Change to the previous page and focus on the last row + * + * @param {DataTable.Api} dt DataTable instance + */ +function keysPageUp(dt) { + // Is there another page to turn to? + var info = dt.page.info(); + + if (info.page > 0) { + dt + .one('draw', function () { + dt.row(':last-child').node().focus(); + }) + .page('previous') + .draw(false); + } +} + /** * Determine the counts used to define the header checkbox's state * @@ -1103,6 +1240,18 @@ apiRegister('select.items()', function (items) { }); }); +apiRegister('select.keys()', function (flag) { + if (flag === undefined) { + return this.context[0]._select.keys; + } + + return this.iterator('table', function (ctx) { + ctx._select.keys = flag; + + keysSet(new DataTable.Api(ctx)); + }); +}); + // Takes effect from the _next_ selection. None disables future selection, but // does not clear the current selection. Use the `deselect` methods for that apiRegister('select.style()', function (style) { @@ -1266,6 +1415,22 @@ apiRegister('row().selected()', function () { return false; }); +apiRegister('row().focus()', function () { + var ctx = this.context[0]; + + if (ctx && this.length && ctx.aoData[this[0]] && ctx.aoData[this[0]].nTr) { + ctx.aoData[this[0]].nTr.focus(); + } +}); + +apiRegister('row().blur()', function () { + var ctx = this.context[0]; + + if (ctx && this.length && ctx.aoData[this[0]] && ctx.aoData[this[0]].nTr) { + ctx.aoData[this[0]].nTr.blur(); + } +}); + apiRegisterPlural('columns().select()', 'column().select()', function (select) { var api = this; diff --git a/types/types.d.ts b/types/types.d.ts index f022dfb..a66e599 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -34,14 +34,24 @@ declare module 'datatables.net' { interface ApiRowMethods { /** - * Select a row + * Blur a row */ - select(): Api; + blur(): Api; /** * Deselect a row */ deselect(): Api; + + /** + * Keyboard focus on a row + */ + focus(): Api; + + /** + * Select a row + */ + select(): Api; } interface ApiRowsMethods { @@ -197,6 +207,9 @@ interface ConfigSelect { */ items?: string; + /** Set keyboard accessability (tab and arrow keys) */ + keys?: boolean; + /** * Set the element selector used for mouse event capture to select items */ @@ -261,6 +274,21 @@ interface ApiSelect { */ items(set: string): Api; + /** + * Get Select's keyboard navigation state + * + * @returns The keyboard navigation state of Select + */ + keys(): boolean; + + /** + * Set Select's keyboard navigation state + * + * @param set Enable (true) or disable (false) row keyboard navigation + * @returns DataTables API instance for chaining. + */ + keys(set: boolean): Api; + /** * Get the current item selector string applied to the table. *