@poveste/plugin-svelte 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 (71) hide show
  1. package/LICENSE +22 -0
  2. package/assets/histoire-svelte-text.svg +89 -0
  3. package/assets/histoire-svelte.svg +52 -0
  4. package/dist/client/MountStory.js +1 -0
  5. package/dist/client/MountStory.svelte +24 -0
  6. package/dist/client/MountVariant.js +1 -0
  7. package/dist/client/MountVariant.svelte +47 -0
  8. package/dist/client/RenderStory.js +1 -0
  9. package/dist/client/RenderStory.svelte +27 -0
  10. package/dist/client/RenderVariant.js +1 -0
  11. package/dist/client/RenderVariant.svelte +32 -0
  12. package/dist/client/Stub.js +1 -0
  13. package/dist/client/Stub.svelte +0 -0
  14. package/dist/client/Wrap.js +1 -0
  15. package/dist/client/Wrap.svelte +63 -0
  16. package/dist/client/index.d.ts +8 -0
  17. package/dist/client/index.js +1 -0
  18. package/dist/client/mount.d.ts +16 -0
  19. package/dist/client/mount.js +1 -0
  20. package/dist/client/render.d.ts +34 -0
  21. package/dist/client/render.js +1 -0
  22. package/dist/client/util.d.ts +4 -0
  23. package/dist/client/util.js +1 -0
  24. package/dist/collect/Story.js +1 -0
  25. package/dist/collect/Story.svelte +34 -0
  26. package/dist/collect/Variant.js +1 -0
  27. package/dist/collect/Variant.svelte +24 -0
  28. package/dist/collect/index.d.ts +2 -0
  29. package/dist/collect/index.js +1 -0
  30. package/dist/commands/generate-story.client.d.ts +3 -0
  31. package/dist/commands/generate-story.client.js +27 -0
  32. package/dist/commands/generate-story.server.d.ts +3 -0
  33. package/dist/commands/generate-story.server.js +52 -0
  34. package/dist/helpers.d.ts +87 -0
  35. package/dist/helpers.js +6 -0
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.js +1 -0
  38. package/dist/index.node.d.ts +3 -0
  39. package/dist/index.node.js +90 -0
  40. package/dist/util/list-components.d.ts +1 -0
  41. package/dist/util/list-components.js +15 -0
  42. package/dist/util/story-hmr.d.ts +7 -0
  43. package/dist/util/story-hmr.js +30 -0
  44. package/dist/util/svelte.d.ts +18 -0
  45. package/dist/util/svelte.js +102 -0
  46. package/package.json +57 -0
  47. package/src/client/MountStory.svelte +24 -0
  48. package/src/client/MountVariant.svelte +47 -0
  49. package/src/client/RenderStory.svelte +27 -0
  50. package/src/client/RenderVariant.svelte +32 -0
  51. package/src/client/Stub.svelte +0 -0
  52. package/src/client/Wrap.svelte +63 -0
  53. package/src/client/index.ts +12 -0
  54. package/src/client/mount.ts +113 -0
  55. package/src/client/render.ts +191 -0
  56. package/src/client/util.ts +52 -0
  57. package/src/collect/Story.svelte +34 -0
  58. package/src/collect/Variant.svelte +24 -0
  59. package/src/collect/index.ts +53 -0
  60. package/src/commands/generate-story.client.ts +29 -0
  61. package/src/commands/generate-story.server.ts +62 -0
  62. package/src/helpers.ts +96 -0
  63. package/src/index.node.ts +101 -0
  64. package/src/index.ts +1 -0
  65. package/src/util/list-components.ts +16 -0
  66. package/src/util/story-hmr.ts +52 -0
  67. package/src/util/svelte.ts +137 -0
  68. package/svelte.config.js +5 -0
  69. package/tsconfig.build.json +7 -0
  70. package/tsconfig.json +41 -0
  71. package/vite.config.ts +103 -0
