Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fluffy-pandas-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte-object": patch
---

fix(prescriptor): properly clean up prescriptor effects on removal
50 changes: 50 additions & 0 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import react from '@vitejs/plugin-react-swc'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import path, { dirname } from 'path';
import { fileURLToPath } from 'url'
import tailwindcss from '@tailwindcss/vite'
import { existsSync } from 'fs'

const workingDir = process.cwd()
// * file:///c:/.../main.ts
const dir = path.join(fileURLToPath(import.meta.url), '..')
const relative = path.relative(dir, workingDir)

const mdx = path.join(relative, '**/*.mdx').replaceAll('\\', '/')
const stories = path.join(relative, '**/*.stories.@(js|jsx|mjs|ts|tsx|svelte)').replaceAll('\\', '/')

const svelteConfig = path.join(workingDir, 'svelte.config.js')
const svelteConfigFallback = path.join(dir, '../svelte.config.js')

/** @type {import('@storybook/svelte-vite').StorybookConfig} */
const config = {
stories: [mdx, stories],
addons: [
getAbsolutePath("@storybook/addon-svelte-csf"),
getAbsolutePath("@storybook/addon-docs"),
getAbsolutePath("@storybook/addon-designs"),
getAbsolutePath("@storybook/addon-a11y"),
getAbsolutePath("@storybook/addon-links")
],
framework: getAbsolutePath("@storybook/svelte-vite"),
viteFinal: async config => {
config.plugins?.splice(0, 0, svelte({
configFile: existsSync(svelteConfig) ? svelteConfig : svelteConfigFallback
}))
config.plugins?.splice(1, 0, tailwindcss())
config.root = workingDir

config.plugins?.push(react({
// Required for `.svelte.ts` files to work correctly
devTarget: 'esnext'
}))
config.resolve?.extensions?.push('.tsx', '.jsx')
return config
}
}

export default config

