@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.
- package/LICENSE +22 -0
- package/client.d.ts +2 -0
- package/collect.d.ts +2 -0
- package/components.d.ts +239 -0
- package/dist/bundled/client/app/MountStory.js +55 -0
- package/dist/bundled/client/app/RenderStory.js +74 -0
- package/dist/bundled/client/app/RouterLinkStub.js +40 -0
- package/dist/bundled/client/app/Story.js +112 -0
- package/dist/bundled/client/app/Variant.js +104 -0
- package/dist/bundled/client/app/auto-props.js +66 -0
- package/dist/bundled/client/app/global-components.js +81 -0
- package/dist/bundled/client/app/host.js +84 -0
- package/dist/bundled/client/app/index.js +6 -0
- package/dist/bundled/client/app/render-context.js +12 -0
- package/dist/bundled/client/app/util.js +70 -0
- package/dist/bundled/client/client.js +10 -0
- package/dist/bundled/client/codegen.js +196 -0
- package/dist/bundled/client/server/Story.js +79 -0
- package/dist/bundled/client/server/Variant.js +46 -0
- package/dist/bundled/client/server/run.js +51 -0
- package/dist/bundled/client/server/stub.js +8 -0
- package/dist/bundled/client/server.js +4 -0
- package/dist/bundled/node_modules/.pnpm/change-case@5.4.4/node_modules/change-case/dist/index.js +118 -0
- package/dist/client/app/MountStory.d.ts +16 -0
- package/dist/client/app/RenderStory.d.ts +38 -0
- package/dist/client/app/RouterLinkStub.d.ts +21 -0
- package/dist/client/app/Story.d.ts +33 -0
- package/dist/client/app/Variant.d.ts +78 -0
- package/dist/client/app/auto-props.d.ts +2 -0
- package/dist/client/app/global-components.d.ts +2 -0
- package/dist/client/app/host.d.ts +16 -0
- package/dist/client/app/index.d.ts +2 -0
- package/dist/client/app/render-context.d.ts +12 -0
- package/dist/client/app/util.d.ts +16 -0
- package/dist/client/client.d.ts +7 -0
- package/dist/client/codegen.d.ts +4 -0
- package/dist/client/server/Story.d.ts +81 -0
- package/dist/client/server/Variant.d.ts +52 -0
- package/dist/client/server/run.d.ts +2 -0
- package/dist/client/server/stub.d.ts +1 -0
- package/dist/client/server.d.ts +1 -0
- package/dist/commands/generate-story.client.d.ts +3 -0
- package/dist/commands/generate-story.client.js +27 -0
- package/dist/commands/generate-story.server.d.ts +3 -0
- package/dist/commands/generate-story.server.js +45 -0
- package/dist/helpers.d.ts +10 -0
- package/dist/helpers.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.node.d.ts +3 -0
- package/dist/index.node.js +54 -0
- package/dist/util/list-components.d.ts +1 -0
- package/dist/util/list-components.js +15 -0
- package/package.json +71 -0
- package/src/client/app/MountStory.ts +83 -0
- package/src/client/app/RenderStory.ts +131 -0
- package/src/client/app/RouterLinkStub.ts +48 -0
- package/src/client/app/Story.ts +209 -0
- package/src/client/app/Variant.ts +196 -0
- package/src/client/app/auto-props.ts +135 -0
- package/src/client/app/global-components.ts +131 -0
- package/src/client/app/host.ts +139 -0
- package/src/client/app/index.ts +2 -0
- package/src/client/app/render-context.ts +23 -0
- package/src/client/app/util.ts +129 -0
- package/src/client/client.ts +8 -0
- package/src/client/codegen.ts +400 -0
- package/src/client/server/Story.ts +121 -0
- package/src/client/server/Variant.ts +56 -0
- package/src/client/server/run.ts +63 -0
- package/src/client/server/stub.ts +12 -0
- package/src/client/server.ts +1 -0
- package/src/commands/generate-story.client.ts +29 -0
- package/src/commands/generate-story.server.ts +55 -0
- package/src/helpers.ts +15 -0
- package/src/index.node.ts +63 -0
- package/src/index.ts +1 -0
- package/src/util/list-components.ts +16 -0
- package/tsconfig.build.json +6 -0
- package/tsconfig.json +40 -0
- 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,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
|
+
})
|