feat(web): expand/collapse sidebar (#16768)
* feat: expand/collapse sidebar * fix: general PR cleanup - add skip link unit test - remove unused tailwind styles - adjust asset grid spacing - fix event propogation * fix: cleaning up event listeners * fix: purchase modal and button on small screens * fix: explicit tailwind classes * fix: no animation on initial page load * fix: sidebar spacing and reactivity * chore: reverting changes to icons in nav and account info panel * fix: remove left margin from the asset grid after merging in new timeline * chore: extract search-bar changes for a separate PR * fix: add margin to memories
This commit is contained in:
@@ -3,15 +3,16 @@
|
||||
|
||||
interface Props {
|
||||
show: boolean;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
let { show = $bindable() }: Props = $props();
|
||||
let { show = $bindable(), active = $bindable() }: Props = $props();
|
||||
</script>
|
||||
|
||||
<button type="button" onclick={() => (show = true)}>Open</button>
|
||||
|
||||
{#if show}
|
||||
<div use:focusTrap>
|
||||
<div use:focusTrap={{ active }}>
|
||||
<div>
|
||||
<span>text</span>
|
||||
<button data-testid="one" type="button" onclick={() => (show = false)}>Close</button>
|
||||
|
||||
@@ -12,6 +12,12 @@ describe('focusTrap action', () => {
|
||||
expect(document.activeElement).toEqual(screen.getByTestId('one'));
|
||||
});
|
||||
|
||||
it('should not set focus if inactive', async () => {
|
||||
render(FocusTrapTest, { show: true, active: false });
|
||||
await tick();
|
||||
expect(document.activeElement).toBe(document.body);
|
||||
});
|
||||
|
||||
it('supports backward focus wrapping', async () => {
|
||||
render(FocusTrapTest, { show: true });
|
||||
await tick();
|
||||
|
||||
@@ -35,12 +35,12 @@ export function clickOutside(node: HTMLElement, options: Options = {}): ActionRe
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClick, true);
|
||||
document.addEventListener('mousedown', handleClick, false);
|
||||
node.addEventListener('keydown', handleKey, false);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener('mousedown', handleClick, true);
|
||||
document.removeEventListener('mousedown', handleClick, false);
|
||||
node.removeEventListener('keydown', handleKey, false);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,16 +1,34 @@
|
||||
import { shortcuts } from '$lib/actions/shortcut';
|
||||
import { tick } from 'svelte';
|
||||
|
||||
const selectors =
|
||||
'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
||||
interface Options {
|
||||
/**
|
||||
* Set whether the trap is active or not.
|
||||
*/
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
export function focusTrap(container: HTMLElement) {
|
||||
const selectors =
|
||||
'button:not([disabled], .hidden), [href]:not(.hidden), input:not([disabled], .hidden), select:not([disabled], .hidden), textarea:not([disabled], .hidden), [tabindex]:not([tabindex="-1"], .hidden)';
|
||||
|
||||
export function focusTrap(container: HTMLElement, options?: Options) {
|
||||
const triggerElement = document.activeElement;
|
||||
|
||||
const focusableElement = container.querySelector<HTMLElement>(selectors);
|
||||
const withDefaults = (options?: Options) => {
|
||||
return {
|
||||
active: options?.active ?? true,
|
||||
};
|
||||
};
|
||||
|
||||
// Use tick() to ensure focus trap works correctly inside <Portal />
|
||||
void tick().then(() => focusableElement?.focus());
|
||||
const setInitialFocus = () => {
|
||||
const focusableElement = container.querySelector<HTMLElement>(selectors);
|
||||
// Use tick() to ensure focus trap works correctly inside <Portal />
|
||||
void tick().then(() => focusableElement?.focus());
|
||||
};
|
||||
|
||||
if (withDefaults(options).active) {
|
||||
setInitialFocus();
|
||||
}
|
||||
|
||||
const getFocusableElements = (): [HTMLElement | null, HTMLElement | null] => {
|
||||
const focusableElements = container.querySelectorAll<HTMLElement>(selectors);
|
||||
@@ -27,7 +45,7 @@ export function focusTrap(container: HTMLElement) {
|
||||
shortcut: { key: 'Tab' },
|
||||
onShortcut: (event) => {
|
||||
const [firstElement, lastElement] = getFocusableElements();
|
||||
if (document.activeElement === lastElement) {
|
||||
if (document.activeElement === lastElement && withDefaults(options).active) {
|
||||
event.preventDefault();
|
||||
firstElement?.focus();
|
||||
}
|
||||
@@ -39,7 +57,7 @@ export function focusTrap(container: HTMLElement) {
|
||||
shortcut: { key: 'Tab', shift: true },
|
||||
onShortcut: (event) => {
|
||||
const [firstElement, lastElement] = getFocusableElements();
|
||||
if (document.activeElement === firstElement) {
|
||||
if (document.activeElement === firstElement && withDefaults(options).active) {
|
||||
event.preventDefault();
|
||||
lastElement?.focus();
|
||||
}
|
||||
@@ -48,6 +66,12 @@ export function focusTrap(container: HTMLElement) {
|
||||
]);
|
||||
|
||||
return {
|
||||
update(newOptions?: Options) {
|
||||
options = newOptions;
|
||||
if (withDefaults(options).active) {
|
||||
setInitialFocus();
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
destroyShortcuts?.();
|
||||
if (triggerElement instanceof HTMLElement) {
|
||||
|
||||
Reference in New Issue
Block a user