Skip to content

Commit

Permalink
Svelte: Fix events not being logged in Actions when a story has decor…
Browse files Browse the repository at this point in the history
…ators
  • Loading branch information
JReinhold committed Jun 14, 2024
1 parent ff0e39a commit c621fdc
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 21 deletions.
20 changes: 12 additions & 8 deletions code/renderers/svelte/src/components/PreviewRender.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
<script context="module">
export const ARG_TYPES_CONTEXT_KEY = 'storybook/argTypes';
</script>

<script>
import { setContext } from 'svelte';
import SlotDecorator from './SlotDecorator.svelte';
import { dedent } from 'ts-dedent';
export let svelteVersion;
export let name;
export let title;
export let storyFn;
Expand All @@ -16,6 +20,8 @@
props = {},
/** @type {{[string]: () => {}}} Attach svelte event handlers */
on,
/** @type {boolean} whether this level of the decorator chain is the last, ie. the actual story */
isOriginalStory,
} = storyFn();
let firstTime = true;
Expand All @@ -30,19 +36,17 @@
Component,
props,
on,
isOriginalStory,
};
}
return storyFn();
}
// reactive, re-render on storyFn change
$: ({ Component, props = {}, on } = getStoryFnValue(storyFn));
$: ({ Component, props = {}, on, isOriginalStory } = getStoryFnValue(storyFn));
const eventsFromArgTypes = Object.fromEntries(
Object.entries(storyContext.argTypes)
.filter(([k, v]) => v.action && props[k] != null)
.map(([k, v]) => [v.action, props[k]])
);
// set the argTypes context, read by the last SlotDecorator that renders the original story
setContext(ARG_TYPES_CONTEXT_KEY, storyContext.argTypes);
if (!Component) {
showError({
Expand All @@ -56,4 +60,4 @@
}
</script>

<SlotDecorator {svelteVersion} {Component} {props} on={{ ...eventsFromArgTypes, ...on }} />
<SlotDecorator {Component} {props} {on} {isOriginalStory} />
40 changes: 30 additions & 10 deletions code/renderers/svelte/src/components/SlotDecorator.svelte
Original file line number Diff line number Diff line change
@@ -1,32 +1,52 @@
<script>
import { onMount } from 'svelte';
import { onMount, getContext } from 'svelte';
import { VERSION as SVELTE_VERSION } from 'svelte/compiler';
import { ARG_TYPES_CONTEXT_KEY } from './PreviewRender.svelte';
export let svelteVersion;
export let decorator = undefined;
export let Component;
export let props = {};
export let on = undefined;
export let isOriginalStory = false;
let instance;
let decoratorInstance;
if (on && svelteVersion < 5) {
const IS_SVELTE_V4 = Number(SVELTE_VERSION[0]) <= 4;
/*
Svelte Docgen will create argTypes for events with the name 'event_eventName'
The Actions addon will convert these to args because they are type: 'action'
We need to filter these args out so they are not passed to the component
*/
let propsWithoutDocgenEvents;
$: propsWithoutDocgenEvents = isOriginalStory
? Object.fromEntries(Object.entries(props).filter(([key]) => !key.startsWith('event_')))
: { ...props };
if (isOriginalStory && IS_SVELTE_V4) {
const argTypes = getContext(ARG_TYPES_CONTEXT_KEY) ?? {};
const eventsFromArgTypes = Object.fromEntries(
Object.entries(argTypes)
.filter(([key, value]) => value.action && props[key] != null)
.map(([key, value]) => [value.action, props[key]])
);
// Attach Svelte event listeners in Svelte v4
// In Svelte v5 this is not possible anymore as instances are no longer classes with $on() properties, so it will be a no-op
onMount(() => {
Object.entries(on).forEach(([eventName, eventCallback]) => {
// instance can be undefined if a decorator doesn't have <slot/>
Object.entries({ ...eventsFromArgTypes, ...on }).forEach(([eventName, eventCallback]) => {
// instance can be undefined if a decorator doesn't have a <slot/>
const inst = instance ?? decoratorInstance;
inst?.$on?.(eventName, eventCallback)
inst?.$on?.(eventName, eventCallback);
});
});
}
</script>
{#if decorator}
<svelte:component this={decorator.Component} {...decorator.props} bind:this={decoratorInstance}>
<svelte:component this={Component} {...props} bind:this={instance} />
<svelte:component this={Component} {...propsWithoutDocgenEvents} bind:this={instance} />
</svelte:component>
{:else}
<svelte:component this={Component} {...props} bind:this={instance} />
<svelte:component this={Component} {...propsWithoutDocgenEvents} bind:this={instance} />
{/if}
3 changes: 2 additions & 1 deletion code/renderers/svelte/src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ function prepareStory(
};
}

return preparedStory;
// no innerStory means this is the last story in the decorator chain, ie. the original story
return { ...preparedStory, isOriginalStory: true };
}

export function decorateStory(storyFn: any, decorators: any[]) {
Expand Down
2 changes: 0 additions & 2 deletions code/renderers/svelte/src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ function renderToCanvasV4(
const mountedComponent = new PreviewRender({
target: canvasElement,
props: {
svelteVersion: 4,
storyFn,
storyContext,
name,
Expand Down Expand Up @@ -149,7 +148,6 @@ function renderToCanvasV5(
name,
title,
showError,
svelteVersion: 5,
});
const mountedComponent = svelte.mount(PreviewRender, {
target: canvasElement,
Expand Down

0 comments on commit c621fdc

Please sign in to comment.