Repository: melt-ui/melt-ui
Pull request #851 (2023-12-23 04:45:31)

chore: upgrade nanoid to v5
## Changes
- upgrade `nanoid` from 4.0.2 to 5.0.4

## Motivation
Keep up to date with dependencies that are consumed by users of the lib.

See breaking changes listed in https://github.com/ai/nanoid/blob/main/CHANGELOG.md#50.


Patch:
diff --git a/.changeset/loud-readers-count.md b/.changeset/loud-readers-count.md
new file mode 100644
index 00000000..6f17b0eb
--- /dev/null
+++ b/.changeset/loud-readers-count.md
@@ -0,0 +1,5 @@
+---
+"@melt-ui/svelte": patch
+---
+
+chore: upgrade nanoid to v5
diff --git a/.changeset/tricky-cougars-burn.md b/.changeset/tricky-cougars-burn.md
new file mode 100644
index 00000000..f8561883
--- /dev/null
+++ b/.changeset/tricky-cougars-burn.md
@@ -0,0 +1,5 @@
+---
+"@melt-ui/svelte": patch
+---
+
+Progress: allow `null` value for indeterminate state
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index acaf9237..f764cafc 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -37,4 +37,4 @@ If applicable, add screenshots to help explain your problem.
 **Additional context**
 Add any other context about the problem here.
 
