Compare commits

..

8 Commits

Author SHA1 Message Date
Greg Johnston
1a7344ca50 update other examples 2023-06-25 13:43:15 -04:00
Greg Johnston
921bb616b4 fmt 2023-06-25 13:31:48 -04:00
Greg Johnston
5bb7122b93 fmt 2023-06-24 17:06:58 -04:00
Greg Johnston
ec2aa3e7a4 adapt example 2023-06-24 17:05:38 -04:00
Greg Johnston
43959a96c7 support server errors 2023-06-24 17:04:50 -04:00
Greg Johnston
7925fc4245 attempt at including server 2023-06-24 16:34:29 -04:00
Greg Johnston
2bd1ad0f11 fmt example 2023-06-23 13:54:42 -04:00
Greg Johnston
dd730aa4ac feat: add an anyhow-like Result type for easier error handling 2023-06-23 13:25:16 -04:00
119 changed files with 592 additions and 1403 deletions

View File

@@ -16,10 +16,10 @@ Please copy and paste the Leptos dependencies and features from your `Cargo.toml
For example:
```toml
leptos = { version = "0.3", features = ["serde"] }
leptos = { version = "0.3", default-features = false, features = ["serde"] }
leptos_axum = { version = "0.3", optional = true }
leptos_meta = { version = "0.3"}
leptos_router = { version = "0.3"}
leptos_meta = { version = "0.3", default-features = false }
leptos_router = { version = "0.3", default-features = false }
```
**To Reproduce**

View File

@@ -1,5 +1,5 @@
[workspace]
resolver = "2"
resolver="2"
members = [
# core
"leptos",
@@ -26,22 +26,22 @@ members = [
exclude = ["benchmarks", "examples"]
[workspace.package]
version = "0.4.0"
version = "0.3.0"
[workspace.dependencies]
leptos = { path = "./leptos", version = "0.4.0" }
leptos_dom = { path = "./leptos_dom", version = "0.4.0" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.4.0" }
leptos_macro = { path = "./leptos_macro", version = "0.4.0" }
leptos_reactive = { path = "./leptos_reactive", version = "0.4.0" }
leptos_server = { path = "./leptos_server", version = "0.4.0" }
server_fn = { path = "./server_fn", version = "0.4.0" }
server_fn_macro = { path = "./server_fn_macro", version = "0.4.0" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.4.0" }
leptos_config = { path = "./leptos_config", version = "0.4.0" }
leptos_router = { path = "./router", version = "0.4.0" }
leptos_meta = { path = "./meta", version = "0.4.0" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.4.0" }
leptos = { path = "./leptos", default-features = false, version = "0.3.0" }
leptos_dom = { path = "./leptos_dom", default-features = false, version = "0.3.0" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.3.0" }
leptos_macro = { path = "./leptos_macro", default-features = false, version = "0.3.0" }
leptos_reactive = { path = "./leptos_reactive", default-features = false, version = "0.3.0" }
leptos_server = { path = "./leptos_server", default-features = false, version = "0.3.0" }
server_fn = { path = "./server_fn", default-features = false, version = "0.3.0" }
server_fn_macro = { path = "./server_fn_macro", default-features = false, version = "0.3.0" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", default-features = false, version = "0.3.0" }
leptos_config = { path = "./leptos_config", default-features = false, version = "0.3.0" }
leptos_router = { path = "./router", version = "0.3.0" }
leptos_meta = { path = "./meta", default-features = false, version = "0.3.0" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.3.0" }
[profile.release]
codegen-units = 1

View File

@@ -68,7 +68,7 @@ Here are some resources for learning more about Leptos:
## `nightly` Note
Most of the examples assume youre using `nightly` version of Rust and the `nightly` feature of Leptos. To use `nightly` Rust, you can either set your toolchain globally or on per-project basis.
Most of the examples assume youre using `nightly` version of Rust. For this, you can either set your toolchain globally or on per-project basis.
To set `nightly` as a default toolchain for all projects (and add the ability to compile Rust to WebAssembly, if you havent already):
@@ -86,9 +86,13 @@ channel = "nightly"
targets = ["wasm32-unknown-unknown"]
```
The `nightly` feature enables the function call syntax for accessing and setting signals, as opposed to `.get()` and `.set()`. This leads to a consistent mental model in which accessing a reactive value of any kind (a signal, memo, or derived signal) is always represented as a function call. This is only possible with nightly Rust and the `nightly` feature.
If youre on `stable`, note the following:
> Note: The `nightly` feature is present on the main branch version right now, but not in 0.3.x. For 0.3.x, nightly is the default and `stable` has a special feature.
1. You need to enable the `"stable"` flag in `Cargo.toml`: `leptos = { version = "0.2", features = ["stable"] }`
2. `nightly` enables the function call syntax for accessing and setting signals. If youre using `stable`,
youll just call `.get()`, `.set()`, or `.update()` manually. Check out the
[`counters_stable` example](https://github.com/leptos-rs/leptos/blob/main/examples/counters_stable/src/main.rs)
for examples of the correct API.
## `cargo-leptos`

View File

@@ -5,7 +5,7 @@ edition = "2021"
[dependencies]
l021 = { package = "leptos", version = "0.2.1" }
leptos = { path = "../leptos", features = ["ssr"] }
leptos = { path = "../leptos", default-features = false, features = ["ssr"] }
sycamore = { version = "0.8", features = ["ssr"] }
yew = { git = "https://github.com/yewstack/yew", features = ["ssr"] }
tokio-test = "0.4"

View File

@@ -31,7 +31,7 @@ cargo init leptos-tutorial
`cd` into your new `leptos-tutorial` project and add `leptos` as a dependency
```bash
cargo add leptos --features=csr,nightly # or just csr if you're using stable Rust
cargo add leptos
```
Create a simple `index.html` in the root of the `leptos-tutorial` directory

View File

@@ -34,7 +34,7 @@ category = "Cleanup"
script = '''
for pw_dir in $(find . -name playwright.config.ts | xargs dirname)
do
rm -rf $pw_dir/playwright-report pw_dir/playwright pw_dir/test-results
rm -rf $pw_dir/playwright-report
done
'''

View File

@@ -1,8 +1,5 @@
extend = [{ path = "../cargo-make/common.toml" }]
[tasks.ci]
alias = "verify-flow"
[tasks.verify-flow]
description = "Provides pre and post hooks for verify"
dependencies = ["pre-verify", "verify", "post-verify"]

View File

@@ -1,7 +0,0 @@
extend = [{ path = "../cargo-make/playwright.toml" }]
[tasks.test-e2e]
dependencies = ["setup-node", "test-playwright-autostart"]
[tasks.clean-all]
dependencies = ["clean-cargo", "clean-node_modules", "clean-playwright"]

View File

@@ -1,119 +0,0 @@
[tasks.clean-playwright]
description = "Delete playwright directories"
category = "Cleanup"
script = '''
for pw_dir in $(find . -name playwright.config.ts | xargs dirname)
do
rm -rf $pw_dir/playwright-report pw_dir/playwright pw_dir/test-results
done
'''
[tasks.test-playwright-autostart]
description = "Run playwright test with server autostart"
category = "Test"
command = "npm"
args = ["run", "e2e:auto-start"]
[tasks.test-playwright]
description = "Run playwright test"
category = "Test"
script = '''
BOLD="\e[1m"
GREEN="\e[0;32m"
RED="\e[0;31m"
RESET="\e[0m"
project_dir=$CARGO_MAKE_WORKING_DIRECTORY
# Discover commands
if command -v pnpm; then
PLAYWRIGHT_CMD=pnpm
elif command -v npm; then
PLAYWRIGHT_CMD=npx
else
echo "${RED}${BOLD}ERROR${RESET} - pnpm or npm is required by this task"
exit 1
fi
# Run playwright command
for pw_path in $(find . -name playwright.config.ts)
do
pw_dir=$(dirname $pw_path)
cd $pw_dir
${PLAYWRIGHT_CMD} playwright test
cd $project_dir
done
'''
[tasks.test-playwright-ui]
description = "Run playwright test --ui"
category = "Test"
script = '''
BOLD="\e[1m"
GREEN="\e[0;32m"
RED="\e[0;31m"
RESET="\e[0m"
project_dir=$CARGO_MAKE_WORKING_DIRECTORY
# Discover commands
if command -v pnpm; then
PLAYWRIGHT_CMD=pnpm
elif command -v npm; then
PLAYWRIGHT_CMD=npx
else
echo "${RED}${BOLD}ERROR${RESET} - pnpm or npm is required by this task"
exit 1
fi
# Run playwright command
for pw_path in $(find . -name playwright.config.ts)
do
pw_dir=$(dirname $pw_path)
cd $pw_dir
${PLAYWRIGHT_CMD} playwright test --ui
cd $project_dir
done
'''
[tasks.test-playwright-report]
description = "Run playwright show-report"
category = "Test"
script = '''
BOLD="\e[1m"
GREEN="\e[0;32m"
RED="\e[0;31m"
RESET="\e[0m"
project_dir=$CARGO_MAKE_WORKING_DIRECTORY
# Discover commands
if command -v pnpm; then
PLAYWRIGHT_CMD=pnpm
elif command -v npm; then
PLAYWRIGHT_CMD=npx
else
echo "${RED}${BOLD}ERROR${RESET} - pnpm or npm is required by this task"
exit 1
fi
# Run playwright command
for pw_path in $(find . -name playwright.config.ts)
do
pw_dir=$(dirname $pw_path)
cd $pw_dir
${PLAYWRIGHT_CMD} playwright show-report
cd $project_dir
done
'''
# ALIASES
[tasks.pw]
dependencies = ["test-playwright"]
[tasks.pw-ui]
dependencies = ["test-playwright-ui"]
[tasks.pw-report]
dependencies = ["test-playwright-report"]

View File

@@ -1,22 +0,0 @@
[tasks.build]
command = "trunk"
args = ["build"]
[tasks.clean-trunk]
command = "trunk"
args = ["clean"]
[tasks.start-trunk]
command = "trunk"
args = ["serve", "--open"]
[tasks.stop-trunk]
script = '''
pkill -f "cargo-make"
pkill -f "trunk"
'''
# ALIASES
[tasks.dev]
dependencies = ["start-trunk"]

View File

@@ -8,7 +8,7 @@ codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr", "nightly"] }
leptos = { path = "../../leptos" }
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"

View File

@@ -19,17 +19,19 @@ console_error_panic_hook = "0.1"
futures = "0.3"
cfg-if = "1"
lazy_static = "1"
leptos = { path = "../../leptos" }
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_meta = { path = "../../meta" }
leptos_router = { path = "../../router" }
leptos_meta = { path = "../../meta", default-features = false }
leptos_router = { path = "../../router", default-features = false }
log = "0.4"
gloo-net = { git = "https://github.com/rustwasm/gloo" }
wasm-bindgen = "=0.2.86"
serde = { version = "1", features = ["derive"] }
[features]
default = ["nightly"]
default = []
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:actix-files",
@@ -39,10 +41,10 @@ ssr = [
"leptos_meta/ssr",
"leptos_router/ssr",
]
nightly = ["leptos/nightly", "leptos_router/nightly"]
stable = ["leptos/stable", "leptos_router/stable"]
[package.metadata.cargo-all-features]
denylist = ["actix-files", "actix-web", "leptos_actix", "nightly"]
denylist = ["actix-files", "actix-web", "leptos_actix", "stable"]
skip_feature_sets = [["ssr", "hydrate"]]
[package.metadata.leptos]

View File

@@ -8,7 +8,7 @@ codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
leptos = { path = "../../leptos", features = ["stable"] }
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../leptos", features = ["csr", "nightly"] }
leptos = { path = "../../leptos" }
log = "0.4"
console_log = "1"
console_error_panic_hook = "0.1.7"

View File

@@ -1,20 +0,0 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# Support playwright testing
node_modules/
test-results/
end2end/playwright-report/
playwright/.cache/
pnpm-lock.yaml
# Support trunk
dist

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
leptos = { path = "../../leptos", features = ["stable"] }
log = "0.4"
console_log = "1"
console_error_panic_hook = "0.1.7"

View File

@@ -1,8 +1,4 @@
extend = [
{ path = "../cargo-make/main.toml" },
{ path = "../cargo-make/trunk_server.toml" },
{ path = "../cargo-make/playwright-test.toml" },
]
extend = [{ path = "../cargo-make/main.toml" }]
[tasks.build]
command = "cargo"

View File

@@ -1,4 +0,0 @@
node_modules/
/test-results/
/playwright-report/
/playwright/.cache/

View File

@@ -1,83 +0,0 @@
{
"name": "grip",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "grip",
"devDependencies": {
"@playwright/test": "^1.35.1"
}
},
"node_modules/.pnpm/@playwright+test@1.33.0": {
"extraneous": true
},
"node_modules/.pnpm/@types+node@20.2.1/node_modules/@types/node": {
"version": "20.2.1",
"extraneous": true,
"license": "MIT"
},
"node_modules/.pnpm/playwright-core@1.33.0/node_modules/playwright-core": {
"version": "1.33.0",
"extraneous": true,
"license": "Apache-2.0",
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=14"
}
},
"node_modules/@playwright/test": {
"version": "1.35.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.35.1.tgz",
"integrity": "sha512-b5YoFe6J9exsMYg0pQAobNDR85T1nLumUYgUTtKm4d21iX2L7WqKq9dW8NGJ+2vX0etZd+Y7UeuqsxDXm9+5ZA==",
"dev": true,
"dependencies": {
"@types/node": "*",
"playwright-core": "1.35.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=16"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/@types/node": {
"version": "20.3.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz",
"integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==",
"dev": true
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/playwright-core": {
"version": "1.35.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.35.1.tgz",
"integrity": "sha512-pNXb6CQ7OqmGDRspEjlxE49w+4YtR6a3X6mT1hZXeJHWmsEz7SunmvZeiG/+y1yyMZdHnnn73WKYdtV1er0Xyg==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=16"
}
}
}
}

