@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,196 @@
1
+ import type { Variant } from '@poveste/shared'
2
+ import type { ComputedRef, PropType } from 'vue'
3
+ import { applyState } from '@poveste/shared'
4
+ import { computed, defineComponent, getCurrentInstance, inject, onBeforeUnmount, useAttrs } from 'vue'
5
+ import { syncVariantAutoProps } from './auto-props.js'
6
+ import { useRenderContext } from './render-context.js'
7
+ import { syncStateBundledAndExternal, toRawDeep } from './util.js'
8
+
9
+ // const logLocation = location.href.includes('__sandbox') ? '[Sandbox]' : '[Host]'
10
+
11
+ export default defineComponent({
12
+
13
+ name: 'Variant',
14
+ __histoireType: 'variant',
15
+
16
+ props: {
17
+ initState: {
18
+ type: Function as PropType<() => any | Promise<any>>,
19
+ default: undefined,
20
+ },
21
+
22
+ source: {
23
+ type: String,
24
+ default: undefined,
25
+ },
26
+
27
+ responsiveDisabled: {
28
+ type: Boolean,
29
+ default: false,
30
+ },
31
+
32
+ autoPropsDisabled: {
33
+ type: Boolean,
34
+ default: false,
35
+ },
36
+
37
+ setupApp: {
38
+ type: Function,
39
+ default: undefined,
40
+ },
41
+
42
+ meta: {
43
+ type: Object as PropType<Variant['meta']>,
44
+ default: undefined,
45
+ },
46
+
47
+ implicit: {
48
+ type: Boolean,
49
+ default: false,
50
+ },
51
+ },
52
+
53
+ async setup(props) {
54
+ const attrs = useAttrs() as {
55
+ variant?: Variant
56
+ }
57
+ const vm = getCurrentInstance()
58
+ const story = inject<ComputedRef<any>>('story')
59
+ const implicitState = inject<() => any>('implicitState')
60
+ const renderContext = useRenderContext()
61
+ let lastPropsTypesSnapshot: string
62
+ let renderVariant: Variant
63
+ let renderStateSync: ReturnType<typeof syncStateBundledAndExternal>
64
+
65
+ const mountVariant = computed(() => attrs.variant)
66
+
67
+ if (renderContext?.mode !== 'render' && typeof props.initState === 'function' && mountVariant.value) {
68
+ const state = await props.initState()
69
+ applyState(mountVariant.value.state, toRawDeep(state))
70
+ }
71
+
72
+ if (renderContext?.mode !== 'render' && mountVariant.value && implicitState) {
73
+ syncStateBundledAndExternal(mountVariant.value.state, implicitState())
74
+ }
75
+
76
+ function updateVariant(variant: Variant) {
77
+ Object.assign(variant, {
78
+ slots: () => vm.proxy.$slots,
79
+ source: props.source,
80
+ responsiveDisabled: props.responsiveDisabled,
81
+ autoPropsDisabled: props.autoPropsDisabled,
82
+ setupApp: props.setupApp,
83
+ meta: props.meta,
84
+ configReady: true,
85
+ })
86
+
87
+ if (!props.implicit && !story.value?.meta?.hasVariantChildComponents) {
88
+ if (!story.value.meta) {
89
+ story.value.meta = {}
90
+ }
91
+
92
+ Object.assign(story.value.meta, {
93
+ hasVariantChildComponents: true,
94
+ })
95
+ }
96
+ }
97
+
98
+ function resolveVariant() {
99
+ if (attrs.variant) {
100
+ return attrs.variant
101
+ }
102
+
103
+ if (renderContext?.mode !== 'render') {
104
+ return null
105
+ }
106
+
107
+ if (!renderVariant) {
108
+ renderVariant = story.value?.variants[renderContext.nextVariantIndex.value]
109
+ renderContext.nextVariantIndex.value++
110
+ }
111
+
112
+ return renderVariant
113
+ }
114
+
115
+ function renderVariantSlot(variant: Variant) {
116
+ if (renderContext.slotName === 'controls') {
117
+ return vm.proxy.$slots.controls?.({
118
+ state: renderContext.externalState,
119
+ }) ?? null
120
+ }
121
+
122
+ if (renderContext.slotName !== 'default') {
123
+ return null
124
+ }
125
+
126
+ const vnodes = vm.proxy.$slots.default?.({
127
+ state: renderContext.externalState,
128
+ }) ?? null
129
+
130
+ if (vnodes && !variant.autoPropsDisabled) {
131
+ lastPropsTypesSnapshot = syncVariantAutoProps(
132
+ variant,
133
+ vnodes,
134
+ renderContext.externalState,
135
+ lastPropsTypesSnapshot,
136
+ )
137
+ }
138
+
139
+ return vnodes
140
+ }
141
+
142
+ function syncRenderVariantState(variant: Variant) {
143
+ if (!implicitState) {
144
+ return
145
+ }
146
+
147
+ const shouldSync = renderContext?.mode === 'render'
148
+ && renderContext.currentVariant?.id === variant.id
149
+
150
+ if (shouldSync && !renderStateSync) {
151
+ renderStateSync = syncStateBundledAndExternal(variant.state, implicitState())
152
+ }
153
+ else if (!shouldSync && renderStateSync) {
154
+ renderStateSync.stop()
155
+ renderStateSync = null
156
+ }
157
+ }
158
+
159
+ if (mountVariant.value) {
160
+ updateVariant(mountVariant.value)
161
+ }
162
+
163
+ onBeforeUnmount(() => {
164
+ renderStateSync?.stop()
165
+ })
166
+
167
+ return {
168
+ renderContext,
169
+ renderVariantSlot,
170
+ resolveVariant,
171
+ syncRenderVariantState,
172
+ updateVariant,
173
+ }
174
+ },
175
+
176
+ render() {
177
+ const variant = this.resolveVariant()
178
+
179
+ if (!variant) {
180
+ return null
181
+ }
182
+
183
+ this.updateVariant(variant)
184
+ this.syncRenderVariantState(variant)
185
+
186
+ if (this.renderContext?.mode !== 'render') {
187
+ return null
188
+ }
189
+
190
+ if (this.renderContext.currentVariant?.id !== variant.id) {
191
+ return null
192
+ }
193
+
194
+ return this.renderVariantSlot(variant)
195
+ },
196
+ })
@@ -0,0 +1,135 @@
1
+ import type { AutoPropComponentDefinition, PropDefinition, Variant } from '@poveste/shared'
2
+ import { applyState } from '@poveste/shared'
3
+ import { getTagName } from '../codegen'
4
+
5
+ export function syncVariantAutoProps(
6
+ variant: Variant,
7
+ vnodes: any,
8
+ externalState: Variant['state'],
9
+ lastSnapshot: string,
10
+ ) {
11
+ const defs = scanForAutoProps(vnodes, externalState)
12
+ const snapshot = JSON.stringify(defs)
13
+
14
+ if (!lastSnapshot || lastSnapshot !== snapshot) {
15
+ applyState(variant.state, {
16
+ _hPropDefs: defs,
17
+ })
18
+
19
+ if (!variant.state._hPropState) {
20
+ applyState(variant.state, {
21
+ _hPropState: {},
22
+ })
23
+ }
24
+ }
25
+
26
+ return snapshot
27
+ }
28
+
29
+ function scanForAutoProps(vnodes: any, externalState: Variant['state']) {
30
+ const result: AutoPropComponentDefinition[] = []
31
+ const traversalState = {
32
+ index: 0,
33
+ }
34
+
35
+ visitVNodes(vnodes, externalState, traversalState, result)
36
+
37
+ return result.filter(def => def.props.length)
38
+ }
39
+
40
+ function visitVNodes(vnodes: any, externalState: Variant['state'], traversalState: { index: number }, result: AutoPropComponentDefinition[]) {
41
+ for (const vnode of normalizeVNodes(vnodes)) {
42
+ if (!vnode) continue
43
+
44
+ if (typeof vnode.type === 'object') {
45
+ const index = traversalState.index++
46
+ const propDefs: PropDefinition[] = []
47
+
48
+ for (const key in vnode.type.props) {
49
+ const prop = vnode.type.props[key]
50
+ let types
51
+ let defaultValue
52
+
53
+ if (prop) {
54
+ const rawTypes = Array.isArray(prop.type)
55
+ ? prop.type
56
+ : typeof prop === 'function'
57
+ ? [prop]
58
+ : [prop.type]
59
+
60
+ types = rawTypes.map((type) => {
61
+ switch (type) {
62
+ case String:
63
+ return 'string'
64
+ case Number:
65
+ return 'number'
66
+ case Boolean:
67
+ return 'boolean'
68
+ case Object:
69
+ return 'object'
70
+ case Array:
71
+ return 'array'
72
+ default:
73
+ return 'unknown'
74
+ }
75
+ })
76
+
77
+ defaultValue = typeof prop.default === 'function'
78
+ ? prop.default.toString()
79
+ : prop.default
80
+ }
81
+
82
+ propDefs.push({
83
+ name: key,
84
+ types,
85
+ required: prop?.required,
86
+ default: defaultValue,
87
+ })
88
+
89
+ if (externalState?._hPropState?.[index]?.[key] != null) {
90
+ if (!vnode.props) {
91
+ vnode.props = {}
92
+ }
93
+ vnode.props[key] = externalState._hPropState[index][key]
94
+
95
+ if (!vnode.dynamicProps) {
96
+ vnode.dynamicProps = []
97
+ }
98
+
99
+ if (!vnode.dynamicProps.includes(key)) {
100
+ vnode.dynamicProps.push(key)
101
+ }
102
+ }
103
+ }
104
+
105
+ result.push({
106
+ name: getTagName(vnode),
107
+ index,
108
+ props: propDefs,
109
+ } as AutoPropComponentDefinition)
110
+ }
111
+
112
+ if (Array.isArray(vnode.children)) {
113
+ visitVNodes(vnode.children, externalState, traversalState, result)
114
+ }
115
+ }
116
+ }
117
+
118
+ function normalizeVNodes(vnodes: any) {
119
+ if (!Array.isArray(vnodes)) {
120
+ return vnodes == null ? [] : [vnodes]
121
+ }
122
+
123
+ const result = []
124
+
125
+ for (const vnode of vnodes) {
126
+ if (Array.isArray(vnode)) {
127
+ result.push(...normalizeVNodes(vnode))
128
+ }
129
+ else if (vnode != null) {
130
+ result.push(vnode)
131
+ }
132
+ }
133
+
134
+ return result
135
+ }
@@ -0,0 +1,131 @@
1
+ import type {
2
+ App as _App,
3
+ } from '@poveste/vendors/vue'
4
+ import type {
5
+ App,
6
+ } from 'vue'
7
+ import { components } from '@poveste/controls'
8
+ import {
9
+ createApp as _createApp,
10
+ h as _h,
11
+ reactive as _reactive,
12
+ } from '@poveste/vendors/vue'
13
+ import {
14
+ defineComponent,
15
+ h,
16
+ onBeforeUnmount,
17
+ onBeforeUpdate,
18
+ onMounted,
19
+ onUpdated,
20
+ ref,
21
+ } from 'vue'
22
+ import Story from './Story'
23
+ import Variant from './Variant'
24
+
25
+ export function registerGlobalComponents(app: App) {
26
+ app.component('Story', Story)
27
+
28
+ app.component('Variant', Variant)
29
+
30
+ for (const key in components) {
31
+ app.component(key, wrapControlComponent(components[key]))
32
+ }
33
+ }
34
+
35
+ function wrapControlComponent(controlComponent) {
36
+ return defineComponent({
37
+ name: controlComponent.name,
38
+ inheritAttrs: false,
39
+ setup(props, { attrs }) {
40
+ const el = ref<HTMLDivElement>()
41
+ const slotEl = ref<HTMLDivElement>()
42
+
43
+ // Attrs
44
+
45
+ const state = _reactive({})
46
+
47
+ function applyState(data) {
48
+ Object.assign(state, data)
49
+ }
50
+
51
+ applyState(attrs)
52
+ onBeforeUpdate(() => {
53
+ applyState(attrs)
54
+ })
55
+
56
+ // Slots
57
+
58
+ let newSlotCalls = []
59
+ const slotCalls = ref([])
60
+
61
+ function moveSlotContent() {
62
+ slotCalls.value.forEach((props, index) => {
63
+ const renderedEl = slotEl.value.querySelector(`[renderslotid="${index}"]`)
64
+ if (!renderedEl) return
65
+ const targetEl = el.value.querySelector(`[slotid="${index}"]`)
66
+ while (targetEl.firstChild) {
67
+ targetEl.removeChild(targetEl.lastChild)
68
+ }
69
+ targetEl.appendChild(renderedEl)
70
+ })
71
+ }
72
+
73
+ // App
74
+
75
+ let app: _App
76
+
77
+ onMounted(() => {
78
+ app = _createApp({
79
+ mounted() {
80
+ slotCalls.value = newSlotCalls
81
+ newSlotCalls = []
82
+ },
83
+ updated() {
84
+ slotCalls.value = newSlotCalls
85
+ newSlotCalls = []
86
+ },
87
+ render() {
88
+ return _h(controlComponent, {
89
+ ...state,
90
+ key: 'component',
91
+ }, {
92
+ default: (props) => {
93
+ newSlotCalls.push(props)
94
+ return _h('div', {
95
+ slotId: newSlotCalls.length - 1,
96
+ })
97
+ },
98
+ })
99
+ },
100
+ })
101
+ app.mount(el.value)
102
+ })
103
+
104
+ onUpdated(() => {
105
+ moveSlotContent()
106
+ })
107
+
108
+ onBeforeUnmount(() => {
109
+ app.unmount()
110
+ })
111
+
112
+ return {
113
+ el,
114
+ slotEl,
115
+ slotCalls,
116
+ }
117
+ },
118
+ render() {
119
+ return [
120
+ h('div', {
121
+ ref: 'el',
122
+ }),
123
+ h('div', {
124
+ ref: 'slotEl',
125
+ }, this.slotCalls.map((props, index) => h('div', {
126
+ renderSlotId: index,
127
+ }, this.$slots.default(props)))),
128
+ ]
129
+ },
130
+ })
131
+ }
@@ -0,0 +1,139 @@
1
+ import type { Story, Variant } from '@poveste/shared'
2
+ import type { App, Component, PropType, VNode } from 'vue'
3
+ import type { Vue3StorySetupApi, Vue3StorySetupHandler } from '../../helpers.js'
4
+ // @ts-expect-error virtual module id
5
+ import * as generatedSetup from 'virtual:$histoire-generated-global-setup'
6
+ // @ts-expect-error virtual module id
7
+ import * as setup from 'virtual:$histoire-setup'
8
+ import {
9
+ createApp,
10
+ defineComponent,
11
+ h,
12
+ Suspense,
13
+ } from 'vue'
14
+ import { registerGlobalComponents } from './global-components.js'
15
+ import { type PreviewRenderContext, provideRenderContext } from './render-context.js'
16
+ import { RouterLinkStub } from './RouterLinkStub'
17
+
18
+ interface PreviewHostOptions {
19
+ name: string
20
+ el: HTMLElement
21
+ getStory: () => Story
22
+ getVariant: () => Variant | null
23
+ renderContext: PreviewRenderContext
24
+ wrapInDiv?: boolean
25
+ }
26
+
27
+ const PreviewHostRoot = defineComponent({
28
+ name: 'PreviewHostRoot',
29
+
30
+ props: {
31
+ story: {
32
+ type: Object as PropType<Story>,
33
+ required: true,
34
+ },
35
+
36
+ renderContext: {
37
+ type: Object as PropType<PreviewRenderContext>,
38
+ required: true,
39
+ },
40
+ },
41
+
42
+ setup(props) {
43
+ provideRenderContext(props.renderContext)
44
+
45
+ return () => h(props.story.file.component, {
46
+ story: props.story,
47
+ })
48
+ },
49
+ })
50
+
51
+ export function createPreviewHost(options: PreviewHostOptions) {
52
+ let app: App = null
53
+ let target: HTMLDivElement = null
54
+
55
+ async function mount() {
56
+ const wrappers: Component[] = []
57
+
58
+ target = document.createElement('div')
59
+ options.el.appendChild(target)
60
+
61
+ app = createApp({
62
+ name: options.name,
63
+
64
+ render: () => {
65
+ let vnode: VNode = h(PreviewHostRoot, {
66
+ story: options.getStory(),
67
+ renderContext: options.renderContext,
68
+ })
69
+
70
+ for (const wrapper of wrappers) {
71
+ const child = vnode
72
+ vnode = h(wrapper, {
73
+ story: options.getStory(),
74
+ variant: options.getVariant(),
75
+ }, () => child)
76
+ }
77
+
78
+ if (options.wrapInDiv) {
79
+ vnode = h('div', vnode)
80
+ }
81
+
82
+ return h(Suspense, {}, vnode)
83
+ },
84
+ })
85
+
86
+ registerGlobalComponents(app)
87
+ app.component('RouterLink', RouterLinkStub)
88
+
89
+ const setupApi: Vue3StorySetupApi = {
90
+ app,
91
+ story: options.getStory(),
92
+ variant: options.getVariant(),
93
+ addWrapper: (wrapper) => {
94
+ wrappers.unshift(wrapper)
95
+ },
96
+ }
97
+
98
+ await runSetupHooks(setupApi)
99
+
100
+ app.mount(target)
101
+ }
102
+
103
+ function unmount() {
104
+ app?.unmount()
105
+ app = null
106
+
107
+ if (target) {
108
+ target.parentNode?.removeChild(target)
109
+ target = null
110
+ }
111
+ }
112
+
113
+ function forceUpdate() {
114
+ app?._instance?.proxy?.$forceUpdate()
115
+ }
116
+
117
+ return {
118
+ mount,
119
+ unmount,
120
+ forceUpdate,
121
+ }
122
+ }
123
+
124
+ async function runSetupHooks(setupApi: Vue3StorySetupApi) {
125
+ if (typeof generatedSetup?.setupVue3 === 'function') {
126
+ const setupFn = generatedSetup.setupVue3 as Vue3StorySetupHandler
127
+ await setupFn(setupApi)
128
+ }
129
+
130
+ if (typeof setup?.setupVue3 === 'function') {
131
+ const setupFn = setup.setupVue3 as Vue3StorySetupHandler
132
+ await setupFn(setupApi)
133
+ }
134
+
135
+ if (typeof setupApi.variant?.setupApp === 'function') {
136
+ const setupFn = setupApi.variant.setupApp as Vue3StorySetupHandler
137
+ await setupFn(setupApi)
138
+ }
139
+ }
@@ -0,0 +1,2 @@
1
+ export { default as MountStory } from './MountStory'
2
+ export { default as RenderStory } from './RenderStory'
@@ -0,0 +1,23 @@
1
+ import type { Variant } from '@poveste/shared'
2
+ import type { InjectionKey } from 'vue'
3
+ import { inject, provide } from 'vue'
4
+
5
+ export interface PreviewRenderContext {
6
+ mode: 'mount' | 'render'
7
+ slotName: string
8
+ currentVariant: Variant | null
9
+ externalState: Variant['state'] | null
10
+ nextVariantIndex: {
11
+ value: number
12
+ }
13
+ }
14
+
15
+ const previewRenderContextKey: InjectionKey<PreviewRenderContext> = Symbol('histoire-preview-render-context')
16
+
17
+ export function provideRenderContext(value: PreviewRenderContext) {
18
+ provide(previewRenderContextKey, value)
19
+ }
20
+
21
+ export function useRenderContext() {
22
+ return inject(previewRenderContextKey, null)
23
+ }