-To reproduce using Stackblitz, use this [template](https://stackblitz.com/edit/melt-ui-playground) and modify the `src/routes/+page.svelte` file.
+To reproduce using SvelteLab, use the [Melt UI template](https://www.sveltelab.dev/?t=melt_ui) and modify the `src/routes/+page.svelte` file.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 81dd081a..2d968e20 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,29 @@
 # @melt-ui/svelte
 
+## 0.67.0
+
+### Minor Changes
+
+- feat: expose `onOutsideClick` handler ([#863](https://github.com/melt-ui/melt-ui/pull/863))
+
+## 0.66.4
+
+### Patch Changes
+
+- Toast: return `aria-live` type as `'assertive' | 'polite'` ([#859](https://github.com/melt-ui/melt-ui/pull/859))
+
+- Tags Input: Fix addOnPaste (closes #830) ([#847](https://github.com/melt-ui/melt-ui/pull/847))
+
+- escape-keydown: remove `capture` listener (fixes [#842](https://github.com/melt-ui/melt-ui/issues/842)) ([#845](https://github.com/melt-ui/melt-ui/pull/845))
+
+- Combobox: change default Value props type ([`835c285a`](https://github.com/melt-ui/melt-ui/commit/835c285a774cc00455980693a5978f0969ef05ee))
+
+## 0.66.3
+
+### Patch Changes
+
+- Menu: fix default active subtrigger ([`84270042`](https://github.com/melt-ui/melt-ui/commit/84270042ee257a9dff0efaa97eacd3ec9ce04a6c))
+
 ## 0.66.2
 
 ### Patch Changes
diff --git a/package.json b/package.json
index a28f035c..162ae406 100644
--- a/package.json
+++ b/package.json
@@ -1,13 +1,13 @@
 {
 	"name": "@melt-ui/svelte",
-	"version": "0.66.2",
+	"version": "0.67.0",
 	"license": "MIT",
 	"repository": "github:melt-ui/melt-ui",
 	"author": "Thomas G. Lopes",
 	"scripts": {
 		"dev": "vite dev",
 		"build": "vite build && npm run package",
-		"deploy": "export PREDEPLOY=0 && vite build",
+		"deploy": "vite build && pnpm run pagefind",
 		"preview": "vite preview",
 		"package": "svelte-kit sync && svelte-package && publint",
 		"prepublishOnly": "npm run package",
@@ -25,8 +25,7 @@
 		"pw": "pnpm exec playwright test",
 		"pw:ui": "pnpm exec playwright test --ui",
 		"changeset": "changeset",
-		"pagefind": "node ./scripts/pagefind.js",
-		"pre-deploy": "export PREDEPLOY=1 && vite build && pnpm run pagefind"
+		"pagefind": "node ./scripts/pagefind.js"
 	},
 	"exports": {
 		".": {
@@ -68,7 +67,7 @@
 		"@internationalized/date": "^3.5.0",
 		"dequal": "^2.0.3",
 		"focus-trap": "^7.5.2",
-		"nanoid": "^4.0.2"
+		"nanoid": "^5.0.4"
 	},
 	"devDependencies": {
 		"@changesets/cli": "^2.26.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d492c258..29bba330 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -21,8 +21,8 @@ dependencies:
     specifier: ^7.5.2
     version: 7.5.2
   nanoid:
-    specifier: ^4.0.2
-    version: 4.0.2
+    specifier: ^5.0.4
+    version: 5.0.4
 
 devDependencies:
   '@changesets/cli':
@@ -45,7 +45,7 @@ devDependencies:
     version: 1.1.57
   '@melt-ui/pp':
     specifier: 0.1.0
-    version: 0.1.0(@melt-ui/svelte@0.63.1)(svelte@4.0.0)
+    version: 0.1.0(@melt-ui/svelte@0.66.2)(svelte@4.0.0)
   '@playwright/test':
     specifier: ^1.39.0
     version: 1.39.0
@@ -2389,6 +2389,12 @@ packages:
       '@floating-ui/utils': 0.1.6
     dev: true
 
+  /@floating-ui/core@1.5.2:
+    resolution: {integrity: sha512-Ii3MrfY/GAIN3OhXNzpCKaLxHQfJF9qvwq/kEJYdqDxeIHa01K8sldugal6TmeeXl+WMvhv9cnVzUTaFFJF09A==}
+    dependencies:
+      '@floating-ui/utils': 0.1.6
+    dev: true
+
   /@floating-ui/dom@1.4.5:
     resolution: {integrity: sha512-96KnRWkRnuBSSFbj0sFGwwOUd8EkiecINVl0O9wiZlZ64EkpyAOG3Xc2vKKNJmru0Z7RqWNymA+6b8OZqjgyyw==}
     dependencies:
@@ -2496,6 +2502,13 @@ packages:
     resolution: {integrity: sha512-nw0Q+oRkizBWMioseI8+2TeUPEyopJVz5YxoYVzR0W1v+2YytiYah7s/ot35F149q/xAg4F1gT/6eTd+tsUpFQ==}
     dependencies:
       '@swc/helpers': 0.5.3
+    dev: false
+
+  /@internationalized/date@3.5.1:
+    resolution: {integrity: sha512-LUQIfwU9e+Fmutc/DpRTGXSdgYZLBegi4wygCWDSVmUdLTaMHsQyASDiJtREwanwKuQLq0hY76fCJ9J/9I2xOQ==}
+    dependencies:
+      '@swc/helpers': 0.5.3
+    dev: true
 
   /@isaacs/cliui@8.0.2:
     resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
@@ -2690,25 +2703,25 @@ packages:
       react: 18.2.0
     dev: true
 
-  /@melt-ui/pp@0.1.0(@melt-ui/svelte@0.63.1)(svelte@4.0.0):
+  /@melt-ui/pp@0.1.0(@melt-ui/svelte@0.66.2)(svelte@4.0.0):
     resolution: {integrity: sha512-GbzqhLDjfxh3BwC0It7kVUHvx05f+IdBad+mXWMO76xgfdqBkAO/pS6Q+tAEntKglQvxXugV3gHAyEJgSI0AlA==}
     engines: {pnpm: '>=8.6.3'}
     peerDependencies:
       '@melt-ui/svelte': '>= 0.29.0'
       svelte: ^3.55.0 || ^4.0.0
     dependencies:
-      '@melt-ui/svelte': 0.63.1(svelte@4.0.0)
+      '@melt-ui/svelte': 0.66.2(svelte@4.0.0)
       svelte: 4.0.0
     dev: true
 
-  /@melt-ui/svelte@0.63.1(svelte@4.0.0):
-    resolution: {integrity: sha512-WzuI4uePPsHMlk1aq95UAgb32gXYRjBSi8DmXApWuM9CpJgcvUt2YcQ5+BPSOyrDOWVvPJd9gL7QA0NPDSCtNw==}
+  /@melt-ui/svelte@0.66.2(svelte@4.0.0):
+    resolution: {integrity: sha512-ufIhgOYP11A/G3AvW+2Qw74UGudMBJQ2wK+sETpU51VkC63/5D2sctKgXzGl0OUEJBlPHku6LRSry9rIeAVkNw==}
     peerDependencies:
       svelte: '>=3 <5'
     dependencies:
-      '@floating-ui/core': 1.5.0
+      '@floating-ui/core': 1.5.2
       '@floating-ui/dom': 1.5.3
-      '@internationalized/date': 3.5.0
+      '@internationalized/date': 3.5.1
       dequal: 2.0.3
       focus-trap: 7.5.4
       nanoid: 4.0.2
@@ -4783,12 +4796,12 @@ packages:
   /@types/eslint-scope@3.7.7:
     resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
     dependencies:
-      '@types/eslint': 8.44.8
+      '@types/eslint': 8.56.0
       '@types/estree': 1.0.5
     dev: true
 
-  /@types/eslint@8.44.8:
-    resolution: {integrity: sha512-4K8GavROwhrYl2QXDXm0Rv9epkA8GBFu0EI+XrrnnuCl7u8CWBRusX7fXJfanhZTDWSAL24gDI/UqXyUM0Injw==}
+  /@types/eslint@8.56.0:
+    resolution: {integrity: sha512-FlsN0p4FhuYRjIxpbdXovvHQhtlG05O1GG/RNWvdAxTboR438IOTwmrY/vLA+Xfgg06BTkP045M3vpFwTMv1dg==}
     dependencies:
       '@types/estree': 1.0.5
       '@types/json-schema': 7.0.15
@@ -4937,6 +4950,12 @@ packages:
       undici-types: 5.26.5
     dev: true
 
+  /@types/node@20.10.5:
+    resolution: {integrity: sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==}
+    dependencies:
+      undici-types: 5.26.5
+    dev: true
+
   /@types/node@20.3.1:
     resolution: {integrity: sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==}
     dev: true
@@ -6544,19 +6563,6 @@ packages:
       ms: 2.0.0
     dev: true
 
-  /debug@3.2.7:
-    resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
-    requiresBuild: true
-    peerDependencies:
-      supports-color: '*'
-    peerDependenciesMeta:
-      supports-color:
-        optional: true
-    dependencies:
-      ms: 2.1.3
-    dev: true
-    optional: true
-
   /debug@4.3.4:
     resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
     engines: {node: '>=6.0'}
@@ -8893,7 +8899,7 @@ packages:
     resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
     engines: {node: '>= 10.13.0'}
     dependencies:
-      '@types/node': 20.10.1
+      '@types/node': 20.10.5
       merge-stream: 2.0.0
       supports-color: 8.1.1
     dev: true
@@ -9110,10 +9116,8 @@ packages:
       image-size: 0.5.5
       make-dir: 2.1.0
       mime: 1.6.0
-      needle: 3.2.0
+      needle: 3.3.1
       source-map: 0.6.1
-    transitivePeerDependencies:
-      - supports-color
     dev: true
 
   /leven@3.1.0:
@@ -9966,8 +9970,8 @@ packages:
       thenify-all: 1.6.0
     dev: true
 
-  /nanoid@3.3.6:
-    resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
+  /nanoid@3.3.7:
+    resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
     hasBin: true
     dev: true
@@ -9976,6 +9980,13 @@ packages:
     resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==}
     engines: {node: ^14 || ^16 || >=18}
     hasBin: true
+    dev: true
+
+  /nanoid@5.0.4:
+    resolution: {integrity: sha512-vAjmBf13gsmhXSgBrtIclinISzFFy22WwCYoyilZlsrRXNIHSwgFQ1bEdjRwMT3aoadeIF6HMuDRlOxzfXV8ig==}
+    engines: {node: ^18 || >=20}
+    hasBin: true
+    dev: false
 
   /natural-compare-lite@1.4.0:
     resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
@@ -9985,17 +9996,14 @@ packages:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
     dev: true
 
-  /needle@3.2.0:
-    resolution: {integrity: sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==}
+  /needle@3.3.1:
+    resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==}
     engines: {node: '>= 4.4.x'}
     hasBin: true
     requiresBuild: true
     dependencies:
-      debug: 3.2.7
       iconv-lite: 0.6.3
       sax: 1.3.0
-    transitivePeerDependencies:
-      - supports-color
     dev: true
     optional: true
 
@@ -10709,7 +10717,7 @@ packages:
     resolution: {integrity: sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==}
     engines: {node: ^10 || ^12 || >=14}
     dependencies:
-      nanoid: 3.3.6
+      nanoid: 3.3.7
       picocolors: 1.0.0
       source-map-js: 1.0.2
     dev: true
@@ -10718,7 +10726,7 @@ packages:
     resolution: {integrity: sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==}
     engines: {node: ^10 || ^12 || >=14}
     dependencies:
-      nanoid: 3.3.6
+      nanoid: 3.3.7
       picocolors: 1.0.0
       source-map-js: 1.0.2
     dev: true
@@ -12446,12 +12454,12 @@ packages:
       jest-worker: 27.5.1
       schema-utils: 3.3.0
       serialize-javascript: 6.0.1
-      terser: 5.24.0
+      terser: 5.26.0
       webpack: 5.89.0(esbuild@0.18.17)
     dev: true
 
-  /terser@5.24.0:
-    resolution: {integrity: sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==}
+  /terser@5.26.0:
+    resolution: {integrity: sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==}
     engines: {node: '>=10'}
     hasBin: true
     dependencies:
diff --git a/scripts/pagefind.js b/scripts/pagefind.js
index 196dcdd5..f8671979 100644
--- a/scripts/pagefind.js
+++ b/scripts/pagefind.js
@@ -11,15 +11,9 @@ await index.addDirectory({
 });
 
 await index.writeFiles({
-	outputPath: 'src/pagefind',
+	outputPath: 'static/pagefind',
 });
 
 await index.writeFiles({
-	outputPath: 'static/pagefind',
+	outputPath: 'build/pagefind',
 });
-
-// Write ts-nocheck to the top of the file
-import fs from 'fs';
-const file = 'src/pagefind/pagefind.js';
-const data = fs.readFileSync(file);
-fs.writeFileSync(file, '// @ts-nocheck\n' + data);
diff --git a/src/app.html b/src/app.html
index de08f804..1fc98f2c 100644
--- a/src/app.html
+++ b/src/app.html
@@ -30,7 +30,35 @@
 			content="An open-source Svelte component library for building high-quality, accessible design systems and web apps."
 		/>
 		<meta name="twitter:image" content="https://www.melt-ui.com/banner.png" />
-		<script src="/pagefind/pagefind.js" type="module"></script>
+
+		<link
+			rel="preload"
+			as="font"
+			href="https://fonts-c75.pages.dev/fonts/GTMaru-Bold.woff"
+			type="font/woff"
+			crossorigin="anonymous"
+		/>
+		<link
+			rel="preload"
+			as="font"
+			href="https://fonts-c75.pages.dev/fonts/GTMaru-Bold.woff2"
+			type="font/woff2"
+			crossorigin="anonymous"
+		/>
+		<link
+			rel="preload"
+			as="font"
+			href="https://fonts-c75.pages.dev/fonts/GTMaru-RegularOblique.woff"
+			type="font/woff"
+			crossorigin="anonymous"
+		/>
+		<link
+			rel="preload"
+			as="font"
+			href="https://fonts-c75.pages.dev/fonts/GTMaru-RegularOblique.woff2"
+			type="font/woff2"
+			crossorigin="anonymous"
+		/>
 
 		%sveltekit.head%
 	</head>
diff --git a/src/docs/data/builders/accordion.ts b/src/docs/data/builders/accordion.ts
index a0e5d406..783803d5 100644
--- a/src/docs/data/builders/accordion.ts
+++ b/src/docs/data/builders/accordion.ts
@@ -9,11 +9,10 @@ import type { BuilderData } from './index.js';
  */
 const OPTION_PROPS = [
 	{
-		name: 'type',
-		type: ["'single'", "'multiple'"],
-		default: "'single'",
-		description:
-			'The type of accordion to create. A `"single"` accordion only allows one item to be open at a time. A `"multiple"` accordion allows multiple items to be open at a time.',
+		name: 'multiple',
+		type: 'boolean',
+		default: 'false',
+		description: 'If `true`, multiple accordion items can be open at the same time.',
 	},
 	PROPS.DISABLED,
 	PROPS.FORCE_VISIBLE,
diff --git a/src/docs/previews/tags-input/main/tailwind/index.svelte b/src/docs/previews/tags-input/main/tailwind/index.svelte
index 440df9a5..38e2d29e 100644
--- a/src/docs/previews/tags-input/main/tailwind/index.svelte
+++ b/src/docs/previews/tags-input/main/tailwind/index.svelte
@@ -11,6 +11,7 @@
 		add(tag) {
 			return { id: tag, value: tag };
 		},
+		addOnPaste: true,
 	});
 </script>
 
diff --git a/src/fonts.css b/src/fonts.css
index 4299e291..fbc58c6e 100644
--- a/src/fonts.css
+++ b/src/fonts.css
@@ -1,7 +1,7 @@
 @font-face {
 	font-family: 'GT Maru Mono';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-Black.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-Black.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaruMono-Black.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaruMono-Black.woff') format('woff');
 	font-weight: 900;
 	font-style: normal;
 	font-display: swap;
@@ -9,8 +9,8 @@
 
 @font-face {
 	font-family: 'GT Maru';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-Regular.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-Regular.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaru-Regular.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaru-Regular.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	font-display: swap;
@@ -18,8 +18,8 @@
 
 @font-face {
 	font-family: 'GT Maru';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-Black.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-Black.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaru-Black.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaru-Black.woff') format('woff');
 	font-weight: 900;
 	font-style: normal;
 	font-display: swap;
@@ -27,8 +27,8 @@
 
 @font-face {
 	font-family: 'GT Maru Mono';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-BoldOblique.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-BoldOblique.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaruMono-BoldOblique.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaruMono-BoldOblique.woff') format('woff');
 	font-weight: bold;
 	font-style: italic;
 	font-display: swap;
@@ -36,8 +36,8 @@
 
 @font-face {
 	font-family: 'GT Maru';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-BoldOblique.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-BoldOblique.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaru-BoldOblique.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaru-BoldOblique.woff') format('woff');
 	font-weight: bold;
 	font-style: italic;
 	font-display: swap;
@@ -45,8 +45,8 @@
 
 @font-face {
 	font-family: 'GT Maru';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-LightOblique.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-LightOblique.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaru-LightOblique.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaru-LightOblique.woff') format('woff');
 	font-weight: 300;
 	font-style: italic;
 	font-display: swap;
@@ -54,8 +54,8 @@
 
 @font-face {
 	font-family: 'GT Maru Mono';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-MediumOblique.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-MediumOblique.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaruMono-MediumOblique.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaruMono-MediumOblique.woff') format('woff');
 	font-weight: 500;
 	font-style: italic;
 	font-display: swap;
@@ -63,8 +63,8 @@
 
 @font-face {
 	font-family: 'GT Maru';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-Medium.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-Medium.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaru-Medium.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaru-Medium.woff') format('woff');
 	font-weight: 500;
 	font-style: normal;
 	font-display: swap;
@@ -72,8 +72,8 @@
 
 @font-face {
 	font-family: 'GT Maru Mono';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-Medium.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-Medium.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaruMono-Medium.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaruMono-Medium.woff') format('woff');
 	font-weight: 500;
 	font-style: normal;
 	font-display: swap;
@@ -81,8 +81,8 @@
 
 @font-face {
 	font-family: 'GT Maru';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-RegularOblique.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-RegularOblique.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaru-RegularOblique.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaru-RegularOblique.woff') format('woff');
 	font-weight: normal;
 	font-style: italic;
 	font-display: swap;
@@ -90,8 +90,8 @@
 
 @font-face {
 	font-family: 'GT Maru';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-MediumOblique.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-MediumOblique.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaru-MediumOblique.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaru-MediumOblique.woff') format('woff');
 	font-weight: 500;
 	font-style: italic;
 	font-display: swap;
@@ -99,8 +99,8 @@
 
 @font-face {
 	font-family: 'GT Maru Mono';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-Bold.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-Bold.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaruMono-Bold.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaruMono-Bold.woff') format('woff');
 	font-weight: bold;
 	font-style: normal;
 	font-display: swap;
@@ -108,8 +108,8 @@
 
 @font-face {
 	font-family: 'GT Maru Mono';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-Regular.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-Regular.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaruMono-Regular.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaruMono-Regular.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	font-display: swap;
@@ -117,8 +117,8 @@
 
 @font-face {
 	font-family: 'GT Maru';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-BlackOblique.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-BlackOblique.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaru-BlackOblique.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaru-BlackOblique.woff') format('woff');
 	font-weight: 900;
 	font-style: italic;
 	font-display: swap;
@@ -126,8 +126,8 @@
 
 @font-face {
 	font-family: 'GT Maru Mono';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-BlackOblique.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-BlackOblique.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaruMono-BlackOblique.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaruMono-BlackOblique.woff') format('woff');
 	font-weight: 900;
 	font-style: italic;
 	font-display: swap;
@@ -135,8 +135,8 @@
 
 @font-face {
 	font-family: 'GT Maru Mono';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-Light.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-Light.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaruMono-Light.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaruMono-Light.woff') format('woff');
 	font-weight: 300;
 	font-style: normal;
 	font-display: swap;
@@ -144,9 +144,8 @@
 
 @font-face {
 	font-family: 'GT Maru Mono';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-RegularOblique.woff2')
-			format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-RegularOblique.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaruMono-RegularOblique.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaruMono-RegularOblique.woff') format('woff');
 	font-weight: normal;
 	font-style: italic;
 	font-display: swap;
@@ -154,8 +153,8 @@
 
 @font-face {
 	font-family: 'GT Maru';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-Bold.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-Bold.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaru-Bold.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaru-Bold.woff') format('woff');
 	font-weight: bold;
 	font-style: normal;
 	font-display: swap;
@@ -163,8 +162,8 @@
 
 @font-face {
 	font-family: 'GT Maru';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-Light.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaru-Light.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaru-Light.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaru-Light.woff') format('woff');
 	font-weight: 300;
 	font-style: normal;
 	font-display: swap;
@@ -172,8 +171,8 @@
 
 @font-face {
 	font-family: 'GT Maru Mono';
-	src: url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-LightOblique.woff2') format('woff2'),
-		url('https://fonts-hazel-mu.vercel.app/fonts/GTMaruMono-LightOblique.woff') format('woff');
+	src: url('https://fonts-c75.pages.dev/fonts/GTMaruMono-LightOblique.woff2') format('woff2'),
+		url('https://fonts-c75.pages.dev/fonts/GTMaruMono-LightOblique.woff') format('woff');
 	font-weight: 300;
 	font-style: italic;
 	font-display: swap;
diff --git a/src/lib/builders/combobox/types.ts b/src/lib/builders/combobox/types.ts
index 1b707139..72127e2c 100644
--- a/src/lib/builders/combobox/types.ts
+++ b/src/lib/builders/combobox/types.ts
@@ -10,7 +10,7 @@ export type {
 } from '../listbox/types.js';
 
 export type CreateComboboxProps<
-	Value,
+	Value = unknown,
 	Multiple extends boolean = false,
 	S extends ListboxSelected<Multiple, Value> = ListboxSelected<Multiple, Value>
 > = Omit<CreateListboxProps<Value, Multiple, S>, 'builder' | 'typeahead'>;
diff --git a/src/lib/builders/context-menu/create.ts b/src/lib/builders/context-menu/create.ts
index db93db00..47a61232 100644
--- a/src/lib/builders/context-menu/create.ts
+++ b/src/lib/builders/context-menu/create.ts
@@ -55,6 +55,7 @@ const defaults = {
 	disableFocusFirstItem: true,
 	closeFocus: undefined,
 	closeOnItemClick: true,
+	onOutsideClick: undefined,
 } satisfies CreateContextMenuProps;
 
 const { name, selector } = createElHelpers<_MenuParts>('context-menu');
@@ -110,6 +111,7 @@ export function createContextMenu(props?: CreateContextMenuProps) {
 	const longPressTimer = writable(0);
 
 	function handleClickOutside(e: PointerEvent) {
+		get(rootOptions.onOutsideClick)?.(e);
 		if (e.defaultPrevented) return;
 
 		const target = e.target;
diff --git a/src/lib/builders/date-picker/create.ts b/src/lib/builders/date-picker/create.ts
index 7564f008..16f21a86 100644
--- a/src/lib/builders/date-picker/create.ts
+++ b/src/lib/builders/date-picker/create.ts
@@ -27,6 +27,7 @@ const defaults = {
 	},
 	closeOnEscape: true,
 	closeOnOutsideClick: true,
+	onOutsideClick: undefined,
 	preventScroll: false,
 	forceVisible: false,
 	locale: 'en',
@@ -72,6 +73,7 @@ export function createDatePicker(props?: CreateDatePickerProps) {
 		forceVisible: withDefaults.forceVisible,
 		openFocus: pickerOpenFocus,
 		ids: withDefaults.popoverIds,
+		onOutsideClick: withDefaults.onOutsideClick,
 	});
 
 	const trigger = builder('popover-trigger', {
diff --git a/src/lib/builders/date-range-picker/create.ts b/src/lib/builders/date-range-picker/create.ts
index 6caecbdb..d704966b 100644
--- a/src/lib/builders/date-range-picker/create.ts
+++ b/src/lib/builders/date-range-picker/create.ts
@@ -37,6 +37,7 @@ const defaults = {
 	minValue: undefined,
 	maxValue: undefined,
 	weekdayFormat: 'narrow',
+	onOutsideClick: undefined,
 } satisfies CreateDateRangePickerProps;
 
 export function createDateRangePicker(props?: CreateDateRangePickerProps) {
@@ -68,6 +69,7 @@ export function createDateRangePicker(props?: CreateDateRangePickerProps) {
 		portal: withDefaults.portal,
 		forceVisible: withDefaults.forceVisible,
 		openFocus: pickerOpenFocus,
+		onOutsideClick: withDefaults.onOutsideClick,
 	});
 
 	const options = toWritableStores({
@@ -151,6 +153,10 @@ export function createDateRangePicker(props?: CreateDateRangePickerProps) {
 		}
 	});
 
+	effect([options.onOutsideClick], ([$onOutsideClick]) => {
+		popover.options.onOutsideClick.set($onOutsideClick);
+	});
+
 	const rangeFieldOptions = omit(
 		rangeField.options,
 		'locale',
diff --git a/src/lib/builders/dialog/create.ts b/src/lib/builders/dialog/create.ts
index ffa9beab..9fc989b4 100644
--- a/src/lib/builders/dialog/create.ts
+++ b/src/lib/builders/dialog/create.ts
@@ -23,11 +23,11 @@ import {
 	sleep,
 	styleToString,
 	toWritableStores,
+	omit,
 } from '$lib/internal/helpers/index.js';
 import type { Defaults, MeltActionReturn } from '$lib/internal/types.js';
 import { tick } from 'svelte';
 import { derived, get, writable } from 'svelte/store';
-import { omit } from '../../internal/helpers/object';
 import type { DialogEvents } from './events.js';
 import type { CreateDialogProps } from './types.js';
 
@@ -51,6 +51,7 @@ const defaults = {
 	forceVisible: false,
 	openFocus: undefined,
 	closeFocus: undefined,
+	onOutsideClick: undefined,
 } satisfies Defaults<CreateDialogProps>;
 
 const openDialogIds = writable<string[]>([]);
@@ -72,6 +73,7 @@ export function createDialog(props?: CreateDialogProps) {
 		forceVisible,
 		openFocus,
 		closeFocus,
+		onOutsideClick,
 	} = options;
 
 	const activeTrigger = writable<HTMLElement | null>(null);
@@ -229,6 +231,7 @@ export function createDialog(props?: CreateDialogProps) {
 					return useClickOutside(node, {
 						enabled: $open,
 						handler: (e: PointerEvent) => {
+							get(onOutsideClick)?.(e);
 							if (e.defaultPrevented) return;
 
 							const $openDialogIds = get(openDialogIds);
diff --git a/src/lib/builders/dialog/types.ts b/src/lib/builders/dialog/types.ts
index d6087b47..7154acdb 100644
--- a/src/lib/builders/dialog/types.ts
+++ b/src/lib/builders/dialog/types.ts
@@ -4,13 +4,62 @@ import type { Writable } from 'svelte/store';
 import type { DialogIdParts, createDialog } from './create.js';
 export type { DialogComponentEvents } from './events.js';
 export type CreateDialogProps = {
+	/**
+	 * If true, the dialog will prevent scrolling on the body
+	 * when it is open.
+	 *
+	 * @default true
+	 */
 	preventScroll?: boolean;
+
+	/**
+	 * If true, the dialog will close when the user presses the escape key.
+	 *
+	 * @default true
+	 */
 	closeOnEscape?: boolean;
+
+	/**
+	 * If true, the dialog will close when the user clicks outside of it.
+	 *
+	 * @default true
+	 */
 	closeOnOutsideClick?: boolean;
+
+	/**
+	 * A custom event handler for the "outside click" event, which
+	 * is handled by the `document`.
+	 * If `event.preventDefault()` is called within the function,
+	 * the dialog will not close when the user clicks outside of it.
+	 */
+	onOutsideClick?: (event: PointerEvent) => void;
+
+	/**
+	 * The `role` attribute to apply to the dialog.
+	 *
+	 * @default 'dialog'
+	 */
 	role?: 'dialog' | 'alertdialog';
+
+	/**
+	 * If true, the dialog will be open by default.
+	 *
+	 * @default false
+	 */
 	defaultOpen?: boolean;
+
+	/**
+	 * A writable store that controls the open state of the dialog.
+	 * @see [Controlled Usage](https://melt-ui.com/docs/controlled#bring-your-own-store)
+	 */
 	open?: Writable<boolean>;
+
+	/**
+	 * A function that will be called when the open state of the dialog changes.
+	 * @see [Controlled Usage](https://melt-ui.com/docs/controlled#change-functions)
+	 */
 	onOpenChange?: ChangeFn<boolean>;
+
 	/**
 	 * If not undefined, the dialog content will be rendered within the provided element or selector.
 	 *
@@ -18,6 +67,13 @@ export type CreateDialogProps = {
 	 */
 	portal?: HTMLElement | string | null;
 
+	/**
+	 * If true, the dialog will be visible regardless of the open state.
+	 * Use this when you want to conditionally render the content of the dialog
+	 * using an `{#if ...}` block.
+	 *
+	 * @default false
+	 */
 	forceVisible?: boolean;
 
 	/**
diff --git a/src/lib/builders/dropdown-menu/create.ts b/src/lib/builders/dropdown-menu/create.ts
index 0d055ef9..8ba8fddf 100644
--- a/src/lib/builders/dropdown-menu/create.ts
+++ b/src/lib/builders/dropdown-menu/create.ts
@@ -21,6 +21,7 @@ const defaults = {
 	closeFocus: undefined,
 	disableFocusFirstItem: false,
 	closeOnItemClick: true,
+	onOutsideClick: undefined,
 } satisfies CreateDropdownMenuProps;
 
 export function createDropdownMenu(props?: CreateDropdownMenuProps) {
diff --git a/src/lib/builders/link-preview/create.ts b/src/lib/builders/link-preview/create.ts
index 99f92273..7152609b 100644
--- a/src/lib/builders/link-preview/create.ts
+++ b/src/lib/builders/link-preview/create.ts
@@ -42,6 +42,7 @@ const defaults = {
 	forceVisible: false,
 	portal: 'body',
 	closeOnEscape: true,
+	onOutsideClick: undefined,
 } satisfies CreateLinkPreviewProps;
 
 export const linkPreviewIdParts = ['trigger', 'content'] as const;
@@ -71,6 +72,7 @@ export function createLinkPreview(props: CreateLinkPreviewProps = {}) {
 		forceVisible,
 		portal,
 		closeOnEscape,
+		onOutsideClick,
 	} = options;
 
 	const ids = toWritableStores({ ...generateIds(linkPreviewIdParts), ...withDefaults.ids });
@@ -190,7 +192,22 @@ export function createLinkPreview(props: CreateLinkPreviewProps = {}) {
 						open: open,
 						options: {
 							floating: $positioning,
-							clickOutside: $closeOnOutsideClick ? undefined : null,
+							clickOutside: $closeOnOutsideClick
+								? {
+										handler: (e) => {
+											get(onOutsideClick)?.(e);
+											if (e.defaultPrevented) return;
+
+											if (
+												isHTMLElement($activeTrigger) &&
+												!$activeTrigger.contains(e.target as Element)
+											) {
+												open.set(false);
+												$activeTrigger.focus();
+											}
+										},
+								  }
+								: null,
 							portal: getPortalDestination(node, $portal),
 							focusTrap: null,
 							escapeKeydown: $closeOnEscape ? undefined : null,
diff --git a/src/lib/builders/link-preview/types.ts b/src/lib/builders/link-preview/types.ts
index 49b772b7..21094a75 100644
--- a/src/lib/builders/link-preview/types.ts
+++ b/src/lib/builders/link-preview/types.ts
@@ -57,6 +57,14 @@ export type CreateLinkPreviewProps = {
 	 */
 	closeOnOutsideClick?: boolean;
 
+	/**
+	 * A custom event handler for the "outside click" event, which
+	 * is handled by the `document`.
+	 * If `event.preventDefault()` is called within the function,
+	 * the dialog will not close when the user clicks outside of it.
+	 */
+	onOutsideClick?: (event: PointerEvent) => void;
+
 	/**
 	 * Whether or not to close the linkpreview when the escape key is pressed
 	 * while it is open.
diff --git a/src/lib/builders/listbox/create.ts b/src/lib/builders/listbox/create.ts
index 168bf2fb..57a03872 100644
--- a/src/lib/builders/listbox/create.ts
+++ b/src/lib/builders/listbox/create.ts
@@ -75,6 +75,7 @@ const defaults = {
 	name: undefined,
 	typeahead: true,
 	highlightOnHover: true,
+	onOutsideClick: undefined,
 } satisfies Defaults<CreateListboxProps<unknown>>;
 
 export const listboxIdParts = ['trigger', 'menu', 'label'] as const;
@@ -133,6 +134,7 @@ export function createListbox<
 		typeahead,
 		name: nameProp,
 		highlightOnHover,
+		onOutsideClick,
 	} = options;
 	const { name, selector } = createElHelpers(withDefaults.builder);
 
@@ -458,15 +460,8 @@ export function createListbox<
 			const unsubscribe = executeCallbacks(
 				// Bind the popper portal to the input element.
 				effect(
-					[isVisible, closeOnEscape, portal, closeOnOutsideClick, positioning, activeTrigger],
-					([
-						$isVisible,
-						$closeOnEscape,
-						$portal,
-						$closeOnOutsideClick,
-						$positioning,
-						$activeTrigger,
-					]) => {
+					[isVisible, portal, closeOnOutsideClick, positioning, activeTrigger],
+					([$isVisible, $portal, $closeOnOutsideClick, $positioning, $activeTrigger]) => {
 						unsubPopper();
 
 						if (!$isVisible || !$activeTrigger) return;
@@ -482,6 +477,9 @@ export function createListbox<
 								clickOutside: $closeOnOutsideClick
 									? {
 											handler: (e) => {
+												get(onOutsideClick)?.(e);
+												if (e.defaultPrevented) return;
+
 												const target = e.target;
 												if (!isElement(target)) return;
 												if (target === $activeTrigger || $activeTrigger.contains(target)) {
@@ -492,13 +490,7 @@ export function createListbox<
 											ignore: ignoreHandler,
 									  }
 									: null,
-								escapeKeydown: $closeOnEscape
-									? {
-											handler: () => {
-												closeMenu();
-											},
-									  }
-									: null,
+								escapeKeydown: null,
 								portal: getPortalDestination(node, $portal),
 							},
 						});
diff --git a/src/lib/builders/listbox/types.ts b/src/lib/builders/listbox/types.ts
index f62a7bc4..ba1bc0a7 100644
--- a/src/lib/builders/listbox/types.ts
+++ b/src/lib/builders/listbox/types.ts
@@ -111,6 +111,14 @@ export type CreateListboxProps<
 	 */
 	closeOnEscape?: boolean;
 
+	/**
+	 * A custom event handler for the "outside click" event, which
+	 * is handled by the `document`.
+	 * If `event.preventDefault()` is called within the function,
+	 * the dialog will not close when the user clicks outside of it.
+	 */
+	onOutsideClick?: (event: PointerEvent) => void;
+
 	/**
 	 * Whether or not to prevent scrolling the page when the
 	 * listbox menu is open.
diff --git a/src/lib/builders/menu/create.ts b/src/lib/builders/menu/create.ts
index 61fc3fa3..a393915a 100644
--- a/src/lib/builders/menu/create.ts
+++ b/src/lib/builders/menu/create.ts
@@ -76,6 +76,7 @@ const defaults = {
 	defaultOpen: false,
 	typeahead: true,
 	closeOnItemClick: true,
+	onOutsideClick: undefined,
 } satisfies Defaults<_CreateMenuProps>;
 
 export function createMenuBuilder(opts: _MenuBuilderOptions) {
@@ -94,6 +95,7 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 		closeFocus,
 		disableFocusFirstItem,
 		closeOnItemClick,
+		onOutsideClick,
 	} = opts.rootOptions;
 
 	const rootOpen = opts.rootOpen;
@@ -189,7 +191,22 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 							open: rootOpen,
 							options: {
 								floating: $positioning,
-								clickOutside: $closeOnOutsideClick ? undefined : null,
+								clickOutside: $closeOnOutsideClick
+									? {
+											handler: (e) => {
+												get(onOutsideClick)?.(e);
+												if (e.defaultPrevented) return;
+
+												if (
+													isHTMLElement($rootActiveTrigger) &&
+													!$rootActiveTrigger.contains(e.target as Element)
+												) {
+													rootOpen.set(false);
+													$rootActiveTrigger.focus();
+												}
+											},
+									  }
+									: null,
 								portal: getPortalDestination(node, $portal),
 								escapeKeydown: $closeOnEscape ? undefined : null,
 							},
@@ -849,6 +866,10 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 			action: (node: HTMLElement): MeltActionReturn<MenuEvents['subTrigger']> => {
 				setMeltMenuAttribute(node, selector);
 				applyAttrsIfDisabled(node);
+				subActiveTrigger.update((p) => {
+					if (p) return p;
+					return node;
+				});
 
 				const unsubTimer = () => {
 					clearTimerStore(subOpenTimer);
diff --git a/src/lib/builders/menu/types.ts b/src/lib/builders/menu/types.ts
index 38e4df10..a5ff239f 100644
--- a/src/lib/builders/menu/types.ts
+++ b/src/lib/builders/menu/types.ts
@@ -60,6 +60,14 @@ export type _CreateMenuProps = {
 	 */
 	closeOnOutsideClick?: boolean;
 
+	/**
+	 * A custom event handler for the "outside click" event, which
+	 * is handled by the `document`.
+	 * If `event.preventDefault()` is called within the function,
+	 * the dialog will not close when the user clicks outside of it.
+	 */
+	onOutsideClick?: (event: PointerEvent) => void;
+
 	/**
 	 * Whether or not to loop the menu navigation.
 	 *
@@ -181,6 +189,7 @@ export type _MenuBuilderOptions = {
 		closeFocus: Writable<FocusProp | undefined>;
 		disableFocusFirstItem: Writable<boolean>;
 		closeOnItemClick: Writable<boolean>;
+		onOutsideClick: Writable<((event: PointerEvent) => void) | undefined>;
 	};
 
 	nextFocusable: Writable<HTMLElement | null>;
diff --git a/src/lib/builders/menubar/create.ts b/src/lib/builders/menubar/create.ts
index 3c923720..22172dbe 100644
--- a/src/lib/builders/menubar/create.ts
+++ b/src/lib/builders/menubar/create.ts
@@ -106,6 +106,7 @@ export function createMenubar(props?: CreateMenubarProps) {
 		closeFocus: undefined,
 		disableFocusFirstItem: false,
 		closeOnItemClick: true,
+		onOutsideClick: undefined,
 	} satisfies CreateMenubarMenuProps;
 
 	const createMenu = (props?: CreateMenubarMenuProps) => {
@@ -115,7 +116,7 @@ export function createMenubar(props?: CreateMenubarProps) {
 
 		// options
 		const options = toWritableStores(withDefaults);
-		const { positioning, portal, forceVisible } = options;
+		const { positioning, portal, forceVisible, closeOnOutsideClick, onOutsideClick } = options;
 
 		const m = createMenuBuilder({
 			rootOptions: options,
@@ -154,8 +155,8 @@ export function createMenubar(props?: CreateMenubarProps) {
 				let unsubPopper = noop;
 
 				const unsubDerived = effect(
-					[rootOpen, rootActiveTrigger, positioning, portal],
-					([$rootOpen, $rootActiveTrigger, $positioning, $portal]) => {
+					[rootOpen, rootActiveTrigger, positioning, portal, closeOnOutsideClick],
+					([$rootOpen, $rootActiveTrigger, $positioning, $portal, $closeOnOutsideClick]) => {
 						unsubPopper();
 						if (!($rootOpen && $rootActiveTrigger)) return;
 
@@ -166,17 +167,21 @@ export function createMenubar(props?: CreateMenubarProps) {
 								options: {
 									floating: $positioning,
 									portal: getPortalDestination(node, $portal),
-									clickOutside: {
-										ignore: (e) => {
-											const target = e.target;
-											const menubarEl = document.getElementById(get(ids.menubar));
-											if (!menubarEl || !isElement(target)) return false;
-											return menubarEl.contains(target);
-										},
-										handler: () => {
-											activeMenu.set('');
-										},
-									},
+									clickOutside: $closeOnOutsideClick
+										? {
+												ignore: (e) => {
+													const target = e.target;
+													const menubarEl = document.getElementById(get(ids.menubar));
+													if (!menubarEl || !isElement(target)) return false;
+													return menubarEl.contains(target);
+												},
+												handler: (e) => {
+													get(onOutsideClick)?.(e);
+													if (e.defaultPrevented) return;
+													activeMenu.set('');
+												},
+										  }
+										: null,
 								},
 							});
 
diff --git a/src/lib/builders/popover/create.ts b/src/lib/builders/popover/create.ts
index fb5cab2d..485b99af 100644
--- a/src/lib/builders/popover/create.ts
+++ b/src/lib/builders/popover/create.ts
@@ -43,6 +43,7 @@ const defaults = {
 	forceVisible: false,
 	openFocus: undefined,
 	closeFocus: undefined,
+	onOutsideClick: undefined,
 } satisfies Defaults<CreatePopoverProps>;
 
 type PopoverParts = 'trigger' | 'content' | 'arrow' | 'close';
@@ -66,6 +67,7 @@ export function createPopover(args?: CreatePopoverProps) {
 		forceVisible,
 		openFocus,
 		closeFocus,
+		onOutsideClick,
 	} = options;
 
 	const openWritable = withDefaults.open ?? writable(withDefaults.defaultOpen);
@@ -179,6 +181,8 @@ export function createPopover(args?: CreatePopoverProps) {
 	}
 
 	function handleClickOutside(e: PointerEvent) {
+		get(onOutsideClick)?.(e);
+		if (e.defaultPrevented) return;
 		const target = e.target;
 		const triggerEl = document.getElementById(get(ids.trigger));
 
diff --git a/src/lib/builders/popover/types.ts b/src/lib/builders/popover/types.ts
index d4f42779..48731e8e 100644
--- a/src/lib/builders/popover/types.ts
+++ b/src/lib/builders/popover/types.ts
@@ -60,6 +60,14 @@ export type CreatePopoverProps = {
 	 */
 	closeOnOutsideClick?: boolean;
 
+	/**
+	 * A custom event handler for the "outside click" event, which
+	 * is handled by the `document`.
+	 * If `event.preventDefault()` is called within the function,
+	 * the dialog will not close when the user clicks outside of it.
+	 */
+	onOutsideClick?: (event: PointerEvent) => void;
+
 	/**
 	 * Whether or not to prevent scrolling when the popover is open.
 	 *
diff --git a/src/lib/builders/progress/types.ts b/src/lib/builders/progress/types.ts
index 9113037c..e840e18e 100644
--- a/src/lib/builders/progress/types.ts
+++ b/src/lib/builders/progress/types.ts
@@ -9,7 +9,7 @@ export type CreateProgressProps = {
 	 *
 	 * @default 0
 	 */
-	defaultValue?: number;
+	defaultValue?: number | null;
 
 	/**
 	 * The controlled value store for the radio group.
@@ -17,14 +17,14 @@ export type CreateProgressProps = {
 	 *
 	 * @see https://melt-ui.com/docs/controlled#bring-your-own-store
 	 */
-	value?: Writable<number>;
+	value?: Writable<number | null>;
 
 	/**
 	 * The callback invoked when the value store of the progress changes.
 	 *
 	 * @see https://melt-ui.com/docs/controlled#change-functions
 	 */
-	onValueChange?: ChangeFn<number>;
+	onValueChange?: ChangeFn<number | null>;
 
 	/**
 	 * The maximum value of the progress.
diff --git a/src/lib/builders/tags-input/create.ts b/src/lib/builders/tags-input/create.ts
index c38c75d4..1c0b2970 100644
--- a/src/lib/builders/tags-input/create.ts
+++ b/src/lib/builders/tags-input/create.ts
@@ -324,13 +324,12 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 					}
 				}),
 				addMeltEventListener(node, 'paste', async (e) => {
-					// Do nothing when there is nothing to paste
-					if (!e.clipboardData) return;
-					const pastedText = e.clipboardData.getData('text');
+					const pastedText = e.clipboardData?.getData('text');
 					if (!pastedText) return;
 
 					// Do nothing when addOnPaste is false
 					if (!get(addOnPaste)) return;
+					e.preventDefault();
 
 					// Update value with the pasted text or set invalid
 					if (isInputValid(pastedText) && (await addTag(pastedText))) {
diff --git a/src/lib/builders/toast/create.ts b/src/lib/builders/toast/create.ts
index c85fa038..5e3bd8f9 100644
--- a/src/lib/builders/toast/create.ts
+++ b/src/lib/builders/toast/create.ts
@@ -111,7 +111,7 @@ export function createToaster<T = object>(props?: CreateToasterProps) {
 					role: 'alert',
 					'aria-describedby': toast.ids.description,
 					'aria-labelledby': toast.ids.title,
-					'aria-live': toast.type === 'foreground' ? 'assertive' : 'polite',
+					'aria-live': toast.type === 'foreground' ? ('assertive' as const) : ('polite' as const),
 					tabindex: -1,
 				};
 			};
diff --git a/src/lib/internal/actions/escape-keydown/action.ts b/src/lib/internal/actions/escape-keydown/action.ts
index 276b4fb6..4181b555 100644
--- a/src/lib/internal/actions/escape-keydown/action.ts
+++ b/src/lib/internal/actions/escape-keydown/action.ts
@@ -28,7 +28,6 @@ const documentEscapeKeyStore = readable<KeyboardEvent | undefined>(
 		// Adds a keydown event listener to the document, calling the keydown function when triggered.
 		const unsubscribe = addEventListener(document, 'keydown', keydown, {
 			passive: false,
-			capture: true,
 		});
 
 		// Returns a function to unsubscribe from the event listener and stop tracking keydown events.
diff --git a/src/pagefind/pagefind.js b/src/pagefind/pagefind.js
deleted file mode 100644
index f44a521c..00000000
--- a/src/pagefind/pagefind.js
+++ /dev/null
@@ -1,10 +0,0 @@
-// @ts-nocheck
-const pagefind_version="1.0.3";let wasm_bindgen;(function(){const __exports={};let script_src;if(typeof document==='undefined'){script_src=location.href}else{script_src=new URL("UNHANDLED",location.href).toString()}let wasm;let cachedUint8Memory0=null;function getUint8Memory0(){if(cachedUint8Memory0===null||cachedUint8Memory0.byteLength===0){cachedUint8Memory0=new Uint8Array(wasm.memory.buffer)}return cachedUint8Memory0}let WASM_VECTOR_LEN=0;function passArray8ToWasm0(arg,malloc){const ptr=malloc(arg.length*1);getUint8Memory0().set(arg,ptr/1);WASM_VECTOR_LEN=arg.length;return ptr}__exports.init_pagefind=function(metadata_bytes){const ptr0=passArray8ToWasm0(metadata_bytes,wasm.__wbindgen_malloc);const len0=WASM_VECTOR_LEN;const ret=wasm.init_pagefind(ptr0,len0);return ret};__exports.load_index_chunk=function(ptr,chunk_bytes){const ptr0=passArray8ToWasm0(chunk_bytes,wasm.__wbindgen_malloc);const len0=WASM_VECTOR_LEN;const ret=wasm.load_index_chunk(ptr,ptr0,len0);return ret};__exports.load_filter_chunk=function(ptr,chunk_bytes){const ptr0=passArray8ToWasm0(chunk_bytes,wasm.__wbindgen_malloc);const len0=WASM_VECTOR_LEN;const ret=wasm.load_filter_chunk(ptr,ptr0,len0);return ret};const cachedTextEncoder=new TextEncoder('utf-8');const encodeString=(typeof cachedTextEncoder.encodeInto==='function'?function(arg,view){return cachedTextEncoder.encodeInto(arg,view)}:function(arg,view){const buf=cachedTextEncoder.encode(arg);view.set(buf);return{read:arg.length,written:buf.length}});function passStringToWasm0(arg,malloc,realloc){if(realloc===undefined){const buf=cachedTextEncoder.encode(arg);const ptr=malloc(buf.length);getUint8Memory0().subarray(ptr,ptr+buf.length).set(buf);WASM_VECTOR_LEN=buf.length;return ptr}let len=arg.length;let ptr=malloc(len);const mem=getUint8Memory0();let offset=0;for(;offset<len;offset++){const code=arg.charCodeAt(offset);if(code>0x7F)break;mem[ptr+offset]=code}if(offset!==len){if(offset!==0){arg=arg.slice(offset)}ptr=realloc(ptr,len,len=offset+arg.length*3);const view=getUint8Memory0().subarray(ptr+offset,ptr+len);const ret=encodeString(arg,view);offset+=ret.written}WASM_VECTOR_LEN=offset;return ptr}__exports.add_synthetic_filter=function(ptr,filter){const ptr0=passStringToWasm0(filter,wasm.__wbindgen_malloc,wasm.__wbindgen_realloc);const len0=WASM_VECTOR_LEN;const ret=wasm.add_synthetic_filter(ptr,ptr0,len0);return ret};let cachedInt32Memory0=null;function getInt32Memory0(){if(cachedInt32Memory0===null||cachedInt32Memory0.byteLength===0){cachedInt32Memory0=new Int32Array(wasm.memory.buffer)}return cachedInt32Memory0}const cachedTextDecoder=new TextDecoder('utf-8',{ignoreBOM:true,fatal:true});cachedTextDecoder.decode();function getStringFromWasm0(ptr,len){return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr,ptr+len))}__exports.request_indexes=function(ptr,query){try{const retptr=wasm.__wbindgen_add_to_stack_pointer(-16);const ptr0=passStringToWasm0(query,wasm.__wbindgen_malloc,wasm.__wbindgen_realloc);const len0=WASM_VECTOR_LEN;wasm.request_indexes(retptr,ptr,ptr0,len0);var r0=getInt32Memory0()[retptr/4+0];var r1=getInt32Memory0()[retptr/4+1];return getStringFromWasm0(r0,r1)}finally{wasm.__wbindgen_add_to_stack_pointer(16);wasm.__wbindgen_free(r0,r1)}};__exports.request_filter_indexes=function(ptr,filters){try{const retptr=wasm.__wbindgen_add_to_stack_pointer(-16);const ptr0=passStringToWasm0(filters,wasm.__wbindgen_malloc,wasm.__wbindgen_realloc);const len0=WASM_VECTOR_LEN;wasm.request_filter_indexes(retptr,ptr,ptr0,len0);var r0=getInt32Memory0()[retptr/4+0];var r1=getInt32Memory0()[retptr/4+1];return getStringFromWasm0(r0,r1)}finally{wasm.__wbindgen_add_to_stack_pointer(16);wasm.__wbindgen_free(r0,r1)}};__exports.request_all_filter_indexes=function(ptr){try{const retptr=wasm.__wbindgen_add_to_stack_pointer(-16);wasm.request_all_filter_indexes(retptr,ptr);var r0=getInt32Memory0()[retptr/4+0];var r1=getInt32Memory0()[retptr/4+1];return getStringFromWasm0(r0,r1)}finally{wasm.__wbindgen_add_to_stack_pointer(16);wasm.__wbindgen_free(r0,r1)}};__exports.filters=function(ptr){try{const retptr=wasm.__wbindgen_add_to_stack_pointer(-16);wasm.filters(retptr,ptr);var r0=getInt32Memory0()[retptr/4+0];var r1=getInt32Memory0()[retptr/4+1];return getStringFromWasm0(r0,r1)}finally{wasm.__wbindgen_add_to_stack_pointer(16);wasm.__wbindgen_free(r0,r1)}};__exports.search=function(ptr,query,filter,sort,exact){try{const retptr=wasm.__wbindgen_add_to_stack_pointer(-16);const ptr0=passStringToWasm0(query,wasm.__wbindgen_malloc,wasm.__wbindgen_realloc);const len0=WASM_VECTOR_LEN;const ptr1=passStringToWasm0(filter,wasm.__wbindgen_malloc,wasm.__wbindgen_realloc);const len1=WASM_VECTOR_LEN;const ptr2=passStringToWasm0(sort,wasm.__wbindgen_malloc,wasm.__wbindgen_realloc);const len2=WASM_VECTOR_LEN;wasm.search(retptr,ptr,ptr0,len0,ptr1,len1,ptr2,len2,exact);var r0=getInt32Memory0()[retptr/4+0];var r1=getInt32Memory0()[retptr/4+1];return getStringFromWasm0(r0,r1)}finally{wasm.__wbindgen_add_to_stack_pointer(16);wasm.__wbindgen_free(r0,r1)}};async function load(module,imports){if(typeof Response==='function'&&module instanceof Response){if(typeof WebAssembly.instantiateStreaming==='function'){try{return await WebAssembly.instantiateStreaming(module,imports)}catch(e){if(module.headers.get('Content-Type')!='application/wasm'){console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n",e)}else{throw e}}}const bytes=await module.arrayBuffer();return await WebAssembly.instantiate(bytes,imports)}else{const instance=await WebAssembly.instantiate(module,imports);if(instance instanceof WebAssembly.Instance){return{instance,module}}else{return instance}}}function getImports(){const imports={};imports.wbg={};return imports}function initMemory(imports,maybe_memory){}function finalizeInit(instance,module){wasm=instance.exports;init.__wbindgen_wasm_module=module;cachedInt32Memory0=null;cachedUint8Memory0=null;return wasm}function initSync(module){const imports=getImports();initMemory(imports);if(!(module instanceof WebAssembly.Module)){module=new WebAssembly.Module(module)}const instance=new WebAssembly.Instance(module,imports);return finalizeInit(instance,module)}async function init(input){if(typeof input==='undefined'){input=script_src.replace(/\.js$/,'_bg.wasm')}const imports=getImports();if(typeof input==='string'||(typeof Request==='function'&&input instanceof Request)||(typeof URL==='function'&&input instanceof URL)){input=fetch(input)}initMemory(imports);const{instance,module}=await load(await input,imports);return finalizeInit(instance,module)}wasm_bindgen=Object.assign(init,{initSync},__exports)})();var u8=Uint8Array;var u16=Uint16Array;var u32=Uint32Array;var fleb=new u8([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]);var fdeb=new u8([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]);var clim=new u8([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]);var freb=function(eb,start){var b=new u16(31);for(var i2=0;i2<31;++i2){b[i2]=start+=1<<eb[i2-1]}var r=new u32(b[30]);for(var i2=1;i2<30;++i2){for(var j=b[i2];j<b[i2+1];++j){r[j]=j-b[i2]<<5|i2}}return[b,r]};var _a=freb(fleb,2);var fl=_a[0];var revfl=_a[1];fl[28]=258,revfl[258]=28;var _b=freb(fdeb,0);var fd=_b[0];var revfd=_b[1];var rev=new u16(32768);for(i=0;i<32768;++i){x=(i&43690)>>>1|(i&21845)<<1;x=(x&52428)>>>2|(x&13107)<<2;x=(x&61680)>>>4|(x&3855)<<4;rev[i]=((x&65280)>>>8|(x&255)<<8)>>>1}var x;var i;var hMap=function(cd,mb,r){var s=cd.length;var i2=0;var l=new u16(mb);for(;i2<s;++i2){if(cd[i2])++l[cd[i2]-1]}var le=new u16(mb);for(i2=0;i2<mb;++i2){le[i2]=le[i2-1]+l[i2-1]<<1}var co;if(r){co=new u16(1<<mb);var rvb=15-mb;for(i2=0;i2<s;++i2){if(cd[i2]){var sv=i2<<4|cd[i2];var r_1=mb-cd[i2];var v=le[cd[i2]-1]++<<r_1;for(var m=v|(1<<r_1)-1;v<=m;++v){co[rev[v]>>>rvb]=sv}}}}else{co=new u16(s);for(i2=0;i2<s;++i2){if(cd[i2]){co[i2]=rev[le[cd[i2]-1]++]>>>15-cd[i2]}}}return co};var flt=new u8(288);for(i=0;i<144;++i)flt[i]=8;var i;for(i=144;i<256;++i)flt[i]=9;var i;for(i=256;i<280;++i)flt[i]=7;var i;for(i=280;i<288;++i)flt[i]=8;var i;var fdt=new u8(32);for(i=0;i<32;++i)fdt[i]=5;var i;var flrm=hMap(flt,9,1);var fdrm=hMap(fdt,5,1);var max=function(a){var m=a[0];for(var i2=1;i2<a.length;++i2){if(a[i2]>m)m=a[i2]}return m};var bits=function(d,p,m){var o=p/8|0;return(d[o]|d[o+1]<<8)>>(p&7)&m};var bits16=function(d,p){var o=p/8|0;return(d[o]|d[o+1]<<8|d[o+2]<<16)>>(p&7)};var shft=function(p){return(p+7)/8|0};var slc=function(v,s,e){if(s==null||s<0)s=0;if(e==null||e>v.length)e=v.length;var n=new(v.BYTES_PER_ELEMENT==2?u16:v.BYTES_PER_ELEMENT==4?u32:u8)(e-s);n.set(v.subarray(s,e));return n};var ec=["unexpected EOF","invalid block type","invalid length/literal","invalid distance","stream finished","no stream handler",,"no callback","invalid UTF-8 data","extra field too long","date not in range 1980-2099","filename too long","stream finishing","invalid zip data"];var err=function(ind,msg,nt){var e=new Error(msg||ec[ind]);e.code=ind;if(Error.captureStackTrace)Error.captureStackTrace(e,err);if(!nt)throw e;return e};var inflt=function(dat,buf,st){var sl=dat.length;if(!sl||st&&st.f&&!st.l)return buf||new u8(0);var noBuf=!buf||st;var noSt=!st||st.i;if(!st)st={};if(!buf)buf=new u8(sl*3);var cbuf=function(l2){var bl=buf.length;if(l2>bl){var nbuf=new u8(Math.max(bl*2,l2));nbuf.set(buf);buf=nbuf}};var final=st.f||0,pos=st.p||0,bt=st.b||0,lm=st.l,dm=st.d,lbt=st.m,dbt=st.n;var tbts=sl*8;do{if(!lm){final=bits(dat,pos,1);var type=bits(dat,pos+1,3);pos+=3;if(!type){var s=shft(pos)+4,l=dat[s-4]|dat[s-3]<<8,t=s+l;if(t>sl){if(noSt)err(0);break}if(noBuf)cbuf(bt+l);buf.set(dat.subarray(s,t),bt);st.b=bt+=l,st.p=pos=t*8,st.f=final;continue}else if(type==1)lm=flrm,dm=fdrm,lbt=9,dbt=5;else if(type==2){var hLit=bits(dat,pos,31)+257,hcLen=bits(dat,pos+10,15)+4;var tl=hLit+bits(dat,pos+5,31)+1;pos+=14;var ldt=new u8(tl);var clt=new u8(19);for(var i2=0;i2<hcLen;++i2){clt[clim[i2]]=bits(dat,pos+i2*3,7)}pos+=hcLen*3;var clb=max(clt),clbmsk=(1<<clb)-1;var clm=hMap(clt,clb,1);for(var i2=0;i2<tl;){var r=clm[bits(dat,pos,clbmsk)];pos+=r&15;var s=r>>>4;if(s<16){ldt[i2++]=s}else{var c=0,n=0;if(s==16)n=3+bits(dat,pos,3),pos+=2,c=ldt[i2-1];else if(s==17)n=3+bits(dat,pos,7),pos+=3;else if(s==18)n=11+bits(dat,pos,127),pos+=7;while(n--)ldt[i2++]=c}}var lt=ldt.subarray(0,hLit),dt=ldt.subarray(hLit);lbt=max(lt);dbt=max(dt);lm=hMap(lt,lbt,1);dm=hMap(dt,dbt,1)}else err(1);if(pos>tbts){if(noSt)err(0);break}}if(noBuf)cbuf(bt+131072);var lms=(1<<lbt)-1,dms=(1<<dbt)-1;var lpos=pos;for(;;lpos=pos){var c=lm[bits16(dat,pos)&lms],sym=c>>>4;pos+=c&15;if(pos>tbts){if(noSt)err(0);break}if(!c)err(2);if(sym<256)buf[bt++]=sym;else if(sym==256){lpos=pos,lm=null;break}else{var add=sym-254;if(sym>264){var i2=sym-257,b=fleb[i2];add=bits(dat,pos,(1<<b)-1)+fl[i2];pos+=b}var d=dm[bits16(dat,pos)&dms],dsym=d>>>4;if(!d)err(3);pos+=d&15;var dt=fd[dsym];if(dsym>3){var b=fdeb[dsym];dt+=bits16(dat,pos)&(1<<b)-1,pos+=b}if(pos>tbts){if(noSt)err(0);break}if(noBuf)cbuf(bt+131072);var end=bt+add;for(;bt<end;bt+=4){buf[bt]=buf[bt-dt];buf[bt+1]=buf[bt+1-dt];buf[bt+2]=buf[bt+2-dt];buf[bt+3]=buf[bt+3-dt]}bt=end}}st.l=lm,st.p=lpos,st.b=bt,st.f=final;if(lm)final=1,st.m=lbt,st.d=dm,st.n=dbt}while(!final);return bt==buf.length?buf:slc(buf,0,bt)};var et=new u8(0);var gzs=function(d){if(d[0]!=31||d[1]!=139||d[2]!=8)err(6,"invalid gzip data");var flg=d[3];var st=10;if(flg&4)st+=d[10]|(d[11]<<8)+2;for(var zs=(flg>>3&1)+(flg>>4&1);zs>0;zs-=!d[st++]);return st+(flg&2)};var gzl=function(d){var l=d.length;return(d[l-4]|d[l-3]<<8|d[l-2]<<16|d[l-1]<<24)>>>0};function gunzipSync(data,out){return inflt(data.subarray(gzs(data),-8),out||new u8(gzl(data)))}var td=typeof TextDecoder!="undefined"&&new TextDecoder();var tds=0;try{td.decode(et,{stream:true});tds=1}catch(e){}var gz_default=gunzipSync;var calculate_excerpt_region=(word_positions,excerpt_length)=>{if(word_positions.length===0){return 0}let words=[];for(const word of word_positions){words[word.location]=words[word.location]||0;words[word.location]+=word.balanced_score}if(words.length<=excerpt_length){return 0}let densest=words.slice(0,excerpt_length).reduce((partialSum,a)=>partialSum+a,0);let working_sum=densest;let densest_at=[0];for(let i2=0;i2<words.length;i2++){const boundary=i2+excerpt_length;working_sum+=(words[boundary]??0)-(words[i2]??0);if(working_sum>densest){densest=working_sum;densest_at=[i2]}else if(working_sum===densest&&densest_at[densest_at.length-1]===i2-1){densest_at.push(i2)}}let midpoint=densest_at[Math.floor(densest_at.length/2)];return midpoint};var build_excerpt=(content,start,length,locations,not_before,not_from)=>{let is_zws_delimited=content.includes("\u200B");let fragment_words=[];if(is_zws_delimited){fragment_words=content.split("\u200B")}else{fragment_words=content.split(/[\r\n\s]+/g)}for(let word of locations){if(fragment_words[word]?.startsWith(`<mark>`)){continue}fragment_words[word]=`<mark>${fragment_words[word]}</mark>`}let endcap=not_from??fragment_words.length;let startcap=not_before??0;if(endcap-startcap<length){length=endcap-startcap}if(start+length>endcap){start=endcap-length}if(start<startcap){start=startcap}return fragment_words.slice(start,start+length).join(is_zws_delimited?"":" ").trim()};var calculate_sub_results=(fragment,desired_excerpt_length)=>{const anchors=fragment.anchors.filter((a)=>/h\d/i.test(a.element)&&a.text?.length&&/\w/.test(a.text)).sort((a,b)=>a.location-b.location);const results=[];let current_anchor_position=0;let current_anchor={title:fragment.meta["title"],url:fragment.url,weighted_locations:[],locations:[],excerpt:""};const add_result=(end_range)=>{if(current_anchor.locations.length){const relative_weighted_locations=current_anchor.weighted_locations.map((l)=>{return{weight:l.weight,balanced_score:l.balanced_score,location:l.location-current_anchor_position}});const excerpt_start=calculate_excerpt_region(relative_weighted_locations,desired_excerpt_length)+current_anchor_position;const excerpt_length=end_range?Math.min(end_range-excerpt_start,desired_excerpt_length):desired_excerpt_length;current_anchor.excerpt=build_excerpt(fragment.raw_content??"",excerpt_start,excerpt_length,current_anchor.locations,current_anchor_position,end_range);results.push(current_anchor)}};for(let word of fragment.weighted_locations){if(!anchors.length||word.location<anchors[0].location){current_anchor.weighted_locations.push(word);current_anchor.locations.push(word.location)}else{let next_anchor=anchors.shift();add_result(next_anchor.location);while(anchors.length&&word.location>=anchors[0].location){next_anchor=anchors.shift()}let anchored_url=fragment.url;try{const url_is_fq=/^((https?:)?\/\/)/.test(anchored_url);if(url_is_fq){let fq_url=new URL(anchored_url);fq_url.hash=next_anchor.id;anchored_url=fq_url.toString()}else{if(!/^\//.test(anchored_url)){anchored_url=`/${anchored_url}`}let fq_url=new URL(`https://example.com${anchored_url}`);fq_url.hash=next_anchor.id;anchored_url=fq_url.toString().replace(/^https:\/\/example.com/,"")}}catch(e){console.error(`Pagefind: Couldn't process ${anchored_url} for a search result`)}current_anchor_position=next_anchor.location;current_anchor={title:next_anchor.text,url:anchored_url,anchor:next_anchor,weighted_locations:[word],locations:[word.location],excerpt:""}}}add_result(anchors[0]?.location);return results};var asyncSleep=async(ms=100)=>{return new Promise((r)=>setTimeout(r,ms))};var PagefindInstance=class{constructor(opts={}){this.version=pagefind_version;this.backend=wasm_bindgen;this.decoder=new TextDecoder("utf-8");this.wasm=null;this.basePath=opts.basePath||"/pagefind/";this.primary=opts.primary||false;if(this.primary&&!opts.basePath){this.initPrimary()}if(/[^\/]$/.test(this.basePath)){this.basePath=`${this.basePath}/`}if(window?.location?.origin&&this.basePath.startsWith(window.location.origin)){this.basePath=this.basePath.replace(window.location.origin,"")}this.baseUrl=opts.baseUrl||this.defaultBaseUrl();if(!/^(\/|https?:\/\/)/.test(this.baseUrl)){this.baseUrl=`/${this.baseUrl}`}this.indexWeight=opts.indexWeight??1;this.excerptLength=opts.excerptLength??30;this.mergeFilter=opts.mergeFilter??{};this.loaded_chunks={};this.loaded_filters={};this.loaded_fragments={};this.raw_ptr=null;this.searchMeta=null;this.languages=null}initPrimary(){let derivedBasePath=import.meta.url.match(/^(.*\/)pagefind.js.*$/)?.[1];if(derivedBasePath){this.basePath=derivedBasePath}else{console.warn(["Pagefind couldn't determine the base of the bundle from the import path. Falling back to the default.","Set a basePath option when initialising Pagefind to ignore this message."].join("\n"))}}defaultBaseUrl(){let default_base=this.basePath.match(/^(.*\/)_?pagefind/)?.[1];return default_base||"/"}async options(options2){const opts=["basePath","baseUrl","indexWeight","excerptLength","mergeFilter"];for(const[k,v]of Object.entries(options2)){if(k==="mergeFilter"){let filters2=this.stringifyFilters(v);let ptr=await this.getPtr();this.raw_ptr=this.backend.add_synthetic_filter(ptr,filters2)}else if(opts.includes(k)){if(k==="basePath"&&typeof v==="string")this.basePath=v;if(k==="baseUrl"&&typeof v==="string")this.baseUrl=v;if(k==="indexWeight"&&typeof v==="number")this.indexWeight=v;if(k==="excerptLength"&&typeof v==="number")this.excerptLength=v;if(k==="mergeFilter"&&typeof v==="object")this.mergeFilter=v}else{console.warn(`Unknown Pagefind option ${k}. Allowed options: [${opts.join(", ")}]`)}}}decompress(data,file="unknown file"){if(this.decoder.decode(data.slice(0,12))==="pagefind_dcd"){return data.slice(12)}data=gz_default(data);if(this.decoder.decode(data.slice(0,12))!=="pagefind_dcd"){console.error(`Decompressing ${file} appears to have failed: Missing signature`);return data}return data.slice(12)}async init(language,opts){await this.loadEntry();let index=this.findIndex(language);let lang_wasm=index.wasm?index.wasm:"unknown";let resources=[this.loadMeta(index.hash)];if(opts.load_wasm===true){resources.push(this.loadWasm(lang_wasm))}await Promise.all(resources);this.raw_ptr=this.backend.init_pagefind(new Uint8Array(this.searchMeta));if(Object.keys(this.mergeFilter)?.length){let filters2=this.stringifyFilters(this.mergeFilter);let ptr=await this.getPtr();this.raw_ptr=this.backend.add_synthetic_filter(ptr,filters2)}}async loadEntry(){try{let entry_response=await fetch(`${this.basePath}pagefind-entry.json?ts=${Date.now()}`);let entry_json=await entry_response.json();this.languages=entry_json.languages;if(entry_json.version!==this.version){if(this.primary){console.warn(["Pagefind JS version doesn't match the version in your search index.",`Pagefind JS: ${this.version}. Pagefind index: ${entry_json.version}`,"If you upgraded Pagefind recently, you likely have a cached pagefind.js file.","If you encounter any search errors, try clearing your cache."].join("\n"))}else{console.warn(["Merging a Pagefind index from a different version than the main Pagefind instance.",`Main Pagefind JS: ${this.version}. Merged index (${this.basePath}): ${entry_json.version}`,"If you encounter any search errors, make sure that both sites are running the same version of Pagefind."].join("\n"))}}}catch(e){console.error(`Failed to load Pagefind metadata:
-${e?.toString()}`);throw new Error("Failed to load Pagefind metadata")}}findIndex(language){if(this.languages){let index=this.languages[language];if(index)return index;index=this.languages[language.split("-")[0]];if(index)return index;let topLang=Object.values(this.languages).sort((a,b)=>b.page_count-a.page_count);if(topLang[0])return topLang[0]}throw new Error("Pagefind Error: No language indexes found.")}async loadMeta(index){try{let compressed_resp=await fetch(`${this.basePath}pagefind.${index}.pf_meta`);let compressed_meta=await compressed_resp.arrayBuffer();this.searchMeta=this.decompress(new Uint8Array(compressed_meta),"Pagefind metadata")}catch(e){console.error(`Failed to load the meta index:
-${e?.toString()}`)}}async loadWasm(language){try{const wasm_url=`${this.basePath}wasm.${language}.pagefind`;let compressed_resp=await fetch(wasm_url);let compressed_wasm=await compressed_resp.arrayBuffer();const final_wasm=this.decompress(new Uint8Array(compressed_wasm),"Pagefind WebAssembly");if(!final_wasm){throw new Error("No WASM after decompression")}this.wasm=await this.backend(final_wasm)}catch(e){console.error(`Failed to load the Pagefind WASM:
-${e?.toString()}`);throw new Error(`Failed to load the Pagefind WASM:
-${e?.toString()}`)}}async _loadGenericChunk(url,method){try{let compressed_resp=await fetch(url);let compressed_chunk=await compressed_resp.arrayBuffer();let chunk=this.decompress(new Uint8Array(compressed_chunk),url);let ptr=await this.getPtr();this.raw_ptr=this.backend[method](ptr,chunk)}catch(e){console.error(`Failed to load the index chunk ${url}:
-${e?.toString()}`)}}async loadChunk(hash){if(!this.loaded_chunks[hash]){const url=`${this.basePath}index/${hash}.pf_index`;this.loaded_chunks[hash]=this._loadGenericChunk(url,"load_index_chunk")}return await this.loaded_chunks[hash]}async loadFilterChunk(hash){if(!this.loaded_filters[hash]){const url=`${this.basePath}filter/${hash}.pf_filter`;this.loaded_filters[hash]=this._loadGenericChunk(url,"load_filter_chunk")}return await this.loaded_filters[hash]}async _loadFragment(hash){let compressed_resp=await fetch(`${this.basePath}fragment/${hash}.pf_fragment`);let compressed_fragment=await compressed_resp.arrayBuffer();let fragment=this.decompress(new Uint8Array(compressed_fragment),`Fragment ${hash}`);return JSON.parse(new TextDecoder().decode(fragment))}async loadFragment(hash,weighted_locations=[]){if(!this.loaded_fragments[hash]){this.loaded_fragments[hash]=this._loadFragment(hash)}let fragment=await this.loaded_fragments[hash];fragment.weighted_locations=weighted_locations;fragment.locations=weighted_locations.map((l)=>l.location);if(!fragment.raw_content){fragment.raw_content=fragment.content.replace(/</g,"&lt;").replace(/>/g,"&gt;");fragment.content=fragment.content.replace(/\u200B/g,"")}if(!fragment.raw_url){fragment.raw_url=fragment.url;fragment.url=this.fullUrl(fragment.raw_url)}const excerpt_start=calculate_excerpt_region(weighted_locations,this.excerptLength);fragment.excerpt=build_excerpt(fragment.raw_content,excerpt_start,this.excerptLength,fragment.locations);fragment.sub_results=calculate_sub_results(fragment,this.excerptLength);return fragment}fullUrl(raw){return`${this.baseUrl}/${raw}`.replace(/\/+/g,"/").replace(/^(https?:\/)/,"$1/")}async getPtr(){while(this.raw_ptr===null){await asyncSleep(50)}if(!this.raw_ptr){console.error("Pagefind: WASM Error (No pointer)");throw new Error("Pagefind: WASM Error (No pointer)")}return this.raw_ptr}parseFilters(str){let output={};if(!str)return output;for(const block of str.split("__PF_FILTER_DELIM__")){let[filter,values]=block.split(/:(.*)$/);output[filter]={};if(values){for(const valueBlock of values.split("__PF_VALUE_DELIM__")){if(valueBlock){let extract=valueBlock.match(/^(.*):(\d+)$/);if(extract){let[,value,count]=extract;output[filter][value]=parseInt(count)??count}}}}}return output}stringifyFilters(obj={}){return JSON.stringify(obj)}stringifySorts(obj={}){let sorts=Object.entries(obj);for(let[sort,direction]of sorts){if(sorts.length>1){console.warn(`Pagefind was provided multiple sort options in this search, but can only operate on one. Using the ${sort} sort.`)}if(direction!=="asc"&&direction!=="desc"){console.warn(`Pagefind was provided a sort with unknown direction ${direction}. Supported: [asc, desc]`)}return`${sort}:${direction}`}return``}async filters(){let ptr=await this.getPtr();let filters2=this.backend.request_all_filter_indexes(ptr);let filter_chunks=filters2.split(" ").filter((v)=>v).map((chunk)=>this.loadFilterChunk(chunk));await Promise.all([...filter_chunks]);ptr=await this.getPtr();let results=this.backend.filters(ptr);return this.parseFilters(results)}async preload(term,options2={}){options2.preload=true;await this.search(term,options2)}async search(term,options2={}){options2={verbose:false,filters:{},sort:{},...options2};const log=(str)=>{if(options2.verbose)console.log(str)};log(`Starting search on ${this.basePath}`);let start=Date.now();let ptr=await this.getPtr();let filter_only=term===null;term=term??"";let exact_search=/^\s*".+"\s*$/.test(term);if(exact_search){log(`Running an exact search`)}term=term.toLowerCase().trim().replace(/[\.`~!@#\$%\^&\*\(\)\{\}\[\]\\\|:;'",<>\/\?\-]/g,"").replace(/\s{2,}/g," ").trim();log(`Normalized search term to ${term}`);if(!term?.length&&!filter_only){return{results:[],unfilteredResultCount:0,filters:{},totalFilters:{},timings:{preload:Date.now()-start,search:Date.now()-start,total:Date.now()-start}}}let sort_list=this.stringifySorts(options2.sort);log(`Stringified sort to ${sort_list}`);const filter_list=this.stringifyFilters(options2.filters);log(`Stringified filters to ${filter_list}`);let index_resp=this.backend.request_indexes(ptr,term);let filter_resp=this.backend.request_filter_indexes(ptr,filter_list);let chunks=index_resp.split(" ").filter((v)=>v).map((chunk)=>this.loadChunk(chunk));let filter_chunks=filter_resp.split(" ").filter((v)=>v).map((chunk)=>this.loadFilterChunk(chunk));await Promise.all([...chunks,...filter_chunks]);log(`Loaded necessary chunks to run search`);if(options2.preload){log(`Preload \u2014 bailing out of search operation now.`);return null}ptr=await this.getPtr();let searchStart=Date.now();let result=this.backend.search(ptr,term,filter_list,sort_list,exact_search);log(`Got the raw search result: ${result}`);let[unfilteredResultCount,all_results,filters2,totalFilters]=result.split(/:([^:]*):(.*)__PF_UNFILTERED_DELIM__(.*)$/);let filterObj=this.parseFilters(filters2);let totalFilterObj=this.parseFilters(totalFilters);log(`Remaining filters: ${JSON.stringify(result)}`);let results=all_results.length?all_results.split(" "):[];let resultsInterface=results.map((result2)=>{let[hash,score,all_locations]=result2.split("@");log(`Processing result: 
-  hash:${hash}
-  score:${score}
-  locations:${all_locations}`);let weighted_locations=all_locations.length?all_locations.split(",").map((l)=>{let[weight,balanced_score,location]=l.split(">");return{weight:parseInt(weight)/24,balanced_score:parseFloat(balanced_score),location:parseInt(location)}}):[];let locations=weighted_locations.map((l)=>l.location);return{id:hash,score:parseFloat(score)*this.indexWeight,words:locations,data:async()=>await this.loadFragment(hash,weighted_locations)}});const searchTime=Date.now()-searchStart;const realTime=Date.now()-start;log(`Found ${results.length} result${results.length == 1 ? "" : "s"} for "${term}" in ${Date.now() - searchStart}ms (${Date.now() - start}ms realtime)`);return{results:resultsInterface,unfilteredResultCount:parseInt(unfilteredResultCount),filters:filterObj,totalFilters:totalFilterObj,timings:{preload:realTime-searchTime,search:searchTime,total:realTime}}}};var Pagefind=class{constructor(options2={}){this.backend=wasm_bindgen;this.primaryLanguage="unknown";this.searchID=0;this.primary=new PagefindInstance({...options2,primary:true});this.instances=[this.primary];this.init(options2?.language)}async options(options2){await this.primary.options(options2)}async init(overrideLanguage){if(document?.querySelector){const langCode=document.querySelector("html")?.getAttribute("lang")||"unknown";this.primaryLanguage=langCode.toLocaleLowerCase()}await this.primary.init(overrideLanguage?overrideLanguage:this.primaryLanguage,{load_wasm:true})}async mergeIndex(indexPath,options2={}){if(this.primary.basePath.startsWith(indexPath)){console.warn(`Skipping mergeIndex ${indexPath} that appears to be the same as the primary index (${this.primary.basePath})`);return}let newInstance=new PagefindInstance({primary:false,basePath:indexPath});this.instances.push(newInstance);while(this.primary.wasm===null){await asyncSleep(50)}await newInstance.init(options2.language||this.primaryLanguage,{load_wasm:false});delete options2["language"];await newInstance.options(options2)}mergeFilters(filters2){const merged={};for(const searchFilter of filters2){for(const[filterKey,values]of Object.entries(searchFilter)){if(!merged[filterKey]){merged[filterKey]=values;continue}else{const filter=merged[filterKey];for(const[valueKey,count]of Object.entries(values)){filter[valueKey]=(filter[valueKey]||0)+count}}}}return merged}async filters(){let filters2=await Promise.all(this.instances.map((i2)=>i2.filters()));return this.mergeFilters(filters2)}async preload(term,options2={}){await Promise.all(this.instances.map((i2)=>i2.preload(term,options2)))}async debouncedSearch(term,options2,debounceTimeoutMs){const thisSearchID=++this.searchID;this.preload(term,options2);await asyncSleep(debounceTimeoutMs);if(thisSearchID!==this.searchID){return null}const searchResult=await this.search(term,options2);if(thisSearchID!==this.searchID){return null}return searchResult}async search(term,options2={}){let search2=await Promise.all(this.instances.map((i2)=>i2.search(term,options2)));const filters2=this.mergeFilters(search2.map((s)=>s.filters));const totalFilters=this.mergeFilters(search2.map((s)=>s.totalFilters));const results=search2.map((s)=>s.results).flat().sort((a,b)=>b.score-a.score);const timings=search2.map((s)=>s.timings);const unfilteredResultCount=search2.reduce((sum,s)=>sum+s.unfilteredResultCount,0);return{results,unfilteredResultCount,filters:filters2,totalFilters,timings}}};var pagefind=void 0;var initial_options=void 0;var init_pagefind=()=>{if(!pagefind){pagefind=new Pagefind(initial_options??{})}};var options=async(new_options)=>{if(pagefind){await pagefind.options(new_options)}else{initial_options=new_options}};var init=async()=>{init_pagefind()};var mergeIndex=async(indexPath,options2)=>{init_pagefind();return await pagefind.mergeIndex(indexPath,options2)};var search=async(term,options2)=>{init_pagefind();return await pagefind.search(term,options2)};var debouncedSearch=async(term,options2,debounceTimeoutMs=300)=>{init_pagefind();return await pagefind.debouncedSearch(term,options2,debounceTimeoutMs)};var preload=async(term,options2)=>{init_pagefind();return await pagefind.preload(term,options2)};var filters=async()=>{init_pagefind();return await pagefind.filters()};export{debouncedSearch,filters,init,mergeIndex,options,preload,search}
\ No newline at end of file
diff --git a/src/routes/(landing-ui)/search.svelte b/src/routes/(landing-ui)/search.svelte
index fa1c6930..774d61c7 100644
--- a/src/routes/(landing-ui)/search.svelte
+++ b/src/routes/(landing-ui)/search.svelte
@@ -20,8 +20,21 @@
 	let pagefind: Pagefind | null = null;
 	let results: AwaitedResult[] = [];
 
+	async function getPagefind() {
+		const res = await fetch('/pagefind/pagefind.js');
+		if (!res.ok) {
+			return null;
+		}
+		const text = await res.text();
+		const blob = new Blob([text], { type: 'application/javascript' });
+		const url = URL.createObjectURL(blob);
+
+		return (await import(/* @vite-ignore */ url)) as Pagefind;
+	}
+
 	onMount(async () => {
-		pagefind = await import('../../pagefind/pagefind.js');
+		pagefind = await getPagefind();
+		if (!pagefind) return;
 
 		await pagefind.init();
 		const promisedResults = (await pagefind.search('')).results;
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index e609d692..74064a51 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -34,7 +34,7 @@
 			class="z-10 mt-[clamp(2rem,5vh,6rem)] max-w-2xl text-center font-display text-5xl sm:text-6xl
 		md:max-w-3xl md:text-7xl lg:max-w-5xl lg:text-[6rem] lg:leading-none"
 		>
-			<Logo class="inline-block h-12 md:-my-2 md:h-20 lg:-my-4 lg:h-32" /> <b>melt</b> away complexity
+			<Logo class="inline-block h-12 md:-my-2 md:h-20 lg:-my-4 lg:h-32" /> <b>melt</b> away<br /> complexity
 		</h1>
 
 		<p


Test patch:
diff --git a/src/tests/combobox/Combobox.spec.ts b/src/tests/combobox/Combobox.spec.ts
index d47e538a..708b57c9 100644
--- a/src/tests/combobox/Combobox.spec.ts
+++ b/src/tests/combobox/Combobox.spec.ts
@@ -140,6 +140,47 @@ describe('Combobox', () => {
 		expect(label.id).toBe(ids.label);
 	});
 
+	test("Doesn't close on outside click if defaultPrevented on `onOutsideClick` handler", async () => {
+		const user = userEvent.setup();
+		const { getByTestId } = render(ComboboxTest, { onOutsideClick: (e) => e.preventDefault() });
+		const input = getByTestId('input');
+
+		await user.click(input);
+		expect(getByTestId('menu')).toBeVisible();
+
+		const outsideClick = getByTestId('outside-click');
+		await user.click(outsideClick);
+		expect(getByTestId('menu')).toBeVisible();
+	});
+
+	test('Closes on escape by default', async () => {
+		const user = userEvent.setup();
+		const { getByTestId } = render(ComboboxTest);
+
+		const input = getByTestId('input');
+		const menu = getByTestId('menu');
+
+		await user.click(input);
+		expect(menu).toBeVisible();
+
+		await user.keyboard(kbd.ESCAPE);
+		expect(menu).not.toBeVisible();
+	});
+
+	test('Respects the `closeOnEscape` prop', async () => {
+		const user = userEvent.setup();
+		const { getByTestId } = render(ComboboxTest, { closeOnEscape: false });
+
+		const input = getByTestId('input');
+		const menu = getByTestId('menu');
+
+		await user.click(input);
+		expect(menu).toBeVisible();
+
+		await user.keyboard(kbd.ESCAPE);
+		expect(menu).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 50b79ce7..7fe4aa0f 100644
--- a/src/tests/combobox/ComboboxTest.svelte
+++ b/src/tests/combobox/ComboboxTest.svelte
@@ -17,6 +17,8 @@
 	export let multiple = false;
 	export let defaultValue: string | undefined = undefined;
 	export let ids: CreateComboboxProps<unknown>['ids'] = undefined;
+	export let onOutsideClick: CreateComboboxProps<unknown>['onOutsideClick'] = undefined;
+	export let closeOnEscape: CreateComboboxProps<unknown>['closeOnEscape'] = undefined;
 
 	const {
 		elements: { menu, input, option, label },
@@ -31,6 +33,8 @@
 				  }
 				: undefined,
 			ids,
+			onOutsideClick,
+			closeOnEscape,
 		})
 	);
 
@@ -60,4 +64,5 @@
 			{/each}
 		</div>
 	</ul>
+	<div data-testid="outside-click" />
 </main>
diff --git a/src/tests/context-menu/ContextMenu.spec.ts b/src/tests/context-menu/ContextMenu.spec.ts
index 2157e7dd..39b823b9 100644
--- a/src/tests/context-menu/ContextMenu.spec.ts
+++ b/src/tests/context-menu/ContextMenu.spec.ts
@@ -205,4 +205,16 @@ describe('Context Menu', () => {
 		const submenu = getByTestId('submenu');
 		expect(submenu.id).toBe(subIds.menu);
 	});
+
+	test("Doesn't close on outsideClick if defaultPrevented in onOutsideClick", async () => {
+		const { user, menu, getByTestId } = await open({
+			onOutsideClick: (e) => {
+				e.preventDefault();
+			},
+		});
+		expect(menu).toBeVisible();
+		const outsideClick = getByTestId('outside-click');
+		await user.click(outsideClick);
+		expect(menu).toBeVisible();
+	});
 });
diff --git a/src/tests/date-picker/DatePicker.spec.ts b/src/tests/date-picker/DatePicker.spec.ts
index 30570601..cdbe3698 100644
--- a/src/tests/date-picker/DatePicker.spec.ts
+++ b/src/tests/date-picker/DatePicker.spec.ts
@@ -545,4 +545,22 @@ describe('DatePicker', () => {
 		await user.click(label);
 		expect(monthSegment).toHaveFocus();
 	});
+
+	test("doesn't close on outside click if preventDefault called in `onOutsideClick`", async () => {
+		const { getByTestId, trigger, user } = setup({
+			onOutsideClick: (e) => {
+				e.preventDefault();
+			},
+		});
+		const content = getByTestId('content');
+
+		expect(content).not.toBeVisible();
+		await user.click(trigger);
+		await waitFor(() => expect(content).toBeVisible());
+		await user.click(content);
+		await waitFor(() => expect(content).toBeVisible());
+		const outside = getByTestId('inside-value');
+		await user.click(outside);
+		await waitFor(() => expect(content).toBeVisible());
+	});
 });
diff --git a/src/tests/date-picker/DatePickerTest.svelte b/src/tests/date-picker/DatePickerTest.svelte
index 4e30dd2d..fa7c38f3 100644
--- a/src/tests/date-picker/DatePickerTest.svelte
+++ b/src/tests/date-picker/DatePickerTest.svelte
@@ -2,7 +2,6 @@
 	import { createDatePicker, type CreateDatePickerProps } from '$lib/builders';
 	import { ChevronRight, ChevronLeft, Calendar } from 'lucide-svelte';
 	import { melt } from '$lib';
-	import { fade } from 'svelte/transition';
 	import { removeUndefined } from '../utils';
 
 	export let value: CreateDatePickerProps['value'] = undefined;
@@ -28,6 +27,7 @@
 	export let placeholder: CreateDatePickerProps['placeholder'] = undefined;
 	export let weekStartsOn: CreateDatePickerProps['weekStartsOn'] = undefined;
 	export let weekdayFormat: CreateDatePickerProps['weekdayFormat'] = undefined;
+	export let onOutsideClick: CreateDatePickerProps['onOutsideClick'] = undefined;
 
 	const {
 		elements: {
@@ -71,6 +71,7 @@
 			isDateDisabled,
 			weekdayFormat,
 			popoverIds,
+			onOutsideClick,
 		})
 	);
 
@@ -118,7 +119,6 @@
 	</div>
 	<div
 		class="z-10 w-80 rounded-[4px] bg-white p-3 shadow-sm"
-		transition:fade={{ duration: 100 }}
 		use:melt={$content}
 		data-testid="content"
 	>
diff --git a/src/tests/dialog/Dialog.spec.ts b/src/tests/dialog/Dialog.spec.ts
index aedfbb86..d34459ea 100644
--- a/src/tests/dialog/Dialog.spec.ts
+++ b/src/tests/dialog/Dialog.spec.ts
@@ -77,6 +77,25 @@ describe('Dialog', () => {
 		expect(content).not.toBeVisible();
 	});
 
+	it('Prevents closing on outside click if `defaultPrevented` in `onOutsideClick` callback', async () => {
+		const { getByTestId, user, trigger } = setup({
+			onOutsideClick: (e) => {
+				e.preventDefault();
+			},
+		});
+		const overlay = getByTestId('overlay');
+		const content = getByTestId('content');
+
+		expect(trigger).toBeVisible();
+		expect(content).not.toBeVisible();
+		await user.click(trigger);
+		expect(content).toBeVisible();
+		await sleep(100);
+		expect(overlay).toBeVisible();
+		await user.click(overlay);
+		expect(content).toBeVisible();
+	});
+
 	it('Portalled el attaches dialog to body', async () => {
 		const { getByTestId, user, trigger } = setup();
 		await user.click(trigger);
diff --git a/src/tests/dropdown-menu/DropdownMenu.spec.ts b/src/tests/dropdown-menu/DropdownMenu.spec.ts
index 28842689..70ea94ac 100644
--- a/src/tests/dropdown-menu/DropdownMenu.spec.ts
+++ b/src/tests/dropdown-menu/DropdownMenu.spec.ts
@@ -244,6 +244,20 @@ describe('Dropdown Menu (Default)', () => {
 		expect(submenu.id).toBe(subIds.menu);
 	});
 
+	test("Doesn't close on outsideClick if defaultPrevented in onOutsideClick", async () => {
+		const { user, trigger, getByTestId } = setup({
+			onOutsideClick: (e) => {
+				e.preventDefault();
+			},
+		});
+		const menu = getByTestId('menu');
+		await user.click(trigger);
+		expect(menu).toBeVisible();
+		const outsideClick = getByTestId('outside-click');
+		await user.click(outsideClick);
+		expect(menu).toBeVisible();
+	});
+
 	test.todo('Radio items');
 });
 
diff --git a/src/tests/dropdown-menu/DropdownMenuTest.svelte b/src/tests/dropdown-menu/DropdownMenuTest.svelte
index 538ea60f..4afc5b2f 100644
--- a/src/tests/dropdown-menu/DropdownMenuTest.svelte
+++ b/src/tests/dropdown-menu/DropdownMenuTest.svelte
@@ -18,13 +18,15 @@
 	const {
 		elements: { trigger, menu, item, separator, arrow },
 		builders: { createSubmenu, createMenuRadioGroup, createCheckboxItem },
-	} = createDropdownMenu({
-		loop,
-		closeFocus,
-		closeOnEscape,
-		closeOnOutsideClick,
-		...$$restProps,
-	});
+	} = createDropdownMenu(
+		removeUndefined({
+			loop,
+			closeFocus,
+			closeOnEscape,
+			closeOnOutsideClick,
+			...$$restProps,
+		})
+	);
 
 	const {
 		elements: { checkboxItem: settingsSyncCheckbox },
diff --git a/src/tests/link-preview/LinkPreview.spec.ts b/src/tests/link-preview/LinkPreview.spec.ts
index 6287d1ed..6f689bd4 100644
--- a/src/tests/link-preview/LinkPreview.spec.ts
+++ b/src/tests/link-preview/LinkPreview.spec.ts
@@ -74,6 +74,20 @@ describe('LinkPreview (Default)', () => {
 		await waitFor(() => expect(content).not.toBeVisible());
 	});
 
+	test.skip('Closes on outside click by default', async () => {
+		const { getByTestId, trigger, user } = setup();
+		const content = getByTestId('content');
+
+		expect(content).not.toBeVisible();
+		await user.hover(trigger);
+		await waitFor(() => expect(content).toBeVisible());
+		await user.click(content);
+		await waitFor(() => expect(content).toBeVisible());
+		const start = getByTestId('start');
+		await user.click(start);
+		await waitFor(() => expect(content).not.toBeVisible());
+	});
+
 	test('Custom ids are applied when provided', async () => {
 		const ids = {
 			content: 'id-content',
diff --git a/src/tests/popover/Popover.spec.ts b/src/tests/popover/Popover.spec.ts
index 4acb626c..0143c7ca 100644
--- a/src/tests/popover/Popover.spec.ts
+++ b/src/tests/popover/Popover.spec.ts
@@ -57,6 +57,23 @@ describe('Popover (Default)', () => {
 		await waitFor(() => expect(content).not.toBeVisible());
 	});
 
+	test('doesnt close when preventDefault called in `onOutsideClick`', async () => {
+		const { content, trigger, user, getByTestId } = setup({
+			onOutsideClick: (e) => {
+				e.preventDefault();
+			},
+		});
+
+		expect(content).not.toBeVisible();
+		await user.click(trigger);
+		await waitFor(() => expect(content).toBeVisible());
+		await user.click(content);
+		await waitFor(() => expect(content).toBeVisible());
+		const outside = getByTestId('outside');
+		await user.click(outside);
+		await waitFor(() => expect(content).toBeVisible());
+	});
+
 	test('Toggles open state on trigger click', async () => {
 		const { content, trigger, user } = setup();
 
diff --git a/src/tests/select/Select.spec.ts b/src/tests/select/Select.spec.ts
index 4155874d..d68dfc47 100644
--- a/src/tests/select/Select.spec.ts
+++ b/src/tests/select/Select.spec.ts
@@ -14,10 +14,10 @@ describe('Select', () => {
 	});
 
 	test('Opens/Closes when trigger is clicked', async () => {
+		const user = userEvent.setup();
 		const { getByTestId } = render(SelectTest);
 		const trigger = getByTestId('trigger');
 		const menu = getByTestId('menu');
-		const user = userEvent.setup();
 
 		expect(menu).not.toBeVisible();
 		await user.click(trigger);
@@ -27,11 +27,26 @@ describe('Select', () => {
 		expect(menu).not.toBeVisible();
 	});
 
-	test.each(OPEN_KEYS)('Opens when %s is pressed', async (key) => {
+	test('Closes on escape keydown', async () => {
+		const user = userEvent.setup();
 		const { getByTestId } = render(SelectTest);
 		const trigger = getByTestId('trigger');
 		const menu = getByTestId('menu');
+
+		expect(menu).not.toBeVisible();
+		await user.click(trigger);
+		expect(menu).toBeVisible();
+		expect(trigger).toHaveFocus();
+
+		await user.keyboard(kbd.ESCAPE);
+		expect(menu).not.toBeVisible();
+	});
+
+	test.each(OPEN_KEYS)('Opens when %s is pressed', async (key) => {
 		const user = userEvent.setup();
+		const { getByTestId } = render(SelectTest);
+		const trigger = getByTestId('trigger');
+		const menu = getByTestId('menu');
 
 		expect(menu).not.toBeVisible();
 		await act(() => trigger.focus());
@@ -40,10 +55,10 @@ describe('Select', () => {
 	});
 
 	test('Toggles when trigger is clicked', async () => {
+		const user = userEvent.setup();
 		const { getByTestId } = render(SelectTest);
 		const trigger = getByTestId('trigger');
 		const menu = getByTestId('menu');
-		const user = userEvent.setup();
 
 		expect(menu).not.toBeVisible();
 		await user.click(trigger);
@@ -54,10 +69,10 @@ describe('Select', () => {
 	});
 
 	test('Toggles when icon within trigger is clicked', async () => {
+		const user = userEvent.setup();
 		const { getByTestId } = render(SelectTest);
 		const icon = getByTestId('icon');
 		const menu = getByTestId('menu');
-		const user = userEvent.setup();
 
 		expect(menu).not.toBeVisible();
 		await user.click(icon);
@@ -68,10 +83,10 @@ describe('Select', () => {
 	});
 
 	test('Selects item when clicked', async () => {
+		const user = userEvent.setup();
 		const { getByTestId } = render(SelectTest);
 		const trigger = getByTestId('trigger');
 		const menu = getByTestId('menu');
-		const user = userEvent.setup();
 
 		expect(trigger).not.toHaveTextContent('Caramel');
 
@@ -98,10 +113,10 @@ describe('Select', () => {
 	});
 
 	test('Selects multiple items when `multiple` is true', async () => {
+		const user = userEvent.setup();
 		const { getByTestId } = render(SelectTest, { multiple: true });
 		const trigger = getByTestId('trigger');
 		const menu = getByTestId('menu');
-		const user = userEvent.setup();
 
 		expect(trigger).not.toHaveTextContent('Caramel');
 
@@ -156,10 +171,10 @@ describe('Select', () => {
 	});
 
 	test('Respects the `closeOnEscape` prop', async () => {
+		const user = userEvent.setup();
 		const { getByTestId } = render(SelectTest, { closeOnEscape: false });
 		const trigger = getByTestId('trigger');
 		const menu = getByTestId('menu');
-		const user = userEvent.setup();
 
 		expect(menu).not.toBeVisible();
 		await user.click(trigger);
@@ -170,10 +185,29 @@ describe('Select', () => {
 	});
 
 	test('Respects the `closeOnOutsideClick` prop', async () => {
+		const user = userEvent.setup();
 		const { getByTestId } = render(SelectTest, { closeOnOutsideClick: false });
 		const trigger = getByTestId('trigger');
 		const menu = getByTestId('menu');
+
+		expect(menu).not.toBeVisible();
+		await user.click(trigger);
+		expect(menu).toBeVisible();
+
+		const outside = getByTestId('outside');
+		await user.click(outside);
+		expect(menu).toBeVisible();
+	});
+
+	test("doesn't close when preventDefault called in `onOutsideClick`", async () => {
 		const user = userEvent.setup();
+		const { getByTestId } = render(SelectTest, {
+			onOutsideClick: (e) => {
+				e.preventDefault();
+			},
+		});
+		const trigger = getByTestId('trigger');
+		const menu = getByTestId('menu');
 
 		expect(menu).not.toBeVisible();
 		await user.click(trigger);
diff --git a/src/tests/select/SelectTest.svelte b/src/tests/select/SelectTest.svelte
index 984fe574..268a7508 100644
--- a/src/tests/select/SelectTest.svelte
+++ b/src/tests/select/SelectTest.svelte
@@ -9,6 +9,7 @@
 	export let closeOnEscape = true;
 	export let closeOnOutsideClick = true;
 	export let ids: CreateSelectProps['ids'] = undefined;
+	export let onOutsideClick: CreateSelectProps['onOutsideClick'] = undefined;
 	const {
 		elements: { trigger, menu, option, group, groupLabel, label },
 		states: { selected, selectedLabel },
@@ -26,6 +27,7 @@
 			closeOnEscape,
 			closeOnOutsideClick,
 			ids,
+			onOutsideClick,
 		})
 	);