@poveste/plugin-svelte 0.6.1 → 0.8.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.
@@ -1,131 +0,0 @@
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
- import { writable } from 'svelte/store'
16
- // @ts-expect-error virtual module id
17
- import * as generatedSetup from 'virtual:$poveste-generated-global-setup'
18
- // @ts-expect-error virtual module id
19
- import * as setup from 'virtual:$poveste-setup'
20
- import {
21
- callSetupFunctions,
22
- mountSvelteComponent,
23
- } from '../util/svelte.js'
24
- import MountStorySvelte from './MountStory.svelte'
25
- import MountVariantSvelte from './MountVariant.svelte'
26
- import StubComponent from './Stub.svelte'
27
-
28
- export default _defineComponent({
29
- name: 'MountStory',
30
-
31
- props: {
32
- story: {
33
- type: Object as _PropType<Story>,
34
- required: true,
35
- },
36
-
37
- // Set by a sandbox: the one variant this realm serves. The story's `{#each}`
38
- // still instantiates a MountVariant per variant — that is the user's
39
- // template — but the others can skip their bookkeeping (#197).
40
- targetVariantId: {
41
- type: String,
42
- default: null,
43
- },
44
- },
45
-
46
- setup(props) {
47
- const el = _ref<HTMLDivElement>()
48
- let app: any
49
- let target: HTMLDivElement
50
- let destroyApp: (() => void) | null = null
51
-
52
- // A store rather than a value, so a retargeted realm (#240) can tell the
53
- // variant it now serves to register without remounting the story — the
54
- // context is fixed at mount, a store subscription is not.
55
- const targetVariantId = writable<string | null>(props.targetVariantId)
56
- _watch(() => props.targetVariantId, (id) => {
57
- targetVariantId.set(id)
58
- })
59
-
60
- async function mountStory() {
61
- target = document.createElement('div')
62
- el.value.appendChild(target)
63
-
64
- const mountedApp = await mountSvelteComponent(props.story.file.component, {
65
- target,
66
- props: {
67
- Hst: {
68
- Story: MountStorySvelte,
69
- Variant: MountVariantSvelte,
70
- ...getControls(),
71
- },
72
- },
73
- context: new Map(Object.entries({
74
- __pvtStory: props.story,
75
- __pvtTargetVariantId: targetVariantId,
76
- })),
77
- }, 'client')
78
- app = mountedApp.app
79
- destroyApp = mountedApp.destroy
80
-
81
- const setupApi: SvelteStorySetupApi = {
82
- app,
83
- story: props.story,
84
- variant: null,
85
- }
86
-
87
- await callSetupFunctions(generatedSetup, setup, setupApi)
88
- }
89
-
90
- function unmountStory() {
91
- destroyApp?.()
92
- destroyApp = null
93
- if (target) {
94
- target.parentNode?.removeChild(target)
95
- target = null
96
- }
97
- app = null
98
- }
99
-
100
- _watch(() => props.story.id, async () => {
101
- unmountStory()
102
- await mountStory()
103
- })
104
-
105
- _onMounted(async () => {
106
- await mountStory()
107
- })
108
-
109
- _onUnmounted(() => {
110
- unmountStory()
111
- })
112
-
113
- return {
114
- el,
115
- }
116
- },
117
-
118
- render() {
119
- return _h('div', {
120
- ref: 'el',
121
- })
122
- },
123
- })
124
-
125
- function getControls() {
126
- const result: Record<string, any> = {}
127
- for (const key in components) {
128
- result[key.substring(3)] = StubComponent
129
- }
130
- return result
131
- }
@@ -1,191 +0,0 @@
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:$poveste-generated-global-setup'
17
- // @ts-expect-error virtual module id
18
- import * as setup from 'virtual:$poveste-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
- __pvtStory: props.story,
87
- __pvtVariant: props.variant,
88
- __pvtSlot: 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
- }
@@ -1,63 +0,0 @@
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
- // Same flag, same #95 caveat as `plugin-vue` and the sandbox bridge: `apply`
20
- // may only claim the next firing when its write will actually cause one, and
21
- // `applyState` reports that. It matters more here than anywhere else, because
22
- // the render loop calls `apply` on *every animation frame* whether or not the
23
- // story did anything — set the flag unconditionally and it is set through
24
- // roughly every other frame, ready to eat whatever lands in it.
25
- //
26
- // The `onChange` direction below still sets it blind. Nothing here can see
27
- // whether handing the state to the component changed anything, so the same
28
- // hazard remains on that side. It cannot be exercised today: `syncState` is
29
- // only reachable through `getLegacyStateApi`, which needs Svelte 4's
30
- // `$capture_state`/`$inject_state`, and the Svelte example is on 5 with both
31
- // state-sync specs already `fixme` under #81. Whoever revives that path
32
- // inherits this note.
33
- let syncing = false
34
-
35
- const _stop = _watch(() => variantState, (value) => {
36
- if (value == null) return
37
- if (syncing) {
38
- syncing = false
39
- return
40
- }
41
- syncing = true
42
- onChange(cleanupState(value))
43
- }, {
44
- deep: true,
45
- immediate: true,
46
- })
47
-
48
- function apply(value) {
49
- if (value == null) return
50
- if (syncing) {
51
- syncing = false
52
- return
53
- }
54
- syncing = applyState(variantState, clone(cleanupState(value)))
55
- }
56
-
57
- return {
58
- apply,
59
- stop() {
60
- _stop()
61
- },
62
- }
63
- }
@@ -1,41 +0,0 @@
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
- export let initState = null
12
-
13
- const addStory = getContext('__pvtAddStory')
14
- const file = getContext('__pvtStoryFile')
15
-
16
- const story = {
17
- id: id ?? file.id,
18
- title: title ?? file.fileName,
19
- group,
20
- layout,
21
- icon,
22
- iconColor,
23
- docsOnly,
24
- variants: [],
25
- }
26
-
27
- addStory(story)
28
-
29
- // Collection renders the story's markup purely to discover its variants, but
30
- // that markup now reads `state`. Without a value here every expression
31
- // referencing it throws and collection fails with a bare
32
- // `Cannot read properties of undefined` (#81).
33
- const state = initState ? initState() : {}
34
-
35
- setContext('__pvtStory', story)
36
- setContext('__pvtAddVariant', (variant) => {
37
- story.variants.push(variant)
38
- })
39
- </script>
40
-
41
- <slot {state} />
@@ -1,28 +0,0 @@
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
- // Absorbs the prop; collection never renders variant children. `export const`
9
- // (the compiler's suggestion) would make passing one an error.
10
- // svelte-ignore export_let_unused
11
- export let initState = null
12
-
13
- const story = getContext('__pvtStory')
14
- const addVariant = getContext('__pvtAddVariant')
15
-
16
- function generateId() {
17
- return `${story.id}-${story.variants.length}`
18
- }
19
-
20
- const variant = {
21
- id: id ?? generateId(),
22
- title,
23
- icon,
24
- iconColor,
25
- }
26
-
27
- addVariant(variant)
28
- </script>
@@ -1,65 +0,0 @@
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:$poveste-generated-global-setup'
6
- // @ts-expect-error virtual module id
7
- import * as setup from 'virtual:$poveste-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
- __pvtAddStory(data) {
28
- storyData.push(data)
29
- },
30
- __pvtStoryFile: 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
- // A file with no `Hst.Story` in it collects nothing, which is a thing people
46
- // write — a scratch file, a component that lost its story tag in a refactor.
47
- // The optional chain below used to guard only the read: with no story the
48
- // condition came out true and the body then dereferenced the same `undefined`,
49
- // so the collector crashed the whole build instead of skipping one file. The
50
- // Vue collector warns and carries on, and the shared warning downstream
51
- // (`No story found for …`) is already written for exactly this.
52
- if (!storyData[0]) {
53
- mountedApp.destroy()
54
- return
55
- }
56
-
57
- if (!storyData[0].variants.length) {
58
- storyData[0].variants.push({
59
- id: '_default',
60
- title: 'default',
61
- })
62
- }
63
-
64
- mountedApp.destroy()
65
- }
@@ -1,29 +0,0 @@
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
@@ -1,62 +0,0 @@
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: 'poveste: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
- }