@poveste/plugin-vue 0.1.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.
Files changed (81) hide show
  1. package/LICENSE +22 -0
  2. package/client.d.ts +2 -0
  3. package/collect.d.ts +2 -0
  4. package/components.d.ts +239 -0
  5. package/dist/bundled/client/app/MountStory.js +55 -0
  6. package/dist/bundled/client/app/RenderStory.js +74 -0
  7. package/dist/bundled/client/app/RouterLinkStub.js +40 -0
  8. package/dist/bundled/client/app/Story.js +112 -0
  9. package/dist/bundled/client/app/Variant.js +104 -0
  10. package/dist/bundled/client/app/auto-props.js +66 -0
  11. package/dist/bundled/client/app/global-components.js +81 -0
  12. package/dist/bundled/client/app/host.js +84 -0
  13. package/dist/bundled/client/app/index.js +6 -0
  14. package/dist/bundled/client/app/render-context.js +12 -0
  15. package/dist/bundled/client/app/util.js +70 -0
  16. package/dist/bundled/client/client.js +10 -0
  17. package/dist/bundled/client/codegen.js +196 -0
  18. package/dist/bundled/client/server/Story.js +79 -0
  19. package/dist/bundled/client/server/Variant.js +46 -0
  20. package/dist/bundled/client/server/run.js +51 -0
  21. package/dist/bundled/client/server/stub.js +8 -0
  22. package/dist/bundled/client/server.js +4 -0
  23. package/dist/bundled/node_modules/.pnpm/change-case@5.4.4/node_modules/change-case/dist/index.js +118 -0
  24. package/dist/client/app/MountStory.d.ts +16 -0
  25. package/dist/client/app/RenderStory.d.ts +38 -0
  26. package/dist/client/app/RouterLinkStub.d.ts +21 -0
  27. package/dist/client/app/Story.d.ts +33 -0
  28. package/dist/client/app/Variant.d.ts +78 -0
  29. package/dist/client/app/auto-props.d.ts +2 -0
  30. package/dist/client/app/global-components.d.ts +2 -0
  31. package/dist/client/app/host.d.ts +16 -0
  32. package/dist/client/app/index.d.ts +2 -0
  33. package/dist/client/app/render-context.d.ts +12 -0
  34. package/dist/client/app/util.d.ts +16 -0
  35. package/dist/client/client.d.ts +7 -0
  36. package/dist/client/codegen.d.ts +4 -0
  37. package/dist/client/server/Story.d.ts +81 -0
  38. package/dist/client/server/Variant.d.ts +52 -0
  39. package/dist/client/server/run.d.ts +2 -0
  40. package/dist/client/server/stub.d.ts +1 -0
  41. package/dist/client/server.d.ts +1 -0
  42. package/dist/commands/generate-story.client.d.ts +3 -0
  43. package/dist/commands/generate-story.client.js +27 -0
  44. package/dist/commands/generate-story.server.d.ts +3 -0
  45. package/dist/commands/generate-story.server.js +45 -0
  46. package/dist/helpers.d.ts +10 -0
  47. package/dist/helpers.js +3 -0
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.js +1 -0
  50. package/dist/index.node.d.ts +3 -0
  51. package/dist/index.node.js +54 -0
  52. package/dist/util/list-components.d.ts +1 -0
  53. package/dist/util/list-components.js +15 -0
  54. package/package.json +71 -0
  55. package/src/client/app/MountStory.ts +83 -0
  56. package/src/client/app/RenderStory.ts +131 -0
  57. package/src/client/app/RouterLinkStub.ts +48 -0
  58. package/src/client/app/Story.ts +209 -0
  59. package/src/client/app/Variant.ts +196 -0
  60. package/src/client/app/auto-props.ts +135 -0
  61. package/src/client/app/global-components.ts +131 -0
  62. package/src/client/app/host.ts +139 -0
  63. package/src/client/app/index.ts +2 -0
  64. package/src/client/app/render-context.ts +23 -0
  65. package/src/client/app/util.ts +129 -0
  66. package/src/client/client.ts +8 -0
  67. package/src/client/codegen.ts +400 -0
  68. package/src/client/server/Story.ts +121 -0
  69. package/src/client/server/Variant.ts +56 -0
  70. package/src/client/server/run.ts +63 -0
  71. package/src/client/server/stub.ts +12 -0
  72. package/src/client/server.ts +1 -0
  73. package/src/commands/generate-story.client.ts +29 -0
  74. package/src/commands/generate-story.server.ts +55 -0
  75. package/src/helpers.ts +15 -0
  76. package/src/index.node.ts +63 -0
  77. package/src/index.ts +1 -0
  78. package/src/util/list-components.ts +16 -0
  79. package/tsconfig.build.json +6 -0
  80. package/tsconfig.json +40 -0
  81. package/vite.config.ts +70 -0