View File

@@ -1,7 +0,0 @@
{
"private": "true",
"scripts": {},
"devDependencies": {
"@playwright/test": "^1.35.1"
}
}

View File

@@ -1,77 +0,0 @@
import { defineConfig, devices } from "@playwright/test";
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./tests",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !process.env.DEV,
/* Retry on CI only */
retries: process.env.DEV ? 0 : 10,
/* Opt out of parallel tests on CI. */
workers: process.env.DEV ? 1 : 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [["html", { open: "never" }], ["list"]],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://127.0.0.1:8080",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
// {
// name: "firefox",
// use: { ...devices["Desktop Firefox"] },
// },
// {
// name: "webkit",
// use: { ...devices["Desktop Safari"] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ..devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
// webServer: {
// command: "cd ../ && trunk serve",
// url: "http://127.0.0.1:8080",
// reuseExistingServer: false, //!process.env.CI,
// },
});

View File

@@ -1,20 +0,0 @@
import { test, expect } from "@playwright/test";
import { CountersPage } from "./fixtures/counters_page";
test.describe("Add 1000 Counters", () => {
test("should increase the number of counters", async ({ page }) => {
const ui = new CountersPage(page);
await Promise.all([
await ui.goto(),
await ui.addOneThousandCountersButton.waitFor(),
]);
await ui.addOneThousandCounters();
await ui.addOneThousandCounters();
await ui.addOneThousandCounters();
await expect(ui.total).toHaveText("0");
await expect(ui.counters).toHaveText("3000");
});
});

View File

@@ -1,16 +0,0 @@
import { test, expect } from "@playwright/test";
import { CountersPage } from "./fixtures/counters_page";
test.describe("Add Counter", () => {
test("should increase the number of counters", async ({ page }) => {
const ui = new CountersPage(page);
await ui.goto();
await ui.addCounter();
await ui.addCounter();
await ui.addCounter();
await expect(ui.total).toHaveText("0");
await expect(ui.counters).toHaveText("3");
});
});

View File

@@ -1,18 +0,0 @@
import { test, expect } from "@playwright/test";
import { CountersPage } from "./fixtures/counters_page";
test.describe("Clear Counters", () => {
test("should reset the counts", async ({ page }) => {
const ui = new CountersPage(page);
await ui.goto();
await ui.addCounter();
await ui.addCounter();
await ui.addCounter();
await ui.clearCounters();
await expect(ui.total).toHaveText("0");
await expect(ui.counters).toHaveText("0");
});
});

View File

@@ -1,17 +0,0 @@
import { test, expect } from "@playwright/test";
import { CountersPage } from "./fixtures/counters_page";
test.describe("Decrement Count", () => {
test("should decrease the total count", async ({ page }) => {
const ui = new CountersPage(page);
await ui.goto();
await ui.addCounter();
await ui.decrementCount();
await ui.decrementCount();
await ui.decrementCount();
await expect(ui.total).toHaveText("-3");
await expect(ui.counters).toHaveText("1");
});
});

View File

@@ -1,31 +0,0 @@
import { test, expect } from "@playwright/test";
import { CountersPage } from "./fixtures/counters_page";
test.describe("Enter Count", () => {
test("should increase the total count", async ({ page }) => {
const ui = new CountersPage(page);
await ui.goto();
await ui.addCounter();
await ui.enterCount("5");
await expect(ui.total).toHaveText("5");
await expect(ui.counters).toHaveText("1");
});
test("should decrease the total count", async ({ page }) => {
const ui = new CountersPage(page);
await ui.goto();
await ui.addCounter();
await ui.addCounter();
await ui.addCounter();
await ui.enterCount("100");
await ui.enterCount("100", 1);
await ui.enterCount("100", 2);
await ui.enterCount("50", 1);
await expect(ui.total).toHaveText("250");
await expect(ui.counters).toHaveText("3");
});
});

View File

@@ -1,98 +0,0 @@
import { expect, Locator, Page } from "@playwright/test";
export class CountersPage {
readonly page: Page;
readonly addCounterButton: Locator;
readonly addOneThousandCountersButton: Locator;
readonly clearCountersButton: Locator;
readonly incrementCountButton: Locator;
readonly counterInput: Locator;
readonly decrementCountButton: Locator;
readonly removeCountButton: Locator;
readonly total: Locator;
readonly counters: Locator;
constructor(page: Page) {
this.page = page;
this.addCounterButton = page.locator("button", { hasText: "Add Counter" });
this.addOneThousandCountersButton = page.locator("button", {
hasText: "Add 1000 Counters",
});
this.clearCountersButton = page.locator("button", {
hasText: "Clear Counters",
});
this.decrementCountButton = page.locator("button", {
hasText: "-1",
});
this.incrementCountButton = page.locator("button", {
hasText: "+1",
});
this.removeCountButton = page.locator("button", {
hasText: "x",
});
this.total = page.getByTestId("total");
this.counters = page.getByTestId("counters");
this.counterInput = page.getByRole("textbox");
}
async goto() {
await this.page.goto("/");
}
async addCounter() {
await Promise.all([
this.addCounterButton.waitFor(),
this.addCounterButton.click(),
]);
}
async addOneThousandCounters() {
this.addOneThousandCountersButton.click();
}
async decrementCount(index: number = 0) {
await Promise.all([
this.decrementCountButton.nth(index).waitFor(),
this.decrementCountButton.nth(index).click(),
]);
}
async incrementCount(index: number = 0) {
await Promise.all([
this.incrementCountButton.nth(index).waitFor(),
this.incrementCountButton.nth(index).click(),
]);
}
async clearCounters() {
await Promise.all([
this.clearCountersButton.waitFor(),
this.clearCountersButton.click(),
]);
}
async enterCount(count: string, index: number = 0) {
await Promise.all([
this.counterInput.nth(index).waitFor(),
this.counterInput.nth(index).fill(count),
]);
}
async removeCounter(index: number = 0) {
await Promise.all([
this.removeCountButton.nth(index).waitFor(),
this.removeCountButton.nth(index).click(),
]);
}
}

View File

@@ -1,17 +0,0 @@
import { test, expect } from "@playwright/test";
import { CountersPage } from "./fixtures/counters_page";
test.describe("Increment Count", () => {
test("should increase the total count", async ({ page }) => {
const ui = new CountersPage(page);
await ui.goto();
await ui.addCounter();
await ui.incrementCount();
await ui.incrementCount();
await ui.incrementCount();
await expect(ui.total).toHaveText("3");
await expect(ui.counters).toHaveText("1");
});
});

View File

@@ -1,18 +0,0 @@
import { test, expect } from "@playwright/test";
import { CountersPage } from "./fixtures/counters_page";
test.describe("Remove Counter", () => {
test("should decrement the number of counters", async ({ page }) => {
const ui = new CountersPage(page);
await ui.goto();
await ui.addCounter();
await ui.addCounter();
await ui.addCounter();
await ui.removeCounter(1);
await expect(ui.total).toHaveText("0");
await expect(ui.counters).toHaveText("2");
});
});

View File

@@ -1,19 +0,0 @@
import { test, expect } from "@playwright/test";
import { CountersPage } from "./fixtures/counters_page";
test.describe("View Counters", () => {
test("should see the title", async ({ page }) => {
const ui = new CountersPage(page);
await ui.goto();
await expect(page).toHaveTitle("Counters (Stable)");
});
test("should see the initial counts", async ({ page }) => {
const counters = new CountersPage(page);
await counters.goto();
await expect(counters.total).toHaveText("0");
await expect(counters.counters).toHaveText("0");
});
});

View File

@@ -1,7 +1,6 @@
<!DOCTYPE html>
<html>
<head>
<title>Counters (Stable)</title>
<link data-trunk rel="rust" data-wasm-opt="z" data-weak-refs/>
</head>
<body></body>

View File

@@ -1,11 +0,0 @@
{
"private": "true",
"scripts": {
"start-server": "trunk serve",
"e2e": "cargo make test-playwright",
"e2e:auto-start": "start-server-and-test start-server http://127.0.0.1:8080 e2e"
},
"devDependencies": {
"start-server-and-test": "^1.15.4"
}
}

View File

