Skip to content

Commit

Permalink
fix: escape keydown
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte committed Dec 31, 2023
1 parent 5db5506 commit c55703e
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 12 deletions.
99 changes: 99 additions & 0 deletions src/lib/internal/escape-keydown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { readable } from 'svelte/store';
import { addEventListener } from './helpers/event.js';
import { chain } from './prevent-scroll.js';
import { isFunction, isHTMLElement, noop } from '$lib/internal/helpers/index.js';

/**
* Creates a readable store that tracks the latest Escape Keydown that occurred on the document.
*
* @returns A function to unsubscribe from the event listener and stop tracking keydown events.
*/
const documentEscapeKeyStore = readable<KeyboardEvent | undefined>(
undefined,
(set): (() => void) => {
/**
* Event handler for keydown events on the document.
* Updates the store's value with the latest Escape Keydown event and then resets it to undefined.
*/
function keydown(event: KeyboardEvent | undefined) {
if (event && event.key === 'Escape') {
set(event);
}

// New subscriptions will not trigger immediately
set(undefined);
}

// Adds a keydown event listener to the document, calling the keydown function when triggered.
const unsubscribe = addEventListener(document, 'keydown', keydown, {
passive: false
});

// Returns a function to unsubscribe from the event listener and stop tracking keydown events.
return unsubscribe;
}
);

export const useEscapeKeydown = (node: HTMLElement, config: EscapeKeydownConfig = {}) => {
let unsub = noop;
function update(config: EscapeKeydownConfig = {}) {
unsub();

unsub = chain(
// Handle escape keydowns
documentEscapeKeyStore.subscribe((e) => {
if (!e) return;
const target = e.target;

if (!isHTMLElement(target) || target.closest('[data-escapee]') !== node) {
return;
}

e.preventDefault();

// If an ignore function is passed, check if it returns true
if (config.ignore) {
if (isFunction(config.ignore)) {
if (config.ignore(e)) return;
}
// If an ignore array is passed, check if any elements in the array match the target
else if (Array.isArray(config.ignore)) {
if (
config.ignore.length > 0 &&
config.ignore.some((ignoreEl) => {
return ignoreEl && target === ignoreEl;
})
)
return;
}
}

// If none of the above conditions are met, call the handler
config.handler?.(e);
}),
(node.dataset.escapee = '')
);
}

update(config);

return {
update,
destroy() {
node.removeAttribute('data-escapee');
unsub();
}
};
};

export type EscapeKeydownConfig = {
/**
* Callback when user presses the escape key element.
*/
handler?: (evt: KeyboardEvent) => void;

/**
* A predicate function or a list of elements that should not trigger the event.
*/
ignore?: ((e: KeyboardEvent) => boolean) | Element[];
};
3 changes: 3 additions & 0 deletions src/lib/internal/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export * from './store.js';
export * from './object.js';
export * from './style.js';
export * from './noop.js';
export * from './event.js';
export * from './is.js';
10 changes: 10 additions & 0 deletions src/lib/internal/helpers/is.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function isHTMLElement(el: unknown): el is HTMLElement {
return el instanceof HTMLElement;
}

export const isBrowser = typeof document !== 'undefined';

// eslint-disable-next-line @typescript-eslint/ban-types
export function isFunction(v: unknown): v is Function {
return typeof v === 'function';
}
3 changes: 3 additions & 0 deletions src/lib/internal/helpers/noop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function noop() {
// do nothing;
}
2 changes: 1 addition & 1 deletion src/lib/internal/prevent-scroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { addEventListener } from './helpers/event.js';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function chain(...callbacks: any[]): (...args: any[]) => void {
export function chain(...callbacks: any[]): (...args: any[]) => void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (...args: any[]) => {
for (const callback of callbacks) {
Expand Down
36 changes: 31 additions & 5 deletions src/lib/internal/vaul.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { usePositionFixed } from './position-fixed.js';
import { onMount } from 'svelte';
import { TRANSITIONS, VELOCITY_THRESHOLD } from './constants.js';
import { addEventListener } from './helpers/event.js';
import { noop } from './helpers/noop.js';
import { useEscapeKeydown } from './escape-keydown.js';

const CLOSE_THRESHOLD = 0.25;

Expand Down Expand Up @@ -140,6 +142,7 @@ export function createVaul(props: CreateVaulProps) {
const drawerRef = writable<HTMLDivElement | undefined>(undefined);
const drawerHeightRef = writable(get(drawerRef)?.getBoundingClientRect().height || 0);
const initialDrawerHeight = writable(0);
let isClosing = false;

function getDefaultActiveSnapPoint() {
if (withDefaults.defaultActiveSnapPoint) {
Expand Down Expand Up @@ -221,6 +224,29 @@ export function createVaul(props: CreateVaulProps) {

const { restorePositionSetting } = usePositionFixed({ isOpen, modal, nested, hasBeenOpened });

effect([drawerRef], ([$drawerRef]) => {
let unsub = noop;

if ($drawerRef) {
const { destroy } = useEscapeKeydown($drawerRef, {
handler: () => {
closeDrawer();
}
});
unsub = destroy;
}

return () => {
unsub();
};
});

function openDrawer() {
if (isClosing) return;
hasBeenOpened.set(true);
isOpen.set(true);
}

function getScale() {
return (window.innerWidth - WINDOW_TOP_OFFSET) / window.innerWidth;
}
Expand Down Expand Up @@ -527,6 +553,7 @@ export function createVaul(props: CreateVaulProps) {
);

function closeDrawer() {
if (isClosing) return;
const $drawerRef = get(drawerRef);
if (!$drawerRef) return;
const $snapPoints = get(snapPoints);
Expand All @@ -544,9 +571,11 @@ export function createVaul(props: CreateVaulProps) {

scaleBackground(false);

isClosing = true;
setTimeout(() => {
visible.set(false);
isOpen.set(false);
isClosing = false;
}, 300);

setTimeout(() => {
Expand Down Expand Up @@ -793,7 +822,8 @@ export function createVaul(props: CreateVaulProps) {
onNestedDrag,
onNestedOpenChange,
onNestedRelease,
restorePositionSetting
restorePositionSetting,
openDrawer
},
refs: {
drawerRef,
Expand All @@ -806,7 +836,3 @@ export function createVaul(props: CreateVaulProps) {
export function dampenValue(v: number) {
return 8 * (Math.log(v + 1) - 2);
}

function noop() {
// do nothing;
}
11 changes: 5 additions & 6 deletions src/lib/vaul/components/root.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
export let shouldScaleBackground: $$Props['shouldScaleBackground'] = false;
const {
states: { isOpen, hasBeenOpened, keyboardIsOpen },
methods: { closeDrawer },
states: { keyboardIsOpen },
methods: { closeDrawer, openDrawer },
refs: { drawerRef },
options: { dismissible },
updateOption
Expand Down Expand Up @@ -54,17 +54,16 @@
</script>

<DialogPrimitive.Root
closeOnEscape={false}
bind:open
preventScroll={false}
openFocus={openFocus ? openFocus : drawerEl}
onOpenChange={(o) => {
onOpenChange?.(o);

if (!o) {
closeDrawer();
} else {
hasBeenOpened.set(true);
isOpen.set(true);
} else if (o) {
openDrawer();
}
}}
onOutsideClick={(e) => {
Expand Down

0 comments on commit c55703e

Please sign in to comment.