diff --git a/docs/3.api/3.utils/navigate-to.md b/docs/3.api/3.utils/navigate-to.md
index d922014bad..4249df62bd 100644
--- a/docs/3.api/3.utils/navigate-to.md
+++ b/docs/3.api/3.utils/navigate-to.md
@@ -18,6 +18,7 @@ interface NavigateToOptions {
replace?: boolean
redirectCode?: number
external?: boolean
+ open?: OpenOptions
}
```
@@ -69,6 +70,58 @@ An object accepting the following properties:
Allows navigating to an external URL when set to `true`. Otherwise, `navigateTo` will throw an error, as external navigation is not allowed by default.
+- `open` (optional)
+
+ **Type**: `OpenOptions`
+
+ Allows navigating to the URL using the [open()](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) method of the window. This option is only applicable on the client side and will be ignored on the server side.
+
+ An object accepting the following properties:
+
+ - `target`
+
+ **Type**: `string`
+
+ **Default**: `'_blank'`
+
+ A string, without whitespace, specifying the name of the browsing context the resource is being loaded into.
+
+ - `windowFeatures` (optional)
+
+ **Type**: `OpenWindowFeatures`
+
+ An object accepting the following properties:
+
+ - `popup` (optional)
+
+ **Type**: `boolean`
+
+ - `width` or `innerWidth` (optional)
+
+ **Type**: `number`
+
+ - `height` or `innerHeight` (optional)
+
+ **Type**: `number`
+
+ - `left` or `screenX` (optional)
+
+ **Type**: `number`
+
+ - `top` or `screenY` (optional)
+
+ **Type**: `number`
+
+ - `noopener` (optional)
+
+ **Type**: `boolean`
+
+ - `noreferrer` (optional)
+
+ **Type**: `boolean`
+
+ Refer to the [documentation](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) for more detailed information on the **windowFeatures** properties.
+
## Examples
### Navigating Within a Vue Component
@@ -120,3 +173,20 @@ await navigateTo('https://nuxt.com', {
})
```
+
+### Navigating using open()
+
+```vue
+
+```
diff --git a/packages/nuxt/src/app/composables/router.ts b/packages/nuxt/src/app/composables/router.ts
index 383433cce9..75bb4cf38d 100644
--- a/packages/nuxt/src/app/composables/router.ts
+++ b/packages/nuxt/src/app/composables/router.ts
@@ -83,10 +83,29 @@ const isProcessingMiddleware = () => {
return false
}
+// Conditional types, either one or other
+type Without = { [P in Exclude]?: never }
+type XOR = (T | U) extends Object ? (Without & U) | (Without & T) : T | U
+
+export type OpenWindowFeatures = {
+ popup?: boolean
+ noopener?: boolean
+ noreferrer?: boolean
+} & XOR<{width?: number}, {innerWidth?: number}>
+ & XOR<{height?: number}, {innerHeight?: number}>
+ & XOR<{left?: number}, {screenX?: number}>
+ & XOR<{top?: number}, {screenY?: number}>
+
+export type OpenOptions = {
+ target: '_blank' | '_parent' | '_self' | '_top' | (string & {})
+ windowFeatures?: OpenWindowFeatures
+}
+
export interface NavigateToOptions {
replace?: boolean
- redirectCode?: number,
+ redirectCode?: number
external?: boolean
+ open?: OpenOptions
}
export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: NavigateToOptions): Promise | false | void | RouteLocationRaw => {
@@ -95,6 +114,23 @@ export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: Na
}
const toPath = typeof to === 'string' ? to : ((to as RouteLocationPathRaw).path || '/')
+
+ // Early open handler
+ if (options?.open) {
+ if (process.client) {
+ const { target = '_blank', windowFeatures = {} } = options.open
+
+ const features = Object.entries(windowFeatures)
+ .filter(([_, value]) => value !== undefined)
+ .map(([feature, value]) => `${feature.toLowerCase()}=${value}`)
+ .join(', ')
+
+ open(toPath, target, features)
+ }
+
+ return Promise.resolve()
+ }
+
const isExternal = options?.external || hasProtocol(toPath, { acceptRelative: true })
if (isExternal && !options?.external) {
throw new Error('Navigating to external URL is not allowed by default. Use `navigateTo (url, { external: true })`.')
diff --git a/test/bundle.test.ts b/test/bundle.test.ts
index e49259d887..6b4c981105 100644
--- a/test/bundle.test.ts
+++ b/test/bundle.test.ts
@@ -34,7 +34,7 @@ describe.skipIf(isWindows || process.env.TEST_BUILDER === 'webpack' || process.e
it('default client bundle size', async () => {
stats.client = await analyzeSizes('**/*.js', publicDir)
- expect(roundToKilobytes(stats.client.totalBytes)).toMatchInlineSnapshot('"98.3k"')
+ expect(roundToKilobytes(stats.client.totalBytes)).toMatchInlineSnapshot('"98.5k"')
expect(stats.client.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
[
"_nuxt/entry.js",
@@ -45,7 +45,7 @@ describe.skipIf(isWindows || process.env.TEST_BUILDER === 'webpack' || process.e
it('default server bundle size', async () => {
stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
- expect(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"62.7k"')
+ expect(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"62.8k"')
const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2286k"')