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
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 (
+
+ Clicked {count} times
+
+ )
+}
+
+/**
+ * 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)}
+ arr.value.push({})}
+ >
+ Add Item
+
+
+ {#each arr.value as item, k}
+
+ #{k}
+
+
+ {#snippet children(prop)}
+
+ {/snippet}
+
+
+ arr.value.splice(k, 1)}
+ >
+ Remove
+
+
+ {/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)}
+ items = [...items, {}]}>Add
+ {/snippet}
+ {#snippet item(prop)}
+
+
+
+ {#snippet children(p)}
+
+ {/snippet}
+
+
+ items = items.filter((_, i) => i !== prop.index)}>
+ Remove
+
+
+ {/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