feat(nuxt): add open option in navigateTo helper (#21333)

This commit is contained in:
Wilson Pinto 2023-06-07 21:27:00 +02:00 committed by GitHub
parent 05f3decfa9
commit c4e5ac83bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 109 additions and 3 deletions

View File

@ -18,6 +18,7 @@ interface NavigateToOptions {
replace?: boolean replace?: boolean
redirectCode?: number redirectCode?: number
external?: boolean 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. 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 ## Examples
### Navigating Within a Vue Component ### Navigating Within a Vue Component
@ -120,3 +173,20 @@ await navigateTo('https://nuxt.com', {
}) })
</script> </script>
``` ```
### Navigating using open()
```vue
<script setup>
// will open 'https://nuxt.com' in a new tab
await navigateTo('https://nuxt.com', {
open: {
target: '_blank',
windowFeatures: {
width: 500,
height: 500
}
}
})
</script>
```

View File

@ -83,10 +83,29 @@ const isProcessingMiddleware = () => {
return false return false
} }
// Conditional types, either one or other
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never }
type XOR<T, U> = (T | U) extends Object ? (Without<T, U> & U) | (Without<U, T> & 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 { export interface NavigateToOptions {
replace?: boolean replace?: boolean
redirectCode?: number, redirectCode?: number
external?: boolean external?: boolean
open?: OpenOptions
} }
export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: NavigateToOptions): Promise<void | NavigationFailure | false> | false | void | RouteLocationRaw => { export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: NavigateToOptions): Promise<void | NavigationFailure | false> | 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 || '/') 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 }) const isExternal = options?.external || hasProtocol(toPath, { acceptRelative: true })
if (isExternal && !options?.external) { if (isExternal && !options?.external) {
throw new Error('Navigating to external URL is not allowed by default. Use `navigateTo (url, { external: true })`.') throw new Error('Navigating to external URL is not allowed by default. Use `navigateTo (url, { external: true })`.')

View File

@ -34,7 +34,7 @@ describe.skipIf(isWindows || process.env.TEST_BUILDER === 'webpack' || process.e
it('default client bundle size', async () => { it('default client bundle size', async () => {
stats.client = await analyzeSizes('**/*.js', publicDir) 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(` expect(stats.client.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
[ [
"_nuxt/entry.js", "_nuxt/entry.js",
@ -45,7 +45,7 @@ describe.skipIf(isWindows || process.env.TEST_BUILDER === 'webpack' || process.e
it('default server bundle size', async () => { it('default server bundle size', async () => {
stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir) 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) const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2286k"') expect(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2286k"')