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"')