@@ -0,0 +1,129 @@
1
+ import { applyState } from '@poveste/shared'
2
+ import {
3
+ isRef as _isRef,
4
+ unref as _unref,
5
+ watch as _watch,
6
+ } from '@poveste/vendors/vue'
7
+ import { isRef, unref, watch } from 'vue'
8
+
9
+ const isObject = val => val !== null && typeof val === 'object'
10
+
11
+ /**
12
+ * Using external/user Vue
13
+ */
14
+ export function toRawDeep(val, seen = new WeakMap()) {
15
+ const unwrappedValue = isRef(val) ? unref(val) : val
16
+
17
+ if (typeof unwrappedValue === 'symbol') {
18
+ return unwrappedValue.toString()
19
+ }
20
+
21
+ if (!isObject(unwrappedValue)) {
22
+ return unwrappedValue
23
+ }
24
+
25
+ if (seen.has(unwrappedValue)) {
26
+ return seen.get(unwrappedValue)
27
+ }
28
+
29
+ if (Array.isArray(unwrappedValue)) {
30
+ const result = []
31
+ seen.set(unwrappedValue, result)
32
+ result.push(...unwrappedValue.map(value => toRawDeep(value, seen)))
33
+ return result
34
+ }
35
+ else {
36
+ const result = {}
37
+ seen.set(unwrappedValue, result)
38
+ toRawObject(unwrappedValue, result, seen)
39
+ return result
40
+ }
41
+ }
42
+
43
+ function toRawObject(obj: Record<any, any>, target: Record<any, any>, seen = new WeakMap()) {
44
+ Object.keys(obj).forEach((key) => {
45
+ target[key] = toRawDeep(obj[key], seen)
46
+ })
47
+ }
48
+
49
+ /**
50
+ * Using bundled Vue
51
+ */
52
+ export function _toRawDeep(val, seen = new WeakMap()) {
53
+ const unwrappedValue = _isRef(val) ? _unref(val) : val
54
+
55
+ if (typeof unwrappedValue === 'symbol') {
56
+ return unwrappedValue.toString()
57
+ }
58
+
59
+ if (!isObject(unwrappedValue)) {
60
+ return unwrappedValue
61
+ }
62
+
63
+ if (seen.has(unwrappedValue)) {
64
+ return seen.get(unwrappedValue)
65
+ }
66
+
67
+ if (Array.isArray(unwrappedValue)) {
68
+ const result = []
69
+ seen.set(unwrappedValue, result)
70
+ result.push(...unwrappedValue.map(value => _toRawDeep(value, seen)))
71
+ return result
72
+ }
73
+ else {
74
+ const result = {}
75
+ seen.set(unwrappedValue, result)
76
+ _toRawObject(unwrappedValue, result, seen)
77
+ return result
78
+ }
79
+ }
80
+
81
+ function _toRawObject(obj: Record<any, any>, target: Record<any, any>, seen = new WeakMap()) {
82
+ Object.keys(obj).forEach((key) => {
83
+ target[key] = toRawDeep(obj[key], seen)
84
+ })
85
+ }
86
+
87
+ /**
88
+ * Synchronize states between the bundled and external/user versions of Vue
89
+ * @param bundledState Reactive state created with the bundled Vue
90
+ * @param externalState Reactive state created with the external/user Vue
91
+ */
92
+ export function syncStateBundledAndExternal(bundledState, externalState) {
93
+ let syncing = false
94
+
95
+ const _stop = _watch(() => bundledState, (value) => {
96
+ if (value == null) return
97
+ if (!syncing) {
98
+ syncing = true
99
+ applyState(externalState, _toRawDeep(value))
100
+ }
101
+ else {
102
+ syncing = false
103
+ }
104
+ }, {
105
+ deep: true,
106
+ immediate: true,
107
+ })
108
+
109
+ const stop = watch(() => externalState, (value) => {
110
+ if (value == null) return
111
+ if (!syncing) {
112
+ syncing = true
113
+ applyState(bundledState, toRawDeep(value))
114
+ }
115
+ else {
116
+ syncing = false
117
+ }
118
+ }, {
119
+ deep: true,
120
+ immediate: true,
121
+ })
122
+
123
+ return {
124
+ stop() {
125
+ _stop()
126
+ stop()
127
+ },
128
+ }
129
+ }
@@ -0,0 +1,8 @@
1
+ export * from './app/index.js'
2
+ export * from './codegen.js'
3
+
4
+ declare module '@poveste/shared' {
5
+ interface StoryMeta {
6
+ hasVariantChildComponents?: boolean
7
+ }
8
+ }
@@ -0,0 +1,400 @@
1
+ // @TODO remove @ts-ignore
2
+
3
+ import type { Variant } from '@poveste/shared'
4
+ import type { VNode } from 'vue'
5
+ import { createAutoBuildingObject, indent, serializeJs, voidElements } from '@poveste/shared'
6
+ import { camelCase, pascalCase } from 'change-case'
7
+ import { Text, vModelCheckbox, vModelDynamic, vModelRadio, vModelSelect, vModelText } from 'vue'
8
+
9
+ export async function generateSourceCode(variant: Variant) {
10
+ const vnode = variant.slots().default?.({ state: variant.state ?? {} }) ?? []
11
+ const list = Array.isArray(vnode) ? vnode : [vnode]
12
+ const lines: string[] = []
13
+ for (const n in list) {
14
+ const vnode = list[n]
15
+ lines.push(...(await printVNode(vnode, variant.state?._hPropState?.[n])).lines)
16
+ }
17
+ return lines.join('\n')
18
+ }
19
+
20
+ async function printVNode(vnode: VNode, propsOverrides: Record<string, any> = null): Promise<{ lines: string[], isText?: boolean }> {
21
+ if (vnode.type === Text) {
22
+ return {
23
+ // @ts-expect-error TODO
24
+ lines: [vnode.children],
25
+ isText: true,
26
+ }
27
+ }
28
+
29
+ const lines: string[] = []
30
+
31
+ if (typeof vnode.type === 'object' || typeof vnode.type === 'string') {
32
+ // Wait for async component
33
+ // @ts-expect-error TODO
34
+ if (vnode.type?.__asyncLoader && !vnode.type.__asyncResolved) {
35
+ // @ts-expect-error TODO
36
+ await vnode.type.__asyncLoader()
37
+ }
38
+
39
+ const attrs: string[][] = []
40
+ let multilineAttrs = false
41
+ const skipProps: string[] = [
42
+ 'key',
43
+ ]
44
+
45
+ // Directives
46
+ function genDirective(dirName: string, dir, valueCode: string = null) {
47
+ let modifiers = ''
48
+ for (const key in dir.modifiers) {
49
+ if (dir.modifiers[key]) {
50
+ modifiers += `.${key}`
51
+ }
52
+ }
53
+ let arg = ''
54
+ if (dir.arg) {
55
+ arg = `:${dir.arg}`
56
+ }
57
+ if (valueCode) {
58
+ // Cleanup render code
59
+ valueCode = valueCode.replace(/^\$(setup|props|data)\./g, '')
60
+ }
61
+ const valueLines = valueCode ? [valueCode] : serializeAndCleanJs(dir.value)
62
+ const attr: string[] = []
63
+ const dirAttr = `v-${dirName}${arg}${modifiers}="`
64
+ if (valueLines.length > 1) {
65
+ attr.push(`${dirAttr}${valueLines[0]}`)
66
+ attr.push(...valueLines.slice(1, valueLines.length - 1))
67
+ attr.push(`${valueLines[valueLines.length - 1]}"`)
68
+ multilineAttrs = true
69
+ }
70
+ else {
71
+ attr.push(`${dirAttr}${valueLines[0] ?? ''}"`)
72
+ }
73
+ attrs.push(attr)
74
+ }
75
+ if (vnode.dirs) {
76
+ for (const dir of vnode.dirs) {
77
+ // Vmodel
78
+ if (dir.dir === vModelText || dir.dir === vModelSelect || dir.dir === vModelRadio || dir.dir === vModelCheckbox || dir.dir === vModelDynamic) {
79
+ const listenerKeys = [`onUpdate:${dir.arg ?? 'modelValue'}`, `onUpdate:${camelCase(dir.arg ?? 'modelValue')}`]
80
+ const listenerKey = listenerKeys.find(key => vnode.props[key])
81
+ const listener = vnode.props[listenerKey]
82
+ let valueCode: string = null
83
+ if (listener) {
84
+ skipProps.push(listenerKey)
85
+ const listenerSource = listener.toString()
86
+ const result = /\(\$event\) => (.*?) = \$event/.exec(listenerSource)
87
+ if (result) {
88
+ valueCode = result[1]
89
+ }
90
+ }
91
+ genDirective('model', dir, valueCode)
92
+ }
93
+ // @ts-expect-error TODO
94
+ else if (dir.instance._ || dir.instance.$) {
95
+ // @ts-expect-error TODO
96
+ const target = dir.instance.$ ?? dir.instance._
97
+ let dirName: string
98
+ for (const directives of [target.directives, target.appContext.directives]) {
99
+ for (const key in directives) {
100
+ if (directives[key] === dir.dir) {
101
+ dirName = key
102
+ break
103
+ }
104
+ }
105
+ if (dirName) break
106
+ }
107
+ // Check in setup state
108
+ if (!dirName) {
109
+ for (const key in target.setupState) {
110
+ if (target.setupState[key] === dir.dir) {
111
+ dirName = key.replace(/^v(\w)/, (_, p1) => p1.toLowerCase())
112
+ break
113
+ }
114
+ }
115
+ }
116
+ if (dirName) {
117
+ genDirective(dirName, dir)
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ // Attributes
124
+ function addAttr(prop: string, value: any) {
125
+ // @ts-expect-error TODO
126
+ if (typeof value !== 'string' || vnode.dynamicProps?.includes(prop)) {
127
+ let directive = ':'
128
+ if (prop.startsWith('on')) {
129
+ directive = '@'
130
+ }
131
+ const arg = directive === '@' ? `${prop[2].toLowerCase()}${prop.slice(3)}` : prop
132
+
133
+ // v-model on component
134
+ const vmodelListeners = [`onUpdate:${prop}`, `onUpdate:${camelCase(prop)}`]
135
+ // @ts-expect-error TODO
136
+ const vmodelListener = vmodelListeners.find(key => vnode.dynamicProps?.includes(key) || (vnode.props && key in vnode.props))
137
+ if (directive === ':' && vmodelListener) {
138
+ // Listener
139
+ skipProps.push(vmodelListener)
140
+ const listener = vnode.props[vmodelListener]
141
+ const listenerSource = listener.toString()
142
+ let valueCode: string
143
+ const result = /\(\$event\) => (.*?) = \$event/.exec(listenerSource)
144
+ if (result) {
145
+ valueCode = result[1]
146
+ }
147
+
148
+ // Modifiers
149
+ const modifiersKey = `${prop === 'modelValue' ? 'model' : prop}Modifiers`
150
+ const modifiers = vnode.props[modifiersKey] ?? {}
151
+ skipProps.push(modifiersKey)
152
+
153
+ // Directive
154
+ genDirective('model', {
155
+ arg: prop === 'modelValue' ? null : prop,
156
+ modifiers,
157
+ value,
158
+ }, valueCode)
159
+ return
160
+ }
161
+
162
+ if (typeof value === 'undefined') {
163
+ return
164
+ }
165
+
166
+ let serialized: string[]
167
+ if (typeof value === 'string' && value.startsWith('{{') && value.endsWith('}}')) {
168
+ // It was formatted from auto building object (slot props)
169
+ serialized = cleanupExpression(value.substring(2, value.length - 2).trim()).split('\n')
170
+ }
171
+ else if (typeof value === 'function') {
172
+ let code = cleanupExpression(value.toString().replace(/'/g, '\\\'').replace(/"/g, '\''))
173
+ const testResult = /function (\S+)\(/.exec(code)
174
+ if (testResult) {
175
+ // Function name only
176
+ serialized = [testResult[1]]
177
+ }
178
+ else {
179
+ if (code.startsWith('($event) => ')) {
180
+ // Remove unnecessary `($event) => `
181
+ code = code.substring('($event) => '.length)
182
+ }
183
+ serialized = code.split('\n')
184
+ }
185
+ }
186
+ else {
187
+ serialized = serializeAndCleanJs(value)
188
+ }
189
+ if (serialized.length > 1) {
190
+ multilineAttrs = true
191
+ const indented: string[] = [`${directive}${arg}="${serialized[0]}`]
192
+ indented.push(...serialized.slice(1, serialized.length - 1))
193
+ indented.push(`${serialized[serialized.length - 1]}"`)
194
+ attrs.push(indented)
195
+ }
196
+ else {
197
+ attrs.push([`${directive}${arg}="${serialized[0]}"`])
198
+ }
199
+ }
200
+ // @ts-expect-error TODO
201
+ else if (vnode.type?.props?.[prop]?.type === Boolean) {
202
+ attrs.push([prop])
203
+ }
204
+ else {
205
+ attrs.push([`${prop}="${value}"`])
206
+ }
207
+ }
208
+
209
+ for (const prop in vnode.props) {
210
+ if (skipProps.includes(prop) || (propsOverrides && prop in propsOverrides)) {
211
+ continue
212
+ }
213
+ const value = vnode.props[prop]
214
+ addAttr(prop, value)
215
+ }
216
+
217
+ if (propsOverrides) {
218
+ for (const prop in propsOverrides) {
219
+ addAttr(prop, propsOverrides[prop])
220
+ }
221
+ }
222
+
223
+ if (attrs.length > 1) {
224
+ multilineAttrs = true
225
+ }
226
+
227
+ // Tags
228
+ const tagName = getTagName(vnode)
229
+
230
+ // Children
231
+ let isChildText = false
232
+ const childLines: string[] = []
233
+ if (typeof vnode.children === 'string') {
234
+ if (tagName === 'pre') {
235
+ childLines.push(vnode.children)
236
+ }
237
+ else {
238
+ childLines.push(...vnode.children.split('\n'))
239
+ }
240
+ isChildText = true
241
+ }
242
+ else if (Array.isArray(vnode.children)) {
243
+ let isAllChildText
244
+ for (const child of vnode.children) {
245
+ // @ts-expect-error TODO
246
+ const result = await printVNode(child)
247
+ if (result.isText) {
248
+ if (isAllChildText === undefined) {
249
+ isAllChildText = true
250
+ }
251
+ const text = result.lines[0]
252
+ if (!childLines.length || /^\s/.test(text)) {
253
+ childLines.push(text.trim())
254
+ }
255
+ else {
256
+ childLines[childLines.length - 1] += text
257
+ }
258
+ }
259
+ else {
260
+ if (isAllChildText === undefined) {
261
+ isAllChildText = false
262
+ }
263
+ childLines.push(...result.lines)
264
+ }
265
+ }
266
+ if (isAllChildText !== undefined) {
267
+ isChildText = isAllChildText
268
+ }
269
+ }
270
+
271
+ // Slots
272
+ if (vnode.children && typeof vnode.children === 'object' && !Array.isArray(vnode.children)) {
273
+ for (const key in vnode.children) {
274
+ if (typeof vnode.children[key] === 'function') {
275
+ const autoObject = createAutoBuildingObject(key => `{{ ${key} }}`, (target, p) => {
276
+ // Vue 3
277
+ if (p === '__v_isRef') {
278
+ return () => false
279
+ }
280
+ })
281
+ const children = vnode.children[key](autoObject.proxy)
282
+ const slotLines: string[] = []
283
+ for (const child of children) {
284
+ slotLines.push(...(await printVNode(child)).lines)
285
+ }
286
+ const slotProps = Object.keys(autoObject.cache)
287
+ if (slotProps.length) {
288
+ childLines.push(`<template #${key}="{ ${slotProps.join(', ')} }">`)
289
+ childLines.push(...indent(slotLines))
290
+ childLines.push('</template>')
291
+ }
292
+ else if (key === 'default') {
293
+ childLines.push(...slotLines)
294
+ }
295
+ else {
296
+ childLines.push(`<template #${key}>`)
297
+ childLines.push(...indent(slotLines))
298
+ childLines.push(`</template>`)
299
+ }
300
+ }
301
+ }
302
+ }
303
+
304
+ // Template
305
+ const tag = [`<${tagName}`]
306
+ if (multilineAttrs) {
307
+ for (const attrLines of attrs) {
308
+ tag.push(...indent(attrLines))
309
+ }
310
+ if (childLines.length > 0) {
311
+ tag.push('>')
312
+ }
313
+ }
314
+ else {
315
+ if (attrs.length === 1) {
316
+ tag[0] += ` ${attrs[0]}`
317
+ }
318
+ if (childLines.length > 0) {
319
+ tag[0] += '>'
320
+ }
321
+ }
322
+
323
+ const isVoid = voidElements.includes(tagName.toLowerCase())
324
+
325
+ if (childLines.length > 0) {
326
+ if (childLines.length === 1 && tag.length === 1 && !attrs.length && isChildText) {
327
+ lines.push(`${tag[0]}${childLines[0]}</${tagName}>`)
328
+ }
329
+ else {
330
+ lines.push(...tag)
331
+ lines.push(...indent(childLines))
332
+ lines.push(`</${tagName}>`)
333
+ }
334
+ }
335
+ else if (tag.length > 1) {
336
+ lines.push(...tag)
337
+ lines.push(isVoid ? '>' : '/>')
338
+ }
339
+ else {
340
+ lines.push(`${tag[0]}${isVoid ? '' : ' /'}>`)
341
+ }
342
+ }
343
+ else if (vnode?.shapeFlag & 1 << 4) {
344
+ // @ts-expect-error TODO
345
+ for (const child of vnode.children) {
346
+ lines.push(...(await printVNode(child)).lines)
347
+ }
348
+ }
349
+
350
+ return {
351
+ lines,
352
+ }
353
+ }
354
+
355
+ export function getTagName(vnode: VNode) {
356
+ if (typeof vnode.type === 'string') {
357
+ return vnode.type
358
+ }
359
+ // @ts-expect-error TODO
360
+ else if (vnode.type?.__asyncResolved) {
361
+ // @ts-expect-error TODO
362
+ const asyncComp = vnode.type?.__asyncResolved
363
+ return asyncComp.name ?? getNameFromFile(asyncComp.__file)
364
+ }
365
+ // @ts-expect-error TODO
366
+ else if (vnode.type?.name) {
367
+ // @ts-expect-error TODO
368
+ return vnode.type.name
369
+ }
370
+ // @ts-expect-error TODO
371
+ else if (vnode.type?.__file) {
372
+ // @ts-expect-error TODO
373
+ return getNameFromFile(vnode.type.__file)
374
+ }
375
+ return 'Anonymous'
376
+ }
377
+
378
+ function getNameFromFile(file: string) {
379
+ const parts = /([^/]+)\.vue$/.exec(file)
380
+ if (parts) {
381
+ return pascalCase(parts[1])
382
+ }
383
+ return 'Anonymous'
384
+ }
385
+
386
+ function serializeAndCleanJs(value: any) {
387
+ const isAutoBuildingObject = !!value?.__autoBuildingObject
388
+ const result = serializeJs(value)
389
+ if (isAutoBuildingObject) {
390
+ // @ts-expect-error TODO
391
+ return [cleanupExpression(result.__autoBuildingObjectGetKey)]
392
+ }
393
+ else {
394
+ return cleanupExpression(result).split('\n')
395
+ }
396
+ }
397
+
398
+ function cleanupExpression(expr: string) {
399
+ return expr.replace(/\$setup\./g, '')
400
+ }
@@ -0,0 +1,121 @@
1
+ import type { ServerStory, ServerStoryFile, ServerVariant } from '@poveste/shared'
2
+ import type { PropType } from 'vue'
3
+ import { defineComponent, inject, onMounted, provide, useAttrs } from 'vue'
4
+ import { autoStubComponents } from './stub'
5
+
6
+ export default defineComponent({
7
+ name: 'PovesteStory',
8
+
9
+ inheritAttrs: false,
10
+
11
+ props: {
12
+ title: {
13
+ type: String,
14
+ default: undefined,
15
+ },
16
+
17
+ id: {
18
+ type: String,
19
+ default: undefined,
20
+ },
21
+
22
+ group: {
23
+ type: String,
24
+ default: undefined,
25
+ },
26
+
27
+ layout: {
28
+ type: Object as PropType<ServerStory['layout']>,
29
+ default: undefined,
30
+ },
31
+
32
+ icon: {
33
+ type: String,
34
+ default: undefined,
35
+ },
36
+
37
+ iconColor: {
38
+ type: String,
39
+ default: undefined,
40
+ },
41
+
42
+ docsOnly: {
43
+ type: Boolean,
44
+ default: false,
45
+ },
46
+
47
+ meta: {
48
+ type: Object as PropType<ServerStory['meta']>,
49
+ default: undefined,
50
+ },
51
+ },
52
+
53
+ setup(props) {
54
+ const attrs = useAttrs() as {
55
+ data: ServerStoryFile
56
+ }
57
+
58
+ // Story
59
+
60
+ const story: ServerStory = {
61
+ id: props.id ?? attrs.data.id,
62
+ title: props.title ?? attrs.data.fileName,
63
+ group: props.group,
64
+ layout: props.layout,
65
+ icon: props.icon,
66
+ iconColor: props.iconColor,
67
+ docsOnly: props.docsOnly,
68
+ meta: props.meta,
69
+ variants: [],
70
+ }
71
+ const addStory = inject('addStory', null)
72
+ addStory?.(story)
73
+
74
+ // Variants
75
+
76
+ provide('story', story)
77
+
78
+ provide('addVariant', (variant: ServerVariant) => {
79
+ story.variants.push(variant)
80
+ })
81
+
82
+ onMounted(() => {
83
+ if (!story.variants.length) {
84
+ story.variants.push({
85
+ id: '_default',
86
+ title: 'default',
87
+ })
88
+ }
89
+ })
90
+
91
+ return {
92
+ story,
93
+ }
94
+ },
95
+
96
+ render() {
97
+ let suppressError = false
98
+ try {
99
+ const vnodes = this.$slots.default?.({
100
+ get state() {
101
+ // No variant tags
102
+ suppressError = true
103
+ return {}
104
+ },
105
+ })
106
+
107
+ // Auto stub components
108
+ if (Array.isArray(vnodes)) {
109
+ autoStubComponents(vnodes)
110
+ }
111
+
112
+ return vnodes
113
+ }
114
+ catch (e) {
115
+ if (!suppressError) {
116
+ console.error(e)
117
+ }
118
+ return null
119
+ }
120
+ },
121
+ })
@@ -0,0 +1,56 @@
1
+ import type { ServerStory, ServerVariant } from '@poveste/shared'
2
+ import { defineComponent, inject, type PropType } from 'vue'
3
+
4
+ export default defineComponent({
5
+ name: 'PovesteVariant',
6
+
7
+ props: {
8
+ title: {
9
+ type: String,
10
+ default: 'untitled',
11
+ },
12
+
13
+ id: {
14
+ type: String,
15
+ default: undefined,
16
+ },
17
+
18
+ icon: {
19
+ type: String,
20
+ default: undefined,
21
+ },
22
+
23
+ iconColor: {
24
+ type: String,
25
+ default: undefined,
26
+ },
27
+
28
+ meta: {
29
+ type: Object as PropType<ServerVariant['meta']>,
30
+ default: undefined,
31
+ },
32
+ },
33
+
34
+ setup(props) {
35
+ const story = inject<ServerStory>('story')
36
+
37
+ function generateId() {
38
+ return `${story.id}-${story.variants.length}`
39
+ }
40
+
41
+ const variant: ServerVariant = {
42
+ id: props.id ?? generateId(),
43
+ title: props.title,
44
+ icon: props.icon,
45
+ iconColor: props.iconColor,
46
+ meta: props.meta,
47
+ }
48
+
49
+ const addVariant = inject('addVariant') as (variant: ServerVariant) => void
50
+ addVariant(variant)
51
+ },
52
+
53
+ render() {
54
+ return null
55
+ },
56
+ })