From 09d353e883fb4baa1a30c0af69ec49b2f96d5956 Mon Sep 17 00:00:00 2001 From: Denis Donici Date: Thu, 5 Mar 2026 23:00:30 +0200 Subject: [PATCH] chore: improved docs. removed live json --- CHANGELOG.md | 6 + README.md | 38 +- assets/js/live_svelte/hooks.svelte.js | 25 +- assets/package-lock.json | 1759 +++++------------ assets/package.json | 10 +- example_project/README.md | 103 +- example_project/assets/js/app.vite.js | 3 - example_project/assets/package-lock.json | 520 +++-- example_project/assets/package.json | 9 +- example_project/assets/svelte/LiveJson.svelte | 22 - example_project/assets/svelte/SsrDemo.svelte | 17 + example_project/assets/svelte/Struct.svelte | 14 +- example_project/lib/example_web.ex | 4 +- .../example_web/components/core_components.ex | 2 +- .../lib/example_web/components/layouts.ex | 4 +- .../controllers/page_html/home.html.heex | 12 +- example_project/lib/example_web/gettext.ex | 2 +- .../lib/example_web/live/live_json.ex | 65 - .../lib/example_web/live/live_ssr.ex | 20 + example_project/lib/example_web/router.ex | 2 +- example_project/mix.exs | 2 - example_project/mix.lock | 3 +- .../test/example_web/live/live_json_test.exs | 76 - .../test/example_web/live/live_ssr_test.exs | 24 + .../phoenix_test/live_json_test.exs | 48 - .../phoenix_test/live_ssr_test.exs | 13 + guides/api_reference.md | 386 ++++ guides/basic_usage.md | 215 ++ guides/configuration.md | 130 ++ guides/deployment.md | 163 ++ guides/forms.md | 212 ++ guides/installation.md | 160 ++ guides/introduction.md | 52 + guides/ssr.md | 163 ++ guides/streams.md | 153 ++ guides/testing.md | 249 +++ guides/troubleshooting.md | 218 ++ guides/uploads.md | 224 +++ lib/live_json.ex | 28 - lib/live_svelte.ex | 63 +- mix.exs | 38 +- package-lock.json | 177 +- package.json | 6 +- test/auto_id_test.exs | 13 +- test/json_library_test.exs | 4 - test/live_svelte/json_test.exs | 2 +- test/props_diff_test.exs | 1 - test/streams_test.exs | 1 - 48 files changed, 3351 insertions(+), 2110 deletions(-) delete mode 100644 example_project/assets/svelte/LiveJson.svelte create mode 100644 example_project/assets/svelte/SsrDemo.svelte delete mode 100644 example_project/lib/example_web/live/live_json.ex create mode 100644 example_project/lib/example_web/live/live_ssr.ex delete mode 100644 example_project/test/example_web/live/live_json_test.exs create mode 100644 example_project/test/example_web/live/live_ssr_test.exs delete mode 100644 example_project/test/example_web/phoenix_test/live_json_test.exs create mode 100644 example_project/test/example_web/phoenix_test/live_ssr_test.exs create mode 100644 guides/api_reference.md create mode 100644 guides/basic_usage.md create mode 100644 guides/configuration.md create mode 100644 guides/deployment.md create mode 100644 guides/forms.md create mode 100644 guides/installation.md create mode 100644 guides/introduction.md create mode 100644 guides/ssr.md create mode 100644 guides/streams.md create mode 100644 guides/testing.md create mode 100644 guides/troubleshooting.md create mode 100644 guides/uploads.md delete mode 100644 lib/live_json.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index 36deedf..fdc962d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Removed + +- **live_json** โ€” The `live_json` hex dependency and `live_json_props` feature have been removed from the library and example project. Use built-in props diffing and JSON Patch (see Configuration) for efficient prop updates instead. + ## 0.17.4 - 2026-02-18 ### Added diff --git a/README.md b/README.md index 64a9fe0..ed2d9ec 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,6 @@ Svelte inside Phoenix LiveView with seamless end-to-end reactivity - โญ **Svelte Preprocessing** Support with [svelte-preprocess](https://github.com/sveltejs/svelte-preprocess) - ๐Ÿฆ„ **Tailwind** Support - ๐Ÿ’€ **Dead View** Support -- ๐Ÿค **live_json** Support - ๐Ÿฆฅ **Slot Interoperability** - ๐Ÿ“˜ **TypeScript** โ€” client assets in TypeScript; public API is type-safe with exported type definitions for consumers @@ -734,42 +733,7 @@ end Phoenix.HTML.Form, Ecto.Changeset, and Phoenix LiveView upload structs have built-in encoders. Date/Time types are encoded as ISO8601 strings. -### live_json - -LiveSvelte has support for [live_json](https://github.com/Miserlou/live_json). - -By default, LiveSvelte sends your entire json object over the wire through LiveView. This can be expensive if your json object is big and changes frequently. - -`live_json` on the other hand allows you to only send a _diff_ of the json to Svelte. This is very useful the bigger your json objects get. - -Counterintuitively, you don't always want to use `live_json`. Sometimes it's cheaper to send your entire object again. Although diffs are small, they do add a little bit of data to your json. So if your json is relatively small, I'd recommend not using `live_json`, but it's something to experiment with for your use-case. - -#### Usage - -1. Install [live_json](https://github.com/Miserlou/live_json#installation) - -2. Use `live_json` in your project with LiveSvelte. For example: - -```elixir -def render(assigns) do - ~H""" - <.svelte name="Component" live_json_props={%{my_prop: @ljmy_prop}} socket={@socket} /> - """ -end - -def mount(_, _, socket) do - # Get `my_big_json_object` somehow - {:ok, LiveJson.initialize("my_prop", my_big_json_object)} -end - -def handle_info(%Broadcast{event: "update", payload: my_big_json_object}, socket) do - {:noreply, LiveJson.push_patch(socket, "my_prop", my_big_json_object)} -end -``` - -#### Example - -You can find an example [here](https://github.com/woutdp/live_svelte/blob/master/example_project/lib/example_web/live/live_json.ex). +For efficient updates when only some props change, use the built-in **props diffing** (see [Configuration](https://hexdocs.pm/live_svelte/configuration.html)) and JSON Patch support; the former `live_json` integration has been removed. ### Structs and Ecto diff --git a/assets/js/live_svelte/hooks.svelte.js b/assets/js/live_svelte/hooks.svelte.js index bf351d6..fac4b8f 100644 --- a/assets/js/live_svelte/hooks.svelte.js +++ b/assets/js/live_svelte/hooks.svelte.js @@ -29,25 +29,9 @@ function getSlots(ref) { return snippets } -function getLiveJsonProps(ref) { - const json = getAttributeJson(ref, "data-live-json") - - // On SSR, data-live-json is the full object we want - // After SSR, data-live-json is an array of keys, and we'll get the data from the window - if (!Array.isArray(json)) return json - - const liveJsonData = {} - for (const liveJsonVariable of json) { - const data = window[liveJsonVariable] - if (data !== undefined) liveJsonData[liveJsonVariable] = data - } - return liveJsonData -} - function getProps(ref) { return { ...getAttributeJson(ref, "data-props"), - ...getLiveJsonProps(ref), ...getSlots(ref), live: ref, } @@ -89,9 +73,7 @@ function update_state(ref) { else state[key] = payload[key] } } - // Always keep live ref, liveJson, and slots in sync - const liveJson = getLiveJsonProps(ref) - for (const key in liveJson) state[key] = liveJson[key] + // Always keep live ref and slots in sync const slots = getSlots(ref) for (const key in slots) state[key] = slots[key] state.live = ref @@ -125,11 +107,6 @@ export function getHooks(components) { const Component = components[componentName] if (!Component) throw new Error(`Unable to find ${componentName} component.`) - for (const liveJsonElement of Object.keys(getAttributeJson(this, "data-live-json"))) { - window.addEventListener(`${liveJsonElement}_initialized`, (_event) => update_state(this), false) - window.addEventListener(`${liveJsonElement}_patched`, (_event) => update_state(this), false) - } - // Mount into the inner phx-update="ignore" div so LiveView's DOM // patching won't destroy Svelte's rendered content on server updates. const target = this.el.querySelector("[data-svelte-target]") diff --git a/assets/package-lock.json b/assets/package-lock.json index d755445..4317099 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -10,24 +10,65 @@ "phoenix_live_view": "file:../deps/phoenix_live_view" }, "devDependencies": { - "@vitest/coverage-v8": "^2.1.0", - "esbuild": "^0.24.0", - "esbuild-svelte": "^0.9.0", + "@vitest/coverage-v8": "^4.0.18", + "esbuild": "^0.27.3", + "esbuild-svelte": "^0.9.4", "jsdom": "^28.1.0", - "typescript": "^5.6.0", - "vitest": "^2.1.0" + "typescript": "^5.9.3", + "vitest": "^4.0.18" } }, "../deps/phoenix": { - "version": "1.7.0", - "license": "MIT" + "version": "1.8.4", + "license": "MIT", + "devDependencies": { + "@babel/cli": "7.28.6", + "@babel/core": "7.29.0", + "@babel/preset-env": "7.29.0", + "@eslint/js": "^9.28.0", + "@stylistic/eslint-plugin": "^5.0.0", + "documentation": "^14.0.3", + "eslint": "9.39.2", + "eslint-plugin-jest": "29.12.1", + "jest": "^30.0.0", + "jest-environment-jsdom": "^30.0.0", + "jsdom": "^27.0.0", + "mock-socket": "^9.3.1" + } }, "../deps/phoenix_html": { - "version": "3.3.1" + "version": "4.3.0" }, "../deps/phoenix_live_view": { - "version": "0.18.15", - "license": "MIT" + "version": "1.1.25", + "license": "MIT", + "dependencies": { + "morphdom": "2.7.8" + }, + "devDependencies": { + "@babel/cli": "7.27.2", + "@babel/core": "7.27.4", + "@babel/preset-env": "7.27.2", + "@babel/preset-typescript": "^7.27.1", + "@eslint/js": "^9.29.0", + "@playwright/test": "^1.56.1", + "@types/jest": "^30.0.0", + "@types/phoenix": "^1.6.6", + "css.escape": "^1.5.1", + "eslint": "9.29.0", + "eslint-plugin-jest": "28.14.0", + "eslint-plugin-playwright": "^2.2.0", + "globals": "^16.2.0", + "jest": "^30.0.0", + "jest-environment-jsdom": "^30.0.0", + "jest-monocart-coverage": "^1.1.1", + "monocart-reporter": "^2.9.21", + "phoenix": "1.7.21", + "prettier": "3.5.3", + "ts-jest": "^29.4.0", + "typescript": "^5.8.3", + "typescript-eslint": "^8.34.0" + } }, "node_modules/@acemir/cssom": { "version": "0.9.31", @@ -159,11 +200,14 @@ } }, "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/@bramus/specificity": { "version": "2.4.2", @@ -313,9 +357,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", - "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", "cpu": [ "ppc64" ], @@ -330,9 +374,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", - "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", "cpu": [ "arm" ], @@ -347,9 +391,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", - "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", "cpu": [ "arm64" ], @@ -364,9 +408,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", - "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", "cpu": [ "x64" ], @@ -381,9 +425,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", - "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", "cpu": [ "arm64" ], @@ -398,9 +442,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", - "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", "cpu": [ "x64" ], @@ -415,9 +459,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", - "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", "cpu": [ "arm64" ], @@ -432,9 +476,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", - "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", "cpu": [ "x64" ], @@ -449,9 +493,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", - "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", "cpu": [ "arm" ], @@ -466,9 +510,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", - "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", "cpu": [ "arm64" ], @@ -483,9 +527,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", - "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", "cpu": [ "ia32" ], @@ -500,9 +544,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", - "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", "cpu": [ "loong64" ], @@ -517,9 +561,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", - "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", "cpu": [ "mips64el" ], @@ -534,9 +578,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", - "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", "cpu": [ "ppc64" ], @@ -551,9 +595,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", - "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", "cpu": [ "riscv64" ], @@ -568,9 +612,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", - "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", "cpu": [ "s390x" ], @@ -585,9 +629,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", - "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", "cpu": [ "x64" ], @@ -601,10 +645,27 @@ "node": ">=18" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", - "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", "cpu": [ "x64" ], @@ -619,9 +680,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", - "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", "cpu": [ "arm64" ], @@ -636,9 +697,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", - "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", "cpu": [ "x64" ], @@ -652,10 +713,27 @@ "node": ">=18" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", - "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", "cpu": [ "x64" ], @@ -670,9 +748,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", - "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", "cpu": [ "arm64" ], @@ -687,9 +765,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", - "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", "cpu": [ "ia32" ], @@ -704,9 +782,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", - "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", "cpu": [ "x64" ], @@ -738,34 +816,6 @@ } } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -802,16 +852,16 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -819,17 +869,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", @@ -1180,6 +1219,31 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1188,31 +1252,29 @@ "license": "MIT" }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz", - "integrity": "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz", + "integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.7", + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.0.18", + "ast-v8-to-istanbul": "^0.3.10", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.12", - "magicast": "^0.3.5", - "std-env": "^3.8.0", - "test-exclude": "^7.0.1", - "tinyrainbow": "^1.2.0" + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.9", - "vitest": "2.1.9" + "@vitest/browser": "4.0.18", + "vitest": "4.0.18" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -1221,38 +1283,40 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", - "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.9", - "@vitest/utils": "2.1.9", - "chai": "^5.1.2", - "tinyrainbow": "^1.2.0" + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", - "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.9", + "@vitest/spy": "4.0.18", "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" + "magic-string": "^0.30.21" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0" + "vite": "^6.0.0 || ^7.0.0-0" }, "peerDependenciesMeta": { "msw": { @@ -1264,70 +1328,66 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", - "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", - "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.9", - "pathe": "^1.1.2" + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", - "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.9", - "magic-string": "^0.30.12", - "pathe": "^1.1.2" + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", - "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^3.0.2" - }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", - "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.9", - "loupe": "^3.1.2", - "tinyrainbow": "^1.2.0" + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" @@ -1367,32 +1427,6 @@ "node": ">= 14" } }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", @@ -1413,6 +1447,18 @@ "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz", + "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -1423,16 +1469,6 @@ "node": ">= 0.4" } }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/bidi-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", @@ -1443,91 +1479,16 @@ "require-from-string": "^2.0.2" } }, - "node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, "engines": { "node": ">=18" } }, - "node_modules/check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/css-tree": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", @@ -1607,30 +1568,6 @@ "dev": true, "license": "MIT" }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, "node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", @@ -1652,9 +1589,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", - "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1666,36 +1603,38 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.0", - "@esbuild/android-arm": "0.24.0", - "@esbuild/android-arm64": "0.24.0", - "@esbuild/android-x64": "0.24.0", - "@esbuild/darwin-arm64": "0.24.0", - "@esbuild/darwin-x64": "0.24.0", - "@esbuild/freebsd-arm64": "0.24.0", - "@esbuild/freebsd-x64": "0.24.0", - "@esbuild/linux-arm": "0.24.0", - "@esbuild/linux-arm64": "0.24.0", - "@esbuild/linux-ia32": "0.24.0", - "@esbuild/linux-loong64": "0.24.0", - "@esbuild/linux-mips64el": "0.24.0", - "@esbuild/linux-ppc64": "0.24.0", - "@esbuild/linux-riscv64": "0.24.0", - "@esbuild/linux-s390x": "0.24.0", - "@esbuild/linux-x64": "0.24.0", - "@esbuild/netbsd-x64": "0.24.0", - "@esbuild/openbsd-arm64": "0.24.0", - "@esbuild/openbsd-x64": "0.24.0", - "@esbuild/sunos-x64": "0.24.0", - "@esbuild/win32-arm64": "0.24.0", - "@esbuild/win32-ia32": "0.24.0", - "@esbuild/win32-x64": "0.24.0" + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" } }, "node_modules/esbuild-svelte": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.9.0.tgz", - "integrity": "sha512-ebGQYTuM4U1Tfx9HdkNtfBjaxY7t7LirlD1yylpSIkhRW+zLzff1wOK1jhuM7ZCnBVCGpt6sGZqiPb5c99KzJg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.9.4.tgz", + "integrity": "sha512-v/a0GjkKN06nal2QLluxjk2GXsei3fdtjIuHRa6pVnri5rQBZ6pj4a2WwjLfRojgRsLwDHl4xSeZ1BeUHsqQrw==", "dev": true, "license": "MIT", "dependencies": { @@ -1747,21 +1686,22 @@ "node": ">=12.0.0" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=12.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, "node_modules/fsevents": { @@ -1779,28 +1719,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1859,16 +1777,6 @@ "node": ">= 14" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -1886,13 +1794,6 @@ "@types/estree": "*" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -1918,21 +1819,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-reports": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", @@ -1947,21 +1833,12 @@ "node": ">=8" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } + "license": "MIT" }, "node_modules/jsdom": { "version": "28.1.0", @@ -2012,40 +1889,26 @@ "dev": true, "license": "MIT" }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, "node_modules/magic-string": { - "version": "0.30.12", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", - "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" } }, "node_modules/make-dir": { @@ -2071,32 +1934,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2123,12 +1960,16 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", "dev": true, - "license": "BlueOak-1.0.0" + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" }, "node_modules/parse5": { "version": "8.0.0", @@ -2143,50 +1984,13 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, "node_modules/phoenix": { "resolved": "../deps/phoenix", "link": true @@ -2206,10 +2010,24 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "dev": true, "funding": [ { @@ -2326,29 +2144,6 @@ "node": ">=10" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -2356,19 +2151,6 @@ "dev": true, "license": "ISC" }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -2393,110 +2175,6 @@ "dev": true, "license": "MIT" }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -2543,21 +2221,6 @@ "dev": true, "license": "MIT" }, - "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -2566,36 +2229,36 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", "dev": true, "license": "MIT", "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/tinyrainbow": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", - "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", - "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", "dev": true, "license": "MIT", "engines": { @@ -2673,22 +2336,25 @@ } }, "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -2697,19 +2363,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -2730,505 +2402,61 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, - "node_modules/vite-node": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", - "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.7", - "es-module-lexer": "^1.5.4", - "pathe": "^1.1.2", - "vite": "^5.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, "node_modules/vitest": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", - "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@vitest/expect": "2.1.9", - "@vitest/mocker": "2.1.9", - "@vitest/pretty-format": "^2.1.9", - "@vitest/runner": "2.1.9", - "@vitest/snapshot": "2.1.9", - "@vitest/spy": "2.1.9", - "@vitest/utils": "2.1.9", - "chai": "^5.1.2", - "debug": "^4.3.7", - "expect-type": "^1.1.0", - "magic-string": "^0.30.12", - "pathe": "^1.1.2", - "std-env": "^3.8.0", + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.1", - "tinypool": "^1.0.1", - "tinyrainbow": "^1.2.0", - "vite": "^5.0.0", - "vite-node": "2.1.9", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.9", - "@vitest/ui": "2.1.9", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", "happy-dom": "*", "jsdom": "*" }, @@ -3236,10 +2464,19 @@ "@edge-runtime/vm": { "optional": true }, + "@opentelemetry/api": { + "optional": true + }, "@types/node": { "optional": true }, - "@vitest/browser": { + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { "optional": true }, "@vitest/ui": { @@ -3301,22 +2538,6 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -3334,104 +2555,6 @@ "node": ">=8" } }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", diff --git a/assets/package.json b/assets/package.json index 19fa248..6067e13 100644 --- a/assets/package.json +++ b/assets/package.json @@ -1,12 +1,12 @@ { "type": "module", "devDependencies": { - "@vitest/coverage-v8": "^2.1.0", - "esbuild": "^0.24.0", - "esbuild-svelte": "^0.9.0", + "@vitest/coverage-v8": "^4.0.18", + "esbuild": "^0.27.3", + "esbuild-svelte": "^0.9.4", "jsdom": "^28.1.0", - "typescript": "^5.6.0", - "vitest": "^2.1.0" + "typescript": "^5.9.3", + "vitest": "^4.0.18" }, "dependencies": { "phoenix": "file:../deps/phoenix", diff --git a/example_project/README.md b/example_project/README.md index 9d22e3f..94311f1 100644 --- a/example_project/README.md +++ b/example_project/README.md @@ -1,37 +1,98 @@ -# Example +# LiveSvelte Example Project -To start your Phoenix server: +A working Phoenix application demonstrating LiveSvelte features โ€” Svelte 5 components +integrated with Phoenix LiveView. -- Run `mix setup` to install and setup dependencies -- Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` +## Setup -Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. +1. Install Elixir + Node.js 19+ +2. Run `mix setup` (installs deps + npm packages + creates DB) +3. Start server: `mix phx.server` +4. Visit: http://localhost:4000 + +## Demo Categories + +### Basics +- **Hello World** (`/hello-world`) โ€” Simplest component rendering +- **Struct Props** (`/live-struct`) โ€” Passing Elixir structs as props (requires `@derive Jason.Encoder`) +- **Lodash** (`/lodash`) โ€” Using npm packages in Svelte components + +### Interactive +- **Counter** (`/live-simple-counter`) โ€” Server state + client events +- **Plus/Minus (Live)** (`/live-plus-minus`) โ€” LiveView event handling +- **Hybrid Counter** (`/live-plus-minus-hybrid`) โ€” Mix of client and server events +- **Lights** (`/live-lights`) โ€” Multiple components sharing LiveView state +- **Sigil** (`/live-sigil`) โ€” Inline Svelte templates with the `~V` sigil + +### Data & Real-Time +- **Streams** (`/streams`) โ€” Phoenix `stream()` for efficient list updates +- **Props Diff** (`/live-props-diff`) โ€” Only changed assigns sent on update (JSON Patch) +- **ID List Diff** (`/live-id-list-diff`) โ€” ID-based list diffing for minimal ops +- **Chat** (`/live-chat`) โ€” Real-time updates with PubSub + `pushEvent` +- **Log List** (`/live-log-list`) โ€” Dynamic list updates +- **Breaking News** (`/live-breaking-news`) โ€” Real-time ticker with `~V` sigil + +### Slots +- **Simple Slots** (`/live-slots-simple`) โ€” Basic slot usage +- **Dynamic Slots** (`/live-slots-dynamic`) โ€” Named slots with dynamic content + +### Composables +- **Form** (`/live-form`) โ€” `useLiveForm()` with Ecto changeset validation +- **File Upload** (`/live-upload`) โ€” `useLiveUpload()` with progress and validation +- **Navigation** (`/live-navigation`) โ€” `useLiveNavigation()` for patch/navigate +- **Composition** (`/live-composition`) โ€” `useLiveSvelte()` for pushEvent in component trees +- **Event Reply** (`/live-event-reply`) โ€” `useEventReply()` for request-response + +### Advanced +- **SSR Demo** (`/live-ssr`) โ€” Server-side rendering with NodeJS (see SSR section below) +- **Client Loading** (`/live-client-side-loading`) โ€” Loading slot shown until hydration + +### Ecto +- **Notes OTP** (`/live-notes-otp`) โ€” SQLite-backed notes with Ecto ## Testing -- Run all tests: `mix test` (by default this excludes E2E tests; use `mix test --only e2e` to run browser tests). -- Run only E2E (browser) tests: `mix test --only e2e` - -E2E test modules must use `@moduletag :e2e`. E2E tests use [Wallaby](https://hexdocs.pm/wallaby) and serve the app from **built** assets (`priv/static`). After changing Svelte (or other frontend) code, rebuild before E2E so tests see your changes: - ```bash -mix assets.js && mix test --only e2e +# Server-side tests (fast, no browser) +mix test --only phoenix_test + +# Browser E2E tests (requires ChromeDriver) +mix test --only e2e + +# All tests +mix test ``` -Or use the alias (builds assets then runs tests; pass `--only e2e` to run only E2E): - +**After changing JS/Svelte files**, rebuild before running tests: ```bash -mix test.e2e --only e2e +mix assets.js && mix test ``` -You need **Chrome** and **ChromeDriver** on your `PATH` for E2E. If Chromedriver is not installed, run `mix test --exclude e2e` to run the rest of the suite. +E2E tests require Chrome + ChromeDriver in PATH. Install with your OS package manager. +See `live_svelte/CLAUDE.md` for detailed testing guidance. -Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). +## SSR (Server-Side Rendering) + +SSR is disabled by default in development. To enable: + +**config/dev.exs:** +```elixir +config :live_svelte, ssr: true, ssr_module: LiveSvelte.SSR.ViteJS +``` + +**For production**, use NodeJS SSR (already configured): +```elixir +config :live_svelte, ssr: true, ssr_module: LiveSvelte.SSR.NodeJS +``` + +Build the SSR bundle before SSR works: +```bash +mix assets.js && mix compile +``` + +The SSR demo page (`/live-ssr`) uses `ssr={true}` on the component. NodeJS must be available for SSR in production; in test env SSR is disabled globally via `config :live_svelte, ssr: false`. ## Learn more -- Official website: https://www.phoenixframework.org/ -- Guides: https://hexdocs.pm/phoenix/overview.html -- Docs: https://hexdocs.pm/phoenix -- Forum: https://elixirforum.com/c/phoenix-forum -- Source: https://github.com/phoenixframework/phoenix +- LiveSvelte: https://github.com/woutdp/live_svelte +- Phoenix: https://hexdocs.pm/phoenix diff --git a/example_project/assets/js/app.vite.js b/example_project/assets/js/app.vite.js index 9110c7b..b63aaf4 100644 --- a/example_project/assets/js/app.vite.js +++ b/example_project/assets/js/app.vite.js @@ -6,8 +6,6 @@ import "phoenix_html" import {Socket} from "phoenix" import {LiveSocket} from "phoenix_live_view" import topbar from "../vendor/topbar" -// TODO(Epic 9): remove createLiveJsonHooks once live_json dependency is removed -import {createLiveJsonHooks} from "live_json" import {getHooks} from "live_svelte" import Components from "virtual:live-svelte-components" @@ -49,7 +47,6 @@ const PropsDiffPayloadDisplay = { } const Hooks = { - ...createLiveJsonHooks(), ...getHooks(Components), PropsDiffPayloadDisplay, } diff --git a/example_project/assets/package-lock.json b/example_project/assets/package-lock.json index e613240..d9c112c 100644 --- a/example_project/assets/package-lock.json +++ b/example_project/assets/package-lock.json @@ -5,52 +5,47 @@ "packages": { "": { "dependencies": { - "live_json": "file:../deps/live_json", "live_svelte": "file:../..", "phoenix": "file:../deps/phoenix", "phoenix_html": "file:../deps/phoenix_html", "phoenix_live_view": "file:../deps/phoenix_live_view" }, "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@sveltejs/vite-plugin-svelte": "^6.2.4", "@types/lodash": "^4.14.192", "daisyui": "^5.0.0", "dynamic-marquee": "^2.6.2", "lodash": "^4.17.21", "stylus": "^0.55.0", - "svelte": "^5.19.8", - "typescript": "^5.6.3", - "vite": "^6.0.0" + "svelte": "^5.53.7", + "typescript": "^5.9.3", + "vite": "^7.3.1" } }, "../..": { "version": "0.17.4", "license": "MIT", "devDependencies": { - "prettier": "3.3.3", - "prettier-plugin-svelte": "^3.2.7", - "svelte": "^5.1.13" + "prettier": "^3.8.1", + "prettier-plugin-svelte": "^3.5.1", + "svelte": "^5.53.7" } }, - "../deps/live_json": { - "version": "0.4.3", - "license": "MIT" - }, "../deps/phoenix": { - "version": "1.8.4", + "version": "1.8.5", "license": "MIT", "devDependencies": { "@babel/cli": "7.28.6", "@babel/core": "7.29.0", "@babel/preset-env": "7.29.0", - "@eslint/js": "^9.28.0", + "@eslint/js": "^10.0.1", "@stylistic/eslint-plugin": "^5.0.0", "documentation": "^14.0.3", - "eslint": "9.39.2", - "eslint-plugin-jest": "29.12.1", + "eslint": "10.0.2", + "eslint-plugin-jest": "29.15.0", "jest": "^30.0.0", "jest-environment-jsdom": "^30.0.0", - "jsdom": "^27.0.0", + "jsdom": "^28.1.0", "mock-socket": "^9.3.1" } }, @@ -82,24 +77,10 @@ "phoenix": "1.7.21" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", "cpu": [ "ppc64" ], @@ -114,9 +95,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", "cpu": [ "arm" ], @@ -131,9 +112,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", "cpu": [ "arm64" ], @@ -148,9 +129,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", "cpu": [ "x64" ], @@ -165,9 +146,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", "cpu": [ "arm64" ], @@ -182,9 +163,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", "cpu": [ "x64" ], @@ -199,9 +180,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", "cpu": [ "arm64" ], @@ -216,9 +197,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", "cpu": [ "x64" ], @@ -233,9 +214,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", "cpu": [ "arm" ], @@ -250,9 +231,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", "cpu": [ "arm64" ], @@ -267,9 +248,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", "cpu": [ "ia32" ], @@ -284,9 +265,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", "cpu": [ "loong64" ], @@ -301,9 +282,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", "cpu": [ "mips64el" ], @@ -318,9 +299,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", "cpu": [ "ppc64" ], @@ -335,9 +316,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", "cpu": [ "riscv64" ], @@ -352,9 +333,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", "cpu": [ "s390x" ], @@ -369,9 +350,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", "cpu": [ "x64" ], @@ -386,9 +367,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", "cpu": [ "arm64" ], @@ -403,9 +384,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", "cpu": [ "x64" ], @@ -420,9 +401,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", "cpu": [ "arm64" ], @@ -437,9 +418,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", "cpu": [ "x64" ], @@ -454,9 +435,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", "cpu": [ "arm64" ], @@ -471,9 +452,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", "cpu": [ "x64" ], @@ -488,9 +469,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", "cpu": [ "arm64" ], @@ -505,9 +486,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", "cpu": [ "ia32" ], @@ -522,9 +503,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", "cpu": [ "x64" ], @@ -539,18 +520,25 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -563,16 +551,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -581,9 +559,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -941,96 +919,56 @@ "win32" ] }, - "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.1.tgz", - "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", + "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", "dev": true, "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", + "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", + "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", - "debug": "^4.4.1", + "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", - "kleur": "^4.1.5", - "magic-string": "^0.30.17", - "vitefu": "^1.0.6" + "magic-string": "^0.30.21", + "obug": "^2.1.0", + "vitefu": "^1.1.1" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22" + "node": "^20.19 || ^22.12 || >=24" }, "peerDependencies": { "svelte": "^5.0.0", - "vite": "^6.0.0" + "vite": "^6.3.0 || ^7.0.0" } }, "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", - "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz", + "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.3.7" + "obug": "^2.1.0" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22" + "node": "^20.19 || ^22.12 || >=24" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", - "vite": "^6.0.0" + "vite": "^6.3.0 || ^7.0.0" } }, - "node_modules/@sveltejs/vite-plugin-svelte-inspector/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@sveltejs/vite-plugin-svelte-inspector/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sveltejs/vite-plugin-svelte/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@sveltejs/vite-plugin-svelte/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1045,10 +983,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "peer": true, @@ -1059,20 +1004,10 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-typescript": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz", - "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": ">=8.9.0" - } - }, "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1199,6 +1134,13 @@ "node": ">=0.10.0" } }, + "node_modules/devalue": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.3.tgz", + "integrity": "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==", + "dev": true, + "license": "MIT" + }, "node_modules/dynamic-marquee": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/dynamic-marquee/-/dynamic-marquee-2.6.4.tgz", @@ -1207,13 +1149,12 @@ "license": "Apache-2.0" }, "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -1221,32 +1162,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" } }, "node_modules/esm-env": { @@ -1257,9 +1198,9 @@ "license": "MIT" }, "node_modules/esrap": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.3.tgz", - "integrity": "sha512-Xddc1RsoFJ4z9nR7W7BFaEPIp4UXoeQ0+077UdWLxbafMQFyU79sQJMk7kxNgRwQ9/aVgaKacCHC2pUACGwmYw==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.3.tgz", + "integrity": "sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1339,20 +1280,6 @@ "@types/estree": "^1.0.6" } }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/live_json": { - "resolved": "../deps/live_json", - "link": true - }, "node_modules/live_svelte": { "resolved": "../..", "link": true @@ -1433,6 +1360,17 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1627,23 +1565,25 @@ } }, "node_modules/svelte": { - "version": "5.19.8", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.19.8.tgz", - "integrity": "sha512-56Vd/nwJrljV0w7RCV1A8sB4/yjSbWW5qrGDTAzp7q42OxwqEWT+6obWzDt41tHjIW+C9Fs2ygtejjJrXR+ZPA==", + "version": "5.53.7", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.7.tgz", + "integrity": "sha512-uxck1KI7JWtlfP3H6HOWi/94soAl23jsGJkBzN2BAWcQng0+lTrRNhxActFqORgnO9BHVd1hKJhG+ljRuIUWfQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@ampproject/remapping": "^2.3.0", + "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", - "acorn-typescript": "^1.4.13", - "aria-query": "^5.3.1", + "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", + "devalue": "^5.6.3", "esm-env": "^1.2.1", - "esrap": "^1.4.3", + "esrap": "^2.2.2", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", @@ -1703,9 +1643,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1717,25 +1657,25 @@ } }, "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -1744,14 +1684,14 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", - "less": "*", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -1852,9 +1792,9 @@ "license": "ISC" }, "node_modules/zimmerframe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", - "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", "dev": true, "license": "MIT" } diff --git a/example_project/assets/package.json b/example_project/assets/package.json index ca76277..a4c5576 100644 --- a/example_project/assets/package.json +++ b/example_project/assets/package.json @@ -1,18 +1,17 @@ { "type": "module", "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@sveltejs/vite-plugin-svelte": "^6.2.4", "@types/lodash": "^4.14.192", "daisyui": "^5.0.0", "dynamic-marquee": "^2.6.2", "lodash": "^4.17.21", "stylus": "^0.55.0", - "svelte": "^5.19.8", - "typescript": "^5.6.3", - "vite": "^6.0.0" + "svelte": "^5.53.7", + "typescript": "^5.9.3", + "vite": "^7.3.1" }, "dependencies": { - "live_json": "file:../deps/live_json", "live_svelte": "file:../..", "phoenix": "file:../deps/phoenix", "phoenix_html": "file:../deps/phoenix_html", diff --git a/example_project/assets/svelte/LiveJson.svelte b/example_project/assets/svelte/LiveJson.svelte deleted file mode 100644 index bbe65e3..0000000 --- a/example_project/assets/svelte/LiveJson.svelte +++ /dev/null @@ -1,22 +0,0 @@ - - -
-

