File size: 46,333 Bytes
f472fe1
 
 
 
 
 
e7d697d
f472fe1
 
e7d697d
f472fe1
e7d697d
f472fe1
621d868
 
 
 
e7d697d
f472fe1
e7d697d
f472fe1
e7d697d
f472fe1
e7d697d
f472fe1
e7d697d
f472fe1
e7d697d
f472fe1
e7d697d
f472fe1
e7d697d
f472fe1
e7d697d
f472fe1
e7d697d
f472fe1
e7d697d
f472fe1
e7d697d
c2f201c
f472fe1
 
e7d697d
 
 
 
 
f472fe1
e7d697d
 
 
 
 
 
 
 
f472fe1
 
e7d697d
 
f472fe1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e7d697d
f472fe1
 
 
 
e7d697d
f472fe1
 
 
 
 
 
 
 
 
 
 
 
 
 
e7d697d
185559e
 
 
 
e7d697d
185559e
 
 
 
 
 
 
f472fe1
185559e
 
 
f472fe1
 
 
 
 
 
 
 
185559e
 
f472fe1
 
e7d697d
 
f472fe1
e7d697d
f472fe1
 
 
 
 
 
 
 
 
 
 
e7d697d
 
f472fe1
621d868
f472fe1
 
 
 
 
 
e7d697d
f472fe1
 
 
 
 
 
 
e7d697d
 
 
f472fe1
 
185559e
 
f472fe1
185559e
f472fe1
185559e
 
 
f472fe1
 
 
e7d697d
f472fe1
e7d697d
 
 
f472fe1
 
 
 
 
e7d697d
f472fe1
 
 
 
 
 
 
 
 
 
 
 
e7d697d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f472fe1
e01363c
 
 
 
 
 
 
 
 
 
 
f472fe1
e7d697d
 
f472fe1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c2f201c
f472fe1
 
 
e7d697d
 
 
 
 
f472fe1
 
 
 
 
 
 
 
 
 
 
e7d697d
 
 
 
 
 
 
621d868
e7d697d
f472fe1
346c3ed
 
 
f472fe1
 
e7d697d
f472fe1
 
 
 
 
 
 
 
 
 
e7d697d
621d868
f472fe1
 
 
621d868
 
 
f472fe1
 
e7d697d
f472fe1
 
621d868
f472fe1
 
 
621d868
 
 
 
 
 
f472fe1
 
 
 
 
51bac51
 
185559e
 
 
 
 
381625b
51bac51
185559e
 
381625b
185559e
 
 
51bac51
185559e
51bac51
185559e
 
51bac51
185559e
51bac51
 
185559e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51bac51
185559e
51bac51
 
 
185559e
51bac51
185559e
51bac51
185559e
 
 
 
 
51bac51
185559e
 
 
 
51bac51
185559e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51bac51
 
185559e
 
 
 
 
 
 
 
 
 
 
51bac51
 
185559e
e01363c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185559e
36d6a44
185559e
 
 
 
 
 
 
 
 
 
 
 
 
 
51bac51
f472fe1
 
 
 
 
 
 
 
 
 
 
 
e7d697d
f472fe1
 
 
 
 
 
e7d697d
f472fe1
 
 
d6d67fb
f472fe1
621d868
f472fe1
 
 
 
 
 
 
 
4b5a5aa
e7d697d
f472fe1
621d868
 
 
f472fe1
 
d6d67fb
f472fe1
 
 
 
e7d697d
f472fe1
 
621d868
f472fe1
 
d6d67fb
f472fe1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381625b
f472fe1
 
 
381625b
f472fe1
 
 
 
 
 
 
 
 
 
 
 
 
381625b
f472fe1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36d6a44
f472fe1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36d6a44
f472fe1
 
 
 
 
 
 
 
 
 
 
 
36d6a44
f472fe1
 
 
 
 
 
 
 
e7d697d
f472fe1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381625b
 
983e286
f472fe1
 
 
 
 
 
