@@ -382,6 +382,12 @@ export default function CH55xBootloaderMinimal() {
382382 return generated ;
383383 } ;
384384
385+ const cloneStepWithId = ( prevStep : HidStepDto , nextStep : HidStepDto ) : HidStepDto => {
386+ const id = getStepId ( prevStep ) ;
387+ stepIdMap . current . set ( nextStep , id ) ;
388+ return nextStep ;
389+ } ;
390+
385391 const resetModalClosePending = ( ) => {
386392 modalClosePendingRef . current . clear ( ) ;
387393 } ;
@@ -523,7 +529,7 @@ export default function CH55xBootloaderMinimal() {
523529 setEditSteps ( ( prev ) => prev . map ( ( s , i ) => {
524530 if ( i !== capturingStepIndex ) return s ;
525531 if ( s . kind !== "Key" ) return s ;
526- return { ...s , keycode : code } ;
532+ return cloneStepWithId ( s , { ...s , keycode : code } ) ;
527533 } ) ) ;
528534 setCapturingStepIndex ( null ) ;
529535 input ?. blur ( ) ;
@@ -908,6 +914,18 @@ export default function CH55xBootloaderMinimal() {
908914 highlightTimerRef . current = window . setTimeout ( ( ) => setHighlightedSteps ( [ ] ) , 450 ) ;
909915 } ;
910916
917+ const isInteractiveElement = ( target : EventTarget | null ) : boolean => {
918+ if ( ! target || ! ( target as HTMLElement ) . closest ) return false ;
919+ const elem = target as HTMLElement ;
920+ if ( elem . closest ( "button, input, select, option, textarea, label" ) ) return true ;
921+ if ( elem . closest ( ".drag-handle" ) ) return true ;
922+ return false ;
923+ } ;
924+
925+ const toggleStepCollapse = ( index : number ) => {
926+ setActiveStepIndex ( ( prev ) => ( prev === index ? null : index ) ) ;
927+ } ;
928+
911929 const moveSteps = ( sourceIndices : number [ ] , targetIndex : number , afterMove ?: ( newIndices : number [ ] , nextSteps : HidStepDto [ ] ) => void , keepSelection : boolean = true ) => {
912930 setEditSteps ( ( prev ) => {
913931 if ( prev . length === 0 ) return prev ;
@@ -1116,14 +1134,19 @@ export default function CH55xBootloaderMinimal() {
11161134 const toggleStepModifier = ( index : number , bit : number ) => {
11171135 setEditSteps ( ( prev ) => prev . map ( ( s , i ) => {
11181136 if ( i !== index || s . kind !== "Key" ) return s ;
1119- return { ...s , modifiers : ( s . modifiers & bit ) !== 0 ? ( s . modifiers & ~ bit ) : ( s . modifiers | bit ) } ;
1137+ const nextStep : HidStepDto = { ...s , modifiers : ( s . modifiers & bit ) !== 0 ? ( s . modifiers & ~ bit ) : ( s . modifiers | bit ) } ;
1138+ return cloneStepWithId ( s , nextStep ) ;
11201139 } ) ) ;
11211140 } ;
11221141
11231142 const updateFunctionValue = ( index : number , value : string ) => {
11241143 const parsed = Number ( value ) ;
11251144 const nextValue = Number . isFinite ( parsed ) && parsed > 0 ? parsed : 1 ;
1126- setEditSteps ( ( prev ) => prev . map ( ( s , i ) => ( i === index && s . kind === "Function" ? { ...s , functionValue : nextValue } : s ) ) ) ;
1145+ setEditSteps ( ( prev ) => prev . map ( ( s , i ) => {
1146+ if ( i !== index || s . kind !== "Function" ) return s ;
1147+ const nextStep : HidStepDto = { ...s , functionValue : nextValue } ;
1148+ return cloneStepWithId ( s , nextStep ) ;
1149+ } ) ) ;
11271150 } ;
11281151
11291152 const updateStepTiming = ( index : number , field : "holdMs" | "gapMs" , value : string ) => {
@@ -1132,16 +1155,16 @@ export default function CH55xBootloaderMinimal() {
11321155 setEditSteps ( ( prev ) => prev . map ( ( s , i ) => {
11331156 if ( i !== index ) return s ;
11341157 if ( s . kind === "Key" ) {
1135- return { ...s , [ field ] : nextValue } as HidStepDto ;
1158+ return cloneStepWithId ( s , { ...s , [ field ] : nextValue } as HidStepDto ) ;
11361159 }
11371160 if ( field === "gapMs" && s . kind === "Pause" ) {
1138- return { ...s , gapMs : nextValue } ;
1161+ return cloneStepWithId ( s , { ...s , gapMs : nextValue } ) ;
11391162 }
11401163 if ( field === "gapMs" && s . kind === "Function" ) {
1141- return { ...s , gapMs : nextValue } ;
1164+ return cloneStepWithId ( s , { ...s , gapMs : nextValue } ) ;
11421165 }
11431166 if ( field === "gapMs" && s . kind === "Mouse" ) {
1144- return { ...s , gapMs : nextValue } ;
1167+ return cloneStepWithId ( s , { ...s , gapMs : nextValue } ) ;
11451168 }
11461169 return s ;
11471170 } ) ) ;
@@ -1154,24 +1177,24 @@ export default function CH55xBootloaderMinimal() {
11541177 const keycode = s . kind === "Key" ? s . keycode : 97 ;
11551178 const holdMs = s . kind === "Key" && s . holdMs > 0 ? s . holdMs : 10 ;
11561179 const gapMs = s . kind === "Key" && s . gapMs > 0 ? s . gapMs : 10 ;
1157- return { kind : "Key" , keycode, modifiers : s . kind === "Key" ? s . modifiers : 0 , holdMs, gapMs } ;
1180+ return cloneStepWithId ( s , { kind : "Key" , keycode, modifiers : s . kind === "Key" ? s . modifiers : 0 , holdMs, gapMs } ) ;
11581181 }
11591182 if ( kind === "Pause" ) {
11601183 const gapMs = s . kind === "Key" || s . kind === "Function" || s . kind === "Mouse" ? ( s . gapMs > 0 ? s . gapMs : 100 ) : s . gapMs ;
1161- return { kind : "Pause" , gapMs : gapMs > 0 ? gapMs : 100 } ;
1184+ return cloneStepWithId ( s , { kind : "Pause" , gapMs : gapMs > 0 ? gapMs : 100 } ) ;
11621185 }
11631186 if ( kind === "Mouse" ) {
11641187 const pointerType = s . kind === "Mouse" ? s . pointerType : HID_POINTER_TYPE . LeftClick ;
11651188 const pointerValue = ( pointerType === HID_POINTER_TYPE . LeftClick || pointerType === HID_POINTER_TYPE . RightClick )
11661189 ? 0
11671190 : defaultMouseValue ( pointerType ) ;
11681191 const gapMs = s . kind === "Mouse" && s . gapMs >= 0 ? s . gapMs : 0 ;
1169- return { kind : "Mouse" , pointerType : pointerType as HidPointerType , pointerValue, gapMs } ;
1192+ return cloneStepWithId ( s , { kind : "Mouse" , pointerType : pointerType as HidPointerType , pointerValue, gapMs } ) ;
11701193 }
11711194 const gapMs = s . kind === "Function" && s . gapMs >= 0 ? s . gapMs : 0 ;
11721195 const functionPointer = s . kind === "Function" ? ( s . functionPointer || DEFAULT_FUNCTION_POINTER ) : DEFAULT_FUNCTION_POINTER ;
11731196 const functionValue = FUNCTIONS_WITH_VALUE . has ( functionPointer ) && s . kind === "Function" && s . functionValue ? s . functionValue : 1 ;
1174- return { kind : "Function" , functionPointer, functionValue, gapMs } ;
1197+ return cloneStepWithId ( s , { kind : "Function" , functionPointer, functionValue, gapMs } ) ;
11751198 } ) ) ;
11761199 if ( kind !== "Key" && capturingStepIndex != null && capturingStepIndex === index ) {
11771200 setCapturingStepIndex ( null ) ;
@@ -1632,7 +1655,7 @@ export default function CH55xBootloaderMinimal() {
16321655 : measuredHeight != null
16331656 ? `${ measuredHeight } px`
16341657 : ( isFresh ? "0px" : "1200px" ) ;
1635- const cardClasses = `step-card${ kind === "Pause" ? " step-card-pause" : "" } ${ selected ? " step-card-selected" : "" } ${ draggingStepIndex === idx ? " step-card-dragging" : "" } ${ dragOverIndex === idx ? " step-card-drop-target" : "" } ${ inDragGroup ? " step-card-drag-group" : "" } ${ highlighted ? " step-card-highlight" : "" } ${ removing ? " step-card-removing" : "" } ${ collapsed ? " step-card-collapsed" : "" } ${ isFresh ? " step-card-fresh" : "" } ` ;
1658+ const cardClasses = `step-card${ selected ? " step-card-selected" : "" } ${ draggingStepIndex === idx ? " step-card-dragging" : "" } ${ dragOverIndex === idx ? " step-card-drop-target" : "" } ${ inDragGroup ? " step-card-drag-group" : "" } ${ highlighted ? " step-card-highlight" : "" } ${ removing ? " step-card-removing" : "" } ${ collapsed ? " step-card-collapsed" : "" } ${ isFresh ? " step-card-fresh" : "" } ` ;
16361659 return (
16371660 < div
16381661 className = { cardClasses }
@@ -1650,11 +1673,10 @@ export default function CH55xBootloaderMinimal() {
16501673 onDrop = { ( e ) => handleDrop ( e , idx ) }
16511674 onDragLeave = { ( ) => { if ( dragOverIndex === idx ) setDragOverIndex ( null ) ; } }
16521675 onDragEnd = { handleDragEnd }
1653- onClick = { ( ) => setActiveStepIndex ( ( prev ) => ( prev === idx ? null : idx ) ) }
16541676 onAnimationEnd = { ( e ) => handleCardAnimationEnd ( stepKey , idx , e ) }
16551677 aria-label = { `Step ${ idx + 1 } ${ kind } ` }
16561678 >
1657- < div className = "step-header" >
1679+ < div className = "step-header" onClick = { ( e ) => { if ( ! isInteractiveElement ( e . target ) ) toggleStepCollapse ( idx ) ; } } >
16581680 < div className = "step-header-left" >
16591681 < span className = "drag-handle" title = "Drag to reorder" > ::</ span >
16601682 < label className = "checkbox step-select" >
@@ -1685,10 +1707,30 @@ export default function CH55xBootloaderMinimal() {
16851707 style = { { maxHeight : bodyMaxHeight } }
16861708 >
16871709 < div className = "step-kind-toggle" >
1688- < button className = { `btn ghost${ kind === "Key" ? " active" : "" } ` } onClick = { ( ) => setStepKind ( idx , "Key" ) } > Key</ button >
1689- < button className = { `btn ghost${ kind === "Pause" ? " active" : "" } ` } onClick = { ( ) => setStepKind ( idx , "Pause" ) } > Pause</ button >
1690- < button className = { `btn ghost${ kind === "Mouse" ? " active" : "" } ` } onClick = { ( ) => setStepKind ( idx , "Mouse" ) } > Mouse</ button >
1691- < button className = { `btn ghost${ kind === "Function" ? " active" : "" } ` } onClick = { ( ) => setStepKind ( idx , "Function" ) } > Function</ button >
1710+ < button
1711+ className = { `btn ghost${ kind === "Key" ? " active" : "" } ` }
1712+ onClick = { ( e ) => { e . stopPropagation ( ) ; setStepKind ( idx , "Key" ) ; } }
1713+ >
1714+ Key
1715+ </ button >
1716+ < button
1717+ className = { `btn ghost${ kind === "Pause" ? " active" : "" } ` }
1718+ onClick = { ( e ) => { e . stopPropagation ( ) ; setStepKind ( idx , "Pause" ) ; } }
1719+ >
1720+ Pause
1721+ </ button >
1722+ < button
1723+ className = { `btn ghost${ kind === "Mouse" ? " active" : "" } ` }
1724+ onClick = { ( e ) => { e . stopPropagation ( ) ; setStepKind ( idx , "Mouse" ) ; } }
1725+ >
1726+ Mouse
1727+ </ button >
1728+ < button
1729+ className = { `btn ghost${ kind === "Function" ? " active" : "" } ` }
1730+ onClick = { ( e ) => { e . stopPropagation ( ) ; setStepKind ( idx , "Function" ) ; } }
1731+ >
1732+ Function
1733+ </ button >
16921734 </ div >
16931735 { kind === "Pause" && (
16941736 < >
@@ -1720,7 +1762,11 @@ export default function CH55xBootloaderMinimal() {
17201762 onChange = { ( e ) => {
17211763 const parsed = Number ( e . target . value ) ;
17221764 if ( ! Number . isFinite ( parsed ) ) return ;
1723- setEditSteps ( ( prev ) => prev . map ( ( s , i ) => ( i === idx && s . kind === "Key" ? { ...s , keycode : parsed } : s ) ) ) ;
1765+ setEditSteps ( ( prev ) => prev . map ( ( s , i ) => {
1766+ if ( i !== idx || s . kind !== "Key" ) return s ;
1767+ const nextStep : HidStepDto = { ...s , keycode : parsed } ;
1768+ return cloneStepWithId ( s , nextStep ) ;
1769+ } ) ) ;
17241770 } }
17251771 >
17261772 < option value = "0" > None (modifiers only)</ option >
@@ -1799,7 +1845,8 @@ export default function CH55xBootloaderMinimal() {
17991845 const nextValue = nextType === HID_POINTER_TYPE . LeftClick || nextType === HID_POINTER_TYPE . RightClick
18001846 ? 0
18011847 : defaultMouseValue ( nextType ) ;
1802- return { ...s , pointerType : nextType , pointerValue : nextValue } ;
1848+ const nextStep : HidStepDto = { ...s , pointerType : nextType , pointerValue : nextValue } ;
1849+ return cloneStepWithId ( s , nextStep ) ;
18031850 } ) ) ;
18041851 } }
18051852 >
@@ -1820,7 +1867,11 @@ export default function CH55xBootloaderMinimal() {
18201867 type = "number"
18211868 min = { 0 }
18221869 value = { step . pointerType === HID_POINTER_TYPE . LeftClick || step . pointerType === HID_POINTER_TYPE . RightClick ? "" : step . pointerValue }
1823- onChange = { ( e ) => setEditSteps ( ( prev ) => prev . map ( ( s , i ) => ( i === idx && s . kind === "Mouse" ? { ...s , pointerValue : Number ( e . target . value ) } : s ) ) ) }
1870+ onChange = { ( e ) => setEditSteps ( ( prev ) => prev . map ( ( s , i ) => {
1871+ if ( i !== idx || s . kind !== "Mouse" ) return s ;
1872+ const nextStep : HidStepDto = { ...s , pointerValue : Number ( e . target . value ) } ;
1873+ return cloneStepWithId ( s , nextStep ) ;
1874+ } ) ) }
18241875 disabled = { step . pointerType === HID_POINTER_TYPE . LeftClick || step . pointerType === HID_POINTER_TYPE . RightClick }
18251876 placeholder = { step . pointerType === HID_POINTER_TYPE . LeftClick || step . pointerType === HID_POINTER_TYPE . RightClick ? "N/A" : "" }
18261877 title = { step . pointerType === HID_POINTER_TYPE . LeftClick || step . pointerType === HID_POINTER_TYPE . RightClick ? "Value is ignored for click actions" : "Movement/scroll amount" }
@@ -1860,7 +1911,8 @@ export default function CH55xBootloaderMinimal() {
18601911 if ( i !== idx || s . kind !== "Function" ) return s ;
18611912 const nextPointer = e . target . value || DEFAULT_FUNCTION_POINTER ;
18621913 const nextValue = FUNCTIONS_WITH_VALUE . has ( nextPointer ) ? ( s . functionValue ?? 1 ) : 1 ;
1863- return { ...s , functionPointer : nextPointer , functionValue : nextValue } ;
1914+ const nextStep : HidStepDto = { ...s , functionPointer : nextPointer , functionValue : nextValue } ;
1915+ return cloneStepWithId ( s , nextStep ) ;
18641916 } ) ) }
18651917 >
18661918 { Object . entries ( FRIENDLY_FUNCTIONS ) . map ( ( [ fn , friendly ] ) => (
0 commit comments