1+ /*
2+ Accessability TODOs:
3+
4+ - Implement all of the data grid keyboard commands in https://www.w3.org/WAI/ARIA/apg/patterns/grid/: Page Up, Page Down, Home, End, Ctrl+Home, Ctrl+End, Ctrl+Space, Shift+Space
5+
6+ */
7+
18const get_platform = ( ) => {
29 // userAgentData is not widely supported yet
310 if ( typeof navigator . userAgentData !== 'undefined' && navigator . userAgentData != null ) {
@@ -64,6 +71,48 @@ window.addEventListener("load", function() {
6471 . join ( "|" ) ;
6572
6673 let EventState = class {
74+
75+ /*
76+ A map of event states that you want to go full-screen to view because
77+ it's wide, but believe me, it's impossible to read when I wrote it as a
78+ tree:
79+
80+ | EventState | Event handlers active in this state | When we enter | When we leave |
81+ |-----------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------+------------------------------------------------------|
82+ | DocumentSelectMode | keydown -> function updateFromKeyEvent, function selectModeKeyboardShortcuts | function cellSelectMode | function cellEditMode |
83+ | | keyup -> function updateFromKeyEvent | function tableFocusIn | function loseFocus |
84+ | | click -> function loseFocus -> -DocumentSelectMode, +DocumentUnfocusedMode | function getFocus | |
85+ | | cut -> function cutSelection | | |
86+ | | copy -> function copySelection | | |
87+ | | paste -> function pasteSelection | | |
88+ |-----------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------+------------------------------------------------------|
89+ | DocumentEditMode | keydown -> function editModeKeyboardShortcuts | function cellEditMode | function cellSelectMode |
90+ |-----------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------+------------------------------------------------------|
91+ | DocumentUnfocusedMode | mousedown -> getFocus -> +DocumentSelectMode, -DocumentUnfocusedMode | function loseFocus | function tableFocusIn |
92+ | | | initialisation | function getFocus |
93+ |-----------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------+------------------------------------------------------|
94+ | TableSelectMode | selectstart -> function preventDefault | function cellSelectMode | function cellEditMode |
95+ | | mousedown -> function startDrag | function tableFocusIn | |
96+ | | | initialisation | |
97+ |-----------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------+------------------------------------------------------|
98+ | TableEditMode | | function cellEditMode | function cellSelectMode |
99+ |-----------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------+------------------------------------------------------|
100+ | TableUnfocusedMode | focusIn -> tableFocusIn -> -DocumentUnfocusedMode, -TableUnfocusedMode, +TableSelectMode, +DocumentSelectMode | function loseFocus | function tableFocusIn |
101+ | | | | function getFocus |
102+ |-----------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------+------------------------------------------------------|
103+ | InputSelectMode | mousedown -> function preventInputFocus, function removeFocus | function cellSelectMode (input elements inside cell) | function cellEditMode (input elements inside cell) |
104+ | | click -> function preventInputFocus | initialisation ("new row"/"new column" logic?!?) | |
105+ | | focus -> function enterCell | initialisation (all input elements in table) | |
106+ | | blur -> function leaveCell | | |
107+ |-----------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------+------------------------------------------------------|
108+ | InputEditMode | blur -> function leaveCell | function cellEditMode (input elements inside cell) | function cellSelectMode (input elements inside cell) |
109+ |-----------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------+------------------------------------------------------|
110+ | InputAliveState | input -> function autoResize, (function createNewRows/function createNewColumns?), function addRowClasses | initialisation ("new row"/"new column" logic?!?) | |
111+ | | change -> function autoResize, (function createNewRows/function createNewColumns?), function addRowClasses | initialisation (all input elements in table) | |
112+ |-----------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------+------------------------------------------------------|
113+
114+ */
115+
67116 constructor ( name ) {
68117 this . name = name ;
69118 this . events = [ ] ;
@@ -165,9 +214,9 @@ window.addEventListener("load", function() {
165214 } ;
166215
167216 get left ( ) { return CellRef . fromCoords ( this . table , this . row + 0 , this . col - 1 ) ; }
168- get right ( ) { return CellRef . fromCoords ( this . table , this . row + 0 , this . col + 1 ) ; }
217+ get right ( ) { return CellRef . fromCoords ( this . table , this . row + 0 , this . col + this . colspan ) ; }
169218 get above ( ) { return CellRef . fromCoords ( this . table , this . row - 1 , this . col + 0 ) ; }
170- get below ( ) { return CellRef . fromCoords ( this . table , this . row + 1 , this . col + 0 ) ; }
219+ get below ( ) { return CellRef . fromCoords ( this . table , this . row + this . rowspan , this . col + 0 ) ; }
171220
172221 equals ( cell ) {
173222 return this . table == cell . table &&
@@ -408,7 +457,6 @@ window.addEventListener("load", function() {
408457 // getRangeOfCells will do the CellRef.fromCoords lookup for every cell,
409458 // any of which might require a scan through the entire <table>.
410459
411-
412460 // Start by normalising min/max values
413461 let minRow = Math . min ( this . actualStartCell . row , this . actualEndCell . row ) ;
414462 let maxRow = Math . max ( this . actualStartCell . row , this . actualEndCell . row ) ;
@@ -605,6 +653,11 @@ window.addEventListener("load", function() {
605653
606654 var tables = document . querySelectorAll ( "table.selectable" ) ;
607655 for ( var table of tables ) {
656+ {
657+ const selectables = table . querySelectorAll ( 'td' ) ;
658+ selectables [ 0 ] . setAttribute ( "tabindex" , 0 ) ; // First one is tabbable, to get the user into the table
659+ }
660+
608661 var selection = NilSelection ;
609662
610663 const changeSelection = function ( startCell , endCell ) {
@@ -619,13 +672,25 @@ window.addEventListener("load", function() {
619672 for ( var cell of oldCells ) {
620673 for ( var className of CellSelectedClasses ) {
621674 cell . node . classList . remove ( className ) ;
675+ cell . node . setAttribute ( "aria-selected" , "false" ) ;
622676 }
623677 }
624678
679+ // Should just be one, but let's be sure
680+ var oldSelectables = table . querySelectorAll ( "[tabindex=\"0\"]" )
681+ for ( var cell of oldSelectables ) {
682+ cell . setAttribute ( "tabindex" , - 1 ) ;
683+ }
684+
625685 selection = newSelection ;
626686 if ( selection == NilSelection ) { return ; }
687+ selection . focus . node . focus ( ) ;
688+ selection . focus . node . setAttribute ( "tabindex" , 0 ) ;
627689 selection . focus . node . classList . add ( CellSelectedFocusClassName ) ;
628- for ( var cell of selection . cells ) { cell . node . classList . add ( CellSelectedClassName ) ; }
690+ for ( var cell of selection . cells ) {
691+ cell . node . classList . add ( CellSelectedClassName ) ;
692+ cell . node . setAttribute ( "aria-selected" , "truee" ) ;
693+ }
629694 for ( var cell of selection . bottomCells ) { cell . node . classList . add ( CellSelectedBottomClassName ) ; }
630695 for ( var cell of selection . topCells ) { cell . node . classList . add ( CellSelectedTopClassName ) ; }
631696 for ( var cell of selection . leftCells ) { cell . node . classList . add ( CellSelectedLeftClassName ) ; }
@@ -783,6 +848,7 @@ window.addEventListener("load", function() {
783848 }
784849
785850 var TableSelectMode = new EventState ( "select" ) ;
851+ var TableUnfocusedMode = new EventState ( "unfocused" ) ;
786852 var TableEditMode = new EventState ( "edit" ) ;
787853 var InputSelectMode = new EventState ( "select" ) ;
788854 var InputEditMode = new EventState ( "edit" ) ;
@@ -901,15 +967,16 @@ window.addEventListener("load", function() {
901967 }
902968
903969 var selectAll = function ( ) {
904- changeSelection ( TableSelection . fromElement ( table ) ) ;
970+ applySelection ( TableSelection . fromElement ( table ) ) ;
905971 }
906972
907973 // List of key codes that we think shouldn't trigger cell editing
908974 const ControlKeyCodes = Object . keys ( KeyGroups )
909975 . filter ( g => g != "Whitespace" && g != "IMEAndComposition" )
910976 . map ( k => KeyGroups [ k ] )
911977 . reduce ( ( acc , cur ) => acc . concat ( cur ) , [ ] )
912- . filter ( k => k != "Backspace" ) ;
978+ . filter ( k => k != "Backspace" )
979+ . concat ( "Tab" ) ;
913980
914981 var valueKeyPressed = function ( keyEvent ) {
915982 return ( ! ControlKeyCodes . includes ( keyEvent . key ) && ! keyEvent . ctrlKey && ! keyEvent . metaKey ) ;
@@ -925,14 +992,12 @@ window.addEventListener("load", function() {
925992 else if ( ! keyEvent . shiftKey && keyEvent . key == "ArrowRight" ) { changeSelection ( selection . focus . right ) ; keyEvent . preventDefault ( ) ; }
926993 else if ( ! keyEvent . shiftKey && keyEvent . key == "ArrowDown" ) { changeSelection ( selection . focus . below ) ; keyEvent . preventDefault ( ) ; }
927994 else if ( ! keyEvent . shiftKey && keyEvent . key == "ArrowUp" ) { changeSelection ( selection . focus . above ) ; keyEvent . preventDefault ( ) ; }
928- else if ( keyEvent . shiftKey && keyEvent . key == "ArrowLeft" ) { changeSelection ( selection . focus , selection . endCell . left ) ; keyEvent . preventDefault ( ) ; }
929- else if ( keyEvent . shiftKey && keyEvent . key == "ArrowRight" ) { changeSelection ( selection . focus , selection . endCell . right ) ; keyEvent . preventDefault ( ) ; }
930- else if ( keyEvent . shiftKey && keyEvent . key == "ArrowDown" ) { changeSelection ( selection . focus , selection . endCell . below ) ; keyEvent . preventDefault ( ) ; }
931- else if ( keyEvent . shiftKey && keyEvent . key == "ArrowUp" ) { changeSelection ( selection . focus , selection . endCell . above ) ; keyEvent . preventDefault ( ) ; }
995+ else if ( keyEvent . shiftKey && keyEvent . key == "ArrowLeft" ) { changeSelection ( selection . focus , selection . actualEndCell . left ) ; keyEvent . preventDefault ( ) ; }
996+ else if ( keyEvent . shiftKey && keyEvent . key == "ArrowRight" ) { changeSelection ( selection . focus , selection . actualEndCell . right ) ; keyEvent . preventDefault ( ) ; }
997+ else if ( keyEvent . shiftKey && keyEvent . key == "ArrowDown" ) { changeSelection ( selection . focus , selection . actualEndCell . below ) ; keyEvent . preventDefault ( ) ; }
998+ else if ( keyEvent . shiftKey && keyEvent . key == "ArrowUp" ) { changeSelection ( selection . focus , selection . actualEndCell . above ) ; keyEvent . preventDefault ( ) ; }
932999 else if ( ! keyEvent . shiftKey && keyEvent . key == "Enter" ) { applySelection ( selection . focusCursor . nextFocusByColumn ( ) ) ; keyEvent . preventDefault ( ) ; }
933- else if ( ! keyEvent . shiftKey && keyEvent . key == "Tab" ) { applySelection ( selection . focusCursor . nextFocusByRow ( ) ) ; keyEvent . preventDefault ( ) ; }
9341000 else if ( keyEvent . shiftKey && keyEvent . key == "Enter" ) { applySelection ( selection . focusCursor . prevFocusByColumn ( ) ) ; keyEvent . preventDefault ( ) ; }
935- else if ( keyEvent . shiftKey && keyEvent . key == "Tab" ) { applySelection ( selection . focusCursor . prevFocusByRow ( ) ) ; keyEvent . preventDefault ( ) ; }
9361001 else if ( valueKeyPressed ( keyEvent ) ) {
9371002 var input = selection . focus . node . querySelector ( InputElementsSelector ) ;
9381003 var event = new KeyboardEvent ( keyEvent . type , keyEvent ) ;
@@ -952,10 +1017,6 @@ window.addEventListener("load", function() {
9521017 cellSelectMode ( selection . focus ) ;
9531018 applySelection ( selection . focusCursor . nextFocusByColumn ( ) ) ;
9541019 }
955- if ( keyEvent . key == "Tab" ) {
956- cellSelectMode ( selection . focus ) ;
957- applySelection ( selection . focusCursor . nextFocusByRow ( ) ) ;
958- }
9591020 if ( keyEvent . key == "Escape" ) {
9601021 cellSelectMode ( selection . focus ) ;
9611022 }
@@ -986,14 +1047,14 @@ window.addEventListener("load", function() {
9861047 if ( event . target . closest ( "table" ) !== table ) {
9871048 DocumentSelectMode . leave ( document ) ;
9881049
989-
990- // If we not set the table's data-persist-selection attribute to "true" then we will apply
991- // the Nil selection when the table loses focus.
992- if ( ! table . dataset . persistSelection || table . dataset . persistSelection . toLowerCase ( ) != "true" ) {
993- applySelection ( NilSelection ) ;
994- }
1050+ // If we not set the table's data-persist-selection attribute to "true" then we will apply
1051+ // the Nil selection when the table loses focus.
1052+ if ( ! table . dataset . persistSelection || table . dataset . persistSelection . toLowerCase ( ) != "true" ) {
1053+ applySelection ( NilSelection ) ;
1054+ }
9951055
9961056 DocumentUnfocusedMode . enter ( document ) ;
1057+ TableUnfocusedMode . enter ( table ) ;
9971058 }
9981059 }
9991060 DocumentSelectMode . addEvent ( "click" , loseFocus ) ;
@@ -1002,8 +1063,25 @@ window.addEventListener("load", function() {
10021063 elementsOutsideTable . snapshotItem ( e ) . addEventListener ( "focus" , loseFocus ) ;
10031064 }
10041065
1066+ var tableFocusIn = function ( event ) {
1067+ var tableSelectables = table . querySelectorAll ( "[tabindex=\"0\"]" )
1068+ if ( tableSelectables [ 0 ] ) {
1069+ // If we don't have a focus, set it to the externally-focussed cell
1070+ if ( selection == NilSelection ) {
1071+ selection = TableSelection . fromElement ( tableSelectables [ 0 ] ) ;
1072+ applySelection ( selection ) ;
1073+ }
1074+ }
1075+ DocumentUnfocusedMode . leave ( document ) ;
1076+ TableUnfocusedMode . leave ( table ) ;
1077+ TableSelectMode . enter ( table ) ;
1078+ DocumentSelectMode . enter ( document ) ;
1079+ }
1080+ TableUnfocusedMode . addEvent ( "focusin" , tableFocusIn ) ;
1081+
10051082 var getFocus = function ( event ) {
10061083 DocumentUnfocusedMode . leave ( document ) ;
1084+ TableUnfocusedMode . leave ( table ) ;
10071085 DocumentSelectMode . enter ( document ) ;
10081086 }
10091087 DocumentUnfocusedMode . addEvent ( "mousedown" , getFocus ) ;
0 commit comments