basecoat-cli 0.3.10-beta.1 → 0.3.10

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.
@@ -83,12 +83,10 @@
83
83
  </footer>
84
84
  {% endif %}
85
85
  {% if close_button %}
86
- <form method="dialog">
87
- <button aria-label="Close dialog">
88
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
86
+ <button type="button" aria-label="Close dialog" onclick="this.closest('dialog').close()">
87
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
89
88
  </button>
90
- </form>
91
89
  {% endif %}
92
90
  </div>
93
91
  </dialog>
94
- {% endmacro %}
92
+ {% endmacro %}
@@ -6,6 +6,7 @@
6
6
  @param name {string} [optional] - The name attribute for the hidden input storing the selected value.
7
7
  @param multiple {boolean} [optional] [default=false] - Enables multiple selection mode.
8
8
  @param placeholder {string} [optional] - Placeholder text shown when no options are selected (multiple mode only).
9
+ @param close_on_select {boolean} [optional] [default=false] - Closes the popover when selecting an option in multiple mode.
9
10
  @param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
10
11
  @param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
11
12
  @param popover_attrs {object} [optional] - Additional HTML attributes for the popover content div.
@@ -21,6 +22,7 @@
21
22
  items=[],
22
23
  multiple=false,
23
24
  placeholder=None,
25
+ close_on_select=false,
24
26
  main_attrs={},
25
27
  trigger_attrs={},
26
28
  popover_attrs={},
@@ -31,7 +33,13 @@
31
33
  ) %}
32
34
  {% set id = id or ("select-" + (range(100000, 999999) | random | string)) %}
33
35
 
34
- {% set selectedArray = ((selected if (selected is iterable and selected is not string) else [selected]) if selected else []) %}
36
+ {% if selected is defined and selected is iterable and selected is not string %}
37
+ {% set selectedArray = selected %}
38
+ {% elif selected is defined %}
39
+ {% set selectedArray = [selected] %}
40
+ {% else %}
41
+ {% set selectedArray = [] %}
42
+ {% endif %}
35
43
  {% set first_option = namespace(item=None) %}
36
44
  {% set selected_options = namespace(items=[]) %}
37
45
 
@@ -70,6 +78,7 @@
70
78
  id="{{ id }}"
71
79
  class="select {{ main_attrs.class }}"
72
80
  {% if multiple and placeholder %}data-placeholder="{{ placeholder }}"{% endif %}
81
+ {% if multiple and close_on_select %}data-close-on-select="true"{% endif %}
73
82
  {% for key, value in main_attrs.items() %}