@@ -56,7 +56,7 @@ pub fn Counters(cx: Scope) -> impl IntoView {
</button>
<p>
"Total: "
<span data-testid="total">{move ||
<span>{move ||
counters.get()
.iter()
.map(|(_, (count, _))| count.get())
@@ -64,7 +64,7 @@ pub fn Counters(cx: Scope) -> impl IntoView {
.to_string()
}</span>
" from "
<span data-testid="counters">{move || counters.with(|counters| counters.len()).to_string()}</span>
<span>{move || counters.with(|counters| counters.len()).to_string()}</span>
" counters."
</p>
<ul>
@@ -99,13 +99,13 @@ fn Counter(
view! { cx,
<li>
<button id="decrement_count" on:click=move |_| set_value.update(move |value| *value -= 1)>"-1"</button>
<button on:click=move |_| set_value.update(move |value| *value -= 1)>"-1"</button>
<input type="text"
prop:value={move || value.get().to_string()}
on:input=input
/>
<span>{value}</span>
<button id="increment_count" on:click=move |_| set_value.update(move |value| *value += 1)>"+1"</button>
<span>{move || value.get().to_string()}</span>
<button on:click=move |_| set_value.update(move |value| *value += 1)>"+1"</button>
<button on:click=move |_| set_counters.update(move |counters| counters.retain(|(counter_id, _)| counter_id != &id))>"x"</button>
</li>
}

View File

@@ -8,7 +8,7 @@ codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr", "nightly"] }
leptos = { path = "../../leptos" }
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"

View File

@@ -10,10 +10,12 @@ crate-type = ["cdylib", "rlib"]
console_log = "1.0.0"
console_error_panic_hook = "0.1.7"
cfg-if = "1.0.0"
leptos = { path = "../../leptos", features = ["nightly"] }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_meta = { path = "../../meta" }
leptos_router = { path = "../../router" }
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_axum = { path = "../../integrations/axum", default-features = false, optional = true }
leptos_meta = { path = "../../meta", default-features = false }
leptos_router = { path = "../../router", default-features = false }
log = "0.4.17"
serde = { version = "1", features = ["derive"] }
simple_logger = "4.0.0"

View File

@@ -8,7 +8,7 @@ codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr", "nightly"] }
leptos = { path = "../../leptos" }
reqwasm = "0.5"
serde = { version = "1", features = ["derive"] }
log = "0.4"

View File

@@ -4,5 +4,5 @@ version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../leptos", features = ["csr", "nightly"] }
leptos = { path = "../../leptos" }
gtk = { version = "0.5.0", package = "gtk4" }

View File

@@ -16,10 +16,12 @@ actix-web = { version = "4", optional = true, features = ["macros"] }
console_log = "1"
console_error_panic_hook = "0.1"
cfg-if = "1"
leptos = { path = "../../leptos", features = ["nightly"] }
leptos_meta = { path = "../../meta", features = ["nightly"] }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_router = { path = "../../router", features = ["nightly"] }
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_meta = { path = "../../meta", default-features = false }
leptos_actix = { path = "../../integrations/actix", default-features = false, optional = true }
leptos_router = { path = "../../router", default-features = false }
log = "0.4"
serde = { version = "1", features = ["derive"] }
gloo-net = { version = "0.2", features = ["http"] }

View File

@@ -14,10 +14,12 @@ lto = true
console_log = "1.0.0"
console_error_panic_hook = "0.1.7"
cfg-if = "1.0.0"
leptos = { path = "../../leptos", features = ["nightly"] }
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_meta = { path = "../../meta", features = ["nightly"] }
leptos_router = { path = "../../router", features = ["nightly"] }
leptos_meta = { path = "../../meta", default-features = false }
leptos_router = { path = "../../router", default-features = false }
log = "0.4.17"
simple_logger = "4.0.0"
serde = { version = "1.0.148", features = ["derive"] }

View File

@@ -8,7 +8,7 @@ codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr", "nightly", "template_macro"] }
leptos = { path = "../../leptos", features=["template_macro"] }
console_log = "1"
log = "0.4"
# used in rand, but we need to enable js feature

View File

@@ -11,10 +11,12 @@ axum = { version = "0.6.18", optional = true }
console_error_panic_hook = "0.1.7"
console_log = "1"
cfg-if = "1"
leptos = { path = "../../leptos", features = ["nightly"] }
leptos_meta = { path = "../../meta", features = ["nightly"] }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_router = { path = "../../router", features = ["nightly"] }
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_meta = { path = "../../meta", default-features = false }
leptos_axum = { path = "../../integrations/axum", default-features = false, optional = true }
leptos_router = { path = "../../router", default-features = false }
log = "0.4.17"
simple_logger = "4"
tokio = { version = "1.28.1", optional = true }

View File

@@ -6,6 +6,6 @@ codegen-units = 1
lto = true
[patch.crates-io]
leptos = { path = "../../leptos", features = ["nightly"] }
leptos_router = { path = "../../router", features = ["nightly"] }
leptos = { path = "../../leptos" }
leptos_router = { path = "../../router" }
api-boundary = { path = "api-boundary" }

View File

@@ -7,9 +7,8 @@ publish = false
[dependencies]
api-boundary = "*"
leptos = { path = "../../../leptos", features = ["csr"] }
leptos_meta = { path = "../../../meta", features = ["csr"] }
leptos_router = { path = "../../../router", features = ["csr"] }
leptos = { version = "0.2.0-alpha2", features = ["stable"] }
leptos_router = { version = "0.2.0-alpha2", features = ["stable", "csr"] }
log = "0.4"
console_error_panic_hook = "0.1"

View File

@@ -8,7 +8,7 @@ codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr", "nightly"] }
leptos = { path = "../../leptos" }
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"

View File

@@ -45,7 +45,7 @@ pub fn App(cx: Scope) -> impl IntoView {
// Button B: pass a closure
<ButtonB on_click=move |_| set_right.update(|value| *value = !*value)/>
// Button C: use a regular event listener
// Button B: use a regular event listener
// setting an event listener on a component like this applies it
// to each of the top-level elements the component returns
<ButtonC on:click=move |_| set_italics.update(|value| *value = !*value)/>

View File

@@ -1,21 +0,0 @@
Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# Support playwright testing
node_modules/
test-results/
end2end/playwright-report/
playwright/.cache/
pnpm-lock.yaml
# Support trunk
dist

View File

@@ -10,12 +10,12 @@ lto = true
[dependencies]
console_log = "1"
log = "0.4"
leptos = { path = "../../leptos", features = ["csr", "nightly"] }
leptos_router = { path = "../../router", features = ["csr", "nightly"] }
leptos = { path = "../../leptos" }
leptos_router = { path = "../../router", features = ["csr"] }
serde = { version = "1", features = ["derive"] }
futures = "0.3"
console_error_panic_hook = "0.1.7"
leptos_meta = { path = "../../meta", features = ["csr", "nightly"] }
leptos_meta = { path = "../../meta", features = ["csr"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.0"

View File

@@ -1,8 +1,4 @@
extend = [
{ path = "../cargo-make/main.toml" },
{ path = "../cargo-make/trunk_server.toml" },
{ path = "../cargo-make/playwright-test.toml" },
]
extend = { path = "../cargo-make/main.toml" }
[tasks.build]
command = "cargo"

View File

@@ -1,36 +0,0 @@
{
"name": "e2e",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@playwright/test": {
"version": "1.35.1",
"dev": true,
"requires": {
"@types/node": "*",
"fsevents": "2.3.2",
"playwright-core": "1.35.1"
}
},
"@types/node": {
"version": "20.3.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.2.tgz",
"integrity": "sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==",
"dev": true
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
},
"playwright-core": {
"version": "1.35.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.35.1.tgz",
"integrity": "sha512-pNXb6CQ7OqmGDRspEjlxE49w+4YtR6a3X6mT1hZXeJHWmsEz7SunmvZeiG/+y1yyMZdHnnn73WKYdtV1er0Xyg==",
"dev": true
}
}
}

View File

@@ -1,7 +0,0 @@
{
"private": "true",
"scripts": {},
"devDependencies": {
"@playwright/test": "^1.35.1"
}
}

View File

@@ -1,77 +0,0 @@
import { defineConfig, devices } from "@playwright/test";
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./tests",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 10 : 10,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "list",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://127.0.0.1:8080",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
// {
// name: "firefox",
// use: { ...devices["Desktop Firefox"] },
// },
// {
// name: "webkit",
// use: { ...devices["Desktop Safari"] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ..devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
// webServer: {
// command: "cd ../ && trunk serve",
// url: "http://127.0.0.1:8080",
// reuseExistingServer: false, //!process.env.CI,
// },
});

View File

@@ -1,30 +0,0 @@
import { test, expect } from "@playwright/test";
test.describe("Test Router example", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/");
});
const links = [
{ label: "Bill Smith", url: "/0" },
{ label: "Tim Jones", url: "/1" },
{ label: "Sally Stevens", url: "/2" },
{ label: "About", url: "/about" },
{ label: "Settings", url: "/settings" },
];
links.forEach(({ label, url }) => {
test(`Can navigate to ${label}`, async ({ page }) => {
await page.getByRole("link", { name: label }).click();
await expect(page.getByRole("heading", { name: label })).toBeVisible();
await expect(page).toHaveURL(url);
});
});
test("Can redirect to home", async ({ page }) => {
await page.getByRole("link", { name: "About" }).click();
await page.getByRole("link", { name: "Redirect to Home" }).click();
await expect(page).toHaveURL("/");
});
});

View File

@@ -1,11 +0,0 @@
{
"private": true,
"scripts": {
"start-server": "trunk serve",
"e2e": "cargo make test-playwright",
"e2e:auto-start": "start-server-and-test start-server http://127.0.0.1:8080 e2e"
},
"devDependencies": {
"start-server-and-test": "^2.0.0"
}
}

View File

@@ -13,10 +13,12 @@ rand = { version = "0.8.5", features = ["min_const_gen"], optional = true }
console_error_panic_hook = "0.1.7"
futures = "0.3.25"
cfg-if = "1.0.0"
leptos = { path = "../../leptos", features = ["nightly"] }
leptos_meta = { path = "../../meta", features = ["nightly"] }
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_meta = { path = "../../meta", default-features = false }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_router = { path = "../../router", features = ["nightly"] }
leptos_router = { path = "../../router", default-features = false }
log = "0.4.17"
simple_logger = "4.0.0"
serde = { version = "1.0.148", features = ["derive"] }

View File

@@ -8,7 +8,7 @@ codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr", "nightly"] }
leptos = { path = "../../leptos" }
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"

View File

@@ -13,10 +13,12 @@ console_error_panic_hook = "0.1"
console_log = "1"
cfg-if = "1"
lazy_static = "1"
leptos = { path = "../../leptos", features = ["nightly"] }
leptos_meta = { path = "../../meta", features = ["nightly"] }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_router = { path = "../../router", features = ["nightly"] }
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_meta = { path = "../../meta", default-features = false }
leptos_actix = { path = "../../integrations/actix", default-features = false, optional = true }
leptos_router = { path = "../../router", default-features = false }
log = "0.4"
serde = { version = "1", features = ["derive"] }
thiserror = "1"

View File

@@ -11,10 +11,12 @@ console_error_panic_hook = "0.1"
console_log = "1"
cfg-if = "1"
lazy_static = "1"
leptos = { path = "../../leptos", features = ["nightly"] }
leptos_meta = { path = "../../meta", features = ["nightly"] }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_router = { path = "../../router", features = ["nightly"] }
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_meta = { path = "../../meta", default-features = false }
leptos_axum = { path = "../../integrations/axum", default-features = false, optional = true }
leptos_router = { path = "../../router", default-features = false }
log = "0.4"
serde = { version = "1", features = ["derive"] }
thiserror = "1"

View File

@@ -10,10 +10,12 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
leptos = { path = "../../leptos", features = ["nightly"] }
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_meta = { path = "../../meta", features = ["nightly"] }
leptos_router = { path = "../../router", features = ["nightly"] }
leptos_meta = { path = "../../meta", default-features = false }
leptos_router = { path = "../../router", default-features = false }
gloo-net = { version = "0.2", features = ["http"] }
log = "0.4"
cfg-if = "1.0"

View File

@@ -4,9 +4,12 @@ version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../leptos", features = ["csr", "nightly"] }
leptos_meta = { path = "../../meta", features = ["csr", "nightly"] }
leptos_router = { path = "../../router", features = ["csr", "nightly"] }
leptos = { version = "0.2", features = [
"serde",
"csr",
] }
leptos_meta = { version = "0.2", features = ["csr"] }
leptos_router = { version = "0.2", features = ["csr"] }
log = "0.4"
gloo-net = { version = "0.2", features = ["http"] }

View File

