@@ -117,6 +117,59 @@ function init_knobs(knobConfigs, gridId, onChange) {
117117 title . style . marginBottom = "4px" ;
118118 label . style . marginBottom = "6px" ;
119119
120+ const rangeWrap = document . createElement ( "div" ) ;
121+ rangeWrap . style . display = "none" ;
122+ rangeWrap . style . marginBottom = "6px" ;
123+
124+ const rangeRow = document . createElement ( "div" ) ;
125+ rangeRow . style . display = "flex" ;
126+ rangeRow . style . gap = "6px" ;
127+ rangeRow . style . marginBottom = "6px" ;
128+
129+ const minField = document . createElement ( "div" ) ;
130+ minField . style . flex = "1" ;
131+ const minLabel = document . createElement ( "div" ) ;
132+ minLabel . textContent = "min_value" ;
133+ minLabel . style . fontSize = "11px" ;
134+ minLabel . style . marginBottom = "2px" ;
135+ const minInput = document . createElement ( "input" ) ;
136+ minInput . type = "text" ;
137+ minInput . style . width = "100%" ;
138+ minInput . style . boxSizing = "border-box" ;
139+ minField . appendChild ( minLabel ) ;
140+ minField . appendChild ( minInput ) ;
141+
142+ const maxField = document . createElement ( "div" ) ;
143+ maxField . style . flex = "1" ;
144+ const maxLabel = document . createElement ( "div" ) ;
145+ maxLabel . textContent = "max_value" ;
146+ maxLabel . style . fontSize = "11px" ;
147+ maxLabel . style . marginBottom = "2px" ;
148+ const maxInput = document . createElement ( "input" ) ;
149+ maxInput . type = "text" ;
150+ maxInput . style . width = "100%" ;
151+ maxInput . style . boxSizing = "border-box" ;
152+ maxField . appendChild ( maxLabel ) ;
153+ maxField . appendChild ( maxInput ) ;
154+
155+ rangeRow . appendChild ( minField ) ;
156+ rangeRow . appendChild ( maxField ) ;
157+
158+ const logRow = document . createElement ( "label" ) ;
159+ logRow . style . display = "flex" ;
160+ logRow . style . alignItems = "center" ;
161+ logRow . style . gap = "6px" ;
162+ logRow . style . fontSize = "11px" ;
163+ const logCheckbox = document . createElement ( "input" ) ;
164+ logCheckbox . type = "checkbox" ;
165+ const logText = document . createElement ( "span" ) ;
166+ logText . textContent = "log" ;
167+ logRow . appendChild ( logCheckbox ) ;
168+ logRow . appendChild ( logText ) ;
169+
170+ rangeWrap . appendChild ( rangeRow ) ;
171+ rangeWrap . appendChild ( logRow ) ;
172+
120173 const input = document . createElement ( "input" ) ;
121174 input . type = "text" ;
122175 input . style . width = "100%" ;
@@ -129,6 +182,12 @@ function init_knobs(knobConfigs, gridId, onChange) {
129182 error . style . minHeight = "14px" ;
130183 error . style . marginBottom = "6px" ;
131184
185+ const learn = document . createElement ( "button" ) ;
186+ learn . type = "button" ;
187+ learn . textContent = "Learn" ;
188+ learn . style . fontSize = "11px" ;
189+ learn . style . marginBottom = "6px" ;
190+
132191 const actions = document . createElement ( "div" ) ;
133192 actions . style . display = "flex" ;
134193 actions . style . gap = "6px" ;
@@ -147,13 +206,66 @@ function init_knobs(knobConfigs, gridId, onChange) {
147206 actions . appendChild ( cancel ) ;
148207 actions . appendChild ( save ) ;
149208 container . appendChild ( title ) ;
209+ container . appendChild ( rangeWrap ) ;
150210 container . appendChild ( label ) ;
151211 container . appendChild ( input ) ;
212+ container . appendChild ( learn ) ;
152213 container . appendChild ( error ) ;
153214 container . appendChild ( actions ) ;
154215 document . body . appendChild ( container ) ;
155216
156- editor = { container : container , input : input , error : error , save : save , cancel : cancel , current : null , title : title } ;
217+ editor = { container : container , input : input , error : error , save : save , cancel : cancel , learn : learn , current : null , title : title , learning : false , minInput : minInput , maxInput : maxInput , logCheckbox : logCheckbox , rangeWrap : rangeWrap } ;
218+
219+ function setLearnActive ( active ) {
220+ editor . learning = active === true ;
221+ if ( editor . learn ) {
222+ editor . learn . style . backgroundColor = editor . learning ? "#1fd17a" : "" ;
223+ editor . learn . style . color = editor . learning ? "#111" : "" ;
224+ }
225+ if ( ! editor . learning && window . cc_learn_handler ) {
226+ window . cc_learn_handler = null ;
227+ }
228+ }
229+
230+ function parseNumberField ( fieldValue ) {
231+ if ( fieldValue . trim ( ) === "" ) {
232+ return null ;
233+ }
234+ const parsed = Number ( fieldValue ) ;
235+ return Number . isFinite ( parsed ) ? parsed : null ;
236+ }
237+
238+ function applyRangeUpdates ( ) {
239+ if ( ! editor . rangeWrap || editor . rangeWrap . style . display === "none" ) {
240+ return true ;
241+ }
242+ const minValue = parseNumberField ( editor . minInput . value ) ;
243+ const maxValue = parseNumberField ( editor . maxInput . value ) ;
244+ if ( minValue === null || maxValue === null ) {
245+ editor . error . textContent = "Enter numeric min/max values." ;
246+ return false ;
247+ }
248+ if ( minValue >= maxValue ) {
249+ editor . error . textContent = "min_value must be less than max_value." ;
250+ return false ;
251+ }
252+ const prevMin = editor . current . min_value ;
253+ const prevMax = editor . current . max_value ;
254+ const prevType = editor . current . knob_type ;
255+ editor . current . min_value = minValue ;
256+ editor . current . max_value = maxValue ;
257+ if ( editor . logCheckbox && editor . logCheckbox . checked ) {
258+ editor . current . knob_type = "log" ;
259+ } else if ( editor . current . knob_type === "log" ) {
260+ editor . current . knob_type = undefined ;
261+ }
262+ const rangeChanged = prevMin !== editor . current . min_value || prevMax !== editor . current . max_value ;
263+ const typeChanged = prevType !== editor . current . knob_type ;
264+ if ( ( rangeChanged || typeChanged ) && typeof window . refresh_knobs_for_channel === "function" ) {
265+ window . refresh_knobs_for_channel ( ) ;
266+ }
267+ return true ;
268+ }
157269
158270 function applyValue ( value ) {
159271 if ( ! editor . current ) {
@@ -169,6 +281,9 @@ function init_knobs(knobConfigs, gridId, onChange) {
169281 save . addEventListener ( "click" , function ( ) {
170282 const trimmed = editor . input . value . trim ( ) ;
171283 if ( trimmed === "" ) {
284+ if ( ! applyRangeUpdates ( ) ) {
285+ return ;
286+ }
172287 applyValue ( "" ) ;
173288 return ;
174289 }
@@ -177,13 +292,38 @@ function init_knobs(knobConfigs, gridId, onChange) {
177292 editor . error . textContent = "Enter an integer 0-127." ;
178293 return ;
179294 }
295+ if ( ! applyRangeUpdates ( ) ) {
296+ return ;
297+ }
180298 applyValue ( parsed ) ;
181299 } ) ;
182300
183301 cancel . addEventListener ( "click" , function ( ) {
184302 hideCcEditor ( editor ) ;
185303 } ) ;
186304
305+ learn . addEventListener ( "click" , function ( ) {
306+ if ( ! editor . current ) {
307+ return ;
308+ }
309+ if ( editor . learning ) {
310+ setLearnActive ( false ) ;
311+ return ;
312+ }
313+ setLearnActive ( true ) ;
314+ window . cc_learn_handler = function ( cc ) {
315+ if ( ! editor . current ) {
316+ setLearnActive ( false ) ;
317+ return ;
318+ }
319+ editor . input . value = String ( cc ) ;
320+ editor . error . textContent = "" ;
321+ setLearnActive ( false ) ;
322+ editor . input . focus ( ) ;
323+ editor . input . select ( ) ;
324+ } ;
325+ } ) ;
326+
187327 input . addEventListener ( "keydown" , function ( event ) {
188328 if ( event . key === "Enter" ) {
189329 event . preventDefault ( ) ;
@@ -206,6 +346,16 @@ function init_knobs(knobConfigs, gridId, onChange) {
206346 } ) ( ) ;
207347
208348 function hideCcEditor ( editor ) {
349+ if ( editor && editor . learning ) {
350+ editor . learning = false ;
351+ if ( editor . learn ) {
352+ editor . learn . style . backgroundColor = "" ;
353+ editor . learn . style . color = "" ;
354+ }
355+ if ( window . cc_learn_handler ) {
356+ window . cc_learn_handler = null ;
357+ }
358+ }
209359 editor . container . style . display = "none" ;
210360 editor . current = null ;
211361 }
@@ -234,6 +384,15 @@ function init_knobs(knobConfigs, gridId, onChange) {
234384 editor . input . value = knobConfig . cc === "" || knobConfig . cc === null || knobConfig . cc === undefined
235385 ? ""
236386 : String ( knobConfig . cc ) ;
387+ const isRangeType = ! knobConfig . knob_type || knobConfig . knob_type === "log" ;
388+ editor . rangeWrap . style . display = isRangeType ? "block" : "none" ;
389+ if ( isRangeType ) {
390+ const minValue = Number . isFinite ( Number ( knobConfig . min_value ) ) ? String ( knobConfig . min_value ) : "" ;
391+ const maxValue = Number . isFinite ( Number ( knobConfig . max_value ) ) ? String ( knobConfig . max_value ) : "" ;
392+ editor . minInput . value = minValue ;
393+ editor . maxInput . value = maxValue ;
394+ editor . logCheckbox . checked = knobConfig . knob_type === "log" ;
395+ }
237396 positionCcEditor ( editor , labelEl ) ;
238397 editor . container . style . display = "block" ;
239398 editor . input . focus ( ) ;
0 commit comments