74
83
  {% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
75
84
  {% endfor %}
@@ -135,28 +144,14 @@
135
144
  {% endif %}
136
145
  </div>
137
146
  </div>
138
- {% if multiple and selectedArray %}
139
- {% for val in selectedArray %}
140
- <input
141
- type="hidden"
142
- name="{{ name or id ~ '-value' }}"
143
- value="{{ val }}"
144
- {% if loop.index0 > 0 %}id="{{ id }}-value-{{ loop.index0 }}"{% endif %}
145
- {% for key, value in input_attrs.items() %}
146
- {% if key != 'name' and key != 'value' %}{{ key }}="{{ value }}"{% endif %}
147
- {% endfor %}
148
- >
147
+ <input
148
+ type="hidden"
149
+ name="{{ name or id ~ '-value' }}"
150
+ value="{% if multiple %}{{ selectedArray | tojson }}{% else %}{{ (default_option.value if default_option) or '' }}{% endif %}"
151
+ {% for key, value in input_attrs.items() %}
152
+ {% if key != 'name' and key != 'value' %}{{ key }}="{{ value }}"{% endif %}
149
153
  {% endfor %}
150
- {% else %}
151
- <input
152
- type="hidden"
153
- name="{{ name or id ~ '-value' }}"
154
- value="{{ (default_option.value if default_option) or '' }}"
155
- {% for key, value in input_attrs.items() %}
156
- {% if key != 'name' and key != 'value' %}{{ key }}="{{ value }}"{% endif %}
157
- {% endfor %}
158
- >
159
- {% endif %}
154
+ >
160
155
  </div>
161
156
  {% endmacro %}
162
157
 
@@ -189,7 +184,7 @@
189
184
  <div
190
185
  id="{{ item_id }}"
191
186
  role="option"
192
- data-value="{{ item.value }}"
187
+ {% if item.value is defined and item.value is not none %}data-value="{{ item.value }}"{% endif %}
193
188
  {% if item.value in selectedArray %}aria-selected="true"{% endif %}
194
189
  {% if item.attrs %}
195
190
  {% for key, value in item.attrs.items() %}
@@ -527,20 +527,19 @@
527
527
  let visibleOptions = [...options];
528
528
  let activeIndex = -1;
529
529
  const isMultiple = listbox.getAttribute('aria-multiselectable') === 'true';
530
- let selectedValues = isMultiple ? new Set() : null;
531
- let placeholder = null;
530
+ const selectedOptions = isMultiple ? new Set() : null;
531
+ const placeholder = isMultiple ? (selectComponent.dataset.placeholder || '') : null;
532
+ const closeOnSelect = selectComponent.dataset.closeOnSelect === 'true';
532
533
 
533
- if (isMultiple) {
534
- placeholder = selectComponent.dataset.placeholder || '';
535
- }
534
+ const getValue = (opt) => opt.dataset.value ?? opt.textContent.trim();
536
535
 
537
536
  const setActiveOption = (index) => {
538
537
  if (activeIndex > -1 && options[activeIndex]) {
539
538
  options[activeIndex].classList.remove('active');
540
539
  }
541
-
540
+
542
541
  activeIndex = index;
543
-
542
+
544
543
  if (activeIndex > -1) {
545
544
  const activeOption = options[activeIndex];
546
545
  activeOption.classList.add('active');
@@ -559,70 +558,43 @@
559
558
  return parseFloat(style.transitionDuration) > 0 || parseFloat(style.transitionDelay) > 0;
560
559
  };
561
560
 
562
- const syncMultipleInputs = () => {
563
- if (!isMultiple) return;
564
- const values = Array.from(selectedValues);
565
- const inputs = Array.from(selectComponent.querySelectorAll(':scope > input[type="hidden"]'));
566
- inputs.slice(1).forEach(inp => inp.remove());
567
-
568
- if (values.length === 0) {
569
- input.value = '';
570
- } else {
571
- input.value = values[0];
572
- let insertAfter = input;
573
- for (let i = 1; i < values.length; i++) {
574
- const clone = input.cloneNode(true);
575
- clone.removeAttribute('id');
576
- clone.value = values[i];
577
- insertAfter.after(clone);
578
- insertAfter = clone;
579
- }
580
- }
581
- };
582
-
583
- const updateMultipleLabel = () => {
584
- if (!isMultiple) return;
585
- const selected = options.filter(opt => selectedValues.has(opt.dataset.value));
586
- if (selected.length === 0) {
587
- selectedLabel.textContent = placeholder;
588
- selectedLabel.classList.add('text-muted-foreground');
589
- } else {
590
- selectedLabel.textContent = selected.map(opt => opt.dataset.label || opt.textContent.trim()).join(', ');
591
- selectedLabel.classList.remove('text-muted-foreground');
592
- }
593
- };
594
-
595
561
  const updateValue = (optionOrOptions, triggerEvent = true) => {
596
562
  let value;
597
563
 
598
564
  if (isMultiple) {
599
565
  const opts = Array.isArray(optionOrOptions) ? optionOrOptions : [];
600
- selectedValues = new Set(opts.map(opt => opt.dataset.value));
601
- options.forEach(opt => {
602
- if (selectedValues.has(opt.dataset.value)) {
603
- opt.setAttribute('aria-selected', 'true');
604
- } else {
605
- opt.removeAttribute('aria-selected');
606
- }
607
- });
608
- updateMultipleLabel();
609
- syncMultipleInputs();
610
- value = Array.from(selectedValues);
566
+ selectedOptions.clear();
567
+ opts.forEach(opt => selectedOptions.add(opt));
568
+
569
+ // Get selected options in DOM order
570
+ const selected = options.filter(opt => selectedOptions.has(opt));
571
+ if (selected.length === 0) {
572
+ selectedLabel.textContent = placeholder;
573
+ selectedLabel.classList.add('text-muted-foreground');
574
+ } else {
575
+ selectedLabel.textContent = selected.map(opt => opt.dataset.label || opt.textContent.trim()).join(', ');
576
+ selectedLabel.classList.remove('text-muted-foreground');
577
+ }
578
+
579
+ value = selected.map(getValue);
580
+ input.value = JSON.stringify(value);
611
581
  } else {
612
582
  const option = optionOrOptions;
613
583
  if (!option) return;
614
584
  selectedLabel.innerHTML = option.innerHTML;
615
- input.value = option.dataset.value;
616
- options.forEach(opt => {
617
- if (opt === option) {
618
- opt.setAttribute('aria-selected', 'true');
619
- } else {
620
- opt.removeAttribute('aria-selected');
621
- }
622
- });
623
- value = option.dataset.value;
585
+ value = getValue(option);
586
+ input.value = value;
624
587
  }
625
588
 
589
+ options.forEach(opt => {
590
+ const isSelected = isMultiple ? selectedOptions.has(opt) : opt === optionOrOptions;
591
+ if (isSelected) {
592
+ opt.setAttribute('aria-selected', 'true');
593
+ } else {
594
+ opt.removeAttribute('aria-selected');
595
+ }
596
+ });
597
+
626
598
  if (triggerEvent) {
627
599
  selectComponent.dispatchEvent(new CustomEvent('change', {
628
600
  detail: { value },
@@ -631,85 +603,73 @@
631
603
  }
632
604
  };
633
605
 
634
- const toggleMultipleValue = (value, triggerEvent = true) => {
635
- if (!isMultiple || value == null) return;
636
-
637
- const newValues = new Set(selectedValues);
638
- if (newValues.has(value)) {
639
- newValues.delete(value);
640
- } else {
641
- newValues.add(value);
642
- }
643
-
644
- const selectedOptions = options.filter(opt => newValues.has(opt.dataset.value));
645
- updateValue(selectedOptions, triggerEvent);
646
- };
647
-
648
606
  const closePopover = (focusOnTrigger = true) => {
649
607
  if (popover.getAttribute('aria-hidden') === 'true') return;
650
-
608
+
651
609
  if (filter) {
652
610
  const resetFilter = () => {
653
611
  filter.value = '';
654
612
  visibleOptions = [...options];
655
613
  allOptions.forEach(opt => opt.setAttribute('aria-hidden', 'false'));
656
614
  };
657
-
615
+
658
616
  if (hasTransition()) {
659
617
  popover.addEventListener('transitionend', resetFilter, { once: true });
660
618
  } else {
661
619
  resetFilter();
662
620
  }
663
621
  }
664
-
622
+
665
623
  if (focusOnTrigger) trigger.focus();
666
624
  popover.setAttribute('aria-hidden', 'true');
667
625
  trigger.setAttribute('aria-expanded', 'false');
668
626
  setActiveOption(-1);
669
- }
670
-
671
- const selectOption = (option) => {
672
- if (!option) return;
673
-
674
- const oldValue = input.value;
675
- const newValue = option.dataset.value;
627
+ };
676
628
 
677
- if (newValue != null && newValue !== oldValue) {
678
- updateValue(option);
629
+ const toggleMultipleValue = (option) => {
630
+ if (selectedOptions.has(option)) {
631
+ selectedOptions.delete(option);
632
+ } else {
633
+ selectedOptions.add(option);
679
634
  }
680
-
681
- closePopover();
635
+ updateValue(options.filter(opt => selectedOptions.has(opt)));
682
636
  };
683
637
 
684
- const selectByValue = (value) => {
685
- const option = options.find(opt => opt.dataset.value === value);
638
+ const select = (value) => {
686
639
  if (isMultiple) {
687
- if (value != null && selectedValues.has(value)) return;
688
- if (option && value != null) {
689
- const newValues = new Set(selectedValues);
690
- newValues.add(value);
691
- const selectedOptions = options.filter(opt => newValues.has(opt.dataset.value));
692
- updateValue(selectedOptions);
693
- }
640
+ const option = options.find(opt => getValue(opt) === value && !selectedOptions.has(opt));
641
+ if (!option) return;
642
+ selectedOptions.add(option);
643
+ updateValue(options.filter(opt => selectedOptions.has(opt)));
694
644
  } else {
695
- selectOption(option);
645
+ const option = options.find(opt => getValue(opt) === value);
646
+ if (!option) return;
647
+ if (input.value !== value) {
648
+ updateValue(option);
649
+ }
650
+ closePopover();
696
651
  }
697
652
  };
698
653
 
699
- const selectAll = () => {
654
+ const deselect = (value) => {
700
655
  if (!isMultiple) return;
701
- updateValue(options.filter(opt => opt.dataset.value != null));
656
+ const option = options.find(opt => getValue(opt) === value && selectedOptions.has(opt));
657
+ if (!option) return;
658
+ selectedOptions.delete(option);
659
+ updateValue(options.filter(opt => selectedOptions.has(opt)));
702
660
  };
703
661
 
704
- const selectNone = () => {
662
+ const toggle = (value) => {
705
663
  if (!isMultiple) return;
706
- updateValue([]);
664
+ const option = options.find(opt => getValue(opt) === value);
665
+ if (!option) return;
666
+ toggleMultipleValue(option);
707
667
  };
708
668
 
709
669
  if (filter) {
710
670
  const filterOptions = () => {
711
671
  const searchTerm = filter.value.trim().toLowerCase();
712
-
672
+
713
673
  setActiveOption(-1);
714
674
 
715
675
  visibleOptions = [];
@@ -735,35 +695,36 @@
735
695
  }
736
696
  });
737
697
  };
738
-
698
+
739
699
  filter.addEventListener('input', filterOptions);
740
700
  }
741
701
 
702
+ // Initialization
742
703
  if (isMultiple) {
743
- const validValues = new Set(options.map(opt => opt.dataset.value).filter(v => v != null));
744
- const inputs = Array.from(selectComponent.querySelectorAll(':scope > input[type="hidden"]'));
745
- const initialValues = inputs
746
- .map(inp => inp.value)
747
- .filter(v => v != null && validValues.has(v));
748
-
749
- let initialOptions;
750
- if (initialValues.length > 0) {
751
- initialOptions = options.filter(opt => initialValues.includes(opt.dataset.value));
752
- } else {
753
- initialOptions = options.filter(opt => opt.getAttribute('aria-selected') === 'true');
754
- }
755
-
756
- updateValue(initialOptions, false);
757
- } else {
758
- let initialOption = options.find(opt => opt.dataset.value === input.value);
759
-
760
- if (!initialOption) {
761
- initialOption = options.find(opt => opt.dataset.value !== undefined) ?? options[0];
762
- }
704
+ const ariaSelected = options.filter(opt => opt.getAttribute('aria-selected') === 'true');
705
+ try {
706
+ const parsed = JSON.parse(input.value || '[]');
707
+ const validValues = new Set(options.map(getValue));
708
+ const initialValues = Array.isArray(parsed) ? parsed.filter(v => validValues.has(v)) : [];
709
+
710
+ const initialOptions = [];
711
+ if (initialValues.length > 0) {
712
+ // Match values to options in order, allowing duplicates
713
+ initialValues.forEach(val => {
714
+ const opt = options.find(o => getValue(o) === val && !initialOptions.includes(o));
715
+ if (opt) initialOptions.push(opt);
716
+ });
717
+ } else {
718
+ initialOptions.push(...ariaSelected);
719
+ }
763
720
 
764
- if (initialOption) {
765
- updateValue(initialOption, false);
721
+ updateValue(initialOptions, false);
722
+ } catch (e) {
723
+ updateValue(ariaSelected, false);
766
724
  }
725
+ } else {
726
+ const initialOption = options.find(opt => getValue(opt) === input.value) || options[0];
727
+ if (initialOption) updateValue(initialOption, false);
767
728
  }
768
729
 
769
730
  const handleKeyNavigation = (event) => {
@@ -780,20 +741,27 @@
780
741
  }
781
742
  return;
782
743
  }
783
-
744
+
784
745
  event.preventDefault();
785
746
 
786
747
  if (event.key === 'Escape') {
787
748
  closePopover();
788
749
  return;
789
750
  }
790
-
751
+
791
752
  if (event.key === 'Enter') {
792
753
  if (activeIndex > -1) {
754
+ const option = options[activeIndex];
793
755
  if (isMultiple) {
794
- toggleMultipleValue(options[activeIndex].dataset.value);
756
+ toggleMultipleValue(option);
757
+ if (closeOnSelect) {
758
+ closePopover();
759
+ }
795
760
  } else {
796
- selectOption(options[activeIndex]);
761
+ if (input.value !== getValue(option)) {
762
+ updateValue(option);
763
+ }
764
+ closePopover();
797
765
  }
798
766
  }
799
767
  return;
@@ -860,7 +828,7 @@
860
828
  document.dispatchEvent(new CustomEvent('basecoat:popover', {
861
829
  detail: { source: selectComponent }
862
830
  }));
863
-
831
+
864
832
  if (filter) {
865
833
  if (hasTransition()) {
866
834
  popover.addEventListener('transitionend', () => {
@@ -873,7 +841,7 @@
873
841
 
874
842
  popover.setAttribute('aria-hidden', 'false');
875
843
  trigger.setAttribute('aria-expanded', 'true');
876
-
844
+
877
845
  const selectedOption = listbox.querySelector('[role="option"][aria-selected="true"]');
878
846
  if (selectedOption) {
879
847
  setActiveOption(options.indexOf(selectedOption));
@@ -892,18 +860,28 @@
892
860
 
893
861
  listbox.addEventListener('click', (event) => {
894
862
  const clickedOption = event.target.closest('[role="option"]');
895
- if (clickedOption) {
896
- if (isMultiple) {
897
- toggleMultipleValue(clickedOption.dataset.value);
898
- setActiveOption(options.indexOf(clickedOption));
863
+ if (!clickedOption) return;
864
+
865
+ const option = options.find(opt => opt === clickedOption);
866
+ if (!option) return;
867
+
868
+ if (isMultiple) {
869
+ toggleMultipleValue(option);
870
+ if (closeOnSelect) {
871
+ closePopover();
872
+ } else {
873
+ setActiveOption(options.indexOf(option));
899
874
  if (filter) {
900
875
  filter.focus();
901
876
  } else {
902
877
  trigger.focus();
903
878
  }
904
- } else {
905
- selectOption(clickedOption);
906
879
  }
880
+ } else {
881
+ if (input.value !== getValue(option)) {
882
+ updateValue(option);
883
+ }
884
+ closePopover();
907
885
  }
908
886
  });
909
887
 
@@ -921,10 +899,41 @@
921
899
 
922
900
  popover.setAttribute('aria-hidden', 'true');
923
901
 
924
- selectComponent.selectByValue = selectByValue;
902
+ // Public API
903
+ Object.defineProperty(selectComponent, 'value', {
904
+ get: () => {
905
+ if (isMultiple) {
906
+ return options.filter(opt => selectedOptions.has(opt)).map(getValue);
907
+ } else {
908
+ return input.value;
909
+ }
910
+ },
911
+ set: (val) => {
912
+ if (isMultiple) {
913
+ const values = Array.isArray(val) ? val : (val != null ? [val] : []);
914
+ const opts = [];
915
+ values.forEach(v => {
916
+ const opt = options.find(o => getValue(o) === v && !opts.includes(o));
917
+ if (opt) opts.push(opt);
918
+ });
919
+ updateValue(opts);
920
+ } else {
921
+ const option = options.find(opt => getValue(opt) === val);
922
+ if (option) {
923
+ updateValue(option);
924
+ closePopover();
925
+ }
926
+ }
927
+ }
928
+ });
929
+
930
+ selectComponent.select = select;
931
+ selectComponent.selectByValue = select; // Backward compatibility alias
925
932
  if (isMultiple) {
926
- selectComponent.selectAll = selectAll;
927
- selectComponent.selectNone = selectNone;
933
+ selectComponent.deselect = deselect;
934
+ selectComponent.toggle = toggle;
935
+ selectComponent.selectAll = () => updateValue(options);
936
+ selectComponent.selectNone = () => updateValue([]);
928
937
  }
929
938
  selectComponent.dataset.selectInitialized = true;
930
939
  selectComponent.dispatchEvent(new CustomEvent('basecoat:initialized'));
@@ -1 +1 @@
1
- (()=>{const e={};let t=null;const n=()=>{Object.entries(e).forEach((([e,{selector:t,init:n}])=>{document.querySelectorAll(t).forEach(n)}))},a=t=>{t.nodeType===Node.ELEMENT_NODE&&Object.entries(e).forEach((([e,{selector:n,init:a}])=>{t.matches(n)&&a(t),t.querySelectorAll(n).forEach(a)}))},i=()=>{t||(t=new MutationObserver((e=>{e.forEach((e=>{e.addedNodes.forEach(a)}))})),t.observe(document.body,{childList:!0,subtree:!0}))};window.basecoat={register:(t,n,a)=>{e[t]={selector:n,init:a}},init:t=>{const n=e[t];if(!n)return void console.warn(`Component '${t}' not found in registry`);const a=`data-${t}-initialized`;document.querySelectorAll(`[${a}]`).forEach((e=>{e.removeAttribute(a)})),document.querySelectorAll(n.selector).forEach(n.init)},initAll:()=>{Object.entries(e).forEach((([e,{selector:t}])=>{const n=`data-${e}-initialized`;document.querySelectorAll(`[${n}]`).forEach((e=>{e.removeAttribute(n)}))})),n()},start:i,stop:()=>{t&&(t.disconnect(),t=null)}},document.addEventListener("DOMContentLoaded",(()=>{n(),i()}))})(),(()=>{const e=e=>{const t=e.querySelector("header input"),n=e.querySelector('[role="menu"]');if(!t||!n){const a=[];return t||a.push("input"),n||a.push("menu"),void console.error(`Command component initialization failed. Missing element(s): ${a.join(", ")}`,e)}const a=Array.from(n.querySelectorAll('[role="menuitem"]')),i=a.filter((e=>!e.hasAttribute("disabled")&&"true"!==e.getAttribute("aria-disabled")));let r=[...i],o=-1;const s=e=>{if(o>-1&&i[o]&&i[o].classList.remove("active"),o=e,o>-1){const e=i[o];e.classList.add("active"),e.id?t.setAttribute("aria-activedescendant",e.id):t.removeAttribute("aria-activedescendant")}else t.removeAttribute("aria-activedescendant")};t.addEventListener("input",(()=>{const e=t.value.trim().toLowerCase();s(-1),r=[],a.forEach((t=>{if(t.hasAttribute("data-force"))return t.setAttribute("aria-hidden","false"),void(i.includes(t)&&r.push(t));const n=(t.dataset.filter||t.textContent).trim().toLowerCase(),a=(t.dataset.keywords||"").toLowerCase().split(/[\s,]+/).filter(Boolean).some((t=>t.includes(e))),o=n.includes(e)||a;t.setAttribute("aria-hidden",String(!o)),o&&i.includes(t)&&r.push(t)})),r.length>0&&(s(i.indexOf(r[0])),r[0].scrollIntoView({block:"nearest"}))}));n.addEventListener("mousemove",(e=>{const t=e.target.closest('[role="menuitem"]');if(t&&r.includes(t)){const e=i.indexOf(t);e!==o&&s(e)}})),n.addEventListener("click",(t=>{const n=t.target.closest('[role="menuitem"]');if(n&&r.includes(n)){const t=e.closest("dialog.command-dialog");t&&!n.hasAttribute("data-keep-command-open")&&t.close()}})),t.addEventListener("keydown",(e=>{if(!["ArrowDown","ArrowUp","Enter","Home","End"].includes(e.key))return;if("Enter"===e.key)return e.preventDefault(),void(o>-1&&i[o]?.click());if(0===r.length)return;e.preventDefault();const t=o>-1?r.indexOf(i[o]):-1;let n=t;switch(e.key){case"ArrowDown":t<r.length-1&&(n=t+1);break;case"ArrowUp":t>0?n=t-1:-1===t&&(n=0);break;case"Home":n=0;break;case"End":n=r.length-1}if(n!==t){const e=r[n];s(i.indexOf(e)),e.scrollIntoView({block:"nearest",behavior:"smooth"})}})),r.length>0&&(s(i.indexOf(r[0])),r[0].scrollIntoView({block:"nearest"})),e.dataset.commandInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("command",".command:not([data-command-initialized])",e)})(),(()=>{const e=e=>{const t=e.querySelector(":scope > button"),n=e.querySelector(":scope > [data-popover]"),a=n.querySelector('[role="menu"]');if(!t||!a||!n){const i=[];return t||i.push("trigger"),a||i.push("menu"),n||i.push("popover"),void console.error(`Dropdown menu initialisation failed. Missing element(s): ${i.join(", ")}`,e)}let i=[],r=-1;const o=(e=!0)=>{"false"!==t.getAttribute("aria-expanded")&&(t.setAttribute("aria-expanded","false"),t.removeAttribute("aria-activedescendant"),n.setAttribute("aria-hidden","true"),e&&t.focus(),d(-1))},s=(r=!1)=>{document.dispatchEvent(new CustomEvent("basecoat:popover",{detail:{source:e}})),t.setAttribute("aria-expanded","true"),n.setAttribute("aria-hidden","false"),i=Array.from(a.querySelectorAll('[role^="menuitem"]')).filter((e=>!e.hasAttribute("disabled")&&"true"!==e.getAttribute("aria-disabled"))),i.length>0&&r&&("first"===r?d(0):"last"===r&&d(i.length-1))},d=e=>{if(r>-1&&i[r]&&i[r].classList.remove("active"),r=e,r>-1&&i[r]){const e=i[r];e.classList.add("active"),t.setAttribute("aria-activedescendant",e.id)}else t.removeAttribute("aria-activedescendant")};t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?o():s(!1)})),e.addEventListener("keydown",(e=>{const n="true"===t.getAttribute("aria-expanded");if("Escape"===e.key)return void(n&&o());if(!n)return void(["Enter"," "].includes(e.key)?(e.preventDefault(),s(!1)):"ArrowDown"===e.key?(e.preventDefault(),s("first")):"ArrowUp"===e.key&&(e.preventDefault(),s("last")));if(0===i.length)return;let a=r;switch(e.key){case"ArrowDown":e.preventDefault(),a=-1===r?0:Math.min(r+1,i.length-1);break;case"ArrowUp":e.preventDefault(),a=-1===r?i.length-1:Math.max(r-1,0);break;case"Home":e.preventDefault(),a=0;break;case"End":e.preventDefault(),a=i.length-1;break;case"Enter":case" ":return e.preventDefault(),i[r]?.click(),void o()}a!==r&&d(a)})),a.addEventListener("mousemove",(e=>{const t=e.target.closest('[role^="menuitem"]');if(t&&i.includes(t)){const e=i.indexOf(t);e!==r&&d(e)}})),a.addEventListener("mouseleave",(()=>{d(-1)})),a.addEventListener("click",(e=>{e.target.closest('[role^="menuitem"]')&&o()})),document.addEventListener("click",(t=>{e.contains(t.target)||o()})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&o(!1)})),e.dataset.dropdownMenuInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("dropdown-menu",".dropdown-menu:not([data-dropdown-menu-initialized])",e)})(),(()=>{const e=e=>{const t=e.querySelector(":scope > button"),n=e.querySelector(":scope > [data-popover]");if(!t||!n){const a=[];return t||a.push("trigger"),n||a.push("content"),void console.error(`Popover initialisation failed. Missing element(s): ${a.join(", ")}`,e)}const a=(e=!0)=>{"false"!==t.getAttribute("aria-expanded")&&(t.setAttribute("aria-expanded","false"),n.setAttribute("aria-hidden","true"),e&&t.focus())};t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?a():(()=>{document.dispatchEvent(new CustomEvent("basecoat:popover",{detail:{source:e}}));const a=n.querySelector("[autofocus]");a&&n.addEventListener("transitionend",(()=>{a.focus()}),{once:!0}),t.setAttribute("aria-expanded","true"),n.setAttribute("aria-hidden","false")})()})),e.addEventListener("keydown",(e=>{"Escape"===e.key&&a()})),document.addEventListener("click",(t=>{e.contains(t.target)||a()})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&a(!1)})),e.dataset.popoverInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("popover",".popover:not([data-popover-initialized])",e)})(),(()=>{const e=e=>{const t=e.querySelector(":scope > button"),n=t.querySelector(":scope > span"),a=e.querySelector(":scope > [data-popover]"),i=a?a.querySelector('[role="listbox"]'):null,r=e.querySelector(':scope > input[type="hidden"]'),o=e.querySelector('header input[type="text"]');if(!(t&&a&&i&&r)){const n=[];return t||n.push("trigger"),a||n.push("popover"),i||n.push("listbox"),r||n.push("input"),void console.error(`Select component initialisation failed. Missing element(s): ${n.join(", ")}`,e)}const s=Array.from(i.querySelectorAll('[role="option"]')),d=s.filter((e=>"true"!==e.getAttribute("aria-disabled")));let c=[...d],l=-1;const u="true"===i.getAttribute("aria-multiselectable");let v=u?new Set:null,p=null;u&&(p=e.dataset.placeholder||"");const m=e=>{if(l>-1&&d[l]&&d[l].classList.remove("active"),l=e,l>-1){const e=d[l];e.classList.add("active"),e.id?t.setAttribute("aria-activedescendant",e.id):t.removeAttribute("aria-activedescendant")}else t.removeAttribute("aria-activedescendant")},f=()=>{const e=getComputedStyle(a);return parseFloat(e.transitionDuration)>0||parseFloat(e.transitionDelay)>0},h=(t,a=!0)=>{let i;if(u){const a=Array.isArray(t)?t:[];v=new Set(a.map((e=>e.dataset.value))),d.forEach((e=>{v.has(e.dataset.value)?e.setAttribute("aria-selected","true"):e.removeAttribute("aria-selected")})),(()=>{if(!u)return;const e=d.filter((e=>v.has(e.dataset.value)));0===e.length?(n.textContent=p,n.classList.add("text-muted-foreground")):(n.textContent=e.map((e=>e.dataset.label||e.textContent.trim())).join(", "),n.classList.remove("text-muted-foreground"))})(),(()=>{if(!u)return;const t=Array.from(v);if(Array.from(e.querySelectorAll(':scope > input[type="hidden"]')).slice(1).forEach((e=>e.remove())),0===t.length)r.value="";else{r.value=t[0];let e=r;for(let n=1;n<t.length;n++){const a=r.cloneNode(!0);a.removeAttribute("id"),a.value=t[n],e.after(a),e=a}}})(),i=Array.from(v)}else{const e=t;if(!e)return;n.innerHTML=e.innerHTML,r.value=e.dataset.value,d.forEach((t=>{t===e?t.setAttribute("aria-selected","true"):t.removeAttribute("aria-selected")})),i=e.dataset.value}a&&e.dispatchEvent(new CustomEvent("change",{detail:{value:i},bubbles:!0}))},b=(e,t=!0)=>{if(!u||null==e)return;const n=new Set(v);n.has(e)?n.delete(e):n.add(e);const a=d.filter((e=>n.has(e.dataset.value)));h(a,t)},w=(e=!0)=>{if("true"!==a.getAttribute("aria-hidden")){if(o){const e=()=>{o.value="",c=[...d],s.forEach((e=>e.setAttribute("aria-hidden","false")))};f()?a.addEventListener("transitionend",e,{once:!0}):e()}e&&t.focus(),a.setAttribute("aria-hidden","true"),t.setAttribute("aria-expanded","false"),m(-1)}},g=e=>{if(!e)return;const t=r.value,n=e.dataset.value;null!=n&&n!==t&&h(e),w()},E=()=>{u&&h(d.filter((e=>null!=e.dataset.value)))},A=()=>{u&&h([])};if(o){const e=()=>{const e=o.value.trim().toLowerCase();m(-1),c=[],s.forEach((t=>{if(t.hasAttribute("data-force"))return t.setAttribute("aria-hidden","false"),void(d.includes(t)&&c.push(t));const n=(t.dataset.filter||t.textContent).trim().toLowerCase(),a=(t.dataset.keywords||"").toLowerCase().split(/[\s,]+/).filter(Boolean).some((t=>t.includes(e))),i=n.includes(e)||a;t.setAttribute("aria-hidden",String(!i)),i&&d.includes(t)&&c.push(t)}))};o.addEventListener("input",e)}if(u){const t=new Set(d.map((e=>e.dataset.value)).filter((e=>null!=e))),n=Array.from(e.querySelectorAll(':scope > input[type="hidden"]')).map((e=>e.value)).filter((e=>null!=e&&t.has(e)));let a;a=n.length>0?d.filter((e=>n.includes(e.dataset.value))):d.filter((e=>"true"===e.getAttribute("aria-selected"))),h(a,!1)}else{let e=d.find((e=>e.dataset.value===r.value));e||(e=d.find((e=>void 0!==e.dataset.value))??d[0]),e&&h(e,!1)}const y=e=>{const n="false"===a.getAttribute("aria-hidden");if(!["ArrowDown","ArrowUp","Enter","Home","End","Escape"].includes(e.key))return;if(!n)return void("Enter"!==e.key&&"Escape"!==e.key&&(e.preventDefault(),t.click()));if(e.preventDefault(),"Escape"===e.key)return void w();if("Enter"===e.key)return void(l>-1&&(u?b(d[l].dataset.value):g(d[l])));if(0===c.length)return;const i=l>-1?c.indexOf(d[l]):-1;let r=i;switch(e.key){case"ArrowDown":i<c.length-1&&(r=i+1);break;case"ArrowUp":i>0?r=i-1:-1===i&&(r=0);break;case"Home":r=0;break;case"End":r=c.length-1}if(r!==i){const e=c[r];m(d.indexOf(e)),e.scrollIntoView({block:"nearest",behavior:"smooth"})}};i.addEventListener("mousemove",(e=>{const t=e.target.closest('[role="option"]');if(t&&c.includes(t)){const e=d.indexOf(t);e!==l&&m(e)}})),i.addEventListener("mouseleave",(()=>{const e=i.querySelector('[role="option"][aria-selected="true"]');m(e?d.indexOf(e):-1)})),t.addEventListener("keydown",y),o&&o.addEventListener("keydown",y);t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?w():(()=>{document.dispatchEvent(new CustomEvent("basecoat:popover",{detail:{source:e}})),o&&(f()?a.addEventListener("transitionend",(()=>{o.focus()}),{once:!0}):o.focus()),a.setAttribute("aria-hidden","false"),t.setAttribute("aria-expanded","true");const n=i.querySelector('[role="option"][aria-selected="true"]');n&&(m(d.indexOf(n)),n.scrollIntoView({block:"nearest"}))})()})),i.addEventListener("click",(e=>{const n=e.target.closest('[role="option"]');n&&(u?(b(n.dataset.value),m(d.indexOf(n)),o?o.focus():t.focus()):g(n))})),document.addEventListener("click",(t=>{e.contains(t.target)||w(!1)})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&w(!1)})),a.setAttribute("aria-hidden","true"),e.selectByValue=e=>{const t=d.find((t=>t.dataset.value===e));if(u){if(null!=e&&v.has(e))return;if(t&&null!=e){const t=new Set(v);t.add(e);const n=d.filter((e=>t.has(e.dataset.value)));h(n)}}else g(t)},u&&(e.selectAll=E,e.selectNone=A),e.dataset.selectInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("select","div.select:not([data-select-initialized])",e)})(),(()=>{if(!window.history.__basecoatPatched){const e=window.history.pushState;window.history.pushState=function(...t){e.apply(this,t),window.dispatchEvent(new Event("basecoat:locationchange"))};const t=window.history.replaceState;window.history.replaceState=function(...e){t.apply(this,e),window.dispatchEvent(new Event("basecoat:locationchange"))},window.history.__basecoatPatched=!0}const e=e=>{const t="false"!==e.dataset.initialOpen,n="true"===e.dataset.initialMobileOpen,a=parseInt(e.dataset.breakpoint)||768;let i=a>0?window.innerWidth>=a?t:n:t;const r=()=>{const t=window.location.pathname.replace(/\/$/,"");e.querySelectorAll("a").forEach((e=>{if(e.hasAttribute("data-ignore-current"))return;new URL(e.href).pathname.replace(/\/$/,"")===t?e.setAttribute("aria-current","page"):e.removeAttribute("aria-current")}))},o=()=>{e.setAttribute("aria-hidden",!i),i?e.removeAttribute("inert"):e.setAttribute("inert","")},s=e=>{i=e,o()},d=e.id;document.addEventListener("basecoat:sidebar",(e=>{if(!e.detail?.id||e.detail.id===d)switch(e.detail?.action){case"open":s(!0);break;case"close":s(!1);break;default:s(!i)}})),e.addEventListener("click",(t=>{const n=t.target,i=e.querySelector("nav");if(window.innerWidth<a&&n.closest("a, button")&&!n.closest("[data-keep-mobile-sidebar-open]"))return document.activeElement&&document.activeElement.blur(),void s(!1);(n===e||i&&!i.contains(n))&&(document.activeElement&&document.activeElement.blur(),s(!1))})),window.addEventListener("popstate",r),window.addEventListener("basecoat:locationchange",r),o(),r(),e.dataset.sidebarInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("sidebar",".sidebar:not([data-sidebar-initialized])",e)})(),(()=>{const e=e=>{const t=e.querySelector('[role="tablist"]');if(!t)return;const n=Array.from(t.querySelectorAll('[role="tab"]')),a=n.map((e=>document.getElementById(e.getAttribute("aria-controls")))).filter(Boolean),i=e=>{n.forEach(((e,t)=>{e.setAttribute("aria-selected","false"),e.setAttribute("tabindex","-1"),a[t]&&(a[t].hidden=!0)})),e.setAttribute("aria-selected","true"),e.setAttribute("tabindex","0");const t=document.getElementById(e.getAttribute("aria-controls"));t&&(t.hidden=!1)};t.addEventListener("click",(e=>{const t=e.target.closest('[role="tab"]');t&&i(t)})),t.addEventListener("keydown",(e=>{const t=e.target;if(!n.includes(t))return;let a;const r=n.indexOf(t);switch(e.key){case"ArrowRight":a=n[(r+1)%n.length];break;case"ArrowLeft":a=n[(r-1+n.length)%n.length];break;case"Home":a=n[0];break;case"End":a=n[n.length-1];break;default:return}e.preventDefault(),i(a),a.focus()})),e.dataset.tabsInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("tabs",".tabs:not([data-tabs-initialized])",e)})(),(()=>{let e;const t=new WeakMap;let n=!1;const a={success:'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>',error:'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>',info:'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>',warning:'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>'};function i(e){if(e.dataset.toastInitialized)return;const a=parseInt(e.dataset.duration),i=-1!==a?a||("error"===e.dataset.category?5e3:3e3):-1,r={remainingTime:i,timeoutId:null,startTime:null};-1!==i&&(n?r.timeoutId=null:(r.startTime=Date.now(),r.timeoutId=setTimeout((()=>s(e)),i))),t.set(e,r),e.dataset.toastInitialized="true"}function r(){n||(n=!0,e.querySelectorAll('.toast:not([aria-hidden="true"])').forEach((e=>{if(!t.has(e))return;const n=t.get(e);n.timeoutId&&(clearTimeout(n.timeoutId),n.timeoutId=null,n.remainingTime-=Date.now()-n.startTime)})))}function o(){n&&(n=!1,e.querySelectorAll('.toast:not([aria-hidden="true"])').forEach((e=>{if(!t.has(e))return;const n=t.get(e);-1===n.remainingTime||n.timeoutId||(n.remainingTime>0?(n.startTime=Date.now(),n.timeoutId=setTimeout((()=>s(e)),n.remainingTime)):s(e))})))}function s(e){if(!t.has(e))return;const n=t.get(e);clearTimeout(n.timeoutId),t.delete(e),e.contains(document.activeElement)&&document.activeElement.blur(),e.setAttribute("aria-hidden","true"),e.addEventListener("transitionend",(()=>e.remove()),{once:!0})}document.addEventListener("basecoat:toast",(t=>{if(!e)return void console.error("Cannot create toast: toaster container not found on page.");const n=function(e){const{category:t="info",title:n,description:i,action:r,cancel:o,duration:s,icon:d}=e,c=d||t&&a[t]||"",l=n?`<h2>${n}</h2>`:"",u=i?`<p>${i}</p>`:"",v=r?.href?`<a href="${r.href}" class="btn" data-toast-action>${r.label}</a>`:r?.onclick?`<button type="button" class="btn" data-toast-action onclick="${r.onclick}">${r.label}</button>`:"",p=o?`<button type="button" class="btn-outline h-6 text-xs px-2.5 rounded-sm" data-toast-cancel onclick="${o?.onclick}">${o.label}</button>`:"",m=`\n <div\n class="toast"\n role="${"error"===t?"alert":"status"}"\n aria-atomic="true"\n ${t?`data-category="${t}"`:""}\n ${void 0!==s?`data-duration="${s}"`:""}\n >\n <div class="toast-content">\n ${c}\n <section>\n ${l}\n ${u}\n </section>\n ${v||p?`<footer>${v}${p}</footer>`:""}\n </div>\n </div>\n </div>\n `,f=document.createElement("template");return f.innerHTML=m.trim(),f.content.firstChild}(t.detail?.config||{});e.appendChild(n)})),window.basecoat&&(window.basecoat.register("toaster","#toaster:not([data-toaster-initialized])",(function(t){t.dataset.toasterInitialized||(e=t,e.addEventListener("mouseenter",r),e.addEventListener("mouseleave",o),e.addEventListener("click",(e=>{const t=e.target.closest(".toast footer a"),n=e.target.closest(".toast footer button");(t||n)&&s(e.target.closest(".toast"))})),e.querySelectorAll(".toast:not([data-toast-initialized])").forEach(i),e.dataset.toasterInitialized="true",e.dispatchEvent(new CustomEvent("basecoat:initialized")))})),window.basecoat.register("toast",".toast:not([data-toast-initialized])",i))})();
1
+ (()=>{const e={};let t=null;const n=()=>{Object.entries(e).forEach((([e,{selector:t,init:n}])=>{document.querySelectorAll(t).forEach(n)}))},i=t=>{t.nodeType===Node.ELEMENT_NODE&&Object.entries(e).forEach((([e,{selector:n,init:i}])=>{t.matches(n)&&i(t),t.querySelectorAll(n).forEach(i)}))},a=()=>{t||(t=new MutationObserver((e=>{e.forEach((e=>{e.addedNodes.forEach(i)}))})),t.observe(document.body,{childList:!0,subtree:!0}))};window.basecoat={register:(t,n,i)=>{e[t]={selector:n,init:i}},init:t=>{const n=e[t];if(!n)return void console.warn(`Component '${t}' not found in registry`);const i=`data-${t}-initialized`;document.querySelectorAll(`[${i}]`).forEach((e=>{e.removeAttribute(i)})),document.querySelectorAll(n.selector).forEach(n.init)},initAll:()=>{Object.entries(e).forEach((([e,{selector:t}])=>{const n=`data-${e}-initialized`;document.querySelectorAll(`[${n}]`).forEach((e=>{e.removeAttribute(n)}))})),n()},start:a,stop:()=>{t&&(t.disconnect(),t=null)}},document.addEventListener("DOMContentLoaded",(()=>{n(),a()}))})(),(()=>{const e=e=>{const t=e.querySelector("header input"),n=e.querySelector('[role="menu"]');if(!t||!n){const i=[];return t||i.push("input"),n||i.push("menu"),void console.error(`Command component initialization failed. Missing element(s): ${i.join(", ")}`,e)}const i=Array.from(n.querySelectorAll('[role="menuitem"]')),a=i.filter((e=>!e.hasAttribute("disabled")&&"true"!==e.getAttribute("aria-disabled")));let r=[...a],o=-1;const s=e=>{if(o>-1&&a[o]&&a[o].classList.remove("active"),o=e,o>-1){const e=a[o];e.classList.add("active"),e.id?t.setAttribute("aria-activedescendant",e.id):t.removeAttribute("aria-activedescendant")}else t.removeAttribute("aria-activedescendant")};t.addEventListener("input",(()=>{const e=t.value.trim().toLowerCase();s(-1),r=[],i.forEach((t=>{if(t.hasAttribute("data-force"))return t.setAttribute("aria-hidden","false"),void(a.includes(t)&&r.push(t));const n=(t.dataset.filter||t.textContent).trim().toLowerCase(),i=(t.dataset.keywords||"").toLowerCase().split(/[\s,]+/).filter(Boolean).some((t=>t.includes(e))),o=n.includes(e)||i;t.setAttribute("aria-hidden",String(!o)),o&&a.includes(t)&&r.push(t)})),r.length>0&&(s(a.indexOf(r[0])),r[0].scrollIntoView({block:"nearest"}))}));n.addEventListener("mousemove",(e=>{const t=e.target.closest('[role="menuitem"]');if(t&&r.includes(t)){const e=a.indexOf(t);e!==o&&s(e)}})),n.addEventListener("click",(t=>{const n=t.target.closest('[role="menuitem"]');if(n&&r.includes(n)){const t=e.closest("dialog.command-dialog");t&&!n.hasAttribute("data-keep-command-open")&&t.close()}})),t.addEventListener("keydown",(e=>{if(!["ArrowDown","ArrowUp","Enter","Home","End"].includes(e.key))return;if("Enter"===e.key)return e.preventDefault(),void(o>-1&&a[o]?.click());if(0===r.length)return;e.preventDefault();const t=o>-1?r.indexOf(a[o]):-1;let n=t;switch(e.key){case"ArrowDown":t<r.length-1&&(n=t+1);break;case"ArrowUp":t>0?n=t-1:-1===t&&(n=0);break;case"Home":n=0;break;case"End":n=r.length-1}if(n!==t){const e=r[n];s(a.indexOf(e)),e.scrollIntoView({block:"nearest",behavior:"smooth"})}})),r.length>0&&(s(a.indexOf(r[0])),r[0].scrollIntoView({block:"nearest"})),e.dataset.commandInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("command",".command:not([data-command-initialized])",e)})(),(()=>{const e=e=>{const t=e.querySelector(":scope > button"),n=e.querySelector(":scope > [data-popover]"),i=n.querySelector('[role="menu"]');if(!t||!i||!n){const a=[];return t||a.push("trigger"),i||a.push("menu"),n||a.push("popover"),void console.error(`Dropdown menu initialisation failed. Missing element(s): ${a.join(", ")}`,e)}let a=[],r=-1;const o=(e=!0)=>{"false"!==t.getAttribute("aria-expanded")&&(t.setAttribute("aria-expanded","false"),t.removeAttribute("aria-activedescendant"),n.setAttribute("aria-hidden","true"),e&&t.focus(),d(-1))},s=(r=!1)=>{document.dispatchEvent(new CustomEvent("basecoat:popover",{detail:{source:e}})),t.setAttribute("aria-expanded","true"),n.setAttribute("aria-hidden","false"),a=Array.from(i.querySelectorAll('[role^="menuitem"]')).filter((e=>!e.hasAttribute("disabled")&&"true"!==e.getAttribute("aria-disabled"))),a.length>0&&r&&("first"===r?d(0):"last"===r&&d(a.length-1))},d=e=>{if(r>-1&&a[r]&&a[r].classList.remove("active"),r=e,r>-1&&a[r]){const e=a[r];e.classList.add("active"),t.setAttribute("aria-activedescendant",e.id)}else t.removeAttribute("aria-activedescendant")};t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?o():s(!1)})),e.addEventListener("keydown",(e=>{const n="true"===t.getAttribute("aria-expanded");if("Escape"===e.key)return void(n&&o());if(!n)return void(["Enter"," "].includes(e.key)?(e.preventDefault(),s(!1)):"ArrowDown"===e.key?(e.preventDefault(),s("first")):"ArrowUp"===e.key&&(e.preventDefault(),s("last")));if(0===a.length)return;let i=r;switch(e.key){case"ArrowDown":e.preventDefault(),i=-1===r?0:Math.min(r+1,a.length-1);break;case"ArrowUp":e.preventDefault(),i=-1===r?a.length-1:Math.max(r-1,0);break;case"Home":e.preventDefault(),i=0;break;case"End":e.preventDefault(),i=a.length-1;break;case"Enter":case" ":return e.preventDefault(),a[r]?.click(),void o()}i!==r&&d(i)})),i.addEventListener("mousemove",(e=>{const t=e.target.closest('[role^="menuitem"]');if(t&&a.includes(t)){const e=a.indexOf(t);e!==r&&d(e)}})),i.addEventListener("mouseleave",(()=>{d(-1)})),i.addEventListener("click",(e=>{e.target.closest('[role^="menuitem"]')&&o()})),document.addEventListener("click",(t=>{e.contains(t.target)||o()})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&o(!1)})),e.dataset.dropdownMenuInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("dropdown-menu",".dropdown-menu:not([data-dropdown-menu-initialized])",e)})(),(()=>{const e=e=>{const t=e.querySelector(":scope > button"),n=e.querySelector(":scope > [data-popover]");if(!t||!n){const i=[];return t||i.push("trigger"),n||i.push("content"),void console.error(`Popover initialisation failed. Missing element(s): ${i.join(", ")}`,e)}const i=(e=!0)=>{"false"!==t.getAttribute("aria-expanded")&&(t.setAttribute("aria-expanded","false"),n.setAttribute("aria-hidden","true"),e&&t.focus())};t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?i():(()=>{document.dispatchEvent(new CustomEvent("basecoat:popover",{detail:{source:e}}));const i=n.querySelector("[autofocus]");i&&n.addEventListener("transitionend",(()=>{i.focus()}),{once:!0}),t.setAttribute("aria-expanded","true"),n.setAttribute("aria-hidden","false")})()})),e.addEventListener("keydown",(e=>{"Escape"===e.key&&i()})),document.addEventListener("click",(t=>{e.contains(t.target)||i()})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&i(!1)})),e.dataset.popoverInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("popover",".popover:not([data-popover-initialized])",e)})(),(()=>{const e=e=>{const t=e.querySelector(":scope > button"),n=t.querySelector(":scope > span"),i=e.querySelector(":scope > [data-popover]"),a=i?i.querySelector('[role="listbox"]'):null,r=e.querySelector(':scope > input[type="hidden"]'),o=e.querySelector('header input[type="text"]');if(!(t&&i&&a&&r)){const n=[];return t||n.push("trigger"),i||n.push("popover"),a||n.push("listbox"),r||n.push("input"),void console.error(`Select component initialisation failed. Missing element(s): ${n.join(", ")}`,e)}const s=Array.from(a.querySelectorAll('[role="option"]')),d=s.filter((e=>"true"!==e.getAttribute("aria-disabled")));let c=[...d],l=-1;const u="true"===a.getAttribute("aria-multiselectable"),p=u?new Set:null,v=u?e.dataset.placeholder||"":null,h="true"===e.dataset.closeOnSelect,f=e=>e.dataset.value??e.textContent.trim(),m=e=>{if(l>-1&&d[l]&&d[l].classList.remove("active"),l=e,l>-1){const e=d[l];e.classList.add("active"),e.id?t.setAttribute("aria-activedescendant",e.id):t.removeAttribute("aria-activedescendant")}else t.removeAttribute("aria-activedescendant")},b=()=>{const e=getComputedStyle(i);return parseFloat(e.transitionDuration)>0||parseFloat(e.transitionDelay)>0},w=(t,i=!0)=>{let a;if(u){const e=Array.isArray(t)?t:[];p.clear(),e.forEach((e=>p.add(e)));const i=d.filter((e=>p.has(e)));0===i.length?(n.textContent=v,n.classList.add("text-muted-foreground")):(n.textContent=i.map((e=>e.dataset.label||e.textContent.trim())).join(", "),n.classList.remove("text-muted-foreground")),a=i.map(f),r.value=JSON.stringify(a)}else{const e=t;if(!e)return;n.innerHTML=e.innerHTML,a=f(e),r.value=a}d.forEach((e=>{(u?p.has(e):e===t)?e.setAttribute("aria-selected","true"):e.removeAttribute("aria-selected")})),i&&e.dispatchEvent(new CustomEvent("change",{detail:{value:a},bubbles:!0}))},g=(e=!0)=>{if("true"!==i.getAttribute("aria-hidden")){if(o){const e=()=>{o.value="",c=[...d],s.forEach((e=>e.setAttribute("aria-hidden","false")))};b()?i.addEventListener("transitionend",e,{once:!0}):e()}e&&t.focus(),i.setAttribute("aria-hidden","true"),t.setAttribute("aria-expanded","false"),m(-1)}},E=e=>{p.has(e)?p.delete(e):p.add(e),w(d.filter((e=>p.has(e))))},A=e=>{if(u){const t=d.find((t=>f(t)===e&&!p.has(t)));if(!t)return;p.add(t),w(d.filter((e=>p.has(e))))}else{const t=d.find((t=>f(t)===e));if(!t)return;r.value!==e&&w(t),g()}},y=e=>{if(!u)return;const t=d.find((t=>f(t)===e&&p.has(t)));t&&(p.delete(t),w(d.filter((e=>p.has(e)))))},k=e=>{if(!u)return;const t=d.find((t=>f(t)===e));t&&E(t)};if(o){const e=()=>{const e=o.value.trim().toLowerCase();m(-1),c=[],s.forEach((t=>{if(t.hasAttribute("data-force"))return t.setAttribute("aria-hidden","false"),void(d.includes(t)&&c.push(t));const n=(t.dataset.filter||t.textContent).trim().toLowerCase(),i=(t.dataset.keywords||"").toLowerCase().split(/[\s,]+/).filter(Boolean).some((t=>t.includes(e))),a=n.includes(e)||i;t.setAttribute("aria-hidden",String(!a)),a&&d.includes(t)&&c.push(t)}))};o.addEventListener("input",e)}if(u){const e=d.filter((e=>"true"===e.getAttribute("aria-selected")));try{const t=JSON.parse(r.value||"[]"),n=new Set(d.map(f)),i=Array.isArray(t)?t.filter((e=>n.has(e))):[],a=[];i.length>0?i.forEach((e=>{const t=d.find((t=>f(t)===e&&!a.includes(t)));t&&a.push(t)})):a.push(...e),w(a,!1)}catch(t){w(e,!1)}}else{const e=d.find((e=>f(e)===r.value))||d[0];e&&w(e,!1)}const L=e=>{const n="false"===i.getAttribute("aria-hidden");if(!["ArrowDown","ArrowUp","Enter","Home","End","Escape"].includes(e.key))return;if(!n)return void("Enter"!==e.key&&"Escape"!==e.key&&(e.preventDefault(),t.click()));if(e.preventDefault(),"Escape"===e.key)return void g();if("Enter"===e.key){if(l>-1){const e=d[l];u?(E(e),h&&g()):(r.value!==f(e)&&w(e),g())}return}if(0===c.length)return;const a=l>-1?c.indexOf(d[l]):-1;let o=a;switch(e.key){case"ArrowDown":a<c.length-1&&(o=a+1);break;case"ArrowUp":a>0?o=a-1:-1===a&&(o=0);break;case"Home":o=0;break;case"End":o=c.length-1}if(o!==a){const e=c[o];m(d.indexOf(e)),e.scrollIntoView({block:"nearest",behavior:"smooth"})}};a.addEventListener("mousemove",(e=>{const t=e.target.closest('[role="option"]');if(t&&c.includes(t)){const e=d.indexOf(t);e!==l&&m(e)}})),a.addEventListener("mouseleave",(()=>{const e=a.querySelector('[role="option"][aria-selected="true"]');m(e?d.indexOf(e):-1)})),t.addEventListener("keydown",L),o&&o.addEventListener("keydown",L);t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?g():(()=>{document.dispatchEvent(new CustomEvent("basecoat:popover",{detail:{source:e}})),o&&(b()?i.addEventListener("transitionend",(()=>{o.focus()}),{once:!0}):o.focus()),i.setAttribute("aria-hidden","false"),t.setAttribute("aria-expanded","true");const n=a.querySelector('[role="option"][aria-selected="true"]');n&&(m(d.indexOf(n)),n.scrollIntoView({block:"nearest"}))})()})),a.addEventListener("click",(e=>{const n=e.target.closest('[role="option"]');if(!n)return;const i=d.find((e=>e===n));i&&(u?(E(i),h?g():(m(d.indexOf(i)),o?o.focus():t.focus())):(r.value!==f(i)&&w(i),g()))})),document.addEventListener("click",(t=>{e.contains(t.target)||g(!1)})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&g(!1)})),i.setAttribute("aria-hidden","true"),Object.defineProperty(e,"value",{get:()=>u?d.filter((e=>p.has(e))).map(f):r.value,set:e=>{if(u){const t=Array.isArray(e)?e:null!=e?[e]:[],n=[];t.forEach((e=>{const t=d.find((t=>f(t)===e&&!n.includes(t)));t&&n.push(t)})),w(n)}else{const t=d.find((t=>f(t)===e));t&&(w(t),g())}}}),e.select=A,e.selectByValue=A,u&&(e.deselect=y,e.toggle=k,e.selectAll=()=>w(d),e.selectNone=()=>w([])),e.dataset.selectInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("select","div.select:not([data-select-initialized])",e)})(),(()=>{if(!window.history.__basecoatPatched){const e=window.history.pushState;window.history.pushState=function(...t){e.apply(this,t),window.dispatchEvent(new Event("basecoat:locationchange"))};const t=window.history.replaceState;window.history.replaceState=function(...e){t.apply(this,e),window.dispatchEvent(new Event("basecoat:locationchange"))},window.history.__basecoatPatched=!0}const e=e=>{const t="false"!==e.dataset.initialOpen,n="true"===e.dataset.initialMobileOpen,i=parseInt(e.dataset.breakpoint)||768;let a=i>0?window.innerWidth>=i?t:n:t;const r=()=>{const t=window.location.pathname.replace(/\/$/,"");e.querySelectorAll("a").forEach((e=>{if(e.hasAttribute("data-ignore-current"))return;new URL(e.href).pathname.replace(/\/$/,"")===t?e.setAttribute("aria-current","page"):e.removeAttribute("aria-current")}))},o=()=>{e.setAttribute("aria-hidden",!a),a?e.removeAttribute("inert"):e.setAttribute("inert","")},s=e=>{a=e,o()},d=e.id;document.addEventListener("basecoat:sidebar",(e=>{if(!e.detail?.id||e.detail.id===d)switch(e.detail?.action){case"open":s(!0);break;case"close":s(!1);break;default:s(!a)}})),e.addEventListener("click",(t=>{const n=t.target,a=e.querySelector("nav");if(window.innerWidth<i&&n.closest("a, button")&&!n.closest("[data-keep-mobile-sidebar-open]"))return document.activeElement&&document.activeElement.blur(),void s(!1);(n===e||a&&!a.contains(n))&&(document.activeElement&&document.activeElement.blur(),s(!1))})),window.addEventListener("popstate",r),window.addEventListener("basecoat:locationchange",r),o(),r(),e.dataset.sidebarInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("sidebar",".sidebar:not([data-sidebar-initialized])",e)})(),(()=>{const e=e=>{const t=e.querySelector('[role="tablist"]');if(!t)return;const n=Array.from(t.querySelectorAll('[role="tab"]')),i=n.map((e=>document.getElementById(e.getAttribute("aria-controls")))).filter(Boolean),a=e=>{n.forEach(((e,t)=>{e.setAttribute("aria-selected","false"),e.setAttribute("tabindex","-1"),i[t]&&(i[t].hidden=!0)})),e.setAttribute("aria-selected","true"),e.setAttribute("tabindex","0");const t=document.getElementById(e.getAttribute("aria-controls"));t&&(t.hidden=!1)};t.addEventListener("click",(e=>{const t=e.target.closest('[role="tab"]');t&&a(t)})),t.addEventListener("keydown",(e=>{const t=e.target;if(!n.includes(t))return;let i;const r=n.indexOf(t);switch(e.key){case"ArrowRight":i=n[(r+1)%n.length];break;case"ArrowLeft":i=n[(r-1+n.length)%n.length];break;case"Home":i=n[0];break;case"End":i=n[n.length-1];break;default:return}e.preventDefault(),a(i),i.focus()})),e.dataset.tabsInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("tabs",".tabs:not([data-tabs-initialized])",e)})(),(()=>{let e;const t=new WeakMap;let n=!1;const i={success:'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>',error:'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>',info:'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>',warning:'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>'};function a(e){if(e.dataset.toastInitialized)return;const i=parseInt(e.dataset.duration),a=-1!==i?i||("error"===e.dataset.category?5e3:3e3):-1,r={remainingTime:a,timeoutId:null,startTime:null};-1!==a&&(n?r.timeoutId=null:(r.startTime=Date.now(),r.timeoutId=setTimeout((()=>s(e)),a))),t.set(e,r),e.dataset.toastInitialized="true"}function r(){n||(n=!0,e.querySelectorAll('.toast:not([aria-hidden="true"])').forEach((e=>{if(!t.has(e))return;const n=t.get(e);n.timeoutId&&(clearTimeout(n.timeoutId),n.timeoutId=null,n.remainingTime-=Date.now()-n.startTime)})))}function o(){n&&(n=!1,e.querySelectorAll('.toast:not([aria-hidden="true"])').forEach((e=>{if(!t.has(e))return;const n=t.get(e);-1===n.remainingTime||n.timeoutId||(n.remainingTime>0?(n.startTime=Date.now(),n.timeoutId=setTimeout((()=>s(e)),n.remainingTime)):s(e))})))}function s(e){if(!t.has(e))return;const n=t.get(e);clearTimeout(n.timeoutId),t.delete(e),e.contains(document.activeElement)&&document.activeElement.blur(),e.setAttribute("aria-hidden","true"),e.addEventListener("transitionend",(()=>e.remove()),{once:!0})}document.addEventListener("basecoat:toast",(t=>{if(!e)return void console.error("Cannot create toast: toaster container not found on page.");const n=function(e){const{category:t="info",title:n,description:a,action:r,cancel:o,duration:s,icon:d}=e,c=d||t&&i[t]||"",l=n?`<h2>${n}</h2>`:"",u=a?`<p>${a}</p>`:"",p=r?.href?`<a href="${r.href}" class="btn" data-toast-action>${r.label}</a>`:r?.onclick?`<button type="button" class="btn" data-toast-action onclick="${r.onclick}">${r.label}</button>`:"",v=o?`<button type="button" class="btn-outline h-6 text-xs px-2.5 rounded-sm" data-toast-cancel onclick="${o?.onclick}">${o.label}</button>`:"",h=`\n <div\n class="toast"\n role="${"error"===t?"alert":"status"}"\n aria-atomic="true"\n ${t?`data-category="${t}"`:""}\n ${void 0!==s?`data-duration="${s}"`:""}\n >\n <div class="toast-content">\n ${c}\n <section>\n ${l}\n ${u}\n </section>\n ${p||v?`<footer>${p}${v}</footer>`:""}\n </div>\n </div>\n </div>\n `,f=document.createElement("template");return f.innerHTML=h.trim(),f.content.firstChild}(t.detail?.config||{});e.appendChild(n)})),window.basecoat&&(window.basecoat.register("toaster","#toaster:not([data-toaster-initialized])",(function(t){t.dataset.toasterInitialized||(e=t,e.addEventListener("mouseenter",r),e.addEventListener("mouseleave",o),e.addEventListener("click",(e=>{const t=e.target.closest(".toast footer a"),n=e.target.closest(".toast footer button");(t||n)&&s(e.target.closest(".toast"))})),e.querySelectorAll(".toast:not([data-toast-initialized])").forEach(a),e.dataset.toasterInitialized="true",e.dispatchEvent(new CustomEvent("basecoat:initialized")))})),window.basecoat.register("toast",".toast:not([data-toast-initialized])",a))})();
@@ -22,20 +22,19 @@
22
22
  let visibleOptions = [...options];
23
23
  let activeIndex = -1;
24
24
  const isMultiple = listbox.getAttribute('aria-multiselectable') === 'true';
25
- let selectedValues = isMultiple ? new Set() : null;
26
- let placeholder = null;
25
+ const selectedOptions = isMultiple ? new Set() : null;
26
+ const placeholder = isMultiple ? (selectComponent.dataset.placeholder || '') : null;
27
+ const closeOnSelect = selectComponent.dataset.closeOnSelect === 'true';
27
28
 
28
- if (isMultiple) {
29
- placeholder = selectComponent.dataset.placeholder || '';
30
- }
29
+ const getValue = (opt) => opt.dataset.value ?? opt.textContent.trim();
31
30
 
32
31
  const setActiveOption = (index) => {
33
32
  if (activeIndex > -1 && options[activeIndex]) {
34
33
  options[activeIndex].classList.remove('active');
35
34
  }
36
-
35
+
37
36
  activeIndex = index;
38
-
37
+
39
38
  if (activeIndex > -1) {
40
39
  const activeOption = options[activeIndex];
41
40
  activeOption.classList.add('active');
@@ -54,70 +53,43 @@
54
53
  return parseFloat(style.transitionDuration) > 0 || parseFloat(style.transitionDelay) > 0;
55
54
  };
56
55
 
57
- const syncMultipleInputs = () => {
58
- if (!isMultiple) return;
59
- const values = Array.from(selectedValues);
60
- const inputs = Array.from(selectComponent.querySelectorAll(':scope > input[type="hidden"]'));
61
- inputs.slice(1).forEach(inp => inp.remove());
62
-
63
- if (values.length === 0) {
64
- input.value = '';
65
- } else {
66
- input.value = values[0];
67
- let insertAfter = input;
68
- for (let i = 1; i < values.length; i++) {
69
- const clone = input.cloneNode(true);
70
- clone.removeAttribute('id');
71
- clone.value = values[i];
72
- insertAfter.after(clone);
73
- insertAfter = clone;
74
- }
75
- }
76
- };
77
-
78
- const updateMultipleLabel = () => {
79
- if (!isMultiple) return;
80
- const selected = options.filter(opt => selectedValues.has(opt.dataset.value));
81
- if (selected.length === 0) {
82
- selectedLabel.textContent = placeholder;
83
- selectedLabel.classList.add('text-muted-foreground');
84
- } else {
85
- selectedLabel.textContent = selected.map(opt => opt.dataset.label || opt.textContent.trim()).join(', ');
86
- selectedLabel.classList.remove('text-muted-foreground');
87
- }
88
- };
89
-
90
56
  const updateValue = (optionOrOptions, triggerEvent = true) => {
91
57
  let value;
92
58
 
93
59
  if (isMultiple) {
94
60
  const opts = Array.isArray(optionOrOptions) ? optionOrOptions : [];
95
- selectedValues = new Set(opts.map(opt => opt.dataset.value));
96
- options.forEach(opt => {
97
- if (selectedValues.has(opt.dataset.value)) {
98
- opt.setAttribute('aria-selected', 'true');
99
- } else {
100
- opt.removeAttribute('aria-selected');
101
- }
102
- });
103
- updateMultipleLabel();
104
- syncMultipleInputs();
105
- value = Array.from(selectedValues);
61
+ selectedOptions.clear();
62
+ opts.forEach(opt => selectedOptions.add(opt));
63
+
64
+ // Get selected options in DOM order
65
+ const selected = options.filter(opt => selectedOptions.has(opt));
66
+ if (selected.length === 0) {
67
+ selectedLabel.textContent = placeholder;
68
+ selectedLabel.classList.add('text-muted-foreground');
69
+ } else {
70
+ selectedLabel.textContent = selected.map(opt => opt.dataset.label || opt.textContent.trim()).join(', ');
71
+ selectedLabel.classList.remove('text-muted-foreground');
72
+ }
73
+
74
+ value = selected.map(getValue);
75
+ input.value = JSON.stringify(value);
106
76
  } else {
107
77
  const option = optionOrOptions;
108
78
  if (!option) return;
109
79
  selectedLabel.innerHTML = option.innerHTML;
110
- input.value = option.dataset.value;
111
- options.forEach(opt => {
112
- if (opt === option) {
113
- opt.setAttribute('aria-selected', 'true');
114
- } else {
115
- opt.removeAttribute('aria-selected');
116
- }
117
- });
118
- value = option.dataset.value;
80
+ value = getValue(option);
81
+ input.value = value;
119
82
  }
120
83
 
84
+ options.forEach(opt => {
85
+ const isSelected = isMultiple ? selectedOptions.has(opt) : opt === optionOrOptions;
86
+ if (isSelected) {
87
+ opt.setAttribute('aria-selected', 'true');
88
+ } else {
89
+ opt.removeAttribute('aria-selected');
90
+ }
91
+ });
92
+
121
93
  if (triggerEvent) {
122
94
  selectComponent.dispatchEvent(new CustomEvent('change', {
123
95
  detail: { value },
@@ -126,85 +98,73 @@
126
98
  }
127
99
  };
128
100
 
129
- const toggleMultipleValue = (value, triggerEvent = true) => {
130
- if (!isMultiple || value == null) return;
131
-
132
- const newValues = new Set(selectedValues);
133
- if (newValues.has(value)) {
134
- newValues.delete(value);
135
- } else {
136
- newValues.add(value);
137
- }
138
-
139
- const selectedOptions = options.filter(opt => newValues.has(opt.dataset.value));
140
- updateValue(selectedOptions, triggerEvent);
141
- };
142
-
143
101
  const closePopover = (focusOnTrigger = true) => {
144
102
  if (popover.getAttribute('aria-hidden') === 'true') return;
145
-
103
+
146
104
  if (filter) {
147
105
  const resetFilter = () => {
148
106
  filter.value = '';
149
107
  visibleOptions = [...options];
150
108
  allOptions.forEach(opt => opt.setAttribute('aria-hidden', 'false'));
151
109
  };
152
-
110
+
153
111
  if (hasTransition()) {
154
112
  popover.addEventListener('transitionend', resetFilter, { once: true });
155
113
  } else {
156
114
  resetFilter();
157
115
  }
158
116
  }
159
-
117
+
160
118
  if (focusOnTrigger) trigger.focus();
161
119
  popover.setAttribute('aria-hidden', 'true');
162
120
  trigger.setAttribute('aria-expanded', 'false');
163
121
  setActiveOption(-1);
164
- }
165
-
166
- const selectOption = (option) => {
167
- if (!option) return;
168
-
169
- const oldValue = input.value;
170
- const newValue = option.dataset.value;
122
+ };
171
123
 
172
- if (newValue != null && newValue !== oldValue) {
173
- updateValue(option);
124
+ const toggleMultipleValue = (option) => {
125
+ if (selectedOptions.has(option)) {
126
+ selectedOptions.delete(option);
127
+ } else {
128
+ selectedOptions.add(option);
174
129
  }
175
-
176
- closePopover();
130
+ updateValue(options.filter(opt => selectedOptions.has(opt)));
177
131
  };
178
132
 
179
- const selectByValue = (value) => {
180
- const option = options.find(opt => opt.dataset.value === value);
133
+ const select = (value) => {
181
134
  if (isMultiple) {
182
- if (value != null && selectedValues.has(value)) return;
183
- if (option && value != null) {
184
- const newValues = new Set(selectedValues);
185
- newValues.add(value);
186
- const selectedOptions = options.filter(opt => newValues.has(opt.dataset.value));
187
- updateValue(selectedOptions);
188
- }
135
+ const option = options.find(opt => getValue(opt) === value && !selectedOptions.has(opt));
136
+ if (!option) return;
137
+ selectedOptions.add(option);
138
+ updateValue(options.filter(opt => selectedOptions.has(opt)));
189
139
  } else {
190
- selectOption(option);
140
+ const option = options.find(opt => getValue(opt) === value);
141
+ if (!option) return;
142
+ if (input.value !== value) {
143
+ updateValue(option);
144
+ }
145
+ closePopover();
191
146
  }
192
147
  };
193
148
 
194
- const selectAll = () => {
149
+ const deselect = (value) => {
195
150
  if (!isMultiple) return;
196
- updateValue(options.filter(opt => opt.dataset.value != null));
151
+ const option = options.find(opt => getValue(opt) === value && selectedOptions.has(opt));
152
+ if (!option) return;
153
+ selectedOptions.delete(option);
154
+ updateValue(options.filter(opt => selectedOptions.has(opt)));
197
155
  };
198
156
 
199
- const selectNone = () => {
157
+ const toggle = (value) => {
200
158
  if (!isMultiple) return;
201
- updateValue([]);
159
+ const option = options.find(opt => getValue(opt) === value);
160
+ if (!option) return;
161
+ toggleMultipleValue(option);
202
162
  };
203
163
 
204
164
  if (filter) {
205
165
  const filterOptions = () => {
206
166
  const searchTerm = filter.value.trim().toLowerCase();
207
-
167
+
208
168
  setActiveOption(-1);
209
169
 
210
170
  visibleOptions = [];
@@ -230,35 +190,36 @@
230
190
  }
231
191
  });
232
192
  };
233
-
193
+
234
194
  filter.addEventListener('input', filterOptions);
235
195
  }
236
196
 
197
+ // Initialization
237
198
  if (isMultiple) {
238
- const validValues = new Set(options.map(opt => opt.dataset.value).filter(v => v != null));
239
- const inputs = Array.from(selectComponent.querySelectorAll(':scope > input[type="hidden"]'));
240
- const initialValues = inputs
241
- .map(inp => inp.value)
242
- .filter(v => v != null && validValues.has(v));
243
-
244
- let initialOptions;
245
- if (initialValues.length > 0) {
246
- initialOptions = options.filter(opt => initialValues.includes(opt.dataset.value));
247
- } else {
248
- initialOptions = options.filter(opt => opt.getAttribute('aria-selected') === 'true');
249
- }
250
-
251
- updateValue(initialOptions, false);
252
- } else {
253
- let initialOption = options.find(opt => opt.dataset.value === input.value);
254
-
255
- if (!initialOption) {
256
- initialOption = options.find(opt => opt.dataset.value !== undefined) ?? options[0];
257
- }
199
+ const ariaSelected = options.filter(opt => opt.getAttribute('aria-selected') === 'true');
200
+ try {
201
+ const parsed = JSON.parse(input.value || '[]');
202
+ const validValues = new Set(options.map(getValue));
203
+ const initialValues = Array.isArray(parsed) ? parsed.filter(v => validValues.has(v)) : [];
204
+
205
+ const initialOptions = [];
206
+ if (initialValues.length > 0) {
207
+ // Match values to options in order, allowing duplicates
208
+ initialValues.forEach(val => {
209
+ const opt = options.find(o => getValue(o) === val && !initialOptions.includes(o));
210
+ if (opt) initialOptions.push(opt);
211
+ });
212
+ } else {
213
+ initialOptions.push(...ariaSelected);
214
+ }
258
215
 
259
- if (initialOption) {
260
- updateValue(initialOption, false);
216
+ updateValue(initialOptions, false);
217
+ } catch (e) {
218
+ updateValue(ariaSelected, false);
261
219
  }
220
+ } else {
221
+ const initialOption = options.find(opt => getValue(opt) === input.value) || options[0];
222
+ if (initialOption) updateValue(initialOption, false);
262
223
  }
263
224
 
264
225
  const handleKeyNavigation = (event) => {
@@ -275,20 +236,27 @@
275
236
  }
276
237
  return;
277
238
  }
278
-
239
+
279
240
  event.preventDefault();
280
241
 
281
242
  if (event.key === 'Escape') {
282
243
  closePopover();
283
244
  return;
284
245
  }
285
-
246
+
286
247
  if (event.key === 'Enter') {
287
248
  if (activeIndex > -1) {
249
+ const option = options[activeIndex];
288
250
  if (isMultiple) {
289
- toggleMultipleValue(options[activeIndex].dataset.value);
251
+ toggleMultipleValue(option);
252
+ if (closeOnSelect) {
253
+ closePopover();
254
+ }
290
255
  } else {
291
- selectOption(options[activeIndex]);
256
+ if (input.value !== getValue(option)) {
257
+ updateValue(option);
258
+ }
259
+ closePopover();
292
260
  }
293
261
  }
294
262
  return;
@@ -355,7 +323,7 @@
355
323
  document.dispatchEvent(new CustomEvent('basecoat:popover', {
356
324
  detail: { source: selectComponent }
357
325
  }));
358
-
326
+
359
327
  if (filter) {
360
328
  if (hasTransition()) {
361
329
  popover.addEventListener('transitionend', () => {
@@ -368,7 +336,7 @@
368
336
 
369
337
  popover.setAttribute('aria-hidden', 'false');
370
338
  trigger.setAttribute('aria-expanded', 'true');
371
-
339
+
372
340
  const selectedOption = listbox.querySelector('[role="option"][aria-selected="true"]');
373
341
  if (selectedOption) {
374
342
  setActiveOption(options.indexOf(selectedOption));
@@ -387,18 +355,28 @@
387
355
 
388
356
  listbox.addEventListener('click', (event) => {
389
357
  const clickedOption = event.target.closest('[role="option"]');
390
- if (clickedOption) {
391
- if (isMultiple) {
392
- toggleMultipleValue(clickedOption.dataset.value);
393
- setActiveOption(options.indexOf(clickedOption));
358
+ if (!clickedOption) return;
359
+
360
+ const option = options.find(opt => opt === clickedOption);
361
+ if (!option) return;
362
+
363
+ if (isMultiple) {
364
+ toggleMultipleValue(option);
365
+ if (closeOnSelect) {
366
+ closePopover();
367
+ } else {
368
+ setActiveOption(options.indexOf(option));
394
369
  if (filter) {
395
370
  filter.focus();
396
371
  } else {
397
372
  trigger.focus();
398
373
  }
399
- } else {
400
- selectOption(clickedOption);
401
374
  }
375
+ } else {
376
+ if (input.value !== getValue(option)) {
377
+ updateValue(option);
378
+ }
379
+ closePopover();
402
380
  }
403
381
  });
404
382
 
@@ -416,10 +394,41 @@
416
394
 
417
395
  popover.setAttribute('aria-hidden', 'true');
418
396
 
419
- selectComponent.selectByValue = selectByValue;
397
+ // Public API
398
+ Object.defineProperty(selectComponent, 'value', {
399
+ get: () => {
400
+ if (isMultiple) {
401
+ return options.filter(opt => selectedOptions.has(opt)).map(getValue);
402
+ } else {
403
+ return input.value;
404
+ }
405
+ },
406
+ set: (val) => {
407
+ if (isMultiple) {
408
+ const values = Array.isArray(val) ? val : (val != null ? [val] : []);
409
+ const opts = [];
410
+ values.forEach(v => {
411
+ const opt = options.find(o => getValue(o) === v && !opts.includes(o));
412
+ if (opt) opts.push(opt);
413
+ });
414
+ updateValue(opts);
415
+ } else {
416
+ const option = options.find(opt => getValue(opt) === val);
417
+ if (option) {
418
+ updateValue(option);
419
+ closePopover();
420
+ }
421
+ }
422
+ }
423
+ });
424
+
425
+ selectComponent.select = select;
426
+ selectComponent.selectByValue = select; // Backward compatibility alias
420
427
  if (isMultiple) {
421
- selectComponent.selectAll = selectAll;
422
- selectComponent.selectNone = selectNone;
428
+ selectComponent.deselect = deselect;
429
+ selectComponent.toggle = toggle;
430
+ selectComponent.selectAll = () => updateValue(options);
431
+ selectComponent.selectNone = () => updateValue([]);
423
432
  }
424
433
  selectComponent.dataset.selectInitialized = true;
425
434
  selectComponent.dispatchEvent(new CustomEvent('basecoat:initialized'));
@@ -1 +1 @@
1
- (()=>{const e=e=>{const t=e.querySelector(":scope > button"),a=t.querySelector(":scope > span"),r=e.querySelector(":scope > [data-popover]"),n=r?r.querySelector('[role="listbox"]'):null,i=e.querySelector(':scope > input[type="hidden"]'),s=e.querySelector('header input[type="text"]');if(!(t&&r&&n&&i)){const a=[];return t||a.push("trigger"),r||a.push("popover"),n||a.push("listbox"),i||a.push("input"),void console.error(`Select component initialisation failed. Missing element(s): ${a.join(", ")}`,e)}const o=Array.from(n.querySelectorAll('[role="option"]')),l=o.filter((e=>"true"!==e.getAttribute("aria-disabled")));let d=[...l],u=-1;const c="true"===n.getAttribute("aria-multiselectable");let f=c?new Set:null,v=null;c&&(v=e.dataset.placeholder||"");const p=e=>{if(u>-1&&l[u]&&l[u].classList.remove("active"),u=e,u>-1){const e=l[u];e.classList.add("active"),e.id?t.setAttribute("aria-activedescendant",e.id):t.removeAttribute("aria-activedescendant")}else t.removeAttribute("aria-activedescendant")},h=()=>{const e=getComputedStyle(r);return parseFloat(e.transitionDuration)>0||parseFloat(e.transitionDelay)>0},b=(t,r=!0)=>{let n;if(c){const r=Array.isArray(t)?t:[];f=new Set(r.map((e=>e.dataset.value))),l.forEach((e=>{f.has(e.dataset.value)?e.setAttribute("aria-selected","true"):e.removeAttribute("aria-selected")})),(()=>{if(!c)return;const e=l.filter((e=>f.has(e.dataset.value)));0===e.length?(a.textContent=v,a.classList.add("text-muted-foreground")):(a.textContent=e.map((e=>e.dataset.label||e.textContent.trim())).join(", "),a.classList.remove("text-muted-foreground"))})(),(()=>{if(!c)return;const t=Array.from(f);if(Array.from(e.querySelectorAll(':scope > input[type="hidden"]')).slice(1).forEach((e=>e.remove())),0===t.length)i.value="";else{i.value=t[0];let e=i;for(let a=1;a<t.length;a++){const r=i.cloneNode(!0);r.removeAttribute("id"),r.value=t[a],e.after(r),e=r}}})(),n=Array.from(f)}else{const e=t;if(!e)return;a.innerHTML=e.innerHTML,i.value=e.dataset.value,l.forEach((t=>{t===e?t.setAttribute("aria-selected","true"):t.removeAttribute("aria-selected")})),n=e.dataset.value}r&&e.dispatchEvent(new CustomEvent("change",{detail:{value:n},bubbles:!0}))},m=(e,t=!0)=>{if(!c||null==e)return;const a=new Set(f);a.has(e)?a.delete(e):a.add(e);const r=l.filter((e=>a.has(e.dataset.value)));b(r,t)},A=(e=!0)=>{if("true"!==r.getAttribute("aria-hidden")){if(s){const e=()=>{s.value="",d=[...l],o.forEach((e=>e.setAttribute("aria-hidden","false")))};h()?r.addEventListener("transitionend",e,{once:!0}):e()}e&&t.focus(),r.setAttribute("aria-hidden","true"),t.setAttribute("aria-expanded","false"),p(-1)}},y=e=>{if(!e)return;const t=i.value,a=e.dataset.value;null!=a&&a!==t&&b(e),A()},E=()=>{c&&b(l.filter((e=>null!=e.dataset.value)))},w=()=>{c&&b([])};if(s){const e=()=>{const e=s.value.trim().toLowerCase();p(-1),d=[],o.forEach((t=>{if(t.hasAttribute("data-force"))return t.setAttribute("aria-hidden","false"),void(l.includes(t)&&d.push(t));const a=(t.dataset.filter||t.textContent).trim().toLowerCase(),r=(t.dataset.keywords||"").toLowerCase().split(/[\s,]+/).filter(Boolean).some((t=>t.includes(e))),n=a.includes(e)||r;t.setAttribute("aria-hidden",String(!n)),n&&l.includes(t)&&d.push(t)}))};s.addEventListener("input",e)}if(c){const t=new Set(l.map((e=>e.dataset.value)).filter((e=>null!=e))),a=Array.from(e.querySelectorAll(':scope > input[type="hidden"]')).map((e=>e.value)).filter((e=>null!=e&&t.has(e)));let r;r=a.length>0?l.filter((e=>a.includes(e.dataset.value))):l.filter((e=>"true"===e.getAttribute("aria-selected"))),b(r,!1)}else{let e=l.find((e=>e.dataset.value===i.value));e||(e=l.find((e=>void 0!==e.dataset.value))??l[0]),e&&b(e,!1)}const g=e=>{const a="false"===r.getAttribute("aria-hidden");if(!["ArrowDown","ArrowUp","Enter","Home","End","Escape"].includes(e.key))return;if(!a)return void("Enter"!==e.key&&"Escape"!==e.key&&(e.preventDefault(),t.click()));if(e.preventDefault(),"Escape"===e.key)return void A();if("Enter"===e.key)return void(u>-1&&(c?m(l[u].dataset.value):y(l[u])));if(0===d.length)return;const n=u>-1?d.indexOf(l[u]):-1;let i=n;switch(e.key){case"ArrowDown":n<d.length-1&&(i=n+1);break;case"ArrowUp":n>0?i=n-1:-1===n&&(i=0);break;case"Home":i=0;break;case"End":i=d.length-1}if(i!==n){const e=d[i];p(l.indexOf(e)),e.scrollIntoView({block:"nearest",behavior:"smooth"})}};n.addEventListener("mousemove",(e=>{const t=e.target.closest('[role="option"]');if(t&&d.includes(t)){const e=l.indexOf(t);e!==u&&p(e)}})),n.addEventListener("mouseleave",(()=>{const e=n.querySelector('[role="option"][aria-selected="true"]');p(e?l.indexOf(e):-1)})),t.addEventListener("keydown",g),s&&s.addEventListener("keydown",g);t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?A():(()=>{document.dispatchEvent(new CustomEvent("basecoat:popover",{detail:{source:e}})),s&&(h()?r.addEventListener("transitionend",(()=>{s.focus()}),{once:!0}):s.focus()),r.setAttribute("aria-hidden","false"),t.setAttribute("aria-expanded","true");const a=n.querySelector('[role="option"][aria-selected="true"]');a&&(p(l.indexOf(a)),a.scrollIntoView({block:"nearest"}))})()})),n.addEventListener("click",(e=>{const a=e.target.closest('[role="option"]');a&&(c?(m(a.dataset.value),p(l.indexOf(a)),s?s.focus():t.focus()):y(a))})),document.addEventListener("click",(t=>{e.contains(t.target)||A(!1)})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&A(!1)})),r.setAttribute("aria-hidden","true"),e.selectByValue=e=>{const t=l.find((t=>t.dataset.value===e));if(c){if(null!=e&&f.has(e))return;if(t&&null!=e){const t=new Set(f);t.add(e);const a=l.filter((e=>t.has(e.dataset.value)));b(a)}}else y(t)},c&&(e.selectAll=E,e.selectNone=w),e.dataset.selectInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("select","div.select:not([data-select-initialized])",e)})();
1
+ (()=>{const e=e=>{const t=e.querySelector(":scope > button"),r=t.querySelector(":scope > span"),n=e.querySelector(":scope > [data-popover]"),i=n?n.querySelector('[role="listbox"]'):null,a=e.querySelector(':scope > input[type="hidden"]'),s=e.querySelector('header input[type="text"]');if(!(t&&n&&i&&a)){const r=[];return t||r.push("trigger"),n||r.push("popover"),i||r.push("listbox"),a||r.push("input"),void console.error(`Select component initialisation failed. Missing element(s): ${r.join(", ")}`,e)}const o=Array.from(i.querySelectorAll('[role="option"]')),c=o.filter((e=>"true"!==e.getAttribute("aria-disabled")));let l=[...c],d=-1;const u="true"===i.getAttribute("aria-multiselectable"),f=u?new Set:null,p=u?e.dataset.placeholder||"":null,v="true"===e.dataset.closeOnSelect,h=e=>e.dataset.value??e.textContent.trim(),b=e=>{if(d>-1&&c[d]&&c[d].classList.remove("active"),d=e,d>-1){const e=c[d];e.classList.add("active"),e.id?t.setAttribute("aria-activedescendant",e.id):t.removeAttribute("aria-activedescendant")}else t.removeAttribute("aria-activedescendant")},m=()=>{const e=getComputedStyle(n);return parseFloat(e.transitionDuration)>0||parseFloat(e.transitionDelay)>0},y=(t,n=!0)=>{let i;if(u){const e=Array.isArray(t)?t:[];f.clear(),e.forEach((e=>f.add(e)));const n=c.filter((e=>f.has(e)));0===n.length?(r.textContent=p,r.classList.add("text-muted-foreground")):(r.textContent=n.map((e=>e.dataset.label||e.textContent.trim())).join(", "),r.classList.remove("text-muted-foreground")),i=n.map(h),a.value=JSON.stringify(i)}else{const e=t;if(!e)return;r.innerHTML=e.innerHTML,i=h(e),a.value=i}c.forEach((e=>{(u?f.has(e):e===t)?e.setAttribute("aria-selected","true"):e.removeAttribute("aria-selected")})),n&&e.dispatchEvent(new CustomEvent("change",{detail:{value:i},bubbles:!0}))},A=(e=!0)=>{if("true"!==n.getAttribute("aria-hidden")){if(s){const e=()=>{s.value="",l=[...c],o.forEach((e=>e.setAttribute("aria-hidden","false")))};m()?n.addEventListener("transitionend",e,{once:!0}):e()}e&&t.focus(),n.setAttribute("aria-hidden","true"),t.setAttribute("aria-expanded","false"),b(-1)}},E=e=>{f.has(e)?f.delete(e):f.add(e),y(c.filter((e=>f.has(e))))},g=e=>{if(u){const t=c.find((t=>h(t)===e&&!f.has(t)));if(!t)return;f.add(t),y(c.filter((e=>f.has(e))))}else{const t=c.find((t=>h(t)===e));if(!t)return;a.value!==e&&y(t),A()}},w=e=>{if(!u)return;const t=c.find((t=>h(t)===e&&f.has(t)));t&&(f.delete(t),y(c.filter((e=>f.has(e)))))},L=e=>{if(!u)return;const t=c.find((t=>h(t)===e));t&&E(t)};if(s){const e=()=>{const e=s.value.trim().toLowerCase();b(-1),l=[],o.forEach((t=>{if(t.hasAttribute("data-force"))return t.setAttribute("aria-hidden","false"),void(c.includes(t)&&l.push(t));const r=(t.dataset.filter||t.textContent).trim().toLowerCase(),n=(t.dataset.keywords||"").toLowerCase().split(/[\s,]+/).filter(Boolean).some((t=>t.includes(e))),i=r.includes(e)||n;t.setAttribute("aria-hidden",String(!i)),i&&c.includes(t)&&l.push(t)}))};s.addEventListener("input",e)}if(u){const e=c.filter((e=>"true"===e.getAttribute("aria-selected")));try{const t=JSON.parse(a.value||"[]"),r=new Set(c.map(h)),n=Array.isArray(t)?t.filter((e=>r.has(e))):[],i=[];n.length>0?n.forEach((e=>{const t=c.find((t=>h(t)===e&&!i.includes(t)));t&&i.push(t)})):i.push(...e),y(i,!1)}catch(t){y(e,!1)}}else{const e=c.find((e=>h(e)===a.value))||c[0];e&&y(e,!1)}const x=e=>{const r="false"===n.getAttribute("aria-hidden");if(!["ArrowDown","ArrowUp","Enter","Home","End","Escape"].includes(e.key))return;if(!r)return void("Enter"!==e.key&&"Escape"!==e.key&&(e.preventDefault(),t.click()));if(e.preventDefault(),"Escape"===e.key)return void A();if("Enter"===e.key){if(d>-1){const e=c[d];u?(E(e),v&&A()):(a.value!==h(e)&&y(e),A())}return}if(0===l.length)return;const i=d>-1?l.indexOf(c[d]):-1;let s=i;switch(e.key){case"ArrowDown":i<l.length-1&&(s=i+1);break;case"ArrowUp":i>0?s=i-1:-1===i&&(s=0);break;case"Home":s=0;break;case"End":s=l.length-1}if(s!==i){const e=l[s];b(c.indexOf(e)),e.scrollIntoView({block:"nearest",behavior:"smooth"})}};i.addEventListener("mousemove",(e=>{const t=e.target.closest('[role="option"]');if(t&&l.includes(t)){const e=c.indexOf(t);e!==d&&b(e)}})),i.addEventListener("mouseleave",(()=>{const e=i.querySelector('[role="option"][aria-selected="true"]');b(e?c.indexOf(e):-1)})),t.addEventListener("keydown",x),s&&s.addEventListener("keydown",x);t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?A():(()=>{document.dispatchEvent(new CustomEvent("basecoat:popover",{detail:{source:e}})),s&&(m()?n.addEventListener("transitionend",(()=>{s.focus()}),{once:!0}):s.focus()),n.setAttribute("aria-hidden","false"),t.setAttribute("aria-expanded","true");const r=i.querySelector('[role="option"][aria-selected="true"]');r&&(b(c.indexOf(r)),r.scrollIntoView({block:"nearest"}))})()})),i.addEventListener("click",(e=>{const r=e.target.closest('[role="option"]');if(!r)return;const n=c.find((e=>e===r));n&&(u?(E(n),v?A():(b(c.indexOf(n)),s?s.focus():t.focus())):(a.value!==h(n)&&y(n),A()))})),document.addEventListener("click",(t=>{e.contains(t.target)||A(!1)})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&A(!1)})),n.setAttribute("aria-hidden","true"),Object.defineProperty(e,"value",{get:()=>u?c.filter((e=>f.has(e))).map(h):a.value,set:e=>{if(u){const t=Array.isArray(e)?e:null!=e?[e]:[],r=[];t.forEach((e=>{const t=c.find((t=>h(t)===e&&!r.includes(t)));t&&r.push(t)})),y(r)}else{const t=c.find((t=>h(t)===e));t&&(y(t),A())}}}),e.select=g,e.selectByValue=g,u&&(e.deselect=w,e.toggle=L,e.selectAll=()=>y(c),e.selectNone=()=>y([])),e.dataset.selectInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("select","div.select:not([data-select-initialized])",e)})();
@@ -6,6 +6,7 @@
6
6
  @param name {string} [optional] - The name attribute for the hidden input storing the selected value.
7
7
  @param multiple {boolean} [optional] [default=false] - Enables multiple selection mode.
8
8
  @param placeholder {string} [optional] - Placeholder text shown when no options are selected (multiple mode only).
9
+ @param close_on_select {boolean} [optional] [default=false] - Closes the popover when selecting an option in multiple mode.
9
10
  @param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
10
11
  @param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
11
12
  @param popover_attrs {object} [optional] - Additional HTML attributes for the popover content div.
@@ -21,6 +22,7 @@
21
22
  items=None,
22
23
  multiple=false,
23
24
  placeholder=None,
25
+ close_on_select=false,
24
26
  main_attrs={},
25
27
  trigger_attrs={},
26
28
  popover_attrs={},
@@ -31,7 +33,13 @@
31
33
  ) %}
32
34
  {% set id = id or ("select-" + (range(100000, 999999) | random | string)) %}
33
35
 
34
- {% set selectedArray = ((selected if (selected is iterable and selected is not string) else [selected]) if selected else []) %}
36
+ {% if selected is defined and selected is iterable and selected is not string %}
37
+ {% set selectedArray = selected %}
38
+ {% elif selected is defined %}
39
+ {% set selectedArray = [selected] %}
40
+ {% else %}
41
+ {% set selectedArray = [] %}
42
+ {% endif %}
35
43
  {% set first_option = [] %}
36
44
  {% set selected_options = [] %}
37
45
 
@@ -70,6 +78,7 @@
70
78
  id="{{ id }}"
71
79
  class="select {{ main_attrs.class }}"
72
80
  {% if multiple and placeholder %}data-placeholder="{{ placeholder }}"{% endif %}
81
+ {% if multiple and close_on_select %}data-close-on-select="true"{% endif %}
73
82
  {% for key, value in main_attrs %}
74
83
  {% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
75
84
  {% endfor %}
@@ -135,28 +144,14 @@
135
144
  {% endif %}
136
145
  </div>
137
146
  </div>
138
- {% if multiple and selectedArray %}
139
- {% for val in selectedArray %}
140
- <input
141
- type="hidden"
142
- name="{{ name or id ~ '-value' }}"
143
- value="{{ val }}"
144
- {% if loop.index0 > 0 %}id="{{ id }}-value-{{ loop.index0 }}"{% endif %}
145
- {% for key, value in input_attrs %}
146
- {% if key != 'name' and key != 'value' %}{{ key }}="{{ value }}"{% endif %}
147
- {% endfor %}
148
- >
147
+ <input
148
+ type="hidden"
149
+ name="{{ name or id ~ '-value' }}"
150
+ value="{% if multiple %}{{ selectedArray | dump }}{% else %}{{ (default_option.value if default_option) or '' }}{% endif %}"
151
+ {% for key, value in input_attrs %}
152
+ {% if key != 'name' and key != 'value' %}{{ key }}="{{ value }}"{% endif %}
149
153
  {% endfor %}
150
- {% else %}
151
- <input
152
- type="hidden"
153
- name="{{ name or id ~ '-value' }}"
154
- value="{{ (default_option.value if default_option) or '' }}"
155
- {% for key, value in input_attrs %}
156
- {% if key != 'name' and key != 'value' %}{{ key }}="{{ value }}"{% endif %}
157
- {% endfor %}
158
- >
159
- {% endif %}
154
+ >
160
155
  </div>
161
156
  {% endmacro %}
162
157
 
@@ -189,7 +184,7 @@
189
184
  <div
190
185
  id="{{ item_id }}"
191
186
  role="option"
192
- data-value="{{ item.value }}"
187
+ {% if item.value is defined and item.value is not none %}data-value="{{ item.value }}"{% endif %}
193
188
  {% if item.value in selectedArray %}aria-selected="true"{% endif %}
194
189
  {% if item.attrs %}
195
190
  {% for key, value in item.attrs %}
@@ -201,4 +196,4 @@
201
196
  </div>
202
197
  {% endif %}
203
198
  {% endfor %}
204
- {% endmacro %}
199
+ {% endmacro %}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "basecoat-cli",
3
- "version": "0.3.10-beta.1",
3
+ "version": "0.3.10",
4
4
  "description": "Add Basecoat components to your project",
5
5
  "author": "hunvreus",
6
6
  "license": "MIT",