381625b
f472fe1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e7d697d
f472fe1
36d6a44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e01363c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f472fe1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
<script lang="ts">
    // --- Imports ---
    import { Block } from "@gradio/atoms";
    import { StatusTracker } from "@gradio/statustracker";
    import type { LoadingStatus } from "@gradio/statustracker";
    import type { Gradio } from "@gradio/utils";
    import { onMount, tick } from "svelte";

    // --- Component Props (passed from Gradio backend) ---
    /** The main data structure driving the UI, an array of property groups. */
    export let value: Array<{ group_name: string, properties: any[] }> = [];
    /** The main label for the component, displayed in the top-level accordion header. */
    export let label: string | undefined = undefined;
    /** The label for the root group of properties, displayed when there is only one group. */
    export let show_group_name_only_one: boolean = true;
    /** If true, disables the accordion functionality, showing all properties without grouping. */
    export let disable_accordion: boolean = false;
    /** Controls the overall visibility of the component. */
    export let visible: boolean = true;
    /** If true, the main accordion is open by default. */
    export let open: boolean = true;
    /** The DOM element ID. */
    export let elem_id: string = "";
    /** Custom CSS classes for the root element. */
    export let elem_classes: string[] = [];
    /** If true, wraps the component in a container with a background. */
    export let container: boolean = false;
    /** The relative size of the component in its container. */
    export let scale: number | null = null;
    /** The minimum width of the component in pixels. */
    export let min_width: number | undefined = undefined;
    /** The fixed width of the component in pixels. */
    export let width: number | undefined = undefined;
    /** The maximum height of the component's content area before scrolling. */
    export let height: number | undefined = undefined;
    /** The loading status object from Gradio. */
    export let loading_status: LoadingStatus | undefined = undefined;
    /** If false, all controls are disabled. */
    export let interactive: boolean = true;
    /** The Gradio event dispatcher instance. */
    export let gradio: Gradio<{ change: any, reset: any, input: any, clear_status: never, expand: never, collapse: never, undo: any }>;

    // --- Internal State ---
    /** Tracks the open/closed state of each individual property group. */
    let groupVisibility: Record<string, boolean> = {};
    /** Holds references to slider input elements for direct DOM manipulation (e.g., setting background). */
    let sliderElements: Record<string, HTMLInputElement> = {};
    /** Combines default and user-provided CSS classes. */
    $: final_classes = ["propertysheet-wrapper", ...elem_classes];
    /** Tracks the validation status (true/false) for each property to apply error styling. */
    let validationState: Record<string, boolean> = {};
    /** A stringified version of the `value` prop, used to detect changes from the backend. */
    let lastValue: string | null = null;
    /** A flag to prevent event loops during property reset operations. */
    let isResetting: boolean = false;
    /** A snapshot of the initial values of all properties, used for the reset functionality. */
    let initialValues: Record<string, any> = {};

    /**

     * Validates a numeric property against its `minimum` and `maximum` constraints.

     * Updates the `validationState` for CSS styling of invalid inputs.

     * @param {any} prop - The property object to validate.

     */
    function validate_prop(prop: any) {
        if (prop.minimum === undefined && prop.maximum === undefined) {
            if (validationState[prop.name] !== true) {
                validationState[prop.name] = true;
                validationState = { ...validationState };
            }
            return;
        }
        const numValue = Number(prop.value);
        let is_valid = true;
        if (prop.minimum !== undefined && numValue < prop.minimum) is_valid = false;
        if (prop.maximum !== undefined && numValue > prop.maximum) is_valid = false;
        if (validationState[prop.name] !== is_valid) {
            validationState[prop.name] = is_valid;
            validationState = { ...validationState };
        }
    }

    /** Controls the component's content height based on the main accordion's open/closed state. */
    let dynamic_height: number | undefined;
    $: dynamic_height = open ? height : undefined;

    /**

     * Iterates through all properties and updates the background visuals of any sliders.

     */
    function updateAllSliders() {
        if (!Array.isArray(value)) return;
        for (const group of value) {
            if (Array.isArray(group.properties)) {
                for (const prop of group.properties) {
                    if (prop.component === 'slider' && sliderElements[prop.name]) {
                        updateSliderBackground(prop, sliderElements[prop.name]);
                    }
                }
            }
        }
    }

    /**

     * Reactive block that triggers whenever the `value` prop changes from the backend

     * OR from a user interaction within the component (like a checkbox toggle).

     * It initializes group visibility, validates properties, and critically,

     * re-updates slider visuals after the DOM has been updated.

     */
    $: if (Array.isArray(value)) {
        if (JSON.stringify(value) !== lastValue) {
            lastValue = JSON.stringify(value);           
            for (const group of value) {
                if (groupVisibility[group.group_name] === undefined) {
                    groupVisibility[group.group_name] = true;
                }
            }
        }
                
        for (const group of value) {
            if (Array.isArray(group.properties)) {
                for (const prop of group.properties) {
                    if (prop.component?.startsWith("number") || prop.component === 'slider') {
                        validate_prop(prop);
                    }
                }
            }
        }
       
        tick().then(updateAllSliders);
    }
    /**

     * Updates a slider's track background to visually represent its value as a percentage.

     * It sets the `--slider-progress` CSS custom property.

     * @param {any} prop - The slider property object.

     * @param {HTMLInputElement} element - The slider's input element.

     */
    function updateSliderBackground(prop: any, element: HTMLInputElement) {
        if (!element) return;
        const min = prop.minimum ?? 0;
        const max = prop.maximum ?? 100;
        const val = Number(prop.value);
        const percentage = val <= min ? 0 : ((val - min) * 100) / (max - min);
        element.style.setProperty('--slider-progress', `${percentage}%`);
    }

    /**

     * Handles the main accordion toggle (the component's top-level header).

     * Dispatches 'expand' or 'collapse' events to Gradio.

     */
    function handle_toggle() {        
        open = !open;
        if (open) gradio.dispatch("expand");
        else gradio.dispatch("collapse");
    }

    /**

     * Toggles the visibility of an individual property group (e.g., "Model", "Sampling").

     * @param {string} groupName - The name of the group to toggle.

     */
    function toggleGroup(groupName: string) {
        groupVisibility[groupName] = !groupVisibility[groupName];
    }
    
    /**

     * Utility function to find the current value of a specific property by its name.

     * Used to implement the `interactive_if` logic.

     * @param {string} prop_name - The name of the property to find.

     */
    function get_prop_value(prop_name: string) {
        if (!Array.isArray(value) || !prop_name) return undefined;
                
        for (const group of value) {
            if (!Array.isArray(group.properties)) continue;        
            const found_prop = group.properties.find(p => p.name === prop_name);
            if (found_prop) {
                return found_prop.value;
            }
        }
        return undefined;
    }

    /**

     * Dispatches a single-property update to the Gradio backend.

     * Used for simple inputs like textboxes, sliders, and checkboxes.

     * Creates a small payload like `{ 'prop_name': new_value }`.

     * @param {"change" | "input"} event_name - The type of event to dispatch.

     * @param {any} changed_prop - The property object that was modified.

     */
    function dispatch_update(event_name: "change" | "input", changed_prop: any) {
        if (validationState[changed_prop.name] === false) {
            return;
        }
        const payload: Record<string, any> = {};
        let final_value = changed_prop.value;
        if (changed_prop.component?.startsWith("number") || changed_prop.component === "slider") {
            final_value = Number(changed_prop.value);
        } else if (changed_prop.component === "checkbox") {
            final_value = changed_prop.value;
        }
        payload[changed_prop.name] = final_value;
        gradio.dispatch(event_name, payload);
    }
    
    /**

     * Handles changes from a dropdown (`select`) element.

     * It updates the local `value` array and then dispatches the *entire updated object*

     * to the backend. This is a robust way to ensure state consistency.

     * @param {Event} event - The DOM change event.

     * @param {any} prop_to_change - The property object being modified.

     */
    async function handle_dropdown_change(event: Event, prop_to_change: any) {
        const new_prop_value = (event.target as HTMLSelectElement).value;

        // Recreate the entire `value` array to ensure Svelte's reactivity.
        value = value.map(group => {
            if (!group.properties) return group;
            return {
                ...group,
                properties: group.properties.map(prop => {
                    if (prop.name === prop_to_change.name) {
                        return { ...prop, value: new_prop_value };
                    }
                    return prop;
                })
            };
        });

        // Wait for Svelte to process the DOM update before dispatching.
        await tick();
        // Dispatch the full, updated value object to the backend.
        gradio.dispatch("change", value);
    }
    /**

     * Handles changes from a multiselect checkbox group.

     * The `bind:group` directive already updated the local `prop.value` array.

     * We just need to dispatch the entire component's state to the backend.

     */
    async function handle_multiselect_change() {
        // Wait for Svelte to process the binding update.
        await tick();
        // Dispatch the full, updated value object to the backend.
        gradio.dispatch("change", value);
    }
    /**

     * Resets a single property to its initial value, which was stored on mount.

     * It dispatches the entire updated `value` object to the backend.

     * @param {string} propName - The name of the property to reset.

     */
    function handle_reset_prop(propName: string) {
        if (isResetting) return;
        isResetting = true;
        if (!(propName in initialValues)) {
            isResetting = false;
            return;
        }
        let updatedValue = value.map(group => {
            if (group.properties) {
                group.properties = group.properties.map(prop => {
                    if (prop.name === propName) {
                        return { ...prop, value: initialValues[propName] };
                    }
                    return prop;
                });
            }
            return group;
        });
        value = updatedValue;
        gradio.dispatch("undo", updatedValue);
        setTimeout(() => { isResetting = false; }, 100);
    }

    /**

     * Creates a snapshot of the initial values of all properties.

     * This snapshot is used by the reset functionality.

     */
    function storeInitialValues() {
        lastValue = JSON.stringify(value);
        if (Array.isArray(value)) {
            value.forEach(group => {
                if (Array.isArray(group.properties)) {
                    group.properties.forEach(prop => {
                        initialValues[prop.name] = prop.value;
                    });
                }
            });
        }
        
        // Ensure sliders are visually updated on initial load.
        setTimeout(updateAllSliders, 50);
    }

    /**

     * Lifecycle hook that runs when the component is first added to the DOM.

     */
    onMount(() => {       
        storeInitialValues();
    });
    $: if (open && groupVisibility) {     
        tick().then(updateAllSliders);
    }
