syd 1.1.0__py3-none-any.whl → 1.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,812 @@
1
+ import { paramOrder, paramInfo, state, updateParameter } from './state.js';
2
+ import { formatLabel } from './utils.js';
3
+ import { handleButtonClick } from './api.js';
4
+
5
+ /**
6
+ * Create UI controls based on parameter types
7
+ */
8
+ export function createControls() {
9
+ const paramControlsContainer = document.getElementById('parameter-controls');
10
+ if (!paramControlsContainer) {
11
+ console.error("Element with ID 'parameter-controls' not found.");
12
+ return;
13
+ }
14
+
15
+ // Clear any existing parameter controls first
16
+ // Add Parameters header
17
+ const paramHeader = document.createElement('div');
18
+ paramHeader.className = 'section-header';
19
+ paramHeader.innerHTML = '<b>Parameters</b>';
20
+ paramControlsContainer.innerHTML = ''; // Clear container AFTER getting header content
21
+ paramControlsContainer.appendChild(paramHeader);
22
+
23
+ // Create controls for each parameter in the order specified by the viewer
24
+ paramOrder.forEach(name => {
25
+ const param = paramInfo[name];
26
+ if (!param) {
27
+ console.warn(`Parameter info not found for ${name} during control creation.`);
28
+ return; // Skip if param info is missing for some reason
29
+ }
30
+
31
+ // Create control group
32
+ const controlGroup = createControlGroup(name, param);
33
+
34
+ // Add to container
35
+ if (controlGroup) {
36
+ paramControlsContainer.appendChild(controlGroup);
37
+ }
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Create a control group for a parameter
43
+ */
44
+ function createControlGroup(name, param) {
45
+ // Skip if param type is unknown
46
+ if (!param.type || param.type === 'unknown') {
47
+ console.warn(`Unknown parameter type for ${name}`);
48
+ return null;
49
+ }
50
+
51
+ // Create control group div
52
+ const controlGroup = document.createElement('div');
53
+ controlGroup.className = 'control-group';
54
+ controlGroup.id = `control-group-${name}`;
55
+
56
+ // Add label
57
+ const label = document.createElement('span');
58
+ label.className = 'control-label';
59
+ label.textContent = formatLabel(name);
60
+ controlGroup.appendChild(label);
61
+
62
+ // Create specific control based on parameter type
63
+ const control = createControl(name, param);
64
+ if (control) {
65
+ controlGroup.appendChild(control);
66
+ }
67
+
68
+ return controlGroup;
69
+ }
70
+
71
+ /**
72
+ * Create a specific control based on parameter type
73
+ */
74
+ function createControl(name, param) {
75
+ switch (param.type) {
76
+ case 'text':
77
+ return createTextControl(name, param);
78
+ case 'boolean':
79
+ return createBooleanControl(name, param);
80
+ case 'integer':
81
+ return createIntegerControl(name, param);
82
+ case 'float':
83
+ return createFloatControl(name, param);
84
+ case 'selection':
85
+ return createSelectionControl(name, param);
86
+ case 'multiple-selection':
87
+ return createMultipleSelectionControl(name, param);
88
+ case 'integer-range':
89
+ return createIntegerRangeControl(name, param);
90
+ case 'float-range':
91
+ return createFloatRangeControl(name, param);
92
+ case 'unbounded-integer':
93
+ return createUnboundedIntegerControl(name, param);
94
+ case 'unbounded-float':
95
+ return createUnboundedFloatControl(name, param);
96
+ case 'button':
97
+ return createButtonControl(name, param);
98
+ default:
99
+ console.warn(`No control implementation for type: ${param.type}`);
100
+ return null;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Create text input control
106
+ */
107
+ function createTextControl(name, param) {
108
+ const input = document.createElement('input');
109
+ input.type = 'text';
110
+ input.id = `${name}-input`;
111
+ input.value = param.value || '';
112
+
113
+ input.addEventListener('change', function() {
114
+ updateParameter(name, this.value);
115
+ });
116
+
117
+ return input;
118
+ }
119
+
120
+ /**
121
+ * Create boolean checkbox control
122
+ */
123
+ function createBooleanControl(name, param) {
124
+ const container = document.createElement('div');
125
+ container.className = 'checkbox-container';
126
+
127
+ const checkbox = document.createElement('input');
128
+ checkbox.type = 'checkbox';
129
+ checkbox.id = `${name}-checkbox`;
130
+ checkbox.checked = param.value === true;
131
+
132
+ checkbox.addEventListener('change', function() {
133
+ updateParameter(name, this.checked);
134
+ });
135
+
136
+ container.appendChild(checkbox);
137
+ return container;
138
+ }
139
+
140
+ /**
141
+ * Create selection dropdown control
142
+ */
143
+ function createSelectionControl(name, param) {
144
+ const select = document.createElement('select');
145
+ select.id = `${name}-select`;
146
+
147
+ // Add options
148
+ if (param.options && Array.isArray(param.options)) {
149
+ param.options.forEach(option => {
150
+ const optionElement = document.createElement('option');
151
+ optionElement.value = option;
152
+ optionElement.textContent = formatLabel(String(option));
153
+ // Store the original type information as a data attribute
154
+ optionElement.dataset.originalType = typeof option;
155
+ // For float values, also store the original value for exact comparison
156
+ if (typeof option === 'number') {
157
+ optionElement.dataset.originalValue = option;
158
+ }
159
+ select.appendChild(optionElement);
160
+ });
161
+ } else {
162
+ console.warn(`No options or invalid options format for selection parameter: ${name}`);
163
+ }
164
+
165
+ // Set default value
166
+ select.value = param.value;
167
+
168
+ // Add event listener
169
+ select.addEventListener('change', function() {
170
+ // Get the selected option element
171
+ const selectedOption = this.options[this.selectedIndex];
172
+ let valueToSend = this.value;
173
+
174
+ // Convert back to the original type if needed
175
+ if (selectedOption && selectedOption.dataset.originalType === 'number') {
176
+ // Use the original value from the dataset for exact precision with floats
177
+ if (selectedOption.dataset.originalValue) {
178
+ valueToSend = parseFloat(selectedOption.dataset.originalValue);
179
+ } else {
180
+ // Fallback if originalValue wasn't stored (shouldn't happen)
181
+ valueToSend = parseFloat(valueToSend);
182
+ }
183
+ }
184
+ // No conversion needed for string, boolean already handled by value
185
+
186
+ updateParameter(name, valueToSend);
187
+ });
188
+
189
+ return select;
190
+ }
191
+
192
+ /**
193
+ * Create multiple selection control
194
+ */
195
+ function createMultipleSelectionControl(name, param) {
196
+ const container = document.createElement('div');
197
+ container.className = 'multiple-selection-container';
198
+
199
+ // Create select element
200
+ const select = document.createElement('select');
201
+ select.id = `${name}-select`;
202
+ select.className = 'multiple-select';
203
+ select.multiple = true;
204
+
205
+ // Add options
206
+ if (param.options && Array.isArray(param.options)) {
207
+ param.options.forEach(option => {
208
+ const optionElement = document.createElement('option');
209
+ optionElement.value = option;
210
+ optionElement.textContent = formatLabel(String(option));
211
+ // Store original type/value info
212
+ optionElement.dataset.originalType = typeof option;
213
+ if (typeof option === 'number') {
214
+ optionElement.dataset.originalValue = option;
215
+ }
216
+
217
+ // Check if this option is selected in the initial value array
218
+ if (Array.isArray(param.value) && param.value.includes(option)) {
219
+ optionElement.selected = true;
220
+ }
221
+
222
+ select.appendChild(optionElement);
223
+ });
224
+ } else {
225
+ console.warn(`No options or invalid options format for multi-selection parameter: ${name}`);
226
+ }
227
+
228
+ // Helper text
229
+ const helperText = document.createElement('div');
230
+ helperText.className = 'helper-text';
231
+ helperText.textContent = 'Ctrl+click to select multiple';
232
+
233
+ // Add event listener
234
+ select.addEventListener('change', function() {
235
+ // Get all selected options
236
+ const selectedValues = Array.from(this.selectedOptions).map(option => {
237
+ let value = option.value;
238
+ // Convert back to original type if needed
239
+ if (option.dataset.originalType === 'number') {
240
+ value = option.dataset.originalValue ? parseFloat(option.dataset.originalValue) : parseFloat(value);
241
+ }
242
+ return value;
243
+ });
244
+ updateParameter(name, selectedValues);
245
+ });
246
+
247
+ container.appendChild(select);
248
+ container.appendChild(helperText);
249
+ return container;
250
+ }
251
+
252
+ /**
253
+ * Create integer control with slider and number input
254
+ */
255
+ function createIntegerControl(name, param) {
256
+ const container = document.createElement('div');
257
+ container.className = 'numeric-control';
258
+
259
+ // Create slider
260
+ const slider = document.createElement('input');
261
+ slider.type = 'range';
262
+ slider.id = `${name}-slider`;
263
+ slider.min = param.min;
264
+ slider.max = param.max;
265
+ slider.step = param.step || 1;
266
+ slider.value = param.value;
267
+
268
+ // Create number input
269
+ const input = document.createElement('input');
270
+ input.type = 'number';
271
+ input.id = `${name}-input`;
272
+ input.min = param.min;
273
+ input.max = param.max;
274
+ input.step = param.step || 1;
275
+ input.value = param.value;
276
+
277
+ // Add event listeners
278
+ slider.addEventListener('input', function() {
279
+ input.value = this.value;
280
+ });
281
+
282
+ slider.addEventListener('change', function() {
283
+ const value = parseInt(this.value, 10);
284
+ input.value = value;
285
+ updateParameter(name, value);
286
+ });
287
+
288
+ input.addEventListener('change', function() {
289
+ const value = parseInt(this.value, 10);
290
+ if (!isNaN(value) && value >= param.min && value <= param.max) {
291
+ slider.value = value;
292
+ updateParameter(name, value);
293
+ } else {
294
+ // Revert to current state value if input is invalid
295
+ this.value = state[name];
296
+ }
297
+ });
298
+
299
+ container.appendChild(slider);
300
+ container.appendChild(input);
301
+ return container;
302
+ }
303
+
304
+ /**
305
+ * Create float control with slider and number input
306
+ */
307
+ function createFloatControl(name, param) {
308
+ // Use the shared controller creator
309
+ const container = createFloatController(name, param);
310
+ const slider = container.querySelector('input[type="range"]');
311
+ const input = container.querySelector('input[type="number"]');
312
+
313
+ // Add event listeners specific to parameter updates
314
+ slider.addEventListener('input', function() {
315
+ input.value = this.value;
316
+ });
317
+
318
+ slider.addEventListener('change', function() {
319
+ const value = parseFloat(this.value);
320
+ input.value = value;
321
+ updateParameter(name, value);
322
+ });
323
+
324
+ input.addEventListener('change', function() {
325
+ const value = parseFloat(this.value);
326
+ if (!isNaN(value) && value >= param.min && value <= param.max) {
327
+ slider.value = value;
328
+ updateParameter(name, value);
329
+ } else {
330
+ // Revert to current state value if input is invalid
331
+ this.value = state[name];
332
+ }
333
+ });
334
+
335
+ return container;
336
+ }
337
+
338
+ /**
339
+ * Create float controller object (container with slider and number input).
340
+ * Exported because it's also used by system_controls.js
341
+ * @param {string} name - Base name for element IDs.
342
+ * @param {object} param - Parameter info (min, max, step, value).
343
+ */
344
+ export function createFloatController(name, param) {
345
+ const container = document.createElement('div');
346
+ container.className = 'numeric-control';
347
+
348
+ // Create slider
349
+ const slider = document.createElement('input');
350
+ slider.type = 'range';
351
+ slider.id = `${name}-slider`;
352
+ slider.min = param.min;
353
+ slider.max = param.max;
354
+ slider.step = param.step || 0.01; // Default step for float
355
+ slider.value = param.value;
356
+
357
+ // Create number input
358
+ const input = document.createElement('input');
359
+ input.type = 'number';
360
+ input.id = `${name}-input`;
361
+ input.min = param.min;
362
+ input.max = param.max;
363
+ input.step = param.step || 0.01; // Default step for float
364
+ input.value = param.value;
365
+
366
+ container.appendChild(slider);
367
+ container.appendChild(input);
368
+ return container;
369
+ }
370
+
371
+ /**
372
+ * Create integer range control with dual sliders
373
+ */
374
+ function createIntegerRangeControl(name, param) {
375
+ return createRangeControl(name, param, parseInt);
376
+ }
377
+
378
+ /**
379
+ * Create float range control with dual sliders
380
+ */
381
+ function createFloatRangeControl(name, param) {
382
+ return createRangeControl(name, param, parseFloat);
383
+ }
384
+
385
+ /**
386
+ * Generic range control creator
387
+ */
388
+ function createRangeControl(name, param, converter) {
389
+ const container = document.createElement('div');
390
+ container.className = 'range-container';
391
+
392
+ // Create inputs container
393
+ const inputsContainer = document.createElement('div');
394
+ inputsContainer.className = 'range-inputs';
395
+
396
+ // Create min input
397
+ const minInput = document.createElement('input');
398
+ minInput.type = 'number';
399
+ minInput.id = `${name}-min-input`;
400
+ minInput.className = 'range-input';
401
+ minInput.min = param.min;
402
+ minInput.max = param.max;
403
+ minInput.step = param.step || (converter === parseInt ? 1 : 0.01); // Default step
404
+ minInput.value = param.value[0];
405
+
406
+ // Create max input
407
+ const maxInput = document.createElement('input');
408
+ maxInput.type = 'number';
409
+ maxInput.id = `${name}-max-input`;
410
+ maxInput.className = 'range-input';
411
+ maxInput.min = param.min;
412
+ maxInput.max = param.max;
413
+ maxInput.step = param.step || (converter === parseInt ? 1 : 0.01);
414
+ maxInput.value = param.value[1];
415
+
416
+ // Create slider container
417
+ const sliderContainer = document.createElement('div');
418
+ sliderContainer.className = 'range-slider-container';
419
+
420
+ // Create min slider
421
+ const minSlider = document.createElement('input');
422
+ minSlider.type = 'range';
423
+ minSlider.id = `${name}-min-slider`;
424
+ minSlider.className = 'range-slider min-slider';
425
+ minSlider.min = param.min;
426
+ minSlider.max = param.max;
427
+ minSlider.step = param.step || (converter === parseInt ? 1 : 0.01);
428
+ minSlider.value = param.value[0];
429
+
430
+ // Create max slider
431
+ const maxSlider = document.createElement('input');
432
+ maxSlider.type = 'range';
433
+ maxSlider.id = `${name}-max-slider`;
434
+ maxSlider.className = 'range-slider max-slider';
435
+ maxSlider.min = param.min;
436
+ maxSlider.max = param.max;
437
+ maxSlider.step = param.step || (converter === parseInt ? 1 : 0.01);
438
+ maxSlider.value = param.value[1];
439
+
440
+ // --- Event Listeners ---
441
+
442
+ // Input listeners (for real-time updates of linked elements and gradient)
443
+ minSlider.addEventListener('input', function() {
444
+ const minVal = converter(this.value);
445
+ const maxVal = converter(maxSlider.value);
446
+ if (minVal <= maxVal) {
447
+ minInput.value = minVal;
448
+ } else {
449
+ // Prevent slider crossing visually, update input to maxVal
450
+ this.value = maxVal;
451
+ minInput.value = maxVal;
452
+ }
453
+ updateSliderGradient(minSlider, maxSlider, sliderContainer);
454
+ });
455
+
456
+ maxSlider.addEventListener('input', function() {
457
+ const minVal = converter(minSlider.value);
458
+ const maxVal = converter(this.value);
459
+ if (maxVal >= minVal) {
460
+ maxInput.value = maxVal;
461
+ } else {
462
+ // Prevent slider crossing visually, update input to minVal
463
+ this.value = minVal;
464
+ maxInput.value = minVal;
465
+ }
466
+ updateSliderGradient(minSlider, maxSlider, sliderContainer);
467
+ });
468
+
469
+ // Change listeners (for updating state and triggering backend calls)
470
+ minSlider.addEventListener('change', function() {
471
+ const minVal = converter(this.value);
472
+ const maxVal = converter(maxSlider.value);
473
+
474
+ // Ensure min doesn't exceed max after potential adjustment during 'input'
475
+ if (minVal <= maxVal) {
476
+ minInput.value = minVal;
477
+ updateSliderGradient(minSlider, maxSlider, sliderContainer);
478
+ updateParameter(name, [minVal, maxVal]);
479
+ } else {
480
+ // If somehow it crossed, snap it back and update state
481
+ this.value = maxVal;
482
+ minInput.value = maxVal;
483
+ updateSliderGradient(minSlider, maxSlider, sliderContainer);
484
+ updateParameter(name, [maxVal, maxVal]);
485
+ }
486
+ });
487
+
488
+ maxSlider.addEventListener('change', function() {
489
+ const minVal = converter(minSlider.value);
490
+ const maxVal = converter(this.value);
491
+
492
+ // Ensure max doesn't go below min after potential adjustment during 'input'
493
+ if (maxVal >= minVal) {
494
+ maxInput.value = maxVal;
495
+ updateSliderGradient(minSlider, maxSlider, sliderContainer);
496
+ updateParameter(name, [minVal, maxVal]);
497
+ } else {
498
+ // If somehow it crossed, snap it back and update state
499
+ this.value = minVal;
500
+ maxInput.value = minVal;
501
+ updateSliderGradient(minSlider, maxSlider, sliderContainer);
502
+ updateParameter(name, [minVal, minVal]);
503
+ }
504
+ });
505
+
506
+ minInput.addEventListener('change', function() {
507
+ const minVal = converter(this.value);
508
+ const maxVal = converter(maxInput.value); // Use current max input value
509
+
510
+ if (!isNaN(minVal) && minVal >= param.min && minVal <= maxVal) {
511
+ minSlider.value = minVal;
512
+ updateSliderGradient(minSlider, maxSlider, sliderContainer);
513
+ updateParameter(name, [minVal, maxVal]);
514
+ } else {
515
+ // Revert input value to current state and update slider/gradient
516
+ this.value = state[name][0];
517
+ minSlider.value = state[name][0];
518
+ updateSliderGradient(minSlider, maxSlider, sliderContainer);
519
+ }
520
+ });
521
+
522
+ maxInput.addEventListener('change', function() {
523
+ const minVal = converter(minInput.value); // Use current min input value
524
+ const maxVal = converter(this.value);
525
+
526
+ if (!isNaN(maxVal) && maxVal <= param.max && maxVal >= minVal) {
527
+ maxSlider.value = maxVal;
528
+ updateSliderGradient(minSlider, maxSlider, sliderContainer);
529
+ updateParameter(name, [minVal, maxVal]);
530
+ } else {
531
+ // Revert input value to current state and update slider/gradient
532
+ this.value = state[name][1];
533
+ maxSlider.value = state[name][1];
534
+ updateSliderGradient(minSlider, maxSlider, sliderContainer);
535
+ }
536
+ });
537
+
538
+ // Assemble the control
539
+ inputsContainer.appendChild(minInput);
540
+ inputsContainer.appendChild(maxInput);
541
+
542
+ sliderContainer.appendChild(minSlider);
543
+ sliderContainer.appendChild(maxSlider);
544
+
545
+ container.appendChild(inputsContainer);
546
+ container.appendChild(sliderContainer);
547
+
548
+ // Set initial gradient state
549
+ updateSliderGradient(minSlider, maxSlider, sliderContainer);
550
+
551
+ return container;
552
+ }
553
+
554
+ /**
555
+ * Create unbounded integer control
556
+ */
557
+ function createUnboundedIntegerControl(name, param) {
558
+ const input = document.createElement('input');
559
+ input.type = 'number';
560
+ input.id = `${name}-input`;
561
+ input.value = param.value;
562
+ input.step = 1;
563
+
564
+ input.addEventListener('change', function() {
565
+ const value = parseInt(this.value, 10);
566
+ if (!isNaN(value)) {
567
+ updateParameter(name, value);
568
+ } else {
569
+ // Revert to current state if input is not a valid integer
570
+ this.value = state[name];
571
+ }
572
+ });
573
+
574
+ return input;
575
+ }
576
+
577
+ /**
578
+ * Create unbounded float control
579
+ */
580
+ function createUnboundedFloatControl(name, param) {
581
+ const input = document.createElement('input');
582
+ input.type = 'number';
583
+ input.id = `${name}-input`;
584
+ input.value = param.value;
585
+ input.step = param.step || 'any'; // Allow any decimal
586
+
587
+ input.addEventListener('change', function() {
588
+ const value = parseFloat(this.value);
589
+ if (!isNaN(value)) {
590
+ updateParameter(name, value);
591
+ } else {
592
+ // Revert to current state if input is not a valid float
593
+ this.value = state[name];
594
+ }
595
+ });
596
+
597
+ return input;
598
+ }
599
+
600
+ /**
601
+ * Create button control
602
+ */
603
+ function createButtonControl(name, param) {
604
+ const button = document.createElement('button');
605
+ button.id = `${name}-button`;
606
+ button.textContent = param.label || formatLabel(name); // Use formatLabel for default
607
+ button.className = 'button-control'; // Add a class for styling
608
+
609
+ button.addEventListener('click', function() {
610
+ // Call the handler from api.js
611
+ handleButtonClick(name);
612
+ });
613
+
614
+ return button;
615
+ }
616
+
617
+ /**
618
+ * Updates the background gradient for the dual range slider.
619
+ * @param {HTMLInputElement} minSlider - The minimum value slider element.
620
+ * @param {HTMLInputElement} maxSlider - The maximum value slider element.
621
+ * @param {HTMLElement} container - The container element holding the sliders.
622
+ */
623
+ function updateSliderGradient(minSlider, maxSlider, container) {
624
+ const rangeMin = parseFloat(minSlider.min);
625
+ const rangeMax = parseFloat(minSlider.max);
626
+ const minVal = parseFloat(minSlider.value);
627
+ const maxVal = parseFloat(maxSlider.value);
628
+
629
+ // Calculate percentages
630
+ const range = rangeMax - rangeMin;
631
+ // Prevent division by zero if min === max
632
+ const minPercent = range === 0 ? 0 : ((minVal - rangeMin) / range) * 100;
633
+ const maxPercent = range === 0 ? 100 : ((maxVal - rangeMin) / range) * 100;
634
+
635
+ // Update CSS custom properties on the container
636
+ container.style.setProperty('--min-pos', `${minPercent}%`);
637
+ container.style.setProperty('--max-pos', `${maxPercent}%`);
638
+ }
639
+
640
+ /**
641
+ * Update a control's value in the UI based on state changes.
642
+ * Exported because it's called from state.js
643
+ * @param {string} name - The name of the parameter.
644
+ * @param {*} value - The new value.
645
+ * @param {object} param - The parameter's info (potentially updated, includes type, min, max, options etc.).
646
+ */
647
+ export function updateControlValue(name, value, param) {
648
+ // Check if param info is available
649
+ if (!param) {
650
+ console.warn(`No parameter info found for ${name} during UI update.`);
651
+ return;
652
+ }
653
+
654
+ // Helper function to safely update element properties
655
+ const safeUpdate = (element, property, newValue) => {
656
+ if (element && element[property] !== newValue) {
657
+ element[property] = newValue;
658
+ }
659
+ };
660
+
661
+ // Helper function to update min/max/step for numeric inputs/sliders
662
+ const updateNumericProps = (element, param) => {
663
+ if (element) {
664
+ safeUpdate(element, 'min', param.min);
665
+ safeUpdate(element, 'max', param.max);
666
+ safeUpdate(element, 'step', param.step || (param.type === 'integer' ? 1 : (param.type === 'integer-range' ? 1 : 0.01)));
667
+ }
668
+ };
669
+
670
+ switch (param.type) {
671
+ case 'text':
672
+ case 'unbounded-integer':
673
+ case 'unbounded-float':
674
+ const inputElem = document.getElementById(`${name}-input`);
675
+ safeUpdate(inputElem, 'value', value);
676
+ // Update step for unbounded types if provided
677
+ if (param.type !== 'text') {
678
+ safeUpdate(inputElem, 'step', param.step || (param.type === 'unbounded-integer' ? 1 : 'any'));
679
+ }
680
+ break;
681
+ case 'boolean':
682
+ const checkboxElem = document.getElementById(`${name}-checkbox`);
683
+ safeUpdate(checkboxElem, 'checked', value === true);
684
+ break;
685
+ case 'integer':
686
+ case 'float':
687
+ const sliderElem = document.getElementById(`${name}-slider`);
688
+ const numInputElem = document.getElementById(`${name}-input`);
689
+ updateNumericProps(sliderElem, param);
690
+ updateNumericProps(numInputElem, param);
691
+ safeUpdate(sliderElem, 'value', value);
692
+ safeUpdate(numInputElem, 'value', value);
693
+ break;
694
+ case 'selection':
695
+ const selectElem = document.getElementById(`${name}-select`);
696
+ if (selectElem) {
697
+ // Rebuild options if they might have changed (param object might be new)
698
+ if (param.options && Array.isArray(param.options)) {
699
+ let optionsChanged = selectElem.options.length !== param.options.length;
700
+ if (!optionsChanged) {
701
+ // Quick check if values differ
702
+ for(let i=0; i < param.options.length; i++) {
703
+ if (selectElem.options[i].value !== String(param.options[i])) {
704
+ optionsChanged = true;
705
+ break;
706
+ }
707
+ }
708
+ }
709
+
710
+ if (optionsChanged) {
711
+ selectElem.innerHTML = ''; // Clear existing
712
+ param.options.forEach(option => {
713
+ const optionElement = document.createElement('option');
714
+ optionElement.value = option;
715
+ optionElement.textContent = formatLabel(String(option));
716
+ optionElement.dataset.originalType = typeof option;
717
+ if (typeof option === 'number') {
718
+ optionElement.dataset.originalValue = option;
719
+ }
720
+ selectElem.appendChild(optionElement);
721
+ });
722
+ }
723
+ }
724
+ // Update the selected value
725
+ safeUpdate(selectElem, 'value', value);
726
+ } else {
727
+ console.warn(`Select element not found for ${name}`);
728
+ }
729
+ break;
730
+ case 'multiple-selection':
731
+ const multiSelectElem = document.getElementById(`${name}-select`);
732
+ if (multiSelectElem) {
733
+ // Similar logic to rebuild options if necessary
734
+ if (param.options && Array.isArray(param.options)) {
735
+ let optionsChanged = multiSelectElem.options.length !== param.options.length;
736
+ if (!optionsChanged) {
737
+ for(let i=0; i < param.options.length; i++) {
738
+ if (multiSelectElem.options[i].value !== String(param.options[i])) {
739
+ optionsChanged = true;
740
+ break;
741
+ }
742
+ }
743
+ }
744
+
745
+ if (optionsChanged) {
746
+ multiSelectElem.innerHTML = ''; // Clear existing
747
+ param.options.forEach(option => {
748
+ const optionElement = document.createElement('option');
749
+ optionElement.value = option;
750
+ optionElement.textContent = formatLabel(String(option));
751
+ optionElement.dataset.originalType = typeof option;
752
+ if (typeof option === 'number') {
753
+ optionElement.dataset.originalValue = option;
754
+ }
755
+ multiSelectElem.appendChild(optionElement);
756
+ });
757
+ }
758
+ }
759
+ // Update selected status for all options
760
+ const valueArray = Array.isArray(value) ? value : [value]; // Ensure value is array
761
+ Array.from(multiSelectElem.options).forEach(option => {
762
+ // Check against original value if available, otherwise string value
763
+ const optionValue = option.dataset.originalValue ? parseFloat(option.dataset.originalValue) : option.value;
764
+ const shouldBeSelected = valueArray.some(val => val == optionValue); // Use loose equality for comparison after potential type conversion
765
+ safeUpdate(option, 'selected', shouldBeSelected);
766
+ });
767
+ } else {
768
+ console.warn(`Multi-select element not found for ${name}`);
769
+ }
770
+ break;
771
+ case 'integer-range':
772
+ case 'float-range':
773
+ const minSliderElem = document.getElementById(`${name}-min-slider`);
774
+ const maxSliderElem = document.getElementById(`${name}-max-slider`);
775
+ const minInputElem = document.getElementById(`${name}-min-input`);
776
+ const maxInputElem = document.getElementById(`${name}-max-input`);
777
+
778
+ updateNumericProps(minSliderElem, param);
779
+ updateNumericProps(maxSliderElem, param);
780
+ updateNumericProps(minInputElem, param);
781
+ updateNumericProps(maxInputElem, param);
782
+
783
+ if (Array.isArray(value) && value.length === 2) {
784
+ safeUpdate(minSliderElem, 'value', value[0]);
785
+ safeUpdate(maxSliderElem, 'value', value[1]);
786
+ safeUpdate(minInputElem, 'value', value[0]);
787
+ safeUpdate(maxInputElem, 'value', value[1]);
788
+
789
+ // Update the gradient background
790
+ const sliderContainer = minSliderElem ? minSliderElem.closest('.range-slider-container') : null;
791
+ if (sliderContainer && minSliderElem && maxSliderElem) {
792
+ updateSliderGradient(minSliderElem, maxSliderElem, sliderContainer);
793
+ } else {
794
+ console.warn(`Could not find elements for updating range slider gradient: ${name}`);
795
+ }
796
+ } else {
797
+ console.warn(`Invalid value format for range control ${name}:`, value);
798
+ }
799
+ break;
800
+ case 'button':
801
+ // Buttons don't have a 'value' to update in the traditional sense.
802
+ // We might update the label if param.label changed, but that's less common.
803
+ const buttonElem = document.getElementById(`${name}-button`);
804
+ if (param.label) {
805
+ safeUpdate(buttonElem, 'textContent', param.label);
806
+ }
807
+ break;
808
+ default:
809
+ // Type was already checked earlier, but good to have a default
810
+ break;
811
+ }
812
+ }