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,105 @@
<script lang="ts" context="module">
import { writable, Writable } from "svelte/store";
import { getContext, setContext } from "svelte";
export enum DisclosureStates {
Open,
Closed,
}
export interface StateDefinition {
// State
disclosureState: DisclosureStates;
panelStore: Writable<HTMLElement | null>;
panelId: string;
buttonStore: Writable<HTMLButtonElement | null>;
buttonId: string;
// State mutators
toggleDisclosure(): void;
closeDisclosure(): void;
// Exposed functions
close(focusableElement: HTMLElement | HTMLElement | null): void;
}
let DISCLOSURE_CONTEXT_NAME = "DisclosureContext";
export function useDisclosureContext(
component: string
): Writable<StateDefinition | undefined> {
let context: Writable<StateDefinition | undefined> | undefined =
getContext(DISCLOSURE_CONTEXT_NAME);
if (context === undefined) {
throw new Error(
`<${component} /> is missing a parent <Disclosure /> component.`
);
}
return context;
}
</script>
<script lang="ts">
import { useId } from "./use-id";
import { match } from "./match";
import { State } from "./open-closed";
export let defaultOpen = false;
let buttonId = `headlessui-disclosure-button-${useId()}`;
let panelId = `headlessui-disclosure-panel-${useId()}`;
let disclosureState: StateDefinition["disclosureState"] = defaultOpen
? DisclosureStates.Open
: DisclosureStates.Closed;
let panelStore: StateDefinition["panelStore"] = writable(null);
let buttonStore: StateDefinition["buttonStore"] = writable(null);
let api: Writable<StateDefinition | undefined> = writable();
setContext(DISCLOSURE_CONTEXT_NAME, api);
$: api.set({
buttonId,
panelId,
disclosureState,
panelStore,
buttonStore,
toggleDisclosure() {
disclosureState = match(disclosureState, {
[DisclosureStates.Open]: DisclosureStates.Closed,
[DisclosureStates.Closed]: DisclosureStates.Open,
});
},
closeDisclosure() {
if (disclosureState === DisclosureStates.Closed) return;
disclosureState = DisclosureStates.Closed;
},
close(focusableElement: HTMLElement | null) {
$api.closeDisclosure();
let restoreElement = (() => {
if (!focusableElement) return $buttonStore;
if (focusableElement instanceof HTMLElement)
return focusableElement;
return $buttonStore;
})();
restoreElement?.focus();
},
});
let openClosedState: Writable<State> | undefined = writable();
setContext("OpenClosed", openClosedState);
$: $openClosedState = match(disclosureState, {
[DisclosureStates.Open]: State.Open,
[DisclosureStates.Closed]: State.Closed,
});
</script>
<div {...$$restProps}>
<slot
open={disclosureState === DisclosureStates.Open}
close={$api?.close}
/>
</div>

View File

@@ -0,0 +1,103 @@
<script lang="ts">
import {
useDisclosureContext,
DisclosureStates,
} from "./Disclosure.svelte";
import { usePanelContext } from "./DisclosurePanel.svelte";
import { useId } from "./use-id";
import { Keys } from "./keyboard";
export let disabled = false;
const api = useDisclosureContext("DisclosureButton");
const panelContext = usePanelContext();
const id = `headlessui-disclosure-button-${useId()}`;
$: buttonStore = $api?.buttonStore;
$: panelStore = $api?.panelStore;
$: isWithinPanel =
panelContext === null ? false : panelContext === $api?.panelId;
function handleClick() {
if (disabled) return;
if (isWithinPanel) {
$api.toggleDisclosure();
$buttonStore?.focus();
} else {
$api.toggleDisclosure();
}
}
function handleKeyDown(event: KeyboardEvent) {
if (disabled) return;
if (isWithinPanel) {
switch (event.key) {
case Keys.Space:
case Keys.Enter:
event.preventDefault();
event.stopPropagation();
$api.toggleDisclosure();
$buttonStore?.focus();
break;
}
} else {
switch (event.key) {
case Keys.Space:
case Keys.Enter:
event.preventDefault();
event.stopPropagation();
$api.toggleDisclosure();
break;
}
}
}
function handleKeyUp(event: KeyboardEvent) {
switch (event.key) {
case Keys.Space:
// Required for firefox, event.preventDefault() in handleKeyDown for
// the Space key doesn't cancel the handleKeyUp, which in turn
// triggers a *click*.
event.preventDefault();
break;
}
}
$: propsWeControl = isWithinPanel
? {}
: {
id,
"aria-expanded": disabled
? undefined
: $api.disclosureState === DisclosureStates.Open,
"aria-controls": $panelStore ? $api?.panelId : undefined,
disabled: disabled ? true : undefined,
};
</script>
{#if isWithinPanel}
<button
{...{ ...$$restProps, ...propsWeControl }}
on:click={handleClick}
on:keydown={handleKeyDown}
>
<slot
open={$api?.disclosureState === DisclosureStates.Open}
close={$api?.close}
/>
</button>
{:else}
<button
{...{ ...$$restProps, ...propsWeControl }}
bind:this={$buttonStore}
on:click={handleClick}
on:keydown={handleKeyDown}
on:keyup={handleKeyUp}
>
<slot
open={$api?.disclosureState === DisclosureStates.Open}
close={$api?.close}
/>
</button>
{/if}

View File

@@ -0,0 +1,40 @@
<script lang="ts" context="module">
import { getContext, setContext } from "svelte";
let DISCLOSURE_PANEL_CONTEXT_NAME = "DisclosurePanelContext";
export function usePanelContext(): string | undefined {
return getContext(DISCLOSURE_PANEL_CONTEXT_NAME);
}
</script>
<script lang="ts">
import {
useDisclosureContext,
DisclosureStates,
} from "./Disclosure.svelte";
import { Writable } from "svelte/store";
import { State } from "./open-closed";
const api = useDisclosureContext("DisclosureButton");
$: id = $api?.panelId;
let openClosedState: Writable<State> | undefined = getContext("OpenClosed");
setContext(DISCLOSURE_PANEL_CONTEXT_NAME, id);
$: panelStore = $api?.panelStore;
$: visible =
$openClosedState !== null
? $openClosedState === State.Open
: $api?.disclosureState === DisclosureStates.Open;
$: propsWeControl = { id };
</script>
{#if visible}
<div {...{ ...$$restProps, ...propsWeControl }} bind:this={$panelStore}>
<slot
open={$api?.disclosureState === DisclosureStates.Open}
close={$api?.close}
/>
</div>
{/if}