</script>

<!-- The HTML template renders the component's UI based on the `value` prop. -->

<Block {visible} {elem_id} elem_classes={final_classes} {container} {scale} {min_width} {width}>

    {#if loading_status}

        <StatusTracker

            autoscroll={gradio.autoscroll}

            i18n={gradio.i18n}

            {...loading_status}

            on:clear_status={() => gradio.dispatch("clear_status")}

        />

    {/if}



    <!-- Main accordion header that toggles the entire component's content -->
    <button class="accordion-header" on:click={handle_toggle} disabled={disable_accordion}>

        {#if label}

            <span class="label">{label}</span>

        {/if}

        {#if !disable_accordion}

            <span class="accordion-icon" style:transform={open ? "rotate(0)" : "rotate(-90deg)"}></span>

        {/if}        

    </button>
    
    <!-- Content wrapper that is shown or hidden based on the 'open' state -->
    <div class:closed={!open} class="content-wrapper">

        {#if open}

            <div class="container" style="--show-group-name: {value.length > 1 || (show_group_name_only_one && value.length === 1) ? 'none' : '1px solid var(--border-color-primary)'}; --sheet-max-height: {height ? `${height}px` : 'none'}">

                {#if Array.isArray(value)}

                    <!-- Loop through each property group -->

                    {#each value as group (group.group_name)}

                        {#if value.length > 1 || (show_group_name_only_one && value.length === 1)}

                            <button class="group-header" on:click={() => toggleGroup(group.group_name)}>

                                <span class="group-title">{group.group_name}</span>

                                <span class="group-toggle-icon">{groupVisibility[group.group_name] ? '−' : '+'}</span>

                            </button>

                        {/if}    

                        {#if groupVisibility[group.group_name]}

                            <div class="properties-grid">

                                <!-- Loop through each property within a group -->

                                {#if Array.isArray(group.properties)}

                                    {#each group.properties as prop (prop.name)}

                                        {#if prop.visible ?? true}    

                                            <!-- Conditional interactivity based on another property's value -->

                                            {@const i_condition = prop.interactive_if}

                                            {@const v_condition = prop.visible_if}

                                            

                                            {@const i_parent_value = i_condition ? get_prop_value(i_condition.field) : null}

                                            {@const v_parent_value = v_condition ? get_prop_value(v_condition.field) : null}



                                            {@const is_interactive = interactive && (

                                                !i_condition ? 

                                                    true

                                                :

                                                i_condition.value !== undefined ?

                                                    Array.isArray(i_condition.value) ?

                                                        i_condition.value.includes(i_parent_value)

                                                    :

                                                        i_parent_value === i_condition.value

                                                :

                                                i_condition.neq !== undefined ?

                                                    i_parent_value !== i_condition.neq

                                                :

                                                    true

                                            )}

                                            

                                            {@const is_visible = (prop.visible ?? true) && (

                                                !v_condition ?

                                                    true

                                                :

                                                v_condition.value !== undefined ?

                                                    Array.isArray(v_condition.value) ?

                                                        v_condition.value.includes(v_parent_value)

                                                    :

                                                        v_parent_value === v_condition.value

                                                :

                                                v_condition.neq !== undefined ?

                                                    v_parent_value !== v_condition.neq

                                                :

                                                    true // Fallback

                                            )}

                                            {#if is_visible}

                                                <label class="prop-label" for={prop.name}>

                                                    <div class="prop-label-wrapper">

                                                        <span>{prop.label}</span>

                                                        <!-- Help tooltip -->

                                                        {#if prop.help}

                                                            <div class="tooltip-container">

                                                                <span class="tooltip-icon">?</span>

                                                                <span class="tooltip-text">{prop.help}</span>

                                                            </div>

                                                        {/if}

                                                    </div>

                                                </label>

                                                

                                                <div class="prop-control">

                                                    <!-- Dynamically render the correct input component based on `prop.component` -->

                                                    {#if prop.component === 'string'}

                                                        <input 

                                                            type="text" 

                                                            bind:value={prop.value} 

                                                            disabled={!is_interactive} 

                                                            on:change={() => dispatch_update("change", prop)} 

                                                            on:input={() => dispatch_update("input", prop)} 

                                                        />

                                                    {:else if prop.component === 'password'}

                                                        <input 

                                                            type="password" 

                                                            bind:value={prop.value} 

                                                            disabled={!is_interactive} 

                                                            on:change={() => dispatch_update("change", prop)} 

                                                            on:input={() => dispatch_update("input", prop)} 

                                                        />

                                                    {:else if prop.component === 'checkbox'}                                                

                                                        <input                                                         

                                                            type="checkbox" 

                                                            bind:checked={prop.value} 

                                                            disabled={!is_interactive} 

                                                            on:change={() => dispatch_update("change", prop)} 

                                                        />                                            

                                                    {:else if prop.component === 'number_integer' || prop.component === 'number_float'}

                                                        <input 

                                                            class:invalid={validationState[prop.name] === false}

                                                            class:disabled={!is_interactive}

                                                            type="number" 

                                                            step={prop.step || 1} 

                                                            bind:value={prop.value} 

                                                            disabled={!is_interactive} 

                                                            on:change={() => dispatch_update("change", prop)} 

                                                            on:input={() => {

                                                                validate_prop(prop);

                                                                dispatch_update("input", prop);

                                                            }}

                                                        />

                                                    {:else if prop.component === 'slider'}

                                                        <div class="slider-container" class:disabled={!is_interactive}>

                                                            <input 

                                                                type="range" 

                                                                min={prop.minimum} 

                                                                max={prop.maximum} 

                                                                step={prop.step || 1} 

                                                                bind:value={prop.value} 

                                                                bind:this={sliderElements[prop.name]}

                                                                disabled={!is_interactive} 

                                                                on:input={() => {

                                                                    validate_prop(prop);

                                                                    updateSliderBackground(prop, sliderElements[prop.name]);

                                                                    dispatch_update("input", prop);

                                                                }} 

                                                                on:change={() => dispatch_update("change", prop)} 

                                                            />

                                                            <span class="slider-value">{prop.value}</span>

                                                        </div>										

                                                    {:else if prop.component === 'colorpicker'}

                                                        <div class="color-picker-container" class:disabled={!is_interactive}>

                                                            <input 

                                                                type="color" 

                                                                class="color-picker-input"

                                                                bind:value={prop.value}

                                                                disabled={!is_interactive}

                                                                on:change={() => dispatch_update("change", prop)}

                                                            />

                                                            <span class="color-picker-value">{prop.value}</span>

                                                        </div>

                                                    {:else if prop.component === 'dropdown'}

                                                        <div class="dropdown-wrapper" class:disabled={!is_interactive}>

                                                            <select 

                                                                disabled={!is_interactive} 

                                                                value={prop.value}

                                                                on:change={(e) => handle_dropdown_change(e, prop)}

                                                            >

                                                                {#if Array.isArray(prop.choices)}

                                                                    {#each prop.choices as choice}

                                                                        <option value={choice} selected={prop.value === choice}>

                                                                            {choice}

                                                                        </option>

                                                                    {/each}

                                                                {/if}

                                                            </select>

                                                            <div class="dropdown-arrow-icon"></div>												

                                                        </div>

                                                    {:else if prop.component === 'radio'}

                                                        <div class="radio-group" class:disabled={!is_interactive} on:change={() => dispatch_update('change', prop)}>

                                                            {#if Array.isArray(prop.choices)}

                                                                {#each prop.choices as choice}

                                                                    <div class="radio-item">

                                                                        <input 

                                                                            type="radio" 

                                                                            id="{prop.name}-{choice}" 

                                                                            name={prop.name}

                                                                            value={choice}

                                                                            bind:group={prop.value}

                                                                            disabled={!is_interactive}

                                                                        >

                                                                        <label for="{prop.name}-{choice}">{choice}</label>

                                                                    </div>

                                                                {/each}

                                                            {/if}

                                                        </div>

                                                    {:else if prop.component === 'multiselect_checkbox'}

                                                        <div class="multiselect-group" class:disabled={!is_interactive}>

                                                            {#if Array.isArray(prop.choices)}

                                                                {#each prop.choices as choice}

                                                                    <div class="multiselect-item">

                                                                        <input 

                                                                            type="checkbox"

                                                                            id="{prop.name}-{choice}"

                                                                            value={choice}

                                                                            bind:group={prop.value}

                                                                            disabled={!is_interactive}

                                                                            on:change={() => handle_multiselect_change()}

                                                                        >

                                                                        <label for="{prop.name}-{choice}">{choice}</label>

                                                                    </div>

                                                                {/each}

                                                            {/if}

                                                        </div>

                                                    {/if}



                                                    <!-- Reset button, visible only when the current value differs from the initial value -->

                                                    {#if prop.component !== 'checkbox'}

                                                        <button 

                                                            class="reset-button-prop" 

                                                            class:visible={initialValues[prop.name] !== prop.value}

                                                            title="Reset to default" 

                                                            on:click|stopPropagation={() => handle_reset_prop(prop.name)}

                                                            disabled={!is_interactive}

                                                        >


                                                        </button>

                                                    {/if}

                                                </div>

                                            {/if}

                                        {/if}

                                    {/each}

                                {/if}

                            </div>

                        {/if}

                    {/each}

                {/if}

            </div>

        {/if}

    </div>

</Block>



<style>

    /* All styles remain the same and are included for completeness */

    :host {

        display: flex;

        flex-direction: column;

        height: 100%;

    }

    :global(.propertysheet-wrapper) {

        overflow: hidden !important;

        display: flex;

        flex-direction: column;

        flex-grow: 1;

        padding: var(--spacing-lg) !important;

    }

    .accordion-header {    

        display: flex;

        justify-content: space-between;

        align-items: center;

        width: 100%;

        cursor: pointer;

        padding: var(--block-title-padding);

        background: var(--block-title-background-fill);

        color: var(--block-title-text-color);

        border-width: 0;

        flex-shrink: 0;

    }

    .accordion-icon{

        margin-left: auto;

    }

    .content-wrapper {

        flex-grow: 1;

        min-height: 0;        

    }

    .container {

        overflow-y: auto;

        height: auto; 

        max-height: var(--sheet-max-height, 500px);

        border-radius: 0 !important;

        border: 1px solid var(--border-color-primary);

        border-top: var(--show-group-name);

        border-bottom-left-radius: var(--radius-lg);

        border-bottom-right-radius: var(--radius-lg);

        background-color: var(--background-fill-secondary);        

    }

    .closed {

        display: none;

    }

    .group-header {

        display: flex;

        justify-content: space-between;

        align-items: center;

        width: 100%;

        padding: var(--spacing-sm) var(--spacing-md);

        background-color: var(--input-background-fill);

        color: var(--body-text-color);

        text-align: left;

        cursor: pointer;

        font-size: var(--text-md);

        font-weight: var(--font-weight-bold);

        border: 1px solid var(--border-color-primary);

    }

    .properties-grid {

        display: grid;

        grid-template-columns: 1fr 2fr;

        gap: 0;

        padding: 0;

    }

    .prop-label,

    .prop-control {

        padding: var(--spacing-sm) var(--spacing-md);

        display: flex;

        align-items: center;

        border-bottom: 1px solid var(--background-fill-secondary);

    }

  

    .prop-label {

        background-color: var(--background-fill-primary);

        color: var(--body-text-color);

        opacity: 3.7;

        font-weight: var(--font-weight-semibold);

        font-size: var(--text-xs);

        text-align: right;

        justify-content: flex-end;

        word-break: break-word;

    }

    .prop-control {

        gap: var(--spacing-sm);

    }

    .properties-grid > :nth-last-child(-n+2) {

        border-bottom: none;

    }

    .prop-control input[type="text"],

    .prop-control input[type="password"],

    .prop-control input[type="number"] {

        background-color: var(--input-background-fill);

        border: var(--input-border-width) solid var(--border-color-primary);

        box-shadow: var(--input-shadow);

        color: var(--input-text-color);

        font-size: var(--input-text-size);

        border-radius: 0;

        width: 100%;

        padding-top: var(--spacing-1);

        padding-bottom: var(--spacing-1);

        padding-left: var(--spacing-md);

        padding-right: var(--spacing-3);

    }

    .prop-control input[type="text"]:focus,

    .prop-control input[type="number"]:focus {

        box-shadow: var(--input-shadow-focus);

        border-color: var(--input-border-color-focus);

        background-color: var(--input-background-fill-focus);

        outline: none;

    }

    .dropdown-wrapper {

        position: relative;

        width: 100%;

    }

    .dropdown-wrapper select {

        -webkit-appearance: none;

        appearance: none;

        background-color: var(--input-background-fill);

        border: var(--input-border-width) solid var(--border-color-primary);

        box-shadow: var(--input-shadow);

        color: var(--input-text-color);

        font-size: var(--input-text-size);

        width: 100%;

        cursor: pointer;

        border-radius: 0;

        padding-top: var(--spacing-1);

        padding-bottom: var(--spacing-1);

        padding-left: var(--spacing-md);

        padding-right: calc(var(--spacing-3) + 1.2em);

    }

    .dropdown-arrow-icon {

        position: absolute;

        top: 50%;

        right: var(--spacing-3);

        transform: translateY(-50%);

        width: 1em;

        height: 1em;

        pointer-events: none;

        z-index: 1;

        background-color: var(--body-text-color-subdued);

        -webkit-mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M6 8l4 4 4-4'/%3e%3c/svg%3e");

        mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M6 8l4 4 4-4'%3e%3c/svg%3e");

    }

    .dropdown-wrapper select:focus {

        box-shadow: var(--input-shadow-focus);

        border-color: var(--input-border-color-focus);

        background-color: var(--input-background-fill-focus);

        outline: none;

    }

    .dropdown-wrapper select option {

        background: var(--input-background-fill);

        color: var(--body-text-color);

    }

    .prop-control input[type="checkbox"] {

        -webkit-appearance: none;

        appearance: none;

        position: relative;

        width: var(--size-4);

        height: var(--size-4);

        border-radius: 5px !important;

        border: 1px solid var(--checkbox-border-color);

        background-color: var(--checkbox-background-color);

        box-shadow: var(--checkbox-shadow);

        cursor: pointer;

        margin: 0;

        transition: background-color 0.2s, border-color 0.2s;

    }

    .prop-control input[type="checkbox"]:hover {

        border-color: var(--checkbox-border-color-hover);

        background-color: var(--checkbox-background-color-hover);

    }

    .prop-control input[type="checkbox"]:focus {

        border-color: var(--checkbox-border-color-focus);

        background-color: var(--checkbox-background-color-focus);

        outline: none;

    }

    .prop-control input[type="checkbox"]:checked {

        background-color: var(--checkbox-background-color-selected);

        border-color: var(--checkbox-border-color-focus);        

    }

    .prop-control input[type="checkbox"]:checked::after {

        content: "";

        position: absolute;

        display: block;

        top: 50%;

        left: 50%;

        width: 4px;

        height: 8px;

        border: solid var(--checkbox-label-text-color-selected);

        border-width: 0 2px 2px 0;

        transform: translate(-50%, -60%) rotate(45deg);

        

    }

    .slider-container {

        display: flex;

        align-items: center;

        gap: var(--spacing-md);

        width: 100%;

    }

    .slider-container input[type="range"] {

        --slider-progress: 0%;

        -webkit-appearance: none;

        appearance: none;

        background: transparent;

        cursor: pointer;

        width: 100%;

    }

    .slider-container input[type="range"]::-webkit-slider-runnable-track {

        height: 8px;

        border-radius: var(--radius-lg);

        background: linear-gradient( to right, var(--slider-color) var(--slider-progress), var(--input-background-fill) var(--slider-progress) );

    }

    .slider-container input[type="range"]::-webkit-slider-thumb {

        -webkit-appearance: none;

        appearance: none;

        margin-top: -4px;

        background-color: white;

        border-radius: 50%;

        height: 16px;

        width: 16px;

        border: 1px solid var(--border-color-primary);

        box-shadow: var(--shadow-drop);

    }

    .slider-container input[type="range"]::-moz-range-track {

        height: 8px;

        border-radius: var(--radius-lg);

        background: linear-gradient( to right, var(--slider-color) var(--slider-progress), var(--input-background-fill) var(--slider-progress) );

    }

    .slider-container input[type="range"]::-moz-range-thumb {

        background-color: white;

        border-radius: 50%;

        height: 16px;

        width: 16px;

        border: 1px solid var(--border-color-primary);

        box-shadow: var(--shadow-drop);

    }

    .slider-value {

        min-width: 40px;

        text-align: right;

        font-family: var(--font-mono);

        font-size: var(--text-xs);

    }

    .prop-label-wrapper {

        display: flex;

        justify-content: flex-end;

        align-items: center;

        gap: var(--spacing-sm);

        width: 100%;

    }

    .tooltip-container {

        position: relative;

        display: inline-flex;

        align-items: center;

        justify-content: center;

    }

    .tooltip-icon {

        display: flex;

        align-items: center;

        justify-content: center;

        width: 14px;

        height: 14px;

        border-radius: 50%;

        background-color: var(--body-text-color-subdued);

        color: var(--background-fill-primary);

        font-size: 10px;

        font-weight: bold;

        cursor: help;

        user-select: none;

    }

    .tooltip-text {

        visibility: hidden;

        width: 200px;

        background-color: var(--body-text-color);

        color: var(--background-fill-primary);

        text-align: center;

        border-radius: var(--radius-md);

        padding: var(--spacing-md);

        position: absolute;

        z-index: 50;

        bottom: -50%;

        left: 100%;        

        transform: translateX(-50%);

        opacity: 0;

        transition: opacity 0.3s;

    }

    .tooltip-container:hover .tooltip-text {

        visibility: visible;

        opacity: 1.0;

    }

	.color-picker-container {

		display: flex;

		align-items: center;

		gap: var(--spacing-md);

		width: 100%;

	}

	.color-picker-input {

		width: 50px;

		height: 28px;

		background-color: transparent;

		border: 1px solid var(--border-color-primary);

		border-radius: var(--radius-sm);

		cursor: pointer;

		padding: 0;

	}

	.color-picker-input::-webkit-color-swatch-wrapper {

		padding: 2px;

	}

	.color-picker-input::-moz-padding {

	    padding: 2px;

	}

	.color-picker-input::-webkit-color-swatch {

		border: none;

		border-radius: var(--radius-sm);

	}

	.color-picker-input::-moz-color-swatch {

		border: none;

		border-radius: var(--radius-sm);

	}

	.color-picker-value {

		font-family: var(--font-mono);

		font-size: var(--text-sm);

		color: var(--body-text-color-subdued);

	}

    .prop-control input.invalid {

        border-color: var(--error-border-color, red) !important;

        box-shadow: 0 0 0 1px var(--error-border-color, red) !important;

    }

    .reset-button-prop {

        display: flex;

        align-items: center;

        justify-content: center;

        background: none;

        border: none;

        border-left: 1px solid var(--border-color-primary);

        cursor: pointer;

        color: var(--body-text-color-subdued);

        font-size: var(--text-lg);

        padding: 0 var(--spacing-2);

        visibility: hidden;

        opacity: 0;

        transition: opacity 150ms ease-in-out, color 150ms ease-in-out;

    }

    .reset-button-prop.visible {

        visibility: visible;

        opacity: 1;

    }

    .reset-button-prop:hover {

        color: var(--body-text-color);

        background-color: var(--background-fill-secondary-hover);

    }

    .reset-button-prop:disabled {

	    color: var(--body-text-color-subdued) !important;

	    opacity: 0.5;

	    cursor: not-allowed;

	    background-color: transparent !important;

	}

    .prop-control .disabled {

		opacity: 0.5;

		pointer-events: none;

		cursor: not-allowed;

	}

	.prop-control .disabled input {

		cursor: not-allowed;

	}

	.reset-button-prop:disabled {

	    opacity: 0.3;

	    cursor: not-allowed;

	    background-color: transparent !important;

	}

    

    .radio-group {

		display: flex;

		flex-wrap: wrap;

		gap: var(--spacing-sm);

		width: 100%;

	}

	.radio-item input[type="radio"] {

		/* Hide the default radio button */

		display: none;

	}

	.radio-item label {

		display: inline-block;

		padding: var(--spacing-xs) var(--spacing-md);

		border: 1px solid var(--border-color-primary);

        border-radius: 5px !important;

		background-color: var(--input-background-fill);

		color: var(--body-text-color);

		font-size: var(--text-xs);

		cursor: pointer;

		user-select: none;

		transition: background-color 0.2s, border-color 0.2s, color 0.2s;

	}

	.radio-group.disabled .radio-item label {

		cursor: not-allowed;

	}

	.radio-item input[type="radio"]:hover + label {

		border-color: var(--border-color-accent-subdued);

		background-color: var(--background-fill-secondary-hover);

	}

	.radio-item input[type="radio"]:checked + label {

		background-color: var(--primary-500);

		border-color: var(--primary-500);

		color: white;

		font-weight: var(--font-weight-bold);

	}

	.radio-group.disabled .radio-item input[type="radio"]:checked + label {

		background-color: var(--neutral-300);

		border-color: var(--neutral-300);

		color: var(--neutral-500);

	}

    .multiselect-group {

        display: flex;

        flex-direction: column;

        gap: var(--spacing-sm);

        width: 100%;

        max-height: 150px; /* Or a height that fits your design */

        overflow-y: auto;

        border: 1px solid var(--border-color-primary);

        padding: var(--spacing-sm);

        background-color: var(--input-background-fill);

    }



    .multiselect-item {

        display: flex;

        align-items: center;

        gap: var(--spacing-sm);

    }



    .multiselect-item label {

        font-size: var(--text-sm);

        color: var(--body-text-color);

        cursor: pointer;

        user-select: none;

    }



    .multiselect-group.disabled .multiselect-item label {

        cursor: not-allowed;

    }

</style>