Repository: melt-ui/melt-ui
Pull request #1140 (2024-03-30 11:23:34)

refactor(internal): add `runAfterTick` prop to `effect`
This PR adds a new prop `runAfterTick` to `effect`.

Previously we needed to run `tick()` in many builders to prevent https://github.com/melt-ui/melt-ui/issues/1048 where the DOM hasn't updated yet before our effect ran which caused issues, however after adding a tick to solve that issue, we then ran into a new issue https://github.com/melt-ui/melt-ui/pull/1087 where we were getting memory leaks due to not calling the cleanup functions both before and after the `tick`. So by adding the `runAfterTick` prop, our effect will run after the DOM has updated and it removes the need to make sure we call all the cleanup functions twice in so many builders.

changeset-bot[bot]: ###  🦋  Changeset detected

Latest commit: f9f9ab2d814b0d3f739658bdcf1d631dcd82818e

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name            | Type  |
| --------------- | ----- |
| @melt-ui/svelte | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/anatolzak/melt-ui/new/refactor/effect-run-after-tick?filename=.changeset/great-apricots-taste.md&value=---%0A%22%40melt-ui%2Fsvelte%22%3A%20patch%0A---%0A%0Arefactor(effect)%3A%20add%20%60runAfterTick%60%20prop%0A)



