From 918372eee1e2225a72c03e4d5b057dc3a263fb88 Mon Sep 17 00:00:00 2001 From: "Arthur @Refzlund" Date: Wed, 26 Nov 2025 14:31:33 +0100 Subject: [PATCH 1/2] feat: add Svelte example component with Storybook integration - Created SvelteExample component with default props. - Added Storybook stories for SvelteExample. - Introduced a new MDX file for documentation of the Svelte example. - Updated dependencies in bun.lock and package.json to latest versions. - Implemented Tailwind CSS for styling in the new components. - Fixed issues with array item removal in SvelteObject and SvelteValue components. - Added a new story to demonstrate the array item removal issue and its solution. - Configured Svelte with Vite preprocess for better integration. --- .storybook/main.js | 50 ++++++++++ .storybook/manager-head.html | 31 ++++++ .storybook/minimal.css | 24 +++++ .storybook/preview.js | 21 ++++ .storybook/storybook/Example.mdx | 8 ++ .storybook/storybook/React.svelte | 73 ++++++++++++++ .storybook/storybook/from-react.ts | 61 ++++++++++++ .storybook/storybook/react/ReactExample.mdx | 11 +++ .../react/ReactExample.stories.svelte | 25 +++++ .storybook/storybook/react/ReactExample.tsx | 54 +++++++++++ .storybook/storybook/svelte/SvelteExample.mdx | 11 +++ .../svelte/SvelteExample.stories.svelte | 16 ++++ .../storybook/svelte/SvelteExample.svelte | 23 +++++ .storybook/tailwind.css | 1 + bun.lock | 31 +++--- debug-storybook.log | 96 +++++++++++++++++++ package.json | 52 +++++----- svelte-object/package.json | 8 +- svelte-object/src/SvelteObject.svelte | 41 +++++--- svelte-object/src/SvelteValue.svelte | 5 +- .../array-item-removal/Issue.stories.svelte | 12 +++ .../issues/array-item-removal/Issue.svelte | 57 +++++++++++ .../issues/array-item-removal/README.mdx | 55 +++++++++++ svelte.config.js | 11 +++ 24 files changed, 719 insertions(+), 58 deletions(-) create mode 100644 .storybook/main.js create mode 100644 .storybook/manager-head.html create mode 100644 .storybook/minimal.css create mode 100644 .storybook/preview.js create mode 100644 .storybook/storybook/Example.mdx create mode 100644 .storybook/storybook/React.svelte create mode 100644 .storybook/storybook/from-react.ts create mode 100644 .storybook/storybook/react/ReactExample.mdx create mode 100644 .storybook/storybook/react/ReactExample.stories.svelte create mode 100644 .storybook/storybook/react/ReactExample.tsx create mode 100644 .storybook/storybook/svelte/SvelteExample.mdx create mode 100644 .storybook/storybook/svelte/SvelteExample.stories.svelte create mode 100644 .storybook/storybook/svelte/SvelteExample.svelte create mode 100644 .storybook/tailwind.css create mode 100644 debug-storybook.log create mode 100644 svelte-object/stories/issues/array-item-removal/Issue.stories.svelte create mode 100644 svelte-object/stories/issues/array-item-removal/Issue.svelte create mode 100644 svelte-object/stories/issues/array-item-removal/README.mdx create mode 100644 svelte.config.js diff --git a/.storybook/main.js b/.storybook/main.js new file mode 100644 index 0000000..cab89dd --- /dev/null +++ b/.storybook/main.js @@ -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`))); +} \ No newline at end of file diff --git a/.storybook/manager-head.html b/.storybook/manager-head.html new file mode 100644 index 0000000..a0a8934 --- /dev/null +++ b/.storybook/manager-head.html @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/.storybook/minimal.css b/.storybook/minimal.css new file mode 100644 index 0000000..d45e9e9 --- /dev/null +++ b/.storybook/minimal.css @@ -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; + } +} + diff --git a/.storybook/preview.js b/.storybook/preview.js new file mode 100644 index 0000000..0ec042a --- /dev/null +++ b/.storybook/preview.js @@ -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 \ No newline at end of file diff --git a/.storybook/storybook/Example.mdx b/.storybook/storybook/Example.mdx new file mode 100644 index 0000000..f44e0e6 --- /dev/null +++ b/.storybook/storybook/Example.mdx @@ -0,0 +1,8 @@ +import { Meta } from '@storybook/addon-docs/blocks' + + + + +# Example + +Example Documentation diff --git a/.storybook/storybook/React.svelte b/.storybook/storybook/React.svelte new file mode 100644 index 0000000..533e94e --- /dev/null +++ b/.storybook/storybook/React.svelte @@ -0,0 +1,73 @@ + + + + +
\ No newline at end of file diff --git a/.storybook/storybook/from-react.ts b/.storybook/storybook/from-react.ts new file mode 100644 index 0000000..dc1abe6 --- /dev/null +++ b/.storybook/storybook/from-react.ts @@ -0,0 +1,61 @@ +import type { Component } from 'svelte' +import _React from './React.svelte' + +/** + * ```html + * + * ``` + * Usage Example + * ---- + * ```html + * + * + * + * + * + * + * {#snippet template()} + * + * {/snippet} + * + * + * + * ``` +*/ +type React = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + >( + $component: React.ComponentType + ): Component +} + +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 \ No newline at end of file diff --git a/.storybook/storybook/react/ReactExample.mdx b/.storybook/storybook/react/ReactExample.mdx new file mode 100644 index 0000000..8d6f24a --- /dev/null +++ b/.storybook/storybook/react/ReactExample.mdx @@ -0,0 +1,11 @@ +import { Canvas, Meta } from '@storybook/addon-docs/blocks' + +import * as Example from './ReactExample.stories.svelte' + + + +# Svelte Example + +Example Documentation + + \ No newline at end of file diff --git a/.storybook/storybook/react/ReactExample.stories.svelte b/.storybook/storybook/react/ReactExample.stories.svelte new file mode 100644 index 0000000..2a8f7e9 --- /dev/null +++ b/.storybook/storybook/react/ReactExample.stories.svelte @@ -0,0 +1,25 @@ + + + + + + {#snippet template()} + + {/snippet} + \ No newline at end of file diff --git a/.storybook/storybook/react/ReactExample.tsx b/.storybook/storybook/react/ReactExample.tsx new file mode 100644 index 0000000..fcacfe3 --- /dev/null +++ b/.storybook/storybook/react/ReactExample.tsx @@ -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 ( + + ) +} + +/** + * A read‑only display of the current counter value. + */ +export type CounterDisplayProps = { + count: number +} + +export const CounterDisplay = ({ count }: CounterDisplayProps) => { + return
Current count: {count}
+} + + +/** + * 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 ( +
+

{label ?? 'No label provided'}

+ + setCount(c => c + 1)} /> +
+ ) +} \ No newline at end of file diff --git a/.storybook/storybook/svelte/SvelteExample.mdx b/.storybook/storybook/svelte/SvelteExample.mdx new file mode 100644 index 0000000..690b6b7 --- /dev/null +++ b/.storybook/storybook/svelte/SvelteExample.mdx @@ -0,0 +1,11 @@ +import { Canvas, Meta } from '@storybook/addon-docs/blocks' + +import * as Example from './SvelteExample.stories.svelte' + + + +# Svelte Example + +Example Documentation + + \ No newline at end of file diff --git a/.storybook/storybook/svelte/SvelteExample.stories.svelte b/.storybook/storybook/svelte/SvelteExample.stories.svelte new file mode 100644 index 0000000..38f5f5e --- /dev/null +++ b/.storybook/storybook/svelte/SvelteExample.stories.svelte @@ -0,0 +1,16 @@ + + + + {#snippet template(args)} + + {/snippet} + \ No newline at end of file diff --git a/.storybook/storybook/svelte/SvelteExample.svelte b/.storybook/storybook/svelte/SvelteExample.svelte new file mode 100644 index 0000000..bb74d22 --- /dev/null +++ b/.storybook/storybook/svelte/SvelteExample.svelte @@ -0,0 +1,23 @@ + + + + +{text} + + + + \ No newline at end of file diff --git a/.storybook/tailwind.css b/.storybook/tailwind.css new file mode 100644 index 0000000..f173aa4 --- /dev/null +++ b/.storybook/tailwind.css @@ -0,0 +1 @@ +@import 'tailwindcss'; \ No newline at end of file diff --git a/bun.lock b/bun.lock index 5fb822a..0fd0411 100644 --- a/bun.lock +++ b/bun.lock @@ -1,33 +1,34 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "dependencies": { - "svelte": "^5.35.2", + "svelte": "^5.44.1", }, "devDependencies": { "@changesets/cli": "^2.29.5", "@refzlund/repo": "github:Refzlund/repo#next", "@svitejs/changesets-changelog-github-compact": "^1.2.0", - "@types/bun": "^1.2.17", - "@types/node": "^22.16.0", + "@types/bun": "^1.3.3", + "@types/node": "^22.19.1", }, }, "svelte-object": { "name": "svelte-object", - "version": "2.0.7", + "version": "2.1.2", "dependencies": { "fast-deep-equal": "^3", }, "devDependencies": { - "@sveltejs/kit": "^2.46.5", - "@sveltejs/package": "^2", + "@sveltejs/kit": "^2.49.0", + "@sveltejs/package": "^2.5.6", "@sveltejs/vite-plugin-svelte": "^6.2.1", - "svelte": "^5", + "svelte": "^5.44.1", "typescript": "^5", }, "peerDependencies": { - "svelte": "^5", + "svelte": "^5.44.1", }, }, }, @@ -332,9 +333,9 @@ "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.6", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ=="], - "@sveltejs/kit": ["@sveltejs/kit@2.46.5", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.3.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-7TSvMrCdmig5TMyYDW876C5FljhA0wlGixtvASCiqUqtLfmyEEpaysXjC7GhR5mWcGRrCGF+L2Bl1eEaW1wTCA=="], + "@sveltejs/kit": ["@sveltejs/kit@2.49.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.3.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-oH8tXw7EZnie8FdOWYrF7Yn4IKrqTFHhXvl8YxXxbKwTMcD/5NNCryUSEXRk2ZR4ojnub0P8rNrsVGHXWqIDtA=="], - "@sveltejs/package": ["@sveltejs/package@2.5.4", "", { "dependencies": { "chokidar": "^4.0.3", "kleur": "^4.1.5", "sade": "^1.8.1", "semver": "^7.5.4", "svelte2tsx": "~0.7.33" }, "peerDependencies": { "svelte": "^3.44.0 || ^4.0.0 || ^5.0.0-next.1" }, "bin": { "svelte-package": "svelte-package.js" } }, "sha512-8+1hccAt0M3PPkHVPKH54Wc+cc1PNxRqCrICZiv/hEEto8KwbQVRghxNgTB4htIPyle+4CIB8RayTQH5zRQh9A=="], + "@sveltejs/package": ["@sveltejs/package@2.5.6", "", { "dependencies": { "chokidar": "^4.0.3", "kleur": "^4.1.5", "sade": "^1.8.1", "semver": "^7.5.4", "svelte2tsx": "~0.7.33" }, "peerDependencies": { "svelte": "^3.44.0 || ^4.0.0 || ^5.0.0-next.1" }, "bin": { "svelte-package": "svelte-package.js" } }, "sha512-1rNQYW/TwDZU6HTAWHvwDAdyRcccYKG7sShbCokhX1Z0spPFcTVbK771fVBcWnh+XChZGcOfi4KLN9AaIrehQQ=="], "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.1", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", "deepmerge": "^4.3.1", "magic-string": "^0.30.17", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ=="], @@ -416,7 +417,7 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - "@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="], + "@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="], "@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="], @@ -432,7 +433,7 @@ "@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="], - "@types/node": ["@types/node@22.18.10", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg=="], + "@types/node": ["@types/node@22.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ=="], "@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], @@ -554,7 +555,7 @@ "browserslist": ["browserslist@4.26.3", "", { "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", "electron-to-chromium": "^1.5.227", "node-releases": "^2.0.21", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w=="], - "bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="], + "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], @@ -630,7 +631,7 @@ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "devalue": ["devalue@5.3.2", "", {}, "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw=="], + "devalue": ["devalue@5.5.0", "", {}, "sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w=="], "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], @@ -1068,7 +1069,7 @@ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - "svelte": ["svelte@5.40.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-EdA6LIo1vZVQOvlGfM18lhYM1k5Dkk/e93IlpzK4OVM6c11sUMHPgCtGFKmdy1WT4XqbpAvicq9JH5MTNoGaKw=="], + "svelte": ["svelte@5.44.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.5.0", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-8VnkRXpa6tJ9IqiwKvzZBNnBy9tZg0N63duDz0EJqiozsmBEAZfHiZzWWWAneIN+cAWkK1JkafW1xIbC4YrdBA=="], "svelte-ast-print": ["svelte-ast-print@0.4.2", "", { "dependencies": { "esrap": "1.2.2", "zimmerframe": "1.1.2" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-hRHHufbJoArFmDYQKCpCvc0xUuIEfwYksvyLYEQyH+1xb5LD5sM/IthfooCdXZQtOIqXz6xm7NmaqdfwG4kh6w=="], diff --git a/debug-storybook.log b/debug-storybook.log new file mode 100644 index 0000000..82dea6a --- /dev/null +++ b/debug-storybook.log @@ -0,0 +1,96 @@ +[13:11:51.842] [INFO] Storybook Upgrade - v10.0.8 +[13:11:51.909] [INFO] detect-projects-spinner-start: Detecting projects... +[13:11:51.911] [DEBUG] Finding Storybook projects... +[13:11:52.037] [DEBUG] Found 1 Storybook projects +[13:11:52.056] [INFO] detect-projects-spinner: Detecting projects: 1 projects +[13:11:52.056] [DEBUG] Getting Storybook data... +[13:11:52.056] [DEBUG] Getting Storybook info... +[13:11:52.069] [DEBUG] Loading main config... +[13:11:53.472] [DEBUG] Getting stories paths... +[13:11:53.539] [DEBUG] Getting package manager... +[13:11:54.058] [DEBUG] Getting Storybook version... +[13:11:54.059] [DEBUG] C:/Users/Arthur/Sync/@packages/svelte-object/.storybook - Validating before version... 9.1.10 +[13:11:54.059] [DEBUG] C:/Users/Arthur/Sync/@packages/svelte-object/.storybook - Validating upgrade compatibility... +[13:11:54.059] [DEBUG] C:/Users/Arthur/Sync/@packages/svelte-object/.storybook - Fetching NPM version information... +[13:11:54.059] [DEBUG] Getting CLI versions from NPM for storybook... +[13:11:54.067] [DEBUG] Getting CLI versions from NPM for storybook@next... +[13:11:55.216] [DEBUG] C:/Users/Arthur/Sync/@packages/svelte-object/.storybook - Evaluating blockers... +[13:11:55.252] [DEBUG] Getting installed version for @storybook/experimental-addon-test... +[13:11:57.269] [DEBUG] An issue occurred while trying to find dependencies metadata using npm. +[13:11:57.269] [INFO] detect-projects-spinner-stop: 1 project detected +[13:11:57.270] [DEBUG] Found 1 valid projects and 0 error projects +[13:11:57.270] [INFO] Upgrading from 9.1.10 to 10.0.8 +[13:11:57.322] [INFO] upgrade-dependencies-task-start: Fetching versions to update package.json files.. +[13:11:57.323] [DEBUG] Updating dependencies in C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.373] [INFO] upgrade-dependencies-task: C:/Users/Arthur/Sync/@packages/svelte-object/package.json +[13:11:57.374] [DEBUG] { + "upgradedDependencies": [] +} +[13:11:57.374] [DEBUG] { + "upgradedDevDependencies": [] +} +[13:11:57.374] [DEBUG] { + "upgradedPeerDependencies": [] +} +[13:11:57.384] [INFO] upgrade-dependencies-task-success: Updated package versions in package.json files +[13:11:57.385] [INFO] detect-automigrations-task-start: Detecting automigrations... +[13:11:57.386] [DEBUG] Starting automigration collection across 1 projects and 18 fixes... +[13:11:57.430] [INFO] detect-automigrations-task: Checking automigrations for +C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.430] [DEBUG] Processing project: C:/Users/Arthur/Sync/@packages/svelte-object/.storybook +[13:11:57.430] [DEBUG] Checking fix eslintPlugin for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.431] [DEBUG] Checking fix addon-mdx-gfm-remove for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.432] [DEBUG] Checking fix addon-storysource-code-panel for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.432] [DEBUG] Checking fix upgrade-storybook-related-dependencies for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.432] [DEBUG] Checking for incompatible storybook packages... +[13:11:57.433] [DEBUG] Checking fix initial-globals for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.456] [DEBUG] Checking fix addon-globals-api for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.460] [DEBUG] Checking fix addon-a11y-addon-test for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.461] [DEBUG] Checking fix consolidated-imports for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.461] [DEBUG] Checking fix addon-experimental-test for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.461] [DEBUG] Using cached installed version for @storybook/experimental-addon-test... +[13:11:57.461] [DEBUG] Checking fix rnstorybook-config for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.462] [DEBUG] Checking fix migrate-addon-console for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.462] [DEBUG] Checking fix remove-addon-interactions for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.462] [DEBUG] Checking fix renderer-to-framework for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.463] [DEBUG] Checking fix remove-essential-addons for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.463] [DEBUG] Checking fix addon-a11y-parameters for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.465] [DEBUG] Checking fix remove-docs-autodocs for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.473] [DEBUG] Checking fix wrap-getAbsolutePath for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.480] [DEBUG] Checking fix fix-faux-esm-require for project C:/Users/Arthur/Sync/@packages/svelte-object/.storybook... +[13:11:57.522] [INFO] detect-automigrations-task: +Automigrations detected: +[13:11:57.577] [INFO] detect-automigrations-task: ✔ eslintPlugin +[13:11:57.631] [INFO] detect-automigrations-task: ✔ wrap-getAbsolutePath +[13:11:57.631] [INFO] detect-automigrations-task-success: 2 automigration(s) detected +[13:12:15.970] [PROMPT] Select automigrations to run {"choice":["wrap-getAbsolutePath"]} +[13:12:15.971] [INFO] automigrate-C:/Users/Arthur/Sync/@packages/svelte-object/.storybook-task-start: Running automigrations for C:/Users/Arthur/Sync/@packages/svelte-object/.storybook +[13:12:16.045] [INFO] automigrate-C:/Users/Arthur/Sync/@packages/svelte-object/.storybook-task: ✔ wrap-getAbsolutePath +[13:12:16.045] [INFO] automigrate-C:/Users/Arthur/Sync/@packages/svelte-object/.storybook-task-success: Completed automigrations for C:/Users/Arthur/Sync/@packages/svelte-object/.storybook +[13:12:16.045] [INFO] Installing dependencies... +[13:12:16.046] [INFO] install-dependencies-task-start: Installing dependencies... +[13:12:16.264] [INFO] Dependencies installed +[13:12:16.264] [INFO] install-dependencies-task-success: Dependencies installed +[13:12:16.267] [WARN] Since you are in a monorepo, we advise you to deduplicate your dependencies. We can do this for you but it might take some time. +[13:12:20.709] [PROMPT] Execute bun run dedupe? {"choice":true} +[13:12:20.709] [INFO] Deduplicating dependencies... +[13:12:20.709] [INFO] dedupe-dependencies-task-start: Deduplicating dependencies... +[13:12:20.764] [ERROR] An error occurred while deduplicating dependencies. {"error":"Error: Command failed with exit code 1: bun dedupe\nerror: Script not found \"dedupe\"\n at makeError (file:///C:/Users/Arthur/AppData/Local/npm-cache/_npx/35296d5896917b91/node_modules/storybook/dist/_node-chunks/chunk-F6NUZ463.js:3655:13)\n at handlePromise (file:///C:/Users/Arthur/AppData/Local/npm-cache/_npx/35296d5896917b91/node_modules/storybook/dist/_node-chunks/chunk-F6NUZ463.js:4586:29)\n at process.processTicksAndRejections (node:internal/process/task_queues:105:5)\n at async Object.executeTask (file:///C:/Users/Arthur/AppData/Local/npm-cache/_npx/35296d5896917b91/node_modules/storybook/dist/node-logger/index.js:4418:7)\n at async BUNProxy.dedupeDependencies (file:///C:/Users/Arthur/AppData/Local/npm-cache/_npx/35296d5896917b91/node_modules/storybook/dist/_node-chunks/chunk-FYLSDVSH.js:11258:5)\n at async file:///C:/Users/Arthur/AppData/Local/npm-cache/_npx/35296d5896917b91/node_modules/@storybook/cli/dist/_node-chunks/run-TLOFK5QH.js:11305:15\n at async withTelemetry (file:///C:/Users/Arthur/AppData/Local/npm-cache/_npx/35296d5896917b91/node_modules/storybook/dist/_node-chunks/chunk-TKXE3RQ5.js:278:12)\n at async upgrade (file:///C:/Users/Arthur/AppData/Local/npm-cache/_npx/35296d5896917b91/node_modules/@storybook/cli/dist/_node-chunks/run-TLOFK5QH.js:11201:3)\n at async Command. (file:///C:/Users/Arthur/AppData/Local/npm-cache/_npx/35296d5896917b91/node_modules/@storybook/cli/dist/_node-chunks/run-TLOFK5QH.js:11451:3)"} +[13:12:20.764] [ERROR] dedupe-dependencies-task-error: An error occurred while deduplicating dependencies. +[13:12:20.765] [ERROR] Error: Command failed with exit code 1: bun dedupe +error: Script not found "dedupe" + at makeError (file:///C:/Users/Arthur/AppData/Local/npm-cache/_npx/35296d5896917b91/node_modules/storybook/dist/_node-chunks/chunk-F6NUZ463.js:3655:13) + at handlePromise (file:///C:/Users/Arthur/AppData/Local/npm-cache/_npx/35296d5896917b91/node_modules/storybook/dist/_node-chunks/chunk-F6NUZ463.js:4586:29) + at process.processTicksAndRejections (node:internal/process/task_queues:105:5) + at async Object.executeTask (file:///C:/Users/Arthur/AppData/Local/npm-cache/_npx/35296d5896917b91/node_modules/storybook/dist/node-logger/index.js:4418:7) + at async BUNProxy.dedupeDependencies (file:///C:/Users/Arthur/AppData/Local/npm-cache/_npx/35296d5896917b91/node_modules/storybook/dist/_node-chunks/chunk-FYLSDVSH.js:11258:5) + at async file:///C:/Users/Arthur/AppData/Local/npm-cache/_npx/35296d5896917b91/node_modules/@storybook/cli/dist/_node-chunks/run-TLOFK5QH.js:11305:15 + at async withTelemetry (file:///C:/Users/Arthur/AppData/Local/npm-cache/_npx/35296d5896917b91/node_modules/storybook/dist/_node-chunks/chunk-TKXE3RQ5.js:278:12) + at async upgrade (file:///C:/Users/Arthur/AppData/Local/npm-cache/_npx/35296d5896917b91/node_modules/@storybook/cli/dist/_node-chunks/run-TLOFK5QH.js:11201:3) + at async Command. (file:///C:/Users/Arthur/AppData/Local/npm-cache/_npx/35296d5896917b91/node_modules/@storybook/cli/dist/_node-chunks/run-TLOFK5QH.js:11451:3) +[13:12:20.814] [INFO] Attention: Storybook now collects completely anonymous telemetry regarding usage. This information is used to shape Storybook's roadmap and prioritize features. +[13:12:20.860] [INFO] You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL: +[13:12:20.905] [INFO] https://storybook.js.org/telemetry +[13:12:20.950] [INFO] +[13:12:21.854] [ERROR] Error: Command failed with exit code 1: bun dedupe +error: Script not found "dedupe" \ No newline at end of file diff --git a/package.json b/package.json index 1c1bdac..c817356 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,27 @@ { - "private": true, - "scripts": { - "app": "cd app && bun run dev", - "package": "cd svelte-object && bun run package", - "publish": "bun run package && changeset publish", - "format": "bunx repo-format", - "stories": "bunx repo-storybook" - }, - "type": "module", - "workspaces": [ - "svelte-object" - ], - "devDependencies": { - "@changesets/cli": "^2.29.5", - "@refzlund/repo": "github:Refzlund/repo#next", - "@svitejs/changesets-changelog-github-compact": "^1.2.0", - "@types/bun": "^1.2.17", - "@types/node": "^22.16.0" - }, - "publishConfig": { - "directory": "svelte-object/_package" - }, - "dependencies": { - "svelte": "^5.35.2" - } -} \ No newline at end of file + "private": true, + "scripts": { + "app": "cd app && bun run dev", + "package": "cd svelte-object && bun run package", + "publish": "bun run package && changeset publish", + "format": "bunx repo-format", + "stories": "bunx repo-storybook" + }, + "type": "module", + "workspaces": [ + "svelte-object" + ], + "devDependencies": { + "@changesets/cli": "^2.29.5", + "@refzlund/repo": "github:Refzlund/repo#next", + "@svitejs/changesets-changelog-github-compact": "^1.2.0", + "@types/bun": "^1.3.3", + "@types/node": "^22.19.1" + }, + "publishConfig": { + "directory": "svelte-object/_package" + }, + "dependencies": { + "svelte": "^5.44.1" + } +} diff --git a/svelte-object/package.json b/svelte-object/package.json index 87240a7..f97a015 100644 --- a/svelte-object/package.json +++ b/svelte-object/package.json @@ -21,13 +21,13 @@ }, "files": [], "peerDependencies": { - "svelte": "^5" + "svelte": "^5.44.1" }, "devDependencies": { - "@sveltejs/kit": "^2.46.5", - "@sveltejs/package": "^2", + "@sveltejs/kit": "^2.49.0", + "@sveltejs/package": "^2.5.6", "@sveltejs/vite-plugin-svelte": "^6.2.1", - "svelte": "^5", + "svelte": "^5.44.1", "typescript": "^5" }, "dependencies": { diff --git a/svelte-object/src/SvelteObject.svelte b/svelte-object/src/SvelteObject.svelte index 083e15b..6936c4f 100644 --- a/svelte-object/src/SvelteObject.svelte +++ b/svelte-object/src/SvelteObject.svelte @@ -41,7 +41,7 @@ name: Prescriptor['name'], getter: Prescriptor['get'], setter: Prescriptor['set'] - ) => void + ) => () => void addValidator(fn: (trigger?: ValidationType) => boolean): void removeValidator(fn: (trigger?: ValidationType) => boolean): void submit: () => void @@ -63,6 +63,9 @@ type T = $$Generic + let removePrescriptor: (() => void) | undefined + onDestroy(() => removePrescriptor?.()) + let { children: slot, @@ -118,27 +121,36 @@ let validators = [] as (typeof validate)[] - const prescriptors: Prescriptor[] = $state([]) + // Use $state.raw to get reactivity without deep proxy tracking + // This avoids proxy equality issues when using indexOf/findIndex + let prescriptors = $state.raw>([]) - $effect.pre(() => { - for(const prescriptor of prescriptors) { + // Each prescriptor creates its own effects via $effect.root + // This allows proper cleanup when the prescriptor is removed + function createPrescriptorEffects(prescriptor: Prescriptor & { removed?: boolean }) { + return $effect.root(() => { + // Sync from parent value to child $effect.pre(() => { - let itemValue = value?.[prescriptor.name] + if (prescriptor.removed) return + let itemValue = value?.[prescriptor.name] untrack(() => { + if (prescriptor.removed) return prescriptor.set(itemValue) }) }) + // Sync from child to parent value $effect.pre(() => { + if (prescriptor.removed) return let itemValue = prescriptor.get() - prescriptor.name untrack(() => { + if (prescriptor.removed) return value ??= {} as T value[prescriptor.name] = itemValue }) }) - } - }) + }) + } const self = { addPrescriptor( @@ -146,7 +158,7 @@ getter: () => unknown, setter: (value: unknown) => void ) { - const prescriptor = { name, get: getter, set: setter } + const prescriptor: Prescriptor & { removed?: boolean } = { name, get: getter, set: setter } if(value) { const current = value[name] @@ -158,9 +170,14 @@ } } - prescriptors.push(prescriptor) + // Create effects for this prescriptor with manual cleanup + const cleanupEffects = createPrescriptorEffects(prescriptor) + + prescriptors = [...prescriptors, prescriptor] return () => { - prescriptors.splice(prescriptors.indexOf(prescriptor), 1) + prescriptor.removed = true + cleanupEffects() + prescriptors = prescriptors.filter(p => p !== prescriptor) } }, addValidator(fn: typeof validate) { @@ -195,7 +212,7 @@ } if(parent && (name !== undefined && name !== null) && name !== '') { - parent.addPrescriptor(name, () => value, v => value = v as T) + removePrescriptor = parent.addPrescriptor(name, () => value, v => value = v as T) } let modificationTimer: ReturnType | undefined diff --git a/svelte-object/src/SvelteValue.svelte b/svelte-object/src/SvelteValue.svelte index 501b853..2e51142 100644 --- a/svelte-object/src/SvelteValue.svelte +++ b/svelte-object/src/SvelteValue.svelte @@ -16,6 +16,9 @@ type T = $$Generic + let removePrescriptor: (() => void) | undefined + onDestroy(() => removePrescriptor?.()) + const object = getContextSvelteObject() let { @@ -133,7 +136,7 @@ if(object && (name !== undefined && name !== null) && name !== '') { - object.addPrescriptor(name, () => value, v => value = v as T) + removePrescriptor = object.addPrescriptor(name, () => value, v => value = v as T) } $effect.pre(() => { diff --git a/svelte-object/stories/issues/array-item-removal/Issue.stories.svelte b/svelte-object/stories/issues/array-item-removal/Issue.stories.svelte new file mode 100644 index 0000000..2dfa72a --- /dev/null +++ b/svelte-object/stories/issues/array-item-removal/Issue.stories.svelte @@ -0,0 +1,12 @@ + + + + {#snippet template()} + + {/snippet} + diff --git a/svelte-object/stories/issues/array-item-removal/Issue.svelte b/svelte-object/stories/issues/array-item-removal/Issue.svelte new file mode 100644 index 0000000..9557164 --- /dev/null +++ b/svelte-object/stories/issues/array-item-removal/Issue.svelte @@ -0,0 +1,57 @@ + + + +
+ + {#snippet children(obj)} + + {#snippet children(arr)} + + + {#each arr.value as item, k} +
+ #{k} + + + {#snippet children(prop)} + + {/snippet} + + + +
+ {/each} + {/snippet} +
+ +
+

obj.value:

+
{JSON.stringify(obj.value, null, '\t')}
+
+ {/snippet} +
+
+ + + diff --git a/svelte-object/stories/issues/array-item-removal/README.mdx b/svelte-object/stories/issues/array-item-removal/README.mdx new file mode 100644 index 0000000..aed3de4 --- /dev/null +++ b/svelte-object/stories/issues/array-item-removal/README.mdx @@ -0,0 +1,55 @@ +When using `I.Array` with a `children` snippet and manually iterating with `{#each}`, removing items via `splice()` doesn't work correctly when using `name={index}`. The removed item "reappears" because the `I.Object` prescriptor writes its cached value back to the array. + +## Root Cause + +Using `I.Object name={k}` where `k` is an array index is fundamentally problematic because: + +1. The prescriptor is registered with a static index at mount time +2. When array items shift (via splice), the prescriptor's index no longer corresponds to the correct item +3. The sync effect writes the old value back to the shifted index + +## Solution + +Use the `item` snippet provided by `I.Array` instead of manual `{#each}` with `name={index}`. The `item` snippet: + +1. Uses object-identity keying internally: `(typeof valueItem === 'object' ? valueItem : i)` +2. Provides `bind:value` on each item, bypassing the prescriptor system entirely +3. Properly tracks item identity across array mutations + +```svelte + + + + {#snippet children(obj)} + + {#snippet children(arr)} + + {/snippet} + {#snippet item(prop)} +
+ + + {#snippet children(p)} + + {/snippet} + + + +
+ {/snippet} +
+ {/snippet} +
+``` + +Key points: +- Use `bind:value={items}` on `I.Array` with an external `$state` variable +- Use the `item` snippet instead of `children` with manual `{#each}` +- Use `bind:value={prop.value}` on nested `I.Object` (no `name` prop) +- Use `filter()` to create a new array reference for removal diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..26e4367 --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,11 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + preprocess: vitePreprocess(), + kit: { + files: { lib: './src' } + } +} + +export default config \ No newline at end of file From fae17cf7ec62711b9041d7d3465c0bee0f7b054f Mon Sep 17 00:00:00 2001 From: "Arthur @Refzlund" Date: Wed, 26 Nov 2025 14:33:16 +0100 Subject: [PATCH 2/2] chore: add changeset for prescriptor cleanup fix --- .changeset/fluffy-pandas-dance.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fluffy-pandas-dance.md diff --git a/.changeset/fluffy-pandas-dance.md b/.changeset/fluffy-pandas-dance.md new file mode 100644 index 0000000..2e8b721 --- /dev/null +++ b/.changeset/fluffy-pandas-dance.md @@ -0,0 +1,5 @@ +--- +"svelte-object": patch +--- + +fix(prescriptor): properly clean up prescriptor effects on removal