@@ -9,6 +9,10 @@ import * as Hangul from "hangul-js";
99import * as Notifications from "../elements/notifications" ;
1010import * as ActivePage from "../states/active-page" ;
1111import * as TestWords from "../test/test-words" ;
12+ import { capsState } from "../test/caps-warning" ;
13+ import * as ShiftTracker from "../test/shift-tracker" ;
14+ import * as AltTracker from "../test/alt-tracker" ;
15+ import * as KeyConverter from "../utils/key-converter" ;
1216
1317const stenoKeys : JSONData . Layout = {
1418 keymapShowTopRow : true ,
@@ -402,6 +406,112 @@ export async function refresh(
402406 }
403407}
404408
409+ const isMacLike = / M a c | i P o d | i P h o n e | i P a d / . test ( navigator . platform ) ;
410+ const symbolsPattern = / ^ [ ^ \p{ L} \p{ N} ] { 1 } $ / u;
411+ type KeymapLegendStates = [ letters : 0 | 1 | 2 | 3 , symbols : 0 | 1 | 2 | 3 ] ;
412+ let keymapLegendStates : KeymapLegendStates = [ 0 , 0 ] ;
413+
414+ function getLegendStates ( ) : KeymapLegendStates | undefined {
415+ // MacOS has different CapsLock and Shift logic than other operating systems
416+ // Windows and Linux only capitalize letters if either Shift OR CapsLock are
417+ // pressed, but not both at once.
418+ // MacOS instead capitalizes when either or both are pressed,
419+ // so we have to check for that.
420+ const shiftState = ShiftTracker . leftState || ShiftTracker . rightState ;
421+ const altState = AltTracker . leftState || AltTracker . rightState ;
422+
423+ const osDependentLettersState = isMacLike
424+ ? shiftState || capsState
425+ : shiftState !== capsState ;
426+
427+ const lettersState = ( osDependentLettersState ? 1 : 0 ) + ( altState ? 2 : 0 ) ;
428+ const symbolsState = ( shiftState ? 1 : 0 ) + ( altState ? 2 : 0 ) ;
429+
430+ const [ previousLettersState , previousSymbolsState ] = keymapLegendStates ;
431+
432+ if (
433+ previousLettersState === lettersState &&
434+ previousSymbolsState === symbolsState
435+ ) {
436+ return ;
437+ }
438+
439+ keymapLegendStates = [
440+ lettersState as 0 | 1 | 2 | 3 ,
441+ symbolsState as 0 | 1 | 2 | 3 ,
442+ ] ;
443+ return keymapLegendStates ;
444+ }
445+
446+ async function updateLegends ( ) : Promise < void > {
447+ const states = getLegendStates ( ) ;
448+ if ( states === undefined ) return ;
449+
450+ const keymapKeys = [ ...document . getElementsByClassName ( "keymapKey" ) ] . filter (
451+ ( el ) => {
452+ const isKeymapKey = el . classList . contains ( "keymapKey" ) ;
453+ const isNotSpace = ! el . classList . contains ( "keySpace" ) ;
454+
455+ return isKeymapKey && isNotSpace ;
456+ }
457+ ) as HTMLElement [ ] ;
458+
459+ const layoutKeys = keymapKeys . map ( ( el ) => el . dataset [ "key" ] ) ;
460+ if ( layoutKeys . includes ( undefined ) ) return ;
461+
462+ const keys = keymapKeys . map ( ( el ) => el . childNodes [ 1 ] ) ;
463+
464+ const [ lettersState , symbolsState ] = states ;
465+
466+ const layoutName =
467+ Config . keymapLayout === "overrideSync"
468+ ? Config . layout === "default"
469+ ? "qwerty"
470+ : Config . layout
471+ : Config . keymapLayout ;
472+
473+ const layout = await JSONData . getLayout ( layoutName ) . catch ( ( ) => undefined ) ;
474+ if ( layout === undefined ) {
475+ Notifications . add ( "Failed to load keymap layout" , - 1 ) ;
476+
477+ return ;
478+ }
479+
480+ for ( let i = 0 ; i < layoutKeys . length ; i ++ ) {
481+ const layoutKey = layoutKeys [ i ] as string ;
482+ const key = keys [ i ] ;
483+ const lowerCaseCharacter = layoutKey [ 0 ] ;
484+ const upperCaseCharacter = layoutKey [ 1 ] ;
485+
486+ if (
487+ key === undefined ||
488+ layoutKey === undefined ||
489+ lowerCaseCharacter === undefined ||
490+ upperCaseCharacter === undefined
491+ )
492+ continue ;
493+
494+ const keyIsSymbol = [ lowerCaseCharacter , upperCaseCharacter ] . some (
495+ ( character ) => symbolsPattern . test ( character ?? "" )
496+ ) ;
497+
498+ const keycode = KeyConverter . layoutKeyToKeycode ( lowerCaseCharacter , layout ) ;
499+ if ( keycode === undefined ) {
500+ return ;
501+ }
502+ const oppositeShift = ShiftTracker . isUsingOppositeShift ( keycode ) ;
503+
504+ const state = keyIsSymbol ? symbolsState : lettersState ;
505+ const characterIndex = oppositeShift ? state : 0 ;
506+
507+ //if the character at the index is undefined, try without alt
508+ const character =
509+ layoutKey [ characterIndex ] ?? layoutKey [ characterIndex - 2 ] ;
510+
511+ key . textContent = character ?? "" ;
512+ }
513+ }
514+
405515ConfigEvent . subscribe ( ( eventKey , newValue ) => {
406516 if ( eventKey === "layout" && Config . keymapLayout === "overrideSync" ) {
407517 void refresh ( Config . keymapLayout ) ;
@@ -427,3 +537,27 @@ KeymapEvent.subscribe((mode, key, correct) => {
427537 void flashKey ( key , correct ) ;
428538 }
429539} ) ;
540+
541+ $ ( document ) . on ( "keydown" , ( e ) => {
542+ if (
543+ Config . keymapLegendStyle === "dynamic" &&
544+ ( e . code === "ShiftLeft" ||
545+ e . code === "ShiftRight" ||
546+ e . code === "AltRight" ||
547+ e . code === "AltLeft" )
548+ ) {
549+ void updateLegends ( ) ;
550+ }
551+ } ) ;
552+
553+ $ ( document ) . on ( "keyup" , ( e ) => {
554+ if (
555+ Config . keymapLegendStyle === "dynamic" &&
556+ ( e . code === "ShiftLeft" ||
557+ e . code === "ShiftRight" ||
558+ e . code === "AltRight" ||
559+ e . code === "AltLeft" )
560+ ) {
561+ void updateLegends ( ) ;
562+ }
563+ } ) ;
0 commit comments