@@ -52,9 +52,8 @@ If you're using VS Code, add the following to your `settings.json`
Install [Tailwind CSS Intellisense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss).
Install [VS Browser](https://marketplace.visualstudio.com/items?itemName=Phu1237.vs-browser) extension (allows you to open a browser at the right window.
Allow vscode Ports forward: 3000, 3001.
Install "VS Browser" extension, a browser at the right window.
Allow vscode Ports forward: 3000, 3001.
## Notes about Tooling

View File

@@ -8,7 +8,7 @@ codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr", "nightly"] }
leptos = { path = "../../leptos" }
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"

View File

@@ -16,10 +16,12 @@ console_error_panic_hook = "0.1.7"
serde = { version = "1.0.152", features = ["derive"] }
futures = "0.3.25"
cfg-if = "1.0.0"
leptos = { path = "../../leptos", features = ["nightly"] }
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_meta = { path = "../../meta", features = ["nightly"] }
leptos_router = { path = "../../router", features = ["nightly"] }
leptos_meta = { path = "../../meta", default-features = false }
leptos_router = { path = "../../router", default-features = false }
log = "0.4.17"
simple_logger = "4.0.0"
gloo = { git = "https://github.com/rustwasm/gloo" }
@@ -30,6 +32,7 @@ sqlx = { version = "0.6.2", features = [
wasm-bindgen = "0.2"
[features]
default = ["ssr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:actix-files",

View File

@@ -11,10 +11,12 @@ console_log = "1.0.0"
console_error_panic_hook = "0.1.7"
futures = "0.3.25"
cfg-if = "1.0.0"
leptos = { path = "../../leptos", features = ["nightly"] }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_meta = { path = "../../meta", features = ["nightly"] }
leptos_router = { path = "../../router", features = ["nightly"] }
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_axum = { path = "../../integrations/axum", default-features = false, optional = true }
leptos_meta = { path = "../../meta", default-features = false }
leptos_router = { path = "../../router", default-features = false }
log = "0.4.17"
simple_logger = "4.0.0"
serde = { version = "1", features = ["derive"] }
@@ -31,6 +33,8 @@ thiserror = "1.0.38"
wasm-bindgen = "0.2"
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:axum",

View File

@@ -11,11 +11,13 @@ console_log = "1.0.0"
console_error_panic_hook = "0.1.7"
futures = "0.3.25"
cfg-if = "1.0.0"
leptos = { path = "../../leptos" }
leptos_viz = { path = "../../integrations/viz", optional = true }
leptos_meta = { path = "../../meta", features = ["nightly"] }
leptos_router = { path = "../../router", features = ["nightly"] }
leptos_reactive = { path = "../../leptos_reactive", features = ["nightly"] }
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_viz = { path = "../../integrations/viz", default-features = false, optional = true }
leptos_meta = { path = "../../meta", default-features = false }
leptos_router = { path = "../../router", default-features = false }
leptos_reactive = { path = "../../leptos_reactive", default-features = false }
log = "0.4.17"
simple_logger = "4.0.0"
serde = { version = "1", features = ["derive"] }
@@ -30,6 +32,7 @@ thiserror = "1.0.38"
wasm-bindgen = "0.2"
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [

View File

@@ -8,7 +8,7 @@ codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["nightly"] }
leptos = { path = "../../leptos", default-features = false }
log = "0.4"
console_log = "1"
console_error_panic_hook = "0.1.7"

View File

@@ -13,19 +13,19 @@ cfg-if = "1"
leptos_dom = { workspace = true }
leptos_macro = { workspace = true }
leptos_reactive = { workspace = true }
leptos_server = { workspace = true}
leptos_server = { workspace = true, default-features = false }
leptos_config = { workspace = true }
tracing = "0.1"
typed-builder = "0.14"
server_fn = { workspace = true}
server_fn = { workspace = true, default-features = false }
web-sys = { version = "0.3.63", optional = true }
wasm-bindgen = { version = "0.2", optional = true }
[dev-dependencies]
leptos = { path = "."}
leptos = { path = ".", default-features = false }
[features]
default = ["serde"]
default = ["csr", "serde"]
template_macro = ["leptos_dom/web", "web-sys", "wasm-bindgen"]
csr = [
"leptos_dom/web",
@@ -47,11 +47,11 @@ ssr = [
"leptos_reactive/ssr",
"leptos_server/ssr",
]
nightly = [
"leptos_dom/nightly",
"leptos_macro/nightly",
"leptos_reactive/nightly",
"leptos_server/nightly",
stable = [
"leptos_dom/stable",
"leptos_macro/stable",
"leptos_reactive/stable",
"leptos_server/stable",
]
serde = ["leptos_reactive/serde"]
serde-lite = ["leptos_reactive/serde-lite"]
@@ -60,7 +60,7 @@ rkyv = ["leptos_reactive/rkyv"]
tracing = ["leptos_macro/tracing"]
[package.metadata.cargo-all-features]
denylist = ["nightly", "tracing", "template_macro", "rustls", "default-tls", "web-sys", "wasm-bindgen"]
denylist = ["stable", "tracing", "template_macro", "rustls", "default-tls", "web-sys", "wasm-bindgen"]
skip_feature_sets = [
[
"csr",

View File

@@ -16,14 +16,14 @@ use leptos_reactive::{
/// # use leptos_dom::*; use leptos::*;
/// # run_scope(create_runtime(), |cx| {
/// let (value, set_value) = create_signal(cx, Ok(0));
/// let on_input = move |ev| set_value.set(event_target_value(&ev).parse::<i32>());
/// let on_input = move |ev| set_value(event_target_value(&ev).parse::<i32>());
///
/// view! { cx,
/// <input type="text" on:input=on_input/>
/// <ErrorBoundary
/// fallback=move |_, _| view! { cx, <p class="error">"Enter a valid number."</p>}
/// >
/// <p>"Value is: " {move || value.get()}</p>
/// <p>"Value is: " {value}</p>
/// </ErrorBoundary>
/// }
/// # });

View File

@@ -26,7 +26,7 @@ use std::hash::Hash;
/// <div>
/// <For
/// // a function that returns the items we're iterating over; a signal is fine
/// each=move || counters.get()
/// each=counters
/// // a unique key for each item
/// key=|counter| counter.id
/// // renders each item to a view

View File

@@ -16,6 +16,14 @@
//! Join us on our [Discord Channel](https://discord.gg/v38Eef6sWG) to see what the community is building.
//! Explore our [Examples](https://github.com/leptos-rs/leptos/tree/main/examples) to see Leptos in action.
//!
//! # `nightly` Note
//! Most of the examples assume youre using `nightly` Rust. If youre on stable, note the following:
//! 1. You need to enable the `"stable"` flag in `Cargo.toml`: `leptos = { version = "0.0", features = ["stable"] }`
//! 2. `nightly` enables the function call syntax for accessing and setting signals. If youre using `stable`,
//! youll just call `.get()`, `.set()`, or `.update()` manually. Check out the
//! [`counters_stable` example](https://github.com/leptos-rs/leptos/blob/main/examples/counters_stable/src/main.rs)
//! for examples of the correct API.
//!
//! # Learning by Example
//!
//! If you want to see what Leptos is capable of, check out
@@ -76,10 +84,12 @@
//! - **Server Functions**: the [server](crate::leptos_server) macro, [create_action], and [create_server_action]
//!
//! # Feature Flags
//! - `nightly`: On `nightly` Rust, enables the function-call syntax for signal getters and setters.
//! - `csr` Client-side rendering: Generate DOM nodes in the browser
//! - `csr` (*Default*) Client-side rendering: Generate DOM nodes in the browser
//! - `ssr` Server-side rendering: Generate an HTML string (typically on the server)
//! - `hydrate` Hydration: use this to add interactivity to an SSRed Leptos app
//! - `stable` By default, Leptos requires `nightly` Rust, which is what allows the ergonomics
//! of calling signals as functions. If you need to use `stable`, you will need to call `.get()`
//! and `.set()` manually.
//! - `serde` (*Default*) In SSR/hydrate mode, uses [serde](https://docs.rs/serde/latest/serde/) to serialize resources and send them
//! from the server to the client.
//! - `serde-lite` In SSR/hydrate mode, uses [serde-lite](https://docs.rs/serde-lite/latest/serde_lite/) to serialize resources and send them
@@ -112,7 +122,7 @@
//! <div>
//! <button on:click=clear>"Clear"</button>
//! <button on:click=decrement>"-1"</button>
//! <span>"Value: " {move || value.get().to_string()} "!"</span>
//! <span>"Value: " {move || value().to_string()} "!"</span>
//! <button on:click=increment>"+1"</button>
//! </div>
//! }

View File

@@ -21,7 +21,7 @@ use std::{cell::RefCell, rc::Rc};
///
/// view! { cx,
/// <Show
/// when=move || value.get() < 5
/// when=move || value() < 5
/// fallback=|cx| view! { cx, "Big number!" }
/// >
/// "Small number!"

View File

@@ -21,7 +21,7 @@ use std::rc::Rc;
///
/// let (cat_count, set_cat_count) = create_signal::<u32>(cx, 1);
///
/// let cats = create_resource(cx, move || cat_count.get(), |count| fetch_cats(count));
/// let cats = create_resource(cx, cat_count, |count| fetch_cats(count));
///
/// view! { cx,
/// <div>
@@ -73,7 +73,7 @@ where
let child = DynChild::new({
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
let current_id = current_id;
let current_id = current_id.clone();
let children = Rc::new(orig_children(cx).into_view(cx));
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
@@ -94,10 +94,10 @@ where
// run the child; we'll probably throw this away, but it will register resource reads
//let after_original_child = HydrationCtx::peek();
{
let initial = {
// no resources were read under this, so just return the child
if context.pending_resources.get() == 0 {
HydrationCtx::continue_from(current_id);
HydrationCtx::continue_from(current_id.clone());
DynChild::new({
let children = Rc::clone(&children);
move || (*children).clone()
@@ -115,7 +115,9 @@ where
{
let orig_children = Rc::clone(&orig_children);
move || {
HydrationCtx::continue_from(current_id);
HydrationCtx::continue_from(
current_id.clone(),
);
DynChild::new({
let orig_children =
orig_children(cx).into_view(cx);
@@ -130,7 +132,9 @@ where
{
let orig_children = Rc::clone(&orig_children);
move || {
HydrationCtx::continue_from(current_id);
HydrationCtx::continue_from(
current_id.clone(),
);
DynChild::new({
let orig_children =
orig_children(cx).into_view(cx);
@@ -145,7 +149,9 @@ where
// return the fallback for now, wrapped in fragment identifier
fallback().into_view(cx)
}
}
};
initial
}
}
})

View File

@@ -32,8 +32,7 @@ use std::{
/// let (cat_count, set_cat_count) = create_signal::<u32>(cx, 1);
/// let (pending, set_pending) = create_signal(cx, false);
///
/// let cats =
/// create_resource(cx, move || cat_count.get(), |count| fetch_cats(count));
/// let cats = create_resource(cx, cat_count, |count| fetch_cats(count));
///
/// view! { cx,
/// <div>
@@ -65,7 +64,7 @@ use std::{
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "info", skip_all)
)]
#[component(transparent)]
#[component]
pub fn Transition<F, E>(
cx: Scope,
/// Will be displayed while resources are pending.

View File

@@ -12,9 +12,9 @@ actix-web = { version = "4", optional = true, features = ["macros"] }
console_error_panic_hook = "0.1"
console_log = "1"
cfg-if = "1"
leptos = { path = "../../..", features = ["serde"] }
leptos = { path = "../../..", default-features = false, features = ["serde"] }
leptos_actix = { path = "../../../../integrations/actix", optional = true }
leptos_router = { path = "../../../../router"}
leptos_router = { path = "../../../../router", default-features = false }
log = "0.4"
simple_logger = "4"
wasm-bindgen = "0.2.87"

View File

@@ -153,30 +153,16 @@ impl TryFrom<String> for Env {
/// If an env var is specified, like `LEPTOS_ENV`, it will override a setting in the file.
pub fn get_config_from_str(text: &str) -> Result<ConfFile, LeptosConfigError> {
let re: Regex = Regex::new(r#"(?m)^\[package.metadata.leptos\]"#).unwrap();
let re_workspace: Regex =
Regex::new(r#"(?m)^\[\[workspace.metadata.leptos\]\]"#).unwrap();
let metadata_name;
let start;
match re.find(text) {
Some(found) => {
metadata_name = "[package.metadata.leptos]";
start = found.start();
}
None => match re_workspace.find(text) {
Some(found) => {
metadata_name = "[[workspace.metadata.leptos]]";
start = found.start();
}
None => return Err(LeptosConfigError::ConfigSectionNotFound),
},
let start = match re.find(text) {
Some(found) => found.start(),
None => return Err(LeptosConfigError::ConfigSectionNotFound),
};
// so that serde error messages have right line number
let newlines = text[..start].matches('\n').count();
let input = "\n".repeat(newlines) + &text[start..];
let toml = input
.replace(metadata_name, "[leptos_options]")
.replace("[package.metadata.leptos]", "[leptos_options]")
.replace('-', "_");
let settings = Config::builder()
// Read the "default" configuration file

View File

@@ -159,8 +159,8 @@ features = [
default = []
web = ["leptos_reactive/csr"]
ssr = ["leptos_reactive/ssr"]
nightly = ["leptos_reactive/nightly"]
stable = ["leptos_reactive/stable"]
[package.metadata.cargo-all-features]
denylist = ["nightly"]
denylist = ["stable"]
skip_feature_sets = [["web", "ssr"]]

View File

@@ -7,7 +7,7 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
leptos = { path = "../../../leptos", features = ["nightly"] }
leptos = { path = "../../../leptos", default-features = false }
actix-web = { version = "4", optional = true }
actix-files = { version = "0.6", optional = true }
wasm-bindgen = { version = "0.2", optional = true }

View File

@@ -6,7 +6,7 @@ edition = "2021"
[dependencies]
console_error_panic_hook = "0.1"
gloo = { version = "0.8", features = ["futures"] }
leptos = { path = "../../../leptos", features = ["nightly", "csr", "tracing"] }
leptos = { path = "../../../leptos", features = ["tracing"] }
tracing = "0.1"
tracing-subscriber = "0.3"
tracing-subscriber-wasm = "0.1"

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../leptos", features = ["csr", "nightly"] }
leptos = { path = "../../leptos" }
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"

View File

@@ -1,7 +1,7 @@
#![deny(missing_docs)]
#![forbid(unsafe_code)]
#![cfg_attr(feature = "nightly", feature(fn_traits))]
#![cfg_attr(feature = "nightly", feature(unboxed_closures))]
#![cfg_attr(not(feature = "stable"), feature(fn_traits))]
#![cfg_attr(not(feature = "stable"), feature(unboxed_closures))]
//! The DOM implementation for `leptos`.
@@ -33,7 +33,7 @@ pub use html::HtmlElement;
use html::{AnyElement, ElementDescriptor};
pub use hydration::{HydrationCtx, HydrationKey};
use leptos_reactive::Scope;
#[cfg(not(feature = "nightly"))]
#[cfg(feature = "stable")]
use leptos_reactive::{
MaybeSignal, Memo, ReadSignal, RwSignal, Signal, SignalGet,
};
@@ -143,7 +143,7 @@ where
}
}
#[cfg(not(feature = "nightly"))]
#[cfg(feature = "stable")]
impl<T> IntoView for ReadSignal<T>
where
T: IntoView + Clone,
@@ -156,7 +156,7 @@ where
DynChild::new(move || self.get()).into_view(cx)
}
}
#[cfg(not(feature = "nightly"))]
#[cfg(feature = "stable")]
impl<T> IntoView for RwSignal<T>
where
T: IntoView + Clone,
@@ -169,7 +169,7 @@ where
DynChild::new(move || self.get()).into_view(cx)
}
}
#[cfg(not(feature = "nightly"))]
#[cfg(feature = "stable")]
impl<T> IntoView for Memo<T>
where
T: IntoView + Clone,
@@ -182,7 +182,7 @@ where
DynChild::new(move || self.get()).into_view(cx)
}
}
#[cfg(not(feature = "nightly"))]
#[cfg(feature = "stable")]
impl<T> IntoView for Signal<T>
where
T: IntoView + Clone,
@@ -195,7 +195,7 @@ where
DynChild::new(move || self.get()).into_view(cx)
}
}
#[cfg(not(feature = "nightly"))]
#[cfg(feature = "stable")]
impl<T> IntoView for MaybeSignal<T>
where
T: IntoView + Clone,
@@ -1125,7 +1125,7 @@ viewable_primitive![
];
cfg_if! {
if #[cfg(feature = "nightly")] {
if #[cfg(not(feature = "stable"))] {
viewable_primitive! {
std::backtrace::Backtrace
}

View File

@@ -154,7 +154,7 @@ impl<T: ElementDescriptor> Clone for NodeRef<T> {
impl<T: ElementDescriptor + 'static> Copy for NodeRef<T> {}
cfg_if::cfg_if! {
if #[cfg(feature = "nightly")] {
if #[cfg(not(feature = "stable"))] {
impl<T: Clone + ElementDescriptor + 'static> FnOnce<()> for NodeRef<T> {
type Output = Option<HtmlElement<T>>;

View File

@@ -296,7 +296,7 @@ fn ooo_body_stream_recurse(
fragments.chain(resources).chain(
futures::stream::once(async move {
let pending = cx.pending_fragments();
if !pending.is_empty() {
if pending.len() > 0 {
let fragments = FuturesUnordered::new();
let serializers = cx.serialization_resolvers();
for (fragment_id, data) in pending {

View File

@@ -70,7 +70,7 @@ impl ViewMacros {
let mut views = Vec::new();
for view in visitor.views {
let span = view.span();
let id = span_to_stable_id(path, span.start().line);
let id = span_to_stable_id(path, span);
let mut tokens = view.tokens.clone().into_iter();
tokens.next(); // cx
tokens.next(); // ,
@@ -148,11 +148,15 @@ impl<'ast> Visit<'ast> for ViewMacroVisitor<'ast> {
}
}
pub fn span_to_stable_id(path: impl AsRef<Path>, line: usize) -> String {
pub fn span_to_stable_id(
path: impl AsRef<Path>,
site: proc_macro2::Span,
) -> String {
let file = path
.as_ref()
.to_str()
.unwrap_or_default()
.replace(['/', '\\'], "-");
format!("{file}-{line}")
let start = site.start();
format!("{}-{:?}", file, start.line)
}

View File

@@ -3,7 +3,7 @@ function patch(json) {
try {
const views = JSON.parse(json);
for (const [id, patches] of views) {
console.log("[HOT RELOAD]", id, patches);
console.log("[HOT RELOAD]", patches);
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT),
open = `leptos-view|${id}|open`,
close = `leptos-view|${id}|close`;

View File

@@ -39,9 +39,9 @@ default = ["ssr"]
csr = []
hydrate = []
ssr = []
nightly = ["server_fn_macro/nightly"]
stable = ["server_fn_macro/stable"]
tracing = []
[package.metadata.cargo-all-features]
denylist = ["nightly", "tracing"]
denylist = ["stable", "tracing"]
skip_feature_sets = [["csr", "hydrate"], ["hydrate", "csr"], ["hydrate", "ssr"]]

View File

@@ -438,18 +438,15 @@ impl Docs {
let mut attrs = attrs
.iter()
.filter_map(|attr| {
let Meta::NameValue(attr) = &attr.meta else {
return None;
let Meta::NameValue(attr ) = &attr.meta else {
return None
};
if !attr.path.is_ident("doc") {
return None;
return None
}
let Some(val) = value_to_string(&attr.value) else {
abort!(
attr,
"expected string literal in value of doc comment"
);
abort!(attr, "expected string literal in value of doc comment");
};
Some((val, attr.path.span()))

View File

@@ -1,4 +1,4 @@
#![cfg_attr(feature = "nightly", feature(proc_macro_span))]
#![cfg_attr(not(feature = "stable"), feature(proc_macro_span))]
#![forbid(unsafe_code)]
#[macro_use]
@@ -94,7 +94,7 @@ mod template;
/// Attributes can take a wide variety of primitive types that can be converted to strings. They can also
/// take an `Option`, in which case `Some` sets the attribute and `None` removes the attribute.
///
/// ```rust,ignore
/// ```rust
/// # use leptos::*;
/// # run_scope(create_runtime(), |cx| {
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
@@ -102,11 +102,11 @@ mod template;
///
/// view! {
/// cx,
/// // ❌ not like this: `count.get()` returns an `i32`, not a function
/// <p>{count.get()}</p>
/// // ❌ not like this: `count()` returns an `i32`, not a function
/// <p>{count()}</p>
/// // ✅ this is good: Leptos sees the function and knows it's a dynamic value
/// <p>{move || count.get()}</p>
/// // 🔥 with the `nightly` feature, `count` is a function, so `count` itself can be passed directly into the view
/// // 🔥 `count` is itself a function, so you can pass it directly (unless you're on `stable`)
/// <p>{count}</p>
/// }
/// # ;
@@ -147,9 +147,9 @@ mod template;
/// <input
/// type="text"
/// name="user_name"
/// value={move || name.get()} // this only sets the default value!
/// prop:value={move || name.get()} // here's how you update values. Sorry, I didnt invent the DOM.
/// on:click=move |ev| set_name.set(event_target_value(&ev)) // `event_target_value` is a useful little Leptos helper
/// value={name} // this only sets the default value!
/// prop:value={name} // here's how you update values. Sorry, I didnt invent the DOM.
/// on:click=move |ev| set_name(event_target_value(&ev)) // `event_target_value` is a useful little Leptos helper
/// />
/// }
/// # ;
@@ -163,7 +163,7 @@ mod template;
/// # run_scope(create_runtime(), |cx| {
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
/// let (count, set_count) = create_signal(cx, 2);
/// view! { cx, <div class:hidden-div={move || count.get() < 3}>"Now you see me, now you dont."</div> }
/// view! { cx, <div class:hidden-div={move || count() < 3}>"Now you see me, now you dont."</div> }
/// # ;
/// # }
/// # });
@@ -176,7 +176,7 @@ mod template;
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
/// let (count, set_count) = create_signal(cx, 2);
/// // `hidden-div-25` is invalid at the moment
/// view! { cx, <div class:hidden-div-25={move || count.get() < 3}>"Now you see me, now you dont."</div> }
/// view! { cx, <div class:hidden-div-25={move || count() < 3}>"Now you see me, now you dont."</div> }
/// # ;
/// # }
/// # });
@@ -191,7 +191,7 @@ mod template;
/// // this allows you to use CSS frameworks that include complex class names
/// view! { cx,
/// <div
/// class=("is-[this_-_really]-necessary-42", move || count.get() < 3)
/// class=("is-[this_-_really]-necessary-42", move || count() < 3)
/// >
/// "Now you see me, now you dont."
/// </div>
@@ -211,9 +211,9 @@ mod template;
/// view! { cx,
/// <div
/// style="position: absolute"
/// style:left=move || format!("{}px", x.get())
/// style:top=move || format!("{}px", y.get())
/// style=("background-color", move || format!("rgb({}, {}, 100)", x.get(), y.get()))
/// style:left=move || format!("{}px", x())
/// style:top=move || format!("{}px", y())
/// style=("background-color", move || format!("rgb({}, {}, 100)", x(), y()))
/// >
/// "Moves when coordinates change"
/// </div>
@@ -285,7 +285,7 @@ mod template;
///
/// // create event handlers for our buttons
/// // note that `value` and `set_value` are `Copy`, so it's super easy to move them into closures
/// let clear = move |_ev| set_value.set(0);
/// let clear = move |_ev| set_value(0);
/// let decrement = move |_ev| set_value.update(|value| *value -= 1);
/// let increment = move |_ev| set_value.update(|value| *value += 1);
///
@@ -295,7 +295,7 @@ mod template;
/// <div>
/// <button on:click=clear>"Clear"</button>
/// <button on:click=decrement>"-1"</button>
/// <span>"Value: " {move || value.get().to_string()} "!"</span>
/// <span>"Value: " {move || value().to_string()} "!"</span>
/// <button on:click=increment>"+1"</button>
/// </div>
/// }
@@ -381,10 +381,10 @@ pub fn view(tokens: TokenStream) -> TokenStream {
fn normalized_call_site(site: proc_macro::Span) -> Option<String> {
cfg_if::cfg_if! {
if #[cfg(all(debug_assertions, feature = "nightly"))] {
if #[cfg(all(debug_assertions, not(feature = "stable")))] {
Some(leptos_hot_reload::span_to_stable_id(
site.source_file().path(),
site.start().line()
site.into()
))
} else {
_ = site;
@@ -467,7 +467,7 @@ pub fn template(tokens: TokenStream) -> TokenStream {
/// // return the user interface, which will be automatically updated
/// // when signal values change
/// view! { cx,
/// <p>"Your name is " {name} " and you are " {move || age.get()} " years old."</p>
/// <p>"Your name is " {name} " and you are " {age} " years old."</p>
/// }
/// }
///
@@ -793,18 +793,15 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
/// If you call a server function from the client (i.e., when the `csr` or `hydrate` features
/// are enabled), it will instead make a network request to the server.
///
/// You can specify one, two, three, or four arguments to the server function:
/// You can specify one, two, or three arguments to the server function:
/// 1. **Required**: A type name that will be used to identify and register the server function
/// (e.g., `MyServerFn`).
/// 2. *Optional*: A URL prefix at which the function will be mounted when its registered
/// (e.g., `"/api"`). Defaults to `"/"`.
/// 3. *Optional*: The encoding for the server function (`"Url"`, `"Cbor"`, `"GetJson"`, or `"GetCbor`". See **Server Function Encodings** below.)
/// 4. *Optional*: A specific endpoint path to be used in the URL. (By default, a unique path will be generated.)
///
/// ```rust,ignore
/// // will generate a server function at `/api-prefix/hello`
/// #[server(MyServerFnType, "/api-prefix", "Url", "hello")]
/// ```
/// 3. *Optional*: either `"Cbor"` (specifying that it should use the binary `cbor` format for
/// serialization) or `"Url"` (specifying that it should be use a URL-encoded form-data string).
/// Defaults to `"Url"`. If you want to use this server function to power a `<form>` that will
/// work without WebAssembly, the encoding must be `"Url"`.
///
/// The server function itself can take any number of arguments, each of which should be serializable
/// and deserializable with `serde`. Optionally, its first argument can be a Leptos
@@ -824,16 +821,17 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
/// ```
///
/// Note the following:
/// - You must **register** the server function by calling `T::register()` somewhere in your main function.
/// - **Server functions must be `async`.** Even if the work being done inside the function body
/// can run synchronously on the server, from the clients perspective it involves an asynchronous
/// function call.
/// - **Server functions must return `Result<T, ServerFnError>`.** Even if the work being done
/// inside the function body cant fail, the processes of serialization/deserialization and the
/// network call are fallible.
/// - **Return types must implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html).**
/// - **Return types must be [Serializable](https://docs.rs/leptos/latest/leptos/trait.Serializable.html).**
/// This should be fairly obvious: we have to serialize arguments to send them to the server, and we
/// need to deserialize the result to return it to the client.
/// - **Arguments must implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html)
/// - **Arguments must be implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html)
/// and [`DeserializeOwned`](https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html).**
/// They are serialized as an `application/x-www-form-urlencoded`
/// form data using [`serde_qs`](https://docs.rs/serde_qs/latest/serde_qs/) or as `application/cbor`
@@ -842,9 +840,6 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
/// - **The `Scope` comes from the server.** Optionally, the first argument of a server function
/// can be a Leptos `Scope`. This scope can be used to inject dependencies like the HTTP request
/// or response or other server-only dependencies, but it does *not* have access to reactive state that exists in the client.
/// - Your server must be ready to handle the server functions at the API prefix you list. The easiest way to do this
/// is to use the `handle_server_fns` function from [`leptos_actix`](https://docs.rs/leptos_actix/latest/leptos_actix/fn.handle_server_fns.html)
/// or [`leptos_axum`](https://docs.rs/leptos_axum/latest/leptos_axum/fn.handle_server_fns.html).
///
/// ## Server Function Encodings
///

View File

@@ -581,14 +581,7 @@ fn attribute_to_tokens_ssr<'a>(
|| name.strip_prefix("style:").is_some()
{
// ignore props for SSR
// ignore classes and styles: we'll handle these separately
if name.starts_with("prop:") {
let value = attr.value();
exprs_for_compiler.push(quote! {
#[allow(unused_braces)]
{ _ = #value; }
});
}
// ignore classes and sdtyles: we'll handle these separately
} else if name == "inner_html" {
return attr.value();
} else {
@@ -613,9 +606,7 @@ fn attribute_to_tokens_ssr<'a>(
if let Some(value) = value_to_string(value) {
template.push_str(&name);
template.push_str("=\"");
template.push_str(&html_escape::encode_quoted_attribute(
&value,
));
template.push_str(&value);
template.push('"');
} else {
template.push_str("{}");
@@ -738,9 +729,7 @@ fn set_class_attribute_ssr(
{
template.push_str(" class=\"");
template.push_str(&html_escape::encode_quoted_attribute(
&static_class_attr,
));
template.push_str(&static_class_attr);
for (_span, value) in dyn_class_attr {
if let Some(value) = value {
@@ -1374,8 +1363,8 @@ pub(crate) fn parse_event_name(name: &str) -> (TokenStream, bool, bool) {
let is_custom = event_type == "Custom";
let Ok(event_type) = event_type.parse::<TokenStream>() else {
abort!(event_type, "couldn't parse event name");
};
abort!(event_type, "couldn't parse event name");
};
let event_type = if is_custom {
quote! { Custom::new(#name) }
@@ -1404,10 +1393,7 @@ pub(crate) fn slot_to_tokens(
let span = node.name().span();
let Some(parent_slots) = parent_slots else {
proc_macro_error::emit_error!(
span,
"slots cannot be used inside HTML elements"
);
proc_macro_error::emit_error!(span, "slots cannot be used inside HTML elements");
return;
};

View File

@@ -68,14 +68,14 @@ hydrate = [
"dep:web-sys",
]
ssr = ["dep:tokio"]
nightly = []
stable = []
serde = []
serde-lite = ["dep:serde-lite"]
miniserde = ["dep:miniserde"]
rkyv = ["dep:rkyv", "dep:bytecheck"]
[package.metadata.cargo-all-features]
denylist = ["nightly"]
denylist = ["stable"]
skip_feature_sets = [
[
"csr",

View File

@@ -29,20 +29,20 @@ use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc};
/// // ✅ use effects to interact between reactive state and the outside world
/// create_effect(cx, move |_| {
/// // immediately prints "Value: 0" and subscribes to `a`
/// log::debug!("Value: {}", a.get());
/// log::debug!("Value: {}", a());
/// });
///
/// set_a.set(1);
/// set_a(1);
/// // ✅ because it's subscribed to `a`, the effect reruns and prints "Value: 1"
///
/// // ❌ don't use effects to synchronize state within the reactive system
/// create_effect(cx, move |_| {
/// // this technically works but can cause unnecessary re-renders
/// // and easily lead to problems like infinite loops
/// set_b.set(a.get() + 1);
/// set_b(a() + 1);
/// });
/// # if !cfg!(feature = "ssr") {
/// # assert_eq!(b.get(), 2);
/// # assert_eq!(b(), 2);
/// # }
/// # }).dispose();
/// ```
@@ -88,19 +88,19 @@ where
/// // ✅ use effects to interact between reactive state and the outside world
/// create_isomorphic_effect(cx, move |_| {
/// // immediately prints "Value: 0" and subscribes to `a`
/// log::debug!("Value: {}", a.get());
/// log::debug!("Value: {}", a());
/// });
///
/// set_a.set(1);
/// set_a(1);
/// // ✅ because it's subscribed to `a`, the effect reruns and prints "Value: 1"
///
/// // ❌ don't use effects to synchronize state within the reactive system
/// create_isomorphic_effect(cx, move |_| {
/// // this technically works but can cause unnecessary re-renders
/// // and easily lead to problems like infinite loops
/// set_b.set(a.get() + 1);
/// set_b(a() + 1);
/// });
/// # assert_eq!(b.get(), 2);
/// # assert_eq!(b(), 2);
/// # }).dispose();
#[cfg_attr(
any(debug_assertions, feature="ssr"),

View File

@@ -1,7 +1,7 @@
#![deny(missing_docs)]
#![cfg_attr(feature = "nightly", feature(fn_traits))]
#![cfg_attr(feature = "nightly", feature(unboxed_closures))]
#![cfg_attr(feature = "nightly", feature(type_name_of_val))]
#![cfg_attr(not(feature = "stable"), feature(fn_traits))]
#![cfg_attr(not(feature = "stable"), feature(unboxed_closures))]
#![cfg_attr(not(feature = "stable"), feature(type_name_of_val))]
//! The reactive system for the [Leptos](https://docs.rs/leptos/latest/leptos/) Web framework.
//!
@@ -44,28 +44,25 @@
//! let (count, set_count) = create_signal(cx, 0);
//!
//! // calling the getter gets the value
//! // can be `count()` on nightly
//! assert_eq!(count.get(), 0);
//! assert_eq!(count(), 0);
//! // calling the setter sets the value
//! // can be `set_count(1)` on nightly
//! set_count.set(1);
//! set_count(1);
//! // or we can mutate it in place with update()
//! set_count.update(|n| *n += 1);
//!
//! // a derived signal: a plain closure that relies on the signal
//! // the closure will run whenever we *access* double_count()
//! let double_count = move || count.get() * 2;
//! let double_count = move || count() * 2;
//! assert_eq!(double_count(), 4);
//!
//! // a memo: subscribes to the signal
//! // the closure will run only when count changes
//! let memoized_triple_count = create_memo(cx, move |_| count.get() * 3);
//! // can be `memoized_triple_count()` on nightly
//! assert_eq!(memoized_triple_count.get(), 6);
//! let memoized_triple_count = create_memo(cx, move |_| count() * 3);
//! assert_eq!(memoized_triple_count(), 6);
//!
//! // this effect will run whenever `count` changes
//! // this effect will run whenever count() changes
//! create_effect(cx, move |_| {
//! println!("Count = {}", count.get());
//! println!("Count = {}", count());
//! });
//! });
//! ```

View File

@@ -40,12 +40,12 @@ use std::{any::Any, cell::RefCell, fmt, marker::PhantomData, rc::Rc};
/// let (value, set_value) = create_signal(cx, 0);
///
/// // 🆗 we could create a derived signal with a simple function
/// let double_value = move || value.get() * 2;
/// set_value.set(2);
/// let double_value = move || value() * 2;
/// set_value(2);
/// assert_eq!(double_value(), 4);
///
/// // but imagine the computation is really expensive
/// let expensive = move || really_expensive_computation(value.get()); // lazy: doesn't run until called
/// let expensive = move || really_expensive_computation(value()); // lazy: doesn't run until called
/// create_effect(cx, move |_| {
/// // 🆗 run #1: calls `really_expensive_computation` the first time
/// log::debug!("expensive = {}", expensive());
@@ -58,15 +58,14 @@ use std::{any::Any, cell::RefCell, fmt, marker::PhantomData, rc::Rc};
///
/// // instead, we create a memo
/// // 🆗 run #1: the calculation runs once immediately
/// let memoized = create_memo(cx, move |_| really_expensive_computation(value.get()));
/// let memoized = create_memo(cx, move |_| really_expensive_computation(value()));
/// create_effect(cx, move |_| {
/// // 🆗 reads the current value of the memo
/// // can be `memoized()` on nightly
/// log::debug!("memoized = {}", memoized.get());
/// // 🆗 reads the current value of the memo
/// log::debug!("memoized = {}", memoized());
/// });
/// create_effect(cx, move |_| {
/// // ✅ reads the current value **without re-running the calculation**
/// let value = memoized.get();
/// let value = memoized();
/// // do something else...
/// });
/// # }).dispose();
@@ -132,12 +131,12 @@ where
/// let (value, set_value) = create_signal(cx, 0);
///
/// // 🆗 we could create a derived signal with a simple function
/// let double_value = move || value.get() * 2;
/// set_value.set(2);
/// let double_value = move || value() * 2;
/// set_value(2);
/// assert_eq!(double_value(), 4);
///
/// // but imagine the computation is really expensive
/// let expensive = move || really_expensive_computation(value.get()); // lazy: doesn't run until called
/// let expensive = move || really_expensive_computation(value()); // lazy: doesn't run until called
/// create_effect(cx, move |_| {
/// // 🆗 run #1: calls `really_expensive_computation` the first time
/// log::debug!("expensive = {}", expensive());
@@ -150,15 +149,14 @@ where
///
/// // instead, we create a memo
/// // 🆗 run #1: the calculation runs once immediately
/// let memoized = create_memo(cx, move |_| really_expensive_computation(value.get()));
/// let memoized = create_memo(cx, move |_| really_expensive_computation(value()));
/// create_effect(cx, move |_| {
/// // 🆗 reads the current value of the memo
/// log::debug!("memoized = {}", memoized.get());
/// log::debug!("memoized = {}", memoized());
/// });
/// create_effect(cx, move |_| {
/// // ✅ reads the current value **without re-running the calculation**
/// // can be `memoized()` on nightly
/// let value = memoized.get();
/// let value = memoized();
/// // do something else...
/// });
/// # }).dispose();
@@ -329,14 +327,13 @@ impl<T> SignalWithUntracked<T> for Memo<T> {
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 0);
/// let double_count = create_memo(cx, move |_| count.get() * 2);
/// let double_count = create_memo(cx, move |_| count() * 2);
///
/// assert_eq!(double_count.get(), 0);
/// set_count.set(1);
/// set_count(1);
///
/// // can be `double_count()` on nightly
/// // assert_eq!(double_count(), 2);
/// assert_eq!(double_count.get(), 2);
/// // double_count() is shorthand for double_count.get()
/// assert_eq!(double_count(), 2);
/// # }).dispose();
/// #
/// ```

View File

@@ -51,7 +51,7 @@ use std::{
/// # // `csr`, `hydrate`, and `ssr` all have issues here
/// # // because we're not running in a browser or in Tokio. Let's just ignore it.
/// # if false {
/// let cats = create_resource(cx, move || how_many_cats.get(), fetch_cat_picture_urls);
/// let cats = create_resource(cx, how_many_cats, fetch_cat_picture_urls);
///
/// // when we read the signal, it contains either
/// // 1) None (if the Future isn't ready yet) or
@@ -59,7 +59,7 @@ use std::{
/// assert_eq!(cats.read(cx), Some(vec!["1".to_string()]));
///
/// // when the signal's value changes, the `Resource` will generate and run a new `Future`
/// set_how_many_cats.set(2);
/// set_how_many_cats(2);
/// assert_eq!(cats.read(cx), Some(vec!["2".to_string()]));
/// # }
/// # }).dispose();
@@ -696,7 +696,7 @@ impl<S, T> SignalSet<T> for Resource<S, T> {
/// # // `csr`, `hydrate`, and `ssr` all have issues here
/// # // because we're not running in a browser or in Tokio. Let's just ignore it.
/// # if false {
/// let cats = create_resource(cx, move || how_many_cats.get(), fetch_cat_picture_urls);
/// let cats = create_resource(cx, how_many_cats, fetch_cat_picture_urls);
///
/// // when we read the signal, it contains either
/// // 1) None (if the Future isn't ready yet) or
@@ -704,7 +704,7 @@ impl<S, T> SignalSet<T> for Resource<S, T> {
/// assert_eq!(cats.read(cx), Some(vec!["1".to_string()]));
///
/// // when the signal's value changes, the `Resource` will generate and run a new `Future`
/// set_how_many_cats.set(2);
/// set_how_many_cats(2);
/// assert_eq!(cats.read(cx), Some(vec!["2".to_string()]));
/// # }
/// # }).dispose();

View File

@@ -189,17 +189,17 @@ impl Scope {
/// let (b, set_b) = create_signal(cx, 0);
/// let c = create_memo(cx, move |_| {
/// // this memo will *only* update when `a` changes
/// a.get() + cx.untrack(move || b.get())
/// a() + cx.untrack(move || b())
/// });
///
/// assert_eq!(c.get(), 0);
/// set_a.set(1);
/// assert_eq!(c.get(), 1);
/// set_b.set(1);
/// assert_eq!(c(), 0);
/// set_a(1);
/// assert_eq!(c(), 1);
/// set_b(1);
/// // hasn't updated, because we untracked before reading b
/// assert_eq!(c.get(), 1);
/// set_a.set(2);
/// assert_eq!(c.get(), 3);
/// assert_eq!(c(), 1);
/// set_a(2);
/// assert_eq!(c(), 3);
///
/// # });
/// ```

View File

@@ -19,7 +19,7 @@ use std::{cell::RefCell, collections::HashMap, hash::Hash, rc::Rc};
/// # use std::cell::RefCell;
/// # create_scope(create_runtime(), |cx| {
/// let (a, set_a) = create_signal(cx, 0);
/// let is_selected = create_selector(cx, move || a.get());
/// let is_selected = create_selector(cx, a);
/// let total_notifications = Rc::new(RefCell::new(0));
/// let not = Rc::clone(&total_notifications);
/// create_isomorphic_effect(cx, {
@@ -33,13 +33,13 @@ use std::{cell::RefCell, collections::HashMap, hash::Hash, rc::Rc};
///
/// assert_eq!(is_selected(5), false);
/// assert_eq!(*total_notifications.borrow(), 0);
/// set_a.set(5);
/// set_a(5);
/// assert_eq!(is_selected(5), true);
/// assert_eq!(*total_notifications.borrow(), 1);
/// set_a.set(5);
/// set_a(5);
/// assert_eq!(is_selected(5), true);
/// assert_eq!(*total_notifications.borrow(), 1);
/// set_a.set(4);
/// set_a(4);
/// assert_eq!(is_selected(5), false);
/// # })
/// # .dispose()

View File

@@ -23,7 +23,7 @@ use thiserror::Error;
macro_rules! impl_get_fn_traits {
($($ty:ident $(($method_name:ident))?),*) => {
$(
#[cfg(feature = "nightly")]
#[cfg(not(feature = "stable"))]
impl<T: Clone> FnOnce<()> for $ty<T> {
type Output = T;
@@ -33,7 +33,7 @@ macro_rules! impl_get_fn_traits {
}
}
#[cfg(feature = "nightly")]
#[cfg(not(feature = "stable"))]
impl<T: Clone> FnMut<()> for $ty<T> {
#[inline(always)]
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
@@ -41,7 +41,7 @@ macro_rules! impl_get_fn_traits {
}
}
#[cfg(feature = "nightly")]
#[cfg(not(feature = "stable"))]
impl<T: Clone> Fn<()> for $ty<T> {
#[inline(always)]
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
@@ -61,7 +61,7 @@ macro_rules! impl_get_fn_traits {
macro_rules! impl_set_fn_traits {
($($ty:ident $($method_name:ident)?),*) => {
$(
#[cfg(feature = "nightly")]
#[cfg(not(feature = "stable"))]
impl<T> FnOnce<(T,)> for $ty<T> {
type Output = ();
@@ -71,7 +71,7 @@ macro_rules! impl_set_fn_traits {
}
}
#[cfg(feature = "nightly")]
#[cfg(not(feature = "stable"))]
impl<T> FnMut<(T,)> for $ty<T> {
#[inline(always)]
extern "rust-call" fn call_mut(&mut self, args: (T,)) -> Self::Output {
@@ -79,7 +79,7 @@ macro_rules! impl_set_fn_traits {
}
}
#[cfg(feature = "nightly")]
#[cfg(not(feature = "stable"))]
impl<T> Fn<(T,)> for $ty<T> {
#[inline(always)]
extern "rust-call" fn call(&self, args: (T,)) -> Self::Output {
@@ -290,26 +290,24 @@ pub trait SignalDispose {
/// let (count, set_count) = create_signal(cx, 0);
///
/// // ✅ calling the getter clones and returns the value
/// // this can be `count()` on nightly
/// assert_eq!(count.get(), 0);
/// assert_eq!(count(), 0);
///
/// // ✅ calling the setter sets the value
/// // this can be `set_count(1)` on nightly
/// set_count.set(1);
/// assert_eq!(count.get(), 1);
/// set_count(1);
/// assert_eq!(count(), 1);
///
/// // ❌ you could call the getter within the setter
/// // set_count.set(count.get() + 1);
/// // set_count(count() + 1);
///
/// // ✅ however it's more efficient to use .update() and mutate the value in place
/// set_count.update(|count: &mut i32| *count += 1);
/// assert_eq!(count.get(), 2);
/// assert_eq!(count(), 2);
///
/// // ✅ you can create "derived signals" with a Fn() -> T interface
/// let double_count = move || count.get() * 2; // signals are `Copy` so you can `move` them anywhere
/// set_count.set(0);
/// // ✅ you can create "derived signals" with the same Fn() -> T interface
/// let double_count = move || count() * 2; // signals are `Copy` so you can `move` them anywhere
/// set_count(0);
/// assert_eq!(double_count(), 0);
/// set_count.set(1);
/// set_count(1);
/// assert_eq!(double_count(), 2);
/// # }).dispose();
/// #
@@ -449,24 +447,24 @@ pub fn create_signal_from_stream<T>(
/// let (count, set_count) = create_signal(cx, 0);
///
/// // ✅ calling the getter clones and returns the value
/// assert_eq!(count.get(), 0);
/// assert_eq!(count(), 0);
///
/// // ✅ calling the setter sets the value
/// set_count.set(1); // `set_count(1)` on nightly
/// assert_eq!(count.get(), 1);
/// set_count(1);
/// assert_eq!(count(), 1);
///
/// // ❌ you could call the getter within the setter
/// // set_count.set(count.get() + 1);
/// // set_count(count() + 1);
///
/// // ✅ however it's more efficient to use .update() and mutate the value in place
/// set_count.update(|count: &mut i32| *count += 1);
/// assert_eq!(count.get(), 2);
/// assert_eq!(count(), 2);
///
/// // ✅ you can create "derived signals" with the same Fn() -> T interface
/// let double_count = move || count.get() * 2; // signals are `Copy` so you can `move` them anywhere
/// set_count.set(0);
/// let double_count = move || count() * 2; // signals are `Copy` so you can `move` them anywhere
/// set_count(0);
/// assert_eq!(double_count(), 0);
/// set_count.set(1);
/// set_count(1);
/// assert_eq!(double_count(), 2);
/// # }).dispose();
/// #
@@ -587,13 +585,13 @@ impl<T> SignalWithUntracked<T> for ReadSignal<T> {
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
///
/// // ❌ unnecessarily clones the string
/// let first_char = move || name.get().chars().next().unwrap();
/// let first_char = move || name().chars().next().unwrap();
/// assert_eq!(first_char(), 'A');
///
/// // ✅ gets the first char without cloning the `String`
/// let first_char = move || name.with(|n| n.chars().next().unwrap());
/// assert_eq!(first_char(), 'A');
/// set_name.set("Bob".to_string());
/// set_name("Bob".to_string());
/// assert_eq!(first_char(), 'B');
/// # });
/// ```
@@ -664,8 +662,8 @@ impl<T> SignalWith<T> for ReadSignal<T> {
///
/// assert_eq!(count.get(), 0);
///
/// // count() is shorthand for count.get() on `nightly`
/// // assert_eq!(count.get(), 0);
/// // count() is shorthand for count.get()
/// assert_eq!(count(), 0);
/// # });
/// ```
impl<T: Clone> SignalGet<T> for ReadSignal<T> {
@@ -851,16 +849,15 @@ impl<T> Hash for ReadSignal<T> {
/// let (count, set_count) = create_signal(cx, 0);
///
/// // ✅ calling the setter sets the value
/// // `set_count(1)` on nightly
/// set_count.set(1);
/// assert_eq!(count.get(), 1);
/// set_count(1);
/// assert_eq!(count(), 1);
///
/// // ❌ you could call the getter within the setter
/// // set_count.set(count.get() + 1);
/// // set_count(count() + 1);
///
/// // ✅ however it's more efficient to use .update() and mutate the value in place
/// set_count.update(|count: &mut i32| *count += 1);
/// assert_eq!(count.get(), 2);
/// assert_eq!(count(), 2);
/// # }).dispose();
/// #
/// ```
@@ -955,13 +952,13 @@ impl<T> SignalUpdateUntracked<T> for WriteSignal<T> {
/// let (count, set_count) = create_signal(cx, 0);
///
/// // notifies subscribers
/// set_count.update(|n| *n = 1); // it's easier just to call set_count.set(1), though!
/// assert_eq!(count.get(), 1);
/// set_count.update(|n| *n = 1); // it's easier just to call set_count(1), though!
/// assert_eq!(count(), 1);
///
/// // you can include arbitrary logic in this update function
/// // also notifies subscribers, even though the value hasn't changed
/// set_count.update(|n| if *n > 3 { *n += 1 });
/// assert_eq!(count.get(), 1);
/// assert_eq!(count(), 1);
/// # }).dispose();
/// ```
impl<T> SignalUpdate<T> for WriteSignal<T> {
@@ -1015,13 +1012,13 @@ impl<T> SignalUpdate<T> for WriteSignal<T> {
/// let (count, set_count) = create_signal(cx, 0);
///
/// // notifies subscribers
/// set_count.update(|n| *n = 1); // it's easier just to call set_count.set(1), though!
/// assert_eq!(count.get(), 1);
/// set_count.update(|n| *n = 1); // it's easier just to call set_count(1), though!
/// assert_eq!(count(), 1);
///
/// // you can include arbitrary logic in this update function
/// // also notifies subscribers, even though the value hasn't changed
/// set_count.update(|n| if *n > 3 { *n += 1 });
/// assert_eq!(count.get(), 1);
/// assert_eq!(count(), 1);
/// # }).dispose();
/// ```
impl<T> SignalSet<T> for WriteSignal<T> {
@@ -1116,14 +1113,14 @@ impl<T> Hash for WriteSignal<T> {
///
/// // ✅ set the value
/// count.set(1);
/// assert_eq!(count.get(), 1);
/// assert_eq!(count(), 1);
///
/// // ❌ you can call the getter within the setter
/// // count.set(count.get() + 1);
///
/// // ✅ however, it's more efficient to use .update() and mutate the value in place
/// count.update(|count: &mut i32| *count += 1);
/// assert_eq!(count.get(), 2);
/// assert_eq!(count(), 2);
/// # }).dispose();
/// #
/// ```
@@ -1176,14 +1173,14 @@ pub fn create_rw_signal<T>(cx: Scope, value: T) -> RwSignal<T> {
///
/// // ✅ set the value
/// count.set(1);
/// assert_eq!(count.get(), 1);
/// assert_eq!(count(), 1);
///
/// // ❌ you can call the getter within the setter
/// // count.set(count.get() + 1);
///
/// // ✅ however, it's more efficient to use .update() and mutate the value in place
/// count.update(|count: &mut i32| *count += 1);
/// assert_eq!(count.get(), 2);
/// assert_eq!(count(), 2);
/// # }).dispose();
/// #
/// ```
@@ -1416,7 +1413,7 @@ impl<T> SignalUpdateUntracked<T> for RwSignal<T> {
/// let name = create_rw_signal(cx, "Alice".to_string());
///
/// // ❌ unnecessarily clones the string
/// let first_char = move || name.get().chars().next().unwrap();
/// let first_char = move || name().chars().next().unwrap();
/// assert_eq!(first_char(), 'A');
///
/// // ✅ gets the first char without cloning the `String`
@@ -1494,8 +1491,8 @@ impl<T> SignalWith<T> for RwSignal<T> {
///
/// assert_eq!(count.get(), 0);
///
/// // count() is shorthand for count.get() on `nightly`
/// // assert_eq!(count(), 0);
/// // count() is shorthand for count.get()
/// assert_eq!(count(), 0);
/// # }).dispose();
/// #
/// ```
@@ -1566,8 +1563,8 @@ impl<T: Clone> SignalGet<T> for RwSignal<T> {
/// let count = create_rw_signal(cx, 0);
///
/// // notifies subscribers
/// count.update(|n| *n = 1); // it's easier just to call set_count.set(1), though!
/// assert_eq!(count.get(), 1);
/// count.update(|n| *n = 1); // it's easier just to call set_count(1), though!
/// assert_eq!(count(), 1);
///
/// // you can include arbitrary logic in this update function
/// // also notifies subscribers, even though the value hasn't changed
@@ -1576,7 +1573,7 @@ impl<T: Clone> SignalGet<T> for RwSignal<T> {
/// *n += 1
/// }
/// });
/// assert_eq!(count.get(), 1);
/// assert_eq!(count(), 1);
/// # }).dispose();
/// ```
impl<T> SignalUpdate<T> for RwSignal<T> {
@@ -1629,9 +1626,9 @@ impl<T> SignalUpdate<T> for RwSignal<T> {
/// # create_scope(create_runtime(), |cx| {
/// let count = create_rw_signal(cx, 0);
///
/// assert_eq!(count.get(), 0);
/// assert_eq!(count(), 0);
/// count.set(1);
/// assert_eq!(count.get(), 1);
/// assert_eq!(count(), 1);
/// # }).dispose();
/// ```
impl<T> SignalSet<T> for RwSignal<T> {
@@ -1709,11 +1706,11 @@ impl<T> RwSignal<T> {
/// # create_scope(create_runtime(), |cx| {
/// let count = create_rw_signal(cx, 0);
/// let read_count = count.read_only();
/// assert_eq!(count.get(), 0);
/// assert_eq!(read_count.get(), 0);
/// assert_eq!(count(), 0);
/// assert_eq!(read_count(), 0);
/// count.set(1);
/// assert_eq!(count.get(), 1);
/// assert_eq!(read_count.get(), 1);
/// assert_eq!(count(), 1);
/// assert_eq!(read_count(), 1);
/// # }).dispose();
/// ```
#[cfg_attr(
@@ -1749,9 +1746,9 @@ impl<T> RwSignal<T> {
/// # create_scope(create_runtime(), |cx| {
/// let count = create_rw_signal(cx, 0);
/// let set_count = count.write_only();
/// assert_eq!(count.get(), 0);
/// set_count.set(1);
/// assert_eq!(count.get(), 1);
/// assert_eq!(count(), 0);
/// set_count(1);
/// assert_eq!(count(), 1);
/// # }).dispose();
/// ```
#[cfg_attr(
@@ -1784,11 +1781,11 @@ impl<T> RwSignal<T> {
/// # create_scope(create_runtime(), |cx| {
/// let count = create_rw_signal(cx, 0);
/// let (get_count, set_count) = count.split();
/// assert_eq!(count.get(), 0);
/// assert_eq!(get_count.get(), 0);
/// set_count.set(1);
/// assert_eq!(count.get(), 1);
/// assert_eq!(get_count.get(), 1);
/// assert_eq!(count(), 0);
/// assert_eq!(get_count(), 0);
/// set_count(1);
/// assert_eq!(count(), 1);
/// assert_eq!(get_count(), 1);
/// # }).dispose();
/// ```
#[cfg_attr(

View File

@@ -45,14 +45,14 @@ where
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 2);
/// let double_count = Signal::derive(cx, move || count.get() * 2);
/// let memoized_double_count = create_memo(cx, move |_| count.get() * 2);
/// let double_count = Signal::derive(cx, move || count() * 2);
/// let memoized_double_count = create_memo(cx, move |_| count() * 2);
///
/// // this function takes any kind of wrapped signal
/// fn above_3(arg: &Signal<i32>) -> bool {
/// // ✅ calling the signal clones and returns the value
/// // can be `arg() > 3` on nightly
/// arg.get() > 3
/// // it is a shorthand for arg.get()
/// arg() > 3
/// }
///
/// assert_eq!(above_3(&count.into()), false);
@@ -210,7 +210,7 @@ impl<T> SignalWithUntracked<T> for Signal<T> {
/// // this function takes any kind of wrapped signal
/// fn current_len_inefficient(arg: Signal<String>) -> usize {
/// // ❌ unnecessarily clones the string
/// arg.get().len()
/// arg().len()
/// }
///
/// fn current_len(arg: &Signal<String>) -> usize {
@@ -222,9 +222,9 @@ impl<T> SignalWithUntracked<T> for Signal<T> {
/// assert_eq!(current_len(&name_upper), 5);
/// assert_eq!(current_len(&memoized_lower.into()), 5);
///
/// assert_eq!(name.get(), "Alice");
/// assert_eq!(name_upper.get(), "ALICE");
/// assert_eq!(memoized_lower.get(), "alice");
/// assert_eq!(name(), "Alice");
/// assert_eq!(name_upper(), "ALICE");
/// assert_eq!(memoized_lower(), "alice");
/// # });
/// ```
impl<T> SignalWith<T> for Signal<T> {
@@ -276,8 +276,8 @@ impl<T> SignalWith<T> for Signal<T> {
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 2);
/// let double_count = Signal::derive(cx, move || count.get() * 2);
/// let memoized_double_count = create_memo(cx, move |_| count.get() * 2);
/// let double_count = Signal::derive(cx, move || count() * 2);
/// let memoized_double_count = create_memo(cx, move |_| count() * 2);
///
/// // this function takes any kind of wrapped signal
/// fn above_3(arg: &Signal<i32>) -> bool {
@@ -342,7 +342,7 @@ where
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 2);
/// let double_count = Signal::derive(cx, move || count.get() * 2);
/// let double_count = Signal::derive(cx, move || count() * 2);
///
/// // this function takes any kind of wrapped signal
/// fn above_3(arg: &Signal<i32>) -> bool {
@@ -499,15 +499,15 @@ impl<T> Eq for SignalTypes<T> where T: PartialEq {}
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 2);
/// let double_count = MaybeSignal::derive(cx, move || count.get() * 2);
/// let memoized_double_count = create_memo(cx, move |_| count.get() * 2);
/// let double_count = MaybeSignal::derive(cx, move || count() * 2);
/// let memoized_double_count = create_memo(cx, move |_| count() * 2);
/// let static_value = 5;
///
/// // this function takes either a reactive or non-reactive value
/// fn above_3(arg: &MaybeSignal<i32>) -> bool {
/// // ✅ calling the signal clones and returns the value
/// // it is a shorthand for arg.get()
/// arg.get() > 3
/// arg() > 3
/// }
///
/// assert_eq!(above_3(&static_value.into()), true);
@@ -550,8 +550,8 @@ impl<T: Default> Default for MaybeSignal<T> {
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 2);
/// let double_count = MaybeSignal::derive(cx, move || count.get() * 2);
/// let memoized_double_count = create_memo(cx, move |_| count.get() * 2);
/// let double_count = MaybeSignal::derive(cx, move || count() * 2);
/// let memoized_double_count = create_memo(cx, move |_| count() * 2);
/// let static_value: MaybeSignal<i32> = 5.into();
///
/// // this function takes any kind of wrapped signal
@@ -596,7 +596,7 @@ impl<T: Clone> SignalGet<T> for MaybeSignal<T> {
/// // this function takes any kind of wrapped signal
/// fn current_len_inefficient(arg: &MaybeSignal<String>) -> usize {
/// // ❌ unnecessarily clones the string
/// arg.get().len()
/// arg().len()
/// }
///
/// fn current_len(arg: &MaybeSignal<String>) -> usize {
@@ -609,10 +609,10 @@ impl<T: Clone> SignalGet<T> for MaybeSignal<T> {
/// assert_eq!(current_len(&memoized_lower.into()), 5);
/// assert_eq!(current_len(&static_value), 3);
///
/// assert_eq!(name.get(), "Alice");
/// assert_eq!(name_upper.get(), "ALICE");
/// assert_eq!(memoized_lower.get(), "alice");
/// assert_eq!(static_value.get(), "Bob");
/// assert_eq!(name(), "Alice");
/// assert_eq!(name_upper(), "ALICE");
/// assert_eq!(memoized_lower(), "alice");
/// assert_eq!(static_value(), "Bob");
/// # });
/// ```
impl<T> SignalWith<T> for MaybeSignal<T> {
@@ -709,7 +709,7 @@ where
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 2);
/// let double_count = Signal::derive(cx, move || count.get() * 2);
/// let double_count = Signal::derive(cx, move || count() * 2);
///
/// // this function takes any kind of wrapped signal
/// fn above_3(arg: &MaybeSignal<i32>) -> bool {

View File

@@ -36,19 +36,19 @@ where
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 2);
/// let set_double_input = SignalSetter::map(cx, move |n| set_count.set(n * 2));
/// let set_double_input = SignalSetter::map(cx, move |n| set_count(n * 2));
///
/// // this function takes any kind of signal setter
/// fn set_to_4(setter: &SignalSetter<i32>) {
/// // ✅ calling the signal sets the value
/// // can be `setter(4)` on nightly
/// setter.set(4);
/// // it is a shorthand for arg.set()
/// setter(4);
/// }
///
/// set_to_4(&set_count.into());
/// assert_eq!(count.get(), 4);
/// assert_eq!(count(), 4);
/// set_to_4(&set_double_input);
/// assert_eq!(count.get(), 8);
/// assert_eq!(count(), 8);
/// # });
/// ```
#[derive(Debug, PartialEq, Eq)]
@@ -121,19 +121,19 @@ where
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 2);
/// let set_double_count = SignalSetter::map(cx, move |n| set_count.set(n * 2));
/// let set_double_count = SignalSetter::map(cx, move |n| set_count(n * 2));
///
/// // this function takes any kind of signal setter
/// fn set_to_4(setter: &SignalSetter<i32>) {
/// // ✅ calling the signal sets the value
/// // can be `setter(4)` on nightly
/// setter.set(4)
/// // it is a shorthand for arg.set()
/// setter(4)
/// }
///
/// set_to_4(&set_count.into());
/// assert_eq!(count.get(), 4);
/// assert_eq!(count(), 4);
/// set_to_4(&set_double_count);
/// assert_eq!(count.get(), 8);
/// assert_eq!(count(), 8);
/// # });
/// ```
#[track_caller]
@@ -164,19 +164,19 @@ where
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 2);
/// let set_double_count = SignalSetter::map(cx, move |n| set_count.set(n * 2));
/// let set_double_count = SignalSetter::map(cx, move |n| set_count(n * 2));
///
/// // this function takes any kind of signal setter
/// fn set_to_4(setter: &SignalSetter<i32>) {
/// // ✅ calling the signal sets the value
/// // can be `setter(4)` on nightly
/// setter.set(4);
/// // it is a shorthand for arg.set()
/// setter(4);
/// }
///
/// set_to_4(&set_count.into());
/// assert_eq!(count.get(), 4);
/// assert_eq!(count(), 4);
/// set_to_4(&set_double_count);
/// assert_eq!(count.get(), 8);
/// assert_eq!(count(), 8);
/// # });
#[cfg_attr(
any(debug_assertions, feature = "ssr"),

View File

@@ -56,17 +56,17 @@ use crate::{
///
/// create_effect(cx, move |_| {
/// // note: in the browser, use leptos::log! instead
/// println!("name is {}", name.get());
/// println!("name is {}", name());
/// });
/// create_effect(cx, move |_| {
/// println!("count is {}", count.get());
/// println!("count is {}", count());
/// });
///
/// // setting count only causes count to log, not name
/// set_count.set(42);
/// set_count(42);
///
/// // setting name only causes name to log, not count
/// set_name.set("Bob".into());
/// set_name("Bob".into());
/// ```
pub fn create_slice<T, O, S>(
cx: Scope,

View File

@@ -87,8 +87,8 @@ impl<T> StoredValue<T> {
///
/// // calling .get_value() clones and returns the value
/// assert_eq!(data.get_value().value, "a");
/// // can be `data().value` on nightly
/// // assert_eq!(data().value, "a");
/// // there's a short-hand getter form
/// assert_eq!(data().value, "a");
/// # });
/// ```
#[track_caller]

View File

@@ -77,8 +77,7 @@ impl Trigger {
/// let o = output.clone();
/// let e = external_data.clone();
/// create_effect(cx, move |_| {
/// // can be `rerun_on_data()` on nightly
/// rerun_on_data.track();
/// rerun_on_data(); // or rerun_on_data.track();
/// write!(o.borrow_mut(), "{}", *e.borrow());
/// *e.borrow_mut() += 1;
/// });
@@ -226,7 +225,7 @@ impl SignalSet<()> for Trigger {
}
}
#[cfg(feature = "nightly")]
#[cfg(not(feature = "stable"))]
impl FnOnce<()> for Trigger {
type Output = ();
@@ -236,7 +235,7 @@ impl FnOnce<()> for Trigger {
}
}
#[cfg(feature = "nightly")]
#[cfg(not(feature = "stable"))]
impl FnMut<()> for Trigger {
#[inline(always)]
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
@@ -244,7 +243,7 @@ impl FnMut<()> for Trigger {
}
}
#[cfg(feature = "nightly")]
#[cfg(not(feature = "stable"))]
impl Fn<()> for Trigger {
#[inline(always)]
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {

Some files were not shown because too many files have changed in this diff Show More