basecoat-cli 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,187 @@
1
+ {#
2
+ Renders a standalone command menu component with search and keyboard navigation.
3
+
4
+ @param id {string} [optional] - Unique identifier for the command component.
5
+ @param items {array} [optional] - Array of items to render (alternative to using caller).
6
+ @param placeholder {string} [optional] [default="Type a command or search..."] - Placeholder text for the search input.
7
+ @param empty_text {string} [optional] [default="No results found."] - Text displayed when no results match the search.
8
+ @param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
9
+ @param input_attrs {object} [optional] - Additional HTML attributes for the search input.
10
+ @param menu_attrs {object} [optional] - Additional HTML attributes for the menu div.
11
+ #}
12
+ {% macro command(
13
+ id=None,
14
+ items=None,
15
+ placeholder="Type a command or search...",
16
+ empty_text="No results found.",
17
+ main_attrs={},
18
+ input_attrs={},
19
+ menu_attrs={}
20
+ ) %}
21
+ {% set id = id or ("command-" ~ range(100000, 999999) | random | string) %}
22
+ <div
23
+ id="{{ id }}"
24
+ class="command {{ main_attrs.class }}"
25
+ aria-label="Command menu"
26
+ {% for key, value in main_attrs.items() %}
27
+ {% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
28
+ {% endfor %}
29
+ >
30
+ <header>
31
+ <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-search-icon lucide-search"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
32
+ <input
33
+ type="text"
34
+ id="{{ id }}-input"
35
+ placeholder="{{ placeholder }}"
36
+ autocomplete="off"
37
+ autocorrect="off"
38
+ spellcheck="false"
39
+ aria-autocomplete="list"
40
+ role="combobox"
41
+ aria-expanded="true"
42
+ aria-controls="{{ id }}-menu"
43
+ {% for key, value in input_attrs.items() %}
44
+ {{ key }}="{{ value }}"
45
+ {% endfor %}
46
+ >
47
+ </header>
48
+ <div
49
+ role="menu"
50
+ id="{{ id }}-menu"
51
+ aria-orientation="vertical"
52
+ data-empty="{{ empty_text }}"
53
+ class="scrollbar {{ menu_attrs.class }}"
54
+ {% for key, value in menu_attrs.items() %}
55
+ {% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
56
+ {% endfor %}
57
+ >
58
+ {% if items and items|length > 0 %}
59
+ {{ render_command_items(items, id ~ "-items" if id else "items") }}
60
+ {% else %}
61
+ {{ caller() if caller }}
62
+ {% endif %}
63
+ </div>
64
+ </div>
65
+ {% endmacro %}
66
+
67
+ {#
68
+ Renders a command dialog (modal command palette).
69
+
70
+ @param id {string} [optional] - Unique identifier for the command dialog.
71
+ @param items {array} [optional] - Array of items to render (alternative to using caller).
72
+ @param placeholder {string} [optional] [default="Type a command or search..."] - Placeholder text for the search input.
73
+ @param empty_text {string} [optional] [default="No results found."] - Text displayed when no results match the search.
74
+ @param dialog_attrs {object} [optional] - Additional HTML attributes for the dialog element.
75
+ @param input_attrs {object} [optional] - Additional HTML attributes for the search input.
76
+ @param menu_attrs {object} [optional] - Additional HTML attributes for the menu div.
77
+ @param open {boolean} [optional] [default=False] - Whether the command dialog should be open initially.
78
+ #}
79
+ {% macro command_dialog(
80
+ id=None,
81
+ items=None,
82
+ placeholder="Type a command or search...",
83
+ empty_text="No results found.",
84
+ dialog_attrs={},
85
+ input_attrs={},
86
+ menu_attrs={},
87
+ open=False
88
+ ) %}
89
+ {% set id = id or ("command-dialog-" ~ range(100000, 999999) | random | string) %}
90
+ <dialog
91
+ id="{{ id }}"
92
+ class="dialog command-dialog {{ dialog_attrs.class }}"
93
+ aria-label="Command menu"
94
+ {% if open %}open{% endif %}
95
+ onclick="if (event.target === this) this.close()"
96
+ {% for key, value in dialog_attrs.items() %}
97
+ {% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
98
+ {% endfor %}
99
+ >
100
+ <div class="command">
101
+ <header>
102
+ <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-search-icon lucide-search"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
103
+ <input
104
+ type="text"
105
+ id="{{ id }}-input"
106
+ placeholder="{{ placeholder }}"
107
+ autocomplete="off"
108
+ autocorrect="off"
109
+ spellcheck="false"
110
+ aria-autocomplete="list"
111
+ role="combobox"
112
+ aria-expanded="true"
113
+ aria-controls="{{ id }}-menu"
114
+ {% for key, value in input_attrs.items() %}
115
+ {{ key }}="{{ value }}"
116
+ {% endfor %}
117
+ >
118
+ </header>
119
+ <div
120
+ role="menu"
121
+ id="{{ id }}-menu"
122
+ aria-orientation="vertical"
123
+ data-empty="{{ empty_text }}"
124
+ class="scrollbar {{ menu_attrs.class }}"
125
+ {% for key, value in menu_attrs.items() %}
126
+ {% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
127
+ {% endfor %}
128
+ >
129
+ {% if items and items|length > 0 %}
130
+ {{ render_command_items(items, id ~ "-items" if id else "items") }}
131
+ {% else %}
132
+ {{ caller() if caller }}
133
+ {% endif %}
134
+ </div>
135
+ <button type="button" aria-label="Close dialog" onclick="this.closest('dialog').close()">
136
+ <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>
137
+ </button>
138
+ </div>
139
+ </dialog>
140
+ {% endmacro %}
141
+
142
+ {#
143
+ Renders a list of items for the command component.
144
+
145
+ @param items {array} - Array of items to render. Each item can be:
146
+ - { type: "item", label: "Text", content: "HTML", keywords: "search terms", disabled: true, attrs: {} }
147
+ - { type: "group", label: "Group Title", items: [...] }
148
+ - { type: "separator" }
149
+ Note: Use attrs to add data-label for alternative search text if needed.
150
+ @param parent_id_prefix {string} [optional] - The prefix for the item id.
151
+ #}
152
+ {% macro render_command_items(items, parent_id_prefix="items") %}
153
+ {% for item in items %}
154
+ {% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
155
+ {% if item.type == "group" %}
156
+ {% set group_label_id = item.id if item.id else "group-label-" + item_id %}
157
+ <div
158
+ role="group"
159
+ aria-labelledby="{{ group_label_id }}"
160
+ {% if item.attrs %}
161
+ {% for key, value in item.attrs.items() %}
162
+ {{ key }}="{{ value }}"
163
+ {% endfor %}
164
+ {% endif %}
165
+ >
166
+ <span role="heading" id="{{ group_label_id }}">{{ item.label }}</span>
167
+ {{ render_command_items(item.items, item_id) if item.items }}
168
+ </div>
169
+ {% elif item.type == "separator" %}
170
+ <hr role="separator" />
171
+ {% elif item.type == "item" or not item.type %}
172
+ <div
173
+ id="{{ item_id }}"
174
+ role="menuitem"
175
+ {% if item.keywords %}data-keywords="{{ item.keywords }}"{% endif %}
176
+ {% if item.disabled %}aria-disabled="true"{% endif %}
177
+ {% if item.attrs %}
178
+ {% for key, value in item.attrs.items() %}
179
+ {{ key }}="{{ value }}"
180
+ {% endfor %}
181
+ {% endif %}
182
+ >
183
+ {{ item.content | safe if item.content else item.label }}
184
+ </div>
185
+ {% endif %}
186
+ {% endfor %}
187
+ {% endmacro %}
@@ -8,7 +8,7 @@
8
8
  @param footer {string} [optional] - HTML content for the dialog footer.
9
9
  @param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
10
10
  @param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
11
- @param dialog_attrs {object} [optional] - Additional HTML attributes for the dialog content article.
11
+ @param dialog_attrs {object} [optional] - Additional HTML attributes for the dialog content container.
12
12
  @param header_attrs {object} [optional] - Additional HTML attributes for the dialog header.
13
13
  @param body_attrs {object} [optional] - Additional HTML attributes for the dialog body section.
14
14
  @param footer_attrs {object} [optional] - Additional HTML attributes for the dialog footer.
@@ -40,7 +40,7 @@
40
40
  {{ key }}="{{ value }}"
41
41
  {% endfor %}
42
42
  >
43
- {{ trigger }}
43
+ {{ trigger | safe }}
44
44
  </button>
45
45
  {% endif %}
46
46
  <dialog
@@ -53,7 +53,7 @@
53
53
  {% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
54
54
  {% endfor %}
55
55
  >
56
- <article>
56
+ <div>
57
57
  {% if title or description %}
58
58
  <header
59
59
  {% for key, value in header_attrs %}
@@ -89,6 +89,6 @@
89
89
  </button>
90
90
  </form>
91
91
  {% endif %}
92
- </article>
92
+ </div>
93
93
  </dialog>
94
94
  {% endmacro %}
@@ -360,7 +360,8 @@
360
360
  return;
361
361
  }
362
362
 
363
- const options = Array.from(listbox.querySelectorAll('[role="option"]'));
363
+ const allOptions = Array.from(listbox.querySelectorAll('[role="option"]'));
364
+ const options = allOptions.filter(opt => opt.getAttribute('aria-disabled') !== 'true');
364
365
  let visibleOptions = [...options];
365
366
  let activeIndex = -1;
366
367
 
@@ -413,7 +414,7 @@
413
414
  const resetFilter = () => {
414
415
  filter.value = '';
415
416
  visibleOptions = [...options];
416
- options.forEach(opt => opt.setAttribute('aria-hidden', 'false'));
417
+ allOptions.forEach(opt => opt.setAttribute('aria-hidden', 'false'));
417
418
  };
418
419
 
419
420
  if (hasTransition()) {
@@ -454,11 +455,11 @@
454
455
  setActiveOption(-1);
455
456
 
456
457
  visibleOptions = [];
457
- options.forEach(option => {
458
+ allOptions.forEach(option => {
458
459
  const optionText = (option.dataset.label || option.textContent).trim().toLowerCase();
459
460
  const matches = optionText.includes(searchTerm);
460
461
  option.setAttribute('aria-hidden', String(!matches));
461
- if (matches) {
462
+ if (matches && options.includes(option)) {
462
463
  visibleOptions.push(option);
463
464
  }
464
465
  });
@@ -468,7 +469,10 @@
468
469
  }
469
470
 
470
471
  let initialOption = options.find(opt => opt.dataset.value === input.value);
471
- if (!initialOption && options.length > 0) initialOption = options[0];
472
+
473
+ if (!initialOption) {
474
+ initialOption = options.find(opt => opt.dataset.value !== undefined) ?? options[0];
475
+ }
472
476
 
473
477
  updateValue(initialOption, false);
474
478
 
@@ -890,7 +894,7 @@
890
894
  clearTimeout(state.timeoutId);
891
895
  toasts.delete(element);
892
896
 
893
- if (document.activeElement) document.activeElement.blur();
897
+ if (element.contains(document.activeElement)) document.activeElement.blur();
894
898
  element.setAttribute('aria-hidden', 'true');
895
899
  element.addEventListener('transitionend', () => element.remove(), { once: true });
896
900
  }
@@ -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(":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=[],o=-1;const r=(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=(o=!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&&o&&("first"===o?d(0):"last"===o&&d(i.length-1))},d=e=>{if(o>-1&&i[o]&&i[o].classList.remove("active"),o=e,o>-1&&i[o]){const e=i[o];e.classList.add("active"),t.setAttribute("aria-activedescendant",e.id)}else t.removeAttribute("aria-activedescendant")};t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?r():s(!1)})),e.addEventListener("keydown",(e=>{const n="true"===t.getAttribute("aria-expanded");if("Escape"===e.key)return void(n&&r());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=o;switch(e.key){case"ArrowDown":e.preventDefault(),a=-1===o?0:Math.min(o+1,i.length-1);break;case"ArrowUp":e.preventDefault(),a=-1===o?i.length-1:Math.max(o-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[o]?.click(),void r()}a!==o&&d(a)})),a.addEventListener("mousemove",(e=>{const t=e.target.closest('[role^="menuitem"]');if(t&&i.includes(t)){const e=i.indexOf(t);e!==o&&d(e)}})),a.addEventListener("mouseleave",(()=>{d(-1)})),a.addEventListener("click",(e=>{e.target.closest('[role^="menuitem"]')&&r()})),document.addEventListener("click",(t=>{e.contains(t.target)||r()})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&r(!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.querySelector('[role="listbox"]'),o=e.querySelector(':scope > input[type="hidden"]'),r=e.querySelector('header input[type="text"]');if(!(t&&a&&i&&o)){const n=[];return t||n.push("trigger"),a||n.push("popover"),i||n.push("listbox"),o||n.push("input"),void console.error(`Select component initialisation failed. Missing element(s): ${n.join(", ")}`,e)}const s=Array.from(i.querySelectorAll('[role="option"]'));let d=[...s],c=-1;const l=e=>{if(c>-1&&s[c]&&s[c].classList.remove("active"),c=e,c>-1){const e=s[c];e.classList.add("active"),e.id?t.setAttribute("aria-activedescendant",e.id):t.removeAttribute("aria-activedescendant")}else t.removeAttribute("aria-activedescendant")},u=()=>{const e=getComputedStyle(a);return parseFloat(e.transitionDuration)>0||parseFloat(e.transitionDelay)>0},v=(t,a=!0)=>{if(t&&(n.innerHTML=t.dataset.label||t.innerHTML,o.value=t.dataset.value,i.querySelector('[role="option"][aria-selected="true"]')?.removeAttribute("aria-selected"),t.setAttribute("aria-selected","true"),a)){const n=new CustomEvent("change",{detail:{value:t.dataset.value},bubbles:!0});e.dispatchEvent(n)}},p=(e=!0)=>{if("true"!==a.getAttribute("aria-hidden")){if(r){const e=()=>{r.value="",d=[...s],s.forEach((e=>e.setAttribute("aria-hidden","false")))};u()?a.addEventListener("transitionend",e,{once:!0}):e()}e&&t.focus(),a.setAttribute("aria-hidden","true"),t.setAttribute("aria-expanded","false"),l(-1)}},h=e=>{if(!e)return;const t=o.value,n=e.dataset.value;null!=n&&n!==t&&v(e),p()};if(r){const e=()=>{const e=r.value.trim().toLowerCase();l(-1),d=[],s.forEach((t=>{const n=(t.dataset.label||t.textContent).trim().toLowerCase().includes(e);t.setAttribute("aria-hidden",String(!n)),n&&d.push(t)}))};r.addEventListener("input",e)}let b=s.find((e=>e.dataset.value===o.value));!b&&s.length>0&&(b=s[0]),v(b,!1);const m=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 p();if("Enter"===e.key)return void(c>-1&&h(s[c]));if(0===d.length)return;const i=c>-1?d.indexOf(s[c]):-1;let o=i;switch(e.key){case"ArrowDown":i<d.length-1&&(o=i+1);break;case"ArrowUp":i>0?o=i-1:-1===i&&(o=0);break;case"Home":o=0;break;case"End":o=d.length-1}if(o!==i){const e=d[o];l(s.indexOf(e)),e.scrollIntoView({block:"nearest",behavior:"smooth"})}};i.addEventListener("mousemove",(e=>{const t=e.target.closest('[role="option"]');if(t&&d.includes(t)){const e=s.indexOf(t);e!==c&&l(e)}})),i.addEventListener("mouseleave",(()=>{const e=i.querySelector('[role="option"][aria-selected="true"]');l(e?s.indexOf(e):-1)})),t.addEventListener("keydown",m),r&&r.addEventListener("keydown",m);t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?p():(()=>{document.dispatchEvent(new CustomEvent("basecoat:popover",{detail:{source:e}})),r&&(u()?a.addEventListener("transitionend",(()=>{r.focus()}),{once:!0}):r.focus()),a.setAttribute("aria-hidden","false"),t.setAttribute("aria-expanded","true");const n=i.querySelector('[role="option"][aria-selected="true"]');n&&(l(s.indexOf(n)),n.scrollIntoView({block:"nearest"}))})()})),i.addEventListener("click",(e=>{const t=e.target.closest('[role="option"]');t&&h(t)})),document.addEventListener("click",(t=>{e.contains(t.target)||p(!1)})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&p(!1)})),a.setAttribute("aria-hidden","true"),e.selectByValue=e=>{const t=s.find((t=>t.dataset.value===e));h(t)},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 o=()=>{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")}))},r=()=>{e.setAttribute("aria-hidden",!i),i?e.removeAttribute("inert"):e.setAttribute("inert","")},s=e=>{i=e,r()},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",o),window.addEventListener("basecoat:locationchange",o),r(),o(),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 o=n.indexOf(t);switch(e.key){case"ArrowRight":a=n[(o+1)%n.length];break;case"ArrowLeft":a=n[(o-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,o={remainingTime:i,timeoutId:null,startTime:null};-1!==i&&(n?o.timeoutId=null:(o.startTime=Date.now(),o.timeoutId=setTimeout((()=>s(e)),i))),t.set(e,o),e.dataset.toastInitialized="true"}function o(){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 r(){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),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:o,cancel:r,duration:s,icon:d}=e,c=d||t&&a[t]||"",l=n?`<h2>${n}</h2>`:"",u=i?`<p>${i}</p>`:"",v=o?.href?`<a href="${o.href}" class="btn" data-toast-action>${o.label}</a>`:o?.onclick?`<button type="button" class="btn" data-toast-action onclick="${o.onclick}">${o.label}</button>`:"",p=r?`<button type="button" class="btn-outline h-6 text-xs px-2.5 rounded-sm" data-toast-cancel onclick="${r?.onclick}">${r.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 ${v||p?`<footer>${v}${p}</footer>`:""}\n </div>\n </div>\n </div>\n `,b=document.createElement("template");return b.innerHTML=h.trim(),b.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",o),e.addEventListener("mouseleave",r),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)}))},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(":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=[],o=-1;const r=(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=(o=!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&&o&&("first"===o?d(0):"last"===o&&d(i.length-1))},d=e=>{if(o>-1&&i[o]&&i[o].classList.remove("active"),o=e,o>-1&&i[o]){const e=i[o];e.classList.add("active"),t.setAttribute("aria-activedescendant",e.id)}else t.removeAttribute("aria-activedescendant")};t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?r():s(!1)})),e.addEventListener("keydown",(e=>{const n="true"===t.getAttribute("aria-expanded");if("Escape"===e.key)return void(n&&r());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=o;switch(e.key){case"ArrowDown":e.preventDefault(),a=-1===o?0:Math.min(o+1,i.length-1);break;case"ArrowUp":e.preventDefault(),a=-1===o?i.length-1:Math.max(o-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[o]?.click(),void r()}a!==o&&d(a)})),a.addEventListener("mousemove",(e=>{const t=e.target.closest('[role^="menuitem"]');if(t&&i.includes(t)){const e=i.indexOf(t);e!==o&&d(e)}})),a.addEventListener("mouseleave",(()=>{d(-1)})),a.addEventListener("click",(e=>{e.target.closest('[role^="menuitem"]')&&r()})),document.addEventListener("click",(t=>{e.contains(t.target)||r()})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&r(!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.querySelector('[role="listbox"]'),o=e.querySelector(':scope > input[type="hidden"]'),r=e.querySelector('header input[type="text"]');if(!(t&&a&&i&&o)){const n=[];return t||n.push("trigger"),a||n.push("popover"),i||n.push("listbox"),o||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=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")},v=()=>{const e=getComputedStyle(a);return parseFloat(e.transitionDuration)>0||parseFloat(e.transitionDelay)>0},p=(t,a=!0)=>{if(t&&(n.innerHTML=t.dataset.label||t.innerHTML,o.value=t.dataset.value,i.querySelector('[role="option"][aria-selected="true"]')?.removeAttribute("aria-selected"),t.setAttribute("aria-selected","true"),a)){const n=new CustomEvent("change",{detail:{value:t.dataset.value},bubbles:!0});e.dispatchEvent(n)}},b=(e=!0)=>{if("true"!==a.getAttribute("aria-hidden")){if(r){const e=()=>{r.value="",c=[...d],s.forEach((e=>e.setAttribute("aria-hidden","false")))};v()?a.addEventListener("transitionend",e,{once:!0}):e()}e&&t.focus(),a.setAttribute("aria-hidden","true"),t.setAttribute("aria-expanded","false"),u(-1)}},h=e=>{if(!e)return;const t=o.value,n=e.dataset.value;null!=n&&n!==t&&p(e),b()};if(r){const e=()=>{const e=r.value.trim().toLowerCase();u(-1),c=[],s.forEach((t=>{const n=(t.dataset.label||t.textContent).trim().toLowerCase().includes(e);t.setAttribute("aria-hidden",String(!n)),n&&d.includes(t)&&c.push(t)}))};r.addEventListener("input",e)}let m=d.find((e=>e.dataset.value===o.value));m||(m=d.find((e=>void 0!==e.dataset.value))??d[0]),p(m,!1);const w=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 b();if("Enter"===e.key)return void(l>-1&&h(d[l]));if(0===c.length)return;const i=l>-1?c.indexOf(d[l]):-1;let o=i;switch(e.key){case"ArrowDown":i<c.length-1&&(o=i+1);break;case"ArrowUp":i>0?o=i-1:-1===i&&(o=0);break;case"Home":o=0;break;case"End":o=c.length-1}if(o!==i){const e=c[o];u(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&&u(e)}})),i.addEventListener("mouseleave",(()=>{const e=i.querySelector('[role="option"][aria-selected="true"]');u(e?d.indexOf(e):-1)})),t.addEventListener("keydown",w),r&&r.addEventListener("keydown",w);t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?b():(()=>{document.dispatchEvent(new CustomEvent("basecoat:popover",{detail:{source:e}})),r&&(v()?a.addEventListener("transitionend",(()=>{r.focus()}),{once:!0}):r.focus()),a.setAttribute("aria-hidden","false"),t.setAttribute("aria-expanded","true");const n=i.querySelector('[role="option"][aria-selected="true"]');n&&(u(d.indexOf(n)),n.scrollIntoView({block:"nearest"}))})()})),i.addEventListener("click",(e=>{const t=e.target.closest('[role="option"]');t&&h(t)})),document.addEventListener("click",(t=>{e.contains(t.target)||b(!1)})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&b(!1)})),a.setAttribute("aria-hidden","true"),e.selectByValue=e=>{const t=d.find((t=>t.dataset.value===e));h(t)},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 o=()=>{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")}))},r=()=>{e.setAttribute("aria-hidden",!i),i?e.removeAttribute("inert"):e.setAttribute("inert","")},s=e=>{i=e,r()},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",o),window.addEventListener("basecoat:locationchange",o),r(),o(),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 o=n.indexOf(t);switch(e.key){case"ArrowRight":a=n[(o+1)%n.length];break;case"ArrowLeft":a=n[(o-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,o={remainingTime:i,timeoutId:null,startTime:null};-1!==i&&(n?o.timeoutId=null:(o.startTime=Date.now(),o.timeoutId=setTimeout((()=>s(e)),i))),t.set(e,o),e.dataset.toastInitialized="true"}function o(){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 r(){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:o,cancel:r,duration:s,icon:d}=e,c=d||t&&a[t]||"",l=n?`<h2>${n}</h2>`:"",u=i?`<p>${i}</p>`:"",v=o?.href?`<a href="${o.href}" class="btn" data-toast-action>${o.label}</a>`:o?.onclick?`<button type="button" class="btn" data-toast-action onclick="${o.onclick}">${o.label}</button>`:"",p=r?`<button type="button" class="btn-outline h-6 text-xs px-2.5 rounded-sm" data-toast-cancel onclick="${r?.onclick}">${r.label}</button>`:"",b=`\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 `,h=document.createElement("template");return h.innerHTML=b.trim(),h.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",o),e.addEventListener("mouseleave",r),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))})();
@@ -0,0 +1,148 @@
1
+ (() => {
2
+ const initCommand = (container) => {
3
+ const input = container.querySelector('header input');
4
+ const menu = container.querySelector('[role="menu"]');
5
+
6
+ if (!input || !menu) {
7
+ const missing = [];
8
+ if (!input) missing.push('input');
9
+ if (!menu) missing.push('menu');
10
+ console.error(`Command component initialization failed. Missing element(s): ${missing.join(', ')}`, container);
11
+ return;
12
+ }
13
+
14
+ const allMenuItems = Array.from(menu.querySelectorAll('[role="menuitem"]'));
15
+ const menuItems = allMenuItems.filter(item =>
16
+ !item.hasAttribute('disabled') &&
17
+ item.getAttribute('aria-disabled') !== 'true'
18
+ );
19
+ let visibleMenuItems = [...menuItems];
20
+ let activeIndex = -1;
21
+
22
+ const setActiveItem = (index) => {
23
+ if (activeIndex > -1 && menuItems[activeIndex]) {
24
+ menuItems[activeIndex].classList.remove('active');
25
+ }
26
+
27
+ activeIndex = index;
28
+
29
+ if (activeIndex > -1) {
30
+ const activeItem = menuItems[activeIndex];
31
+ activeItem.classList.add('active');
32
+ if (activeItem.id) {
33
+ input.setAttribute('aria-activedescendant', activeItem.id);
34
+ } else {
35
+ input.removeAttribute('aria-activedescendant');
36
+ }
37
+ } else {
38
+ input.removeAttribute('aria-activedescendant');
39
+ }
40
+ };
41
+
42
+ const filterMenuItems = () => {
43
+ const searchTerm = input.value.trim().toLowerCase();
44
+
45
+ setActiveItem(-1);
46
+
47
+ visibleMenuItems = [];
48
+ allMenuItems.forEach(item => {
49
+ const itemText = (item.dataset.label || item.textContent).trim().toLowerCase();
50
+ const keywords = (item.dataset.keywords || '').toLowerCase();
51
+ const matches = itemText.includes(searchTerm) || keywords.includes(searchTerm);
52
+ item.setAttribute('aria-hidden', String(!matches));
53
+ if (matches && menuItems.includes(item)) {
54
+ visibleMenuItems.push(item);
55
+ }
56
+ });
57
+
58
+ if (visibleMenuItems.length > 0) {
59
+ setActiveItem(menuItems.indexOf(visibleMenuItems[0]));
60
+ visibleMenuItems[0].scrollIntoView({ block: 'nearest' });
61
+ }
62
+ };
63
+
64
+ input.addEventListener('input', filterMenuItems);
65
+
66
+ const handleKeyNavigation = (event) => {
67
+ if (!['ArrowDown', 'ArrowUp', 'Enter', 'Home', 'End'].includes(event.key)) {
68
+ return;
69
+ }
70
+
71
+ if (event.key === 'Enter') {
72
+ event.preventDefault();
73
+ if (activeIndex > -1) {
74
+ menuItems[activeIndex]?.click();
75
+ }
76
+ return;
77
+ }
78
+
79
+ if (visibleMenuItems.length === 0) return;
80
+
81
+ event.preventDefault();
82
+
83
+ const currentVisibleIndex = activeIndex > -1 ? visibleMenuItems.indexOf(menuItems[activeIndex]) : -1;
84
+ let nextVisibleIndex = currentVisibleIndex;
85
+
86
+ switch (event.key) {
87
+ case 'ArrowDown':
88
+ if (currentVisibleIndex < visibleMenuItems.length - 1) {
89
+ nextVisibleIndex = currentVisibleIndex + 1;
90
+ }
91
+ break;
92
+ case 'ArrowUp':
93
+ if (currentVisibleIndex > 0) {
94
+ nextVisibleIndex = currentVisibleIndex - 1;
95
+ } else if (currentVisibleIndex === -1) {
96
+ nextVisibleIndex = 0;
97
+ }
98
+ break;
99
+ case 'Home':
100
+ nextVisibleIndex = 0;
101
+ break;
102
+ case 'End':
103
+ nextVisibleIndex = visibleMenuItems.length - 1;
104
+ break;
105
+ }
106
+
107
+ if (nextVisibleIndex !== currentVisibleIndex) {
108
+ const newActiveItem = visibleMenuItems[nextVisibleIndex];
109
+ setActiveItem(menuItems.indexOf(newActiveItem));
110
+ newActiveItem.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
111
+ }
112
+ };
113
+
114
+ menu.addEventListener('mousemove', (event) => {
115
+ const menuItem = event.target.closest('[role="menuitem"]');
116
+ if (menuItem && visibleMenuItems.includes(menuItem)) {
117
+ const index = menuItems.indexOf(menuItem);
118
+ if (index !== activeIndex) {
119
+ setActiveItem(index);
120
+ }
121
+ }
122
+ });
123
+
124
+ menu.addEventListener('click', (event) => {
125
+ const clickedItem = event.target.closest('[role="menuitem"]');
126
+ if (clickedItem && visibleMenuItems.includes(clickedItem)) {
127
+ const dialog = container.closest('dialog.command-dialog');
128
+ if (dialog && !clickedItem.hasAttribute('data-keep-command-open')) {
129
+ dialog.close();
130
+ }
131
+ }
132
+ });
133
+
134
+ input.addEventListener('keydown', handleKeyNavigation);
135
+
136
+ if (visibleMenuItems.length > 0) {
137
+ setActiveItem(menuItems.indexOf(visibleMenuItems[0]));
138
+ visibleMenuItems[0].scrollIntoView({ block: 'nearest' });
139
+ }
140
+
141
+ container.dataset.commandInitialized = true;
142
+ container.dispatchEvent(new CustomEvent('basecoat:initialized'));
143
+ };
144
+
145
+ if (window.basecoat) {
146
+ window.basecoat.register('command', '.command:not([data-command-initialized])', initCommand);
147
+ }
148
+ })();
@@ -0,0 +1 @@
1
+ (()=>{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"]')),o=i.filter((e=>!e.hasAttribute("disabled")&&"true"!==e.getAttribute("aria-disabled")));let a=[...o],r=-1;const s=e=>{if(r>-1&&o[r]&&o[r].classList.remove("active"),r=e,r>-1){const e=o[r];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),a=[],i.forEach((t=>{const n=(t.dataset.label||t.textContent).trim().toLowerCase(),i=(t.dataset.keywords||"").toLowerCase(),r=n.includes(e)||i.includes(e);t.setAttribute("aria-hidden",String(!r)),r&&o.includes(t)&&a.push(t)})),a.length>0&&(s(o.indexOf(a[0])),a[0].scrollIntoView({block:"nearest"}))}));n.addEventListener("mousemove",(e=>{const t=e.target.closest('[role="menuitem"]');if(t&&a.includes(t)){const e=o.indexOf(t);e!==r&&s(e)}})),n.addEventListener("click",(t=>{const n=t.target.closest('[role="menuitem"]');if(n&&a.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(r>-1&&o[r]?.click());if(0===a.length)return;e.preventDefault();const t=r>-1?a.indexOf(o[r]):-1;let n=t;switch(e.key){case"ArrowDown":t<a.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=a.length-1}if(n!==t){const e=a[n];s(o.indexOf(e)),e.scrollIntoView({block:"nearest",behavior:"smooth"})}})),a.length>0&&(s(o.indexOf(a[0])),a[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)})();
@@ -16,7 +16,8 @@
16
16
  return;
17
17
  }
18
18
 
19
- const options = Array.from(listbox.querySelectorAll('[role="option"]'));
19
+ const allOptions = Array.from(listbox.querySelectorAll('[role="option"]'));
20
+ const options = allOptions.filter(opt => opt.getAttribute('aria-disabled') !== 'true');
20
21
  let visibleOptions = [...options];
21
22
  let activeIndex = -1;
22
23
 
@@ -69,7 +70,7 @@
69
70
  const resetFilter = () => {
70
71
  filter.value = '';
71
72
  visibleOptions = [...options];
72
- options.forEach(opt => opt.setAttribute('aria-hidden', 'false'));
73
+ allOptions.forEach(opt => opt.setAttribute('aria-hidden', 'false'));
73
74
  };
74
75
 
75
76
  if (hasTransition()) {
@@ -110,11 +111,11 @@
110
111
  setActiveOption(-1);
111
112
 
112
113
  visibleOptions = [];
113
- options.forEach(option => {
114
+ allOptions.forEach(option => {
114
115
  const optionText = (option.dataset.label || option.textContent).trim().toLowerCase();
115
116
  const matches = optionText.includes(searchTerm);
116
117
  option.setAttribute('aria-hidden', String(!matches));
117
- if (matches) {
118
+ if (matches && options.includes(option)) {
118
119
  visibleOptions.push(option);
119
120
  }
120
121
  });
@@ -124,7 +125,10 @@
124
125
  }
125
126
 
126
127
  let initialOption = options.find(opt => opt.dataset.value === input.value);
127
- if (!initialOption && options.length > 0) initialOption = options[0];
128
+
129
+ if (!initialOption) {
130
+ initialOption = options.find(opt => opt.dataset.value !== undefined) ?? options[0];
131
+ }
128
132
 
129
133
  updateValue(initialOption, false);
130
134
 
@@ -1 +1 @@
1
- (()=>{const e=e=>{const t=e.querySelector(":scope > button"),n=t.querySelector(":scope > span"),a=e.querySelector(":scope > [data-popover]"),r=a.querySelector('[role="listbox"]'),i=e.querySelector(':scope > input[type="hidden"]'),o=e.querySelector('header input[type="text"]');if(!(t&&a&&r&&i)){const n=[];return t||n.push("trigger"),a||n.push("popover"),r||n.push("listbox"),i||n.push("input"),void console.error(`Select component initialisation failed. Missing element(s): ${n.join(", ")}`,e)}const s=Array.from(r.querySelectorAll('[role="option"]'));let c=[...s],d=-1;const l=e=>{if(d>-1&&s[d]&&s[d].classList.remove("active"),d=e,d>-1){const e=s[d];e.classList.add("active"),e.id?t.setAttribute("aria-activedescendant",e.id):t.removeAttribute("aria-activedescendant")}else t.removeAttribute("aria-activedescendant")},u=()=>{const e=getComputedStyle(a);return parseFloat(e.transitionDuration)>0||parseFloat(e.transitionDelay)>0},v=(t,a=!0)=>{if(t&&(n.innerHTML=t.dataset.label||t.innerHTML,i.value=t.dataset.value,r.querySelector('[role="option"][aria-selected="true"]')?.removeAttribute("aria-selected"),t.setAttribute("aria-selected","true"),a)){const n=new CustomEvent("change",{detail:{value:t.dataset.value},bubbles:!0});e.dispatchEvent(n)}},p=(e=!0)=>{if("true"!==a.getAttribute("aria-hidden")){if(o){const e=()=>{o.value="",c=[...s],s.forEach((e=>e.setAttribute("aria-hidden","false")))};u()?a.addEventListener("transitionend",e,{once:!0}):e()}e&&t.focus(),a.setAttribute("aria-hidden","true"),t.setAttribute("aria-expanded","false"),l(-1)}},b=e=>{if(!e)return;const t=i.value,n=e.dataset.value;null!=n&&n!==t&&v(e),p()};if(o){const e=()=>{const e=o.value.trim().toLowerCase();l(-1),c=[],s.forEach((t=>{const n=(t.dataset.label||t.textContent).trim().toLowerCase().includes(e);t.setAttribute("aria-hidden",String(!n)),n&&c.push(t)}))};o.addEventListener("input",e)}let f=s.find((e=>e.dataset.value===i.value));!f&&s.length>0&&(f=s[0]),v(f,!1);const h=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 p();if("Enter"===e.key)return void(d>-1&&b(s[d]));if(0===c.length)return;const r=d>-1?c.indexOf(s[d]):-1;let i=r;switch(e.key){case"ArrowDown":r<c.length-1&&(i=r+1);break;case"ArrowUp":r>0?i=r-1:-1===r&&(i=0);break;case"Home":i=0;break;case"End":i=c.length-1}if(i!==r){const e=c[i];l(s.indexOf(e)),e.scrollIntoView({block:"nearest",behavior:"smooth"})}};r.addEventListener("mousemove",(e=>{const t=e.target.closest('[role="option"]');if(t&&c.includes(t)){const e=s.indexOf(t);e!==d&&l(e)}})),r.addEventListener("mouseleave",(()=>{const e=r.querySelector('[role="option"][aria-selected="true"]');l(e?s.indexOf(e):-1)})),t.addEventListener("keydown",h),o&&o.addEventListener("keydown",h);t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?p():(()=>{document.dispatchEvent(new CustomEvent("basecoat:popover",{detail:{source:e}})),o&&(u()?a.addEventListener("transitionend",(()=>{o.focus()}),{once:!0}):o.focus()),a.setAttribute("aria-hidden","false"),t.setAttribute("aria-expanded","true");const n=r.querySelector('[role="option"][aria-selected="true"]');n&&(l(s.indexOf(n)),n.scrollIntoView({block:"nearest"}))})()})),r.addEventListener("click",(e=>{const t=e.target.closest('[role="option"]');t&&b(t)})),document.addEventListener("click",(t=>{e.contains(t.target)||p(!1)})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&p(!1)})),a.setAttribute("aria-hidden","true"),e.selectByValue=e=>{const t=s.find((t=>t.dataset.value===e));b(t)},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"),a=t.querySelector(":scope > span"),i=e.querySelector(":scope > [data-popover]"),n=i.querySelector('[role="listbox"]'),r=e.querySelector(':scope > input[type="hidden"]'),o=e.querySelector('header input[type="text"]');if(!(t&&i&&n&&r)){const a=[];return t||a.push("trigger"),i||a.push("popover"),n||a.push("listbox"),r||a.push("input"),void console.error(`Select component initialisation failed. Missing element(s): ${a.join(", ")}`,e)}const s=Array.from(n.querySelectorAll('[role="option"]')),d=s.filter((e=>"true"!==e.getAttribute("aria-disabled")));let c=[...d],l=-1;const u=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")},v=()=>{const e=getComputedStyle(i);return parseFloat(e.transitionDuration)>0||parseFloat(e.transitionDelay)>0},p=(t,i=!0)=>{if(t&&(a.innerHTML=t.dataset.label||t.innerHTML,r.value=t.dataset.value,n.querySelector('[role="option"][aria-selected="true"]')?.removeAttribute("aria-selected"),t.setAttribute("aria-selected","true"),i)){const a=new CustomEvent("change",{detail:{value:t.dataset.value},bubbles:!0});e.dispatchEvent(a)}},b=(e=!0)=>{if("true"!==i.getAttribute("aria-hidden")){if(o){const e=()=>{o.value="",c=[...d],s.forEach((e=>e.setAttribute("aria-hidden","false")))};v()?i.addEventListener("transitionend",e,{once:!0}):e()}e&&t.focus(),i.setAttribute("aria-hidden","true"),t.setAttribute("aria-expanded","false"),u(-1)}},f=e=>{if(!e)return;const t=r.value,a=e.dataset.value;null!=a&&a!==t&&p(e),b()};if(o){const e=()=>{const e=o.value.trim().toLowerCase();u(-1),c=[],s.forEach((t=>{const a=(t.dataset.label||t.textContent).trim().toLowerCase().includes(e);t.setAttribute("aria-hidden",String(!a)),a&&d.includes(t)&&c.push(t)}))};o.addEventListener("input",e)}let E=d.find((e=>e.dataset.value===r.value));E||(E=d.find((e=>void 0!==e.dataset.value))??d[0]),p(E,!1);const h=e=>{const a="false"===i.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 b();if("Enter"===e.key)return void(l>-1&&f(d[l]));if(0===c.length)return;const n=l>-1?c.indexOf(d[l]):-1;let r=n;switch(e.key){case"ArrowDown":n<c.length-1&&(r=n+1);break;case"ArrowUp":n>0?r=n-1:-1===n&&(r=0);break;case"Home":r=0;break;case"End":r=c.length-1}if(r!==n){const e=c[r];u(d.indexOf(e)),e.scrollIntoView({block:"nearest",behavior:"smooth"})}};n.addEventListener("mousemove",(e=>{const t=e.target.closest('[role="option"]');if(t&&c.includes(t)){const e=d.indexOf(t);e!==l&&u(e)}})),n.addEventListener("mouseleave",(()=>{const e=n.querySelector('[role="option"][aria-selected="true"]');u(e?d.indexOf(e):-1)})),t.addEventListener("keydown",h),o&&o.addEventListener("keydown",h);t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?b():(()=>{document.dispatchEvent(new CustomEvent("basecoat:popover",{detail:{source:e}})),o&&(v()?i.addEventListener("transitionend",(()=>{o.focus()}),{once:!0}):o.focus()),i.setAttribute("aria-hidden","false"),t.setAttribute("aria-expanded","true");const a=n.querySelector('[role="option"][aria-selected="true"]');a&&(u(d.indexOf(a)),a.scrollIntoView({block:"nearest"}))})()})),n.addEventListener("click",(e=>{const t=e.target.closest('[role="option"]');t&&f(t)})),document.addEventListener("click",(t=>{e.contains(t.target)||b(!1)})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&b(!1)})),i.setAttribute("aria-hidden","true"),e.selectByValue=e=>{const t=d.find((t=>t.dataset.value===e));f(t)},e.dataset.selectInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("select","div.select:not([data-select-initialized])",e)})();
@@ -99,7 +99,7 @@
99
99
  clearTimeout(state.timeoutId);
