@forgebook/nuxt 0.0.0-stage → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +121 -2
- package/dist/src/app/components.d.ts +119 -0
- package/dist/src/app/components.js +196 -0
- package/dist/src/app/data.d.ts +109 -0
- package/dist/src/app/data.js +155 -0
- package/dist/src/app/define.d.ts +30 -0
- package/dist/src/app/define.js +30 -0
- package/dist/src/app/errors.d.ts +43 -0
- package/dist/src/app/errors.js +41 -0
- package/dist/src/app/fetch.d.ts +63 -0
- package/dist/src/app/fetch.js +73 -0
- package/dist/src/app/head.d.ts +28 -0
- package/dist/src/app/head.js +23 -0
- package/dist/src/app/router.d.ts +69 -0
- package/dist/src/app/router.js +93 -0
- package/dist/src/app/state.d.ts +107 -0
- package/dist/src/app/state.js +137 -0
- package/dist/src/asyncDataRun.d.ts +39 -0
- package/dist/src/asyncDataRun.js +128 -0
- package/dist/src/nuxtError.d.ts +50 -0
- package/dist/src/nuxtError.js +65 -0
- package/dist/src/nuxtRenderer.d.ts +14 -0
- package/dist/src/nuxtRenderer.js +67 -0
- package/dist/src/nuxtStore.d.ts +144 -0
- package/dist/src/nuxtStore.js +196 -0
- package/dist/src/pageProject.d.ts +11 -0
- package/dist/src/pageProject.js +11 -0
- package/dist/src/parameters.d.ts +67 -0
- package/dist/src/parameters.js +89 -0
- package/dist/src/project.d.ts +49 -0
- package/dist/src/project.js +7 -0
- package/dist/src/renderer.d.ts +9 -0
- package/dist/src/renderer.js +10 -0
- package/dist/src/stories.d.ts +54 -0
- package/dist/src/stories.js +11 -0
- package/dist/src/storyCode.d.ts +3 -0
- package/dist/src/storyCode.js +9 -0
- package/dist/src/storyFetch.d.ts +74 -0
- package/dist/src/storyFetch.js +146 -0
- package/dist/src/storyNuxt.d.ts +48 -0
- package/dist/src/storyNuxt.js +26 -0
- package/dist/src/vite/componentNames.d.ts +36 -0
- package/dist/src/vite/componentNames.js +72 -0
- package/dist/src/vite/extractDocs.d.ts +16 -0
- package/dist/src/vite/extractDocs.js +22 -0
- package/dist/src/vite/nuxt.d.ts +13 -0
- package/dist/src/vite/nuxt.js +36 -0
- package/dist/src/vite/nuxtConfig.d.ts +85 -0
- package/dist/src/vite/nuxtConfig.js +261 -0
- package/dist/src/vite/nuxtPlugin.d.ts +51 -0
- package/dist/src/vite/nuxtPlugin.js +466 -0
- package/dist/src/vite/pageRoutes.d.ts +23 -0
- package/dist/src/vite/pageRoutes.js +88 -0
- package/dist/src/vite/placeKeys.d.ts +10 -0
- package/dist/src/vite/placeKeys.js +117 -0
- package/dist/src/vite/standIns.d.ts +26 -0
- package/dist/src/vite/standIns.js +96 -0
- package/package.json +118 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ensiria
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,122 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @forgebook/nuxt
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Nuxt for [Forgebook](https://www.npmjs.com/package/forgebook): a Nuxt project's components drawn outside Nuxt, with what
|
|
4
|
+
they use of it. Their auto-imports, the `~` and `@` aliases, `#imports`, `#app` and `#components`, Nuxt's router
|
|
5
|
+
through `useRoute`, `navigateTo` and `NuxtLink`, `useFetch` and `useAsyncData`, `useState`, `useCookie`,
|
|
6
|
+
`useRuntimeConfig` and `useAppConfig`. It is [`@forgebook/vue`](https://www.npmjs.com/package/@forgebook/vue) with a
|
|
7
|
+
Vite plugin and a story's own Nuxt.
|
|
8
|
+
|
|
9
|
+
**Requirements:** Nuxt 3 or 4 · Vue 3.5 · vue-router 4.5 or 5 (Nuxt's) · `forgebook` · Vite 8 · React in the project, for
|
|
10
|
+
the catalogue's own interface (the frames that draw your components load none) ·
|
|
11
|
+
[MIT licence](https://github.com/ensiria/forgebook/blob/develop/packages/nuxt/LICENSE)
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install --save-dev forgebook @forgebook/nuxt react react-dom
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The catalogue's configuration, in the Nuxt project's folder, names it as its framework:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// forgebook.config.ts
|
|
21
|
+
import { nuxt } from "@forgebook/nuxt/vite";
|
|
22
|
+
import { defineConfig } from "forgebook";
|
|
23
|
+
|
|
24
|
+
export default defineConfig({
|
|
25
|
+
framework: nuxt(),
|
|
26
|
+
title: "My components",
|
|
27
|
+
stories: ["app/components/**/*.stories.ts"]
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
and the stories take their types from it. A story says where its component is and what Nuxt would have given it, under
|
|
32
|
+
`parameters.nuxt`:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import type { Meta, Story } from "@forgebook/nuxt";
|
|
36
|
+
|
|
37
|
+
import BlogPost from "./BlogPost.vue"; // `useRoute()`, `useAsyncData(`post-${slug}`, …)`, `<BaseBadge>`
|
|
38
|
+
|
|
39
|
+
export default {
|
|
40
|
+
title: "Blog/BlogPost",
|
|
41
|
+
component: BlogPost,
|
|
42
|
+
parameters: { nuxt: { route: { name: "blog-slug", params: { slug: "hello" } } } }
|
|
43
|
+
} satisfies Meta<typeof BlogPost>;
|
|
44
|
+
|
|
45
|
+
// what `useAsyncData` returns for its key, without calling its handler
|
|
46
|
+
export const Published: Story<typeof BlogPost> = {
|
|
47
|
+
parameters: {
|
|
48
|
+
nuxt: { route: "/blog/hello", data: { "post-hello": { title: "Hello", status: "published" } } }
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
| `parameters.nuxt` | What it sets |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `route` | Where the story starts: a path (`/blog/hello?tab=1`), or a location by path (`{ path, query, hash }`) or by a page's name (`{ name: "blog-slug", params: { slug: "hello" } }`); `/` by default |
|
|
56
|
+
| `data` | What `useAsyncData` and `useFetch` return, by key, without calling their handler. `useFetch`'s key is the request as it is made, unless it is given one: its URL with its query (`/api/products?page=2`), the method before it when not GET, the body after it (`POST /api/cart {"id":1}`) |
|
|
57
|
+
| `state` | What `useState` starts with, by key |
|
|
58
|
+
| `cookies` | What `useCookie` reads, by name |
|
|
59
|
+
| `runtimeConfig.public` | The runtime config's public values, over the project's |
|
|
60
|
+
| `appConfig` | The app config, over the project's |
|
|
61
|
+
|
|
62
|
+
A story's `nuxt` replaces the meta's whole, as every parameter does: a story that adds `data` writes its `route` too.
|
|
63
|
+
Without `data` for a key, the handler runs, and a request goes out: the catalogue's
|
|
64
|
+
[MSW handlers](https://github.com/ensiria/forgebook/blob/develop/packages/forgebook/README.md) answer it. A play reads
|
|
65
|
+
where the story went, and what it called, with `storyNuxt()`:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { storyNuxt } from "@forgebook/nuxt/story";
|
|
69
|
+
|
|
70
|
+
export const GoesToAbout: Story<typeof SiteNav> = {
|
|
71
|
+
play: async ({ canvas, userEvent }) => {
|
|
72
|
+
await userEvent.click(canvas.getByRole("link", { name: "About" }));
|
|
73
|
+
await canvas.findByText("On /about");
|
|
74
|
+
if (storyNuxt().route.path !== "/about") throw new Error("Not on /about");
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
| Import | What it is |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `@forgebook/nuxt` | `Meta`, `Story`, `Preview`: Vue's story types, with `parameters.nuxt` |
|
|
82
|
+
| `@forgebook/nuxt/vite` | `nuxt()`, the framework adapter of `forgebook.config.ts`: Vue's plugin and this package's |
|
|
83
|
+
| `@forgebook/nuxt/story` | `storyNuxt()`: the story's route, its calls to Nuxt, its state, cookies and data |
|
|
84
|
+
| `@forgebook/nuxt/renderer` | `nuxtRenderer`, for the generated server tests (`storyServerTests(source, { renderer: nuxtRenderer })`) |
|
|
85
|
+
|
|
86
|
+
`@forgebook/nuxt/app/*`, `@forgebook/nuxt/project` and `@forgebook/nuxt/code` are internal: the code the plugin
|
|
87
|
+
writes and the catalogue's pages import them, so they may change in any release. A component imports Nuxt's names as it
|
|
88
|
+
does in the app (auto-imported, or from `#imports`).
|
|
89
|
+
|
|
90
|
+
## What a component gets
|
|
91
|
+
|
|
92
|
+
The plugin reads `nuxt.config` (with `defineNuxtConfig` given as Nuxt gives it) and the project's folders, and does what
|
|
93
|
+
Nuxt's build does for a component's code:
|
|
94
|
+
|
|
95
|
+
- **Auto-imports**: Vue's API, the files of `composables/`, `utils/` and `shared/utils/` (and `imports.dirs`), and
|
|
96
|
+
Nuxt's composables, through unimport, the library Nuxt uses. `#imports` and `#app` export them too.
|
|
97
|
+
- **Components**: the files of `components/` (or the folders `components` names), named as Nuxt names them
|
|
98
|
+
(`components/base/Button.vue` is `BaseButton`), their `Lazy` forms, and Nuxt's own (`NuxtLink`, `ClientOnly`,
|
|
99
|
+
`DevOnly`, `NuxtLayout`, `NuxtErrorBoundary`, and `NuxtImg`, drawn as a plain image). `#components` exports them.
|
|
100
|
+
- **Aliases**: `~` and `@` (the sources' folder, `app/` in Nuxt 4's layout), `~~` and `@@` (the project's), `#shared`,
|
|
101
|
+
`assets`, `public` and the config's `alias`.
|
|
102
|
+
- **The router**: vue-router's own, on a memory history, with a route for each file of `pages/` (named as Nuxt names
|
|
103
|
+
them) and one for any other path. A link moves it, `useRoute` follows, and the frame's address never changes. Each
|
|
104
|
+
move and each `navigateTo` is listed among the story's events; a link out of the app is recorded, not followed.
|
|
105
|
+
- **The config**: `useRuntimeConfig()` returns the runtime config's `public` part, its `NUXT_PUBLIC_` variables over it,
|
|
106
|
+
and `app`; its private keys never reach the page. `useAppConfig()` returns `nuxt.config`'s `appConfig` and
|
|
107
|
+
`app.config.ts`, merged. The style sheets of `css` load in every frame. `import.meta.client` is `true`.
|
|
108
|
+
|
|
109
|
+
What Nuxt does on its server has no server to run on: `useRequestHeaders` returns none, `useHead` and `useSeoMeta`
|
|
110
|
+
leave the frame's head alone, `definePageMeta` and route middleware do nothing, and `showError` keeps the error for
|
|
111
|
+
`useError` instead of drawing Nuxt's error page. The generated server tests run no `useAsyncData` handler: they draw
|
|
112
|
+
the story's `data`, or nothing.
|
|
113
|
+
|
|
114
|
+
Nuxt's own build is not run, so the work of a Nuxt module or plugin is not done: a component that needs what one
|
|
115
|
+
provides (a Pinia store, i18n) gets it from a decorator, and a module's components and composables are not
|
|
116
|
+
auto-imported. `#build/…` modules are Nuxt's build's own and fail by name. Nuxt layers (`extends`) and
|
|
117
|
+
`definePageMeta`'s `path` and `name` are not read. A Nuxt function this package does not stand in for is not given: a
|
|
118
|
+
file that uses one fails on it by name.
|
|
119
|
+
|
|
120
|
+
`forgebook init --from storybook` reads a project set up with `@storybook-vue/nuxt` (what Nuxt's Storybook module
|
|
121
|
+
writes), and says what a story sets differently. The rest of what Forgebook does is
|
|
122
|
+
[Forgebook's README](https://github.com/ensiria/forgebook/blob/develop/packages/forgebook/README.md).
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nuxt's built-in components in a catalogue: `NuxtLink` on vue-router's `RouterLink`, `ClientOnly`, `DevOnly`,
|
|
3
|
+
* `NuxtLayout`, `NuxtErrorBoundary`, and those with nothing to draw in a story (`NuxtPage`, `NuxtLoadingIndicator`,
|
|
4
|
+
* `NuxtRouteAnnouncer`). `NuxtImg` and `NuxtPicture`, which `@nuxt/image` adds, draw the image as it is, since there is
|
|
5
|
+
* no image server.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* A link inside the app moves the story's router, as Nuxt's does. A link outside it (another origin, `external`, a
|
|
9
|
+
* target other than `_self`) is drawn as Nuxt draws it, and its click is recorded instead of followed, so the story's
|
|
10
|
+
* frame stays where it is.
|
|
11
|
+
*
|
|
12
|
+
* Internal: the code the plugin writes imports it (`#imports`, `#app`, `#components`); a story or a component imports
|
|
13
|
+
* Nuxt's names as in the app, never this module, whose exports may change in any release.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import { type Component } from "vue";
|
|
18
|
+
import { type RouteLocationRaw } from "vue-router";
|
|
19
|
+
/** What `NuxtLink` takes. */
|
|
20
|
+
export type NuxtLinkProps = {
|
|
21
|
+
/** Where it goes: a path, a location, or an address outside the app. */
|
|
22
|
+
readonly to?: RouteLocationRaw;
|
|
23
|
+
/** `to`'s other name. */
|
|
24
|
+
readonly href?: RouteLocationRaw;
|
|
25
|
+
/** Whether the address is outside the app. */
|
|
26
|
+
readonly external?: boolean;
|
|
27
|
+
/** The anchor's target. */
|
|
28
|
+
readonly target?: string;
|
|
29
|
+
/** The anchor's `rel`; `noopener noreferrer` for an external link by default. */
|
|
30
|
+
readonly rel?: string;
|
|
31
|
+
/** Whether an external link gets no `rel`. */
|
|
32
|
+
readonly noRel?: boolean;
|
|
33
|
+
/** Whether it replaces the history's entry. */
|
|
34
|
+
readonly replace?: boolean;
|
|
35
|
+
/** The class of a link to the current route or one of its parents. */
|
|
36
|
+
readonly activeClass?: string;
|
|
37
|
+
/** The class of a link to the current route. */
|
|
38
|
+
readonly exactActiveClass?: string;
|
|
39
|
+
/** The `aria-current` of a link to the current route. */
|
|
40
|
+
readonly ariaCurrentValue?: "page" | "step" | "location" | "date" | "time" | "true" | "false";
|
|
41
|
+
/** Whether the default slot draws the link itself. */
|
|
42
|
+
readonly custom?: boolean;
|
|
43
|
+
/** Whether it prefetches: no effect in a story. */
|
|
44
|
+
readonly prefetch?: boolean;
|
|
45
|
+
/** Whether it does not prefetch: no effect in a story. */
|
|
46
|
+
readonly noPrefetch?: boolean;
|
|
47
|
+
};
|
|
48
|
+
/** A link, inside the app or outside it, as Nuxt's `NuxtLink`. */
|
|
49
|
+
export declare const NuxtLink: import("vue").DefineSetupFnComponent<NuxtLinkProps, import("vue").EmitsOptions, {}, NuxtLinkProps & ({
|
|
50
|
+
[x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined;
|
|
51
|
+
} | {
|
|
52
|
+
[x: `on${Capitalize<string>}`]: ((...args: never) => any) | undefined;
|
|
53
|
+
}), import("vue").PublicProps>;
|
|
54
|
+
/** What `ClientOnly` takes. */
|
|
55
|
+
export type ClientOnlyProps = {
|
|
56
|
+
/** The text drawn before it is mounted. */
|
|
57
|
+
readonly fallback?: string;
|
|
58
|
+
/** The element the fallback text is drawn in; `span` by default. */
|
|
59
|
+
readonly fallbackTag?: string;
|
|
60
|
+
/** `fallback`'s other name. */
|
|
61
|
+
readonly placeholder?: string;
|
|
62
|
+
/** `fallbackTag`'s other name. */
|
|
63
|
+
readonly placeholderTag?: string;
|
|
64
|
+
};
|
|
65
|
+
/** Draws its content once mounted in the page, and its fallback before (on the server), as Nuxt's. */
|
|
66
|
+
export declare const ClientOnly: import("vue").DefineSetupFnComponent<ClientOnlyProps, import("vue").EmitsOptions, {}, ClientOnlyProps & ({
|
|
67
|
+
[x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined;
|
|
68
|
+
} | {
|
|
69
|
+
[x: `on${Capitalize<string>}`]: ((...args: never) => any) | undefined;
|
|
70
|
+
}), import("vue").PublicProps>;
|
|
71
|
+
/** Draws its content in development, which a catalogue is. */
|
|
72
|
+
export declare const DevOnly: import("vue").DefineSetupFnComponent<Record<string, never>, import("vue").EmitsOptions, {}, Record<string, never> & ({
|
|
73
|
+
[x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined;
|
|
74
|
+
} | {
|
|
75
|
+
[x: `on${Capitalize<string>}`]: ((...args: never) => any) | undefined;
|
|
76
|
+
}), import("vue").PublicProps>;
|
|
77
|
+
/** A layout: a story has none, so its content is drawn as it is. */
|
|
78
|
+
export declare const NuxtLayout: import("vue").DefineSetupFnComponent<{
|
|
79
|
+
readonly name?: string;
|
|
80
|
+
readonly fallback?: string;
|
|
81
|
+
}, import("vue").EmitsOptions, {}, {
|
|
82
|
+
readonly name?: string;
|
|
83
|
+
readonly fallback?: string;
|
|
84
|
+
} & ({
|
|
85
|
+
[x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined;
|
|
86
|
+
} | {
|
|
87
|
+
[x: `on${Capitalize<string>}`]: ((...args: never) => any) | undefined;
|
|
88
|
+
}), import("vue").PublicProps>;
|
|
89
|
+
/** Catches what its content throws, and draws its `error` slot instead, with what clears it. */
|
|
90
|
+
export declare const NuxtErrorBoundary: import("vue").DefineSetupFnComponent<Record<string, never>, "error"[], {}, Record<string, never> & {
|
|
91
|
+
onError?: (...args: any[]) => any;
|
|
92
|
+
}, import("vue").PublicProps>;
|
|
93
|
+
/** The page of the route: a story draws its own component, not a page. */
|
|
94
|
+
export declare const NuxtPage: Component;
|
|
95
|
+
/** The bar shown while a page loads: none loads in a story. */
|
|
96
|
+
export declare const NuxtLoadingIndicator: Component;
|
|
97
|
+
/** What tells a screen reader the page changed: no page changes in a story. */
|
|
98
|
+
export declare const NuxtRouteAnnouncer: Component;
|
|
99
|
+
/** What `NuxtImg` takes: the image's attributes. */
|
|
100
|
+
export type NuxtImgProps = {
|
|
101
|
+
/** The image's address. */
|
|
102
|
+
readonly src?: string;
|
|
103
|
+
/** Its text alternative. */
|
|
104
|
+
readonly alt?: string;
|
|
105
|
+
/** Its width. */
|
|
106
|
+
readonly width?: string | number;
|
|
107
|
+
/** Its height. */
|
|
108
|
+
readonly height?: string | number;
|
|
109
|
+
/** How it loads. */
|
|
110
|
+
readonly loading?: "lazy" | "eager";
|
|
111
|
+
/** Its sizes. */
|
|
112
|
+
readonly sizes?: string;
|
|
113
|
+
};
|
|
114
|
+
/** `@nuxt/image`'s image, drawn as it is: there is no image server to resize it. */
|
|
115
|
+
export declare const NuxtImg: import("vue").DefineSetupFnComponent<NuxtImgProps, {}, {}, NuxtImgProps & {}, import("vue").PublicProps>;
|
|
116
|
+
/** `@nuxt/image`'s picture, drawn as one image: there is no image server to give the other formats. */
|
|
117
|
+
export declare const NuxtPicture: import("vue").DefineSetupFnComponent<NuxtImgProps, {}, {}, NuxtImgProps & {}, import("vue").PublicProps>;
|
|
118
|
+
/** The components Nuxt registers itself, by name: what a story's template finds without importing them. */
|
|
119
|
+
export declare const BUILT_IN_COMPONENTS: Readonly<Record<string, Component>>;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nuxt's built-in components in a catalogue: `NuxtLink` on vue-router's `RouterLink`, `ClientOnly`, `DevOnly`,
|
|
3
|
+
* `NuxtLayout`, `NuxtErrorBoundary`, and those with nothing to draw in a story (`NuxtPage`, `NuxtLoadingIndicator`,
|
|
4
|
+
* `NuxtRouteAnnouncer`). `NuxtImg` and `NuxtPicture`, which `@nuxt/image` adds, draw the image as it is, since there is
|
|
5
|
+
* no image server.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* A link inside the app moves the story's router, as Nuxt's does. A link outside it (another origin, `external`, a
|
|
9
|
+
* target other than `_self`) is drawn as Nuxt draws it, and its click is recorded instead of followed, so the story's
|
|
10
|
+
* frame stays where it is.
|
|
11
|
+
*
|
|
12
|
+
* Internal: the code the plugin writes imports it (`#imports`, `#app`, `#components`); a story or a component imports
|
|
13
|
+
* Nuxt's names as in the app, never this module, whose exports may change in any release.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import { defineComponent, h, onErrorCaptured, onMounted, ref } from "vue";
|
|
18
|
+
import { RouterLink } from "vue-router";
|
|
19
|
+
import { useNuxtStore } from "../nuxtStore.js";
|
|
20
|
+
/** Whether an address is outside the app: another origin, or a protocol other than the web's. */
|
|
21
|
+
const isExternalAddress = (to) => typeof to === "string" && (/^[a-z][a-z\d+.-]*:/i.test(to) || to.startsWith("//"));
|
|
22
|
+
/**
|
|
23
|
+
* Whether a link goes outside the app: `external`, another origin, or a target other than `_self`.
|
|
24
|
+
*
|
|
25
|
+
* @param props - the link's props.
|
|
26
|
+
* @param to - where it goes.
|
|
27
|
+
*/
|
|
28
|
+
const isLinkExternal = (props, to) => props.external === true ||
|
|
29
|
+
isExternalAddress(to) ||
|
|
30
|
+
(props.target !== undefined && props.target !== "_self");
|
|
31
|
+
/**
|
|
32
|
+
* The props `RouterLink` gets from `NuxtLink`'s.
|
|
33
|
+
*
|
|
34
|
+
* @param props - the link's props.
|
|
35
|
+
* @param to - where it goes.
|
|
36
|
+
*/
|
|
37
|
+
const routerLinkProps = (props, to) => ({
|
|
38
|
+
to,
|
|
39
|
+
replace: props.replace === true,
|
|
40
|
+
custom: props.custom === true,
|
|
41
|
+
...(props.activeClass === undefined ? {} : { activeClass: props.activeClass }),
|
|
42
|
+
...(props.exactActiveClass === undefined ? {} : { exactActiveClass: props.exactActiveClass }),
|
|
43
|
+
...(props.ariaCurrentValue === undefined ? {} : { ariaCurrentValue: props.ariaCurrentValue })
|
|
44
|
+
});
|
|
45
|
+
/**
|
|
46
|
+
* Whether a click would open a link in another tab or window: a modifier key held, or a button other than the first.
|
|
47
|
+
*
|
|
48
|
+
* @param event - the click.
|
|
49
|
+
*/
|
|
50
|
+
const isOtherWindowClick = (event) => event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0;
|
|
51
|
+
/**
|
|
52
|
+
* What a link does with a click the story's frame must not follow: one that would open another tab (for a link inside
|
|
53
|
+
* the app, the catalogue's own page at that path), and any click on a link out of the app. The click is recorded among
|
|
54
|
+
* the story's events instead.
|
|
55
|
+
*
|
|
56
|
+
* @param href - where the link goes.
|
|
57
|
+
* @param isExternal - whether it goes out of the app, so even a plain click stays.
|
|
58
|
+
* @param record - records a call.
|
|
59
|
+
*/
|
|
60
|
+
const keptClicks = (href, isExternal, record) => {
|
|
61
|
+
const keep = (event) => {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
record("navigateTo", [href, isOtherWindowClick(event) ? { open: true } : { external: true }]);
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
onClick: (event) => {
|
|
67
|
+
if (isExternal || isOtherWindowClick(event))
|
|
68
|
+
keep(event);
|
|
69
|
+
},
|
|
70
|
+
onAuxclick: keep
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* A link outside the app, drawn as Nuxt draws it, its click recorded instead of followed.
|
|
75
|
+
*
|
|
76
|
+
* @param props - the link's props.
|
|
77
|
+
* @param href - where it goes.
|
|
78
|
+
* @param slots - its slots.
|
|
79
|
+
* @param record - records a call.
|
|
80
|
+
*/
|
|
81
|
+
const externalLink = (props, href, slots, record) => {
|
|
82
|
+
const rel = props.noRel === true ? undefined : (props.rel ?? "noopener noreferrer");
|
|
83
|
+
const clicks = keptClicks(href, true, record);
|
|
84
|
+
if (props.custom === true)
|
|
85
|
+
return h("span", { style: "display: contents" }, slots["default"]?.({
|
|
86
|
+
href,
|
|
87
|
+
navigate: clicks.onClick,
|
|
88
|
+
rel,
|
|
89
|
+
target: props.target,
|
|
90
|
+
isExternal: true,
|
|
91
|
+
isActive: false
|
|
92
|
+
}));
|
|
93
|
+
return h("a", { href, rel, target: props.target, ...clicks }, slots["default"]?.());
|
|
94
|
+
};
|
|
95
|
+
/** A link, inside the app or outside it, as Nuxt's `NuxtLink`. */
|
|
96
|
+
export const NuxtLink = defineComponent((props, { slots }) => {
|
|
97
|
+
const store = useNuxtStore();
|
|
98
|
+
return () => {
|
|
99
|
+
const to = props.to ?? props.href ?? "";
|
|
100
|
+
const isExternal = isLinkExternal(props, to);
|
|
101
|
+
const href = typeof to === "string" && isExternal ? to : store.router.resolve(to).href;
|
|
102
|
+
if (isExternal)
|
|
103
|
+
return externalLink(props, href, slots, store.record);
|
|
104
|
+
// a click RouterLink leaves to the browser (another tab) is kept: it would open the catalogue at that path
|
|
105
|
+
const clicks = props.custom === true ? {} : keptClicks(href, false, store.record);
|
|
106
|
+
return h(RouterLink, { ...routerLinkProps(props, to), ...clicks }, slots);
|
|
107
|
+
};
|
|
108
|
+
}, {
|
|
109
|
+
name: "NuxtLink",
|
|
110
|
+
props: {
|
|
111
|
+
to: { type: null, required: false },
|
|
112
|
+
href: { type: null, required: false },
|
|
113
|
+
external: { type: Boolean, default: false },
|
|
114
|
+
target: { type: String, required: false },
|
|
115
|
+
rel: { type: String, required: false },
|
|
116
|
+
noRel: { type: Boolean, default: false },
|
|
117
|
+
replace: { type: Boolean, default: false },
|
|
118
|
+
activeClass: { type: String, required: false },
|
|
119
|
+
exactActiveClass: { type: String, required: false },
|
|
120
|
+
ariaCurrentValue: { type: null, required: false },
|
|
121
|
+
custom: { type: Boolean, default: false },
|
|
122
|
+
prefetch: { type: Boolean, default: undefined },
|
|
123
|
+
noPrefetch: { type: Boolean, default: false }
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
/** Draws its content once mounted in the page, and its fallback before (on the server), as Nuxt's. */
|
|
127
|
+
export const ClientOnly = defineComponent((props, { slots }) => {
|
|
128
|
+
const isMounted = ref(false);
|
|
129
|
+
onMounted(() => {
|
|
130
|
+
isMounted.value = true;
|
|
131
|
+
});
|
|
132
|
+
return () => {
|
|
133
|
+
if (isMounted.value)
|
|
134
|
+
return slots["default"]?.() ?? null;
|
|
135
|
+
const fallback = slots["fallback"] ?? slots["placeholder"];
|
|
136
|
+
if (fallback !== undefined)
|
|
137
|
+
return fallback();
|
|
138
|
+
const text = props.fallback ?? props.placeholder;
|
|
139
|
+
return text === undefined ? null : h(props.fallbackTag ?? props.placeholderTag ?? "span", text);
|
|
140
|
+
};
|
|
141
|
+
}, { name: "ClientOnly", props: ["fallback", "fallbackTag", "placeholder", "placeholderTag"] });
|
|
142
|
+
/** Draws its content in development, which a catalogue is. */
|
|
143
|
+
export const DevOnly = defineComponent((_props, { slots }) => () => slots["default"]?.() ?? null, { name: "DevOnly" });
|
|
144
|
+
/** A layout: a story has none, so its content is drawn as it is. */
|
|
145
|
+
export const NuxtLayout = defineComponent((_props, { slots }) => () => slots["default"]?.() ?? null, { name: "NuxtLayout", props: ["name", "fallback"] });
|
|
146
|
+
/** Catches what its content throws, and draws its `error` slot instead, with what clears it. */
|
|
147
|
+
export const NuxtErrorBoundary = defineComponent((_props, { slots, emit }) => {
|
|
148
|
+
const error = ref(null);
|
|
149
|
+
onErrorCaptured((caught) => {
|
|
150
|
+
error.value = caught;
|
|
151
|
+
emit("error", caught);
|
|
152
|
+
return false;
|
|
153
|
+
});
|
|
154
|
+
const clearError = () => {
|
|
155
|
+
error.value = null;
|
|
156
|
+
};
|
|
157
|
+
return () => (error.value === null ? slots["default"]?.() : slots["error"]?.({ error, clearError })) ?? null;
|
|
158
|
+
}, { name: "NuxtErrorBoundary", emits: ["error"] });
|
|
159
|
+
/**
|
|
160
|
+
* A component with nothing to draw in a story.
|
|
161
|
+
*
|
|
162
|
+
* @param name - its name.
|
|
163
|
+
*/
|
|
164
|
+
const nothing = (name) => defineComponent(() => () => null, { name, props: [] });
|
|
165
|
+
/** The page of the route: a story draws its own component, not a page. */
|
|
166
|
+
export const NuxtPage = nothing("NuxtPage");
|
|
167
|
+
/** The bar shown while a page loads: none loads in a story. */
|
|
168
|
+
export const NuxtLoadingIndicator = nothing("NuxtLoadingIndicator");
|
|
169
|
+
/** What tells a screen reader the page changed: no page changes in a story. */
|
|
170
|
+
export const NuxtRouteAnnouncer = nothing("NuxtRouteAnnouncer");
|
|
171
|
+
/**
|
|
172
|
+
* The image's attributes, as `NuxtImg` gives them: what has no value left out.
|
|
173
|
+
*
|
|
174
|
+
* @param props - the props.
|
|
175
|
+
*/
|
|
176
|
+
const imageAttributes = (props) => Object.fromEntries(Object.entries(props).filter(([, value]) => value !== undefined));
|
|
177
|
+
/** `@nuxt/image`'s image, drawn as it is: there is no image server to resize it. */
|
|
178
|
+
export const NuxtImg = defineComponent((props) => () => h("img", imageAttributes(props)), {
|
|
179
|
+
name: "NuxtImg",
|
|
180
|
+
props: ["src", "alt", "width", "height", "loading", "sizes"]
|
|
181
|
+
});
|
|
182
|
+
/** `@nuxt/image`'s picture, drawn as one image: there is no image server to give the other formats. */
|
|
183
|
+
export const NuxtPicture = defineComponent((props) => () => h("picture", [h("img", imageAttributes(props))]), { name: "NuxtPicture", props: ["src", "alt", "width", "height", "loading", "sizes"] });
|
|
184
|
+
/** The components Nuxt registers itself, by name: what a story's template finds without importing them. */
|
|
185
|
+
export const BUILT_IN_COMPONENTS = {
|
|
186
|
+
NuxtLink,
|
|
187
|
+
ClientOnly,
|
|
188
|
+
DevOnly,
|
|
189
|
+
NuxtLayout,
|
|
190
|
+
NuxtErrorBoundary,
|
|
191
|
+
NuxtPage,
|
|
192
|
+
NuxtLoadingIndicator,
|
|
193
|
+
NuxtRouteAnnouncer,
|
|
194
|
+
NuxtImg,
|
|
195
|
+
NuxtPicture
|
|
196
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nuxt's data fetching in a catalogue: `useAsyncData`, its lazy form, `refreshNuxtData`, `clearNuxtData` and
|
|
3
|
+
* `useNuxtData`. `useFetch` is written on it (`fetch.ts`).
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* A story gives the data of a key (`parameters.nuxt.data`) and the composable returns it without calling its handler,
|
|
7
|
+
* as Nuxt returns the payload the server sent. Without it the handler runs. The calls of one key share their data, as
|
|
8
|
+
* Nuxt's do; a key that is a ref follows it, each value with its own data. A call without a key gets the one the plugin
|
|
9
|
+
* writes for its place in the code, as Nuxt's compiler does.
|
|
10
|
+
*
|
|
11
|
+
* On the server (a generated server test) no handler runs, since no server is there to answer: the story's data is
|
|
12
|
+
* drawn, or nothing.
|
|
13
|
+
*
|
|
14
|
+
* What is returned is Nuxt 4's: `data` and `error` are `undefined` until there is one, and the result can be awaited,
|
|
15
|
+
* at once for a lazy call. `server`, `dedupe` and `deep` change nothing in the page, where everything runs.
|
|
16
|
+
*
|
|
17
|
+
* Internal: the code the plugin writes imports it (`#imports`, `#app`, `#components`); a story or a component imports
|
|
18
|
+
* Nuxt's names as in the app, never this module, whose exports may change in any release.
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import { type Ref, type WatchSource } from "vue";
|
|
23
|
+
import { type AsyncDataHandler } from "../asyncDataRun.ts";
|
|
24
|
+
import type { NuxtError } from "../nuxtError.ts";
|
|
25
|
+
import { type AsyncDataStatus } from "../nuxtStore.ts";
|
|
26
|
+
/** What `useAsyncData` returns: its data, where it stands, and what runs it again. */
|
|
27
|
+
export type AsyncData<TData> = {
|
|
28
|
+
/** What the handler returned (transformed and picked), or the story's data for the key. */
|
|
29
|
+
readonly data: Ref<TData | undefined>;
|
|
30
|
+
/** Whether the handler is running. */
|
|
31
|
+
readonly pending: Readonly<Ref<boolean>>;
|
|
32
|
+
/** Where it stands. */
|
|
33
|
+
readonly status: Ref<AsyncDataStatus>;
|
|
34
|
+
/** What the handler threw. */
|
|
35
|
+
readonly error: Ref<NuxtError | undefined>;
|
|
36
|
+
/** Runs the handler again. */
|
|
37
|
+
readonly refresh: () => Promise<void>;
|
|
38
|
+
/** Runs the handler again: `refresh`'s other name. */
|
|
39
|
+
readonly execute: () => Promise<void>;
|
|
40
|
+
/** Empties the data and the error, as before the first run. */
|
|
41
|
+
readonly clear: () => void;
|
|
42
|
+
};
|
|
43
|
+
/** What `useAsyncData` takes besides its key and handler. */
|
|
44
|
+
export type AsyncDataOptions<TResult> = {
|
|
45
|
+
/** Whether the server waits for it: no effect in a story. */
|
|
46
|
+
readonly server?: boolean;
|
|
47
|
+
/** Whether what awaits it waits for the data; `false` by default. */
|
|
48
|
+
readonly lazy?: boolean;
|
|
49
|
+
/** Whether it runs at once; `true` by default. */
|
|
50
|
+
readonly immediate?: boolean;
|
|
51
|
+
/** The data before the handler returns, and after a failure. */
|
|
52
|
+
readonly default?: () => unknown;
|
|
53
|
+
/** Changes what the handler returned. */
|
|
54
|
+
readonly transform?: (input: TResult) => unknown;
|
|
55
|
+
/** The keys of the result kept; all by default. */
|
|
56
|
+
readonly pick?: readonly string[];
|
|
57
|
+
/** What runs the handler again when it changes; `false` follows nothing, a key that is a ref included. */
|
|
58
|
+
readonly watch?: readonly WatchSource[] | false;
|
|
59
|
+
/** Whether a second call waits for the first (`"defer"`) or starts over (`"cancel"`): no effect in a story. */
|
|
60
|
+
readonly dedupe?: "cancel" | "defer";
|
|
61
|
+
/** Whether the data is deep reactive: no effect in a story. */
|
|
62
|
+
readonly deep?: boolean;
|
|
63
|
+
};
|
|
64
|
+
/** A key: a text, or a ref whose value is the key of the moment. */
|
|
65
|
+
export type AsyncDataKey = string | Readonly<Ref<string>>;
|
|
66
|
+
/**
|
|
67
|
+
* Fetches data once for a key, shared by every call with the key: the story's data for it when the story gives some,
|
|
68
|
+
* else what the handler returns.
|
|
69
|
+
*
|
|
70
|
+
* @param key - the key, or a ref whose value is the key.
|
|
71
|
+
* @param handler - what fetches the data.
|
|
72
|
+
* @param options - its options.
|
|
73
|
+
*/
|
|
74
|
+
export declare function useAsyncData<TResult>(key: AsyncDataKey, handler: AsyncDataHandler<TResult>, options?: AsyncDataOptions<TResult>): AsyncData<TResult> & Promise<AsyncData<TResult>>;
|
|
75
|
+
/**
|
|
76
|
+
* Fetches data, under the key of its place in the code.
|
|
77
|
+
*
|
|
78
|
+
* @param handler - what fetches the data.
|
|
79
|
+
* @param options - its options.
|
|
80
|
+
*/
|
|
81
|
+
export declare function useAsyncData<TResult>(handler: AsyncDataHandler<TResult>, options?: AsyncDataOptions<TResult>): AsyncData<TResult> & Promise<AsyncData<TResult>>;
|
|
82
|
+
/**
|
|
83
|
+
* `useAsyncData` that does not hold what awaits it.
|
|
84
|
+
*
|
|
85
|
+
* @param key - the key.
|
|
86
|
+
* @param handler - what fetches the data.
|
|
87
|
+
* @param options - its options.
|
|
88
|
+
*/
|
|
89
|
+
export declare const useLazyAsyncData: <TResult>(key: AsyncDataKey, handler: AsyncDataHandler<TResult>, options?: AsyncDataOptions<TResult>) => AsyncData<TResult> & Promise<AsyncData<TResult>>;
|
|
90
|
+
/**
|
|
91
|
+
* Runs the handlers of keys again.
|
|
92
|
+
*
|
|
93
|
+
* @param keys - a key, keys, or none for all.
|
|
94
|
+
*/
|
|
95
|
+
export declare const refreshNuxtData: (keys?: string | readonly string[]) => Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Empties the data of keys, back to each one's default.
|
|
98
|
+
*
|
|
99
|
+
* @param keys - a key, keys, or none for all.
|
|
100
|
+
*/
|
|
101
|
+
export declare const clearNuxtData: (keys?: string | readonly string[]) => void;
|
|
102
|
+
/**
|
|
103
|
+
* The data of a key another call fetched.
|
|
104
|
+
*
|
|
105
|
+
* @param key - the key.
|
|
106
|
+
*/
|
|
107
|
+
export declare const useNuxtData: (key: string) => {
|
|
108
|
+
readonly data: Ref<unknown>;
|
|
109
|
+
};
|