Patch:
diff --git a/.changeset/cyan-toes-jam.md b/.changeset/cyan-toes-jam.md
new file mode 100644
index 00000000..42f07751
--- /dev/null
+++ b/.changeset/cyan-toes-jam.md
@@ -0,0 +1,5 @@
+---
+'@melt-ui/svelte': patch
+---
+
+Refactor effect internals to prevent potential memory leaks
diff --git a/.changeset/dry-dolls-greet.md b/.changeset/dry-dolls-greet.md
deleted file mode 100644
index 89bb0c7c..00000000
--- a/.changeset/dry-dolls-greet.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@melt-ui/svelte': patch
----
-
-fix(link preview): Fixed bug where content shows up at top left of the page after remounting trigger (fixes #1060)
diff --git a/.changeset/good-pants-crash.md b/.changeset/good-pants-crash.md
deleted file mode 100644
index 0be0476a..00000000
--- a/.changeset/good-pants-crash.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@melt-ui/svelte': patch
----
-
-Fixed bug where focus trap would deactivate on an intercepted outside interaction (closes #1134)
diff --git a/.changeset/hungry-steaks-whisper.md b/.changeset/hungry-steaks-whisper.md
deleted file mode 100644
index 590b8a34..00000000
--- a/.changeset/hungry-steaks-whisper.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@melt-ui/svelte': patch
----
-
-Fixed bug where focus trap would deactivate on escape that was intercepted (closes #1132)
diff --git a/.changeset/khaki-files-try.md b/.changeset/khaki-files-try.md
deleted file mode 100644
index 39b1cd66..00000000
--- a/.changeset/khaki-files-try.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@melt-ui/svelte': patch
----
-
-Enhanced type accuracy by implementing as const for builder props, ensuring more precise type inference.
diff --git a/.changeset/nine-wolves-check.md b/.changeset/nine-wolves-check.md
deleted file mode 100644
index 81787443..00000000
--- a/.changeset/nine-wolves-check.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@melt-ui/svelte': patch
----
-
-fix data-state consistency to be based on `$open` and avoid overriding floating-ui styles #1041
diff --git a/.changeset/old-taxis-yell.md b/.changeset/old-taxis-yell.md
deleted file mode 100644
index 4c9353c0..00000000
--- a/.changeset/old-taxis-yell.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@melt-ui/svelte': patch
----
-
-Fix link preview: reset activeTrigger on trigger unmount
diff --git a/.changeset/six-insects-joke.md b/.changeset/six-insects-joke.md
deleted file mode 100644
index 9d9b82c4..00000000
--- a/.changeset/six-insects-joke.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@melt-ui/svelte': patch
----
-
-Fixed a bug where manually closing a dialog or popover would not restore focus to the trigger (closes #1109)
diff --git a/.changeset/tough-squids-rescue.md b/.changeset/tough-squids-rescue.md
deleted file mode 100644
index 4c2b16ea..00000000
--- a/.changeset/tough-squids-rescue.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@melt-ui/svelte': patch
----
-
-Refactor focus trap internals
diff --git a/.changeset/young-peaches-retire.md b/.changeset/young-peaches-retire.md
deleted file mode 100644
index f5b95594..00000000
--- a/.changeset/young-peaches-retire.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@melt-ui/svelte': patch
----
-
-Fixed bug where outside interactions could not be intercepted (closes #917)
diff --git a/.github/ISSUE_TEMPLATE/component.md b/.github/ISSUE_TEMPLATE/component.md
deleted file mode 100644
index 95760412..00000000
--- a/.github/ISSUE_TEMPLATE/component.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-name: Builder
-about: Builder creation request
-title: "✨ Builder:"
-labels: builder
-assignees: ''
-
----
-
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000..b6e75739
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,11 @@
+blank_issues_enabled: false
+contact_links:
+  - name: 🌟 Feature Request
+    url: https://melt-ui.canny.io/
+    about: Request, vote and explore new features
+  - name: 💬 Questions & Discussions
+    url: https://github.com/melt-ui/melt-ui/discussions
+    about: Ask questions and discuss topics with other developers
+  - name: 👾 Chat about Melt UI with the community
+    url: https://melt-ui.com/discord
+    about: Chat with the community and the team behind Melt UI
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md
deleted file mode 100644
index a6024ab8..00000000
--- a/.github/ISSUE_TEMPLATE/documentation.md
+++ /dev/null
@@ -1,10 +0,0 @@
----
-name: Documentation
-about: Additions or changes to the documentation site
-title: "\U0001F4C1Docs:"
-labels: documentation
-assignees: ''
-
----
-
-
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index 4197b5d8..00000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-name: Feature request
-about: Suggest an idea for this project
-title: "\U0001F31FFeature request:"
-labels: enhancement
-assignees: ''
-
----
-
-**Is your feature request related to a problem? Please describe.**
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-
-**Describe the solution you'd like**
-A clear and concise description of what you want to happen.
-
-**Describe alternatives you've considered**
-A clear and concise description of any alternative solutions or features you've considered.
-
-**Additional context**
-Add any other context or screenshots about the feature request here.
diff --git a/.github/ISSUE_TEMPLATE/tech-debt.md b/.github/ISSUE_TEMPLATE/tech-debt.md
deleted file mode 100644
index 2a5d81b5..00000000
--- a/.github/ISSUE_TEMPLATE/tech-debt.md
+++ /dev/null
@@ -1,10 +0,0 @@
----
-name: Tech Debt
-about: Technical debt, proposed architecture changes, etc.
-title: "⚙Tech Debt:"
-labels: tech debt
-assignees: ''
-
----
-
-
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dfa35dc6..ca262bff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,27 @@
 # @melt-ui/svelte
 
+## 0.76.3
+
+### Patch Changes
+
+- fix(link preview): Fixed bug where content shows up at top left of the page after remounting trigger (fixes [#1060](https://github.com/melt-ui/melt-ui/issues/1060)) ([#1053](https://github.com/melt-ui/melt-ui/pull/1053))
+
+- Fixed bug where focus trap would deactivate on an intercepted outside interaction (closes #1134) ([#1135](https://github.com/melt-ui/melt-ui/pull/1135))
+
+- Fixed bug where focus trap would deactivate on escape that was intercepted (closes #1132) ([#1133](https://github.com/melt-ui/melt-ui/pull/1133))
+
+- Enhanced type accuracy by implementing as const for builder props, ensuring more precise type inference. ([#1015](https://github.com/melt-ui/melt-ui/pull/1015))
+
+- fix data-state consistency to be based on `$open` and avoid overriding floating-ui styles #1041 ([#1042](https://github.com/melt-ui/melt-ui/pull/1042))
+
+- Fix link preview: reset activeTrigger on trigger unmount ([#1129](https://github.com/melt-ui/melt-ui/pull/1129))
+
+- Fixed a bug where manually closing a dialog or popover would not restore focus to the trigger (closes #1109) ([#1110](https://github.com/melt-ui/melt-ui/pull/1110))
+
+- Refactor focus trap internals ([#1136](https://github.com/melt-ui/melt-ui/pull/1136))
+
+- Fixed bug where outside interactions could not be intercepted (closes #917) ([#1114](https://github.com/melt-ui/melt-ui/pull/1114))
+
 ## 0.76.2
 
 ### Patch Changes
diff --git a/README.md b/README.md
index 499784a2..5b650d3f 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
 
 [![](https://img.shields.io/github/actions/workflow/status/melt-ui/melt-ui/ci.yaml?style=flat)](https://github.com/melt-ui/melt-ui/actions/workflows/ci.yaml)
 ![](https://img.shields.io/github/license/melt-ui/melt-ui?style=flat)
-[![](https://dcbadge.vercel.app/api/server/2QDjZkYunf?style=flat)](https://discord.gg/2QDjZkYunf)
+[![](https://dcbadge.vercel.app/api/server/2QDjZkYunf?style=flat)](https://melt-ui.com/discord)
 
 ## About
 
@@ -67,10 +67,10 @@ if it wasn't for the work of some amazing people.
 
 Got any questions? Want to talk to the maintainers?
 
-Our [Discord community](https://discord.gg/2QDjZkYunf) is a great place to get in touch with us, and
+Our [Discord community](https://melt-ui.com/discord) is a great place to get in touch with us, and
 we'd love to have you there.
 
-<a href="https://discord.gg/2QDjZkYunf" alt="Melt UI Discord community">
+<a href="https://melt-ui.com/discord" alt="Melt UI Discord community">
 <picture>
   <source media="(prefers-color-scheme: dark)" srcset="https://invidget.switchblade.xyz/2QDjZkYunf">
   <img alt="Melt UI Discord community" src="https://invidget.switchblade.xyz/2QDjZkYunf?theme=light">
diff --git a/package.json b/package.json
index e79589bc..8367b75f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@melt-ui/svelte",
-	"version": "0.76.2",
+	"version": "0.76.3",
 	"license": "MIT",
 	"repository": "github:melt-ui/melt-ui",
 	"author": "Thomas G. Lopes",
diff --git a/src/docs/components/custom-event-dialog.svelte b/src/docs/components/custom-event-dialog.svelte
index e308fa4a..cf33f3e5 100644
--- a/src/docs/components/custom-event-dialog.svelte
+++ b/src/docs/components/custom-event-dialog.svelte
@@ -22,8 +22,8 @@
 {#if $open}
 	<div use:melt={$overlay} class="fixed inset-0 z-40 bg-black/50" />
 	<div
-		class="fixed left-[50%] top-[50%] z-50 max-h-[85vh] w-[90vw] max-w-[960px]
-    translate-x-[-50%] translate-y-[-50%] rounded-md
+		class="fixed left-1/2 top-1/2 z-50 max-h-[85vh] w-[90vw] max-w-[960px]
+    -translate-x-1/2 -translate-y-1/2 rounded-md
     bg-neutral-800 p-4 shadow-lg md:p-8"
 		transition:flyAndScale={{
 			duration: 150,
diff --git a/src/docs/components/tables/custom-events-table.svelte b/src/docs/components/tables/custom-events-table.svelte
index 423a5567..1e372c03 100644
--- a/src/docs/components/tables/custom-events-table.svelte
+++ b/src/docs/components/tables/custom-events-table.svelte
@@ -22,10 +22,8 @@
 				<table class="w-full min-w-[540px] text-left sm:min-w-full">
 					<tbody class="divide-y divide-neutral-700">
 						<tr class="w-full text-neutral-300">
-							<td class="w-[50%] whitespace-nowrap py-2 pl-4 text-sm font-medium sm:pl-0">
-								Event
-							</td>
-							<td class="w-[50%] whitespace-nowrap py-2 text-sm font-medium">Value</td>
+							<td class="w-1/2 whitespace-nowrap py-2 pl-4 text-sm font-medium sm:pl-0"> Event </td>
+							<td class="w-1/2 whitespace-nowrap py-2 text-sm font-medium">Value</td>
 						</tr>
 						{#each data as { name }}
 							<tr>
diff --git a/src/docs/components/tables/data-attr-table.svelte b/src/docs/components/tables/data-attr-table.svelte
index b290f647..807d2b58 100644
--- a/src/docs/components/tables/data-attr-table.svelte
+++ b/src/docs/components/tables/data-attr-table.svelte
@@ -21,10 +21,10 @@
 				<table class="w-full min-w-[540px] text-left sm:min-w-full">
 					<tbody class="divide-y divide-neutral-700">
 						<tr class="w-full text-neutral-300">
-							<td class="w-[50%] whitespace-nowrap py-2 pl-4 text-sm font-medium sm:pl-0">
+							<td class="w-1/2 whitespace-nowrap py-2 pl-4 text-sm font-medium sm:pl-0">
 								Data Attribute
 							</td>
-							<td class="w-[50%] whitespace-nowrap py-2 text-sm font-medium">Value</td>
+							<td class="w-1/2 whitespace-nowrap py-2 text-sm font-medium">Value</td>
 						</tr>
 						{#each data as { name, value }}
 							<tr>
diff --git a/src/docs/components/tables/props-table.svelte b/src/docs/components/tables/props-table.svelte
index 0c085e5e..9a0980e6 100644
--- a/src/docs/components/tables/props-table.svelte
+++ b/src/docs/components/tables/props-table.svelte
@@ -26,11 +26,9 @@
 				<table class="w-full min-w-[540px] text-left sm:min-w-full">
 					<tbody class="divide-y divide-neutral-700">
 						<tr class="w-1/4 text-neutral-300">
-							<td class="w-[25%] whitespace-nowrap py-2 pl-4 text-sm font-medium sm:pl-0">
-								Prop
-							</td>
-							<td class="w-[25%] whitespace-nowrap py-2 text-sm font-medium">Default</td>
-							<td class="w-[50%] whitespace-nowrap py-2 text-sm font-medium">Type / Description</td>
+							<td class="w-1/4 whitespace-nowrap py-2 pl-4 text-sm font-medium sm:pl-0"> Prop </td>
+							<td class="w-1/4 whitespace-nowrap py-2 text-sm font-medium">Default</td>
+							<td class="w-1/2 whitespace-nowrap py-2 text-sm font-medium">Type / Description</td>
 						</tr>
 						{#each data as prop}
 							<tr>
diff --git a/src/docs/components/tables/returned-props-table.svelte b/src/docs/components/tables/returned-props-table.svelte
index d4a991d9..b00a5b49 100644
--- a/src/docs/components/tables/returned-props-table.svelte
+++ b/src/docs/components/tables/returned-props-table.svelte
@@ -27,10 +27,10 @@
 				<table class="w-full min-w-[540px] text-left sm:min-w-full">
 					<tbody class="divide-y divide-neutral-700">
 						<tr class="w-1/4 text-neutral-300">
-							<td class="w-[25%] whitespace-nowrap py-2 pl-4 text-sm font-medium sm:pl-0">
+							<td class="w-1/4 whitespace-nowrap py-2 pl-4 text-sm font-medium sm:pl-0">
 								{tableHeading}
 							</td>
-							<td class="w-[75%] whitespace-nowrap py-2 text-sm font-medium">Description</td>
+							<td class="w-3/4 whitespace-nowrap py-2 text-sm font-medium">Description</td>
 						</tr>
 						{#each data as returnedProp}
 							<tr>
diff --git a/src/docs/components/type-dialog.svelte b/src/docs/components/type-dialog.svelte
index 31c73567..94e6d47e 100644
--- a/src/docs/components/type-dialog.svelte
+++ b/src/docs/components/type-dialog.svelte
@@ -23,8 +23,8 @@
 {#if $open}
 	<div use:melt={$overlay} class="fixed inset-0 z-40 bg-black/50" />
 	<div
-		class="fixed left-[50%] top-[50%] z-50 max-h-[85vh] w-[90vw] max-w-[960px]
-    translate-x-[-50%] translate-y-[-50%] rounded-md
+		class="fixed left-1/2 top-1/2 z-50 max-h-[85vh] w-[90vw] max-w-[960px]
+    -translate-x-1/2 -translate-y-1/2 rounded-md
     bg-neutral-800 p-4 shadow-lg md:p-8"
 		transition:flyAndScale={{
 			duration: 150,
diff --git a/src/docs/config.ts b/src/docs/config.ts
index 0a92f000..c246bc15 100644
--- a/src/docs/config.ts
+++ b/src/docs/config.ts
@@ -10,7 +10,7 @@ export const siteConfig = {
 		'An open-source collection of accessible & customizable component builders for creating user interfaces with Svelte.',
 	links: {
 		github: 'https://github.com/melt-ui/melt-ui',
-		discord: 'https://discord.gg/cee8gHrznd',
+		discord: 'https://melt-ui.com/discord',
 	},
 	keywords: 'meltui,svelte,sveltekit,sveltekit components,svelte headless, radix svelte',
 };
diff --git a/src/docs/content/usage.md b/src/docs/content/usage.md
index ef82cbb8..ab6d3a67 100644
--- a/src/docs/content/usage.md
+++ b/src/docs/content/usage.md
@@ -44,10 +44,10 @@ Melt's props, it's all good.
   import { createCollapsible, melt } from '@melt-ui/svelte';
   import Button from '$components/button.svelte';
 
-	const {
-		elements: { root, content, trigger },
-		states: { open }
-	} = createCollapsible()
+ const {
+  elements: { root, content, trigger },
+  states: { open }
+ } = createCollapsible()
 </script>
 
 <!-- Using Svelte Scoped Styles -->
@@ -167,23 +167,23 @@ generic `Button` component. You may want to pass down a `trigger` element from a
 ```svelte
 <!-- Button.svelte -->
 <script lang="ts">
-	import { type AnyMeltElement, emptyMeltElement } from '@melt-ui/svelte'
+ import { type AnyMeltElement, emptyMeltElement } from '@melt-ui/svelte'
 
-	export let element: AnyMeltElement = emptyMeltElement
+ export let element: AnyMeltElement = emptyMeltElement
 </script>
 
 <button use:melt={$element}>
-	<slot />
+ <slot />
 </button>
 
 <!-- Later, in +page.svelte -->
 <script>
-	import { createCollapsible, melt } from '@melt-ui/svelte'
-	import Button from './Button.svelte'
+ import { createCollapsible, melt } from '@melt-ui/svelte'
+ import Button from './Button.svelte'
 
-	const {
-		elements: { trigger }
-	} = createCollapsible()
+ const {
+  elements: { trigger }
+ } = createCollapsible()
 </script>
 
 <Button element={trigger}>Toggle</Button>
@@ -194,5 +194,5 @@ generic `Button` component. You may want to pass down a `trigger` element from a
 If you run into any bugs, or would like to request a feature, please feel free to
 [open an issue](https://github.com/melt-ui/melt-ui/issues/new) on GitHub.
 
-You can also reach out to us on [Discord](https://discord.gg/cee8gHrznd) if you have any questions
-or just want to chat.
+You can also reach out to us on [Discord](https://melt-ui.com/discord) if you have any questions or
+just want to chat.
diff --git a/src/docs/data/builders/accordion.ts b/src/docs/data/builders/accordion.ts
index 783803d5..2dd68909 100644
--- a/src/docs/data/builders/accordion.ts
+++ b/src/docs/data/builders/accordion.ts
@@ -24,18 +24,6 @@ const builder = builderSchema(BUILDER_NAME, {
 	title: 'createAccordion',
 	props: [
 		...OPTION_PROPS,
-		{
-			name: 'multiple',
-			type: 'boolean',
-			default: 'false',
-			description: 'If `true`, multiple accordion items can be open at the same time.',
-		},
-		{
-			name: 'disabled',
-			type: 'boolean',
-			default: 'false',
-			description: 'Whether or not the accordion is disabled.',
-		},
 		{
 			name: 'defaultValue',
 			type: ['string', 'string[]', 'undefined'],
diff --git a/src/docs/data/builders/menu.ts b/src/docs/data/builders/menu.ts
index a7846582..7df8c0e5 100644
--- a/src/docs/data/builders/menu.ts
+++ b/src/docs/data/builders/menu.ts
@@ -1,14 +1,14 @@
 import { ATTRS, DESCRIPTIONS, KBD, PROPS, SEE } from '$docs/constants.js';
 import type { APISchema, KeyboardSchema } from '$docs/types.js';
 import {
-	toKebabCase,
 	builderSchema,
 	elementSchema,
+	floatingSideAndAlignDataAttrs,
+	floatingSideDataAttr,
 	genElements,
 	genProps,
 	propsToOptions,
-	floatingSideAndAlignDataAttrs,
-	floatingSideDataAttr,
+	toKebabCase,
 } from '$docs/utils/index.js';
 
 import { menuEvents } from '$lib/builders/menu/events.js';
@@ -247,7 +247,6 @@ function getMenuItemSchema(builderName: Menu) {
 	const ITEM = 'item';
 	return elementSchema(ITEM, {
 		description: 'A basic menu item.',
-		props: [PROPS.DISABLED],
 		dataAttributes: [
 			{
 				name: 'data-orientation',
diff --git a/src/docs/data/builders/menubar.ts b/src/docs/data/builders/menubar.ts
index c61f23b4..32b2f87a 100644
--- a/src/docs/data/builders/menubar.ts
+++ b/src/docs/data/builders/menubar.ts
@@ -1,11 +1,11 @@
 import { KBD, PROPS } from '$docs/constants.js';
 import type { KeyboardSchema } from '$docs/types.js';
 import { builderSchema, elementSchema } from '$docs/utils/index.js';
-import type { BuilderData } from './index.js';
-import { builder as dropdownBuilder } from './dropdown-menu.js';
-import { getMenuSchemas, getMenuTriggerDataAttrs } from './menu.js';
 import { menubarEvents } from '$lib/builders/menubar/events.js';
 import { menubarIdParts } from '$lib/index.js';
+import { builder as dropdownBuilder } from './dropdown-menu.js';
+import type { BuilderData } from './index.js';
+import { getMenuSchemas, getMenuTriggerDataAttrs } from './menu.js';
 
 const OPTION_PROPS = [PROPS.CLOSE_ON_ESCAPE, PROPS.LOOP, PROPS.PREVENT_SCROLL];
 const BUILDER_NAME = 'menubar';
diff --git a/src/docs/previews/dialog/alert/tailwind/index.svelte b/src/docs/previews/dialog/alert/tailwind/index.svelte
index 442b16f3..96c97d84 100644
--- a/src/docs/previews/dialog/alert/tailwind/index.svelte
+++ b/src/docs/previews/dialog/alert/tailwind/index.svelte
@@ -33,8 +33,8 @@
 	<div class="force-dark" use:melt={$portalled}>
 		<div use:melt={$overlay} class="fixed inset-0 z-50 bg-black/50" />
 		<div
-			class="fixed left-[50%] top-[50%] z-50 max-h-[85vh] w-[90vw]
-            max-w-[450px] translate-x-[-50%] translate-y-[-50%] rounded-md bg-white
+			class="fixed left-1/2 top-1/2 z-50 max-h-[85vh] w-[90vw]
+            max-w-[450px] -translate-x-1/2 -translate-y-1/2 rounded-md bg-white
             p-6 shadow-lg"
 			transition:flyAndScale={{
 				duration: 150,
diff --git a/src/docs/previews/dialog/controlled/tailwind/index.svelte b/src/docs/previews/dialog/controlled/tailwind/index.svelte
index fa0dfdd7..d9d6f6d8 100644
--- a/src/docs/previews/dialog/controlled/tailwind/index.svelte
+++ b/src/docs/previews/dialog/controlled/tailwind/index.svelte
@@ -35,8 +35,8 @@
 	<div class="force-dark" use:melt={$portalled}>
 		<div use:melt={$overlay} class="fixed inset-0 z-50 bg-black/50" />
 		<div
-			class="fixed left-[50%] top-[50%] z-50 max-h-[85vh] w-[90vw]
-            max-w-[450px] translate-x-[-50%] translate-y-[-50%] rounded-md bg-white
+			class="fixed left-1/2 top-1/2 z-50 max-h-[85vh] w-[90vw]
+            max-w-[450px] -translate-x-1/2 -translate-y-1/2 rounded-md bg-white
             p-6 shadow-lg"
 			transition:flyAndScale={{
 				duration: 150,
diff --git a/src/docs/previews/dialog/main/tailwind/index.svelte b/src/docs/previews/dialog/main/tailwind/index.svelte
index a64e1d1c..8f87572b 100644
--- a/src/docs/previews/dialog/main/tailwind/index.svelte
+++ b/src/docs/previews/dialog/main/tailwind/index.svelte
@@ -37,8 +37,8 @@
 			transition:fade={{ duration: 150 }}
 		/>
 		<div
-			class="fixed left-[50%] top-[50%] z-50 max-h-[85vh] w-[90vw]
-            max-w-[450px] translate-x-[-50%] translate-y-[-50%] rounded-xl bg-white
+			class="fixed left-1/2 top-1/2 z-50 max-h-[85vh] w-[90vw]
+            max-w-[450px] -translate-x-1/2 -translate-y-1/2 rounded-xl bg-white
             p-6 shadow-lg"
 			transition:flyAndScale={{
 				duration: 150,
diff --git a/src/docs/previews/dialog/nested/tailwind/index.svelte b/src/docs/previews/dialog/nested/tailwind/index.svelte
index 852b639a..1102320b 100644
--- a/src/docs/previews/dialog/nested/tailwind/index.svelte
+++ b/src/docs/previews/dialog/nested/tailwind/index.svelte
@@ -42,8 +42,8 @@
 	<div class="force-dark" use:melt={$portalled}>
 		<div use:melt={$overlay} class="fixed inset-0 z-50 bg-black/50" />
 		<div
-			class="fixed left-[50%] top-[50%] z-50 max-h-[85vh] w-[90vw]
-            max-w-[450px] translate-x-[-50%] translate-y-[-50%] rounded-md bg-white
+			class="fixed left-1/2 top-1/2 z-50 max-h-[85vh] w-[90vw]
+            max-w-[450px] -translate-x-1/2 -translate-y-1/2 rounded-md bg-white
             p-6 shadow-lg"
 			transition:flyAndScale={{
 				duration: 150,
@@ -82,8 +82,8 @@
 						class="fixed inset-0 z-50 bg-black/75"
 					/>
 					<div
-						class="fixed left-[50%] top-[50%] z-50 max-h-[85vh] w-[90vw]
-                        max-w-[500px] translate-x-[-50%] translate-y-[-50%] rounded-md bg-white
+						class="fixed left-1/2 top-1/2 z-50 max-h-[85vh] w-[90vw]
+                        max-w-[500px] -translate-x-1/2 -translate-y-1/2 rounded-md bg-white
                         p-6 shadow-2xl"
 						transition:flyAndScale={{
 							duration: 150,
diff --git a/src/lib/builders/context-menu/create.ts b/src/lib/builders/context-menu/create.ts
index 130e0ca5..a1928f83 100644
--- a/src/lib/builders/context-menu/create.ts
+++ b/src/lib/builders/context-menu/create.ts
@@ -20,10 +20,10 @@ import {
 	withGet,
 	type WithGet,
 	portalAttr,
+	type Point,
 } from '$lib/internal/helpers/index.js';
 import type { MeltActionReturn } from '$lib/internal/types.js';
 import type { VirtualElement } from '@floating-ui/core';
-import { tick } from 'svelte';
 import { derived, writable, type Readable } from 'svelte/store';
 import {
 	applyAttrsIfDisabled,
@@ -33,7 +33,6 @@ import {
 	handleMenuNavigation,
 	handleTabNavigation,
 	setMeltMenuAttribute,
-	type Point,
 	type _MenuParts,
 } from '../menu/index.js';
 import type { ContextMenuEvents } from './events.js';
@@ -156,28 +155,26 @@ export function createContextMenu(props?: CreateContextMenuProps) {
 				]) => {
 					unsubPopper();
 					if (!$isVisible || !$rootActiveTrigger) return;
-					tick().then(() => {
-						unsubPopper();
-						setMeltMenuAttribute(node, selector);
-						const $virtual = virtual.get();
-						unsubPopper = usePopper(node, {
-							anchorElement: $virtual ? $virtual : $rootActiveTrigger,
-							open: rootOpen,
-							options: {
-								floating: $positioning,
-								modal: {
-									closeOnInteractOutside: $closeOnOutsideClick,
-									onClose: () => {
-										rootOpen.set(false);
-									},
-									shouldCloseOnInteractOutside: handleClickOutside,
+					setMeltMenuAttribute(node, selector);
+					const $virtual = virtual.get();
+					unsubPopper = usePopper(node, {
+						anchorElement: $virtual ? $virtual : $rootActiveTrigger,
+						open: rootOpen,
+						options: {
+							floating: $positioning,
+							modal: {
+								closeOnInteractOutside: $closeOnOutsideClick,
+								onClose: () => {
+									rootOpen.set(false);
 								},
-								portal: getPortalDestination(node, $portal),
-								escapeKeydown: $closeOnEscape ? undefined : null,
+								shouldCloseOnInteractOutside: handleClickOutside,
 							},
-						}).destroy;
-					});
-				}
+							portal: getPortalDestination(node, $portal),
+							escapeKeydown: $closeOnEscape ? undefined : null,
+						},
+					}).destroy;
+				},
+				{ runAfterTick: true }
 			);
 
 			const unsubEvents = executeCallbacks(
diff --git a/src/lib/builders/link-preview/create.ts b/src/lib/builders/link-preview/create.ts
index ec30050d..6b54a78d 100644
--- a/src/lib/builders/link-preview/create.ts
+++ b/src/lib/builders/link-preview/create.ts
@@ -27,7 +27,6 @@ import { generateIds } from '../../internal/helpers/id.js';
 import { omit } from '../../internal/helpers/object.js';
 import type { LinkPreviewEvents } from './events.js';
 import type { CreateLinkPreviewProps } from './types.js';
-import { tick } from 'svelte';
 
 type LinkPreviewParts = 'trigger' | 'content' | 'arrow';
 const { name } = createElHelpers<LinkPreviewParts>('hover-card');
@@ -187,38 +186,32 @@ export function createLinkPreview(props: CreateLinkPreviewProps = {}) {
 				]) => {
 					unsubPopper();
 					if (!$isVisible || !$activeTrigger) return;
-
-					tick().then(() => {
-						unsubPopper();
-						unsubPopper = usePopper(node, {
-							anchorElement: $activeTrigger,
-							open: open,
-							options: {
-								floating: $positioning,
-								modal: {
-									closeOnInteractOutside: $closeOnOutsideClick,
-									onClose: () => {
-										open.set(false);
-										$activeTrigger.focus();
-									},
-									shouldCloseOnInteractOutside: (e) => {
-										onOutsideClick.get()?.(e);
-										if (e.defaultPrevented) return false;
-										if (
-											isHTMLElement($activeTrigger) &&
-											$activeTrigger.contains(e.target as Element)
-										)
-											return false;
-										return true;
-									},
+					unsubPopper = usePopper(node, {
+						anchorElement: $activeTrigger,
+						open: open,
+						options: {
+							floating: $positioning,
+							modal: {
+								closeOnInteractOutside: $closeOnOutsideClick,
+								onClose: () => {
+									open.set(false);
+									$activeTrigger.focus();
+								},
+								shouldCloseOnInteractOutside: (e) => {
+									onOutsideClick.get()?.(e);
+									if (e.defaultPrevented) return false;
+									if (isHTMLElement($activeTrigger) && $activeTrigger.contains(e.target as Element))
+										return false;
+									return true;
 								},
-								portal: getPortalDestination(node, $portal),
-								focusTrap: null,
-								escapeKeydown: $closeOnEscape ? undefined : null,
 							},
-						}).destroy;
-					});
-				}
+							portal: getPortalDestination(node, $portal),
+							focusTrap: null,
+							escapeKeydown: $closeOnEscape ? undefined : null,
+						},
+					}).destroy;
+				},
+				{ runAfterTick: true }
 			);
 
 			unsub = executeCallbacks(
diff --git a/src/lib/builders/listbox/create.ts b/src/lib/builders/listbox/create.ts
index 1942eb42..2c60eefa 100644
--- a/src/lib/builders/listbox/create.ts
+++ b/src/lib/builders/listbox/create.ts
@@ -474,42 +474,37 @@ export function createListbox<
 					[isVisible, portal, closeOnOutsideClick, positioning, activeTrigger],
 					([$isVisible, $portal, $closeOnOutsideClick, $positioning, $activeTrigger]) => {
 						unsubPopper();
-
 						if (!$isVisible || !$activeTrigger) return;
-
-						tick().then(() => {
-							unsubPopper();
-							const ignoreHandler = createClickOutsideIgnore(ids.trigger.get());
-
-							unsubPopper = usePopper(node, {
-								anchorElement: $activeTrigger,
-								open,
-								options: {
-									floating: $positioning,
-									focusTrap: null,
-									modal: {
-										closeOnInteractOutside: $closeOnOutsideClick,
-										onClose: closeMenu,
-										shouldCloseOnInteractOutside: (e) => {
-											onOutsideClick.get()?.(e);
-											if (e.defaultPrevented) return false;
-											const target = e.target;
-											if (!isElement(target)) return false;
-											if (target === $activeTrigger || $activeTrigger.contains(target)) {
-												return false;
-											}
-											// return opposite of the result of the ignoreHandler
-											if (ignoreHandler(e)) return false;
-											return true;
-										},
+						const ignoreHandler = createClickOutsideIgnore(ids.trigger.get());
+						unsubPopper = usePopper(node, {
+							anchorElement: $activeTrigger,
+							open,
+							options: {
+								floating: $positioning,
+								focusTrap: null,
+								modal: {
+									closeOnInteractOutside: $closeOnOutsideClick,
+									onClose: closeMenu,
+									shouldCloseOnInteractOutside: (e) => {
+										onOutsideClick.get()?.(e);
+										if (e.defaultPrevented) return false;
+										const target = e.target;
+										if (!isElement(target)) return false;
+										if (target === $activeTrigger || $activeTrigger.contains(target)) {
+											return false;
+										}
+										// return opposite of the result of the ignoreHandler
+										if (ignoreHandler(e)) return false;
+										return true;
 									},
-
-									escapeKeydown: null,
-									portal: getPortalDestination(node, $portal),
 								},
-							}).destroy;
-						});
-					}
+
+								escapeKeydown: null,
+								portal: getPortalDestination(node, $portal),
+							},
+						}).destroy;
+					},
+					{ runAfterTick: true }
 				)
 			);
 			return {
diff --git a/src/lib/builders/menu/create.ts b/src/lib/builders/menu/create.ts
index 98b4fc2e..6748b790 100644
--- a/src/lib/builders/menu/create.ts
+++ b/src/lib/builders/menu/create.ts
@@ -32,6 +32,8 @@ import {
 	styleToString,
 	toWritableStores,
 	portalAttr,
+	type Polygon,
+	isPointerInGraceArea,
 } from '$lib/internal/helpers/index.js';
 import type { Defaults, MeltActionReturn, TextDirection } from '$lib/internal/types.js';
 import { tick } from 'svelte';
@@ -189,36 +191,34 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 				]) => {
 					unsubPopper();
 					if (!$isVisible || !$rootActiveTrigger) return;
-					tick().then(() => {
-						unsubPopper();
-						setMeltMenuAttribute(node, selector);
-						unsubPopper = usePopper(node, {
-							anchorElement: $rootActiveTrigger,
-							open: rootOpen,
-							options: {
-								floating: $positioning,
-								modal: {
-									closeOnInteractOutside: $closeOnOutsideClick,
-									shouldCloseOnInteractOutside: (e) => {
-										onOutsideClick.get()?.(e);
-										if (e.defaultPrevented) return false;
-
-										if (
-											isHTMLElement($rootActiveTrigger) &&
-											$rootActiveTrigger.contains(e.target as Element)
-										) {
-											return false;
-										}
-										return true;
-									},
-									onClose: () => rootOpen.set(false),
+					setMeltMenuAttribute(node, selector);
+					unsubPopper = usePopper(node, {
+						anchorElement: $rootActiveTrigger,
+						open: rootOpen,
+						options: {
+							floating: $positioning,
+							modal: {
+								closeOnInteractOutside: $closeOnOutsideClick,
+								shouldCloseOnInteractOutside: (e) => {
+									onOutsideClick.get()?.(e);
+									if (e.defaultPrevented) return false;
+
+									if (
+										isHTMLElement($rootActiveTrigger) &&
+										$rootActiveTrigger.contains(e.target as Element)
+									) {
+										return false;
+									}
+									return true;
 								},
-								portal: getPortalDestination(node, $portal),
-								escapeKeydown: $closeOnEscape ? undefined : null,
+								onClose: () => rootOpen.set(false),
 							},
-						}).destroy;
-					});
-				}
+							portal: getPortalDestination(node, $portal),
+							escapeKeydown: $closeOnEscape ? undefined : null,
+						},
+					}).destroy;
+				},
+				{ runAfterTick: true }
 			);
 
 			const unsubEvents = executeCallbacks(
@@ -782,23 +782,21 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 						if (!$subIsVisible) return;
 						const activeTrigger = subActiveTrigger.get();
 						if (!activeTrigger) return;
-						tick().then(() => {
-							unsubPopper();
-							const parentMenuEl = getParentMenu(activeTrigger);
-
-							unsubPopper = usePopper(node, {
-								anchorElement: activeTrigger,
-								open: subOpen,
-								options: {
-									floating: $positioning,
-									portal: isHTMLElement(parentMenuEl) ? parentMenuEl : undefined,
-									modal: null,
-									focusTrap: null,
-									escapeKeydown: null,
-								},
-							}).destroy;
-						});
-					}
+						const parentMenuEl = getParentMenu(activeTrigger);
+
+						unsubPopper = usePopper(node, {
+							anchorElement: activeTrigger,
+							open: subOpen,
+							options: {
+								floating: $positioning,
+								portal: isHTMLElement(parentMenuEl) ? parentMenuEl : undefined,
+								modal: null,
+								focusTrap: null,
+								escapeKeydown: null,
+							},
+						}).destroy;
+					},
+					{ runAfterTick: true }
 				);
 
 				const unsubEvents = executeCallbacks(
@@ -1562,39 +1560,9 @@ export function handleMenuNavigation(e: KeyboardEvent, loop?: boolean) {
 	handleRovingFocus(candidateNodes[nextIndex]);
 }
 
-export type Point = { x: number; y: number };
-type Polygon = Point[];
 type Side = 'left' | 'right';
 type GraceIntent = { area: Polygon; side: Side };
 
-function isPointerInGraceArea(e: PointerEvent, area?: Polygon) {
-	if (!area) return false;
-	const cursorPos = { x: e.clientX, y: e.clientY };
-	return isPointInPolygon(cursorPos, area);
-}
-
-/**
- * Determine if a point is inside of a polygon.
- *
- * @see https://github.com/substack/point-in-polygon
- */
-function isPointInPolygon(point: Point, polygon: Polygon) {
-	const { x, y } = point;
-	let inside = false;
-	for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
-		const xi = polygon[i].x;
-		const yi = polygon[i].y;
-		const xj = polygon[j].x;
-		const yj = polygon[j].y;
-
-		// prettier-ignore
-		const intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
-		if (intersect) inside = !inside;
-	}
-
-	return inside;
-}
-
 function isFocusWithinSubmenu(submenuId: string) {
 	const activeEl = document.activeElement;
 	if (!isHTMLElement(activeEl)) return false;
diff --git a/src/lib/builders/menubar/create.ts b/src/lib/builders/menubar/create.ts
index 5391d1e4..9bdb472d 100644
--- a/src/lib/builders/menubar/create.ts
+++ b/src/lib/builders/menubar/create.ts
@@ -29,7 +29,6 @@ import {
 } from '$lib/internal/helpers/index.js';
 import { safeOnDestroy, safeOnMount } from '$lib/internal/helpers/lifecycle.js';
 import type { MeltActionReturn } from '$lib/internal/types.js';
-import { tick } from 'svelte';
 import { writable } from 'svelte/store';
 import {
 	applyAttrsIfDisabled,
@@ -174,35 +173,32 @@ export function createMenubar(props?: CreateMenubarProps) {
 					[rootOpen, rootActiveTrigger, positioning, portal, closeOnOutsideClick],
 					([$rootOpen, $rootActiveTrigger, $positioning, $portal, $closeOnOutsideClick]) => {
 						unsubPopper();
-						if (!($rootOpen && $rootActiveTrigger)) return;
-
-						tick().then(() => {
-							unsubPopper();
-							unsubPopper = usePopper(node, {
-								anchorElement: $rootActiveTrigger,
-								open: rootOpen,
-								options: {
-									floating: $positioning,
-									portal: getPortalDestination(node, $portal),
-									modal: {
-										closeOnInteractOutside: $closeOnOutsideClick,
-										shouldCloseOnInteractOutside: (e) => {
-											onOutsideClick.get()?.(e);
-											if (e.defaultPrevented) return false;
-											const target = e.target;
-											const menubarEl = document.getElementById(ids.menubar.get());
-											if (!menubarEl || !isElement(target)) return true;
-											if (menubarEl.contains(target)) return false;
-											return true;
-										},
-										onClose: () => {
-											activeMenu.set('');
-										},
+						if (!$rootOpen || !$rootActiveTrigger) return;
+						unsubPopper = usePopper(node, {
+							anchorElement: $rootActiveTrigger,
+							open: rootOpen,
+							options: {
+								floating: $positioning,
+								portal: getPortalDestination(node, $portal),
+								modal: {
+									closeOnInteractOutside: $closeOnOutsideClick,
+									shouldCloseOnInteractOutside: (e) => {
+										onOutsideClick.get()?.(e);
+										if (e.defaultPrevented) return false;
+										const target = e.target;
+										const menubarEl = document.getElementById(ids.menubar.get());
+										if (!menubarEl || !isElement(target)) return true;
+										if (menubarEl.contains(target)) return false;
+										return true;
+									},
+									onClose: () => {
+										activeMenu.set('');
 									},
 								},
-							}).destroy;
-						});
-					}
+							},
+						}).destroy;
+					},
+					{ runAfterTick: true }
 				);
 
 				const unsubEvents = executeCallbacks(
diff --git a/src/lib/builders/popover/create.ts b/src/lib/builders/popover/create.ts
index 80c22778..20f77842 100644
--- a/src/lib/builders/popover/create.ts
+++ b/src/lib/builders/popover/create.ts
@@ -130,26 +130,23 @@ export function createPopover(args?: CreatePopoverProps) {
 				]) => {
 					unsubPopper();
 					if (!$isVisible || !$activeTrigger) return;
-
-					tick().then(() => {
-						unsubPopper();
-						unsubPopper = usePopper(node, {
-							anchorElement: $activeTrigger,
-							open,
-							options: {
-								floating: $positioning,
-								focusTrap: $disableFocusTrap ? null : undefined,
-								modal: {
-									shouldCloseOnInteractOutside: shouldCloseOnInteractOutside,
-									onClose: handleClose,
-									closeOnInteractOutside: $closeOnOutsideClick,
-								},
-								escapeKeydown: $closeOnEscape ? { handler: handleClose } : null,
-								portal: getPortalDestination(node, $portal),
+					unsubPopper = usePopper(node, {
+						anchorElement: $activeTrigger,
+						open,
+						options: {
+							floating: $positioning,
+							focusTrap: $disableFocusTrap ? null : undefined,
+							modal: {
+								shouldCloseOnInteractOutside: shouldCloseOnInteractOutside,
+								onClose: handleClose,
+								closeOnInteractOutside: $closeOnOutsideClick,
 							},
-						}).destroy;
-					});
-				}
+							escapeKeydown: $closeOnEscape ? { handler: handleClose } : null,
+							portal: getPortalDestination(node, $portal),
+						},
+					}).destroy;
+				},
+				{ runAfterTick: true }
 			);
 
 			return {
diff --git a/src/lib/builders/tooltip/create.ts b/src/lib/builders/tooltip/create.ts
index 61dfddab..d4bcd27d 100644
--- a/src/lib/builders/tooltip/create.ts
+++ b/src/lib/builders/tooltip/create.ts
@@ -15,10 +15,10 @@ import {
 	noop,
 	omit,
 	overridable,
-	pointInPolygon,
 	styleToString,
 	toWritableStores,
 	portalAttr,
+	isPointerInGraceArea,
 } from '$lib/internal/helpers/index.js';
 
 import { useFloating, usePortal } from '$lib/internal/actions/index.js';
@@ -27,7 +27,6 @@ import { derived, writable, type Writable } from 'svelte/store';
 import { generateIds } from '../../internal/helpers/id.js';
 import type { TooltipEvents } from './events.js';
 import type { CreateTooltipProps } from './types.js';
-import { tick } from 'svelte';
 
 const defaults = {
 	positioning: {
@@ -216,15 +215,11 @@ export function createTooltip(props?: CreateTooltipProps) {
 					unsubFloating();
 					const triggerEl = getEl('trigger');
 					if (!$isVisible || !triggerEl) return;
-
-					tick().then(() => {
-						unsubPortal();
-						unsubFloating();
-						const portalDest = getPortalDestination(node, $portal);
-						if (portalDest) unsubPortal = usePortal(node, portalDest).destroy;
-						unsubFloating = useFloating(triggerEl, node, $positioning).destroy;
-					});
-				}
+					const portalDest = getPortalDestination(node, $portal);
+					if (portalDest) unsubPortal = usePortal(node, portalDest).destroy;
+					unsubFloating = useFloating(triggerEl, node, $positioning).destroy;
+				},
+				{ runAfterTick: true }
 			);
 
 			/**
@@ -306,14 +301,7 @@ export function createTooltip(props?: CreateTooltipProps) {
 					? [triggerEl]
 					: [triggerEl, contentEl];
 				const polygon = makeHullFromElements(polygonElements);
-
-				isMouseInTooltipArea = pointInPolygon(
-					{
-						x: e.clientX,
-						y: e.clientY,
-					},
-					polygon
-				);
+				isMouseInTooltipArea = isPointerInGraceArea(e, polygon);
 
 				if ($openReason !== 'pointer') return;
 
diff --git a/src/lib/internal/actions/escape-keydown/action.ts b/src/lib/internal/actions/escape-keydown/action.ts
index 04c6190c..05bd15e3 100644
--- a/src/lib/internal/actions/escape-keydown/action.ts
+++ b/src/lib/internal/actions/escape-keydown/action.ts
@@ -57,25 +57,7 @@ export const useEscapeKeydown = ((node, config = {}) => {
 				}
 
 				e.preventDefault();
-
-				// If an ignore function is passed, check if it returns true
-				if (options.ignore) {
-					if (isFunction(options.ignore)) {
-						if (options.ignore(e)) return;
-					}
-					// If an ignore array is passed, check if any elements in the array match the target
-					else if (Array.isArray(options.ignore)) {
-						if (
-							options.ignore.length > 0 &&
-							options.ignore.some((ignoreEl) => {
-								return ignoreEl && target === ignoreEl;
-							})
-						)
-							return;
-					}
-				}
-
-				// If none of the above conditions are met, call the handler
+				if (shouldIgnoreEvent(e, options.ignore)) return;
 				options.handler?.(e);
 			}),
 			effect(enabled, ($enabled) => {
@@ -98,3 +80,12 @@ export const useEscapeKeydown = ((node, config = {}) => {
 		},
 	};
 }) satisfies Action<HTMLElement, EscapeKeydownConfig>;
+
+const shouldIgnoreEvent = (e: KeyboardEvent, ignore: EscapeKeydownConfig['ignore']): boolean => {
+	if (!ignore) return false;
+	if (isFunction(ignore) && ignore(e)) return true;
+	if (Array.isArray(ignore) && ignore.some((ignoreEl) => e.target === ignoreEl)) {
+		return true;
+	}
+	return false;
+};
diff --git a/src/lib/internal/helpers/id.ts b/src/lib/internal/helpers/id.ts
index f88b4ebf..cb0540a5 100644
--- a/src/lib/internal/helpers/id.ts
+++ b/src/lib/internal/helpers/id.ts
@@ -1,4 +1,5 @@
 import { nanoid } from 'nanoid/non-secure';
+import type { Expand } from '../types.js';
 
 /**
  * A function that generates a random id
diff --git a/src/lib/internal/helpers/index.ts b/src/lib/internal/helpers/index.ts
index b360d341..d4ff1842 100644
--- a/src/lib/internal/helpers/index.ts
+++ b/src/lib/internal/helpers/index.ts
@@ -27,3 +27,4 @@ export * from './ignore.js';
 export * from './focus.js';
 export * from './math.js';
 export * from './withGet.js';
+export * from './pointer.js';
diff --git a/src/lib/internal/helpers/pointer.ts b/src/lib/internal/helpers/pointer.ts
new file mode 100644
index 00000000..d41dd20a
--- /dev/null
+++ b/src/lib/internal/helpers/pointer.ts
@@ -0,0 +1,9 @@
+import { pointInPolygon, type Polygon } from './index.js';
+
+export function isPointerInGraceArea(
+	e: Pick<PointerEvent, 'clientX' | 'clientY'>,
+	area?: Polygon
+): boolean {
+	if (!area) return false;
+	return pointInPolygon({ x: e.clientX, y: e.clientY }, area);
+}
diff --git a/src/lib/internal/helpers/store/effect.ts b/src/lib/internal/helpers/store/effect.ts
index a4666a61..c42a39bf 100644
--- a/src/lib/internal/helpers/store/effect.ts
+++ b/src/lib/internal/helpers/store/effect.ts
@@ -2,6 +2,7 @@ import type { Stores, StoresValues } from 'svelte/store';
 import { derived } from 'svelte/store';
 import { noop } from '../index.js';
 import { safeOnDestroy } from '../lifecycle.js';
+import { tick } from 'svelte';
 
 type EffectOptions = {
 	/**
@@ -9,6 +10,12 @@ type EffectOptions = {
 	 * @default undefined
 	 */
 	skipFirstRun?: boolean;
+
+	/**
+	 * Whether to run effect after a tick
+	 * @default undefined
+	 */
+	runAfterTick?: boolean;
 };
 
 /**
@@ -26,7 +33,7 @@ export function effect<S extends Stores>(
 	fn: (values: StoresValues<S>) => (() => void) | void,
 	opts: EffectOptions = {}
 ): () => void {
-	const { skipFirstRun } = opts;
+	const { skipFirstRun, runAfterTick } = opts;
 	let isFirstRun = true;
 	let cb: (() => void) | void = undefined;
 
@@ -35,9 +42,15 @@ export function effect<S extends Stores>(
 		cb?.();
 		if (isFirstRun && skipFirstRun) {
 			isFirstRun = false;
-		} else {
-			cb = fn(stores);
+			return;
+		}
+		if (runAfterTick) {
+			tick().then(() => {
+				cb = fn(stores);
+			});
+			return;
 		}
+		cb = fn(stores);
 	}).subscribe(noop);
 
 	const unsub = () => {
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 95cc1f38..ea0ab5fd 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -79,9 +79,9 @@
 			<Slider class="absolute left-1/2 top-[25rem] translate-x-[calc(-50%+30px)]" />
 			<Switch class="absolute left-[52.5rem] top-[3.5rem] sm:left-[62.5rem] sm:top-[3.5rem]" />
 			<TagsInput class="absolute left-[70%] top-[10rem]" />
-			<PinInput class="absolute left-[62%] top-[15rem] sm:left-[67.5%] lg:left-[75%]" />
+			<PinInput class="absolute left-[62%] top-[15rem] sm:left-[67.5%] lg:left-3/4" />
 			<Popover
-				class="absolute left-[25%] top-[10rem] hidden md:flex lg:left-[20%] "
+				class="absolute left-1/4 top-[10rem] hidden md:flex lg:left-[20%] "
 				contentClass="hidden md:block"
 			/>
 			<Tabs class="absolute left-[20rem] top-[30rem] sm:left-0 sm:top-[16rem]" />
diff --git a/src/routes/discord/+page.ts b/src/routes/discord/+page.ts
new file mode 100644
index 00000000..72cb6445
--- /dev/null
+++ b/src/routes/discord/+page.ts
@@ -0,0 +1,6 @@
+import { redirect } from '@sveltejs/kit';
+import type { PageLoad } from './$types.js';
+
+export const load: PageLoad = async () => {
+	redirect(303, 'https://discord.gg/cee8gHrznd');
+};
diff --git a/src/stories/Dialog/BaseDialog.svelte b/src/stories/Dialog/BaseDialog.svelte
index 28a97187..08095664 100644
--- a/src/stories/Dialog/BaseDialog.svelte
+++ b/src/stories/Dialog/BaseDialog.svelte
@@ -11,8 +11,8 @@
 <div use:melt={$portalled}>
 	<div use:melt={$overlay} class="fixed inset-0 z-40 bg-black/50" />
 	<div
-		class="fixed left-[50%] top-[50%] z-50 max-h-[85vh] w-[90vw] max-w-[450px]
-				translate-x-[-50%] translate-y-[-50%] rounded-md bg-white p-6
+		class="fixed left-1/2 top-1/2 z-50 max-h-[85vh] w-[90vw] max-w-[450px]
+				-translate-x-1/2 -translate-y-1/2 rounded-md bg-white p-6
 				shadow-lg"
 		use:melt={$content}
 	>


Test patch:
diff --git a/src/lib/internal/helpers/tests/effect.spec.ts b/src/lib/internal/helpers/tests/effect.spec.ts
index 03e07966..1c8ced5a 100644
--- a/src/lib/internal/helpers/tests/effect.spec.ts
+++ b/src/lib/internal/helpers/tests/effect.spec.ts
@@ -1,5 +1,7 @@
 import { writable } from 'svelte/store';
-import { effect } from '../index.js';
+import { effect, noop, sleep } from '../index.js';
+import { vi } from 'vitest';
+import { tick } from 'svelte';
 
 describe('effect', () => {
 	it('Gets called initially', () => {
@@ -47,7 +49,7 @@ describe('effect', () => {
 				calls++;
 			};
 		});
-
+		expect(calls).toBe(0);
 		unsub();
 		expect(calls).toBe(1);
 	});
@@ -68,4 +70,23 @@ describe('effect', () => {
 		expect(calls).toBe(1);
 		unsub();
 	});
+
+	it('Respect `runAfterTick` prop', async () => {
+		vi.mock('svelte', () => ({
+			...vi.importActual('svelte'),
+			tick: vi.fn(() => Promise.resolve()),
+		}));
+		const fn = vi.fn(noop);
+		const unsub = effect([], fn, { runAfterTick: true });
+
+		expect(fn).not.toHaveBeenCalled();
+		expect(tick).toHaveBeenCalled();
+		/** Fn should not be called synchronously */
+		expect(fn).not.toHaveBeenCalled();
+		await sleep(0);
+		expect(fn).toHaveBeenCalled();
+
+		unsub();
+		vi.restoreAllMocks();
+	});
 });
diff --git a/src/tests/combobox/Combobox.spec.ts b/src/tests/combobox/Combobox.spec.ts
index e51d5f07..725c6f21 100644
--- a/src/tests/combobox/Combobox.spec.ts
+++ b/src/tests/combobox/Combobox.spec.ts
@@ -154,7 +154,7 @@ describe('Combobox', () => {
 		expect(getByTestId('menu')).toBeVisible();
 	});
 
-	test.skip('Closes on outside click by default', async () => {
+	test('Closes on outside click by default', async () => {
 		const user = userEvent.setup();
 		const { getByTestId } = render(ComboboxTest);
 		const input = getByTestId('input');
@@ -209,6 +209,22 @@ describe('Combobox', () => {
 		expect(menu).toBeVisible();
 	});
 
+	test('should not prevent focusing on another input on outside interaction of combobox', async () => {
+		const user = userEvent.setup();
+		const { getByTestId } = render(ComboboxTest, { closeOnEscape: false });
+
+		const input = getByTestId('input');
+		const menu = getByTestId('menu');
+		const otherInput = getByTestId('other-input');
+
+		await user.click(input);
+		expect(menu).toBeVisible();
+
+		await user.click(otherInput);
+		expect(otherInput).toHaveFocus();
+		await waitFor(() => expect(menu).not.toBeVisible());
+	});
+
 	test.todo('Selects multiple items when `multiple` is true');
 	test.todo('Manually setting the value updates the label');
 	test.todo('Updating options and setting the value updates the label');
diff --git a/src/tests/combobox/ComboboxTest.svelte b/src/tests/combobox/ComboboxTest.svelte
index 89d910aa..bcacbc13 100644
--- a/src/tests/combobox/ComboboxTest.svelte
+++ b/src/tests/combobox/ComboboxTest.svelte
@@ -67,4 +67,6 @@
 		</div>
 	</ul>
 	<div data-testid="outside-click" />
+
+	<input type="text" data-testid="other-input" aria-label="other input" />
 </main>
diff --git a/src/tests/dialog/Dialog.spec.ts b/src/tests/dialog/Dialog.spec.ts
index 8010c461..c039b0d5 100644
--- a/src/tests/dialog/Dialog.spec.ts
+++ b/src/tests/dialog/Dialog.spec.ts
@@ -233,16 +233,10 @@ describe('Dialog', () => {
 	});
 
 	it("Doesn't deactivate focus trap on outside click provided `closeOnOutsideClick` false", async () => {
-		const { getByTestId, user, overlay, content } = await open({
-			closeOnOutsideClick: false,
-		});
-		const closer = getByTestId('floating-closer');
-
+		const { user, overlay, content } = await open({ closeOnOutsideClick: false });
 		await user.click(overlay);
 		expect(content).toBeVisible();
-		expect(content).toHaveFocus();
-		await user.tab({ shift: true });
-		expect(closer).not.toHaveFocus();
+		await assertActiveFocusTrap(user, content);
 	});
 
 	it('Returns focus to trigger when manually setting `open` state to false', async () => {
diff --git a/src/tests/popover/Popover.spec.ts b/src/tests/popover/Popover.spec.ts
index 19dfc93f..545462f3 100644
--- a/src/tests/popover/Popover.spec.ts
+++ b/src/tests/popover/Popover.spec.ts
@@ -114,16 +114,14 @@ describe('Popover (Default)', () => {
 		await user.keyboard(kbd.ESCAPE);
 		expect(content).toBeVisible();
 		expect(getByTestId('content')).toHaveFocus();
-		await user.tab({ shift: true });
-		expect(getByTestId('trigger')).not.toHaveFocus();
+		await assertActiveFocusTrap(user, content);
 	});
 
 	it("Doesn't deactivate focus trap on outside click provided `closeOnOutsideClick` false", async () => {
 		const { getByTestId, user, content } = await open({ closeOnOutsideClick: false });
 		await user.click(getByTestId('outside'));
 		expect(content).toBeVisible();
-		await user.tab();
-		expect(getByTestId('closeFocus')).not.toHaveFocus();
+		await assertActiveFocusTrap(user, content);
 	});
 
 	it('Returns focus to trigger when manually setting `open` state to false', async () => {