@poveste/plugin-vue 0.15.0 → 0.16.0
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/README.md +1 -1
- package/components.d.ts +4 -0
- package/dist/bundled/client/app/MountStory.js +5 -0
- package/dist/bundled/client/app/Story.js +58 -57
- package/dist/bundled/client/app/Variant.js +42 -41
- package/dist/bundled/client/app/context.js +5 -0
- package/dist/bundled/client/app/global-components.js +1 -1
- package/dist/bundled/client/app/render-context.js +7 -7
- package/dist/bundled/client/app/util.js +32 -27
- package/dist/bundled/client/context.js +20 -0
- package/dist/bundled/client/server/Story.js +28 -27
- package/dist/bundled/client/server/Variant.js +13 -17
- package/dist/bundled/client/server/context.js +5 -0
- package/dist/bundled/client/server/run.js +27 -27
- package/dist/client/app/MountStory.d.ts +9 -0
- package/dist/client/app/context.d.ts +9 -0
- package/dist/client/app/render-context.d.ts +15 -0
- package/dist/client/app/util.d.ts +32 -0
- package/dist/client/context.d.ts +40 -0
- package/dist/client/server/context.d.ts +7 -0
- package/package.json +13 -13
- package/src/client/__tests__/context.spec.ts +50 -0
- package/src/client/app/MountStory.ts +6 -0
- package/src/client/app/Story.ts +9 -4
- package/src/client/app/Variant.ts +10 -5
- package/src/client/app/__tests__/state-sync.spec.ts +113 -2
- package/src/client/app/__tests__/writable-binding.spec.ts +39 -0
- package/src/client/app/context.ts +17 -0
- package/src/client/app/render-context.ts +25 -5
- package/src/client/app/util.ts +88 -3
- package/src/client/context.ts +66 -0
- package/src/client/server/Story.ts +7 -7
- package/src/client/server/Variant.ts +13 -17
- package/src/client/server/__tests__/nesting-errors.spec.ts +48 -0
- package/src/client/server/context.ts +18 -0
- package/src/client/server/run.ts +7 -4
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Variant } from '@poveste/shared'
|
|
2
|
-
import
|
|
3
|
-
import { inject, provide } from 'vue'
|
|
2
|
+
import { createContext } from '../context.js'
|
|
4
3
|
|
|
5
4
|
export interface PreviewRenderContext {
|
|
6
5
|
mode: 'mount' | 'render'
|
|
@@ -12,18 +11,39 @@ export interface PreviewRenderContext {
|
|
|
12
11
|
* app realm, where the story view needs all of them mounted.
|
|
13
12
|
*/
|
|
14
13
|
targetVariantId?: string | null
|
|
14
|
+
/**
|
|
15
|
+
* Whether this mount pass owns the live state sync for its variants.
|
|
16
|
+
*
|
|
17
|
+
* False in a sandbox realm, where the render tree beside it already syncs the
|
|
18
|
+
* same `variant.state` against the same story's bindings. Two syncs over one
|
|
19
|
+
* state is two deep watchers walking the whole graph on every edit, and a
|
|
20
|
+
* story holding a large one pays for both: #964 measured the sandbox running
|
|
21
|
+
* three syncs where the app realm runs one.
|
|
22
|
+
*
|
|
23
|
+
* The app realm keeps it. Its mount pass is the only thing that fills
|
|
24
|
+
* `variant.state` there — with no controls slot nothing renders, so nothing
|
|
25
|
+
* else would — and that is what the controls panel builds from.
|
|
26
|
+
*/
|
|
27
|
+
syncState?: boolean
|
|
15
28
|
externalState: Variant['state'] | null
|
|
16
29
|
nextVariantIndex: {
|
|
17
30
|
value: number
|
|
18
31
|
}
|
|
19
32
|
}
|
|
20
33
|
|
|
21
|
-
|
|
34
|
+
/*
|
|
35
|
+
* This one was already a typed symbol with its provide and inject together —
|
|
36
|
+
* it was the pattern the other six were measured against (#981). It goes
|
|
37
|
+
* through the shared helper so the package has one shape rather than two,
|
|
38
|
+
* which is the mistake #978 is about, in miniature.
|
|
39
|
+
*/
|
|
40
|
+
const renderContext = createContext<PreviewRenderContext>('poveste-preview-render-context', '<Story>')
|
|
22
41
|
|
|
23
42
|
export function provideRenderContext(value: PreviewRenderContext) {
|
|
24
|
-
provide(
|
|
43
|
+
renderContext.provide(value)
|
|
25
44
|
}
|
|
26
45
|
|
|
46
|
+
/** Null outside a preview, which several call sites read with `?.`. */
|
|
27
47
|
export function useRenderContext() {
|
|
28
|
-
return
|
|
48
|
+
return renderContext.injectOptional()
|
|
29
49
|
}
|
package/src/client/app/util.ts
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
import type { ComponentInternalInstance } from 'vue'
|
|
2
|
-
import { applyState, createStateBaseline } from '@poveste/shared'
|
|
2
|
+
import { applyState, createStateBaseline, isMarkedRaw, isPlainObject } from '@poveste/shared'
|
|
3
3
|
import {
|
|
4
4
|
isRef as _isRef,
|
|
5
5
|
unref as _unref,
|
|
6
6
|
watch as _watch,
|
|
7
7
|
} from '@poveste/vendors/vue'
|
|
8
|
-
import { getCurrentInstance, isRef, unref, watch } from 'vue'
|
|
8
|
+
import { getCurrentInstance, isReadonly, isRef, markRaw, unref, watch } from 'vue'
|
|
9
9
|
|
|
10
10
|
const isObject = (val: unknown): val is object => val !== null && typeof val === 'object'
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* A value Vue has marked raw is carried by reference rather than rebuilt.
|
|
14
|
+
*
|
|
15
|
+
* Both sides of this bridge live in one realm, so the reference is the useful
|
|
16
|
+
* thing to hand over: the baseline then holds the same identity the next pass
|
|
17
|
+
* reads, `isEquivalent` settles it by `Object.is`, and nothing walks whatever
|
|
18
|
+
* is behind it. Rebuilding it was ten of the twenty traversals a keystroke
|
|
19
|
+
* cost (#957).
|
|
20
|
+
*
|
|
21
|
+
* The bridge that leaves this realm cannot do the same — see `toRawDeep` in
|
|
22
|
+
* `poveste-app`, where the mark does not survive the crossing and the key is
|
|
23
|
+
* dropped instead.
|
|
24
|
+
*/
|
|
12
25
|
/**
|
|
13
26
|
* Using external/user Vue
|
|
14
27
|
*/
|
|
@@ -23,10 +36,32 @@ export function toRawDeep(val: unknown, seen = new WeakMap()): any {
|
|
|
23
36
|
return unwrappedValue
|
|
24
37
|
}
|
|
25
38
|
|
|
39
|
+
if (isMarkedRaw(unwrappedValue)) {
|
|
40
|
+
return unwrappedValue
|
|
41
|
+
}
|
|
42
|
+
|
|
26
43
|
if (seen.has(unwrappedValue)) {
|
|
27
44
|
return seen.get(unwrappedValue)
|
|
28
45
|
}
|
|
29
46
|
|
|
47
|
+
if (!Array.isArray(unwrappedValue) && !isPlainObject(unwrappedValue)) {
|
|
48
|
+
// A `Date`, a `Map`, a class instance. Both sides of this bridge live in
|
|
49
|
+
// one realm, so the reference is the useful thing to hand over — the same
|
|
50
|
+
// answer `copyState` in `@poveste/shared` gives. Rebuilding it as a plain
|
|
51
|
+
// object emptied it: a `Date` has no own enumerable keys, so it arrived as
|
|
52
|
+
// `{}`, and the baseline's `isEquivalent` was handed two of those and
|
|
53
|
+
// called them equivalent (#977).
|
|
54
|
+
//
|
|
55
|
+
// Marked on the way through, which is not decoration. `applyState` writes
|
|
56
|
+
// this into the other Vue's reactive graph, and a reference to something
|
|
57
|
+
// like a Nuxt app instance — which a `<script setup>` binding can be — is
|
|
58
|
+
// then deep-watched: `useNuxtApp()` in a story recursed until the stack
|
|
59
|
+
// gave out. Flattening used to bound that by accident. `markRaw` bounds it
|
|
60
|
+
// deliberately, and is the treatment #974 already gives a value that must
|
|
61
|
+
// cross by reference.
|
|
62
|
+
return markRaw(unwrappedValue)
|
|
63
|
+
}
|
|
64
|
+
|
|
30
65
|
if (Array.isArray(unwrappedValue)) {
|
|
31
66
|
const result: unknown[] = []
|
|
32
67
|
seen.set(unwrappedValue, result)
|
|
@@ -61,10 +96,25 @@ export function _toRawDeep(val: unknown, seen = new WeakMap()): any {
|
|
|
61
96
|
return unwrappedValue
|
|
62
97
|
}
|
|
63
98
|
|
|
99
|
+
if (isMarkedRaw(unwrappedValue)) {
|
|
100
|
+
return unwrappedValue
|
|
101
|
+
}
|
|
102
|
+
|
|
64
103
|
if (seen.has(unwrappedValue)) {
|
|
65
104
|
return seen.get(unwrappedValue)
|
|
66
105
|
}
|
|
67
106
|
|
|
107
|
+
if (!Array.isArray(unwrappedValue) && !isPlainObject(unwrappedValue)) {
|
|
108
|
+
// A `Date`, a `Map`, a class instance. Both sides of this bridge live in
|
|
109
|
+
// one realm, so the reference is the useful thing to hand over — the same
|
|
110
|
+
// answer given to a marked value just above, and the same one `copyState`
|
|
111
|
+
// in `@poveste/shared` gives. Rebuilding it as a plain object emptied it: a
|
|
112
|
+
// `Date` has no own enumerable keys, so it arrived as `{}`, and the
|
|
113
|
+
// baseline's `isEquivalent` was handed two of those and called them
|
|
114
|
+
// equivalent (#977). Marked for the reason given on the walker above.
|
|
115
|
+
return markRaw(unwrappedValue)
|
|
116
|
+
}
|
|
117
|
+
|
|
68
118
|
if (Array.isArray(unwrappedValue)) {
|
|
69
119
|
const result: unknown[] = []
|
|
70
120
|
seen.set(unwrappedValue, result)
|
|
@@ -79,9 +129,22 @@ export function _toRawDeep(val: unknown, seen = new WeakMap()): any {
|
|
|
79
129
|
}
|
|
80
130
|
}
|
|
81
131
|
|
|
132
|
+
/**
|
|
133
|
+
* `_toRawDeep`, not `toRawDeep`: this is the bundled Vue's walker, and handing
|
|
134
|
+
* the subtree to the external one meant only the top level was ever walked by
|
|
135
|
+
* the copy of Vue that owns it.
|
|
136
|
+
*
|
|
137
|
+
* Nothing observable changes today, which is why it survived. The two walkers
|
|
138
|
+
* differ only in whose `isRef`/`unref` they call, and `isRef` is a read of
|
|
139
|
+
* `__v_isRef` — a property, not an identity — so either copy answers correctly
|
|
140
|
+
* for a ref the other created. It is fixed as a trap rather than as a defect:
|
|
141
|
+
* the next rule that lands on one walker and not the other would apply to the
|
|
142
|
+
* first level of bundled state and to nothing beneath it, and the diff would
|
|
143
|
+
* look right.
|
|
144
|
+
*/
|
|
82
145
|
function _toRawObject(obj: Record<any, any>, target: Record<any, any>, seen = new WeakMap()) {
|
|
83
146
|
Object.keys(obj).forEach((key) => {
|
|
84
|
-
target[key] =
|
|
147
|
+
target[key] = _toRawDeep(obj[key], seen)
|
|
85
148
|
})
|
|
86
149
|
}
|
|
87
150
|
|
|
@@ -149,6 +212,28 @@ export function syncStateBundledAndExternal(bundledState: Record<string, any>, e
|
|
|
149
212
|
}
|
|
150
213
|
|
|
151
214
|
/** The component being set up. Throws outside `setup`, where Vue returns null. */
|
|
215
|
+
/**
|
|
216
|
+
* Whether a `<script setup>` binding can be story state a control drives.
|
|
217
|
+
*
|
|
218
|
+
* A readonly ref cannot: the write-back is refused, so the two sides of the
|
|
219
|
+
* bridge never agree on it and the sync re-walks forever (#959). `useTemplateRef`
|
|
220
|
+
* returns one — in dev builds only, which is why a built book is quiet and the
|
|
221
|
+
* mode story authors work in is not.
|
|
222
|
+
*
|
|
223
|
+
* It has to be asked of the ref rather than of what the ref holds. The guard
|
|
224
|
+
* beside this one tests the value, and a value test cannot classify a template
|
|
225
|
+
* ref at all: at setup time it holds `null`, and what it will hold later is not
|
|
226
|
+
* knowable from here. Readonly-ness is a property of the ref itself and is
|
|
227
|
+
* settled by the time the binding is registered.
|
|
228
|
+
*
|
|
229
|
+
* A `computed` is caught by the same rule and for the same reason — it is a
|
|
230
|
+
* derived value, and a control that cannot write is not a control. Before this
|
|
231
|
+
* it appeared in the panel and warned when written.
|
|
232
|
+
*/
|
|
233
|
+
export function isWritableBinding(value: unknown): boolean {
|
|
234
|
+
return !(isRef(value) && isReadonly(value))
|
|
235
|
+
}
|
|
236
|
+
|
|
152
237
|
export function useInstance(name: string): ComponentInternalInstance {
|
|
153
238
|
const vm = getCurrentInstance()
|
|
154
239
|
if (!vm) {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { InjectionKey } from 'vue'
|
|
2
|
+
import { inject as vueInject, provide as vueProvide } from 'vue'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A context keyed by a symbol, with its provide and its inject in one place.
|
|
6
|
+
*
|
|
7
|
+
* Six of these used to be string keys carrying `any`, and two of the six were
|
|
8
|
+
* the same string meaning two different things in two realms (#981). A symbol
|
|
9
|
+
* cannot collide and the type travels with it.
|
|
10
|
+
*
|
|
11
|
+
* `inject` and `injectOptional` are separate functions rather than one call with a
|
|
12
|
+
* default, because "nobody provided this" and "this is allowed to be absent"
|
|
13
|
+
* are different questions that used to arrive as the same `undefined` — and the
|
|
14
|
+
* first one is what an author hits when they nest a component wrongly.
|
|
15
|
+
*
|
|
16
|
+
* Reka's `createContext` is this shape and is already a dependency of the
|
|
17
|
+
* controls package. It is not one of this package's, and this is fifteen lines,
|
|
18
|
+
* so it is written here rather than taking a dependency for it.
|
|
19
|
+
*/
|
|
20
|
+
export interface Context<T> {
|
|
21
|
+
provide: (value: T) => void
|
|
22
|
+
/** Throws, naming the provider, when nothing provided it. */
|
|
23
|
+
inject: (consumer: string) => T
|
|
24
|
+
/** `null` when nothing provided it, which the caller is expected to handle. */
|
|
25
|
+
injectOptional: () => T | null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `description` is what a symbol shows in a stack; `provider` is the component
|
|
30
|
+
* a reader has to go and add, and is the only part of the error they can act on.
|
|
31
|
+
*
|
|
32
|
+
* The key comes from the global registry rather than being minted here, because
|
|
33
|
+
* this module is evaluated more than once. A collection worker's module graph is
|
|
34
|
+
* invalidated while a story is mid-run, so `provide` and `inject` can each hold
|
|
35
|
+
* their own instance of this file — a plain `Symbol()` then differs between them
|
|
36
|
+
* and the injection misses, which reads as a `<Variant>` outside its `<Story>`.
|
|
37
|
+
* Every description here is already unique, so the registry cannot re-collide
|
|
38
|
+
* the way the string keys in #981 did.
|
|
39
|
+
*
|
|
40
|
+
* Absence is read as `null` rather than `undefined` so that a provider passing
|
|
41
|
+
* `undefined` on purpose is still a provider. None does today, and this is the
|
|
42
|
+
* cheaper of the two mistakes to have made.
|
|
43
|
+
*/
|
|
44
|
+
export function createContext<T>(description: string, provider: string): Context<T> {
|
|
45
|
+
const key: InjectionKey<T> = Symbol.for(description)
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
provide(value) {
|
|
49
|
+
vueProvide(key, value)
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
inject(consumer) {
|
|
53
|
+
const value = vueInject(key, null)
|
|
54
|
+
|
|
55
|
+
if (value === null) {
|
|
56
|
+
throw new Error(`[poveste] a ${consumer} has to be inside a ${provider}`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return value
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
injectOptional() {
|
|
63
|
+
return vueInject(key, null)
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { ServerStory,
|
|
1
|
+
import type { ServerStory, ServerVariant } from '@poveste/shared'
|
|
2
2
|
import type { PropType } from 'vue'
|
|
3
|
-
import { defineComponent,
|
|
3
|
+
import { defineComponent, onMounted } from 'vue'
|
|
4
|
+
import { addStoryContext, addVariantContext, serverStoryContext, storyFileContext } from './context.js'
|
|
4
5
|
import { autoStubComponents } from './stub.js'
|
|
5
6
|
|
|
6
7
|
export default defineComponent({
|
|
@@ -51,7 +52,7 @@ export default defineComponent({
|
|
|
51
52
|
},
|
|
52
53
|
|
|
53
54
|
setup(props) {
|
|
54
|
-
const file =
|
|
55
|
+
const file = storyFileContext.injectOptional()
|
|
55
56
|
|
|
56
57
|
// Story
|
|
57
58
|
|
|
@@ -72,14 +73,13 @@ export default defineComponent({
|
|
|
72
73
|
meta: props.meta,
|
|
73
74
|
variants: [],
|
|
74
75
|
}
|
|
75
|
-
|
|
76
|
-
addStory?.(story)
|
|
76
|
+
addStoryContext.injectOptional()?.(story)
|
|
77
77
|
|
|
78
78
|
// Variants
|
|
79
79
|
|
|
80
|
-
provide(
|
|
80
|
+
serverStoryContext.provide(story)
|
|
81
81
|
|
|
82
|
-
provide(
|
|
82
|
+
addVariantContext.provide((variant: ServerVariant) => {
|
|
83
83
|
story.variants.push(variant)
|
|
84
84
|
})
|
|
85
85
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ServerVariant } from '@poveste/shared'
|
|
2
2
|
import type { PropType } from 'vue'
|
|
3
|
-
import { defineComponent
|
|
3
|
+
import { defineComponent } from 'vue'
|
|
4
|
+
import { addVariantContext, serverStoryContext } from './context.js'
|
|
4
5
|
|
|
5
6
|
export default defineComponent({
|
|
6
7
|
name: 'PovesteVariant',
|
|
@@ -33,25 +34,20 @@ export default defineComponent({
|
|
|
33
34
|
},
|
|
34
35
|
|
|
35
36
|
setup(props) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const variant: ServerVariant = {
|
|
46
|
-
id: props.id ?? generateId(),
|
|
37
|
+
// Asked for before anything reads it, so a missing `<Story>` is reported
|
|
38
|
+
// once and the same way whether or not the variant carries an `id`. The
|
|
39
|
+
// guard this replaces sat inside the branch that generates one, so an
|
|
40
|
+
// explicit `id` skipped it and reached `undefined(variant)` instead (#981).
|
|
41
|
+
const story = serverStoryContext.inject('<Variant>')
|
|
42
|
+
const addVariant = addVariantContext.inject('<Variant>')
|
|
43
|
+
|
|
44
|
+
addVariant({
|
|
45
|
+
id: props.id ?? `${story.id}-${story.variants.length}`,
|
|
47
46
|
title: props.title,
|
|
48
47
|
icon: props.icon,
|
|
49
48
|
iconColor: props.iconColor,
|
|
50
49
|
meta: props.meta,
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const addVariant = inject('addVariant') as (variant: ServerVariant) => void
|
|
54
|
-
addVariant(variant)
|
|
50
|
+
})
|
|
55
51
|
},
|
|
56
52
|
|
|
57
53
|
render() {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { createApp, h } from 'vue'
|
|
4
|
+
import Story from '../Story.js'
|
|
5
|
+
import Variant from '../Variant.js'
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* What a story author gets for a nesting mistake in collect mode, where the
|
|
9
|
+
* stack has nothing in it about stories (#981).
|
|
10
|
+
*
|
|
11
|
+
* Mounted the way `run.ts` mounts them — a bare `createApp` — rather than
|
|
12
|
+
* through a test-utils wrapper, because the missing provider is the whole
|
|
13
|
+
* subject and a harness that supplies one would prove nothing.
|
|
14
|
+
*/
|
|
15
|
+
function mountAlone(component: any, props: Record<string, unknown> = {}) {
|
|
16
|
+
const app = createApp({ render: () => h(component, props) })
|
|
17
|
+
const el = document.createElement('div')
|
|
18
|
+
try {
|
|
19
|
+
app.mount(el)
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
app.unmount()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('a <Variant> outside a <Story>', () => {
|
|
27
|
+
it('says which component it needed, when it carries an id', () => {
|
|
28
|
+
// The branch that generates an id already threw a good error, so an
|
|
29
|
+
// explicit id was the case that reached `undefined(variant)` and said
|
|
30
|
+
// `addVariant is not a function`.
|
|
31
|
+
expect(() => mountAlone(Variant, { id: 'mine', title: 'Solo' })).toThrow(/<Variant>.*<Story>/)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('says the same thing when it does not', () => {
|
|
35
|
+
expect(() => mountAlone(Variant, { title: 'Solo' })).toThrow(/<Variant>.*<Story>/)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('never reports that a function is not a function', () => {
|
|
39
|
+
expect(() => mountAlone(Variant, { id: 'mine', title: 'Solo' })).not.toThrow(/is not a function/)
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('a <Story> outside the collect runner', () => {
|
|
44
|
+
it('says which provider it needed', () => {
|
|
45
|
+
// `hstStoryFile` comes from `run.ts`, the only thing that mounts these.
|
|
46
|
+
expect(() => mountAlone(Story, { title: 'Solo' })).toThrow(/<Story>/)
|
|
47
|
+
})
|
|
48
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ServerStory, ServerStoryFile, ServerVariant } from '@poveste/shared'
|
|
2
|
+
import { createContext } from '../context.js'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Collect mode's contexts. The story one is deliberately not the app's: they
|
|
6
|
+
* carry different shapes in different realms and shared one string key, so
|
|
7
|
+
* nothing stopped one being injected where the other was expected (#981).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** The file being collected, provided by `run.ts` rather than by a component. */
|
|
11
|
+
export const storyFileContext = createContext<ServerStoryFile>('poveste-server-story-file', 'story file')
|
|
12
|
+
|
|
13
|
+
/** Optional: a `<Story>` collected outside a run has nowhere to report to. */
|
|
14
|
+
export const addStoryContext = createContext<(story: ServerStory) => void>('poveste-server-add-story', 'story file')
|
|
15
|
+
|
|
16
|
+
export const serverStoryContext = createContext<ServerStory>('poveste-server-story', '<Story>')
|
|
17
|
+
|
|
18
|
+
export const addVariantContext = createContext<(variant: ServerVariant) => void>('poveste-server-add-variant', '<Story>')
|
package/src/client/server/run.ts
CHANGED
|
@@ -7,6 +7,7 @@ import * as generatedSetup from 'virtual:$poveste-generated-global-setup'
|
|
|
7
7
|
import * as setup from 'virtual:$poveste-setup'
|
|
8
8
|
import { createApp, h } from 'vue'
|
|
9
9
|
import { VUE_SETUP_HOOK_NAMES } from '../../setup-hooks.js'
|
|
10
|
+
import { addStoryContext, storyFileContext } from './context.js'
|
|
10
11
|
import Story from './Story.js'
|
|
11
12
|
import Variant from './Variant.js'
|
|
12
13
|
|
|
@@ -14,11 +15,13 @@ export async function run({ file, storyData, el }: ServerRunPayload) {
|
|
|
14
15
|
const { default: Comp } = await import(/* @vite-ignore */ file.moduleId)
|
|
15
16
|
|
|
16
17
|
const app = createApp({
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
// Provided from `setup` rather than the options object so the keys can be
|
|
19
|
+
// the symbols the components inject; the options form takes string keys.
|
|
20
|
+
setup() {
|
|
21
|
+
addStoryContext.provide((data: ServerStory) => {
|
|
19
22
|
storyData.push(data)
|
|
20
|
-
}
|
|
21
|
-
|
|
23
|
+
})
|
|
24
|
+
storyFileContext.provide(file)
|
|
22
25
|
},
|
|
23
26
|
render() {
|
|
24
27
|
return h(Comp)
|