Initial commit with files

Still need to fix the imports
This commit is contained in:
Ryan Gossiaux
2021-12-13 17:13:47 -08:00
parent 42aba8a158
commit db9ec57065
56 changed files with 4034 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
<script lang="ts" context="module">
export type PopoverPanelContext = string | null;
</script>
<script lang="ts">
import { Keys } from "./keyboard";
import { State } from "./open-closed";
import {
getFocusableElements,
Focus,
FocusResult,
focusIn,
} from "./focus-management";
import { getContext, setContext, onMount } from "svelte";
import type { Writable } from "svelte/store";
import { PopoverStates, StateDefinition } from "./Popover.svelte";
let panelStore: SvelteStore<HTMLDivElement> = getContext("PopoverPanelRef");
export let focus = false;
let api: Writable<StateDefinition> | undefined = getContext("PopoverApi");
setContext("PopoverPanelContext", $api.panelId);
let openClosedState: Writable<State> | undefined = getContext("OpenClosed");
$: visible =
openClosedState !== undefined
? $openClosedState === State.Open
: $api.popoverState === PopoverStates.Open;
onMount(() => {
if (!focus) return;
if ($api.popoverState !== PopoverStates.Open) return;
if (!$api.panel) return;
let activeElement = document.activeElement as HTMLElement;
if ($api.panel?.contains(activeElement)) return; // Already focused within Dialog
focusIn($api.panel!, Focus.First);
});
function handleWindowKeydown(event: KeyboardEvent) {
if ($api.popoverState !== PopoverStates.Open) return;
if (!$api.panel) return;
if (event.key !== Keys.Tab) return;
if (!document.activeElement) return;
if (!$api.panel?.contains(document.activeElement)) return;
// We will take-over the default tab behaviour so that we have a bit
// control over what is focused next. It will behave exactly the same,
// but it will also "fix" some issues based on whether you are using a
// Portal or not.
event.preventDefault();
let result = focusIn(
$api.panel!,
event.shiftKey ? Focus.Previous : Focus.Next
);
if (result === FocusResult.Underflow) {
return $api.button?.focus();
} else if (result === FocusResult.Overflow) {
if (!$api.button) return;
let elements = getFocusableElements();
let buttonIdx = elements.indexOf($api.button!);
let nextElements = elements
.splice(buttonIdx + 1) // Elements after button
.filter((element) => !$api.panel?.contains(element)); // Ignore items in panel
// Try to focus the next element, however it could fail if we are in a
// Portal that happens to be the very last one in the DOM. In that
// case we would Error (because nothing after the button is
// focusable). Therefore we will try and focus the very first item in
// the document.body.
if (focusIn(nextElements, Focus.First) === FocusResult.Error) {
focusIn(document.body, Focus.First);
}
}
}
function handleFocus() {
if (!focus) return;
if ($api.popoverState !== PopoverStates.Open) return;
if (!$api.panel) return;
if ($api.panel?.contains(document.activeElement as HTMLElement)) return;
$api.closePopover();
}
function handleKeydown(event: KeyboardEvent) {
switch (event.key) {
case Keys.Escape:
if ($api.popoverState !== PopoverStates.Open) return;
if (!$api.panel) return;
if (!$api.panel?.contains(document.activeElement)) return;
event.preventDefault();
event.stopPropagation();
$api.closePopover();
$api.button?.focus();
break;
}
}
</script>
<svelte:window
on:keydown={handleWindowKeydown}
on:focus|capture={handleFocus}
/>
{#if visible}
<div {...$$restProps} on:keydown={handleKeydown} bind:this={$panelStore}>
<slot
open={$api.popoverState === PopoverStates.Open}
close={$api.close}
/>
</div>
{/if}