Check the WebSocket to see how much data is transferred.

-
-
-
Key length
-
{keyCount.toLocaleString()}
-
-
-
Rough byte size
-
{byteSize.toLocaleString()}
-
-
- -
diff --git a/example_project/assets/svelte/SsrDemo.svelte b/example_project/assets/svelte/SsrDemo.svelte new file mode 100644 index 0000000..6d6302a --- /dev/null +++ b/example_project/assets/svelte/SsrDemo.svelte @@ -0,0 +1,17 @@ + + +
+

{greeting}

+

This text was rendered by the server before JavaScript loaded.

+
+ + {clicks} +
+

Click counter is client-side only (not SSR)

+
diff --git a/example_project/assets/svelte/Struct.svelte b/example_project/assets/svelte/Struct.svelte index 1fcff2c..26fae49 100644 --- a/example_project/assets/svelte/Struct.svelte +++ b/example_project/assets/svelte/Struct.svelte @@ -1,13 +1,21 @@
Struct -
{JSON.stringify(struct, null, 2)}
- +
diff --git a/example_project/lib/example_web.ex b/example_project/lib/example_web.ex index 870f22c..b741071 100644 --- a/example_project/lib/example_web.ex +++ b/example_project/lib/example_web.ex @@ -43,7 +43,7 @@ defmodule ExampleWeb do layouts: [html: ExampleWeb.Layouts] import Plug.Conn - import ExampleWeb.Gettext + use Gettext, backend: ExampleWeb.Gettext unquote(verified_routes()) end @@ -85,7 +85,7 @@ defmodule ExampleWeb do import Phoenix.HTML # Core UI components and translation import ExampleWeb.CoreComponents - import ExampleWeb.Gettext + use Gettext, backend: ExampleWeb.Gettext import LiveSvelte diff --git a/example_project/lib/example_web/components/core_components.ex b/example_project/lib/example_web/components/core_components.ex index 4ee214c..2ea4a97 100644 --- a/example_project/lib/example_web/components/core_components.ex +++ b/example_project/lib/example_web/components/core_components.ex @@ -9,9 +9,9 @@ defmodule ExampleWeb.CoreComponents do Icons are provided by [heroicons](https://heroicons.com). See `icon/1` for usage. """ use Phoenix.Component + use Gettext, backend: ExampleWeb.Gettext alias Phoenix.LiveView.JS - import ExampleWeb.Gettext @doc """ Renders a modal. diff --git a/example_project/lib/example_web/components/layouts.ex b/example_project/lib/example_web/components/layouts.ex index 114c000..b68f878 100644 --- a/example_project/lib/example_web/components/layouts.ex +++ b/example_project/lib/example_web/components/layouts.ex @@ -32,7 +32,6 @@ defmodule ExampleWeb.Layouts do %{label: "Log List", to: ~p"/live-log-list"}, %{label: "Breaking News", to: ~p"/live-breaking-news"}, %{label: "Chat", to: ~p"/live-chat"}, - %{label: "LiveJSON", to: ~p"/live-json"}, %{label: "Props Diff", to: ~p"/live-props-diff"}, %{label: "ID List Diff", to: ~p"/live-id-list-diff"} ] @@ -47,7 +46,8 @@ defmodule ExampleWeb.Layouts do %{ label: "Advanced", links: [ - %{label: "Client Loading", to: ~p"/live-client-side-loading"} + %{label: "Client Loading", to: ~p"/live-client-side-loading"}, + %{label: "SSR Demo", to: ~p"/live-ssr"} ] }, %{ diff --git a/example_project/lib/example_web/controllers/page_html/home.html.heex b/example_project/lib/example_web/controllers/page_html/home.html.heex index c222c46..a87fbef 100644 --- a/example_project/lib/example_web/controllers/page_html/home.html.heex +++ b/example_project/lib/example_web/controllers/page_html/home.html.heex @@ -114,12 +114,6 @@ - PubSub + pushEvent -
  • - - LiveJSON - - - Efficient JSON diffing -
  • Props Diff @@ -180,6 +174,12 @@ - Loading states and SSR
  • +
  • + + SSR Demo + + - Server-side rendering with NodeJS +
  • diff --git a/example_project/lib/example_web/gettext.ex b/example_project/lib/example_web/gettext.ex index 7d87b5d..11dce1b 100644 --- a/example_project/lib/example_web/gettext.ex +++ b/example_project/lib/example_web/gettext.ex @@ -20,5 +20,5 @@ defmodule ExampleWeb.Gettext do See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. """ - use Gettext, otp_app: :example + use Gettext.Backend, otp_app: :example end diff --git a/example_project/lib/example_web/live/live_json.ex b/example_project/lib/example_web/live/live_json.ex deleted file mode 100644 index df528b4..0000000 --- a/example_project/lib/example_web/live/live_json.ex +++ /dev/null @@ -1,65 +0,0 @@ -defmodule ExampleWeb.LiveJson do - use ExampleWeb, :live_view - - def render(assigns) do - ~H""" -
    -

    - Live JSON -

    -

    - Large payloads are patched over the wire. Compare SSR vs no-SSR and watch the WebSocket traffic when removing elements. -

    - -
    -
    -
    - - SSR - - <.svelte - name="LiveJson" - live_json_props={%{big_data_set: @ljbig_data_set}} - socket={@socket} - /> -
    -
    -
    -
    - - No SSR - - <.svelte name="LiveJson" live_json_props={%{big_data_set: @ljbig_data_set}} ssr={false} /> -
    -
    -
    -
    - """ - end - - def mount(_session, _params, socket) do - data = - for i <- 1..100_000, - into: %{} do - {i, Enum.random(1..1_000_000_000)} - end - - {:ok, LiveJson.initialize(socket, "big_data_set", data)} - end - - def handle_event("remove_element", _values, socket) do - random_key = - socket.assigns.ljbig_data_set - |> Map.keys() - |> Enum.random() - - { - :noreply, - LiveJson.push_patch( - socket, - "big_data_set", - Map.delete(socket.assigns.ljbig_data_set, random_key) - ) - } - end -end diff --git a/example_project/lib/example_web/live/live_ssr.ex b/example_project/lib/example_web/live/live_ssr.ex new file mode 100644 index 0000000..8fa7d38 --- /dev/null +++ b/example_project/lib/example_web/live/live_ssr.ex @@ -0,0 +1,20 @@ +defmodule ExampleWeb.LiveSsr do + use ExampleWeb, :live_view + + def mount(_params, _session, socket) do + {:ok, assign(socket, greeting: "Hello from the server!")} + end + + def render(assigns) do + ~H""" +
    +

    SSR Demo

    +

    + This component is rendered on the server using NodeJS. The initial HTML includes + the Svelte output before the client-side JavaScript runs. +

    + <.svelte name="SsrDemo" props={%{greeting: @greeting}} socket={@socket} ssr={true} /> +
    + """ + end +end diff --git a/example_project/lib/example_web/router.ex b/example_project/lib/example_web/router.ex index 6d40ad1..8a662b7 100644 --- a/example_project/lib/example_web/router.ex +++ b/example_project/lib/example_web/router.ex @@ -32,7 +32,6 @@ defmodule ExampleWeb.Router do live("/live-log-list", LiveLogList) live("/live-breaking-news", LiveBreakingNews) live("/live-chat", LiveChat) - live("/live-json", LiveJson) live("/live-props-diff", LivePropsDiff) live("/streams", Streams) live("/live-id-list-diff", LiveIdListDiff) @@ -47,6 +46,7 @@ defmodule ExampleWeb.Router do live("/live-event-reply", LiveEventReply) live("/live-navigation", LiveNavigation) live("/live-navigation/:page", LiveNavigation) + live("/live-ssr", LiveSsr) live("/live-composition", LiveComposition) end diff --git a/example_project/mix.exs b/example_project/mix.exs index 72ef339..944ca24 100644 --- a/example_project/mix.exs +++ b/example_project/mix.exs @@ -36,8 +36,6 @@ defmodule Example.MixProject do {:ecto_sql, "~> 3.12"}, {:gettext, "~> 0.20"}, {:json_diff_ex, "~> 0.6", override: true}, - {:jsonpatch, "~> 2.3", override: true}, - {:live_json, "~> 0.4.5"}, {:live_svelte, path: ".."}, # {:live_svelte, "~> 0.17.4"}, {:phoenix, "~> 1.8.0"}, diff --git a/example_project/mix.lock b/example_project/mix.lock index ef5bdb8..518d410 100644 --- a/example_project/mix.lock +++ b/example_project/mix.lock @@ -22,14 +22,13 @@ "json_diff_ex": {:hex, :json_diff_ex, "0.7.0", "ef9a7809fce09fecc7fe4ffcac85658ab6de6aaccd55e056dea16da4ecfa6121", [:mix], [], "hexpm", "be71212b736f0f36ef5d805c7d72de4d4e57b5176d0cca99d78eb4d8198da547"}, "jsonpatch": {:hex, :jsonpatch, "2.3.1", "49c380f458debbd2bc6e256daeab1081dc89624288f3d77ea83952229388d316", [:make, :mix], [], "hexpm", "06c3e4fff3574cc54d335041f6322fe1b72756e396dd472615ce350d3dd5e758"}, "lazy_html": {:hex, :lazy_html, "0.1.8", "677a8642e644eef8de98f3040e2520d42d0f0f8bd6c5cd49db36504e34dffe91", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.9.0", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}], "hexpm", "0d8167d930b704feb94b41414ca7f5779dff9bca7fcf619fcef18de138f08736"}, - "live_json": {:hex, :live_json, "0.4.5", "7a97932a8bb944d546a2e0af231aa90889eeaa0ab3f77afc50748636d9202581", [:mix], [{:jason, ">= 1.3.0", [hex: :jason, repo: "hexpm", optional: true]}, {:json_diff_ex, "~> 0.5.0", [hex: :json_diff_ex, repo: "hexpm", optional: false]}, {:jsonpatch, "~> 0.13.1", [hex: :jsonpatch, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, ">= 3.1.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, ">= 0.16.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "43f941c94ef3a917f0940552316ae23128b8da274511d1d356494441a69edd71"}, "live_svelte": {:hex, :live_svelte, "0.17.2", "b644a22c96e44e03491240e6302fc21cae0c66e2b702531cbbd4732a9f08cc1d", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:nodejs, "~> 3.1", [hex: :nodejs, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, ">= 3.3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, ">= 0.18.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "17ddf9bca37ddc8e1090dad267cc81f846a8ad8647864e6172fb9fcdd7a0c066"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, "mimerl": {:hex, :mimerl, "1.4.0", "3882a5ca67fbbe7117ba8947f27643557adec38fa2307490c4c4207624cb213b", [:rebar3], [], "hexpm", "13af15f9f68c65884ecca3a3891d50a7b57d82152792f3e19d88650aa126b144"}, "nodejs": {:hex, :nodejs, "3.1.3", "8693fae9fbefa14fb99329292c226df4d4711acfa5a3fa4182dd8d3f779b30bf", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5.1", [hex: :poolboy, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.7", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e7751aad77ac55f8e6c5c07617378afd88d2e0c349d9db2ebb5273aae46ef6a9"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, - "phoenix": {:hex, :phoenix, "1.8.4", "0387f84f00071cba8d71d930b9121b2fb3645197a9206c31b908d2e7902a4851", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c988b1cd3b084eebb13e6676d572597d387fa607dab258526637b4e6c4c08543"}, + "phoenix": {:hex, :phoenix, "1.8.5", "919db335247e6d4891764dc3063415b0d2457641c5f9b3751b5df03d8e20bbcf", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "83b2bb125127e02e9f475c8e3e92736325b5b01b0b9b05407bcb4083b7a32485"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.7.0", "75c4b9dfb3efdc42aec2bd5f8bccd978aca0651dbcbc7a3f362ea5d9d43153c6", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "1d75011e4254cb4ddf823e81823a9629559a1be93b4321a6a5f11a5306fbf4cc"}, "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"}, diff --git a/example_project/test/example_web/live/live_json_test.exs b/example_project/test/example_web/live/live_json_test.exs deleted file mode 100644 index c116db1..0000000 --- a/example_project/test/example_web/live/live_json_test.exs +++ /dev/null @@ -1,76 +0,0 @@ -defmodule ExampleWeb.LiveJsonTest do - @moduledoc """ - E2E test for the LiveJson LiveView (/live-json). - Validates that the page mounts with two sections (SSR and No SSR), shows key length - and byte size, and that clicking Remove element decreases the key count. - """ - use ExampleWeb.FeatureCase, async: false - - @moduletag :e2e - - # First key-count dd in the first section (SSR) - defp first_key_count_dd(session) do - session |> all(Query.css("[data-testid='live-json-key-count']")) |> List.first() - end - - defp wait_for_key_count(session, expected, attempts \\ 50) do - dd = first_key_count_dd(session) - actual = dd && Wallaby.Element.text(dd) - - cond do - actual == expected -> - session - - attempts == 0 -> - raise "timeout waiting for key count #{inspect(expected)}, got #{inspect(actual)}" - - true -> - :timer.sleep(100) - wait_for_key_count(session, expected, attempts - 1) - end - end - - test "page mounts and shows heading and description", %{session: session} do - session - |> visit("/live-json") - |> assert_has(Query.css("h2", text: "Live JSON")) - |> assert_has( - Query.css("p", - text: - "Large payloads are patched over the wire. Compare SSR vs no-SSR and watch the WebSocket traffic when removing elements." - ) - ) - end - - test "renders two sections with key length and Remove element button", %{session: session} do - session = visit(session, "/live-json") - - # Two section cards (SSR and No SSR); at least one of each badge - ssr_badges = session |> all(Query.css("section.card span.badge", text: "SSR")) - no_ssr_badges = session |> all(Query.css("section.card span.badge", text: "No SSR")) - assert length(ssr_badges) >= 1 - assert length(no_ssr_badges) >= 1 - - # Wait for Svelte to hydrate and show key count - session = wait_for_key_count(session, "100,000") - key_length_dts = session |> all(Query.css("dt", text: "Key length")) - assert length(key_length_dts) >= 1 - remove_btns = session |> all(Query.css("[data-testid='live-json-remove-element']")) - assert length(remove_btns) >= 1 - end - - test "clicking Remove element decreases key count", %{session: session} do - session = - session - |> visit("/live-json") - |> wait_for_key_count("100,000") - - # Click the first section's Remove element button - [remove_btn | _] = session |> all(Query.css("[data-testid='live-json-remove-element']")) - Wallaby.Element.click(remove_btn) - - session = wait_for_key_count(session, "99,999") - dd = first_key_count_dd(session) - assert Wallaby.Element.text(dd) == "99,999" - end -end diff --git a/example_project/test/example_web/live/live_ssr_test.exs b/example_project/test/example_web/live/live_ssr_test.exs new file mode 100644 index 0000000..667b4a3 --- /dev/null +++ b/example_project/test/example_web/live/live_ssr_test.exs @@ -0,0 +1,24 @@ +defmodule ExampleWeb.Live.LiveSsrTest do + @moduledoc """ + E2E test for the LiveSsr LiveView (/live-ssr). + Validates that the SSR demo page renders the greeting from props and that + the client-side click counter works after hydration. + """ + use ExampleWeb.FeatureCase, async: false + + @moduletag :e2e + + test "renders SSR greeting and click counter works", %{session: session} do + session = visit(session, "/live-ssr") + + session |> find(Query.css("[data-testid='ssr-greeting']", text: "Hello from the server!")) + + count_el = session |> find(Query.css("[data-testid='click-count']")) + assert Wallaby.Element.text(count_el) == "0" + + session = session |> click(Query.css("[data-testid='ssr-increment']")) + + count_el = session |> find(Query.css("[data-testid='click-count']")) + assert Wallaby.Element.text(count_el) == "1" + end +end diff --git a/example_project/test/example_web/phoenix_test/live_json_test.exs b/example_project/test/example_web/phoenix_test/live_json_test.exs deleted file mode 100644 index 48e331e..0000000 --- a/example_project/test/example_web/phoenix_test/live_json_test.exs +++ /dev/null @@ -1,48 +0,0 @@ -defmodule ExampleWeb.PhoenixTest.LiveJsonTest do - @moduledoc """ - PhoenixTest (in-process) for LiveJson (/live-json). - Validates that the page renders two LiveJson sections (SSR and No SSR) - and shows key length and Remove element button. Remove-element behavior - is covered in E2E (live_json_test.exs). - """ - use ExampleWeb.ConnCase, async: false - import PhoenixTest - - @moduletag :phoenix_test - - setup do - ssr = Application.get_env(:live_svelte, :ssr, false) - Application.put_env(:live_svelte, :ssr, true) - - on_exit(fn -> - Application.put_env(:live_svelte, :ssr, ssr) - end) - - :ok - end - - test "renders page heading and description", %{conn: conn} do - conn - |> visit("/live-json") - |> assert_has("h2", text: "Live JSON") - |> assert_has("p", - text: - "Large payloads are patched over the wire. Compare SSR vs no-SSR and watch the WebSocket traffic when removing elements." - ) - end - - test "renders two sections (SSR and No SSR) with LiveJson component", %{conn: conn} do - conn - |> visit("/live-json") - |> assert_has("span.badge", text: "SSR") - |> assert_has("span.badge", text: "No SSR") - |> assert_has("[data-name='LiveJson']", count: 2) - end - - test "shows key length and Remove element button", %{conn: conn} do - conn - |> visit("/live-json") - |> assert_has("dt", text: "Key length") - |> assert_has("[data-testid='live-json-remove-element']") - end -end diff --git a/example_project/test/example_web/phoenix_test/live_ssr_test.exs b/example_project/test/example_web/phoenix_test/live_ssr_test.exs new file mode 100644 index 0000000..2268521 --- /dev/null +++ b/example_project/test/example_web/phoenix_test/live_ssr_test.exs @@ -0,0 +1,13 @@ +defmodule ExampleWeb.LiveSsrTest do + use ExampleWeb.ConnCase, async: true + @moduletag :phoenix_test + + import PhoenixTest + + test "renders SSR demo page", %{conn: conn} do + conn + |> visit("/live-ssr") + |> assert_has("h2", text: "SSR Demo") + |> assert_has("[data-props*='greeting']") + end +end diff --git a/guides/api_reference.md b/guides/api_reference.md new file mode 100644 index 0000000..5576ca0 --- /dev/null +++ b/guides/api_reference.md @@ -0,0 +1,386 @@ +# API Reference + +Complete reference for all public LiveSvelte APIs. + +## Elixir API + +### `LiveSvelte.svelte/1` + +Renders a Svelte component in a LiveView template. + +```heex +<.svelte name="Counter" props={%{count: @count}} socket={@socket} /> +``` + +**Attributes:** + +| Attribute | Type | Default | Required | Description | +|-----------|------|---------|----------|-------------| +| `name` | `string` | โ€” | โœ“ | Component name (filename without `.svelte`, relative to `assets/svelte/`) | +| `props` | `map` | `%{}` | | Props to pass to the component | +| `socket` | `map` | `nil` | | LiveView socket โ€” required when `ssr: true` | +| `id` | `string` | auto | | Stable DOM id override | +| `key` | `any` | `nil` | | Identity key for DOM id generation in loops | +| `class` | `string` | `nil` | | CSS class on the wrapper div | +| `ssr` | `boolean` | `true` | | Enable SSR for this component | +| `diff` | `boolean` | `true` | | Enable props diffing (requires `enable_props_diff: true` in config) | +| `:loading` slot | | | | Content shown while component loads (only with `ssr={false}`) | +| `:inner_block` slot | | | | Inner content (passed to Svelte as a slot) | + +**Name examples:** +``` +Counter.svelte โ†’ name="Counter" +forms/UserForm.svelte โ†’ name="forms/UserForm" +``` + +--- + +### `~V` Sigil + +Inline Svelte template as a LiveView render macro. + +```elixir +def render(assigns) do + ~V""" + +

    Count: {count}

    + """ +end +``` + +All LiveView assigns are automatically available as props. The template is written to `assets/svelte/_build/MyModule.svelte` at compile time. + +--- + +### `LiveSvelte.Components` + +Auto-generated shorthand component functions based on discovered `.svelte` files. + +```elixir +# In web module html_helpers: +use LiveSvelte.Components + +# In templates โ€” instead of <.svelte name="Counter" ...>: +<.Counter count={@count} socket={@socket} /> +``` + +`Counter.svelte` โ†’ `<.Counter>`, `forms/UserForm.svelte` โ†’ `<.forms_UserForm>` (slashes converted to underscores). + +--- + +### `LiveSvelte.Test.get_svelte/1,2` + +Inspect Svelte component props from HTML in tests. + +```elixir +import LiveSvelte.Test + +# Get first component in HTML +component = get_svelte(html) + +# Get component by name +component = get_svelte(html, name: "Counter") + +# Get component by DOM id +component = get_svelte(html, id: "Counter-1") + +# Get directly from a LiveView +{:ok, view, _html} = live(conn, "/counter") +component = get_svelte(view, name: "Counter") +``` + +Returns a map with: +- `name` โ€” component name string +- `id` โ€” DOM id of the wrapper element +- `props` โ€” decoded props map (string keys) +- `slots` โ€” map of slot name โ†’ HTML string +- `ssr` โ€” boolean, whether SSR was active + +**Example:** +```elixir +{:ok, _view, html} = live(conn, "/counter") +component = get_svelte(html, name: "Counter") +assert component.props["count"] == 0 +``` + +--- + +### `LiveSvelte.Encoder` Protocol + +Protocol for encoding custom structs as JSON props. Implement it directly or use `@derive`: + +```elixir +# Simple derive โ€” exposes all public fields +@derive LiveSvelte.Encoder +defstruct [:id, :name] + +# Restricted derive โ€” only expose listed fields +@derive {LiveSvelte.Encoder, only: [:id, :name, :email]} +defstruct [:id, :name, :email, :password_hash] + +# Excluded fields derive +@derive {LiveSvelte.Encoder, except: [:password_hash]} +defstruct [:id, :name, :email, :password_hash] +``` + +Without `@derive`, passing a struct as a prop will raise an error. + +--- + +### `LiveSvelte.Reload` / `vite_assets/0` + +HMR helper for development. Includes the Vite dev server client script. + +```heex + +<%= if Application.get_env(:live_svelte, :ssr_module) == LiveSvelte.SSR.ViteJS do %> + +<% end %> +``` + +--- + +## JavaScript API + +### `getHooks(Components)` + +Entry point. Returns a hooks map to pass to `LiveSocket`: + +```ts +import { getHooks } from "live_svelte" +import Components from "virtual:live-svelte-components" + +const liveSocket = new LiveSocket("/live", Socket, { + hooks: getHooks(Components), + params: { _csrf_token: csrfToken } +}) +``` + +--- + +### `useLiveSvelte()` + +Access the Phoenix hook context from any LiveSvelte-mounted component. + +```ts +import { useLiveSvelte } from "live_svelte" +``` + +```svelte + +``` + +**Returns:** +- `live` โ€” raw Phoenix hook context +- `pushEvent(event, payload, callback?)` โ€” push event to LiveView +- `pushEventTo(target, event, payload, callback?)` โ€” push event to specific LiveView + +--- + +### `useLiveEvent(event, callback)` + +Subscribe to a server-sent LiveView event. Automatically cleans up on component destroy. + +```svelte + +``` + +--- + +### `useLiveConnection()` + +Reactive WebSocket connection state. + +```svelte + + +{#if !conn.connected} + +{/if} +``` + +**Returns:** +- `connected` โ€” `boolean`, reactive + +--- + +### `useLiveNavigation()` + +Client-side LiveView navigation. + +```svelte + + + + +``` + +**Returns:** +- `patch(hrefOrParams, opts?)` โ€” patch current LiveView (triggers `handle_params/3`) +- `navigate(href, opts?)` โ€” navigate to a new LiveView + +Both accept `{ replace: true }` to use `history.replaceState`. + +--- + +### `useLiveForm(formFn, opts?)` + +Reactive form binding with Ecto changeset support. See [Forms and Validation](forms.md) for full documentation. + +```ts +import { useLiveForm } from "live_svelte" +``` + +```svelte + +``` + +**Parameters:** +- `formFn` โ€” getter function returning the form prop +- `opts?` โ€” `{ changeEvent?, submitEvent?, debounceInMilliseconds? }` + +**Returns:** +- `field(name)` โ€” field descriptor with `name`, `value`, `error`, `phx-debounce` +- `fieldArray(name)` โ€” array field with `fields`, `append`, `prepend`, `remove` + +--- + +### `useLiveUpload(uploadConfig, options)` + +File upload integration. See [File Uploads](uploads.md) for full documentation. + +```ts +import { useLiveUpload } from "live_svelte" +``` + +```svelte + +``` + +**Parameters:** +- `uploadConfig` โ€” the upload config object (e.g. `uploads.avatar`), passed directly not as a getter +- `options` โ€” `{ changeEvent?: string, submitEvent: string }` โ€” `submitEvent` is required + +**Returns:** +- `showFilePicker()` โ€” open file picker dialog +- `addFiles(files)` โ€” enqueue files from `File[]` or `DataTransfer` (drag-drop) +- `entries` โ€” `Readable` store โ€” use `$entries` in templates +- `progress` โ€” `Readable` โ€” overall progress 0โ€“100 +- `valid` โ€” `Readable` โ€” true when no top-level upload errors +- `submit()` โ€” programmatic form submit +- `cancel(ref?)` โ€” cancel entry by ref string, or all when omitted +- `clear()` โ€” reset file input +- `sync(config)` โ€” merge updated config from server; call in `$effect` + +--- + +### `useEventReply()` + +Request-response pattern: push an event and await a reply. + +```ts +import { useEventReply } from "live_svelte" +``` + +```svelte + +``` + +**Returns:** +- `push(event, payload)` โ€” returns a `Promise` that resolves with the server reply + +The LiveView must reply using `{:reply, payload, socket}` in `handle_event/3`: + +```elixir +def handle_event("save", params, socket) do + {:reply, %{status: "ok"}, socket} +end +``` + +--- + +### `Link` Component + +Client-side navigation component. Svelte equivalent of Phoenix's `<.link>`. + +```svelte + + +Go to other page +Replace history +``` + +--- + +## Telemetry Events + +| Event | Measurements | Metadata | Description | +|-------|-------------|----------|-------------| +| `[:live_svelte, :ssr, :start]` | `%{system_time: integer}` | `%{component: name}` | SSR render begins | +| `[:live_svelte, :ssr, :stop]` | `%{duration_microseconds: integer}` | `%{component: name}` | SSR render completes | +| `[:live_svelte, :ssr, :exception]` | `%{system_time: integer}` | `%{component: name, reason: term}` | SSR render fails | + +--- + +## Configuration Keys + +See [Configuration](configuration.md) for full details. + +| Key | Default | Description | +|-----|---------|-------------| +| `config :live_svelte, :ssr` | `true` | Global SSR enable/disable | +| `config :live_svelte, :ssr_module` | `LiveSvelte.SSR.NodeJS` | SSR module | +| `config :live_svelte, :json_library` | `LiveSvelte.JSON` | JSON encoder | +| `config :live_svelte, :enable_props_diff` | `true` | Props diffing system | +| `config :live_svelte, :gettext_backend` | `nil` | Gettext for form errors | +| `config :live_svelte, :vite_host` | `"http://localhost:5173"` | Vite dev server URL | diff --git a/guides/basic_usage.md b/guides/basic_usage.md new file mode 100644 index 0000000..fd0a427 --- /dev/null +++ b/guides/basic_usage.md @@ -0,0 +1,215 @@ +# Basic Usage + +This guide covers the fundamentals of using LiveSvelte: the `<.svelte>` component, props, events, and the `~V` sigil. + +## Your First Component + +### 1. Create a Svelte component + +Place Svelte files in `assets/svelte/`. LiveSvelte discovers all `*.svelte` files in that directory at compile time. + +```svelte + + + +
    +

    Count: {count}

    + +
    +``` + +### 2. Use it in a LiveView + +```elixir +# lib/my_app_web/live/counter_live.ex +defmodule MyAppWeb.CounterLive do + use MyAppWeb, :live_view + + def mount(_params, _session, socket) do + {:ok, assign(socket, :count, 0)} + end + + def handle_event("increment", _params, socket) do + {:noreply, update(socket, :count, &(&1 + 1))} + end + + def render(assigns) do + ~H""" + <.svelte name="Counter" props={%{count: @count}} socket={@socket} /> + """ + end +end +``` + +That's it. When the user clicks the button, `pushEvent("increment", {})` sends the event to `handle_event/3`, the count is incremented, and Svelte re-renders automatically. + +## Props + +Pass any JSON-serializable map as `props`: + +```heex +<.svelte name="UserCard" props={%{name: @user.name, role: @user.role}} socket={@socket} /> +``` + +In the component, receive with `$props()`: + +```svelte + + +
    +

    {name}

    + {role} +
    +``` + +### Struct Props + +Structs must implement the `LiveSvelte.Encoder` protocol before being passed as props. Use `@derive` for the default implementation: + +```elixir +defmodule MyApp.User do + @derive {LiveSvelte.Encoder, only: [:id, :name, :email]} + defstruct [:id, :name, :email, :password_hash] +end +``` + +The `only:` list controls which fields are exposed. Never derive without `only:` for structs with sensitive fields. + +## The `live` Prop + +LiveSvelte automatically passes a `live` prop to every mounted component. Use it to communicate with the server: + +```svelte + +``` + +## Composable Alternative to `live` Prop + +Instead of using the `live` prop, you can use composables which work from any component in the tree โ€” no prop drilling: + +```svelte + +``` + +See the [API Reference](api_reference.md) for all composables. + +## Component Shorthand with `LiveSvelte.Components` + +Add `use LiveSvelte.Components` to your LiveView (or web module) for shorthand component functions: + +```elixir +# In web module html_helpers (added by Igniter installer): +import LiveSvelte +use LiveSvelte.Components +``` + +Then instead of `<.svelte name="Counter" ...>`, use: + +```heex +<.Counter count={@count} socket={@socket} /> +``` + +The function names are generated from your `.svelte` filenames. `Counter.svelte` โ†’ `<.Counter>`, `UserCard.svelte` โ†’ `<.UserCard>`. + +> #### `socket` is required for SSR {: .info} +> +> Always pass `socket={@socket}` when SSR is enabled. It's used to detect the initial dead render vs. connected live render. You can omit it only when `ssr={false}`. + +## Inline Templates with the `~V` Sigil + +For small, one-off components, write Svelte templates inline using the `~V` sigil: + +```elixir +def render(assigns) do + ~V""" + +

    Count is {count}

    + """ +end +``` + +The sigil writes the template to `assets/svelte/_build/` at compile time and mounts it like any other component. All LiveView assigns are automatically available as props. + +> #### Svelte 5 Syntax Required {: .warning} +> +> Always use Svelte 5 runes syntax. Do NOT use Svelte 4 patterns: +> +> | โŒ Svelte 4 | โœ… Svelte 5 | +> |-------------|-------------| +> | `export let count` | `let { count } = $props()` | +> | `let x = 0` (reactive) | `let x = $state(0)` | +> | `$: doubled = x * 2` | `let doubled = $derived(x * 2)` | +> | ` + + + +{#each filtered as item} +
  • {item.name}
  • +{/each} +``` + +## Component Discovery + +LiveSvelte scans `assets/svelte/**/*.svelte` at compile time. Component names in `<.svelte name="...">` are relative paths without the `.svelte` extension: + +``` +assets/svelte/Counter.svelte โ†’ name="Counter" +assets/svelte/forms/UserForm.svelte โ†’ name="forms/UserForm" +``` + +## phx-update="ignore" + +LiveSvelte automatically sets `phx-update="ignore"` on the component wrapper div, which prevents LiveView from patching Svelte's DOM after mount. All updates flow through the hook. This is required for correct operation โ€” do not override it. diff --git a/guides/configuration.md b/guides/configuration.md new file mode 100644 index 0000000..3ea1a70 --- /dev/null +++ b/guides/configuration.md @@ -0,0 +1,130 @@ +# Configuration + +All LiveSvelte configuration is set via `Application.put_env(:live_svelte, key, value)` in your config files. + +## Application Config Keys + +| Key | Default | Description | +|-----|---------|-------------| +| `:ssr` | `true` | Enable server-side rendering globally | +| `:ssr_module` | `LiveSvelte.SSR.NodeJS` | SSR module: `NodeJS` or `ViteJS` | +| `:json_library` | `LiveSvelte.JSON` | JSON encoder (e.g. `Jason`) | +| `:enable_props_diff` | `true` | Enable three-tier props diffing system | +| `:gettext_backend` | `nil` | Gettext module for form error translation | +| `:vite_host` | `"http://localhost:5173"` | Vite dev server URL (used by ViteJS SSR mode) | + +## Typical Configuration by Environment + +### `config/config.exs` (base) + +```elixir +config :live_svelte, ssr: true +``` + +### `config/dev.exs` (development) + +```elixir +config :live_svelte, + ssr_module: LiveSvelte.SSR.ViteJS, + vite_host: "http://localhost:5173" +``` + +### `config/prod.exs` (production) + +```elixir +config :live_svelte, + ssr_module: LiveSvelte.SSR.NodeJS, + ssr: true +``` + +### `config/test.exs` (test) + +```elixir +# SSR is off in tests by default (NodeJS not started in test env) +config :live_svelte, ssr: false +``` + +## Per-Component Attributes + +These `<.svelte>` component attributes override global config for individual components: + +| Attribute | Type | Default | Description | +|-----------|------|---------|-------------| +| `name` | `string` | **required** | Svelte component filename (without `.svelte`) | +| `props` | `map` | `%{}` | Props passed to the component | +| `socket` | `map` | `nil` | LiveView socket โ€” required when `ssr: true` | +| `id` | `string` | auto | Stable DOM id override | +| `key` | `any` | `nil` | Identity key for DOM id in loops | +| `class` | `string` | `nil` | CSS class for the wrapper div | +| `ssr` | `boolean` | `true` | Enable SSR for this component | +| `diff` | `boolean` | `true` | Enable props diffing for this component | + +### Examples + +```heex + +<.svelte name="HeavyChart" props={%{data: @data}} socket={@socket} ssr={false} /> + + +<.svelte name="SimpleDisplay" props={%{label: @label}} socket={@socket} diff={false} /> + + +<.svelte name="Item" props={%{id: item.id, title: item.title}} socket={@socket} key={item.id} /> +``` + +## Vite Plugin Options + +Configure the `liveSveltePlugin` in `assets/vite.config.mjs`: + +```js +import { liveSveltePlugin } from "live_svelte/vitePlugin" + +export default defineConfig({ + plugins: [ + svelte(), + liveSveltePlugin({ + // Options (all optional): + paths: ["assets/svelte"], // Directories to scan for .svelte files + entrypoint: "assets/js/app.js" // Main app entry point + }) + ] +}) +``` + +### Default Paths + +By default, `liveSveltePlugin` discovers Svelte components in: +- `assets/svelte/**/*.svelte` +- `lib/**/*.svelte` (for colocated components next to LiveView modules) + +## JSON Library + +By default, LiveSvelte uses its own JSON encoder which handles `LiveSvelte.Encoder` protocol automatically. To use `Jason` instead: + +```elixir +config :live_svelte, json_library: Jason +``` + +When using an external JSON library, LiveSvelte still runs all values through `LiveSvelte.Encoder` before passing to the library. + +## Props Diffing + +Props diffing is enabled by default. To disable globally: + +```elixir +config :live_svelte, enable_props_diff: false +``` + +When disabled, LiveSvelte always sends the full props map on every update. This can be useful for debugging or for very simple UIs where diffing overhead is not worth it. + +The three tiers (change tracking โ†’ JSON Patch โ†’ ID-based list diffing) are all part of the same system and toggle together. + +## Gettext Integration + +To translate Ecto changeset error messages using your Gettext backend: + +```elixir +config :live_svelte, gettext_backend: MyAppWeb.Gettext +``` + +This affects `useLiveForm` โ€” error messages shown in `field().error` will use translated strings from your `priv/gettext/` directory. diff --git a/guides/deployment.md b/guides/deployment.md new file mode 100644 index 0000000..4ac6670 --- /dev/null +++ b/guides/deployment.md @@ -0,0 +1,163 @@ +# Deployment + +Deploying a LiveSvelte application requires Node.js on the server for SSR (server-side rendering). This guide covers the production build process and deployment considerations. + +## Requirements + +- **Node.js 19+** on the production server (for `LiveSvelte.SSR.NodeJS`) +- Standard Phoenix/Elixir deployment tooling (releases, Docker, etc.) + +## Build Steps + +```bash +# 1. Build client bundle and SSR bundle +mix assets.js + +# 2. Compile application (copies SSR bundle to _build) +mix compile + +# OR in a single release command: +MIX_ENV=prod mix assets.js && MIX_ENV=prod mix release +``` + +### What `mix assets.js` Does + +The `assets.js` alias runs (in order): + +1. `npx vite build` โ€” builds the client JavaScript bundle to `priv/static/assets/` +2. `npx vite build --config vite.ssr.config.js` โ€” builds the SSR bundle to `priv/svelte/server.js` +3. `tailwind default` โ€” builds CSS to `priv/static/assets/app.css` + +> #### Vite Must Run Before Tailwind {: .warning} +> +> The Vite client build uses `emptyOutDir: true` and clears `priv/static/assets/` first. Always run Vite before Tailwind, or Tailwind's `app.css` will be deleted. The `mix assets.js` alias handles this order correctly. + +## NodeJS Supervisor + +The Igniter installer adds `NodeJS.Supervisor` to your `application.ex`: + +```elixir +defmodule MyApp.Application do + use Application + + def start(_type, _args) do + children = [ + # ... other children ... + {NodeJS.Supervisor, [path: LiveSvelte.SSR.NodeJS.server_path(), pool_size: 4]} + ] + + opts = [strategy: :one_for_one, name: MyApp.Supervisor] + Supervisor.start_link(children, opts) + end +end +``` + +`LiveSvelte.SSR.NodeJS.server_path/0` returns the path to `priv/svelte/server.js`, which is the SSR bundle. + +Adjust `pool_size` based on expected SSR load. A pool of 4 workers is a reasonable default. + +## Production Config + +```elixir +# config/prod.exs +config :live_svelte, + ssr_module: LiveSvelte.SSR.NodeJS, + ssr: true +``` + +## SSR Bundle + +The SSR bundle (`priv/svelte/server.js`) is: +- Built from `assets/vite.ssr.config.js` +- Fully self-contained (all dependencies bundled, `ssr: { noExternal: true }`) +- Required to be present at application start when `ssr_module: LiveSvelte.SSR.NodeJS` + +After `mix assets.js`, `mix compile` copies `priv/svelte/server.js` into `_build/`. This copy in `_build/` is what NodeJS.Supervisor actually loads at runtime. + +> #### Always Compile After Building SSR Bundle {: .info} +> +> After `mix assets.js`, run `mix compile` so `_build/` gets the updated SSR bundle. In a CI/CD pipeline, ensure both steps run. + +## Docker Deployment + +Include Node.js in your Docker image: + +```dockerfile +# Multi-stage build example +FROM node:20-slim AS assets-builder +WORKDIR /app +COPY assets/ assets/ +COPY deps/ deps/ +RUN cd assets && npm install +RUN mix assets.js + +FROM elixir:1.17-slim AS release-builder +# ... standard Elixir release steps ... +RUN mix release + +FROM elixir:1.17-slim +# Include Node.js for SSR +RUN apt-get update && apt-get install -y nodejs npm +COPY --from=release-builder /app/_build/prod/rel/my_app ./ +CMD ["/app/bin/my_app", "start"] +``` + +A simpler approach is to use an image that includes both Elixir and Node.js: + +```dockerfile +FROM hexpm/elixir:1.17.3-erlang-27.1.2-debian-bookworm-20241016-slim +RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get install -y nodejs +``` + +## Disabling SSR in Production + +If you choose not to use SSR (e.g. to avoid Node.js on the server), disable it globally and remove `NodeJS.Supervisor`: + +```elixir +# config/prod.exs +config :live_svelte, ssr: false +``` + +Remove `NodeJS.Supervisor` from `application.ex` children. The `{:nodejs, "~> 3.1"}` dependency can remain in `mix.exs` but won't be used. + +## Per-Component SSR Opt-Out + +Even with global SSR enabled, you can disable SSR for expensive components to reduce Node.js load: + +```heex +<.svelte name="HeavyVisualization" props={%{data: @data}} socket={@socket} ssr={false} /> +``` + +Use SSR primarily for above-the-fold content where first-paint HTML matters. + +## Telemetry for Observability + +Attach SSR telemetry handlers to monitor production performance: + +```elixir +:telemetry.attach_many( + "live-svelte-ssr-metrics", + [ + [:live_svelte, :ssr, :stop], + [:live_svelte, :ssr, :exception] + ], + fn + [:live_svelte, :ssr, :stop], measurements, _meta, _ -> + MyApp.Metrics.histogram("live_svelte.ssr.duration", measurements.duration_microseconds) + [:live_svelte, :ssr, :exception], _measurements, meta, _ -> + Logger.error("SSR failed: #{inspect(meta.reason)}") + end, + nil +) +``` + +## Upgrading + +When upgrading LiveSvelte versions: + +1. Update `{:live_svelte, "~> x.y"}` in `mix.exs` +2. Run `mix deps.get` +3. Check `CHANGELOG.md` for breaking changes +4. Rebuild: `mix assets.js && mix compile` +5. Run tests: `mix test` diff --git a/guides/forms.md b/guides/forms.md new file mode 100644 index 0000000..0ec659f --- /dev/null +++ b/guides/forms.md @@ -0,0 +1,212 @@ +# Forms and Validation + +LiveSvelte provides `useLiveForm` for building reactive forms backed by Ecto changesets with server-side validation. + +## Quick Example + +**LiveView:** + +```elixir +defmodule MyAppWeb.UserFormLive do + use MyAppWeb, :live_view + alias MyApp.Accounts + + def mount(_params, _session, socket) do + form = to_form(Accounts.change_user(%Accounts.User{})) + {:ok, assign(socket, form: form)} + end + + def handle_event("validate", %{"user" => params}, socket) do + form = params |> Accounts.change_user() |> Map.put(:action, :validate) |> to_form() + {:noreply, assign(socket, form: form)} + end + + def handle_event("submit", %{"user" => params}, socket) do + case Accounts.create_user(params) do + {:ok, _user} -> {:noreply, push_navigate(socket, to: "/")} + {:error, changeset} -> {:noreply, assign(socket, form: to_form(changeset))} + end + end + + def render(assigns) do + ~H""" + <.svelte name="UserForm" props={%{form: @form}} socket={@socket} /> + """ + end +end +``` + +**Svelte Component:** + +```svelte + + + +
    +
    + + + {#if field("name").error} + {field("name").error} + {/if} +
    + +
    + + + {#if field("email").error} + {field("email").error} + {/if} +
    + + +
    +``` + +## The `useLiveForm` Composable + +```ts +import { useLiveForm } from "live_svelte" + +const { field, fieldArray } = useLiveForm(() => form, options?) +``` + +The first argument is a **getter function** (not the form value directly). This ensures `useLiveForm` always reads the latest reactive prop value. + +### Options + +```ts +type FormOptions = { + changeEvent?: string // Event name for validation (default: "validate") + submitEvent?: string // Event name for submission (default: "submit") + debounceInMilliseconds?: number // Debounce delay for change events (default: 300) +} +``` + +## The `field()` Function + +`field(name)` returns an object you can spread onto an `` element: + +```svelte + +``` + +It returns: +- `name` โ€” the HTML input name (matches changeset field) +- `value` โ€” current field value from the changeset +- `error` โ€” error message string (or `null`) +- `phx-debounce` โ€” debounce attribute for change events + +You can also access properties individually: + +```svelte + +{#if field("email").error} +

    {field("email").error}

    +{/if} +``` + +## Nested Fields + +Access nested fields with dot notation: + +```svelte + + +``` + +## Dynamic Arrays with `fieldArray()` + +For `embeds_many` or `has_many` with nested forms: + +```svelte + + +{#each skills.fields as skillField, i} +
    + + +
    +{/each} + + +``` + +`fieldArray(path)` returns: +- `fields` โ€” reactive array of field descriptors +- `append(value)` โ€” add an item to the end +- `prepend(value)` โ€” add an item to the start +- `remove(index)` โ€” remove an item by index + +## Encoding Changesets as Props + +To pass a changeset form as props, use `LiveSvelte.Encoder` for the changeset data. Phoenix's `to_form/1` produces a `Phoenix.HTML.Form` struct that LiveSvelte can encode automatically. + +For custom structs used inside the form data, use `@derive`: + +```elixir +defmodule MyApp.Address do + @derive {LiveSvelte.Encoder, only: [:street, :city, :zip]} + embedded_schema do + field :street, :string + field :city, :string + field :zip, :string + end +end +``` + +## TypeScript Types + +```ts +import type { Form } from "live_svelte" + +// Type your component props +let { form }: { form: Form<{ name: string; email: string }> } = $props() + +const { field } = useLiveForm(() => form) +// field("name").value is typed as string +``` + +## Gettext Integration + +If you have a Gettext backend configured, LiveSvelte translates error messages automatically: + +```elixir +# config/config.exs +config :live_svelte, gettext_backend: MyAppWeb.Gettext +``` + +Error messages from changesets will use your Gettext translations. + +## Full Form Example with Validation + +```elixir +# LiveView +def handle_event("validate", %{"user" => params}, socket) do + changeset = + %User{} + |> User.changeset(params) + |> Map.put(:action, :validate) + + {:noreply, assign(socket, form: to_form(changeset))} +end +``` + +Setting `action: :validate` on the changeset causes Ecto to include validation errors, which LiveSvelte then passes back to the `field().error` values in the component. diff --git a/guides/installation.md b/guides/installation.md new file mode 100644 index 0000000..108d73a --- /dev/null +++ b/guides/installation.md @@ -0,0 +1,160 @@ +# Installation + +LiveSvelte uses [Vite](https://vitejs.dev/) for both client and SSR builds, replacing the default `esbuild` setup in Phoenix projects. + +## Prerequisites + +- **Node.js 19+** โ€” required for SSR (server-side rendering) +- **Elixir 1.17+** +- **Phoenix 1.8+** โ€” required for the Igniter installer +- **Igniter** โ€” the installation scaffolding tool + +## Quick Start (Recommended) + +### Step 1: Install the Igniter archive + +```bash +mix archive.install hex igniter_new +``` + +### Step 2: Add LiveSvelte to your project + +For an **existing** Phoenix 1.8+ project: + +```bash +mix igniter.install live_svelte +``` + +For a **new** project with LiveSvelte pre-installed: + +```bash +mix igniter.new my_app --with phx.new --install live_svelte +``` + +Use the `--bun` flag to use `bun` instead of `npm`: + +```bash +mix igniter.install live_svelte --bun +``` + +### Step 3: Install JS dependencies and build + +```bash +cd assets && npm install && cd .. +mix assets.js +mix phx.server +``` + +Visit `/svelte_demo` to verify the installation with the generated demo component. + +## What the Installer Does + +Running `mix igniter.install live_svelte` makes the following changes to your project: + +**`assets/package.json`** โ€” adds: +- `live_svelte: "file:../deps/live_svelte"` (dependency) +- `svelte: "^5.0.0"` (dev dependency) +- `@sveltejs/vite-plugin-svelte` (dev dependency) + +**`assets/vite.config.mjs`** โ€” adds the Svelte plugin and `liveSveltePlugin`: +```js +import { svelte } from "@sveltejs/vite-plugin-svelte" +import { liveSveltePlugin } from "live_svelte/vitePlugin" + +// ... +plugins: [svelte(), liveSveltePlugin()], +``` + +**`assets/vite.ssr.config.js`** โ€” new file for the SSR bundle (Node.js server rendering) + +**`assets/js/app.js`** โ€” adds hook wiring: +```js +import { getHooks } from "live_svelte" +import Components from "virtual:live-svelte-components" + +const liveSocket = new LiveSocket("/live", Socket, { + hooks: getHooks(Components), + // ... +}) +``` + +**`lib/app_web.ex`** โ€” adds `import LiveSvelte` to `html_helpers` + +**`lib/app/application.ex`** โ€” adds NodeJS supervisor for production SSR: +```elixir +{NodeJS.Supervisor, [path: LiveSvelte.SSR.NodeJS.server_path(), pool_size: 4]} +``` + +**`config/config.exs`** โ€” base SSR config: +```elixir +config :live_svelte, ssr: true +``` + +**`config/dev.exs`** โ€” development SSR via Vite dev server: +```elixir +config :live_svelte, + ssr_module: LiveSvelte.SSR.ViteJS, + vite_host: "http://localhost:5173" +``` + +**`config/prod.exs`** โ€” production SSR via NodeJS: +```elixir +config :live_svelte, + ssr_module: LiveSvelte.SSR.NodeJS, + ssr: true +``` + +**`mix.exs`** โ€” adds the `assets.js` alias that runs both Vite builds plus Tailwind: +```elixir +"assets.js": [ + "cmd npx vite build", + "cmd npx vite build --config vite.ssr.config.js", + "tailwind default" +] +``` + +**`assets/svelte/`** โ€” creates the Svelte components directory with a demo component + +**`assets/app.css`** โ€” adds `@source "../svelte";` if Tailwind is present + +**`.gitignore`** โ€” adds `/assets/svelte/_build/` and `/priv/svelte/` + +> #### Phoenix Version Requirement {: .warning} +> +> The Igniter installer requires **Phoenix 1.8+**. The library itself works with Phoenix 1.7+ when installed manually. + +## Manual Installation + +> #### Manual Installation Not Recommended {: .warning} +> +> Manual installation steps are complex and kept out-of-date as dependencies evolve. We strongly recommend using `mix igniter.install live_svelte` instead. + +If you must install manually (e.g. Phoenix < 1.8), the overall steps mirror the LiveVue manual installation process: + +1. Add `{:live_svelte, "~> 0.17"}` to `mix.exs` deps +2. Configure Vite with the Svelte plugin and `liveSveltePlugin` +3. Create `vite.ssr.config.js` +4. Wire up `getHooks(Components)` in `app.js` +5. Add `import LiveSvelte` to `html_helpers` +6. Add `NodeJS.Supervisor` to `application.ex` +7. Configure SSR in `config/` + +## Disabling SSR + +If you don't need server-side rendering, disable it globally: + +```elixir +# config/config.exs +config :live_svelte, ssr: false +``` + +Or per-component: + +```heex +<.svelte name="Counter" props={%{count: @count}} socket={@socket} ssr={false} /> +``` + +## Next Steps + +- [Basic Usage](basic_usage.md) โ€” your first `<.svelte>` component +- [Configuration](configuration.md) โ€” all config options diff --git a/guides/introduction.md b/guides/introduction.md new file mode 100644 index 0000000..974278c --- /dev/null +++ b/guides/introduction.md @@ -0,0 +1,52 @@ +# Introduction + +LiveSvelte brings end-to-end reactivity between [Phoenix LiveView](https://github.com/phoenixframework/phoenix_live_view) and [Svelte 5](https://svelte.dev/) components. Server state flows directly into Svelte components as reactive props, and user interactions in Svelte components push events back to the LiveView process โ€” all over the existing Phoenix WebSocket. + +## Three-Layer Architecture + +LiveSvelte is built on three layers that work together: + +``` +LiveView (Elixir) โ†’ SvelteHook (Phoenix hook) โ†’ Svelte 5 (component) +server assigns reads data attrs reactive props +handle_event/3 pushEvent/handleEvent $props(), $state() +``` + +1. **LiveView** renders an HTML wrapper div with JSON-encoded props in `data-props`. State lives on the server. +2. **SvelteHook** (a Phoenix LiveView JS hook) mounts and updates Svelte components. It reads `data-props` on mount and applies patches on update. +3. **Svelte 5 component** receives props via `$props()` and re-renders reactively whenever the server sends updates. + +## Key Features + +- **Full Svelte 5 support** โ€” `$props()`, `$state()`, `$derived()`, runes syntax, snippets +- **Server-side rendering (SSR)** โ€” Optional first-paint HTML via NodeJS (production) or ViteJS (development) +- **Efficient props diffing** โ€” Three-tier system: change tracking โ†’ JSON Patch โ†’ ID-based list diffing +- **Phoenix Streams** โ€” Native support for `stream()` with efficient patch operations +- **Composables** โ€” `useLiveSvelte`, `useLiveEvent`, `useLiveConnection`, `useLiveNavigation`, `useLiveForm`, `useLiveUpload`, `useEventReply` +- **TypeScript** โ€” Full type support across Elixir and JavaScript boundaries +- **Igniter installer** โ€” One-command setup with `mix igniter.install live_svelte` +- **Vite** โ€” Development HMR and optimized production builds + +## When to Use LiveSvelte + +LiveSvelte is a good fit when you want: + +- Rich, interactive UI components with real-time server state +- Svelte 5's reactive primitives and component model alongside LiveView's server logic +- Gradual adoption โ€” mix `<.svelte>` components with regular HEEX templates + +Plain LiveView may be sufficient when: + +- Your UI interactions map naturally to LiveView events without needing local component state +- You don't need Svelte-specific features like `$derived()` or Svelte snippets + +## Compatibility + +| Dependency | Version | +|------------------|----------------------| +| LiveSvelte | 0.17.4 | +| Svelte | 5.x | +| Phoenix | 1.7+ (1.8+ for Igniter installer) | +| Phoenix LiveView | 1.0+ | +| Elixir | 1.17+ | +| Node.js | 19+ (for SSR) | diff --git a/guides/ssr.md b/guides/ssr.md new file mode 100644 index 0000000..0ac8345 --- /dev/null +++ b/guides/ssr.md @@ -0,0 +1,163 @@ +# Server-Side Rendering + +LiveSvelte supports server-side rendering (SSR) of Svelte components, which provides meaningful HTML on the first paint before the JavaScript bundle loads. + +## How SSR Works + +When SSR is active, the initial (dead) render calls Node.js to execute Svelte's `render()` function server-side. The result is embedded directly in the HTML response: + +1. LiveSvelte calls `SSR.render(component_name, props, slots)` +2. `render()` returns `%{"head" => "", "html" => "
    ...
    ", "css" => %{"code" => "..."}}` +3. The `head` and CSS styles are included in the page; `html` is placed inside the `data-svelte-target` div +4. When JavaScript loads, the `SvelteHook` hydrates the existing DOM instead of mounting fresh + +## SSR Modes + +LiveSvelte has two SSR modules for different environments: + +### NodeJS Mode (Production) + +Uses [`elixir-nodejs`](https://github.com/revelrylabs/elixir-nodejs) to run a pool of Node.js workers that execute the SSR bundle. + +```elixir +# config/prod.exs +config :live_svelte, + ssr_module: LiveSvelte.SSR.NodeJS, + ssr: true +``` + +The SSR bundle is built by: +```bash +mix assets.js # runs: npx vite build --config vite.ssr.config.js +``` + +This produces `priv/svelte/server.js`, which the NodeJS supervisor loads on application start. + +### ViteJS Mode (Development) + +Forwards SSR requests to the Vite dev server over HTTP. This provides instant HMR without rebuilding the SSR bundle on every change. + +```elixir +# config/dev.exs +config :live_svelte, + ssr_module: LiveSvelte.SSR.ViteJS, + vite_host: "http://localhost:5173" +``` + +> #### ViteJS Mode Requires Vite Dev Server {: .warning} +> +> `LiveSvelte.SSR.ViteJS` only works when `mix phx.server` is running alongside the Vite dev server started by Phoenix's watchers. If the Vite server is not running, SSR will silently fall back to client-only rendering. + +## Configuration + +Enable/disable SSR globally: + +```elixir +# config/config.exs +config :live_svelte, ssr: true # enabled (default) +config :live_svelte, ssr: false # disabled +``` + +Select SSR module: + +```elixir +config :live_svelte, ssr_module: LiveSvelte.SSR.NodeJS # production (default) +config :live_svelte, ssr_module: LiveSvelte.SSR.ViteJS # development +``` + +## Per-Component SSR Opt-Out + +Disable SSR for a specific component: + +```heex +<.svelte name="HeavyChart" props={%{data: @data}} socket={@socket} ssr={false} /> +``` + +Components with `ssr={false}` render a loading slot or nothing on the first paint, then mount client-side normally. + +## HMR in Development + +When running with `LiveSvelte.SSR.ViteJS`, changes to Svelte files trigger automatic hot module replacement. The `SvelteHook` re-mounts affected components without a full page reload. + +Add the `LiveSvelte.Reload` module to your layouts to enable this: + +```elixir +# config/dev.exs โ€” added by the Igniter installer +config :live_svelte, + vite_host: "http://localhost:5173" +``` + +Use `vite_assets/0` in your layout to include Vite's HMR client: + +```heex + +<%= if Application.get_env(:live_svelte, :ssr_module) == LiveSvelte.SSR.ViteJS do %> + +<% end %> +``` + +## Loading Slot + +Show content while a component is loading (only when `ssr={false}`): + +```heex +<.svelte name="SlowChart" props={%{data: @data}} socket={@socket} ssr={false}> + <:loading> +
    Loading chart...
    + + +``` + +> #### Loading Slot + SSR Incompatible {: .warning} +> +> The `:loading` slot is mutually exclusive with SSR. Using both together produces a compile warning. If SSR is active, the loading slot is ignored. + +## Telemetry + +LiveSvelte emits telemetry events for SSR operations: + +| Event | When | +|-------|------| +| `[:live_svelte, :ssr, :start]` | SSR render begins | +| `[:live_svelte, :ssr, :stop]` | SSR render completes; includes `duration_microseconds` measurement | +| `[:live_svelte, :ssr, :exception]` | SSR render throws an exception | + +Attach handlers for observability: + +```elixir +:telemetry.attach( + "live-svelte-ssr", + [:live_svelte, :ssr, :stop], + fn _event, measurements, _metadata, _config -> + Logger.debug("SSR render took #{measurements.duration_microseconds}ยตs") + end, + nil +) +``` + +## Testing with SSR + +SSR is disabled in the test environment by default. To write tests that verify SSR output, enable it per test suite: + +```elixir +defmodule MyAppWeb.SsrTest do + use MyAppWeb.ConnCase, async: false # must be async: false + + setup do + Application.put_env(:live_svelte, :ssr, true) + on_exit(fn -> Application.put_env(:live_svelte, :ssr, false) end) + :ok + end + + test "renders SSR HTML", %{conn: conn} do + html = conn |> get("/counter") |> html_response(200) + assert html =~ "data-ssr=\"true\"" + end +end +``` + +Use `get/2` + `html_response/2` for initial HTML checks โ€” `visit/2` from PhoenixTest connects the socket and transitions past the dead render. + +## Production Deployment + +See [Deployment](deployment.md) for complete Node.js setup instructions for production. diff --git a/guides/streams.md b/guides/streams.md new file mode 100644 index 0000000..9a88584 --- /dev/null +++ b/guides/streams.md @@ -0,0 +1,153 @@ +# Phoenix Streams + +LiveSvelte has native support for [Phoenix Streams](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#stream/4), enabling efficient DOM list management without holding full lists in memory on the server. + +## Basic Streams + +**LiveView:** + +```elixir +defmodule MyAppWeb.ItemsLive do + use MyAppWeb, :live_view + + def mount(_params, _session, socket) do + {:ok, stream(socket, :items, MyApp.list_items())} + end + + def handle_event("delete", %{"id" => id}, socket) do + item = MyApp.get_item!(id) + MyApp.delete_item!(item) + {:noreply, stream_delete(socket, :items, item)} + end + + def render(assigns) do + ~H""" + <.svelte name="ItemList" props={%{items: @streams.items}} socket={@socket} /> + """ + end +end +``` + +**Svelte Component:** + +```svelte + + + +{#each items as item (item.__dom_id)} +
    +

    {item.name}

    +
    +{/each} +``` + +> #### Use `__dom_id` as the key {: .info} +> +> Always use `item.__dom_id` as the `{#each}` key. LiveSvelte uses this to track item identity for efficient updates. + +## Stream Operations + +All Phoenix stream operations work automatically: + +```elixir +# Insert at the end (default) +socket |> stream_insert(socket, :items, new_item) + +# Insert at the beginning +socket |> stream_insert(socket, :items, new_item, at: 0) + +# Delete by item (must have :id field) +socket |> stream_delete(socket, :items, item) + +# Reset the entire stream +socket |> stream(socket, :items, new_items, reset: true) +``` + +## Efficient Stream Patches + +LiveSvelte sends stream changes as compact JSON Patch operations via `data-streams-diff`, rather than re-sending the full list on every change. This makes stream updates extremely efficient โ€” inserting a single item sends a single operation regardless of list size. + +The patch operations used: + +| Operation | Description | +|-----------|-------------| +| `upsert` | Insert or update an item at a specific position | +| `remove` | Delete an item by `__dom_id` | +| `replace` | Reset the entire list | +| `limit` | Trim the list to the given max size | + +These are applied client-side by the `SvelteHook` before updating the Svelte component's `items` prop. + +## Accessing Stream Data in Components + +Streams are passed as arrays to Svelte components. Each item has all its original fields plus `__dom_id`: + +```svelte + + +
      + {#each messages as message (message.__dom_id)} +
    • + {message.user}: {message.text} +
    • + {/each} +
    +``` + +## Multiple Streams + +Pass multiple streams to a single component: + +```elixir +def mount(_, _, socket) do + {:ok, + socket + |> stream(:messages, []) + |> stream(:users, [])} +end + +def render(assigns) do + ~H""" + <.svelte + name="Chat" + props={%{messages: @streams.messages, users: @streams.users}} + socket={@socket} + /> + """ +end +``` + +```svelte + + + +``` + +## Encoding Stream Items + +Stream items go through `LiveSvelte.Encoder` before being sent to the client. For custom structs, add `@derive`: + +```elixir +defmodule MyApp.Message do + @derive {LiveSvelte.Encoder, only: [:id, :user, :text, :inserted_at]} + defstruct [:id, :user, :text, :inserted_at] +end +``` + +The `@derive` restriction is enforced โ€” fields not in `only:` are excluded even after `__dom_id` is added. + +## ID-Based Diffing + +For arrays where items have an `:id` field, LiveSvelte uses ID-based list diffing (Tier 3 of the props diffing system). This means: + +- Inserting at position 0 sends a single `upsert` op, not N `replace` ops +- Reordering sends minimal operations +- List updates stay efficient regardless of list size + +Items must have an `:id` field for ID-based diffing to activate. The `__dom_id` set by Phoenix Streams already guarantees this. diff --git a/guides/testing.md b/guides/testing.md new file mode 100644 index 0000000..0a6c005 --- /dev/null +++ b/guides/testing.md @@ -0,0 +1,249 @@ +# Testing + +The LiveSvelte example project has two complementary test layers: fast server-side tests via PhoenixTest, and full-stack browser tests via Wallaby. + +## Build Before Testing + +> #### Critical: Always Build Before Tests {: .warning} +> +> After any changes to Svelte components or JS files, always run: +> +> ```bash +> cd example_project +> mix assets.js && mix compile +> ``` +> +> `mix assets.js` runs Vite builds (client + SSR). `mix compile` copies the updated SSR bundle into `_build/`. Forgetting this step is the most common cause of "my JS changes have no effect" test failures. + +## PhoenixTest (Server-Side, Fast) + +PhoenixTest tests validate server-side behavior without a browser. They are fast, reliable, and do not require chromedriver. + +```bash +cd example_project +mix test --only phoenix_test +``` + +Tag test modules with `@moduletag :phoenix_test`: + +```elixir +defmodule MyAppWeb.CounterTest do + use MyAppWeb.ConnCase, async: true + + @moduletag :phoenix_test + + import PhoenixTest + + test "increments counter", %{conn: conn} do + conn + |> visit("/counter") + |> assert_has("h1", text: "Counter") + |> assert_has("[data-props*='\"count\":0']") + end +end +``` + +### What PhoenixTest Can Verify + +- LiveView renders correct HTML (headings, labels, lists) +- `data-props` contains the expected JSON for Svelte components +- LiveView events update assigns and re-render correctly +- Server-side rendered content + +### What PhoenixTest Cannot Verify + +- Whether Svelte components use the props they receive +- Client-side rendering (Svelte output) +- Interactions inside Svelte-rendered elements (SSR is off in tests by default) + +### Workaround for Svelte-Rendered Elements + +Use `unwrap/2` to access the LiveView test process and trigger events directly: + +```elixir +session +|> unwrap(fn view -> + Phoenix.LiveViewTest.render_click(view, "increment") +end) +|> assert_has("[data-props*='\"count\":1']") +``` + +## Wallaby E2E (Browser-Based, Full Stack) + +Wallaby tests use chromedriver to run a real browser. They validate the full pipeline: LiveView โ†’ SvelteHook โ†’ Svelte component. + +```bash +cd example_project +mix test --only e2e +``` + +Tag test modules with `@moduletag :e2e`: + +```elixir +defmodule MyAppWeb.CounterE2ETest do + use MyAppWeb.FeatureCase, async: false + + @moduletag :e2e + + test "Svelte counter increments", %{session: session} do + session + |> visit("/counter") + |> assert_text("Count: 0") + |> click(button("Increment")) + |> assert_text("Count: 1") + end +end +``` + +### What Wallaby Can Verify + +- Svelte components render the correct data from server props +- Client-side interactions (buttons rendered by Svelte, not LiveView) +- Full data flow from server through to Svelte re-renders +- HMR and dynamic updates + +### Requirements + +Wallaby requires chromedriver installed and available in `PATH`: + +```bash +# Check if chromedriver is available +chromedriver --version + +# On macOS with Homebrew: +brew install chromedriver + +# On Ubuntu/Debian: +sudo apt-get install chromium-driver +``` + +## Running Both Layers + +```bash +# Server-side only (fast, no browser needed) +mix assets.js && mix test --only phoenix_test + +# Browser E2E only +mix assets.js && mix test --only e2e + +# Everything +mix assets.js && mix test +``` + +## `LiveSvelte.Test` โ€” Component Introspection + +`LiveSvelte.Test` provides helper functions to inspect Svelte component props in server-side tests: + +```elixir +import LiveSvelte.Test + +# In a PhoenixTest or ConnCase test: +{:ok, view, html} = live(conn, "/counter") +component = get_svelte(html, name: "Counter") + +assert component.name == "Counter" +assert component.props["count"] == 0 +``` + +### `get_svelte/1` and `get_svelte/2` + +```elixir +# Get first Svelte component in the HTML +component = get_svelte(html) + +# Get component by name +component = get_svelte(html, name: "Counter") + +# Get component by DOM id +component = get_svelte(html, id: "Counter-1") + +# Get from a LiveView directly +{:ok, view, _html} = live(conn, "/counter") +component = get_svelte(view, name: "Counter") +``` + +The returned map has: +- `name` โ€” component name string +- `id` โ€” DOM id of the component wrapper +- `props` โ€” decoded props map (string keys) +- `slots` โ€” map of slot name โ†’ HTML string +- `ssr` โ€” boolean, whether SSR was used + +### Example: Asserting Props after Events + +```elixir +test "props update after event", %{conn: conn} do + {:ok, view, html} = live(conn, "/counter") + + # Initial props + assert get_svelte(html, name: "Counter").props["count"] == 0 + + # Trigger event + html = render_click(view, "increment") + + # Updated props + assert get_svelte(html, name: "Counter").props["count"] == 1 +end +``` + +## Vitest (JavaScript Unit Tests) + +JavaScript composables and utilities have unit tests using Vitest: + +```bash +cd example_project/assets +npm test # Run tests once +npm run test:watch # Watch mode +``` + +Test files are colocated with source files (`*.test.ts`). + +## Tagging Convention + +Use `@moduletag` (not `@tag`) for consistent filtering: + +```elixir +# โœ… Correct โ€” applies to ALL tests in the module +@moduletag :phoenix_test + +# โŒ Wrong โ€” only applies to the NEXT test +@tag :phoenix_test +``` + +## Test File Layout + +``` +example_project/test/example_web/ +โ”œโ”€โ”€ phoenix_test/ # PhoenixTest (server-side) +โ”‚ โ”œโ”€โ”€ hello_world_test.exs +โ”‚ โ”œโ”€โ”€ live_struct_test.exs +โ”‚ โ””โ”€โ”€ ... +โ””โ”€โ”€ live/ # Wallaby E2E (browser) + โ”œโ”€โ”€ live_struct_test.exs + โ””โ”€โ”€ ... +``` + +## SSR Testing + +SSR is off in tests by default. To test SSR output: + +```elixir +defmodule MyAppWeb.SsrTest do + use MyAppWeb.ConnCase, async: false # SSR state is global โ€” must use async: false + + setup do + Application.put_env(:live_svelte, :ssr, true) + on_exit(fn -> Application.put_env(:live_svelte, :ssr, false) end) + :ok + end + + test "renders SSR HTML on first request", %{conn: conn} do + # Use get/html_response for dead render โ€” NOT visit/2 + html = conn |> get("/counter") |> html_response(200) + assert html =~ ~s(data-ssr="true") + assert html =~ " Use `get/2` + `html_response/2` for SSR checks. `visit/2` from PhoenixTest connects the LiveView socket and transitions past the initial dead render. diff --git a/guides/troubleshooting.md b/guides/troubleshooting.md new file mode 100644 index 0000000..c6f841f --- /dev/null +++ b/guides/troubleshooting.md @@ -0,0 +1,218 @@ +# Troubleshooting + +Common issues encountered when using LiveSvelte, and how to resolve them. + +## "My JS Changes Have No Effect" + +**Symptom:** You modified a Svelte component or JavaScript file, but the browser still shows old behavior. + +**Cause:** Vite hasn't rebuilt the assets, so the browser is loading stale bundles. + +**Fix:** +```bash +cd example_project +mix assets.js && mix compile +``` + +`mix assets.js` runs Vite builds (client + SSR). `mix compile` copies the updated SSR bundle into `_build/`. Both steps are required after any change to `assets/`. + +## SSR Renders Stale HTML + +**Symptom:** Server-side rendered HTML shows old component output even after updating Svelte files. + +**Cause:** `_build/test/lib/example/priv/svelte/server.js` is a **copy** (not a symlink) of `priv/svelte/server.js`. It is updated by `mix compile`, not by `mix assets.js` alone. + +**Fix:** +```bash +mix assets.js && mix compile +``` + +If you're seeing stale SSR in tests, ensure the `on_exit` cleanup properly resets SSR state. + +## Svelte CSS Overwrites Tailwind's `app.css` + +**Symptom:** After building, `priv/static/assets/app.css` contains only Svelte component styles, and Tailwind styles are missing. + +**Cause:** The Svelte Vite plugin is extracting component CSS to a file, which overwrites the Tailwind output. + +**Fix:** Ensure your `vite.config.mjs` passes `css: "injected"` to the Svelte plugin: + +```js +import { svelte } from "@sveltejs/vite-plugin-svelte" + +export default defineConfig({ + plugins: [ + svelte({ + compilerOptions: { + css: "injected" // โ† This is required + } + }), + // ... + ] +}) +``` + +This injects Svelte component CSS directly into the JS bundle instead of extracting it to a separate file. + +## Component Not Found / `virtual:live-svelte-components` Resolution Fails + +**Symptom:** Browser console shows `Error: Failed to resolve module 'virtual:live-svelte-components'` or a specific component name is not found. + +**Cause:** The `liveSveltePlugin` is missing from one or both Vite configs. + +**Fix:** Ensure `liveSveltePlugin()` is in **both** `vite.config.mjs` (client build) and `vite.ssr.config.js` (SSR build): + +```js +// assets/vite.config.mjs +import { liveSveltePlugin } from "live_svelte/vitePlugin" +plugins: [svelte(), liveSveltePlugin()] + +// assets/vite.ssr.config.js +import { liveSveltePlugin } from "live_svelte/vitePlugin" +plugins: [svelte(), liveSveltePlugin()] +``` + +Also verify that your Svelte files are in `assets/svelte/` and have the `.svelte` extension. + +## Wallaby E2E Tests Fail + +**Symptom:** Wallaby tests fail with a browser connection error or chromedriver not found. + +**Cause:** chromedriver is not installed or not in `PATH`. + +**Fix:** +```bash +# Check installation +chromedriver --version + +# Install on macOS +brew install chromedriver + +# Install on Ubuntu/Debian +sudo apt-get install chromium-driver + +# Or use a specific version with webdriver-manager +npm install -g webdriver-manager +webdriver-manager update +``` + +Also ensure `mix assets.js` has been run before E2E tests โ€” the browser needs the built JS to function. + +## `mix live_svelte.install` Says "Task Not Found" + +**Symptom:** Running `mix live_svelte.install` prints "The task 'live_svelte.install' could not be found". + +**Cause:** The `:igniter` dependency is not in your project's deps, or `mix deps.get` hasn't been run. + +**Fix:** +```bash +# Ensure igniter is installed +mix archive.install hex igniter_new + +# Then install using igniter: +mix igniter.install live_svelte +``` + +If you added `{:live_svelte, ...}` to `mix.exs` and ran `mix deps.get` manually, also run: +```bash +mix deps.compile +``` + +## `import LiveSvelte` Missing from html_helpers + +**Symptom:** Using `<.svelte>` in a LiveView template produces `function component svelte/1 is undefined`. + +**Cause:** `import LiveSvelte` was not added to the web module's `html_helpers`. + +**Fix:** Add it manually in `lib/my_app_web.ex`: + +```elixir +defp html_helpers do + quote do + # ... existing imports ... + import LiveSvelte + end +end +``` + +> Note: use `import LiveSvelte`, not `use LiveSvelte`. + +## Props Not Reactive After Navigation + +**Symptom:** After navigating within the same LiveView, Svelte component props stop updating. + +**Cause:** If `phx-update="ignore"` is missing or incorrectly overridden on the component wrapper, LiveView will re-patch Svelte's DOM on updates, breaking reactivity. + +**Fix:** Do not add `phx-update` attributes to the `<.svelte>` component call โ€” LiveSvelte sets it automatically on the wrapper div. If you are nesting the component inside a container with `phx-update`, ensure that container is set correctly: + +```heex + +<.svelte name="Counter" props={%{count: @count}} socket={@socket} /> + + +
    + <.svelte name="Counter" props={%{count: @count}} socket={@socket} /> +
    +``` + +For containers that wrap multiple components, use `phx-update="ignore"` on the outer container if it should not be re-rendered, or ensure each component has a stable `id`. + +## NodeJS.Supervisor Not Starting + +**Symptom:** Application fails to start with `NodeJS.Supervisor` error, or SSR silently fails in production. + +**Cause:** `ssr_module` is not set to `LiveSvelte.SSR.NodeJS` in production config, or the SSR bundle (`priv/svelte/server.js`) is missing. + +**Fix:** +1. Ensure `config/prod.exs` has: + ```elixir + config :live_svelte, ssr_module: LiveSvelte.SSR.NodeJS + ``` + +2. Ensure the SSR bundle was built: + ```bash + MIX_ENV=prod mix assets.js && MIX_ENV=prod mix compile + ``` + +3. Check that `NodeJS.Supervisor` is in `application.ex`: + ```elixir + {NodeJS.Supervisor, [path: LiveSvelte.SSR.NodeJS.server_path(), pool_size: 4]} + ``` + +## Svelte 4 Syntax Errors + +**Symptom:** Component fails with unexpected syntax errors like `export let` or ` + +
    e.key === "Enter" && showFilePicker()} +> + Click to select a file (or drag and drop) +
    + +{#each $entries as entry (entry.ref)} +
    +

    {entry.client_name}

    + + + {entry.progress}% + + + {#each entry.errors as error} +

    {error}

    + {/each} + + +
    +{/each} + + +``` + +## The `useLiveUpload` Composable + +```ts +import { useLiveUpload } from "live_svelte" + +const { showFilePicker, entries, submit, cancel, clear, sync } = useLiveUpload( + uploads.avatar, + { changeEvent: "validate", submitEvent: "submit" } +) + +// Sync updated config from server on every render +$effect(() => sync(uploads.avatar)) +``` + +The first argument is the **upload config object** for a specific upload field (e.g., `uploads.avatar`). Pass it directly โ€” not as a getter function. + +Call `sync(uploads.avatar)` in a `$effect` to keep the composable up-to-date whenever the server sends an updated config. + +> `useLiveUpload` creates a hidden `
    ` and `` internally and appends them to the LiveView element. You do not need to add a form in your Svelte template. + +### Options + +```ts +interface UploadOptions { + changeEvent?: string // Server event for phx-change (validation). Optional. + submitEvent: string // Server event for phx-submit. REQUIRED. +} +``` + +## Return Values + +| Value | Type | Description | +|-------|------|-------------| +| `showFilePicker()` | `() => void` | Opens the native file picker dialog | +| `addFiles(files)` | `(files: File[] \| DataTransfer) => void` | Enqueue files programmatically (for drag-drop) | +| `entries` | `Readable` | Reactive store of current upload entries. Use `$entries` in templates. | +| `progress` | `Readable` | Overall upload progress 0โ€“100 averaged across all entries | +| `valid` | `Readable` | `true` when the upload config has no top-level errors | +| `submit()` | `() => void` | Dispatch a form submit event to trigger Phoenix upload | +| `cancel(ref?)` | `(ref?: string) => void` | Cancel entry by ref string, or all entries when called with no arg | +| `clear()` | `() => void` | Reset the hidden input to clear the file queue | +| `sync(config)` | `(config: UploadConfig) => void` | Merge updated config from server. Call in `$effect`. | + +## Upload Entry Fields + +Each entry in `entries` has: + +| Field | Type | Description | +|-------|------|-------------| +| `ref` | `string` | Unique entry identifier | +| `client_name` | `string` | Original filename | +| `client_size` | `number` | File size in bytes | +| `client_type` | `string` | MIME type | +| `progress` | `number` | Upload progress (0โ€“100) | +| `errors` | `string[]` | Validation error messages | +| `valid` | `boolean` | Whether entry passes validation | +| `done` | `boolean` | Whether upload is complete | +| `preflighted` | `boolean` | Whether Phoenix has acknowledged (preflighted) this entry | + +## Drag and Drop + +```svelte + + +
    { e.preventDefault(); dragOver = true }} + ondragleave={() => { dragOver = false }} + ondrop={(e) => { + e.preventDefault() + dragOver = false + // Phoenix LiveView handles the drop via phx-drop-target + }} + phx-drop-target={uploads.avatar?.ref} +> + Drop files here +
    +``` + +## Multiple Files + +Configure `max_entries` on the LiveView side: + +```elixir +allow_upload(:photos, accept: ~w(.jpg .png .gif), max_entries: 5) +``` + +The `entries` array in the component will reflect all selected files. + +## Validation + +File validation is configured with `allow_upload/3` options: + +```elixir +allow_upload(:avatar, + accept: ~w(.jpg .png .webp), + max_entries: 1, + max_file_size: 10_000_000 # 10 MB +) +``` + +Validation errors appear in `entry.errors` as human-readable strings. + +## Progress Tracking + +Upload progress is automatically tracked per entry via `entry.progress` (0โ€“100): + +```svelte +{#each $entries as entry (entry.ref)} +
    + {entry.client_name} +
    + {#if entry.done} + โœ“ Complete + {/if} +
    +{/each} +``` diff --git a/lib/live_json.ex b/lib/live_json.ex deleted file mode 100644 index b3d7c0b..0000000 --- a/lib/live_json.ex +++ /dev/null @@ -1,28 +0,0 @@ -defmodule LiveSvelte.LiveJson do - use Phoenix.Component - - attr( - :live_json_props, - :map, - default: %{}, - doc: "LiveJSON props to pass to the svelte component" - ) - - attr( - :svelte_id, - :string, - required: true, - doc: "Stable DOM id from the parent svelte component" - ) - - slot(:inner_block) - - def live_json(assigns) do - ~H""" - <%= if @live_json_props != %{} do %> -
    - <% end %> - <%= render_slot(@inner_block) %> - """ - end -end diff --git a/lib/live_svelte.ex b/lib/live_svelte.ex index 8e863ce..0c30a6c 100644 --- a/lib/live_svelte.ex +++ b/lib/live_svelte.ex @@ -7,7 +7,6 @@ defmodule LiveSvelte do use Phoenix.Component import Phoenix.HTML - import LiveSvelte.LiveJson alias Phoenix.LiveView alias Phoenix.LiveView.LiveStream @@ -55,11 +54,6 @@ defmodule LiveSvelte do default: nil, doc: "LiveView socket, only needed when ssr: true" - attr :live_json_props, :map, - default: %{}, - doc: "LiveJson props to pass to the Svelte component", - examples: [%{my_big_data_set: %{some_data: 1}}] - attr :diff, :boolean, default: true, doc: @@ -120,11 +114,7 @@ defmodule LiveSvelte do ssr_code = if init and dead and ssr_active and assigns.ssr do try do - props = - Map.merge( - Map.get(assigns, :props, %{}), - Map.get(assigns, :live_json_props, %{}) - ) + props = Map.get(assigns, :props, %{}) SSR.render(assigns.name, props, slots) rescue @@ -146,36 +136,31 @@ defmodule LiveSvelte do |> assign(:streams_diff, streams_diff) ~H""" - <.live_json live_json_props={@live_json_props} svelte_id={@svelte_id}> - +
    Slots.base_encode_64() |> json} + phx-hook="SvelteHook" + phx-update="ignore" + class={@class} + > +
    <%= raw(@ssr_render["head"]) %> - -
    Map.keys() |> json() - } - data-slots={@slots |> Slots.base_encode_64() |> json} - phx-hook="SvelteHook" - phx-update="ignore" - class={@class} - > -
    - <%= raw(@ssr_render["head"]) %> - - <%= raw(@ssr_render["html"]) %> - <%= render_slot(@loading) %> -
    + + <%= raw(@ssr_render["html"]) %> + <%= render_slot(@loading) %>
    - +
    """ end diff --git a/mix.exs b/mix.exs index 2745c2e..1d0fa1a 100644 --- a/mix.exs +++ b/mix.exs @@ -26,12 +26,44 @@ defmodule LiveSvelte.MixProject do source_ref: "v#{@version}", source_url: @repo_url, homepage_url: @repo_url, - main: "LiveSvelte", + main: "readme", logo: "logo_3.png", links: %{ "GitHub" => @repo_url, "Sponsor" => "https://github.com/sponsors/woutdp" - } + }, + extras: [ + "README.md": [title: "LiveSvelte"], + + # Getting Started + "guides/installation.md": [title: "Installation"], + "guides/basic_usage.md": [title: "Basic Usage"], + + # Core Usage + "guides/forms.md": [title: "Forms and Validation"], + "guides/uploads.md": [title: "File Uploads"], + "guides/streams.md": [title: "Phoenix Streams"], + "guides/ssr.md": [title: "Server-Side Rendering"], + "guides/configuration.md": [title: "Configuration"], + + # Reference + "guides/api_reference.md": [title: "API Reference"], + + # Advanced Topics + "guides/introduction.md": [title: "Introduction"], + "guides/testing.md": [title: "Testing"], + "guides/deployment.md": [title: "Deployment"], + + # Help & Troubleshooting + "guides/troubleshooting.md": [title: "Troubleshooting"] + ], + groups_for_extras: [ + "Getting Started": ~r/guides\/(installation|basic_usage)/, + "Core Usage": ~r/guides\/(forms|uploads|streams|ssr|configuration)/, + Reference: ~r/guides\/api_reference/, + "Advanced Topics": ~r/guides\/(introduction|testing|deployment)/, + "Help & Troubleshooting": ~r/guides\/troubleshooting/ + ] ] ] end @@ -45,7 +77,7 @@ defmodule LiveSvelte.MixProject do GitHub: @repo_url }, files: - ~w(assets/copy/tsconfig.json assets/js lib mix.exs package.json .formatter.exs LICENSE.md README.md CHANGELOG.md) + ~w(assets/copy/tsconfig.json assets/js guides lib mix.exs package.json .formatter.exs LICENSE.md README.md CHANGELOG.md) ] end diff --git a/package-lock.json b/package-lock.json index e3798f3..f17bc0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,38 +9,31 @@ "version": "0.17.4", "license": "MIT", "devDependencies": { - "prettier": "3.3.3", - "prettier-plugin-svelte": "^3.2.7", - "svelte": "^5.1.13" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" + "prettier": "^3.8.1", + "prettier-plugin-svelte": "^3.5.1", + "svelte": "^5.53.7" } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -53,16 +46,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -71,9 +54,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -81,17 +64,34 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", + "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "dev": true, "license": "MIT" }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "peer": true, @@ -102,20 +102,10 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-typescript": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz", - "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": ">=8.9.0" - } - }, "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", "dev": true, "license": "Apache-2.0", "engines": { @@ -132,32 +122,48 @@ "node": ">= 0.4" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devalue": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.3.tgz", + "integrity": "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==", + "dev": true, + "license": "MIT" + }, "node_modules/esm-env": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.1.4.tgz", - "integrity": "sha512-oO82nKPHKkzIj/hbtuDYy/JHqBHFlMIW36SDiPCVsj87ntDLcWN+sJ1erdVryd4NxODacFTsdrIE3b7IamqbOg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", "dev": true, "license": "MIT" }, "node_modules/esrap": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz", - "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.3.tgz", + "integrity": "sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15", - "@types/estree": "^1.0.1" + "@jridgewell/sourcemap-codec": "^1.4.15" } }, "node_modules/is-reference": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", - "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "*" + "@types/estree": "^1.0.6" } }, "node_modules/locate-character": { @@ -178,9 +184,9 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", "peer": true, @@ -195,9 +201,9 @@ } }, "node_modules/prettier-plugin-svelte": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.2.7.tgz", - "integrity": "sha512-/Dswx/ea0lV34If1eDcG3nulQ63YNr5KPDfMsjbdtpSWOxKKJ7nAc2qlVuYwEvCr4raIuredNoR7K4JCkmTGaQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.5.1.tgz", + "integrity": "sha512-65+fr5+cgIKWKiqM1Doum4uX6bY8iFCdztvvp2RcF+AJoieaw9kJOFMNcJo/bkmKYsxFaM9OsVZK/gWauG/5mg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -206,23 +212,26 @@ } }, "node_modules/svelte": { - "version": "5.1.13", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.1.13.tgz", - "integrity": "sha512-xVNk8yLsZNfkyqWzVg8+nfU9ewiSjVW0S4qyTxfKa6Y7P5ZBhA+LDsh2cHWIXJQMltikQAk6W3sqGdQZSH58PA==", + "version": "5.53.7", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.7.tgz", + "integrity": "sha512-uxck1KI7JWtlfP3H6HOWi/94soAl23jsGJkBzN2BAWcQng0+lTrRNhxActFqORgnO9BHVd1hKJhG+ljRuIUWfQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@ampproject/remapping": "^2.3.0", + "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", - "acorn-typescript": "^1.4.13", - "aria-query": "^5.3.1", + "aria-query": "5.3.1", "axobject-query": "^4.1.0", - "esm-env": "^1.0.0", - "esrap": "^1.2.2", - "is-reference": "^3.0.2", + "clsx": "^2.1.1", + "devalue": "^5.6.3", + "esm-env": "^1.2.1", + "esrap": "^2.2.2", + "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" diff --git a/package.json b/package.json index d09a4e1..8b8d5bb 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,9 @@ "format": "npx prettier --write ." }, "devDependencies": { - "prettier": "3.3.3", - "prettier-plugin-svelte": "^3.2.7", - "svelte": "^5.1.13" + "prettier": "^3.8.1", + "prettier-plugin-svelte": "^3.5.1", + "svelte": "^5.53.7" }, "exports": { ".": { diff --git a/test/auto_id_test.exs b/test/auto_id_test.exs index 695fef8..3efd277 100644 --- a/test/auto_id_test.exs +++ b/test/auto_id_test.exs @@ -14,7 +14,6 @@ defmodule LiveSvelte.AutoIdTest do id: Keyword.get(opts, :id), key: Keyword.get(opts, :key), props: Keyword.get(opts, :props, %{}), - live_json_props: %{}, ssr: false, class: nil, loading: [], @@ -298,19 +297,9 @@ defmodule LiveSvelte.AutoIdTest do end # --------------------------------------------------------------------------- - # Tests โ€” live_json and phx-update (unchanged from before) + # Tests โ€” phx-update # --------------------------------------------------------------------------- - describe "live_json id derivation" do - test "live_json div id is prefixed with lj- and uses the svelte id" do - html = render_html(base_assigns("Counter", props: %{x: 1})) - - # When live_json_props is empty, no lj- div is rendered. - # Verify the component id is still correct. - assert extract_id(html) == "Counter" - end - end - describe "phx-update attribute" do test "outer hook container has phx-update=ignore to protect Svelte DOM" do html = render_html(base_assigns("Counter")) diff --git a/test/json_library_test.exs b/test/json_library_test.exs index 26dd4c2..1f05ae8 100644 --- a/test/json_library_test.exs +++ b/test/json_library_test.exs @@ -21,7 +21,6 @@ defmodule LiveSvelte.JSONLibraryTest do socket: nil, name: "Test", props: data, - live_json_props: %{}, ssr: false, class: nil, loading: [], @@ -58,7 +57,6 @@ defmodule LiveSvelte.JSONLibraryTest do socket: nil, name: "Test", props: data, - live_json_props: %{}, ssr: false, class: nil, loading: [], @@ -87,7 +85,6 @@ defmodule LiveSvelte.JSONLibraryTest do socket: nil, name: "Legacy", props: data, - live_json_props: %{}, ssr: false, class: nil, loading: [], @@ -120,7 +117,6 @@ defmodule LiveSvelte.JSONLibraryTest do socket: nil, name: "StructTest", props: data, - live_json_props: %{}, ssr: false, class: nil, loading: [], diff --git a/test/live_svelte/json_test.exs b/test/live_svelte/json_test.exs index e8ae8f2..2ad3caf 100644 --- a/test/live_svelte/json_test.exs +++ b/test/live_svelte/json_test.exs @@ -145,7 +145,7 @@ defmodule LiveSvelte.JSONTest do assert decoded["2"] == "b" end - test "encodes large maps with integer keys (LiveJson scenario)" do + test "encodes large maps with integer keys" do data = for i <- 1..100, into: %{}, do: {i, i * 2} result = JSON.encode!(data) decoded = :json.decode(result) diff --git a/test/props_diff_test.exs b/test/props_diff_test.exs index 04a1346..5739393 100644 --- a/test/props_diff_test.exs +++ b/test/props_diff_test.exs @@ -10,7 +10,6 @@ defmodule LiveSvelte.PropsDiffTest do id: nil, key: nil, props: Keyword.get(opts, :props, %{}), - live_json_props: %{}, ssr: false, class: nil, loading: [], diff --git a/test/streams_test.exs b/test/streams_test.exs index dd0f7d1..1939aaa 100644 --- a/test/streams_test.exs +++ b/test/streams_test.exs @@ -31,7 +31,6 @@ defmodule LiveSvelte.StreamsTest do id: nil, key: nil, props: %{}, - live_json_props: %{}, ssr: false, class: nil, loading: [],