fix(ui): repair sidebar drawer, dropdown focus and footer slots
This commit is contained in:
1 parent
f7fe3bb17b
commit
6acf78a72c
8 files changed
+378
-34
No files matched your search
@@ -12,6 +12,7 @@ items: unknown[] = []
|
||||
open: boolean = false
|
||||
defaultOpen: boolean = false
|
||||
label: string = "Open menu"
|
||||
ariaLabel: string = ""
|
||||
icon: string = ""
|
||||
showChevron: boolean = true
|
||||
menuLabel: string = "Dropdown menu"
|
||||
@@ -47,6 +48,11 @@ items: unknown[] = []
|
||||
visible = false
|
||||
output.close({ reason: reason, sourceEvent: sourceEvent })
|
||||
output.toggle({ open: false, reason: reason, sourceEvent: sourceEvent })
|
||||
if (reason === "escape") {
|
||||
const root = sourceEvent.currentTarget.closest(".wrn-dropdown") || sourceEvent.currentTarget
|
||||
const trigger = root.querySelector(".wrn-dropdown__trigger")
|
||||
if (trigger) { trigger.focus() }
|
||||
}
|
||||
}
|
||||
|
||||
client function toggleMenu(sourceEvent) {
|
||||
@@ -65,14 +71,14 @@ items: unknown[] = []
|
||||
output.select({
|
||||
item: item,
|
||||
itemIndex: itemIndex,
|
||||
value: item.value || "",
|
||||
value: item.value == null ? "" : item.value,
|
||||
sourceEvent: sourceEvent
|
||||
})
|
||||
if (item.action) {
|
||||
output.action({
|
||||
item: item,
|
||||
itemIndex: itemIndex,
|
||||
value: item.value || "",
|
||||
value: item.value == null ? "" : item.value,
|
||||
sourceEvent: sourceEvent
|
||||
})
|
||||
}
|
||||
@@ -83,18 +89,24 @@ items: unknown[] = []
|
||||
|
||||
client function moveFocus(sourceEvent, direction) {
|
||||
const root = sourceEvent.currentTarget.closest(".wrn-dropdown") || sourceEvent.currentTarget
|
||||
const options = [...root.querySelectorAll("[data-dropdown-item]:not([disabled])")]
|
||||
if (!options.length) {
|
||||
return
|
||||
}
|
||||
const activeIndex = options.indexOf(document.activeElement)
|
||||
const nextIndex = (activeIndex + direction + options.length) % options.length
|
||||
options[nextIndex].focus()
|
||||
window.setTimeout(function () {
|
||||
if (!root.isConnected || !isOpen()) { return }
|
||||
const options = [...root.querySelectorAll('[data-dropdown-item]:not([disabled]):not([aria-disabled="true"])')]
|
||||
if (!options.length) {
|
||||
return
|
||||
}
|
||||
const activeIndex = options.indexOf(document.activeElement)
|
||||
const nextIndex = activeIndex < 0 ? (direction > 0 ? 0 : options.length - 1) : (activeIndex + direction + options.length) % options.length
|
||||
options[nextIndex].focus()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
client function handleKeydown(sourceEvent) {
|
||||
if (disabled) { return }
|
||||
if (sourceEvent.key === "Escape") {
|
||||
if (!isOpen()) { return }
|
||||
sourceEvent.preventDefault()
|
||||
sourceEvent.stopPropagation()
|
||||
hideMenu("escape", sourceEvent)
|
||||
} else if (sourceEvent.key === "ArrowDown") {
|
||||
sourceEvent.preventDefault()
|
||||
@@ -111,19 +123,26 @@ items: unknown[] = []
|
||||
} else if (sourceEvent.key === "Home" && isOpen()) {
|
||||
sourceEvent.preventDefault()
|
||||
const root = sourceEvent.currentTarget.closest(".wrn-dropdown") || sourceEvent.currentTarget
|
||||
const first = root.querySelector("[data-dropdown-item]:not([disabled])")
|
||||
const first = root.querySelector('[data-dropdown-item]:not([disabled]):not([aria-disabled="true"])')
|
||||
if (first) {
|
||||
first.focus()
|
||||
}
|
||||
} else if (sourceEvent.key === "End" && isOpen()) {
|
||||
sourceEvent.preventDefault()
|
||||
const root = sourceEvent.currentTarget.closest(".wrn-dropdown") || sourceEvent.currentTarget
|
||||
const options = [...root.querySelectorAll("[data-dropdown-item]:not([disabled])")]
|
||||
const options = [...root.querySelectorAll('[data-dropdown-item]:not([disabled]):not([aria-disabled="true"])')]
|
||||
if (options.length) {
|
||||
options[options.length - 1].focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client function handleFocusout(sourceEvent) {
|
||||
const root = sourceEvent.currentTarget
|
||||
if (isOpen() && sourceEvent.relatedTarget && !root.contains(sourceEvent.relatedTarget)) {
|
||||
hideMenu("focusout", sourceEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
@@ -138,12 +157,14 @@ items: unknown[] = []
|
||||
data-variant='{variant}'
|
||||
class='wrn-dropdown {class}'
|
||||
@keydown='handleKeydown(event)'
|
||||
@focusout='handleFocusout(event)'
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="wrn-dropdown__trigger"
|
||||
disabled='{disabled}'
|
||||
aria-haspopup="menu"
|
||||
aria-label='{ariaLabel || label}'
|
||||
aria-expanded='{open || visible ? "true" : "false"}'
|
||||
@click='toggleMenu(event)'
|
||||
>
|
||||
@@ -194,6 +215,7 @@ items: unknown[] = []
|
||||
data-danger='{item.danger ? "true" : "false"}'
|
||||
data-selected='{item.selected || item.checked ? "true" : "false"}'
|
||||
aria-disabled='{item.disabled ? "true" : "false"}'
|
||||
tabindex='{item.disabled ? "-1" : "0"}'
|
||||
class="wrn-dropdown__item"
|
||||
@click='chooseItem(item, itemIndex, event)'
|
||||
>
|
||||
@@ -460,7 +482,7 @@ items: unknown[] = []
|
||||
border: 0;
|
||||
border-radius: 0.72rem;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
text-align: start;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 150ms ease, color 150ms ease, transform 150ms ease;
|
||||
@@ -550,7 +572,7 @@ items: unknown[] = []
|
||||
}
|
||||
|
||||
@media (max-width: 639px) {
|
||||
.wrn-dropdown__panel {
|
||||
.wrn-dropdown[data-placement] .wrn-dropdown__panel {
|
||||
position: fixed;
|
||||
right: 0.75rem;
|
||||
bottom: 0.75rem;
|
||||
|
||||
@@ -475,6 +475,7 @@ component Footer {
|
||||
}
|
||||
|
||||
.wrn-footer__copyright {
|
||||
grid-column: 2;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
@@ -502,7 +503,12 @@ component Footer {
|
||||
text-underline-offset: 0.2em;
|
||||
}
|
||||
|
||||
.wrn-footer__copyright-left {
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
.wrn-footer__copyright-right {
|
||||
grid-column: 3;
|
||||
justify-content: flex-end;
|
||||
text-align: right;
|
||||
}
|
||||
@@ -545,10 +551,12 @@ component Footer {
|
||||
}
|
||||
.wrn-footer__copyright-left,
|
||||
.wrn-footer__copyright-right {
|
||||
grid-column: 1;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
.wrn-footer__copyright {
|
||||
grid-column: 1;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ component Sidebar {
|
||||
active: string = ""
|
||||
mobileLabel: string = "Open navigation"
|
||||
drawerTitle: string = "Navigation"
|
||||
mobile: boolean = true
|
||||
drawerPlacement: string = "left"
|
||||
class: string = ""
|
||||
}
|
||||
|
||||
@@ -60,14 +62,16 @@ component Sidebar {
|
||||
return Boolean(entry.value) && entry.value === active
|
||||
}
|
||||
|
||||
client function choose(entry, level) {
|
||||
client function choose(entry, level, sourceEvent) {
|
||||
if (entry.disabled) {
|
||||
sourceEvent.preventDefault()
|
||||
return
|
||||
}
|
||||
output.select({ item: entry, value: entry.value || "", level: level })
|
||||
if (drawerOpen) {
|
||||
drawerOpen = false
|
||||
output.close({ source: "select" })
|
||||
output.toggle({ open: false })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +95,7 @@ component Sidebar {
|
||||
class='wrn-sidebar {class}'
|
||||
data-color='{color}'
|
||||
data-size='{size}'
|
||||
data-mobile='{mobile ? "true" : "false"}'
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
@@ -117,7 +122,8 @@ component Sidebar {
|
||||
data-active='{isActive(entry)}'
|
||||
aria-current='{isActive(entry) ? "page" : "false"}'
|
||||
aria-disabled='{entry.disabled ? "true" : "false"}'
|
||||
@click='choose(entry, 1)'
|
||||
tabindex='{entry.disabled ? "-1" : "0"}'
|
||||
@click='choose(entry, 1, event)'
|
||||
>
|
||||
<span
|
||||
class='wrn-sidebar__icon {entry.icon}'
|
||||
@@ -137,7 +143,9 @@ component Sidebar {
|
||||
data-active='{isActive(child)}'
|
||||
aria-current='{isActive(child) ? "page" : "false"}'
|
||||
aria-disabled='{child.disabled ? "true" : "false"}'
|
||||
@click='choose(child, 2)'
|
||||
data-wrn-roving-item="true"
|
||||
tabindex='{child.disabled ? "-1" : "0"}'
|
||||
@click='choose(child, 2, event)'
|
||||
>
|
||||
<span
|
||||
class='wrn-sidebar__icon {child.icon}'
|
||||
@@ -159,7 +167,10 @@ component Sidebar {
|
||||
href='{leaf.href || "#"}'
|
||||
data-active='{isActive(leaf)}'
|
||||
aria-current='{isActive(leaf) ? "page" : "false"}'
|
||||
@click='choose(leaf, 3)'
|
||||
aria-disabled='{leaf.disabled ? "true" : "false"}'
|
||||
data-wrn-roving-item="true"
|
||||
tabindex='{leaf.disabled ? "-1" : "0"}'
|
||||
@click='choose(leaf, 3, event)'
|
||||
>
|
||||
<span class="wrn-sidebar__label">{leaf.label}</span>
|
||||
</a>
|
||||
@@ -175,15 +186,94 @@ component Sidebar {
|
||||
<slot />
|
||||
</nav>
|
||||
|
||||
{#if mobile}
|
||||
<Drawer
|
||||
open={drawerOpen}
|
||||
placement="left"
|
||||
placement={drawerPlacement}
|
||||
title={drawerTitle}
|
||||
label={label}
|
||||
@close="closeDrawer()"
|
||||
>
|
||||
<nav class="wrn-sidebar__drawer-nav" aria-label='{label}'>
|
||||
<ul class="wrn-sidebar__list" data-wrn-roving="vertical">
|
||||
{#each entryList() as entry}
|
||||
<li class="wrn-sidebar__entry" data-group='{isGroup(entry)}'>
|
||||
<p class="wrn-sidebar__heading" data-show="entry.heading">{entry.heading}</p>
|
||||
|
||||
<a
|
||||
class="wrn-sidebar__link"
|
||||
data-show="!entry.heading"
|
||||
href='{entry.href || "#"}'
|
||||
data-wrn-roving-item="true"
|
||||
data-active='{isActive(entry)}'
|
||||
aria-current='{isActive(entry) ? "page" : "false"}'
|
||||
aria-disabled='{entry.disabled ? "true" : "false"}'
|
||||
tabindex='{entry.disabled ? "-1" : "0"}'
|
||||
@click='choose(entry, 1, event)'
|
||||
>
|
||||
<span
|
||||
class='wrn-sidebar__icon {entry.icon}'
|
||||
data-show="entry.icon"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<span class="wrn-sidebar__label">{entry.label}</span>
|
||||
<span class="wrn-sidebar__badge" data-show="entry.badge">{entry.badge}</span>
|
||||
</a>
|
||||
|
||||
<ul class="wrn-sidebar__sublist" data-show="childrenOf(entry).length > 0">
|
||||
{#each childrenOf(entry) as child}
|
||||
<li class="wrn-sidebar__entry">
|
||||
<a
|
||||
class="wrn-sidebar__link"
|
||||
href='{child.href || "#"}'
|
||||
data-active='{isActive(child)}'
|
||||
aria-current='{isActive(child) ? "page" : "false"}'
|
||||
aria-disabled='{child.disabled ? "true" : "false"}'
|
||||
data-wrn-roving-item="true"
|
||||
tabindex='{child.disabled ? "-1" : "0"}'
|
||||
@click='choose(child, 2, event)'
|
||||
>
|
||||
<span
|
||||
class='wrn-sidebar__icon {child.icon}'
|
||||
data-show="child.icon"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<span class="wrn-sidebar__label">{child.label}</span>
|
||||
<span class="wrn-sidebar__badge" data-show="child.badge">{child.badge}</span>
|
||||
</a>
|
||||
|
||||
<ul
|
||||
class="wrn-sidebar__sublist wrn-sidebar__sublist--level3"
|
||||
data-show="childrenOf(child).length > 0"
|
||||
>
|
||||
{#each childrenOf(child) as leaf}
|
||||
<li class="wrn-sidebar__entry">
|
||||
<a
|
||||
class="wrn-sidebar__link"
|
||||
href='{leaf.href || "#"}'
|
||||
data-active='{isActive(leaf)}'
|
||||
aria-current='{isActive(leaf) ? "page" : "false"}'
|
||||
aria-disabled='{leaf.disabled ? "true" : "false"}'
|
||||
data-wrn-roving-item="true"
|
||||
tabindex='{leaf.disabled ? "-1" : "0"}'
|
||||
@click='choose(leaf, 3, event)'
|
||||
>
|
||||
<span class="wrn-sidebar__label">{leaf.label}</span>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
<slot name="drawer"></slot>
|
||||
</Drawer>
|
||||
{/if}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -231,9 +321,21 @@ component Sidebar {
|
||||
}
|
||||
|
||||
.wrn-sidebar__sublist {
|
||||
margin-left: 0.85rem;
|
||||
padding-left: 0.5rem;
|
||||
border-left: 1px solid var(--wrn-color-border);
|
||||
margin-inline-start: 0.85rem;
|
||||
padding-inline-start: 0.5rem;
|
||||
border-inline-start: 1px solid var(--wrn-color-border);
|
||||
}
|
||||
|
||||
.wrn-sidebar__entry[data-group="true"] > .wrn-sidebar__sublist {
|
||||
margin-inline-start: 0;
|
||||
padding-inline-start: 0;
|
||||
border-inline-start: 0;
|
||||
}
|
||||
|
||||
.wrn-sidebar__icon {
|
||||
width: 1.05rem;
|
||||
height: 1.05rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wrn-sidebar__entry[data-group="true"] + .wrn-sidebar__entry {
|
||||
@@ -286,7 +388,7 @@ component Sidebar {
|
||||
}
|
||||
|
||||
.wrn-sidebar__badge {
|
||||
margin-left: auto;
|
||||
margin-inline-start: auto;
|
||||
padding: 0.05rem 0.4rem;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--sidebar-accent) 16%, transparent);
|
||||
@@ -323,11 +425,11 @@ component Sidebar {
|
||||
* the focus trap and the scroll lock.
|
||||
*/
|
||||
@media (max-width: 767px) {
|
||||
.wrn-sidebar__launcher {
|
||||
.wrn-sidebar[data-mobile="true"] > .wrn-sidebar__launcher {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.wrn-sidebar__rail {
|
||||
.wrn-sidebar[data-mobile="true"] > .wrn-sidebar__rail {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user