function getAbsolutePath(value) {
return dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`)));
}
31 changes: 31 additions & 0 deletions .storybook/manager-head.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<style>
.sidebar-header > *:not(:has(> button)) {
display: none;
}

:has(> .sidebar-header) {
display: grid;
align-items: center;
grid-template-areas:
'sidebar-search sidebar-header'
'sidebar-menu sidebar-menu';

> .sidebar-header {
margin: 0 !important;
padding: 0 !important;
grid-area: sidebar-header;
}

> #storybook-explorer-menu {
margin: 0 !important;
padding: 0 !important;
grid-area: sidebar-menu;
}

> :has(.search-field) {
margin: 0 !important;
padding: 0 !important;
grid-area: sidebar-search;
}
}
</style>
24 changes: 24 additions & 0 deletions .storybook/minimal.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.sidebar-header > *:not(:has(> button)) {
display: none;
}

:has(> .sidebar-header) {
display: grid;

grid-template-areas:
'sidebar-search sidebar-header'
'sidebar-menu sidebar-menu';

> .sidebar-header {
grid-area: sidebar-header;
}

> #storybook-explorer-menu {
grid-area: sidebar-menu;
}

> :has(.search-field) {
grid-area: sidebar-search;
}
}

21 changes: 21 additions & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import './minimal.css'
import './tailwind.css'

/** @type {import('@storybook/svelte').Preview} */
const preview = {
parameters: {
options: {
/**
* @param {import('storybook/internal/types').IndexEntry} a
* @param {import('storybook/internal/types').IndexEntry} b
*/
storySort: (a, b) => {
if(a.type === 'docs') return b.type === 'docs' ? 0 : -1
if(b.type === 'docs') return 1
return a.title === b.title ? 0 : a.title.localeCompare(b.title, undefined, { numeric: true })
}
}
}
}

export default preview
8 changes: 8 additions & 0 deletions .storybook/storybook/Example.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Meta } from '@storybook/addon-docs/blocks'


<Meta title='Testing' />

# Example

Example Documentation
73 changes: 73 additions & 0 deletions .storybook/storybook/React.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<!-- @component

A wrapper for React components in Svelte.

@example
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf'

import React from '../React.svelte'
import { CounterComposite } from './ReactExample'

const { Story } = defineMeta({
title: 'React Example',
component: React,
args: {
$component: CounterComposite,
label: 'Test',
// ...props
}
})

</script>

<Story name='Default' />

<Story name='Template'>

{#snippet template()}
<React $component={CounterComposite} label='Test' ...props />
{/snippet}

</Story>
-->

<script lang='ts'>
import { onMount, onDestroy } from 'svelte'
import { createRoot, type Root } from 'react-dom/client'
import React, { createElement } from 'react'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type TComponentType = $$Generic<React.ComponentType<any>>
type Props = {
$component: TComponentType
} & (TComponentType extends React.ComponentType<infer P> ? P : never)

let {
$component: component,
...p
}: Props = $props()

// local refs --------------------------------------------------------------
let container: HTMLDivElement
let root: Root

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const render = () => root.render(createElement(component as any, p as any))

onMount(() => {
root = createRoot(container)
render()
})

// re-render when props change
$effect(() => {
if(root) {
render()
}
})

onDestroy(() => root?.unmount())
</script>

<div bind:this={container}></div>
61 changes: 61 additions & 0 deletions .storybook/storybook/from-react.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { Component } from 'svelte'
import _React from './React.svelte'

/**
* ```html
*
* ```
* Usage Example
* ----
* ```html
*
* <script lang='ts' module>
* import { defineMeta } from '@storybook/addon-svelte-csf'
*
* import fromReact from '../from-react'
* import { CounterComposite as _CounterComposite } from './ReactExample'
*
* const CounterComposite = fromReact(_CounterComposite)
*
* const { Story } = defineMeta({
* title: 'React/Example',
* component: CounterComposite,
* args: {
* label: 'Test'
* }
* })
*
* export declare const Default
* export declare const Template
*
* </script>
*
* <Story name='Default' />
*
* <Story name='Template'>
* {#snippet template()}
* <CounterComposite label='Test' />
* {/snippet}
* </Story>
*
*
* ```
*/
type React = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
<TProps extends Record<PropertyKey, any>>(
$component: React.ComponentType<TProps>
): Component<TProps>
}

const React: React = ($component) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (_internal: any, props: any) => {
return _React(_internal, {
...props,
$component
})
}
}

export default React
11 changes: 11 additions & 0 deletions .storybook/storybook/react/ReactExample.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Canvas, Meta } from '@storybook/addon-docs/blocks'

import * as Example from './ReactExample.stories.svelte'

<Meta title='React/Example' />

# Svelte Example

Example Documentation

<Canvas of={Example.Default} />
25 changes: 25 additions & 0 deletions .storybook/storybook/react/ReactExample.stories.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script lang='ts' module>
import { defineMeta } from '@storybook/addon-svelte-csf'

import fromReact from '../from-react'
import { CounterComposite as XCounterComposite } from './ReactExample'

const CounterComposite = fromReact(XCounterComposite)

const { Story } = defineMeta({
title: 'React/Example',
component: CounterComposite,
args: {
label: 'Test'
}
})

</script>

<Story name='Default' />

<Story name='Template'>
{#snippet template()}
<CounterComposite label='Test' />
{/snippet}
</Story>
54 changes: 54 additions & 0 deletions .storybook/storybook/react/ReactExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useState } from 'react'

/**
* A button that increments the shared counter when clicked.
*/
export type CounterButtonProps = {
/** current counter value (display only) */
count: number
/** callback to increment the counter */
onIncrement: () => void
}

export const CounterButton = ({ count, onIncrement }: CounterButtonProps) => {
return (
<button
onClick={onIncrement}
className='cursor-pointer rounded border border-gray-400 bg-gray-100 px-4 py-2 hover:bg-gray-200'
>
Clicked {count} times
</button>
)
}

/**
* A read‑only display of the current counter value.
*/
export type CounterDisplayProps = {
count: number
}

export const CounterDisplay = ({ count }: CounterDisplayProps) => {
return <div className='text-lg font-bold'>Current count: {count}</div>
}


/**
* Composite component that wires CounterDisplay and CounterButton together.
* This one is handy when you want a single React story.
*/

export type CompositeProps = {
label: string
}

export const CounterComposite = ({ label }: CompositeProps) => {
const [count, setCount] = useState(0)
return (
<div>
<h1>{label ?? 'No label provided'}</h1>
<CounterDisplay count={count} />
<CounterButton count={count} onIncrement={() => setCount(c => c + 1)} />
</div>
)
}
11 changes: 11 additions & 0 deletions .storybook/storybook/svelte/SvelteExample.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Canvas, Meta } from '@storybook/addon-docs/blocks'

import * as Example from './SvelteExample.stories.svelte'

<Meta title='Svelte/Example' />

# Svelte Example

Example Documentation

<Canvas of={Example.Default} />
Loading