mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-11 04:38:18 +00:00
Fixing bugs and docs issues
This commit is contained in:
@@ -3,25 +3,37 @@
|
||||
<head>
|
||||
{# Dark mode #}
|
||||
<script>
|
||||
(function() {
|
||||
(() => {
|
||||
try {
|
||||
const storedMode = localStorage.getItem('darkMode');
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
if (storedMode === 'dark' || (storedMode === null && prefersDark)) document.documentElement.classList.add('dark');
|
||||
} catch (e) {
|
||||
console.error('Could not apply dark mode from localStorage', e);
|
||||
}
|
||||
const stored = localStorage.getItem('themeMode');
|
||||
if (stored ? stored === 'dark'
|
||||
: matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
const apply = dark => {
|
||||
document.documentElement.classList.toggle('dark', dark);
|
||||
try { localStorage.setItem('themeMode', dark ? 'dark' : 'light'); } catch (_) {}
|
||||
};
|
||||
|
||||
document.addEventListener('basecoat:theme', (event) => {
|
||||
const mode = event.detail?.mode;
|
||||
apply(mode === 'dark' ? true
|
||||
: mode === 'light' ? false
|
||||
: !document.documentElement.classList.contains('dark'));
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script src="/assets/js/dark-mode.js" defer></script>
|
||||
<script src="/assets/js/theme-switcher.js" defer></script>
|
||||
{# Theme variant #}
|
||||
<script>
|
||||
(function() {
|
||||
try {
|
||||
const storedTheme = localStorage.getItem('themeVariant');
|
||||
if (storedTheme) document.documentElement.classList.add(`theme-${storedTheme}`);
|
||||
} catch (e) {
|
||||
console.error('Could not apply theme variant from localStorage', e);
|
||||
} catch (event) {
|
||||
console.error('Could not apply theme variant from localStorage', event);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="flex h-14 w-full items-center gap-2 px-4">
|
||||
<button
|
||||
type="button"
|
||||
onclick="window.dispatchEvent(new CustomEvent('sidebar:toggle'))"
|
||||
onclick="document.dispatchEvent(new CustomEvent('basecoat:sidebar'))"
|
||||
aria-label="Toggle sidebar"
|
||||
data-tooltip="Toggle sidebar"
|
||||
data-side="bottom"
|
||||
@@ -44,7 +44,7 @@
|
||||
aria-label="Toggle dark mode"
|
||||
data-tooltip="Toggle dark mode"
|
||||
data-side="bottom"
|
||||
data-action="toggle-dark-mode"
|
||||
onclick="document.dispatchEvent(new CustomEvent('basecoat:theme'))"
|
||||
class="btn-icon-outline size-8"
|
||||
>
|
||||
<span class="hidden dark:block">{% lucide "sun" %}</span>
|
||||
|
||||
@@ -31,6 +31,6 @@
|
||||
};
|
||||
|
||||
updateSlider(sliderEl);
|
||||
sliderEl.addEventListener('input', (e) => updateSlider(e.target));
|
||||
sliderEl.addEventListener('input', (event) => updateSlider(event.target));
|
||||
})();
|
||||
</script>
|
||||
@@ -680,9 +680,6 @@
|
||||
.gap-x-2 {
|
||||
column-gap: calc(var(--spacing) * 2);
|
||||
}
|
||||
.gap-x-3 {
|
||||
column-gap: calc(var(--spacing) * 3);
|
||||
}
|
||||
.gap-x-10 {
|
||||
column-gap: calc(var(--spacing) * 10);
|
||||
}
|
||||
@@ -709,9 +706,6 @@
|
||||
.gap-y-10 {
|
||||
row-gap: calc(var(--spacing) * 10);
|
||||
}
|
||||
.self-start {
|
||||
align-self: flex-start;
|
||||
}
|
||||
.truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -1021,6 +1015,11 @@
|
||||
.filter {
|
||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||
}
|
||||
.transition {
|
||||
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
}
|
||||
.transition-all {
|
||||
transition-property: all;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
@@ -1596,45 +1595,17 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.\[\&\>\[role\=img\]\]\:size-4 {
|
||||
&>[role=img] {
|
||||
width: calc(var(--spacing) * 4);
|
||||
height: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
.\[\&\>\[role\=img\]\]\:shrink-0 {
|
||||
&>[role=img] {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
.\[\&\>\[role\=img\]\>svg\]\:size-4 {
|
||||
&>[role=img]>svg {
|
||||
width: calc(var(--spacing) * 4);
|
||||
height: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
.\[\&\>svg\]\:mb-3 {
|
||||
&>svg {
|
||||
margin-bottom: calc(var(--spacing) * 3);
|
||||
}
|
||||
}
|
||||
.\[\&\>svg\]\:size-4 {
|
||||
&>svg {
|
||||
width: calc(var(--spacing) * 4);
|
||||
height: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
.\[\&\>svg\]\:size-6 {
|
||||
&>svg {
|
||||
width: calc(var(--spacing) * 6);
|
||||
height: calc(var(--spacing) * 6);
|
||||
}
|
||||
}
|
||||
.\[\&\>svg\]\:shrink-0 {
|
||||
&>svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
@@ -2536,7 +2507,6 @@
|
||||
.dropdown-menu {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
padding: calc(var(--spacing) * 1);
|
||||
[data-popover] {
|
||||
padding: calc(var(--spacing) * 1);
|
||||
min-width: anchor-size(width);
|
||||
|
||||
@@ -32,7 +32,7 @@ toc:
|
||||
trigger_attrs={"class": "flex size-9 items-center justify-center h-4 w-4 hover:text-foreground cursor-pointer"}
|
||||
) %}
|
||||
<nav role="menu">
|
||||
<button type="button" role="menuitem" onclick="console.log('Documentation')">Documentation</button>
|
||||
<button type="button" role="menuitem">Documentation</button>
|
||||
<button type="button" role="menuitem">Themes</button>
|
||||
<button type="button" role="menuitem">GitHub</button>
|
||||
</nav>
|
||||
|
||||
@@ -575,7 +575,7 @@ const formatDateLabel = (dateString) => {
|
||||
try {
|
||||
const date = new Date(dateString + 'T00:00:00');
|
||||
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); // e.g., "Apr 1"
|
||||
} catch (e) {
|
||||
} catch (event) {
|
||||
return dateString; // Fallback
|
||||
}
|
||||
};
|
||||
|
||||
@@ -32,7 +32,7 @@ toc:
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/select.min.js" defer></script>{% endset %}
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/sidebar.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
@@ -76,7 +76,7 @@ toc:
|
||||
menu=menu
|
||||
) }}
|
||||
<main>
|
||||
<button type="button" onclick="window.dispatchEvent(new CustomEvent('sidebar:toggle'))">Toggle sidebar</button>
|
||||
<button type="button" onclick="window.dispatchEvent(new CustomEvent('basecoat:sidebar'))">Toggle sidebar</button>
|
||||
<h1>Content</h1>
|
||||
</main>
|
||||
{% endset %}
|
||||
@@ -95,7 +95,7 @@ toc:
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>Any elememt on the page can toggle, open or close the sidebar by dispatching the <code>sidebar:toggle</code>, <code>sidebar:open</code> or <code>sidebar:close</code> events.</p>
|
||||
<p>Any elememt on the page can toggle, open or close the sidebar by dispatching the <code>basecoat:sidebar</code>, <code>sidebar:open</code> or <code>sidebar:close</code> events.</p>
|
||||
|
||||
<p>You can include the JavaScript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
@@ -94,6 +94,10 @@ toc:
|
||||
{% set code_toaster %}<div id="toaster" class="toaster"></div>{% endset %}
|
||||
{{ code_block(code_toaster | prettyHtml, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>You can set up the alignment of the toaster using the <code>data-align</code> attribute: <code>start</code>, <code>center</code>, or <code>end</code> (default to <code>end</code>).</p>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">Step 3: Add your toasts</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
@@ -248,7 +252,7 @@ toc:
|
||||
{{ toast(
|
||||
title="Event has been created",
|
||||
description="Sunday, December 03, 2023 at 9:00 AM",
|
||||
cancel={ label: "Undo", click: "close()" }
|
||||
cancel={ label: "Undo" }
|
||||
) }}{% endraw %}{% endset %}
|
||||
|
||||
{% from "tabs.njk" import tabs %}
|
||||
|
||||
+19
-16
@@ -51,15 +51,17 @@ layout: layouts/layout.njk
|
||||
<p class="text-sm text-muted-foreground">p@example.com</p>
|
||||
</div>
|
||||
|
||||
{% call select(
|
||||
{{ select(
|
||||
is_combobox=true,
|
||||
popover_attrs={"data-align": "end"}
|
||||
) %}
|
||||
<div role="option" data-value="viewer">Viewer</div>
|
||||
<div role="option" data-value="developer">Developer</div>
|
||||
<div role="option" data-value="billing">Billing</div>
|
||||
<div role="option" data-value="owner">Owner</div>
|
||||
{% endcall %}
|
||||
popover_attrs={"data-align": "end"},
|
||||
items=[
|
||||
{ type: "item", value: "", label: "Empty" },
|
||||
{ type: "item", value: "viewer", label: "Viewer" },
|
||||
{ type: "item", value: "developer", label: "Developer" },
|
||||
{ type: "item", value: "billing", label: "Billing" },
|
||||
{ type: "item", value: "owner", label: "Owner" }
|
||||
]
|
||||
) }}
|
||||
</li>
|
||||
<li class="flex items-center gap-4">
|
||||
<img src="/assets/images/avatar-3.png" alt="Isabella Nguyen" class="w-10 h-10 rounded-full" />
|
||||
@@ -68,15 +70,16 @@ layout: layouts/layout.njk
|
||||
<p class="text-sm text-muted-foreground">i@example.com</p>
|
||||
</div>
|
||||
|
||||
{% call select(
|
||||
{{ select(
|
||||
is_combobox=true,
|
||||
popover_attrs={"data-align": "end"}
|
||||
) %}
|
||||
<div role="option" data-value="viewer">Viewer</div>
|
||||
<div role="option" data-value="developer">Developer</div>
|
||||
<div role="option" data-value="billing">Billing</div>
|
||||
<div role="option" data-value="owner">Owner</div>
|
||||
{% endcall %}
|
||||
popover_attrs={"data-align": "end"},
|
||||
items=[
|
||||
{ type: "item", value: "viewer", label: "Viewer" },
|
||||
{ type: "item", value: "developer", label: "Developer" },
|
||||
{ type: "item", value: "billing", label: "Billing" },
|
||||
{ type: "item", value: "owner", label: "Owner" }
|
||||
]
|
||||
) }}
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -171,6 +171,8 @@ import 'basecoat-css/tabs';{% endraw %}{% endset %}
|
||||
<p>For more complex components, you can use <a href="https://mozilla.github.io/nunjucks/" target="_blank">Nunjucks</a> or <a href="https://jinja.palletsprojects.com" target="_blank">Jinja</a> macros to help you generate the HTML and JavaScript code.</p>
|
||||
<p>To install them, either <a href="https://github.com/hunvreus/basecoat/tree/main/src" target="_blank">copy them directly from the GitHub repository</a> or use <a href="#install-cli">the CLI</a> to do the work for you.</p>
|
||||
<p>For example, here's how you could use the <code>select()</code> macro to generate the HTML and JavaScript code for a <a href="/components/select">Select</a> component:</p>
|
||||
</div>
|
||||
|
||||
{% set code %}{% raw %}{% call select() %}
|
||||
<div role="group" aria-labelledby="fruit-options-label">
|
||||
<span id="fruit-options-label" role="heading">Fruits</span>
|
||||
@@ -182,16 +184,6 @@ import 'basecoat-css/tabs';{% endraw %}{% endset %}
|
||||
</div>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(code, "jinja") }}
|
||||
<p>Make sure to include the JavaScript code <b>before</b> Alpine.js core JS file:</p>
|
||||
{% set code %}<script src="/assets/js/select.js" defer></script>
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerSelect(Alpine);
|
||||
});
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code, "jinja") }}
|
||||
</div>
|
||||
|
||||
<h2 id="install-theming"><a href="#install-theming">Theming</a></h2>
|
||||
|
||||
@@ -233,7 +225,7 @@ import 'basecoat-css/tabs';{% endraw %}{% endset %}
|
||||
|
||||
{% set code %}@import "tailwindcss";
|
||||
@import "basecoat-css";
|
||||
@import "theme.css";{% endset %}
|
||||
@import "./custom.css";{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
|
||||
<section class="prose">
|
||||
|
||||
@@ -504,7 +504,7 @@
|
||||
/* Dropdown Menu */
|
||||
@layer components {
|
||||
.dropdown-menu {
|
||||
@apply relative inline-flex p-1;
|
||||
@apply relative inline-flex;
|
||||
|
||||
[data-popover] {
|
||||
@apply p-1;
|
||||
|
||||
+18
-16
@@ -16,7 +16,7 @@
|
||||
id=None,
|
||||
selected=None,
|
||||
name=None,
|
||||
items=None,
|
||||
items=[],
|
||||
main_attrs={},
|
||||
trigger_attrs={},
|
||||
popover_attrs={},
|
||||
@@ -30,25 +30,27 @@
|
||||
{% set first_option = namespace(item=None) %}
|
||||
{% set selected_option = namespace(item=None) %}
|
||||
|
||||
{% for item in items %}
|
||||
{% if item.type == "group" %}
|
||||
{% for sub_item in item.items %}
|
||||
{% if items %}
|
||||
{% for item in items %}
|
||||
{% if item.type == "group" %}
|
||||
{% for sub_item in item.items %}
|
||||
{% if not first_option.item %}
|
||||
{% set first_option.item = sub_item %}
|
||||
{% endif %}
|
||||
{% if selected and sub_item.value == selected and not selected_option.item %}
|
||||
{% set selected_option.item = sub_item %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% if not first_option.item %}
|
||||
{% set first_option.item = sub_item %}
|
||||
{% set first_option.item = item %}
|
||||
{% endif %}
|
||||
{% if selected and sub_item.value == selected and not selected_option.item %}
|
||||
{% set selected_option.item = sub_item %}
|
||||
{% if selected and item.value == selected and not selected_option.item %}
|
||||
{% set selected_option.item = item %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% if not first_option.item %}
|
||||
{% set first_option.item = item %}
|
||||
{% endif %}
|
||||
{% if selected and item.value == selected and not selected_option.item %}
|
||||
{% set selected_option.item = item %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% set default_option = selected_option.item or first_option.item or None %}
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
(() => {
|
||||
document.body.addEventListener('click', (event) => {
|
||||
const toggleButton = event.target.closest('[data-action="toggle-dark-mode"]');
|
||||
|
||||
if (!toggleButton) return;
|
||||
|
||||
const isDark = document.documentElement.classList.toggle('dark');
|
||||
localStorage.setItem('darkMode', isDark ? 'dark' : 'light');
|
||||
});
|
||||
})();
|
||||
+27
-19
@@ -5,7 +5,11 @@
|
||||
const menu = popover.querySelector('[role="menu"]');
|
||||
|
||||
if (!trigger || !menu || !popover) {
|
||||
console.error('Dropdown menu component is missing a trigger, a menu, or a popover content element.', dropdownMenuComponent);
|
||||
const missing = [];
|
||||
if (!trigger) missing.push('trigger');
|
||||
if (!menu) missing.push('menu');
|
||||
if (!popover) missing.push('popover');
|
||||
console.error(`Dropdown menu initialisation failed. Missing element(s): ${missing.join(', ')}`, dropdownMenuComponent);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,7 +22,7 @@
|
||||
trigger.removeAttribute('aria-activedescendant');
|
||||
popover.setAttribute('aria-hidden', 'true');
|
||||
trigger.focus();
|
||||
activeIndex = -1;
|
||||
setActiveItem(-1);
|
||||
};
|
||||
|
||||
const openMenu = () => {
|
||||
@@ -53,17 +57,17 @@
|
||||
}
|
||||
});
|
||||
|
||||
dropdownMenuComponent.addEventListener('keydown', (e) => {
|
||||
dropdownMenuComponent.addEventListener('keydown', (event) => {
|
||||
const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
if (event.key === 'Escape') {
|
||||
if (isExpanded) closeMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isExpanded) {
|
||||
if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(event.key)) {
|
||||
event.preventDefault();
|
||||
openMenu();
|
||||
}
|
||||
return;
|
||||
@@ -73,26 +77,30 @@
|
||||
|
||||
let nextIndex = activeIndex;
|
||||
|
||||
switch (e.key) {
|
||||
switch (event.key) {
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
nextIndex = activeIndex < menuItems.length - 1 ? activeIndex + 1 : 0;
|
||||
break;
|
||||
event.preventDefault();
|
||||
if (activeIndex < menuItems.length - 1) {
|
||||
nextIndex = activeIndex + 1;
|
||||
}
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
nextIndex = activeIndex > 0 ? activeIndex - 1 : menuItems.length - 1;
|
||||
event.preventDefault();
|
||||
if (activeIndex > 0) {
|
||||
nextIndex = activeIndex - 1;
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
e.preventDefault();
|
||||
event.preventDefault();
|
||||
nextIndex = 0;
|
||||
break;
|
||||
case 'End':
|
||||
e.preventDefault();
|
||||
event.preventDefault();
|
||||
nextIndex = menuItems.length - 1;
|
||||
break;
|
||||
case 'Enter':
|
||||
case ' ':
|
||||
e.preventDefault();
|
||||
event.preventDefault();
|
||||
menuItems[activeIndex]?.click();
|
||||
closeMenu();
|
||||
return;
|
||||
@@ -103,14 +111,14 @@
|
||||
}
|
||||
});
|
||||
|
||||
menu.addEventListener('click', (e) => {
|
||||
if (e.target.closest('[role^="menuitem"]')) {
|
||||
menu.addEventListener('click', (event) => {
|
||||
if (event.target.closest('[role^="menuitem"]')) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!dropdownMenuComponent.contains(e.target)) {
|
||||
document.addEventListener('click', (event) => {
|
||||
if (!dropdownMenuComponent.contains(event.target)) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
+8
-5
@@ -4,7 +4,10 @@
|
||||
const content = popoverComponent.querySelector(':scope > [data-popover]');
|
||||
|
||||
if (!trigger || !content) {
|
||||
console.error('Popover component is missing a trigger button or a content element.', popoverComponent);
|
||||
const missing = [];
|
||||
if (!trigger) missing.push('trigger');
|
||||
if (!content) missing.push('content');
|
||||
console.error(`Popover initialisation failed. Missing element(s): ${missing.join(', ')}`, popoverComponent);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -36,14 +39,14 @@
|
||||
}
|
||||
});
|
||||
|
||||
popoverComponent.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
popoverComponent.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
closePopover();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!popoverComponent.contains(e.target)) {
|
||||
document.addEventListener('click', (event) => {
|
||||
if (!popoverComponent.contains(event.target)) {
|
||||
closePopover();
|
||||
}
|
||||
});
|
||||
|
||||
+65
-24
@@ -6,12 +6,25 @@
|
||||
const listbox = popover.querySelector('[role="listbox"]');
|
||||
const input = selectComponent.querySelector(':scope > input[type="hidden"]');
|
||||
const filter = selectComponent.querySelector('header input[type="text"]');
|
||||
if (!trigger || !popover || !listbox || !input) return;
|
||||
if (!trigger || !popover || !listbox || !input) {
|
||||
const missing = [];
|
||||
if (!trigger) missing.push('trigger');
|
||||
if (!popover) missing.push('popover');
|
||||
if (!listbox) missing.push('listbox');
|
||||
if (!input) missing.push('input');
|
||||
console.error(`Select component initialisation failed. Missing element(s): ${missing.join(', ')}`, selectComponent);
|
||||
return;
|
||||
}
|
||||
|
||||
const options = Array.from(listbox.querySelectorAll('[role="option"]'));
|
||||
let visibleOptions = [...options];
|
||||
let activeIndex = -1;
|
||||
|
||||
const hasTransition = () => {
|
||||
const style = getComputedStyle(popover);
|
||||
return parseFloat(style.transitionDuration) > 0 || parseFloat(style.transitionDelay) > 0;
|
||||
};
|
||||
|
||||
const updateValue = (option) => {
|
||||
if (option) {
|
||||
selectedValue.innerHTML = option.dataset.label || option.innerHTML;
|
||||
@@ -21,14 +34,26 @@
|
||||
}
|
||||
};
|
||||
|
||||
const closePopover = () => {
|
||||
const closePopover = (focusOnTrigger = true) => {
|
||||
if (popover.getAttribute('aria-hidden') === 'true') return;
|
||||
|
||||
if (filter) {
|
||||
const resetFilter = () => {
|
||||
filter.value = '';
|
||||
visibleOptions = [...options];
|
||||
options.forEach(opt => opt.setAttribute('aria-hidden', 'false'));
|
||||
};
|
||||
|
||||
if (hasTransition()) {
|
||||
popover.addEventListener('transitionend', resetFilter, { once: true });
|
||||
} else {
|
||||
resetFilter();
|
||||
}
|
||||
}
|
||||
|
||||
if (focusOnTrigger) trigger.focus();
|
||||
popover.setAttribute('aria-hidden', 'true');
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
if (filter) {
|
||||
filter.value = '';
|
||||
visibleOptions = [...options];
|
||||
options.forEach(opt => opt.setAttribute('aria-hidden', 'false'));
|
||||
}
|
||||
trigger.removeAttribute('aria-activedescendant');
|
||||
if (activeIndex > -1) options[activeIndex]?.classList.remove('active');
|
||||
activeIndex = -1;
|
||||
@@ -37,10 +62,17 @@
|
||||
const selectOption = (option) => {
|
||||
if (!option) return;
|
||||
|
||||
if (option.dataset.value) {
|
||||
if (option.dataset.value != null) {
|
||||
updateValue(option);
|
||||
}
|
||||
|
||||
closePopover();
|
||||
|
||||
const event = new CustomEvent('change', {
|
||||
detail: { value: option.dataset.value },
|
||||
bubbles: true
|
||||
});
|
||||
selectComponent.dispatchEvent(event);
|
||||
};
|
||||
|
||||
if (filter) {
|
||||
@@ -67,36 +99,36 @@
|
||||
filter.addEventListener('input', filterOptions);
|
||||
}
|
||||
|
||||
let initialOption = options.find(opt => input.value && opt.dataset.value === input.value);
|
||||
let initialOption = options.find(opt => opt.dataset.value === input.value);
|
||||
if (!initialOption && options.length > 0) initialOption = options[0];
|
||||
|
||||
updateValue(initialOption);
|
||||
|
||||
const handleKeyNavigation = (e) => {
|
||||
const handleKeyNavigation = (event) => {
|
||||
const isPopoverOpen = popover.getAttribute('aria-hidden') === 'false';
|
||||
|
||||
if (!['ArrowDown', 'ArrowUp', 'Enter', 'Home', 'End', 'Escape'].includes(e.key)) {
|
||||
if (!['ArrowDown', 'ArrowUp', 'Enter', 'Home', 'End', 'Escape'].includes(event.key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPopoverOpen) {
|
||||
if (e.key !== 'Enter' && e.key !== 'Escape') {
|
||||
e.preventDefault();
|
||||
if (event.key !== 'Enter' && event.key !== 'Escape') {
|
||||
event.preventDefault();
|
||||
trigger.click();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
event.preventDefault();
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
if (event.key === 'Escape') {
|
||||
closePopover();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'Enter') {
|
||||
if (event.key === 'Enter') {
|
||||
if (activeIndex > -1) {
|
||||
selectOption(visibleOptions[activeIndex]);
|
||||
selectOption(options[activeIndex]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -106,7 +138,7 @@
|
||||
const currentVisibleIndex = activeIndex > -1 ? visibleOptions.indexOf(options[activeIndex]) : -1;
|
||||
let nextVisibleIndex = currentVisibleIndex;
|
||||
|
||||
switch (e.key) {
|
||||
switch (event.key) {
|
||||
case 'ArrowDown':
|
||||
if (currentVisibleIndex < visibleOptions.length - 1) {
|
||||
nextVisibleIndex = currentVisibleIndex + 1;
|
||||
@@ -154,9 +186,18 @@
|
||||
if (isExpanded) {
|
||||
closePopover();
|
||||
} else {
|
||||
if (filter) {
|
||||
if (hasTransition()) {
|
||||
popover.addEventListener('transitionend', () => {
|
||||
filter.focus();
|
||||
}, { once: true });
|
||||
} else {
|
||||
filter.focus();
|
||||
}
|
||||
}
|
||||
|
||||
popover.setAttribute('aria-hidden', 'false');
|
||||
trigger.setAttribute('aria-expanded', 'true');
|
||||
if (filter) filter.focus();
|
||||
|
||||
const selectedOption = listbox.querySelector('[role="option"][aria-selected="true"]');
|
||||
if (selectedOption) {
|
||||
@@ -173,16 +214,16 @@
|
||||
}
|
||||
});
|
||||
|
||||
listbox.addEventListener('click', (e) => {
|
||||
const clickedOption = e.target.closest('[role="option"]');
|
||||
listbox.addEventListener('click', (event) => {
|
||||
const clickedOption = event.target.closest('[role="option"]');
|
||||
if (clickedOption) {
|
||||
selectOption(clickedOption);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!selectComponent.contains(e.target)) {
|
||||
closePopover();
|
||||
document.addEventListener('click', (event) => {
|
||||
if (!selectComponent.contains(event.target)) {
|
||||
closePopover(false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+16
-10
@@ -55,18 +55,24 @@
|
||||
|
||||
const sidebarId = sidebarComponent.id;
|
||||
|
||||
window.addEventListener('sidebar:open', (e) => {
|
||||
if (!e.detail?.id || e.detail.id === sidebarId) setState(true);
|
||||
});
|
||||
window.addEventListener('sidebar:close', (e) => {
|
||||
if (!e.detail?.id || e.detail.id === sidebarId) setState(false);
|
||||
});
|
||||
window.addEventListener('sidebar:toggle', (e) => {
|
||||
if (!e.detail?.id || e.detail.id === sidebarId) setState(!open);
|
||||
document.addEventListener('basecoat:sidebar', (event) => {
|
||||
if (event.detail?.id && event.detail.id !== sidebarId) return;
|
||||
|
||||
switch (event.detail?.action) {
|
||||
case 'open':
|
||||
setState(true);
|
||||
break;
|
||||
case 'close':
|
||||
setState(false);
|
||||
break;
|
||||
default:
|
||||
setState(!open);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
sidebarComponent.addEventListener('click', (e) => {
|
||||
const target = e.target;
|
||||
sidebarComponent.addEventListener('click', (event) => {
|
||||
const target = event.target;
|
||||
const nav = sidebarComponent.querySelector('nav');
|
||||
|
||||
const isMobile = window.innerWidth < breakpoint;
|
||||
|
||||
+6
-6
@@ -19,19 +19,19 @@
|
||||
if (activePanel) activePanel.hidden = false;
|
||||
};
|
||||
|
||||
tablist.addEventListener('click', (e) => {
|
||||
const clickedTab = e.target.closest('[role="tab"]');
|
||||
tablist.addEventListener('click', (event) => {
|
||||
const clickedTab = event.target.closest('[role="tab"]');
|
||||
if (clickedTab) selectTab(clickedTab);
|
||||
});
|
||||
|
||||
tablist.addEventListener('keydown', (e) => {
|
||||
const currentTab = e.target;
|
||||
tablist.addEventListener('keydown', (event) => {
|
||||
const currentTab = event.target;
|
||||
if (!tabs.includes(currentTab)) return;
|
||||
|
||||
let nextTab;
|
||||
const currentIndex = tabs.indexOf(currentTab);
|
||||
|
||||
switch (e.key) {
|
||||
switch (event.key) {
|
||||
case 'ArrowRight':
|
||||
nextTab = tabs[(currentIndex + 1) % tabs.length];
|
||||
break;
|
||||
@@ -48,7 +48,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
event.preventDefault();
|
||||
selectTab(nextTab);
|
||||
nextTab.focus();
|
||||
});
|
||||
|
||||
+8
-8
@@ -15,11 +15,11 @@
|
||||
|
||||
toaster.addEventListener('mouseenter', pauseAllTimeouts);
|
||||
toaster.addEventListener('mouseleave', resumeAllTimeouts);
|
||||
toaster.addEventListener('click', (e) => {
|
||||
const actionLink = e.target.closest('.toast footer a');
|
||||
const actionButton = e.target.closest('.toast footer button');
|
||||
toaster.addEventListener('click', (event) => {
|
||||
const actionLink = event.target.closest('.toast footer a');
|
||||
const actionButton = event.target.closest('.toast footer button');
|
||||
if (actionLink || actionButton) {
|
||||
closeToast(e.target.closest('.toast'));
|
||||
closeToast(event.target.closest('.toast'));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -109,8 +109,8 @@
|
||||
try {
|
||||
const func = new Function('close', actionString);
|
||||
func(() => closeToast(toast));
|
||||
} catch (e) {
|
||||
console.error('Error executing toast action:', e);
|
||||
} catch (event) {
|
||||
console.error('Error executing toast action:', event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,12 +166,12 @@
|
||||
const initialToaster = document.getElementById('toaster');
|
||||
if (initialToaster) initToaster(initialToaster);
|
||||
|
||||
window.addEventListener('basecoat:toast', (e) => {
|
||||
window.addEventListener('basecoat:toast', (event) => {
|
||||
if (!toaster) {
|
||||
console.error('Cannot create toast: toaster container not found on page.');
|
||||
return;
|
||||
}
|
||||
const config = e.detail?.config || {};
|
||||
const config = event.detail?.config || {};
|
||||
const toastElement = createToast(config);
|
||||
toaster.appendChild(toastElement);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user