@@ -0,0 +1,63 @@
1
+ <script>
2
+ import { onMount, createEventDispatcher } from 'svelte'
3
+ import {
4
+ createApp as _createApp,
5
+ h as _h,
6
+ reactive as _reactive,
7
+ } from '@poveste/vendors/vue'
8
+
9
+ export let controlComponent
10
+ export let value
11
+
12
+ const dispatch = createEventDispatcher()
13
+
14
+ let el
15
+
16
+ let app
17
+
18
+ const state = _reactive({})
19
+
20
+ function updateState (value, attrs) {
21
+ Object.assign(state, {
22
+ value,
23
+ attrs,
24
+ })
25
+ }
26
+
27
+ onMount(() => {
28
+ updateState(value, $$restProps)
29
+
30
+ app = _createApp({
31
+ render () {
32
+ const finalListeners = {}
33
+ if (controlComponent.emits) {
34
+ for (const k in controlComponent.emits) {
35
+ const key = Array.isArray(controlComponent.emits) ? controlComponent.emits[k] : k
36
+ const finalKey = key === 'input' ? 'update:modelValue' : key
37
+ finalListeners[`on${finalKey.charAt(0).toUpperCase()}${finalKey.substring(1)}`] = (...args) => {
38
+ if (key === 'update:modelValue') {
39
+ value = args[0]
40
+ } else {
41
+ dispatch(key, ...args)
42
+ }
43
+ }
44
+ }
45
+ }
46
+
47
+ return _h(controlComponent, {
48
+ modelValue: value,
49
+ ...state.attrs,
50
+ ...finalListeners,
51
+ key: 'component',
52
+ })
53
+ },
54
+ })
55
+ app.mount(el)
56
+ })
57
+
58
+ $: updateState(value, $$restProps)
59
+ </script>
60
+
61
+ <div bind:this={el}>
62
+ {controlComponent.name}
63
+ </div>
@@ -0,0 +1,12 @@
1
+ export { default as MountStory } from './mount'
2
+ export { default as RenderStory } from './render'
3
+
4
+ declare module '@poveste/shared' {
5
+ interface StoryMeta {
6
+ hasVariantChildComponents?: boolean
7
+ }
8
+ }
9
+
10
+ export function generateSourceCode() {
11
+ // noop
12
+ }
@@ -0,0 +1,113 @@
1
+ import type { Story } from '@poveste/shared'
2
+ import type {
3
+ PropType as _PropType,
4
+ } from '@poveste/vendors/vue'
5
+ import type { SvelteStorySetupApi } from '../helpers.js'
6
+ import { components } from '@poveste/controls'
7
+ import {
8
+ defineComponent as _defineComponent,
9
+ h as _h,
10
+ onMounted as _onMounted,
11
+ onUnmounted as _onUnmounted,
12
+ ref as _ref,
13
+ watch as _watch,
14
+ } from '@poveste/vendors/vue'
15
+ // @ts-expect-error virtual module id
16
+ import * as generatedSetup from 'virtual:$histoire-generated-global-setup'
17
+ // @ts-expect-error virtual module id
18
+ import * as setup from 'virtual:$histoire-setup'
19
+ import {
20
+ callSetupFunctions,
21
+ mountSvelteComponent,
22
+ } from '../util/svelte.js'
23
+ import MountStorySvelte from './MountStory.svelte'
24
+ import MountVariantSvelte from './MountVariant.svelte'
25
+ import StubComponent from './Stub.svelte'
26
+
27
+ export default _defineComponent({
28
+ name: 'MountStory',
29
+
30
+ props: {
31
+ story: {
32
+ type: Object as _PropType<Story>,
33
+ required: true,
34
+ },
35
+ },
36
+
37
+ setup(props) {
38
+ const el = _ref<HTMLDivElement>()
39
+ let app: any
40
+ let target: HTMLDivElement
41
+ let destroyApp: (() => void) | null = null
42
+
43
+ async function mountStory() {
44
+ target = document.createElement('div')
45
+ el.value.appendChild(target)
46
+
47
+ const mountedApp = await mountSvelteComponent(props.story.file.component, {
48
+ target,
49
+ props: {
50
+ Hst: {
51
+ Story: MountStorySvelte,
52
+ Variant: MountVariantSvelte,
53
+ ...getControls(),
54
+ },
55
+ },
56
+ context: new Map(Object.entries({
57
+ __hstStory: props.story,
58
+ })),
59
+ }, 'client')
60
+ app = mountedApp.app
61
+ destroyApp = mountedApp.destroy
62
+
63
+ const setupApi: SvelteStorySetupApi = {
64
+ app,
65
+ story: props.story,
66
+ variant: null,
67
+ }
68
+
69
+ await callSetupFunctions(generatedSetup, setup, setupApi)
70
+ }
71
+
72
+ function unmountStory() {
73
+ destroyApp?.()
74
+ destroyApp = null
75
+ if (target) {
76
+ target.parentNode?.removeChild(target)
77
+ target = null
78
+ }
79
+ app = null
80
+ }
81
+
82
+ _watch(() => props.story.id, async () => {
83
+ unmountStory()
84
+ await mountStory()
85
+ })
86
+
87
+ _onMounted(async () => {
88
+ await mountStory()
89
+ })
90
+
91
+ _onUnmounted(() => {
92
+ unmountStory()
93
+ })
94
+
95
+ return {
96
+ el,
97
+ }
98
+ },
99
+
100
+ render() {
101
+ return _h('div', {
102
+ ref: 'el',
103
+ })
104
+ },
105
+ })
106
+
107
+ function getControls() {
108
+ const result: Record<string, any> = {}
109
+ for (const key in components) {
110
+ result[key.substring(3)] = StubComponent
111
+ }
112
+ return result
113
+ }
@@ -0,0 +1,191 @@
1
+ import type { Story, Variant } from '@poveste/shared'
2
+ import type {
3
+ PropType as _PropType,
4
+ } from '@poveste/vendors/vue'
5
+ import type { SvelteStorySetupApi, SvelteStorySetupHandler } from '../helpers.js'
6
+ import { components } from '@poveste/controls'
7
+ import {
8
+ defineComponent as _defineComponent,
9
+ h as _h,
10
+ onMounted as _onMounted,
11
+ onUnmounted as _onUnmounted,
12
+ ref as _ref,
13
+ watch as _watch,
14
+ } from '@poveste/vendors/vue'
15
+ // @ts-expect-error virtual module id
16
+ import * as generatedSetup from 'virtual:$histoire-generated-global-setup'
17
+ // @ts-expect-error virtual module id
18
+ import * as setup from 'virtual:$histoire-setup'
19
+ import {
20
+ callSetupFunctions,
21
+ createWrappedComponent,
22
+ getLegacyStateApi,
23
+ mountSvelteComponent,
24
+ } from '../util/svelte.js'
25
+ import RenderStorySvelte from './RenderStory.svelte'
26
+ import RenderVariantSvelte from './RenderVariant.svelte'
27
+ import { syncState } from './util'
28
+ import Wrap from './Wrap.svelte'
29
+
30
+ export default _defineComponent({
31
+ name: 'RenderStory',
32
+
33
+ props: {
34
+ variant: {
35
+ type: Object as _PropType<Variant>,
36
+ required: true,
37
+ },
38
+
39
+ story: {
40
+ type: Object as _PropType<Story>,
41
+ required: true,
42
+ },
43
+
44
+ slotName: {
45
+ type: String,
46
+ default: 'default',
47
+ },
48
+ },
49
+
50
+ setup(props, { emit }) {
51
+ const el = _ref<HTMLDivElement>()
52
+ let app: any
53
+ let target: HTMLDivElement
54
+
55
+ let tearDownHandlers: (() => void)[] = []
56
+
57
+ function documentOn(event, cb) {
58
+ document.addEventListener(event, cb)
59
+ const off = () => document.removeEventListener(event, cb)
60
+ tearDownHandlers.push(off)
61
+ return {
62
+ off,
63
+ }
64
+ }
65
+
66
+ async function mountStory() {
67
+ target = document.createElement('div')
68
+ el.value.appendChild(target)
69
+
70
+ let components = []
71
+ const { off: registerComponentOff } = documentOn('SvelteRegisterComponent', (e) => {
72
+ const { component } = e.detail
73
+ components.push(component)
74
+ })
75
+
76
+ const mountedApp = await mountSvelteComponent(props.story.file.component, {
77
+ target,
78
+ props: {
79
+ Hst: {
80
+ Story: RenderStorySvelte,
81
+ Variant: RenderVariantSvelte,
82
+ ...getControls(),
83
+ },
84
+ },
85
+ context: new Map(Object.entries({
86
+ __hstStory: props.story,
87
+ __hstVariant: props.variant,
88
+ __hstSlot: props.slotName,
89
+ })),
90
+ }, 'client')
91
+ app = mountedApp.app
92
+ tearDownHandlers.push(() => {
93
+ mountedApp.destroy()
94
+ })
95
+
96
+ let appComponent = components.find(c => c.$$ && app?.$$ && c.$$ === app.$$) ?? app
97
+ registerComponentOff()
98
+ components = []
99
+
100
+ function patchReplaceIfAvailable() {
101
+ const origReplace = appComponent?.$replace
102
+ if (!origReplace) {
103
+ return
104
+ }
105
+
106
+ appComponent.$replace = (...args) => {
107
+ const result = origReplace.apply(appComponent, args)
108
+ appComponent = result ?? appComponent
109
+ return result
110
+ }
111
+ }
112
+ patchReplaceIfAvailable()
113
+
114
+ const stateApi = getLegacyStateApi(appComponent)
115
+ if (stateApi) {
116
+ const { apply, stop } = syncState(props.variant.state, (value) => {
117
+ stateApi.injectState(value)
118
+ })
119
+ tearDownHandlers.push(stop)
120
+
121
+ let frameId: number
122
+ const syncFromComponent = () => {
123
+ apply(stateApi.captureState())
124
+ frameId = requestAnimationFrame(syncFromComponent)
125
+ }
126
+
127
+ frameId = requestAnimationFrame(syncFromComponent)
128
+ tearDownHandlers.push(() => {
129
+ cancelAnimationFrame(frameId)
130
+ })
131
+
132
+ apply(stateApi.captureState())
133
+ }
134
+
135
+ const setupApi: SvelteStorySetupApi = {
136
+ app,
137
+ story: props.story,
138
+ variant: props.variant,
139
+ }
140
+
141
+ await callSetupFunctions(generatedSetup, setup, setupApi, props.variant.setupApp as SvelteStorySetupHandler | null)
142
+
143
+ emit('ready')
144
+ }
145
+
146
+ function unmountStory() {
147
+ tearDownHandlers.forEach(fn => fn())
148
+ tearDownHandlers = []
149
+ if (target) {
150
+ target.parentNode?.removeChild(target)
151
+ target = null
152
+ }
153
+ app = null
154
+ }
155
+
156
+ _watch(() => props.story.id, async () => {
157
+ unmountStory()
158
+ await mountStory()
159
+ })
160
+
161
+ _onMounted(async () => {
162
+ await mountStory()
163
+ })
164
+
165
+ _onUnmounted(() => {
166
+ unmountStory()
167
+ })
168
+
169
+ return {
170
+ el,
171
+ }
172
+ },
173
+
174
+ render() {
175
+ return _h('div', {
176
+ ref: 'el',
177
+ })
178
+ },
179
+ })
180
+
181
+ function getControls() {
182
+ const result: Record<string, any> = {}
183
+ for (const key in components) {
184
+ result[key.substring(3)] = wrapComponent(components[key])
185
+ }
186
+ return result
187
+ }
188
+
189
+ function wrapComponent(controlComponent) {
190
+ return createWrappedComponent(Wrap, controlComponent)
191
+ }
@@ -0,0 +1,52 @@
1
+ import { applyState, clone } from '@poveste/shared'
2
+ import { watch as _watch } from '@poveste/vendors/vue'
3
+
4
+ function cleanupState(state: Record<string, any>): Record<string, any> {
5
+ const result = {}
6
+ for (const key in state) {
7
+ if (key === 'Hst') continue
8
+ const value = state[key]
9
+ if (typeof value === 'function') continue
10
+ if (typeof value === 'undefined') continue
11
+ if (value instanceof HTMLElement) continue
12
+ if (typeof value === 'object' && value?.$$) continue
13
+ result[key] = value
14
+ }
15
+ return result
16
+ }
17
+
18
+ export function syncState(variantState, onChange: (state) => unknown) {
19
+ let syncing = false
20
+
21
+ const _stop = _watch(() => variantState, (value) => {
22
+ if (value == null) return
23
+ if (!syncing) {
24
+ syncing = true
25
+ onChange(cleanupState(value))
26
+ }
27
+ else {
28
+ syncing = false
29
+ }
30
+ }, {
31
+ deep: true,
32
+ immediate: true,
33
+ })
34
+
35
+ function apply(value) {
36
+ if (value == null) return
37
+ if (!syncing) {
38
+ syncing = true
39
+ applyState(variantState, clone(cleanupState(value)))
40
+ }
41
+ else {
42
+ syncing = false
43
+ }
44
+ }
45
+
46
+ return {
47
+ apply,
48
+ stop() {
49
+ _stop()
50
+ },
51
+ }
52
+ }
@@ -0,0 +1,34 @@
1
+ <script>
2
+ import { getContext, setContext } from 'svelte'
3
+
4
+ export let title = null
5
+ export let id = null
6
+ export let group = null
7
+ export let layout = null
8
+ export let icon = null
9
+ export let iconColor = null
10
+ export let docsOnly = false
11
+
12
+ const addStory = getContext('__hstAddStory')
13
+ const file = getContext('__hstStoryFile')
14
+
15
+ const story = {
16
+ id: id ?? file.id,
17
+ title: title ?? file.fileName,
18
+ group,
19
+ layout,
20
+ icon,
21
+ iconColor,
22
+ docsOnly,
23
+ variants: [],
24
+ }
25
+
26
+ addStory(story)
27
+
28
+ setContext('__hstStory', story)
29
+ setContext('__hstAddVariant', (variant) => {
30
+ story.variants.push(variant)
31
+ })
32
+ </script>
33
+
34
+ <slot />
@@ -0,0 +1,24 @@
1
+ <script>
2
+ import { getContext } from 'svelte'
3
+
4
+ export let title = 'untitled'
5
+ export let id = null
6
+ export let icon = null
7
+ export let iconColor = null
8
+
9
+ const story = getContext('__hstStory')
10
+ const addVariant = getContext('__hstAddVariant')
11
+
12
+ function generateId () {
13
+ return `${story.id}-${story.variants.length}`
14
+ }
15
+
16
+ const variant = {
17
+ id: id ?? generateId(),
18
+ title,
19
+ icon,
20
+ iconColor,
21
+ }
22
+
23
+ addVariant(variant)
24
+ </script>
@@ -0,0 +1,53 @@
1
+ import type { ServerRunPayload } from '@poveste/shared'
2
+ import type { SvelteStorySetupApi } from '../helpers.js'
3
+ import { tick } from 'svelte'
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
+ callSetupFunctions,
10
+ mountSvelteComponent,
11
+ } from '../util/svelte.js'
12
+ import Story from './Story.svelte'
13
+ import Variant from './Variant.svelte'
14
+
15
+ export async function run({ file, el, storyData }: ServerRunPayload) {
16
+ const { default: Comp } = await import(/* @vite-ignore */ file.moduleId)
17
+
18
+ const mountedApp = await mountSvelteComponent(Comp, {
19
+ target: el,
20
+ props: {
21
+ Hst: {
22
+ Story,
23
+ Variant,
24
+ },
25
+ },
26
+ context: new Map(Object.entries({
27
+ __hstAddStory(data) {
28
+ storyData.push(data)
29
+ },
30
+ __hstStoryFile: file,
31
+ })),
32
+ }, 'client')
33
+ const app = mountedApp.app
34
+
35
+ const setupApi: SvelteStorySetupApi = {
36
+ app,
37
+ story: null,
38
+ variant: null,
39
+ }
40
+
41
+ await callSetupFunctions(generatedSetup, setup, setupApi)
42
+
43
+ await tick()
44
+
45
+ if (!storyData[0]?.variants.length) {
46
+ storyData[0].variants.push({
47
+ id: '_default',
48
+ title: 'default',
49
+ })
50
+ }
51
+
52
+ mountedApp.destroy()
53
+ }
@@ -0,0 +1,29 @@
1
+ import type { ClientCommandOptions } from 'poveste'
2
+ import { kebabCase } from 'change-case'
3
+ import { openStory, sendEvent } from 'poveste/plugin'
4
+
5
+ export default {
6
+ prompts: [
7
+ {
8
+ field: 'component',
9
+ label: 'Choose a component',
10
+ type: 'select',
11
+ options: async search => sendEvent('listSvelteComponents', { search }),
12
+ required: true,
13
+ },
14
+ {
15
+ field: 'fileName',
16
+ label: 'File name',
17
+ type: 'text',
18
+ required: true,
19
+ defaultValue: answers => answers.component?.replace(/[^/]+\/([^/]+)\.svelte$/, '$1.story.svelte'),
20
+ },
21
+ ],
22
+ clientAction: (params) => {
23
+ const index = params.component.lastIndexOf('/')
24
+ const dirname = params.component.substring(0, index + 1)
25
+ const file = `${dirname}${params.fileName}`
26
+ const storyId = kebabCase(file.toLowerCase())
27
+ openStory(storyId)
28
+ },
29
+ } as ClientCommandOptions
@@ -0,0 +1,62 @@
1
+ import type { PluginCommand } from 'poveste'
2
+ import fs from 'node:fs'
3
+ import launchEditor from 'launch-editor'
4
+ import path from 'pathe'
5
+
6
+ export default {
7
+ id: 'histoire:plugin-svelte:generate-story',
8
+ label: 'Generate Svelte story from component',
9
+ icon: 'https://svelte.dev/favicon.png',
10
+ searchText: 'generate create',
11
+ async serverAction(params) {
12
+ const targetFile = path.join(path.dirname(params.component), params.fileName)
13
+
14
+ if (fs.existsSync(targetFile)) {
15
+ throw new Error(`File ${targetFile} already exists`)
16
+ }
17
+
18
+ const { component, componentName, isTs } = await getComponentInfo(params.component)
19
+
20
+ const content = `<script${isTs ? ' lang="ts"' : ''}>
21
+ ${isTs
22
+ ? `import type { Hst } from '@poveste/plugin-svelte'\n
23
+ `
24
+ : ''}import ${componentName} from './${component}'
25
+
26
+ export let Hst${isTs ? ': Hst' : ''}
27
+ </script>
28
+
29
+ <Hst.Story>
30
+ <Hst.Variant title="Default">
31
+ <${componentName} />
32
+
33
+ <svelte:fragment slot="controls">
34
+ <!-- Put controls here -->
35
+ </svelte:fragment>
36
+ </Hst.Variant>
37
+ </Hst.Story>
38
+ `
39
+
40
+ await fs.promises.writeFile(targetFile, content, 'utf-8')
41
+
42
+ launchEditor(targetFile)
43
+ },
44
+ clientSetupFile: '@poveste/plugin-svelte/dist/commands/generate-story.client.js',
45
+ } as PluginCommand
46
+
47
+ async function isComponentTs(component: string) {
48
+ const componentContent = await fs.promises.readFile(component, 'utf-8')
49
+ return componentContent.includes('lang="ts"')
50
+ }
51
+
52
+ async function getComponentInfo(file: string) {
53
+ const component = path.basename(file)
54
+ const componentName = component.replace(path.extname(component), '')
55
+ const isTs = await isComponentTs(file)
56
+
57
+ return {
58
+ component,
59
+ componentName,
60
+ isTs,
61
+ }
62
+ }