100
100
  toasts.delete(element);
101
101
 
102
- if (document.activeElement) document.activeElement.blur();
102
+ if (element.contains(document.activeElement)) document.activeElement.blur();
103
103
  element.setAttribute('aria-hidden', 'true');
104
104
  element.addEventListener('transitionend', () => element.remove(), { once: true });
105
105
  }
@@ -1 +1 @@
1
- (()=>{let t;const e=new WeakMap;let n=!1;const o={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(t){if(t.dataset.toastInitialized)return;const o=parseInt(t.dataset.duration),i=-1!==o?o||("error"===t.dataset.category?5e3:3e3):-1,a={remainingTime:i,timeoutId:null,startTime:null};-1!==i&&(n?a.timeoutId=null:(a.startTime=Date.now(),a.timeoutId=setTimeout((()=>s(t)),i))),e.set(t,a),t.dataset.toastInitialized="true"}function a(){n||(n=!0,t.querySelectorAll('.toast:not([aria-hidden="true"])').forEach((t=>{if(!e.has(t))return;const n=e.get(t);n.timeoutId&&(clearTimeout(n.timeoutId),n.timeoutId=null,n.remainingTime-=Date.now()-n.startTime)})))}function r(){n&&(n=!1,t.querySelectorAll('.toast:not([aria-hidden="true"])').forEach((t=>{if(!e.has(t))return;const n=e.get(t);-1===n.remainingTime||n.timeoutId||(n.remainingTime>0?(n.startTime=Date.now(),n.timeoutId=setTimeout((()=>s(t)),n.remainingTime)):s(t))})))}function s(t){if(!e.has(t))return;const n=e.get(t);clearTimeout(n.timeoutId),e.delete(t),document.activeElement&&document.activeElement.blur(),t.setAttribute("aria-hidden","true"),t.addEventListener("transitionend",(()=>t.remove()),{once:!0})}document.addEventListener("basecoat:toast",(e=>{if(!t)return void console.error("Cannot create toast: toaster container not found on page.");const n=function(t){const{category:e="info",title:n,description:i,action:a,cancel:r,duration:s,icon:d}=t,c=d||e&&o[e]||"",l=n?`<h2>${n}</h2>`:"",u=i?`<p>${i}</p>`:"",h=a?.href?`<a href="${a.href}" class="btn" data-toast-action>${a.label}</a>`:a?.onclick?`<button type="button" class="btn" data-toast-action onclick="${a.onclick}">${a.label}</button>`:"",m=r?`<button type="button" class="btn-outline h-6 text-xs px-2.5 rounded-sm" data-toast-cancel onclick="${r?.onclick}">${r.label}</button>`:"",w=`\n <div\n class="toast"\n role="${"error"===e?"alert":"status"}"\n aria-atomic="true"\n ${e?`data-category="${e}"`:""}\n ${void 0!==s?`data-duration="${s}"`:""}\n >\n <div class="toast-content">\n ${c}\n <section>\n ${l}\n ${u}\n </section>\n ${h||m?`<footer>${h}${m}</footer>`:""}\n </div>\n </div>\n </div>\n `,g=document.createElement("template");return g.innerHTML=w.trim(),g.content.firstChild}(e.detail?.config||{});t.appendChild(n)})),window.basecoat&&(window.basecoat.register("toaster","#toaster:not([data-toaster-initialized])",(function(e){e.dataset.toasterInitialized||(t=e,t.addEventListener("mouseenter",a),t.addEventListener("mouseleave",r),t.addEventListener("click",(t=>{const e=t.target.closest(".toast footer a"),n=t.target.closest(".toast footer button");(e||n)&&s(t.target.closest(".toast"))})),t.querySelectorAll(".toast:not([data-toast-initialized])").forEach(i),t.dataset.toasterInitialized="true",t.dispatchEvent(new CustomEvent("basecoat:initialized")))})),window.basecoat.register("toast",".toast:not([data-toast-initialized])",i))})();
1
+ (()=>{let t;const e=new WeakMap;let n=!1;const o={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(t){if(t.dataset.toastInitialized)return;const o=parseInt(t.dataset.duration),i=-1!==o?o||("error"===t.dataset.category?5e3:3e3):-1,a={remainingTime:i,timeoutId:null,startTime:null};-1!==i&&(n?a.timeoutId=null:(a.startTime=Date.now(),a.timeoutId=setTimeout((()=>s(t)),i))),e.set(t,a),t.dataset.toastInitialized="true"}function a(){n||(n=!0,t.querySelectorAll('.toast:not([aria-hidden="true"])').forEach((t=>{if(!e.has(t))return;const n=e.get(t);n.timeoutId&&(clearTimeout(n.timeoutId),n.timeoutId=null,n.remainingTime-=Date.now()-n.startTime)})))}function r(){n&&(n=!1,t.querySelectorAll('.toast:not([aria-hidden="true"])').forEach((t=>{if(!e.has(t))return;const n=e.get(t);-1===n.remainingTime||n.timeoutId||(n.remainingTime>0?(n.startTime=Date.now(),n.timeoutId=setTimeout((()=>s(t)),n.remainingTime)):s(t))})))}function s(t){if(!e.has(t))return;const n=e.get(t);clearTimeout(n.timeoutId),e.delete(t),t.contains(document.activeElement)&&document.activeElement.blur(),t.setAttribute("aria-hidden","true"),t.addEventListener("transitionend",(()=>t.remove()),{once:!0})}document.addEventListener("basecoat:toast",(e=>{if(!t)return void console.error("Cannot create toast: toaster container not found on page.");const n=function(t){const{category:e="info",title:n,description:i,action:a,cancel:r,duration:s,icon:d}=t,c=d||e&&o[e]||"",l=n?`<h2>${n}</h2>`:"",u=i?`<p>${i}</p>`:"",h=a?.href?`<a href="${a.href}" class="btn" data-toast-action>${a.label}</a>`:a?.onclick?`<button type="button" class="btn" data-toast-action onclick="${a.onclick}">${a.label}</button>`:"",m=r?`<button type="button" class="btn-outline h-6 text-xs px-2.5 rounded-sm" data-toast-cancel onclick="${r?.onclick}">${r.label}</button>`:"",w=`\n <div\n class="toast"\n role="${"error"===e?"alert":"status"}"\n aria-atomic="true"\n ${e?`data-category="${e}"`:""}\n ${void 0!==s?`data-duration="${s}"`:""}\n >\n <div class="toast-content">\n ${c}\n <section>\n ${l}\n ${u}\n </section>\n ${h||m?`<footer>${h}${m}</footer>`:""}\n </div>\n </div>\n </div>\n `,g=document.createElement("template");return g.innerHTML=w.trim(),g.content.firstChild}(e.detail?.config||{});t.appendChild(n)})),window.basecoat&&(window.basecoat.register("toaster","#toaster:not([data-toaster-initialized])",(function(e){e.dataset.toasterInitialized||(t=e,t.addEventListener("mouseenter",a),t.addEventListener("mouseleave",r),t.addEventListener("click",(t=>{const e=t.target.closest(".toast footer a"),n=t.target.closest(".toast footer button");(e||n)&&s(t.target.closest(".toast"))})),t.querySelectorAll(".toast:not([data-toast-initialized])").forEach(i),t.dataset.toasterInitialized="true",t.dispatchEvent(new CustomEvent("basecoat:initialized")))})),window.basecoat.register("toast",".toast:not([data-toast-initialized])",i))})();
@@ -0,0 +1,187 @@
1
+ {#
2
+ Renders a standalone command menu component with search and keyboard navigation.
3
+
4
+ @param id {string} [optional] - Unique identifier for the command component.
5
+ @param items {array} [optional] - Array of items to render (alternative to using caller).
6
+ @param placeholder {string} [optional] [default="Type a command or search..."] - Placeholder text for the search input.
7
+ @param empty_text {string} [optional] [default="No results found."] - Text displayed when no results match the search.
8
+ @param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
9
+ @param input_attrs {object} [optional] - Additional HTML attributes for the search input.
10
+ @param menu_attrs {object} [optional] - Additional HTML attributes for the menu div.
11
+ #}
12
+ {% macro command(
13
+ id=None,
14
+ items=None,
15
+ placeholder="Type a command or search...",
16
+ empty_text="No results found.",
17
+ main_attrs={},
18
+ input_attrs={},
19
+ menu_attrs={}
20
+ ) %}
21
+ {% set id = id or ("command-" + (range(100000, 999999) | random | string)) %}
22
+ <div
23
+ id="{{ id }}"
24
+ class="command {{ main_attrs.class }}"
25
+ aria-label="Command menu"
26
+ {% for key, value in main_attrs %}
27
+ {% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
28
+ {% endfor %}
29
+ >
30
+ <header>
31
+ <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-search-icon lucide-search"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
32
+ <input
33
+ type="text"
34
+ id="{{ id }}-input"
35
+ placeholder="{{ placeholder }}"
36
+ autocomplete="off"
37
+ autocorrect="off"
38
+ spellcheck="false"
39
+ aria-autocomplete="list"
40
+ role="combobox"
41
+ aria-expanded="true"
42
+ aria-controls="{{ id }}-menu"
43
+ {% for key, value in input_attrs %}
44
+ {{ key }}="{{ value }}"
45
+ {% endfor %}
46
+ >
47
+ </header>
48
+ <div
49
+ role="menu"
50
+ id="{{ id }}-menu"
51
+ aria-orientation="vertical"
52
+ data-empty="{{ empty_text }}"
53
+ class="scrollbar {{ menu_attrs.class }}"
54
+ {% for key, value in menu_attrs %}
55
+ {% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
56
+ {% endfor %}
57
+ >
58
+ {% if items and items.length > 0 %}
59
+ {{ render_command_items(items, id ~ "-items" if id else "items") }}
60
+ {% else %}
61
+ {{ caller() if caller }}
62
+ {% endif %}
63
+ </div>
64
+ </div>
65
+ {% endmacro %}
66
+
67
+ {#
68
+ Renders a command dialog (modal command palette).
69
+
70
+ @param id {string} [optional] - Unique identifier for the command dialog.
71
+ @param items {array} [optional] - Array of items to render (alternative to using caller).
72
+ @param placeholder {string} [optional] [default="Type a command or search..."] - Placeholder text for the search input.
73
+ @param empty_text {string} [optional] [default="No results found."] - Text displayed when no results match the search.
74
+ @param dialog_attrs {object} [optional] - Additional HTML attributes for the dialog element.
75
+ @param input_attrs {object} [optional] - Additional HTML attributes for the search input.
76
+ @param menu_attrs {object} [optional] - Additional HTML attributes for the menu div.
77
+ @param open {boolean} [optional] [default=false] - Whether the command dialog should be open initially.
78
+ #}
79
+ {% macro command_dialog(
80
+ id=None,
81
+ items=None,
82
+ placeholder="Type a command or search...",
83
+ empty_text="No results found.",
84
+ dialog_attrs={},
85
+ input_attrs={},
86
+ menu_attrs={},
87
+ open=false
88
+ ) %}
89
+ {% set id = id or ("command-dialog-" + (range(100000, 999999) | random | string)) %}
90
+ <dialog
91
+ id="{{ id }}"
92
+ class="command-dialog {{ dialog_attrs.class }}"
93
+ aria-label="Command menu"
94
+ {% if open %}open{% endif %}
95
+ onclick="if (event.target === this) this.close()"
96
+ {% for key, value in dialog_attrs %}
97
+ {% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
98
+ {% endfor %}
99
+ >
100
+ <div class="command">
101
+ <header>
102
+ <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-search-icon lucide-search"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
103
+ <input
104
+ type="text"
105
+ id="{{ id }}-input"
106
+ placeholder="{{ placeholder }}"
107
+ autocomplete="off"
108
+ autocorrect="off"
109
+ spellcheck="false"
110
+ aria-autocomplete="list"
111
+ role="combobox"
112
+ aria-expanded="true"
113
+ aria-controls="{{ id }}-menu"
114
+ {% for key, value in input_attrs %}
115
+ {{ key }}="{{ value }}"
116
+ {% endfor %}
117
+ >
118
+ </header>
119
+ <div
120
+ role="menu"
121
+ id="{{ id }}-menu"
122
+ aria-orientation="vertical"
123
+ data-empty="{{ empty_text }}"
124
+ class="scrollbar {{ menu_attrs.class }}"
125
+ {% for key, value in menu_attrs %}
126
+ {% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
127
+ {% endfor %}
128
+ >
129
+ {% if items and items.length > 0 %}
130
+ {{ render_command_items(items, id ~ "-items" if id else "items") }}
131
+ {% else %}
132
+ {{ caller() if caller }}
133
+ {% endif %}
134
+ </div>
135
+ <button type="button" aria-label="Close dialog" onclick="this.closest('dialog').close()">
136
+ <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>
137
+ </button>
138
+ </div>
139
+ </dialog>
140
+ {% endmacro %}
141
+
142
+ {#
143
+ Renders a list of items for the command component.
144
+
145
+ @param items {array} - Array of items to render. Each item can be:
146
+ - { type: "item", label: "Text", content: "HTML", keywords: "search terms", disabled: true, attrs: {} }
147
+ - { type: "group", label: "Group Title", items: [...] }
148
+ - { type: "separator" }
149
+ Note: Use attrs to add data-label for alternative search text if needed.
150
+ @param parent_id_prefix {string} [optional] - The prefix for the item id.
151
+ #}
152
+ {% macro render_command_items(items, parent_id_prefix="items") %}
153
+ {% for item in items %}
154
+ {% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
155
+ {% if item.type == "group" %}
156
+ {% set group_label_id = item.id if item.id else "group-label-" + item_id %}
157
+ <div
158
+ role="group"
159
+ aria-labelledby="{{ group_label_id }}"
160
+ {% if item.attrs %}
161
+ {% for key, value in item.attrs %}
162
+ {{ key }}="{{ value }}"
163
+ {% endfor %}
164
+ {% endif %}
165
+ >
166
+ <span role="heading" id="{{ group_label_id }}">{{ item.label }}</span>
167
+ {{ render_command_items(item.items, item_id) if item.items }}
168
+ </div>
169
+ {% elif item.type == "separator" %}
170
+ <hr role="separator" />
171
+ {% elif item.type == "item" or not item.type %}
172
+ <div
173
+ id="{{ item_id }}"
174
+ role="menuitem"
175
+ {% if item.keywords %}data-keywords="{{ item.keywords }}"{% endif %}
176
+ {% if item.disabled %}aria-disabled="true"{% endif %}
177
+ {% if item.attrs %}
178
+ {% for key, value in item.attrs %}
179
+ {{ key }}="{{ value }}"
180
+ {% endfor %}
181
+ {% endif %}
182
+ >
183
+ {{ item.content | safe if item.content else item.label }}
184
+ </div>
185
+ {% endif %}
186
+ {% endfor %}
187
+ {% endmacro %}
@@ -8,7 +8,7 @@
8
8
  @param footer {string} [optional] - HTML content for the dialog footer.
9
9
  @param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
10
10
  @param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
11
- @param dialog_attrs {object} [optional] - Additional HTML attributes for the dialog content article.
11
+ @param dialog_attrs {object} [optional] - Additional HTML attributes for the dialog content container.
12
12
  @param header_attrs {object} [optional] - Additional HTML attributes for the dialog header.
13
13
  @param body_attrs {object} [optional] - Additional HTML attributes for the dialog body section.
14
14
  @param footer_attrs {object} [optional] - Additional HTML attributes for the dialog footer.
@@ -40,7 +40,7 @@
40
40
  {{ key }}="{{ value }}"
41
41
  {% endfor %}
42
42
  >
43
- {{ trigger }}
43
+ {{ trigger | safe }}
44
44
  </button>
45
45
  {% endif %}
46
46
  <dialog
@@ -53,7 +53,7 @@
53
53
  {% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
54
54
  {% endfor %}
55
55
  >
56
- <article>
56
+ <div>
57
57
  {% if title or description %}
58
58
  <header
59
59
  {% for key, value in header_attrs %}
@@ -87,6 +87,6 @@
87
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>
88
88
  </button>
89
89
  {% endif %}
90
- </article>
90
+ </div>
91
91
  </dialog>
92
92
  {% endmacro %}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "basecoat-cli",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Add Basecoat components to your project",
5
5
  "author": "hunvreus",
6
6
  "license": "MIT",
@@ -30,8 +30,7 @@
30
30
  "css",
31
31
  "html",
32
32
  "jinja",
33
- "nunjucks",
34
- "alpinejs"
33
+ "nunjucks"
35
34
  ],
36
35
  "repository": {
37
36
  "type": "git",