Skip to content

Commit

Permalink
fix: closing and focus issues (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte authored Jan 7, 2024
1 parent edf4a91 commit 4fc104f
Show file tree
Hide file tree
Showing 12 changed files with 134 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .changeset/dirty-dragons-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"vaul-svelte": patch
---

fix: closing and focus restoration issues
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,6 @@
"types": "./dist/index.d.ts",
"type": "module",
"dependencies": {
"bits-ui": "^0.13.0"
"bits-ui": "^0.13.3"
}
}
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions src/lib/internal/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Action } from 'svelte/action';

export type SvelteEvent<T extends Event = Event, U extends EventTarget = EventTarget> = T & {
currentTarget: EventTarget & U;
};
Expand All @@ -11,3 +13,15 @@ export type Expand<T> = T extends object
? { [K in keyof O]: O[K] }
: never
: T;

export type Builder<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Element = any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Param = any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Attributes extends Record<string, any> = Record<string, any>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
> = Record<string, any> & {
action: Action<Element, Param, Attributes>;
};
13 changes: 10 additions & 3 deletions src/lib/internal/vaul.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ export function createVaul(props: CreateVaulProps) {
)
);

// keep a reference to the trigger element so we can refocus when it closes via the keyboard
const triggerRef = writable<HTMLButtonElement | undefined>(undefined);

const { onDrag: onDragProp, onRelease: onReleaseProp, onClose, onOpenChange } = withDefaults;

const {
Expand Down Expand Up @@ -237,7 +240,7 @@ export function createVaul(props: CreateVaulProps) {

if ($drawerRef) {
unsub = handleEscapeKeydown($drawerRef, () => {
closeDrawer();
closeDrawer(true);
});
}

Expand Down Expand Up @@ -549,7 +552,7 @@ export function createVaul(props: CreateVaulProps) {
}
);

function closeDrawer() {
function closeDrawer(withKeyboard: boolean = false) {
if (isClosing) return;
const $drawerRef = get(drawerRef);
if (!$drawerRef) return;
Expand All @@ -572,6 +575,9 @@ export function createVaul(props: CreateVaulProps) {
visible.set(false);
isOpen.set(false);
isClosing = false;
if (withKeyboard) {
get(triggerRef)?.focus();
}
}, 300);

const $snapPoints = get(snapPoints);
Expand Down Expand Up @@ -818,7 +824,8 @@ export function createVaul(props: CreateVaulProps) {
},
refs: {
drawerRef,
overlayRef
overlayRef,
triggerRef
},
options
};
Expand Down
30 changes: 30 additions & 0 deletions src/lib/vaul/components/close.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import type { CloseProps } from './types.js';
import { getCtx } from '../ctx.js';
type $$Props = CloseProps;
export let el: $$Props['el'] = undefined;
const {
methods: { closeDrawer }
} = getCtx();
</script>

<DialogPrimitive.Close
bind:el
on:click={(e) => {
e.preventDefault();
closeDrawer();
}}
on:keydown={(e) => {
if (e.detail.originalEvent.key === 'Enter' || e.detail.originalEvent.key === ' ') {
e.preventDefault();
closeDrawer(true);
}
}}
{...$$restProps}
>
<slot />
</DialogPrimitive.Close>
4 changes: 2 additions & 2 deletions src/lib/vaul/components/content.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import type { OverlayProps } from './types.js';
import type { ContentProps } from './types.js';
import { getCtx } from '../ctx.js';
import Visible from './visible.svelte';
type $$Props = OverlayProps;
type $$Props = ContentProps;
const {
refs: { drawerRef },
Expand Down
6 changes: 3 additions & 3 deletions src/lib/vaul/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ export { default as Root } from './root.svelte';
export { default as Content } from './content.svelte';
export { default as Overlay } from './overlay.svelte';
export { default as NestedRoot } from './nested-root.svelte';
export { default as Close } from './close.svelte';
export { default as Trigger } from './trigger.svelte';

const Trigger = DialogPrimitive.Trigger;
const Portal = DialogPrimitive.Portal;
const Close = DialogPrimitive.Close;
const Title = DialogPrimitive.Title;
const Description = DialogPrimitive.Description;

export { Trigger, Portal, Close, Title, Description };
export { Portal, Title, Description };

export * from './types.js';
4 changes: 4 additions & 0 deletions src/lib/vaul/components/overlay.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import type { OverlayProps } from './types.js';
type $$Props = OverlayProps;
import { getCtx } from '../ctx.js';
const {
Expand Down
29 changes: 29 additions & 0 deletions src/lib/vaul/components/trigger-wrapper.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script lang="ts">
// This is kinda weird but we need to be able to pass the builder down somehow
// if someone uses `asChild` to modify it. Since it's only exposed as a slot prop
// we need something in between the `<DialogPrimitive.Trigger>` and the slot
import type { Builder } from '$lib/internal/types.js';
import { getCtx } from '../ctx.js';
export let meltBuilder: Builder;
const {
refs: { triggerRef }
} = getCtx();
$: ({ action, ...rest } = meltBuilder);
// We're wrapping the melt action so we can set the triggerRef
// even if a user is using `asChild`
const wrappedAction = (node: HTMLElement) => {
triggerRef.set(node as HTMLButtonElement);
return action(node);
};
$: Object.assign(rest, {
action: wrappedAction
});
</script>

<slot newBuilder={rest} />
31 changes: 31 additions & 0 deletions src/lib/vaul/components/trigger.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import TriggerWrapper from './trigger-wrapper.svelte';
import { getCtx } from '../ctx.js';
type $$Props = DialogPrimitive.TriggerProps;
type $$Events = DialogPrimitive.TriggerEvents;
const {
refs: { triggerRef }
} = getCtx();
export let el: HTMLButtonElement | undefined = undefined;
export let asChild: boolean = false;
$: if (el) {
triggerRef.set(el);
}
</script>

{#if asChild}
<DialogPrimitive.Trigger {asChild} let:builder on:click on:keydown bind:el {...$$restProps}>
<TriggerWrapper meltBuilder={builder} let:newBuilder>
<slot builder={newBuilder} />
</TriggerWrapper>
</DialogPrimitive.Trigger>
{:else}
<DialogPrimitive.Trigger let:builder on:click on:keydown bind:el {...$$restProps}>
<slot {builder} />
</DialogPrimitive.Trigger>
{/if}
3 changes: 1 addition & 2 deletions src/routes/hero.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,9 @@
<Drawer.Root shouldScaleBackground>
<Drawer.Trigger asChild let:builder>
<button
class="rounded-full bg-white px-4 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
use:builder.action
{...builder}
type="button"
class="rounded-full bg-white px-4 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
>
Open Drawer
</button>
Expand Down

0 comments on commit 4fc104f

Please sign in to comment.