@@ -130,7 +130,8 @@ export class AccountUserControlsPage {
130130 defaultLists : [ ...root . querySelectorAll ( "[data-account-user-controls-default-list]" ) ] ,
131131 deviceSelect : root . querySelector ( "[data-account-user-controls-device]" ) ,
132132 deviceStatus : root . querySelector ( "[data-account-user-controls-device-status]" ) ,
133- list : root . querySelector ( "[data-account-user-controls-list]" ) ,
133+ familyButtons : [ ...root . querySelectorAll ( "[data-account-user-controls-edit-family]" ) ] ,
134+ lists : [ ...root . querySelectorAll ( "[data-account-user-controls-list]" ) ] ,
134135 refresh : root . querySelector ( "[data-account-user-controls-refresh]" ) ,
135136 saveAll : root . querySelector ( "[data-account-user-controls-save-all]" ) ,
136137 status : root . querySelector ( "[data-account-user-controls-status]" ) ,
@@ -152,10 +153,15 @@ export class AccountUserControlsPage {
152153 this . elements . addProfile ?. addEventListener ( "click" , ( ) => this . addProfileForSelectedDevice ( ) ) ;
153154 this . elements . saveAll ?. addEventListener ( "click" , ( ) => this . saveCurrentState ( ) ) ;
154155 this . elements . deviceSelect ?. addEventListener ( "change" , ( ) => this . renderDeviceStatus ( ) ) ;
155- this . elements . list ?. addEventListener ( "click" , ( event ) => this . handleListClick ( event ) ) ;
156- this . elements . list ?. addEventListener ( "change" , ( event ) => this . handleListChange ( event ) ) ;
157- this . elements . list ?. addEventListener ( "input" , ( event ) => this . handleListInput ( event ) ) ;
158- this . elements . list ?. addEventListener ( "dblclick" , ( event ) => this . handleListDoubleClick ( event ) ) ;
156+ this . elements . familyButtons . forEach ( ( button ) => {
157+ button . addEventListener ( "click" , ( ) => this . editFamilyMappings ( button . dataset . accountUserControlsEditFamily || "" ) ) ;
158+ } ) ;
159+ this . elements . lists . forEach ( ( list ) => {
160+ list . addEventListener ( "click" , ( event ) => this . handleListClick ( event ) ) ;
161+ list . addEventListener ( "change" , ( event ) => this . handleListChange ( event ) ) ;
162+ list . addEventListener ( "input" , ( event ) => this . handleListInput ( event ) ) ;
163+ list . addEventListener ( "dblclick" , ( event ) => this . handleListDoubleClick ( event ) ) ;
164+ } ) ;
159165 this . startDevicePolling ( ) ;
160166 }
161167
@@ -177,25 +183,7 @@ export class AccountUserControlsPage {
177183 }
178184
179185 deviceOptions ( ) {
180- const keyboard = {
181- controllerId : "generic-keyboard" ,
182- controllerName : "Keyboard" ,
183- deviceType : "Keyboard" ,
184- inputs : [ ...KEYBOARD_INPUTS ] ,
185- label : "Keyboard" ,
186- mappingProfile : "Keyboard Profile" ,
187- value : "keyboard" ,
188- } ;
189- const mouse = {
190- controllerId : "generic-mouse" ,
191- controllerName : "Mouse" ,
192- deviceType : "Mouse" ,
193- inputs : [ ...MOUSE_INPUTS ] ,
194- label : "Mouse" ,
195- mappingProfile : "Mouse Profile" ,
196- value : "mouse" ,
197- } ;
198- const gamepads = this . availableGamepads ( ) . map ( ( gamepad ) => {
186+ return this . availableGamepads ( ) . map ( ( gamepad ) => {
199187 const controllerName = gamepad . id || `Gamepad ${ gamepad . index } ` ;
200188 return {
201189 controllerId : gamepad . id || `gamepad-${ gamepad . index } ` ,
@@ -207,15 +195,37 @@ export class AccountUserControlsPage {
207195 value : `gamepad-${ gamepad . index } ` ,
208196 } ;
209197 } ) ;
210- return [ keyboard , mouse , ...gamepads ] ;
198+ }
199+
200+ familyDevice ( family ) {
201+ if ( family === "Mouse" ) {
202+ return {
203+ controllerId : "generic-mouse" ,
204+ controllerName : "Mouse" ,
205+ deviceType : "Mouse" ,
206+ inputs : [ ...MOUSE_INPUTS ] ,
207+ label : "Mouse" ,
208+ mappingProfile : "Mouse Profile" ,
209+ value : "mouse" ,
210+ } ;
211+ }
212+ return {
213+ controllerId : "generic-keyboard" ,
214+ controllerName : "Keyboard" ,
215+ deviceType : "Keyboard" ,
216+ inputs : [ ...KEYBOARD_INPUTS ] ,
217+ label : "Keyboard" ,
218+ mappingProfile : "Keyboard Profile" ,
219+ value : "keyboard" ,
220+ } ;
211221 }
212222
213223 deviceRefreshMessage ( ) {
214224 const gamepadCount = this . availableGamepads ( ) . length ;
215225 if ( gamepadCount > 0 ) {
216- return `PASS: Keyboard and Mouse available. ${ gamepadCount } game controller${ gamepadCount === 1 ? "" : "s" } detected automatically.` ;
226+ return `PASS: ${ gamepadCount } game controller${ gamepadCount === 1 ? "" : "s" } detected automatically.` ;
217227 }
218- return "PASS: Keyboard and Mouse available. Gamepads auto-detect after browser exposes them." ;
228+ return "PASS: Game controllers auto-detect after the browser exposes them." ;
219229 }
220230
221231 selectedDevice ( ) {
@@ -287,7 +297,7 @@ export class AccountUserControlsPage {
287297 }
288298 const selectedValue = this . elements . deviceSelect . value ;
289299 const options = [
290- { label : "Choose a physical controller" , value : "" } ,
300+ { label : "Choose a game controller" , value : "" } ,
291301 ...this . deviceOptions ( ) . map ( ( device ) => ( { label : device . label , value : device . value } ) ) ,
292302 ] ;
293303 this . elements . deviceSelect . classList . add ( "tool-form-control" ) ;
@@ -368,30 +378,62 @@ export class AccountUserControlsPage {
368378 } , { once : true } ) ;
369379 }
370380
381+ profileListFamily ( profile ) {
382+ const deviceType = normalizeText ( profile ?. deviceType ) ;
383+ if ( deviceType === "Keyboard" || deviceType === "Mouse" ) {
384+ return deviceType ;
385+ }
386+ return "Gamepad" ;
387+ }
388+
389+ emptyProfileMessage ( family ) {
390+ if ( family === "Keyboard" ) {
391+ return "No keyboard user control profile saved yet." ;
392+ }
393+ if ( family === "Mouse" ) {
394+ return "No mouse user control profile saved yet." ;
395+ }
396+ return "No game controller profiles saved yet." ;
397+ }
398+
371399 renderProfiles ( ) {
372- if ( ! this . elements . list ) {
400+ if ( ! this . elements . lists . length ) {
373401 return ;
374402 }
375- const rows = [ ] ;
403+ const rowsByFamily = new Map ( this . elements . lists . map ( ( list ) => [
404+ list . dataset . accountUserControlsListFamily || "Gamepad" ,
405+ [ ] ,
406+ ] ) ) ;
376407 if ( this . editingProfile ) {
377- rows . push ( ...this . renderEditingRows ( this . editingProfile . values ) ) ;
408+ const family = this . profileListFamily ( this . editingProfile . values ) ;
409+ rowsByFamily . get ( family ) ?. push ( ...this . renderEditingRows ( this . editingProfile . values ) ) ;
378410 }
379411 const visibleProfiles = this . editingProfile ?. id
380412 ? this . profiles . filter ( ( profile ) => profile . id !== this . editingProfile . id )
381413 : this . profiles ;
382- rows . push ( ...visibleProfiles . map ( ( profile ) => this . renderProfileRow ( profile ) ) ) ;
383- if ( ! rows . length ) {
384- const row = document . createElement ( "tr" ) ;
385- const cell = document . createElement ( "td" ) ;
386- cell . colSpan = 7 ;
387- cell . textContent = "No account user control profiles saved yet." ;
388- row . append ( cell ) ;
389- rows . push ( row ) ;
390- }
391- this . elements . list . replaceChildren ( ...rows ) ;
414+ visibleProfiles . forEach ( ( profile ) => {
415+ const family = this . profileListFamily ( profile ) ;
416+ rowsByFamily . get ( family ) ?. push ( this . renderProfileRow ( profile ) ) ;
417+ } ) ;
418+ this . elements . lists . forEach ( ( list ) => {
419+ const family = list . dataset . accountUserControlsListFamily || "Gamepad" ;
420+ const rows = rowsByFamily . get ( family ) || [ ] ;
421+ if ( ! rows . length ) {
422+ const row = document . createElement ( "tr" ) ;
423+ const cell = document . createElement ( "td" ) ;
424+ cell . colSpan = 7 ;
425+ cell . textContent = this . emptyProfileMessage ( family ) ;
426+ row . append ( cell ) ;
427+ rows . push ( row ) ;
428+ }
429+ list . replaceChildren ( ...rows ) ;
430+ } ) ;
392431 if ( this . elements . addProfile ) {
393432 this . elements . addProfile . disabled = Boolean ( this . editingProfile ) ;
394433 }
434+ this . elements . familyButtons . forEach ( ( button ) => {
435+ button . disabled = Boolean ( this . editingProfile ) ;
436+ } ) ;
395437 }
396438
397439 profileInputSummary ( profile ) {
@@ -455,8 +497,18 @@ export class AccountUserControlsPage {
455497 const wrapper = document . createElement ( "div" ) ;
456498 wrapper . className = "content-grid" ;
457499 wrapper . dataset . accountUserControlsInputPair = "true" ;
458- const label = document . createElement ( "strong" ) ;
459- label . textContent = inputMapping . physicalInput ;
500+ const editablePhysicalInput = profile . deviceType === "Keyboard" || profile . deviceType === "Mouse" ;
501+ const physicalInputControl = editablePhysicalInput
502+ ? document . createElement ( "input" )
503+ : document . createElement ( "strong" ) ;
504+ if ( editablePhysicalInput ) {
505+ physicalInputControl . type = "text" ;
506+ physicalInputControl . value = inputMapping . physicalInput ;
507+ physicalInputControl . dataset . accountUserControlsPhysicalInput = String ( index ) ;
508+ physicalInputControl . setAttribute ( "aria-label" , `${ profile . deviceType } physical input` ) ;
509+ } else {
510+ physicalInputControl . textContent = inputMapping . physicalInput ;
511+ }
460512 const stack = document . createElement ( "div" ) ;
461513 stack . className = "content-stack content-stack--compact" ;
462514 const normalizedOptions = [
@@ -522,7 +574,7 @@ export class AccountUserControlsPage {
522574 validation . className = "status" ;
523575 validation . dataset . accountUserControlsInputValidation = String ( index ) ;
524576 stack . append ( validation ) ;
525- wrapper . append ( label , stack ) ;
577+ wrapper . append ( physicalInputControl , stack ) ;
526578 return wrapper ;
527579 }
528580
@@ -579,24 +631,42 @@ export class AccountUserControlsPage {
579631 this . setStatus ( `Review ${ profile . mappingProfile } before saving.` ) ;
580632 }
581633
634+ editFamilyMappings ( family ) {
635+ const normalizedFamily = family === "Mouse" ? "Mouse" : "Keyboard" ;
636+ const device = this . familyDevice ( normalizedFamily ) ;
637+ const existing = this . profiles . find ( ( profile ) => profile . deviceType === device . deviceType && profile . controllerId === device . controllerId ) ;
638+ if ( existing ) {
639+ this . editingProfile = { id : existing . id , values : existing } ;
640+ this . renderProfiles ( ) ;
641+ this . setStatus ( `Editing existing ${ existing . mappingProfile } .` ) ;
642+ return ;
643+ }
644+ const profile = this . profileFromDevice ( device ) ;
645+ this . editingProfile = { id : profile . id , values : profile } ;
646+ this . renderProfiles ( ) ;
647+ this . setStatus ( `Review ${ profile . mappingProfile } before saving.` ) ;
648+ }
649+
582650 profileFromEditingRow ( ) {
583651 const values = this . editingProfile ?. values || { } ;
584- const details = this . elements . list ? .querySelector ( "[data-account-user-controls-editing-details]" ) ;
652+ const details = this . root . querySelector ( "[data-account-user-controls-editing-details]" ) ;
585653 const inputMappings = ( values . inputMappings || [ ] ) . map ( ( mapping , index ) => {
654+ const physicalInputControl = details ?. querySelector ( `[data-account-user-controls-physical-input="${ index } "]` ) ;
586655 const normalizedSelect = details ?. querySelector ( `[data-account-user-controls-input-normalized="${ index } "]` ) ;
587656 const negativeSelect = details ?. querySelector ( `[data-account-user-controls-input-negative="${ index } "]` ) ;
588657 const positiveSelect = details ?. querySelector ( `[data-account-user-controls-input-positive="${ index } "]` ) ;
589658 const deadzoneInput = details ?. querySelector ( `[data-account-user-controls-deadzone="${ index } "]` ) ;
590659 const invertInput = details ?. querySelector ( `[data-account-user-controls-invert="${ index } "]` ) ;
591660 const sensitivityInput = details ?. querySelector ( `[data-account-user-controls-sensitivity="${ index } "]` ) ;
661+ const physicalInput = normalizeText ( physicalInputControl ?. value ?? mapping . physicalInput ) ;
592662 const negativeNormalizedInput = normalizeText ( negativeSelect ?. value ?? mapping . negativeNormalizedInput ) ;
593663 const positiveNormalizedInput = normalizeText ( positiveSelect ?. value ?? mapping . positiveNormalizedInput ) ;
594664 return {
595665 deadzone : Number ( deadzoneInput ?. value ?? mapping . deadzone ) ,
596666 invert : Boolean ( invertInput ?. checked ?? mapping . invert ) ,
597667 negativeNormalizedInput,
598668 normalizedInput : positiveNormalizedInput || normalizeText ( normalizedSelect ?. value ?? mapping . normalizedInput ) || negativeNormalizedInput ,
599- physicalInput : mapping . physicalInput ,
669+ physicalInput,
600670 positiveNormalizedInput,
601671 sensitivity : sensitivityInput ? Number ( sensitivityInput . value ) : mapping . sensitivity ,
602672 } ;
@@ -605,6 +675,7 @@ export class AccountUserControlsPage {
605675 ...values ,
606676 id : this . editingProfile ?. id || "" ,
607677 inputMappings,
678+ inputs : inputMappings . map ( ( mapping ) => mapping . physicalInput ) ,
608679 } ) ;
609680 }
610681
@@ -662,7 +733,7 @@ export class AccountUserControlsPage {
662733
663734 renderInputValidation ( validation ) {
664735 const invalidIndexes = new Set ( validation . invalidIndexes || [ ] ) ;
665- this . elements . list ? .querySelectorAll ( "[data-account-user-controls-input-validation]" ) . forEach ( ( status ) => {
736+ this . root . querySelectorAll ( "[data-account-user-controls-input-validation]" ) . forEach ( ( status ) => {
666737 const index = Number ( status . dataset . accountUserControlsInputValidation ) ;
667738 status . textContent = invalidIndexes . has ( index )
668739 ? "Needs normalized control, valid deadzone, and valid sensitivity."
0 commit comments