@@ -11,24 +11,25 @@ import {
1111
1212const DEVICE_POLL_INTERVAL_MS = 1200 ;
1313const INPUT_CAPTURE_TIMEOUT_MS = 5000 ;
14+ const INPUT_CAPTURE_CLICK_SUPPRESSION_MS = 250 ;
1415const KEYBOARD_INPUTS = Object . freeze ( [ "KeyW" , "KeyA" , "KeyS" , "KeyD" , "Space" , "ShiftLeft" , "ControlLeft" , "Enter" , "Backspace" , "KeyP" ] ) ;
1516const MOUSE_INPUTS = Object . freeze ( [ "MouseButton0" , "MouseButton2" , "MouseButton1" , "MouseWheelUp" , "MouseWheelDown" , "MouseX-" , "MouseX+" , "MouseY-" , "MouseY+" ] ) ;
1617const KEYBOARD_MOUSE_EXCLUDED_NORMALIZED_PREFIXES = Object . freeze ( [ "dpad." , "trigger." ] ) ;
1718const SUPPORTED_CONTROL_TYPES = Object . freeze ( [
18- "Keyboard Key" ,
19- "Mouse Button" ,
20- "Mouse Axis" ,
21- "Mouse Wheel" ,
22- "Gamepad Button" ,
23- "Gamepad Trigger" ,
19+ "Gamepad Axis" ,
2420 "Gamepad Bumper" ,
21+ "Gamepad Button" ,
2522 "Gamepad Stick Button" ,
26- "Gamepad Axis" ,
27- "Joystick Button" ,
23+ "Gamepad Trigger" ,
2824 "Joystick Axis" ,
29- "Touch Button" ,
30- "Touch Axis / Pad" ,
25+ "Joystick Button" ,
26+ "Keyboard Key" ,
27+ "Mouse Axis" ,
28+ "Mouse Button" ,
29+ "Mouse Wheel" ,
3130 "Pointer Drag" ,
31+ "Touch Axis / Pad" ,
32+ "Touch Button" ,
3233] ) ;
3334
3435function normalizeText ( value ) {
@@ -149,7 +150,6 @@ export class AccountUserControlsPage {
149150 familyButtons : [ ...root . querySelectorAll ( "[data-account-user-controls-edit-family]" ) ] ,
150151 lists : [ ...root . querySelectorAll ( "[data-account-user-controls-list]" ) ] ,
151152 refresh : root . querySelector ( "[data-account-user-controls-refresh]" ) ,
152- selectedDeviceOptions : root . querySelector ( "[data-account-user-controls-selected-device-options]" ) ,
153153 selectedDeviceStatus : root . querySelector ( "[data-account-user-controls-selected-device-status]" ) ,
154154 status : root . querySelector ( "[data-account-user-controls-status]" ) ,
155155 types : root . querySelector ( "[data-account-user-controls-types]" ) ,
@@ -168,11 +168,11 @@ export class AccountUserControlsPage {
168168 this . elements . refresh ?. addEventListener ( "click" , ( ) => {
169169 this . renderDeviceSelect ( ) ;
170170 this . renderSelectedInputDevices ( ) ;
171+ this . renderProfiles ( ) ;
171172 this . setStatus ( this . deviceRefreshMessage ( ) ) ;
172173 } ) ;
173174 this . elements . addProfile ?. addEventListener ( "click" , ( ) => this . addProfileForSelectedDevice ( ) ) ;
174175 this . elements . deviceSelect ?. addEventListener ( "change" , ( ) => this . renderDeviceStatus ( ) ) ;
175- this . elements . selectedDeviceOptions ?. addEventListener ( "change" , ( event ) => this . handleSelectedInputDeviceChange ( event ) ) ;
176176 this . elements . familyButtons . forEach ( ( button ) => {
177177 button . addEventListener ( "click" , ( ) => this . editFamilyMappings ( button . dataset . accountUserControlsEditFamily || "" ) ) ;
178178 } ) ;
@@ -426,68 +426,63 @@ export class AccountUserControlsPage {
426426 } ) ;
427427 }
428428
429+ defaultSelectionChoice ( family ) {
430+ const profile = this . defaultProfileForFamily ( family ) ;
431+ return {
432+ controllerId : profile . controllerId ,
433+ deviceType : family ,
434+ label : `${ family } Default Profile` ,
435+ profileId : "" ,
436+ selectionKey : `default:${ family . toLowerCase ( ) } ` ,
437+ selectionType : "default" ,
438+ } ;
439+ }
440+
441+ detectedDeviceSelectionChoice ( device ) {
442+ return {
443+ controllerId : device . controllerId ,
444+ deviceType : "Gamepad" ,
445+ label : device . label ,
446+ profileId : "" ,
447+ selectionKey : `device:gamepad:${ keyFromText ( device . controllerId || device . value ) } ` ,
448+ selectionType : "device" ,
449+ } ;
450+ }
451+
452+ profileSelectionChoice ( profile ) {
453+ return {
454+ controllerId : profile . controllerId ,
455+ deviceType : this . profileListFamily ( profile ) ,
456+ label : `${ profile . controllerName } (${ profile . mappingProfile } )` ,
457+ profileId : profile . id ,
458+ selectionKey : `profile:${ profile . id } ` ,
459+ selectionType : "profile" ,
460+ } ;
461+ }
462+
429463 selectedInputDeviceChoices ( ) {
430464 const choices = [ ] ;
431465 const seenKeys = new Set ( ) ;
432- const profilesByFamily = new Map ( [
433- [ "Keyboard" , [ ] ] ,
434- [ "Mouse" , [ ] ] ,
435- [ "Gamepad" , [ ] ] ,
436- ] ) ;
437- this . profiles . forEach ( ( profile ) => {
438- profilesByFamily . get ( this . profileListFamily ( profile ) ) ?. push ( profile ) ;
439- } ) ;
440466 const addChoice = ( choice ) => {
441467 if ( seenKeys . has ( choice . selectionKey ) ) {
442468 return ;
443469 }
444470 seenKeys . add ( choice . selectionKey ) ;
445471 choices . push ( choice ) ;
446472 } ;
447- const addProfileChoice = ( profile ) => {
448- addChoice ( {
449- controllerId : profile . controllerId ,
450- deviceType : this . profileListFamily ( profile ) ,
451- label : `${ profile . controllerName } (${ profile . mappingProfile } )` ,
452- profileId : profile . id ,
453- selectionKey : `profile:${ profile . id } ` ,
454- selectionType : "profile" ,
455- } ) ;
456- } ;
457- [ "Keyboard" , "Mouse" ] . forEach ( ( family ) => {
458- const profiles = profilesByFamily . get ( family ) || [ ] ;
459- if ( profiles . length ) {
460- profiles . forEach ( addProfileChoice ) ;
461- return ;
462- }
463- const device = this . familyDevice ( family ) ;
464- addChoice ( {
465- controllerId : device . controllerId ,
466- deviceType : family ,
467- label : device . label ,
468- profileId : "" ,
469- selectionKey : `device:${ device . value } ` ,
470- selectionType : "device" ,
471- } ) ;
472- } ) ;
473- const gamepadProfiles = profilesByFamily . get ( "Gamepad" ) || [ ] ;
473+ [ "Keyboard" , "Mouse" , "Gamepad" ] . forEach ( ( family ) => addChoice ( this . defaultSelectionChoice ( family ) ) ) ;
474474 const profiledGamepadIds = new Set ( ) ;
475- gamepadProfiles . forEach ( ( profile ) => {
476- profiledGamepadIds . add ( profile . controllerId ) ;
477- addProfileChoice ( profile ) ;
475+ this . profiles . forEach ( ( profile ) => {
476+ if ( this . profileListFamily ( profile ) === "Gamepad" ) {
477+ profiledGamepadIds . add ( profile . controllerId ) ;
478+ }
479+ addChoice ( this . profileSelectionChoice ( profile ) ) ;
478480 } ) ;
479481 this . deviceOptions ( ) . forEach ( ( device ) => {
480482 if ( profiledGamepadIds . has ( device . controllerId ) ) {
481483 return ;
482484 }
483- addChoice ( {
484- controllerId : device . controllerId ,
485- deviceType : "Gamepad" ,
486- label : device . label ,
487- profileId : "" ,
488- selectionKey : `device:gamepad:${ keyFromText ( device . controllerId || device . value ) } ` ,
489- selectionType : "device" ,
490- } ) ;
485+ addChoice ( this . detectedDeviceSelectionChoice ( device ) ) ;
491486 } ) ;
492487 return choices ;
493488 }
@@ -506,6 +501,9 @@ export class AccountUserControlsPage {
506501 }
507502 return this . deviceOptions ( ) . some ( ( device ) => device . controllerId === profile . controllerId ) ;
508503 }
504+ if ( selection . selectionType === "default" ) {
505+ return true ;
506+ }
509507 if ( selection . deviceType === "Keyboard" || selection . deviceType === "Mouse" ) {
510508 return true ;
511509 }
@@ -530,23 +528,6 @@ export class AccountUserControlsPage {
530528 }
531529
532530 renderSelectedInputDevices ( ) {
533- if ( ! this . elements . selectedDeviceOptions ) {
534- this . renderSelectedInputDeviceStatus ( ) ;
535- return ;
536- }
537- const choices = this . selectedInputDeviceChoices ( ) ;
538- const controls = choices . map ( ( choice ) => {
539- const label = document . createElement ( "label" ) ;
540- const input = document . createElement ( "input" ) ;
541- input . type = "radio" ;
542- input . name = "account-user-controls-selected-device" ;
543- input . value = choice . selectionKey ;
544- input . dataset . accountUserControlsSelectedDevice = choice . selectionKey ;
545- input . checked = this . selectedInputDevice ?. selectionKey === choice . selectionKey ;
546- label . append ( input , document . createTextNode ( ` ${ choice . label } ` ) ) ;
547- return label ;
548- } ) ;
549- this . elements . selectedDeviceOptions . replaceChildren ( ...controls ) ;
550531 this . renderSelectedInputDeviceStatus ( ) ;
551532 }
552533
@@ -648,7 +629,7 @@ export class AccountUserControlsPage {
648629 }
649630
650631 tableColumnCountForFamily ( family ) {
651- return family === "Keyboard" ? 4 : 7 ;
632+ return family === "Keyboard" ? 5 : 8 ;
652633 }
653634
654635 normalizedOptionsForProfile ( profile ) {
@@ -687,6 +668,14 @@ export class AccountUserControlsPage {
687668 rows . push ( this . renderReadonlyProfileDetailsRow ( defaultProfile , family ) ) ;
688669 }
689670 } ) ;
671+ const profiledGamepadIds = new Set ( this . profiles
672+ . filter ( ( profile ) => this . profileListFamily ( profile ) === "Gamepad" )
673+ . map ( ( profile ) => profile . controllerId ) ) ;
674+ this . deviceOptions ( )
675+ . filter ( ( device ) => ! profiledGamepadIds . has ( device . controllerId ) )
676+ . forEach ( ( device ) => {
677+ rowsByFamily . get ( "Gamepad" ) ?. push ( this . renderDetectedDeviceRow ( device ) ) ;
678+ } ) ;
690679 if ( this . editingProfile ) {
691680 const family = this . profileListFamily ( this . editingProfile . values ) ;
692681 rowsByFamily . get ( family ) ?. push ( ...this . renderEditingRows ( this . editingProfile . values ) ) ;
@@ -720,6 +709,19 @@ export class AccountUserControlsPage {
720709 this . renderSelectedInputDevices ( ) ;
721710 }
722711
712+ selectedDeviceCell ( choice ) {
713+ const cell = document . createElement ( "td" ) ;
714+ const input = document . createElement ( "input" ) ;
715+ input . type = "radio" ;
716+ input . name = "account-user-controls-selected-device" ;
717+ input . value = choice . selectionKey ;
718+ input . dataset . accountUserControlsSelectedDevice = choice . selectionKey ;
719+ input . checked = this . selectedInputDevice ?. selectionKey === choice . selectionKey ;
720+ input . setAttribute ( "aria-label" , `Select ${ choice . label } ` ) ;
721+ cell . append ( input ) ;
722+ return cell ;
723+ }
724+
723725 readonlyNormalizedSummary ( mapping ) {
724726 if ( physicalInputIsAnalog ( mapping . physicalInput ) ) {
725727 const negative = normalizeText ( mapping . negativeNormalizedInput ) || "Unassigned" ;
@@ -782,12 +784,14 @@ export class AccountUserControlsPage {
782784 row . dataset . accountUserControlsDefaultProfile = family ;
783785 if ( family === "Keyboard" ) {
784786 row . append (
787+ this . selectedDeviceCell ( this . defaultSelectionChoice ( family ) ) ,
785788 tableCell ( "Default Profile" ) ,
786789 tableCell ( `${ profile . inputMappings . length } Physical Inputs` ) ,
787790 tableCell ( this . profileInputSummary ( profile ) ) ,
788791 ) ;
789792 } else {
790793 row . append (
794+ this . selectedDeviceCell ( this . defaultSelectionChoice ( family ) ) ,
791795 tableCell ( "Default Profile" ) ,
792796 tableCell ( `${ profile . inputMappings . length } Physical Inputs` ) ,
793797 tableCell ( this . profileInputSummary ( profile ) ) ,
@@ -805,6 +809,22 @@ export class AccountUserControlsPage {
805809 return row ;
806810 }
807811
812+ renderDetectedDeviceRow ( device ) {
813+ const row = document . createElement ( "tr" ) ;
814+ row . dataset . accountUserControlsDetectedDevice = device . controllerId ;
815+ row . append (
816+ this . selectedDeviceCell ( this . detectedDeviceSelectionChoice ( device ) ) ,
817+ tableCell ( device . label ) ,
818+ tableCell ( `${ device . inputs . length } Detected Inputs` ) ,
819+ tableCell ( "Create my profile to customize" ) ,
820+ tableCell ( "N/A" ) ,
821+ tableCell ( "N/A" ) ,
822+ tableCell ( "N/A" ) ,
823+ tableCell ( "Detected" ) ,
824+ ) ;
825+ return row ;
826+ }
827+
808828 profileInputSummary ( profile ) {
809829 const assigned = profile . inputMappings . reduce ( ( count , mapping ) => {
810830 if ( ! physicalInputIsAnalog ( mapping . physicalInput ) ) {
@@ -845,12 +865,14 @@ export class AccountUserControlsPage {
845865 const family = this . profileListFamily ( profile ) ;
846866 if ( family === "Keyboard" ) {
847867 row . append (
868+ this . selectedDeviceCell ( this . profileSelectionChoice ( profile ) ) ,
848869 tableCell ( profile . controllerName ) ,
849870 tableCell ( `${ profile . inputMappings . length } Physical Inputs` ) ,
850871 tableCell ( this . profileInputSummary ( profile ) ) ,
851872 ) ;
852873 } else {
853874 row . append (
875+ this . selectedDeviceCell ( this . profileSelectionChoice ( profile ) ) ,
854876 tableCell ( profile . controllerName ) ,
855877 tableCell ( `${ profile . inputMappings . length } Physical Inputs` ) ,
856878 tableCell ( this . profileInputSummary ( profile ) ) ,
@@ -1006,13 +1028,15 @@ export class AccountUserControlsPage {
10061028 actions . append ( group ) ;
10071029 if ( family === "Keyboard" ) {
10081030 row . append (
1031+ tableCell ( "Editing" ) ,
10091032 controllerCell ,
10101033 tableCell ( `${ profile . inputMappings . length } Physical Inputs` ) ,
10111034 tableCell ( this . profileInputSummary ( profile ) ) ,
10121035 actions ,
10131036 ) ;
10141037 } else {
10151038 row . append (
1039+ tableCell ( "Editing" ) ,
10161040 controllerCell ,
10171041 tableCell ( `${ profile . inputMappings . length } Physical Inputs` ) ,
10181042 tableCell ( this . profileInputSummary ( profile ) ) ,
@@ -1234,6 +1258,14 @@ export class AccountUserControlsPage {
12341258 if ( target . isConnected ) {
12351259 target . value = binding ;
12361260 target . focus ( ) ;
1261+ if ( deviceType === "Mouse" && event . type === "mousedown" ) {
1262+ target . dataset . accountUserControlsSuppressCaptureClick = "true" ;
1263+ window . setTimeout ( ( ) => {
1264+ if ( target . isConnected ) {
1265+ delete target . dataset . accountUserControlsSuppressCaptureClick ;
1266+ }
1267+ } , INPUT_CAPTURE_CLICK_SUPPRESSION_MS ) ;
1268+ }
12371269 }
12381270 this . setStatus ( `Captured ${ binding } . Save the profile to keep it.` ) ;
12391271 } ;
@@ -1289,6 +1321,11 @@ export class AccountUserControlsPage {
12891321 ? event . target . closest ( "[data-account-user-controls-physical-input]" )
12901322 : null ;
12911323 if ( physicalInputTarget instanceof HTMLInputElement ) {
1324+ if ( physicalInputTarget . dataset . accountUserControlsSuppressCaptureClick === "true" ) {
1325+ event . preventDefault ( ) ;
1326+ event . stopPropagation ( ) ;
1327+ return ;
1328+ }
12921329 this . beginPhysicalInputCapture ( physicalInputTarget ) ;
12931330 return ;
12941331 }
@@ -1328,7 +1365,14 @@ export class AccountUserControlsPage {
13281365 }
13291366 }
13301367
1331- handleListChange ( ) {
1368+ handleListChange ( event ) {
1369+ const selectedDeviceTarget = event . target instanceof Element
1370+ ? event . target . closest ( "[data-account-user-controls-selected-device]" )
1371+ : null ;
1372+ if ( selectedDeviceTarget instanceof HTMLInputElement ) {
1373+ this . handleSelectedInputDeviceChange ( event ) ;
1374+ return ;
1375+ }
13321376 this . setStatus ( "Unsaved user control profile changes." ) ;
13331377 }
13341378
0 commit comments