@@ -14,11 +14,13 @@ import { html } from "htm/preact";
1414import { BlissSymbol } from "./BlissSymbol" ;
1515import { changeEncodingContents } from "./GlobalData" ;
1616import { ContentBmwEncodingType , EncodingType } from "./index.d" ;
17- import { generateGridStyle } from "./GlobalUtils" ;
17+ import { generateGridStyle , clamp , speak } from "./GlobalUtils" ;
1818import "./ContentBmwEncoding.scss" ;
1919
2020export const INPUT_AREA_ID = "bmw-encoding-area" ; // better way?
2121
22+ const isApplePlatform = navigator . platform . startsWith ( "Mac" ) || navigator . platform . startsWith ( "iPhone" ) || navigator . platform . startsWith ( "iPad" ) ;
23+
2224type ContentBmwEncodingProps = {
2325 id : string ,
2426 options : ContentBmwEncodingType
@@ -66,6 +68,64 @@ function generateMarkupArray (payloadArray: Array<EncodingType>, caretPos: numbe
6668 } ) ;
6769}
6870
71+ function moveCursor ( positionChange : number = 1 ) {
72+ // Note: the new caretPosition can equal -1 indicating that the caret is before the
73+ // first symbol in the `payloads` array. But, it cannot be less than -1.
74+ const newPosition = clamp ( changeEncodingContents . value . caretPosition + positionChange , - 1 , changeEncodingContents . value . payloads . length - 1 ) ;
75+ changeEncodingContents . value = {
76+ payloads : changeEncodingContents . value . payloads ,
77+ caretPosition : newPosition
78+ } ;
79+ } ;
80+
81+ export function incrementCursor ( ) {
82+ moveCursor ( 1 ) ;
83+ }
84+
85+ export function decrementCursor ( ) {
86+ moveCursor ( - 1 ) ;
87+ }
88+
89+ export function moveCursorToHome ( ) {
90+ moveCursor ( Number . NEGATIVE_INFINITY ) ;
91+ } ;
92+
93+ export function moveCursorToEnd ( ) {
94+ moveCursor ( Number . POSITIVE_INFINITY ) ;
95+ } ;
96+
97+ function handleKeyDown ( event : KeyboardEvent ) {
98+ if ( ( ! ( isApplePlatform && event . metaKey ) && event . key === "ArrowLeft" ) || event . key === "ArrowDown" ) {
99+ decrementCursor ( ) ;
100+ speak ( "backward" ) ;
101+ }
102+
103+ if ( ( ! ( isApplePlatform && event . metaKey ) && event . key === "ArrowRight" ) || event . key === "ArrowUp" ) {
104+ incrementCursor ( ) ;
105+ speak ( "forward" ) ;
106+ }
107+
108+ if (
109+ event . key === "Home" ||
110+ ( event . ctrlKey && event . key === "a" ) ||
111+ ( isApplePlatform && event . metaKey && event . key === "ArrowLeft" )
112+ ) {
113+ event . preventDefault ( ) ;
114+ moveCursorToHome ( ) ;
115+ speak ( "move cursor to start" ) ;
116+ }
117+
118+ if (
119+ event . key === "End" ||
120+ ( event . ctrlKey && event . key === "e" ) ||
121+ ( isApplePlatform && event . metaKey && event . key === "ArrowRight" )
122+ ) {
123+ event . preventDefault ( ) ;
124+ moveCursorToEnd ( ) ;
125+ speak ( "move cursor to end" ) ;
126+ }
127+ }
128+
69129export function ContentBmwEncoding ( props : ContentBmwEncodingProps ) : VNode {
70130 const { id, options } = props ;
71131 const { columnStart, columnSpan, rowStart, rowSpan } = options ;
@@ -76,7 +136,15 @@ export function ContentBmwEncoding (props: ContentBmwEncodingProps): VNode {
76136 ) ;
77137
78138 return html `
79- < div id ="${ id } " class ="bmwEncodingArea " role ="textbox " aria-label ="Input Area " aria-readonly ="true " style ="${ gridStyles } ">
139+ < div
140+ id ="${ id } "
141+ class ="bmwEncodingArea "
142+ role ="textbox "
143+ aria-label ="Input Area "
144+ aria-readonly ="true "
145+ style ="${ gridStyles } "
146+ tabindex ="0 "
147+ onKeyDown =${ handleKeyDown } >
80148 ${ contentsMarkupArray }
81149 </ div >
82150 ` ;
0 commit comments