mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 18:22:34 -05:00
Compare commits
14 Commits
1457
...
0.5.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bee9bd8f67 | ||
|
|
8d3874f8a9 | ||
|
|
bade16d227 | ||
|
|
e0a132bde3 | ||
|
|
4d7e1f4d26 | ||
|
|
700eee6604 | ||
|
|
694ed61e4c | ||
|
|
d7330097ba | ||
|
|
c65a3a6ca3 | ||
|
|
793c191619 | ||
|
|
6c3e2fe53e | ||
|
|
08c419e3ee | ||
|
|
736f4185b5 | ||
|
|
9cc0fc8c49 |
19
.github/workflows/run-cargo-make-task.yml
vendored
19
.github/workflows/run-cargo-make-task.yml
vendored
@@ -95,14 +95,17 @@ jobs:
|
||||
|
||||
- name: Maybe install playwright browser dependencies
|
||||
run: |
|
||||
playwright_count=$(find ${{inputs.directory}} -name playwright.config.ts | wc -l)
|
||||
if [ $playwright_count -eq 1 ]; then
|
||||
echo playwright required
|
||||
sudo apt-get update
|
||||
sudo apt-get install libegl1 libvpx7 libevent-2.1-7 libopus0 libopengl0 libwoff1 libharfbuzz-icu0 libgstreamer-plugins-base1.0-0 libgstreamer-gl1.0-0 libhyphen0 libmanette-0.2-0 libgles2 gstreamer1.0-libav
|
||||
else
|
||||
echo playwright is not required
|
||||
fi
|
||||
for pw_path in $(find ${{inputs.directory}} -name playwright.config.ts)
|
||||
do
|
||||
pw_dir=$(dirname $pw_path)
|
||||
if [ ! -v $pw_dir ]; then
|
||||
echo "Playwright required in $pw_dir"
|
||||
cd $pw_dir
|
||||
pnpm dlx playwright install --with-deps
|
||||
else
|
||||
echo Playwright is not required
|
||||
fi
|
||||
done
|
||||
|
||||
# Run Cargo Make Task
|
||||
- name: ${{ inputs.cargo_make_task }}
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,3 +9,5 @@ Cargo.lock
|
||||
.idea
|
||||
.direnv
|
||||
.envrc
|
||||
|
||||
.vscode
|
||||
28
Cargo.toml
28
Cargo.toml
@@ -26,22 +26,22 @@ members = [
|
||||
exclude = ["benchmarks", "examples"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.5.0"
|
||||
version = "0.5.0-beta2"
|
||||
|
||||
[workspace.dependencies]
|
||||
leptos = { path = "./leptos", version = "0.5.0" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.5.0" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.5.0" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.5.0" }
|
||||
leptos_reactive = { path = "./leptos_reactive", version = "0.5.0" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.5.0" }
|
||||
server_fn = { path = "./server_fn", version = "0.5.0" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.5.0" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.5.0" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.5.0" }
|
||||
leptos_router = { path = "./router", version = "0.5.0" }
|
||||
leptos_meta = { path = "./meta", version = "0.5.0" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.5.0" }
|
||||
leptos = { path = "./leptos", version = "0.5.0-beta2" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.5.0-beta2" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.5.0-beta2" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.5.0-beta2" }
|
||||
leptos_reactive = { path = "./leptos_reactive", version = "0.5.0-beta2" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.5.0-beta2" }
|
||||
server_fn = { path = "./server_fn", version = "0.5.0-beta2" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.5.0-beta2" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.5.0-beta2" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.5.0-beta2" }
|
||||
leptos_router = { path = "./router", version = "0.5.0-beta2" }
|
||||
leptos_meta = { path = "./meta", version = "0.5.0-beta2" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.5.0-beta2" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
- [Error Handling](./view/07_errors.md)
|
||||
- [Parent-Child Communication](./view/08_parent_child.md)
|
||||
- [Passing Children to Components](./view/09_component_children.md)
|
||||
- [No Macros: The View Builder Syntax](./view/builder.md)
|
||||
- [Reactivity](./reactivity/README.md)
|
||||
- [Working with Signals](./reactivity/working_with_signals.md)
|
||||
- [Responding to Changes with `create_effect`](./reactivity/14_create_effect.md)
|
||||
|
||||
@@ -70,6 +70,18 @@ There are a few things to note about the way you define a server function, too.
|
||||
- We provide the macro a path. This is a prefix for the path at which we’ll mount a server function handler on our server. (See examples for [Actix](https://github.com/leptos-rs/leptos/blob/main/examples/todo_app_sqlite/src/main.rs#L44) and [Axum](https://github.com/leptos-rs/leptos/blob/598523cd9d0d775b017cb721e41ebae9349f01e2/examples/todo_app_sqlite_axum/src/main.rs#L51).)
|
||||
- You’ll need to have `serde` as a dependency with the `derive` featured enabled for the macro to work properly. You can easily add it to `Cargo.toml` with `cargo add serde --features=derive`.
|
||||
|
||||
## Server Function URL Prefixes
|
||||
|
||||
You can optionally define a specific URL prefix to be used in the definition of the server function.
|
||||
This is done by providing an optional 2nd argument to the `#[server]` macro.
|
||||
By default the URL prefix will be `/api`, if not specified.
|
||||
Here are some examples:
|
||||
|
||||
```rust
|
||||
#[server(AddTodo)] // will use the default URL prefix of `/api`
|
||||
#[server(AddTodo, "/foo")] // will use the URL prefix of `/foo`
|
||||
```
|
||||
|
||||
## Server Function Encodings
|
||||
|
||||
By default, the server function call is a `POST` request that serializes the arguments as URL-encoded form data in the body of the request. (This means that server functions can be called from HTML forms, which we’ll see in a future chapter.) But there are a few other methods supported. Optionally, we can provide another argument to the `#[server]` macro to specify an alternate encoding:
|
||||
@@ -105,6 +117,21 @@ In other words, you have two choices:
|
||||
>
|
||||
> The CBOR encoding is suported for historical reasons; an earlier version of server functions used a URL encoding that didn’t support nested objects like structs or vectors as server function arguments, which CBOR did. But note that the CBOR forms encounter the same issue as `PUT`, `DELETE`, or JSON: they do not degrade gracefully if the WASM version of your app is not available.
|
||||
|
||||
|
||||
## Server Functions Endpoint Paths
|
||||
|
||||
By default, a unique path will be generated. You can optionally define a specific endpoint path to be used in the URL. This is done by providing an optional 4th argument to the `#[server]` macro. Leptos will generate the complete path by concatenating the URL prefix (2nd argument) and the endpoint path (4th argument).
|
||||
For example,
|
||||
|
||||
```rust
|
||||
#[server(MyServerFnType, "/api", "Url", "hello")]
|
||||
```
|
||||
will generate a server function endpoint at `/api/hello` that accepts a POST request.
|
||||
|
||||
> **Can I use the same server function endpoint path with multiple encodings?**
|
||||
>
|
||||
> No. Different server functions must have unique paths. The `#[server]` macro automatically generates unique paths, but you need to be careful if you choose to specify the complete path manually, as the server looks up server functions by their path.
|
||||
|
||||
## An Important Note on Security
|
||||
|
||||
Server functions are a cool technology, but it’s very important to remember. **Server functions are not magic; they’re syntax sugar for defining a public API.** The _body_ of a server function is never made public; it’s just part of your server binary. But the server function is a publicly accessible API endpoint, and it’s return value is just a JSON or similar blob. You should _never_ return something sensitive from a server function.
|
||||
|
||||
98
docs/book/src/view/builder.md
Normal file
98
docs/book/src/view/builder.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# No Macros: The View Builder Syntax
|
||||
|
||||
> If you’re perfectly happy with the `view!` macro syntax described so far, you’re welcome to skip this chapter. The builder syntax described in this section is always available, but never required.
|
||||
|
||||
For one reason or another, many developers would prefer to avoid macros. Perhaps you don’t like the limited `rustfmt` support. (Although, you should check out [`leptosfmt`](https://github.com/bram209/leptosfmt), which is an excellent tool!) Perhaps you worry about the effect of macros on compile time. Perhaps you prefer the aesthetics of pure Rust syntax, or you have trouble context-switching between an HTML-like syntax and your Rust code. Or perhaps you want more flexibility in how you create and manipulate HTML elements than the `view` macro provides.
|
||||
|
||||
If you fall into any of those camps, the builder syntax may be for you.
|
||||
|
||||
The `view` macro expands an HTML-like syntax to a series of Rust functions and method calls. If you’d rather not use the `view` macro, you can simply use that expanded syntax yourself. And it’s actually pretty nice!
|
||||
|
||||
First off, if you want you can even drop the `#[component]` macro: a component is just a setup function that creates your view, so you can define a component as a simple function call:
|
||||
|
||||
```rust
|
||||
pub fn counter(initial_value: i32, step: u32) -> impl IntoView { }
|
||||
```
|
||||
|
||||
Elements are created by calling a function with the same name as the HTML element:
|
||||
|
||||
```rust
|
||||
p()
|
||||
```
|
||||
|
||||
You can add children to the element with [`.child()`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html#method.child), which takes a single child or a tuple or array of types that implement [`IntoView`](https://docs.rs/leptos/latest/leptos/trait.IntoView.html).
|
||||
|
||||
```rust
|
||||
p().child((em().child("Big, "), strong().child("bold "), "text"))
|
||||
```
|
||||
|
||||
Attributes are added with [`.attr()`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html#method.attr). This can take any of the same types that you could pass as an attribute into the view macro (types that implement [`IntoAttribute`](https://docs.rs/leptos/latest/leptos/trait.IntoAttribute.html)).
|
||||
|
||||
```rust
|
||||
p().attr("id", "foo").attr("data-count", move || count().to_string())
|
||||
```
|
||||
|
||||
Similarly, the `class:`, `prop:`, and `style:` syntaxes map directly onto [`.class()`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html#method.class), [`.prop()`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html#method.prop), and [`.style()`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html#method.style) methods.
|
||||
|
||||
Event listeners can be added with [`.on()`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html#method.on). Typed events found in [`leptos::ev`](https://docs.rs/leptos/latest/leptos/ev/index.html) prevent typos in event names and allow for correct type inference in the callback function.
|
||||
|
||||
```rust
|
||||
button()
|
||||
.on(ev::click, move |_| set_count.update(|count| count.clear()))
|
||||
.child("Clear")
|
||||
```
|
||||
|
||||
> Many additional methods can be found in the [`HtmlElement`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html#method.child) docs, including some methods that are not directly available in the `view` macro.
|
||||
|
||||
All of this adds up to a very Rusty syntax to build full-featured views, if you prefer this style.
|
||||
|
||||
```rust
|
||||
/// A simple counter view.
|
||||
// A component is really just a function call: it runs once to create the DOM and reactive system
|
||||
pub fn counter(initial_value: i32, step: u32) -> impl IntoView {
|
||||
let (count, set_count) = create_signal(0);
|
||||
|
||||
div()
|
||||
.child((
|
||||
button()
|
||||
// typed events found in leptos::ev
|
||||
// 1) prevent typos in event names
|
||||
// 2) allow for correct type inference in callbacks
|
||||
.on(ev::click, move |_| set_count.update(|count| count.clear()))
|
||||
.child("Clear"),
|
||||
button()
|
||||
.on(ev::click, move |_| {
|
||||
set_count.update(|count| count.decrease())
|
||||
})
|
||||
.child("-1"),
|
||||
span().child(("Value: ", move || count.get().value(), "!")),
|
||||
button()
|
||||
.on(ev::click, move |_| {
|
||||
set_count.update(|count| count.increase())
|
||||
})
|
||||
.child("+1"),
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
This also has the benefit of being more flexible: because these are all plain Rust functions and methods, it’s easier to use them in things like iterator adapters without any additional “magic”:
|
||||
|
||||
```rust
|
||||
// take some set of attribute names and values
|
||||
let attrs: Vec<(&str, AttributeValue)> = todo!();
|
||||
// you can use the builder syntax to “spread” these onto the
|
||||
// element in a way that’s not possible with the view macro
|
||||
let p = attrs
|
||||
.into_iter()
|
||||
.fold(p(), |el, (name, value)| el.attr(name, value));
|
||||
|
||||
```
|
||||
|
||||
> ## Performance Note
|
||||
>
|
||||
> One caveat: the `view` macro applies significant optimizations in server-side-rendering (SSR) mode to improve HTML rendering performance significantly (think 2-4x faster, depending on the characteristics of any given app). It does this by analyzing your `view` at compile time and converting the static parts into simple HTML strings, rather than expanding them into the builder syntax.
|
||||
>
|
||||
> This means two things:
|
||||
>
|
||||
> 1. The builder syntax and `view` macro should not be mixed, or should only be mixed very carefully: at least in SSR mode, the output of the `view` should be treated as a “black box” that can’t have additional builder methods applied to it without causing inconsistencies.
|
||||
> 2. Using the builder syntax will result in less-than-optimal SSR performance. It won’t be slow, by any means (and it’s worth running your own benchmarks in any case), just slower than the `view`-optimized version.
|
||||
@@ -53,6 +53,8 @@ echo "CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = $examples"
|
||||
workspace = false
|
||||
description = "report ci test runners for each example - OPTION: [all]"
|
||||
script = '''
|
||||
set -emu
|
||||
|
||||
BOLD="\e[1m"
|
||||
GREEN="\e[0;32m"
|
||||
ITALIC="\e[3m"
|
||||
@@ -77,36 +79,54 @@ for path in $makefile_paths; do
|
||||
|
||||
test_runner=
|
||||
|
||||
test_count=$(grep -rl -E "#\[(test|rstest)\]" | wc -l)
|
||||
test_count=$(grep -rl -E "#\[test\]" | wc -l)
|
||||
if [ $test_count -gt 0 ]; then
|
||||
test_runner="-C"
|
||||
test_runner="T"
|
||||
fi
|
||||
|
||||
while read -r line; do
|
||||
case $line in
|
||||
*"cucumber"*)
|
||||
test_runner=$test_runner"C"
|
||||
;;
|
||||
*"rstest"*)
|
||||
test_runner=$test_runner"R"
|
||||
;;
|
||||
esac
|
||||
done <"./Cargo.toml"
|
||||
|
||||
while read -r line; do
|
||||
case $line in
|
||||
*"wasm-test.toml"*)
|
||||
test_runner=$test_runner"-W"
|
||||
test_runner=$test_runner"W"
|
||||
;;
|
||||
*"playwright-test.toml"*)
|
||||
test_runner=$test_runner"-P"
|
||||
test_runner=$test_runner"P"
|
||||
;;
|
||||
*"cargo-leptos-test.toml"*)
|
||||
test_runner=$test_runner"-L"
|
||||
test_runner=$test_runner"L"
|
||||
;;
|
||||
esac
|
||||
done <"./Makefile.toml"
|
||||
|
||||
if [ ! -z "$1" ]; then
|
||||
runners=$(echo ${test_runner} | grep -o . | sort | tr -d "\n")
|
||||
|
||||
if [ ! -z ${1+x} ]; then
|
||||
# Show all examples
|
||||
echo "$path ${BOLD}${test_runner}${RESET}"
|
||||
elif [ ! -z $test_runner ]; then
|
||||
echo "$path ➤ ${BOLD}${runners}${RESET}"
|
||||
elif [ ! -z $runners ]; then
|
||||
# Filter out examples that do not run tests in `ci`
|
||||
echo "$path ${BOLD}${test_runner}${RESET}"
|
||||
echo "$path ➤ ${BOLD}${runners}${RESET}"
|
||||
fi
|
||||
|
||||
cd ${start_path}
|
||||
done
|
||||
echo
|
||||
echo "${ITALIC}Runners: C = Cargo Test, L = Cargo Leptos Test, P = Playwright Test, W = WASM Test${RESET}"
|
||||
echo "${ITALIC}Runners: C = Cucumber, L = Cargo Leptos, P = Playwright, R = RS Test, T = Cargo, W = WASM${RESET}"
|
||||
echo
|
||||
'''
|
||||
|
||||
# ALIASES
|
||||
|
||||
[tasks.tr]
|
||||
alias = "test-runner-report"
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
[tasks.clean]
|
||||
dependencies = [
|
||||
"clean-cargo",
|
||||
"clean-trunk",
|
||||
"clean-node_modules",
|
||||
"clean-playwright",
|
||||
"clean-cargo",
|
||||
"clean-trunk",
|
||||
"clean-node_modules",
|
||||
"clean-playwright",
|
||||
]
|
||||
|
||||
[tasks.clean-cargo]
|
||||
command = "cargo"
|
||||
args = ["clean"]
|
||||
command = "rm"
|
||||
args = ["-rf", "target"]
|
||||
|
||||
[tasks.clean-trunk]
|
||||
command = "trunk"
|
||||
args = ["clean"]
|
||||
script = '''
|
||||
find . -type d -name target | xargs rm -rf
|
||||
'''
|
||||
|
||||
[tasks.clean-node_modules]
|
||||
script = '''
|
||||
|
||||
@@ -5,7 +5,6 @@ use leptos::{ev, html::*, *};
|
||||
pub fn counter(initial_value: i32, step: u32) -> impl IntoView {
|
||||
let (count, set_count) = create_signal(Count::new(initial_value, step));
|
||||
|
||||
// elements are created by calling a function with a Scope argument
|
||||
// the function name is the same as the HTML tag name
|
||||
div()
|
||||
// children can be added with .child()
|
||||
@@ -25,20 +24,13 @@ pub fn counter(initial_value: i32, step: u32) -> impl IntoView {
|
||||
set_count.update(|count| count.decrease())
|
||||
})
|
||||
.child("-1"),
|
||||
span()
|
||||
.child("Value: ")
|
||||
// reactive values are passed to .child() as a tuple
|
||||
// (Scope, [child function]) so an effect can be created
|
||||
.child(move || count.get().value())
|
||||
.child("!"),
|
||||
))
|
||||
.child(
|
||||
span().child(("Value: ", move || count.get().value(), "!")),
|
||||
button()
|
||||
.on(ev::click, move |_| {
|
||||
set_count.update(|count| count.increase())
|
||||
})
|
||||
.child("+1"),
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -16,7 +16,8 @@ leptos_reactive = { workspace = true }
|
||||
leptos_server = { workspace = true }
|
||||
leptos_config = { workspace = true }
|
||||
tracing = "0.1"
|
||||
typed-builder = "0.14"
|
||||
typed-builder = "0.16"
|
||||
typed-builder-macro = "0.16"
|
||||
server_fn = { workspace = true }
|
||||
web-sys = { version = "0.3.63", optional = true }
|
||||
wasm-bindgen = { version = "0.2", optional = true }
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::TextProp;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A collection of additional HTML attributes to be applied to an element,
|
||||
/// each of which may or may not be reactive.
|
||||
#[derive(Default, Clone)]
|
||||
#[derive(Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct AdditionalAttributes(pub(crate) Vec<(String, TextProp)>);
|
||||
pub struct AdditionalAttributes(pub(crate) Rc<[(String, TextProp)]>);
|
||||
|
||||
impl<I, T, U> From<I> for AdditionalAttributes
|
||||
where
|
||||
@@ -22,6 +23,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AdditionalAttributes {
|
||||
fn default() -> Self {
|
||||
Self([].into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over additional HTML attributes.
|
||||
#[repr(transparent)]
|
||||
pub struct AdditionalAttributesIter<'a>(
|
||||
|
||||
@@ -175,7 +175,6 @@ pub use leptos_server::{
|
||||
ServerFnErrorErr,
|
||||
};
|
||||
pub use server_fn::{self, ServerFn as _};
|
||||
pub use typed_builder;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "template_macro"))]
|
||||
pub use {leptos_macro::template, wasm_bindgen, web_sys};
|
||||
mod error_boundary;
|
||||
@@ -195,6 +194,12 @@ pub use text_prop::TextProp;
|
||||
#[doc(hidden)]
|
||||
pub use tracing;
|
||||
pub use transition::*;
|
||||
#[doc(hidden)]
|
||||
pub use typed_builder;
|
||||
#[doc(hidden)]
|
||||
pub use typed_builder::Optional;
|
||||
#[doc(hidden)]
|
||||
pub use typed_builder_macro;
|
||||
extern crate self as leptos;
|
||||
|
||||
/// The most common type for the `children` property on components,
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use leptos_reactive::Oco;
|
||||
use std::{fmt::Debug, rc::Rc};
|
||||
|
||||
/// Describes a value that is either a static or a reactive string, i.e.,
|
||||
/// a [`String`], a [`&str`], or a reactive `Fn() -> String`.
|
||||
#[derive(Clone)]
|
||||
pub struct TextProp(Rc<dyn Fn() -> String>);
|
||||
pub struct TextProp(Rc<dyn Fn() -> Oco<'static, str>>);
|
||||
|
||||
impl TextProp {
|
||||
/// Accesses the current value of the property.
|
||||
#[inline(always)]
|
||||
pub fn get(&self) -> String {
|
||||
pub fn get(&self) -> Oco<'static, str> {
|
||||
(self.0)()
|
||||
}
|
||||
}
|
||||
@@ -21,23 +22,38 @@ impl Debug for TextProp {
|
||||
|
||||
impl From<String> for TextProp {
|
||||
fn from(s: String) -> Self {
|
||||
let s: Oco<'_, str> = Oco::Counted(Rc::from(s));
|
||||
TextProp(Rc::new(move || s.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for TextProp {
|
||||
fn from(s: &str) -> Self {
|
||||
let s = s.to_string();
|
||||
impl From<&'static str> for TextProp {
|
||||
fn from(s: &'static str) -> Self {
|
||||
let s: Oco<'_, str> = s.into();
|
||||
TextProp(Rc::new(move || s.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> From<F> for TextProp
|
||||
impl From<Rc<str>> for TextProp {
|
||||
fn from(s: Rc<str>) -> Self {
|
||||
let s: Oco<'_, str> = s.into();
|
||||
TextProp(Rc::new(move || s.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Oco<'static, str>> for TextProp {
|
||||
fn from(s: Oco<'static, str>) -> Self {
|
||||
TextProp(Rc::new(move || s.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, S> From<F> for TextProp
|
||||
where
|
||||
F: Fn() -> String + 'static,
|
||||
F: Fn() -> S + 'static,
|
||||
S: Into<Oco<'static, str>>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn from(s: F) -> Self {
|
||||
TextProp(Rc::new(s))
|
||||
TextProp(Rc::new(move || s().into()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ config = "0.13.3"
|
||||
regex = "1.7.0"
|
||||
serde = { version = "1.0.151", features = ["derive"] }
|
||||
thiserror = "1.0.38"
|
||||
typed-builder = "0.14"
|
||||
typed-builder = "0.16"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1", features = ["rt", "macros"] }
|
||||
|
||||
@@ -14,12 +14,12 @@ pub use dyn_child::*;
|
||||
pub use each::*;
|
||||
pub use errors::*;
|
||||
pub use fragment::*;
|
||||
use leptos_reactive::untrack_with_diagnostics;
|
||||
use leptos_reactive::{untrack_with_diagnostics, Oco};
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use once_cell::unsync::OnceCell;
|
||||
use std::fmt;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use std::rc::Rc;
|
||||
use std::{borrow::Cow, fmt};
|
||||
pub use unit::*;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use wasm_bindgen::JsCast;
|
||||
@@ -55,7 +55,7 @@ pub struct ComponentRepr {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
mounted: Rc<OnceCell<()>>,
|
||||
#[cfg(any(debug_assertions, feature = "ssr"))]
|
||||
pub(crate) name: Cow<'static, str>,
|
||||
pub(crate) name: Oco<'static, str>,
|
||||
#[cfg(debug_assertions)]
|
||||
_opening: Comment,
|
||||
/// The children of the component.
|
||||
@@ -163,24 +163,24 @@ impl IntoView for ComponentRepr {
|
||||
impl ComponentRepr {
|
||||
/// Creates a new [`Component`].
|
||||
#[inline(always)]
|
||||
pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
|
||||
pub fn new(name: impl Into<Oco<'static, str>>) -> Self {
|
||||
Self::new_with_id_concrete(name.into(), HydrationCtx::id())
|
||||
}
|
||||
|
||||
/// Creates a new [`Component`] with the given hydration ID.
|
||||
#[inline(always)]
|
||||
pub fn new_with_id(
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
name: impl Into<Oco<'static, str>>,
|
||||
id: HydrationKey,
|
||||
) -> Self {
|
||||
Self::new_with_id_concrete(name.into(), id)
|
||||
}
|
||||
|
||||
fn new_with_id_concrete(name: Cow<'static, str>, id: HydrationKey) -> Self {
|
||||
fn new_with_id_concrete(name: Oco<'static, str>, id: HydrationKey) -> Self {
|
||||
let markers = (
|
||||
Comment::new(Cow::Owned(format!("</{name}>")), &id, true),
|
||||
Comment::new(format!("</{name}>"), &id, true),
|
||||
#[cfg(debug_assertions)]
|
||||
Comment::new(Cow::Owned(format!("<{name}>")), &id, false),
|
||||
Comment::new(format!("<{name}>"), &id, false),
|
||||
);
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
@@ -236,7 +236,7 @@ where
|
||||
V: IntoView,
|
||||
{
|
||||
id: HydrationKey,
|
||||
name: Cow<'static, str>,
|
||||
name: Oco<'static, str>,
|
||||
children_fn: F,
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ where
|
||||
V: IntoView,
|
||||
{
|
||||
/// Creates a new component.
|
||||
pub fn new(name: impl Into<Cow<'static, str>>, f: F) -> Self {
|
||||
pub fn new(name: impl Into<Oco<'static, str>>, f: F) -> Self {
|
||||
Self {
|
||||
id: HydrationCtx::id(),
|
||||
name: name.into(),
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
Comment, IntoView, View,
|
||||
};
|
||||
use cfg_if::cfg_if;
|
||||
use std::{borrow::Cow, cell::RefCell, fmt, ops::Deref, rc::Rc};
|
||||
use std::{cell::RefCell, fmt, ops::Deref, rc::Rc};
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
use crate::{mount_child, prepare_to_move, unmount_child, MountKind, Mountable, Text};
|
||||
@@ -83,9 +83,9 @@ impl Mountable for DynChildRepr {
|
||||
impl DynChildRepr {
|
||||
fn new_with_id(id: HydrationKey) -> Self {
|
||||
let markers = (
|
||||
Comment::new(Cow::Borrowed("</DynChild>"), &id, true),
|
||||
Comment::new("</DynChild>", &id, true),
|
||||
#[cfg(debug_assertions)]
|
||||
Comment::new(Cow::Borrowed("<DynChild>"), &id, false),
|
||||
Comment::new("<DynChild>", &id, false),
|
||||
);
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use crate::hydration::HydrationKey;
|
||||
use crate::{hydration::HydrationCtx, Comment, CoreComponent, IntoView, View};
|
||||
use leptos_reactive::{as_child_of_current_owner, Disposer};
|
||||
use std::{borrow::Cow, cell::RefCell, fmt, hash::Hash, ops::Deref, rc::Rc};
|
||||
use std::{cell::RefCell, fmt, hash::Hash, ops::Deref, rc::Rc};
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use web::*;
|
||||
|
||||
@@ -79,9 +79,9 @@ impl Default for EachRepr {
|
||||
let id = HydrationCtx::id();
|
||||
|
||||
let markers = (
|
||||
Comment::new(Cow::Borrowed("</Each>"), &id, true),
|
||||
Comment::new("</Each>", &id, true),
|
||||
#[cfg(debug_assertions)]
|
||||
Comment::new(Cow::Borrowed("<Each>"), &id, false),
|
||||
Comment::new("<Each>", &id, false),
|
||||
);
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
@@ -224,13 +224,13 @@ impl EachItem {
|
||||
|
||||
let markers = (
|
||||
if needs_closing {
|
||||
Some(Comment::new(Cow::Borrowed("</EachItem>"), &id, true))
|
||||
Some(Comment::new("</EachItem>", &id, true))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
#[cfg(debug_assertions)]
|
||||
if needs_closing {
|
||||
Some(Comment::new(Cow::Borrowed("<EachItem>"), &id, false))
|
||||
Some(Comment::new("<EachItem>", &id, false))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod typed;
|
||||
|
||||
use std::{borrow::Cow, cell::RefCell, collections::HashSet};
|
||||
use leptos_reactive::Oco;
|
||||
use std::{cell::RefCell, collections::HashSet};
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use wasm_bindgen::{
|
||||
convert::FromWasmAbi, intern, prelude::Closure, JsCast, JsValue,
|
||||
@@ -8,7 +9,7 @@ use wasm_bindgen::{
|
||||
};
|
||||
|
||||
thread_local! {
|
||||
pub(crate) static GLOBAL_EVENTS: RefCell<HashSet<Cow<'static, str>>> = RefCell::new(HashSet::new());
|
||||
pub(crate) static GLOBAL_EVENTS: RefCell<HashSet<Oco<'static, str>>> = RefCell::new(HashSet::new());
|
||||
}
|
||||
|
||||
// Used in template macro
|
||||
@@ -47,8 +48,8 @@ pub fn add_event_helper<E: crate::ev::EventDescriptor + 'static>(
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
pub fn add_event_listener<E>(
|
||||
target: &web_sys::Element,
|
||||
key: Cow<'static, str>,
|
||||
event_name: Cow<'static, str>,
|
||||
key: Oco<'static, str>,
|
||||
event_name: Oco<'static, str>,
|
||||
#[cfg(debug_assertions)] mut cb: Box<dyn FnMut(E)>,
|
||||
#[cfg(not(debug_assertions))] cb: Box<dyn FnMut(E)>,
|
||||
options: &Option<web_sys::AddEventListenerOptions>,
|
||||
@@ -115,7 +116,7 @@ pub(crate) fn add_event_listener_undelegated<E>(
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
pub(crate) fn add_delegated_event_listener(
|
||||
key: &str,
|
||||
event_name: Cow<'static, str>,
|
||||
event_name: Oco<'static, str>,
|
||||
options: &Option<web_sys::AddEventListenerOptions>,
|
||||
) {
|
||||
GLOBAL_EVENTS.with(|global_events| {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Types for all DOM events.
|
||||
|
||||
use std::{borrow::Cow, marker::PhantomData};
|
||||
use leptos_reactive::Oco;
|
||||
use std::marker::PhantomData;
|
||||
use wasm_bindgen::convert::FromWasmAbi;
|
||||
|
||||
/// A trait for converting types into [web_sys events](web_sys).
|
||||
@@ -16,10 +17,10 @@ pub trait EventDescriptor: Clone {
|
||||
const BUBBLES: bool;
|
||||
|
||||
/// The name of the event, such as `click` or `mouseover`.
|
||||
fn name(&self) -> Cow<'static, str>;
|
||||
fn name(&self) -> Oco<'static, str>;
|
||||
|
||||
/// The key used for event delegation.
|
||||
fn event_delegation_key(&self) -> Cow<'static, str>;
|
||||
fn event_delegation_key(&self) -> Oco<'static, str>;
|
||||
|
||||
/// Return the options for this type. This is only used when you create a [`Custom`] event
|
||||
/// handler.
|
||||
@@ -39,12 +40,12 @@ impl<Ev: EventDescriptor> EventDescriptor for undelegated<Ev> {
|
||||
type EventType = Ev::EventType;
|
||||
|
||||
#[inline(always)]
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
fn name(&self) -> Oco<'static, str> {
|
||||
self.0.name()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn event_delegation_key(&self) -> Cow<'static, str> {
|
||||
fn event_delegation_key(&self) -> Oco<'static, str> {
|
||||
self.0.event_delegation_key()
|
||||
}
|
||||
|
||||
@@ -54,7 +55,7 @@ impl<Ev: EventDescriptor> EventDescriptor for undelegated<Ev> {
|
||||
/// A custom event.
|
||||
#[derive(Debug)]
|
||||
pub struct Custom<E: FromWasmAbi = web_sys::Event> {
|
||||
name: Cow<'static, str>,
|
||||
name: Oco<'static, str>,
|
||||
options: Option<web_sys::AddEventListenerOptions>,
|
||||
_event_type: PhantomData<E>,
|
||||
}
|
||||
@@ -72,11 +73,11 @@ impl<E: FromWasmAbi> Clone for Custom<E> {
|
||||
impl<E: FromWasmAbi> EventDescriptor for Custom<E> {
|
||||
type EventType = E;
|
||||
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
fn name(&self) -> Oco<'static, str> {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn event_delegation_key(&self) -> Cow<'static, str> {
|
||||
fn event_delegation_key(&self) -> Oco<'static, str> {
|
||||
format!("$$${}", self.name).into()
|
||||
}
|
||||
|
||||
@@ -92,7 +93,7 @@ impl<E: FromWasmAbi> Custom<E> {
|
||||
/// Creates a custom event type that can be used within
|
||||
/// [`HtmlElement::on`](crate::HtmlElement::on), for events
|
||||
/// which are not covered in the [`ev`](crate::ev) module.
|
||||
pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
|
||||
pub fn new(name: impl Into<Oco<'static, str>>) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
options: None,
|
||||
@@ -299,12 +300,12 @@ macro_rules! generate_event_types {
|
||||
type EventType = web_sys::$web_event;
|
||||
|
||||
#[inline(always)]
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
fn name(&self) -> Oco<'static, str> {
|
||||
stringify!([< $($event)+ >]).into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn event_delegation_key(&self) -> Cow<'static, str> {
|
||||
fn event_delegation_key(&self) -> Oco<'static, str> {
|
||||
concat!("$$$", stringify!([< $($event)+ >])).into()
|
||||
}
|
||||
|
||||
|
||||
@@ -66,12 +66,13 @@ use crate::{
|
||||
macro_helpers::{IntoAttribute, IntoClass, IntoProperty, IntoStyle},
|
||||
Element, Fragment, IntoView, NodeRef, Text, View,
|
||||
};
|
||||
use std::{borrow::Cow, fmt};
|
||||
use leptos_reactive::Oco;
|
||||
use std::fmt;
|
||||
|
||||
/// Trait which allows creating an element tag.
|
||||
pub trait ElementDescriptor: ElementDescriptorBounds {
|
||||
/// The name of the element, i.e., `div`, `p`, `custom-element`.
|
||||
fn name(&self) -> Cow<'static, str>;
|
||||
fn name(&self) -> Oco<'static, str>;
|
||||
|
||||
/// Determines if the tag is void, i.e., `<input>` and `<br>`.
|
||||
#[inline(always)]
|
||||
@@ -126,7 +127,7 @@ where
|
||||
/// Represents potentially any element.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AnyElement {
|
||||
pub(crate) name: Cow<'static, str>,
|
||||
pub(crate) name: Oco<'static, str>,
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
pub(crate) element: web_sys::HtmlElement,
|
||||
pub(crate) is_void: bool,
|
||||
@@ -159,7 +160,7 @@ impl std::convert::AsRef<web_sys::HtmlElement> for AnyElement {
|
||||
}
|
||||
|
||||
impl ElementDescriptor for AnyElement {
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
fn name(&self) -> Oco<'static, str> {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
@@ -178,7 +179,7 @@ impl ElementDescriptor for AnyElement {
|
||||
/// Represents a custom HTML element, such as `<my-element>`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Custom {
|
||||
name: Cow<'static, str>,
|
||||
name: Oco<'static, str>,
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
element: web_sys::HtmlElement,
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
@@ -187,7 +188,7 @@ pub struct Custom {
|
||||
|
||||
impl Custom {
|
||||
/// Creates a new custom element with the given tag name.
|
||||
pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
|
||||
pub fn new(name: impl Into<Oco<'static, str>>) -> Self {
|
||||
let name = name.into();
|
||||
let id = HydrationCtx::id();
|
||||
|
||||
@@ -266,7 +267,7 @@ impl std::convert::AsRef<web_sys::HtmlElement> for Custom {
|
||||
}
|
||||
|
||||
impl ElementDescriptor for Custom {
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
fn name(&self) -> Oco<'static, str> {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
@@ -294,12 +295,12 @@ cfg_if! {
|
||||
#[derive(educe::Educe, Clone)]
|
||||
#[educe(Debug)]
|
||||
pub struct HtmlElement<El: ElementDescriptor> {
|
||||
pub(crate) element: El,
|
||||
pub(crate) attrs: SmallVec<[(Cow<'static, str>, Cow<'static, str>); 4]>,
|
||||
#[educe(Debug(ignore))]
|
||||
pub(crate) children: ElementChildren,
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) view_marker: Option<String>
|
||||
pub(crate) element: El,
|
||||
pub(crate) attrs: SmallVec<[(Oco<'static, str>, Oco<'static, str>); 4]>,
|
||||
#[educe(Debug(ignore))]
|
||||
pub(crate) children: ElementChildren,
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) view_marker: Option<String>
|
||||
}
|
||||
|
||||
#[derive(Clone, educe::Educe, PartialEq, Eq)]
|
||||
@@ -308,14 +309,14 @@ cfg_if! {
|
||||
#[educe(Default)]
|
||||
Empty,
|
||||
Children(Vec<View>),
|
||||
InnerHtml(Cow<'static, str>),
|
||||
InnerHtml(Oco<'static, str>),
|
||||
Chunks(Vec<StringOrView>)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone)]
|
||||
pub enum StringOrView {
|
||||
String(Cow<'static, str>),
|
||||
String(Oco<'static, str>),
|
||||
View(std::rc::Rc<dyn Fn() -> View>)
|
||||
}
|
||||
|
||||
@@ -445,7 +446,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
/// Adds an `id` to the element.
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn id(self, id: impl Into<Cow<'static, str>>) -> Self {
|
||||
pub fn id(self, id: impl Into<Oco<'static, str>>) -> Self {
|
||||
let id = id.into();
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
@@ -575,7 +576,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
#[cfg_attr(all(target_arch = "wasm32", feature = "web"), inline(always))]
|
||||
pub fn attr(
|
||||
self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
name: impl Into<Oco<'static, str>>,
|
||||
attr: impl IntoAttribute,
|
||||
) -> Self {
|
||||
let name = name.into();
|
||||
@@ -634,7 +635,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
#[track_caller]
|
||||
pub fn class(
|
||||
self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
name: impl Into<Oco<'static, str>>,
|
||||
class: impl IntoClass,
|
||||
) -> Self {
|
||||
let name = name.into();
|
||||
@@ -686,7 +687,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
/// Adds a list of classes separated by ASCII whitespace to an element.
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn classes(self, classes: impl Into<Cow<'static, str>>) -> Self {
|
||||
pub fn classes(self, classes: impl Into<Oco<'static, str>>) -> Self {
|
||||
self.classes_inner(&classes.into())
|
||||
}
|
||||
|
||||
@@ -698,7 +699,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = C>,
|
||||
C: Into<Cow<'static, str>>,
|
||||
C: Into<Oco<'static, str>>,
|
||||
{
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
{
|
||||
@@ -708,12 +709,12 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
|
||||
leptos_reactive::create_effect(
|
||||
move |prev_classes: Option<
|
||||
SmallVec<[Cow<'static, str>; 4]>,
|
||||
SmallVec<[Oco<'static, str>; 4]>,
|
||||
>| {
|
||||
let classes = classes_signal()
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<SmallVec<[Cow<'static, str>; 4]>>(
|
||||
.collect::<SmallVec<[Oco<'static, str>; 4]>>(
|
||||
);
|
||||
|
||||
let new_classes = classes
|
||||
@@ -797,7 +798,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
#[track_caller]
|
||||
pub fn style(
|
||||
self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
name: impl Into<Oco<'static, str>>,
|
||||
style: impl IntoStyle,
|
||||
) -> Self {
|
||||
let name = name.into();
|
||||
@@ -856,7 +857,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
#[track_caller]
|
||||
pub fn prop(
|
||||
self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
name: impl Into<Oco<'static, str>>,
|
||||
value: impl IntoProperty,
|
||||
) -> Self {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
@@ -1016,7 +1017,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
/// sanitize the input to avoid a cross-site scripting (XSS)
|
||||
/// vulnerability.
|
||||
#[inline(always)]
|
||||
pub fn inner_html(self, html: impl Into<Cow<'static, str>>) -> Self {
|
||||
pub fn inner_html(self, html: impl Into<Oco<'static, str>>) -> Self {
|
||||
let html = html.into();
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
@@ -1103,7 +1104,7 @@ pub fn custom<El: ElementDescriptor>(el: El) -> HtmlElement<Custom> {
|
||||
|
||||
/// Creates a text node.
|
||||
#[inline(always)]
|
||||
pub fn text(text: impl Into<Cow<'static, str>>) -> Text {
|
||||
pub fn text(text: impl Into<Oco<'static, str>>) -> Text {
|
||||
Text::new(text.into())
|
||||
}
|
||||
|
||||
@@ -1190,7 +1191,7 @@ macro_rules! generate_html_tags {
|
||||
|
||||
impl ElementDescriptor for [<$tag:camel $($trailing_)?>] {
|
||||
#[inline(always)]
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
fn name(&self) -> Oco<'static, str> {
|
||||
stringify!($tag).into()
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ pub use events::{typed as ev, typed::EventHandler};
|
||||
pub use html::HtmlElement;
|
||||
use html::{AnyElement, ElementDescriptor};
|
||||
pub use hydration::{HydrationCtx, HydrationKey};
|
||||
use leptos_reactive::Oco;
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
use leptos_reactive::{
|
||||
MaybeProp, MaybeSignal, Memo, ReadSignal, RwSignal, Signal, SignalGet,
|
||||
@@ -240,7 +241,7 @@ cfg_if! {
|
||||
pub struct Element {
|
||||
#[doc(hidden)]
|
||||
#[cfg(debug_assertions)]
|
||||
pub name: Cow<'static, str>,
|
||||
pub name: Oco<'static, str>,
|
||||
#[doc(hidden)]
|
||||
pub element: web_sys::HtmlElement,
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -261,9 +262,9 @@ cfg_if! {
|
||||
/// HTML element.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct Element {
|
||||
name: Cow<'static, str>,
|
||||
name: Oco<'static, str>,
|
||||
is_void: bool,
|
||||
attrs: SmallVec<[(Cow<'static, str>, Cow<'static, str>); 4]>,
|
||||
attrs: SmallVec<[(Oco<'static, str>, Oco<'static, str>); 4]>,
|
||||
children: ElementChildren,
|
||||
id: HydrationKey,
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -396,13 +397,13 @@ impl Element {
|
||||
struct Comment {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
node: web_sys::Node,
|
||||
content: Cow<'static, str>,
|
||||
content: Oco<'static, str>,
|
||||
}
|
||||
|
||||
impl Comment {
|
||||
#[inline]
|
||||
fn new(
|
||||
content: impl Into<Cow<'static, str>>,
|
||||
content: impl Into<Oco<'static, str>>,
|
||||
id: &HydrationKey,
|
||||
closing: bool,
|
||||
) -> Self {
|
||||
@@ -410,7 +411,7 @@ impl Comment {
|
||||
}
|
||||
|
||||
fn new_inner(
|
||||
content: Cow<'static, str>,
|
||||
content: Oco<'static, str>,
|
||||
id: &HydrationKey,
|
||||
closing: bool,
|
||||
) -> Self {
|
||||
@@ -466,12 +467,13 @@ pub struct Text {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
node: web_sys::Node,
|
||||
/// The current contents of the text node.
|
||||
pub content: Cow<'static, str>,
|
||||
pub content: Oco<'static, str>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Text {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "\"{}\"", self.content)
|
||||
fmt::Debug::fmt(&self.content, f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,7 +486,7 @@ impl IntoView for Text {
|
||||
|
||||
impl Text {
|
||||
/// Creates a new [`Text`].
|
||||
pub fn new(content: Cow<'static, str>) -> Self {
|
||||
pub fn new(content: Oco<'static, str>) -> Self {
|
||||
Self {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
node: crate::document()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use leptos_reactive::Oco;
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
use leptos_reactive::{
|
||||
MaybeProp, MaybeSignal, Memo, ReadSignal, RwSignal, Signal, SignalGet,
|
||||
@@ -14,11 +15,11 @@ use wasm_bindgen::UnwrapThrowExt;
|
||||
#[derive(Clone)]
|
||||
pub enum Attribute {
|
||||
/// A plain string value.
|
||||
String(Cow<'static, str>),
|
||||
String(Oco<'static, str>),
|
||||
/// A (presumably reactive) function, which will be run inside an effect to do targeted updates to the attribute.
|
||||
Fn(Rc<dyn Fn() -> Attribute>),
|
||||
/// An optional string value, which sets the attribute to the value if `Some` and removes the attribute if `None`.
|
||||
Option(Option<Cow<'static, str>>),
|
||||
Option(Option<Oco<'static, str>>),
|
||||
/// A boolean attribute, which sets the attribute if `true` and removes the attribute if `false`.
|
||||
Bool(bool),
|
||||
}
|
||||
@@ -29,7 +30,7 @@ impl Attribute {
|
||||
pub fn as_value_string(
|
||||
&self,
|
||||
attr_name: &'static str,
|
||||
) -> Cow<'static, str> {
|
||||
) -> Oco<'static, str> {
|
||||
match self {
|
||||
Attribute::String(value) => {
|
||||
format!("{attr_name}=\"{value}\"").into()
|
||||
@@ -46,14 +47,14 @@ impl Attribute {
|
||||
.map(|value| format!("{attr_name}=\"{value}\"").into())
|
||||
.unwrap_or_default(),
|
||||
Attribute::Bool(include) => {
|
||||
Cow::Borrowed(if *include { attr_name } else { "" })
|
||||
Oco::Borrowed(if *include { attr_name } else { "" })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the attribute to its HTML value at that moment, not including
|
||||
/// the attribute name, so it can be rendered on the server.
|
||||
pub fn as_nameless_value_string(&self) -> Option<Cow<'static, str>> {
|
||||
pub fn as_nameless_value_string(&self) -> Option<Oco<'static, str>> {
|
||||
match self {
|
||||
Attribute::String(value) => Some(value.clone()),
|
||||
Attribute::Fn(f) => {
|
||||
@@ -148,7 +149,7 @@ impl IntoAttribute for Option<Attribute> {
|
||||
impl IntoAttribute for String {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::String(Cow::Owned(self))
|
||||
Attribute::String(Oco::Owned(self))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
@@ -157,13 +158,22 @@ impl IntoAttribute for String {
|
||||
impl IntoAttribute for &'static str {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::String(Cow::Borrowed(self))
|
||||
Attribute::String(Oco::Borrowed(self))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Cow<'static, str> {
|
||||
impl IntoAttribute for Rc<str> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::String(Oco::Counted(self))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Oco<'static, str> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::String(self)
|
||||
@@ -184,7 +194,7 @@ impl IntoAttribute for bool {
|
||||
impl IntoAttribute for Option<String> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::Option(self.map(Cow::Owned))
|
||||
Attribute::Option(self.map(Oco::Owned))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
@@ -193,13 +203,31 @@ impl IntoAttribute for Option<String> {
|
||||
impl IntoAttribute for Option<&'static str> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::Option(self.map(Cow::Borrowed))
|
||||
Attribute::Option(self.map(Oco::Borrowed))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Option<Rc<str>> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::Option(self.map(Oco::Counted))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Option<Cow<'static, str>> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::Option(self.map(Oco::from))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Option<Oco<'static, str>> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::Option(self)
|
||||
@@ -331,7 +359,7 @@ attr_signal_type_optional!(MaybeProp<T>);
|
||||
#[inline(never)]
|
||||
pub fn attribute_helper(
|
||||
el: &web_sys::Element,
|
||||
name: Cow<'static, str>,
|
||||
name: Oco<'static, str>,
|
||||
value: Attribute,
|
||||
) {
|
||||
use leptos_reactive::create_render_effect;
|
||||
|
||||
@@ -65,14 +65,14 @@ impl Class {
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use std::borrow::Cow;
|
||||
use leptos_reactive::Oco;
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
#[doc(hidden)]
|
||||
#[inline(never)]
|
||||
pub fn class_helper(
|
||||
el: &web_sys::Element,
|
||||
name: Cow<'static, str>,
|
||||
name: Oco<'static, str>,
|
||||
value: Class,
|
||||
) {
|
||||
use leptos_reactive::create_render_effect;
|
||||
|
||||
@@ -115,13 +115,13 @@ prop_signal_type!(MaybeSignal<T>);
|
||||
prop_signal_type_optional!(MaybeProp<T>);
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use std::borrow::Cow;
|
||||
use leptos_reactive::Oco;
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
#[inline(never)]
|
||||
pub(crate) fn property_helper(
|
||||
el: &web_sys::Element,
|
||||
name: Cow<'static, str>,
|
||||
name: Oco<'static, str>,
|
||||
value: Property,
|
||||
) {
|
||||
use leptos_reactive::create_render_effect;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use leptos_reactive::Oco;
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
use leptos_reactive::{
|
||||
MaybeProp, MaybeSignal, Memo, ReadSignal, RwSignal, Signal, SignalGet,
|
||||
@@ -8,9 +9,9 @@ use std::{borrow::Cow, rc::Rc};
|
||||
#[derive(Clone)]
|
||||
pub enum Style {
|
||||
/// A plain string value.
|
||||
Value(Cow<'static, str>),
|
||||
Value(Oco<'static, str>),
|
||||
/// An optional string value, which sets the property to the value if `Some` and removes the property if `None`.
|
||||
Option(Option<Cow<'static, str>>),
|
||||
Option(Option<Oco<'static, str>>),
|
||||
/// A (presumably reactive) function, which will be run inside an effect to update the style.
|
||||
Fn(Rc<dyn Fn() -> Style>),
|
||||
}
|
||||
@@ -45,28 +46,70 @@ pub trait IntoStyle {
|
||||
impl IntoStyle for &'static str {
|
||||
#[inline(always)]
|
||||
fn into_style(self) -> Style {
|
||||
Style::Value(self.into())
|
||||
Style::Value(Oco::Borrowed(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for String {
|
||||
#[inline(always)]
|
||||
fn into_style(self) -> Style {
|
||||
Style::Value(Oco::Owned(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Rc<str> {
|
||||
#[inline(always)]
|
||||
fn into_style(self) -> Style {
|
||||
Style::Value(Oco::Counted(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Cow<'static, str> {
|
||||
#[inline(always)]
|
||||
fn into_style(self) -> Style {
|
||||
Style::Value(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Oco<'static, str> {
|
||||
#[inline(always)]
|
||||
fn into_style(self) -> Style {
|
||||
Style::Value(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Option<&'static str> {
|
||||
#[inline(always)]
|
||||
fn into_style(self) -> Style {
|
||||
Style::Option(self.map(Cow::Borrowed))
|
||||
Style::Option(self.map(Oco::Borrowed))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Option<String> {
|
||||
#[inline(always)]
|
||||
fn into_style(self) -> Style {
|
||||
Style::Option(self.map(Cow::Owned))
|
||||
Style::Option(self.map(Oco::Owned))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Option<Rc<str>> {
|
||||
#[inline(always)]
|
||||
fn into_style(self) -> Style {
|
||||
Style::Option(self.map(Oco::Counted))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Option<Cow<'static, str>> {
|
||||
#[inline(always)]
|
||||
fn into_style(self) -> Style {
|
||||
Style::Option(self.map(Oco::from))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Option<Oco<'static, str>> {
|
||||
#[inline(always)]
|
||||
fn into_style(self) -> Style {
|
||||
Style::Option(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +130,7 @@ impl Style {
|
||||
pub fn as_value_string(
|
||||
&self,
|
||||
style_name: &'static str,
|
||||
) -> Option<Cow<'static, str>> {
|
||||
) -> Option<Oco<'static, str>> {
|
||||
match self {
|
||||
Style::Value(value) => {
|
||||
Some(format!("{style_name}: {value};").into())
|
||||
@@ -111,10 +154,11 @@ impl Style {
|
||||
#[inline(never)]
|
||||
pub fn style_helper(
|
||||
el: &web_sys::Element,
|
||||
name: Cow<'static, str>,
|
||||
name: Oco<'static, str>,
|
||||
value: Style,
|
||||
) {
|
||||
use leptos_reactive::create_render_effect;
|
||||
use std::ops::Deref;
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
let el = el.unchecked_ref::<web_sys::HtmlElement>();
|
||||
@@ -132,16 +176,16 @@ pub fn style_helper(
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if old.as_ref() != Some(&new) {
|
||||
style_expression(&style_list, &name, new.as_ref(), true)
|
||||
style_expression(&style_list, &name, new.as_deref(), true)
|
||||
}
|
||||
new
|
||||
});
|
||||
}
|
||||
Style::Value(value) => {
|
||||
style_expression(&style_list, &name, Some(&value), false)
|
||||
style_expression(&style_list, &name, Some(value.deref()), false)
|
||||
}
|
||||
Style::Option(value) => {
|
||||
style_expression(&style_list, &name, value.as_ref(), false)
|
||||
style_expression(&style_list, &name, value.as_deref(), false)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -151,7 +195,7 @@ pub fn style_helper(
|
||||
pub(crate) fn style_expression(
|
||||
style_list: &web_sys::CssStyleDeclaration,
|
||||
style_name: &str,
|
||||
value: Option<&Cow<'static, str>>,
|
||||
value: Option<&str>,
|
||||
force: bool,
|
||||
) {
|
||||
use crate::HydrationCtx;
|
||||
@@ -160,7 +204,7 @@ pub(crate) fn style_expression(
|
||||
let style_name = wasm_bindgen::intern(style_name);
|
||||
|
||||
if let Some(value) = value {
|
||||
if let Err(e) = style_list.set_property(style_name, &value) {
|
||||
if let Err(e) = style_list.set_property(style_name, value) {
|
||||
crate::error!("[HtmlElement::style()] {e:?}");
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -5,7 +5,7 @@ use wasm_bindgen::UnwrapThrowExt;
|
||||
macro_rules! tracing_props {
|
||||
() => {
|
||||
::leptos::leptos_dom::tracing::span!(
|
||||
::leptos::leptos_dom::tracing::Level::DEBUG,
|
||||
::leptos::leptos_dom::tracing::Level::TRACE,
|
||||
"leptos_dom::tracing_props",
|
||||
props = String::from("[]")
|
||||
);
|
||||
@@ -24,7 +24,7 @@ macro_rules! tracing_props {
|
||||
props.pop();
|
||||
props.push(']');
|
||||
::leptos::leptos_dom::tracing::span!(
|
||||
::leptos::leptos_dom::tracing::Level::DEBUG,
|
||||
::leptos::leptos_dom::tracing::Level::TRACE,
|
||||
"leptos_dom::tracing_props",
|
||||
props
|
||||
);
|
||||
@@ -48,10 +48,21 @@ impl<T: serde::Serialize> SerializeMatch for &Match<&T> {
|
||||
type Return = String;
|
||||
fn spez(&self) -> Self::Return {
|
||||
let name = self.name;
|
||||
serde_json::to_string(self.value.get().unwrap_throw()).map_or_else(
|
||||
|err| format!(r#"{{"name": "{name}", "error": "{err}"}}"#),
|
||||
|value| format!(r#"{{"name": "{name}", "value": {value}}}"#),
|
||||
)
|
||||
|
||||
// suppresses warnings when serializing signals into props
|
||||
#[cfg(debug_assertions)]
|
||||
let prev = leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
|
||||
let value = serde_json::to_string(self.value.get().unwrap_throw())
|
||||
.map_or_else(
|
||||
|err| format!(r#"{{"name": "{name}", "error": "{err}"}}"#),
|
||||
|value| format!(r#"{{"name": "{name}", "value": {value}}}"#),
|
||||
);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
leptos_reactive::SpecialNonReactiveZone::exit(prev);
|
||||
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,9 +74,7 @@ impl<T> DefaultMatch for Match<&T> {
|
||||
type Return = String;
|
||||
fn spez(&self) -> Self::Return {
|
||||
let name = self.name;
|
||||
format!(
|
||||
r#"{{"name": "{name}", "error": "The trait `serde::Serialize` is not implemented"}}"#
|
||||
)
|
||||
format!(r#"{{"name": "{name}", "value": "[unserializable value]"}}"#)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +156,8 @@ fn match_serialize() {
|
||||
|
||||
#[test]
|
||||
fn match_no_serialize() {
|
||||
#![allow(clippy::needless_borrow)]
|
||||
|
||||
struct CustomStruct {
|
||||
field: &'static str,
|
||||
}
|
||||
@@ -159,7 +170,7 @@ fn match_no_serialize() {
|
||||
.spez();
|
||||
assert_eq!(
|
||||
prop,
|
||||
r#"{"name": "test", "error": "The trait `serde::Serialize` is not implemented"}"#
|
||||
r#"{"name": "test", "value": "[unserializable value]"}"#
|
||||
);
|
||||
// Verification of ownership
|
||||
assert_eq!(test.field, "field");
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use super::{ElementDescriptor, HtmlElement};
|
||||
use crate::HydrationCtx;
|
||||
use cfg_if::cfg_if;
|
||||
use std::borrow::Cow;
|
||||
use leptos_reactive::Oco;
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
use once_cell::unsync::Lazy as LazyCell;
|
||||
@@ -145,7 +145,7 @@ macro_rules! generate_math_tags {
|
||||
}
|
||||
|
||||
impl ElementDescriptor for [<$tag:camel $($second:camel $($third:camel)?)?>] {
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
fn name(&self) -> Oco<'static, str> {
|
||||
stringify!($tag).into()
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ use crate::{
|
||||
use cfg_if::cfg_if;
|
||||
use futures::{stream::FuturesUnordered, Future, Stream, StreamExt};
|
||||
use itertools::Itertools;
|
||||
use leptos_reactive::*;
|
||||
use std::{borrow::Cow, pin::Pin};
|
||||
use leptos_reactive::{Oco, *};
|
||||
use std::pin::Pin;
|
||||
|
||||
type PinnedFuture<T> = Pin<Box<dyn Future<Output = T>>>;
|
||||
|
||||
@@ -30,7 +30,7 @@ type PinnedFuture<T> = Pin<Box<dyn Future<Output = T>>>;
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
instrument(level = "info", skip_all,)
|
||||
)]
|
||||
pub fn render_to_string<F, N>(f: F) -> String
|
||||
pub fn render_to_string<F, N>(f: F) -> Oco<'static, str>
|
||||
where
|
||||
F: FnOnce() -> N + 'static,
|
||||
N: IntoView,
|
||||
@@ -42,7 +42,7 @@ where
|
||||
|
||||
runtime.dispose();
|
||||
|
||||
html.into()
|
||||
html
|
||||
}
|
||||
|
||||
/// Renders a function to a stream of HTML strings.
|
||||
@@ -87,7 +87,7 @@ pub fn render_to_stream(
|
||||
)]
|
||||
pub fn render_to_stream_with_prefix(
|
||||
view: impl FnOnce() -> View + 'static,
|
||||
prefix: impl FnOnce() -> Cow<'static, str> + 'static,
|
||||
prefix: impl FnOnce() -> Oco<'static, str> + 'static,
|
||||
) -> impl Stream<Item = String> {
|
||||
let (stream, runtime) =
|
||||
render_to_stream_with_prefix_undisposed(view, prefix);
|
||||
@@ -116,7 +116,7 @@ pub fn render_to_stream_with_prefix(
|
||||
)]
|
||||
pub fn render_to_stream_with_prefix_undisposed(
|
||||
view: impl FnOnce() -> View + 'static,
|
||||
prefix: impl FnOnce() -> Cow<'static, str> + 'static,
|
||||
prefix: impl FnOnce() -> Oco<'static, str> + 'static,
|
||||
) -> (impl Stream<Item = String>, RuntimeId) {
|
||||
render_to_stream_with_prefix_undisposed_with_context(view, prefix, || {})
|
||||
}
|
||||
@@ -142,7 +142,7 @@ pub fn render_to_stream_with_prefix_undisposed(
|
||||
)]
|
||||
pub fn render_to_stream_with_prefix_undisposed_with_context(
|
||||
view: impl FnOnce() -> View + 'static,
|
||||
prefix: impl FnOnce() -> Cow<'static, str> + 'static,
|
||||
prefix: impl FnOnce() -> Oco<'static, str> + 'static,
|
||||
additional_context: impl FnOnce() + 'static,
|
||||
) -> (impl Stream<Item = String>, RuntimeId) {
|
||||
render_to_stream_with_prefix_undisposed_with_context_and_block_replacement(
|
||||
@@ -179,7 +179,7 @@ pub fn render_to_stream_with_prefix_undisposed_with_context(
|
||||
)]
|
||||
pub fn render_to_stream_with_prefix_undisposed_with_context_and_block_replacement(
|
||||
view: impl FnOnce() -> View + 'static,
|
||||
prefix: impl FnOnce() -> Cow<'static, str> + 'static,
|
||||
prefix: impl FnOnce() -> Oco<'static, str> + 'static,
|
||||
additional_context: impl FnOnce() + 'static,
|
||||
replace_blocks: bool,
|
||||
) -> (impl Stream<Item = String>, RuntimeId) {
|
||||
@@ -363,7 +363,7 @@ impl View {
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
instrument(level = "info", skip_all,)
|
||||
)]
|
||||
pub fn render_to_string(self) -> Cow<'static, str> {
|
||||
pub fn render_to_string(self) -> Oco<'static, str> {
|
||||
#[cfg(all(feature = "web", feature = "ssr"))]
|
||||
crate::console_error(
|
||||
"\n[DANGER] You have both `csr` and `ssr` or `hydrate` and `ssr` \
|
||||
@@ -381,7 +381,7 @@ impl View {
|
||||
pub(crate) fn render_to_string_helper(
|
||||
self,
|
||||
dont_escape_text: bool,
|
||||
) -> Cow<'static, str> {
|
||||
) -> Oco<'static, str> {
|
||||
match self {
|
||||
View::Text(node) => {
|
||||
if dont_escape_text {
|
||||
@@ -450,7 +450,7 @@ impl View {
|
||||
)
|
||||
.into()
|
||||
})
|
||||
as Box<dyn FnOnce() -> Cow<'static, str>>,
|
||||
as Box<dyn FnOnce() -> Oco<'static, str>>,
|
||||
),
|
||||
CoreComponent::DynChild(node) => {
|
||||
let child = node.child.take();
|
||||
@@ -500,7 +500,7 @@ impl View {
|
||||
"".into()
|
||||
}
|
||||
})
|
||||
as Box<dyn FnOnce() -> Cow<'static, str>>,
|
||||
as Box<dyn FnOnce() -> Oco<'static, str>>,
|
||||
)
|
||||
}
|
||||
CoreComponent::Each(node) => {
|
||||
@@ -554,7 +554,7 @@ impl View {
|
||||
.join("")
|
||||
.into()
|
||||
})
|
||||
as Box<dyn FnOnce() -> Cow<'static, str>>,
|
||||
as Box<dyn FnOnce() -> Oco<'static, str>>,
|
||||
)
|
||||
}
|
||||
};
|
||||
@@ -598,15 +598,15 @@ impl View {
|
||||
.join("")
|
||||
.into()
|
||||
} else {
|
||||
let tag_name = el.name;
|
||||
let tag_name: Oco<'_, str> = el.name;
|
||||
|
||||
let mut inner_html = None;
|
||||
let mut inner_html: Option<Oco<'_, str>> = None;
|
||||
|
||||
let attrs = el
|
||||
.attrs
|
||||
.into_iter()
|
||||
.filter_map(
|
||||
|(name, value)| -> Option<Cow<'static, str>> {
|
||||
|(name, value)| -> Option<Oco<'static, str>> {
|
||||
if value.is_empty() {
|
||||
Some(format!(" {name}").into())
|
||||
} else if name == "inner_html" {
|
||||
@@ -615,9 +615,9 @@ impl View {
|
||||
} else {
|
||||
Some(
|
||||
format!(
|
||||
" {name}=\"{}\"",
|
||||
html_escape::encode_double_quoted_attribute(&value)
|
||||
)
|
||||
" {name}=\"{}\"",
|
||||
html_escape::encode_double_quoted_attribute(&value)
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
@@ -729,9 +729,9 @@ pub(crate) fn render_serializers(
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn escape_attr<T>(value: &T) -> Cow<'_, str>
|
||||
pub fn escape_attr<T>(value: &T) -> Oco<'_, str>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
html_escape::encode_double_quoted_attribute(value)
|
||||
html_escape::encode_double_quoted_attribute(value).into()
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use cfg_if::cfg_if;
|
||||
use futures::{channel::mpsc::UnboundedSender, Stream, StreamExt};
|
||||
use itertools::Itertools;
|
||||
use leptos_reactive::{
|
||||
create_runtime, suspense::StreamChunk, RuntimeId, SharedContext,
|
||||
create_runtime, suspense::StreamChunk, Oco, RuntimeId, SharedContext,
|
||||
};
|
||||
use std::{borrow::Cow, collections::VecDeque};
|
||||
|
||||
@@ -59,7 +59,7 @@ pub fn render_to_stream_in_order(
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub fn render_to_stream_in_order_with_prefix(
|
||||
view: impl FnOnce() -> View + 'static,
|
||||
prefix: impl FnOnce() -> Cow<'static, str> + 'static,
|
||||
prefix: impl FnOnce() -> Oco<'static, str> + 'static,
|
||||
) -> impl Stream<Item = String> {
|
||||
#[cfg(all(feature = "web", feature = "ssr"))]
|
||||
crate::console_error(
|
||||
@@ -89,7 +89,7 @@ pub fn render_to_stream_in_order_with_prefix(
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub fn render_to_stream_in_order_with_prefix_undisposed_with_context(
|
||||
view: impl FnOnce() -> View + 'static,
|
||||
prefix: impl FnOnce() -> Cow<'static, str> + 'static,
|
||||
prefix: impl FnOnce() -> Oco<'static, str> + 'static,
|
||||
additional_context: impl FnOnce() + 'static,
|
||||
) -> (impl Stream<Item = String>, RuntimeId) {
|
||||
HydrationCtx::reset_id();
|
||||
@@ -287,12 +287,11 @@ impl View {
|
||||
StringOrView::String(string) => {
|
||||
chunks.push_back(StreamChunk::Sync(string))
|
||||
}
|
||||
StringOrView::View(view) => {
|
||||
view().into_stream_chunks_helper(
|
||||
StringOrView::View(view) => view()
|
||||
.into_stream_chunks_helper(
|
||||
chunks,
|
||||
is_script_or_style,
|
||||
);
|
||||
}
|
||||
),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -313,9 +312,9 @@ impl View {
|
||||
} else {
|
||||
Some(
|
||||
format!(
|
||||
" {name}=\"{}\"",
|
||||
html_escape::encode_double_quoted_attribute(&value)
|
||||
)
|
||||
" {name}=\"{}\"",
|
||||
html_escape::encode_double_quoted_attribute(&value)
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
@@ -350,7 +349,7 @@ impl View {
|
||||
}
|
||||
}
|
||||
ElementChildren::InnerHtml(inner_html) => {
|
||||
chunks.push_back(StreamChunk::Sync(inner_html));
|
||||
chunks.push_back(StreamChunk::Sync(inner_html))
|
||||
}
|
||||
// handled above
|
||||
ElementChildren::Chunks(_) => unreachable!(),
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
use super::{html::HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG, HydrationKey};
|
||||
use super::{ElementDescriptor, HtmlElement};
|
||||
use crate::HydrationCtx;
|
||||
use leptos_reactive::Oco;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use once_cell::unsync::Lazy as LazyCell;
|
||||
use std::borrow::Cow;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
@@ -142,7 +142,7 @@ macro_rules! generate_svg_tags {
|
||||
}
|
||||
|
||||
impl ElementDescriptor for [<$tag:camel $($second:camel $($third:camel)?)?>] {
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
fn name(&self) -> Oco<'static, str> {
|
||||
stringify!($tag).into()
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ tracing = "0.1.37"
|
||||
|
||||
[dev-dependencies]
|
||||
log = "0.4"
|
||||
typed-builder = "0.14"
|
||||
typed-builder = "0.16"
|
||||
trybuild = "1"
|
||||
leptos = { path = "../leptos" }
|
||||
insta = "1.29"
|
||||
|
||||
@@ -252,8 +252,9 @@ impl ToTokens for Model {
|
||||
#[doc = ""]
|
||||
#docs
|
||||
#component_fn_prop_docs
|
||||
#[derive(::leptos::typed_builder::TypedBuilder)]
|
||||
#[builder(doc)]
|
||||
#[derive(::leptos::typed_builder_macro::TypedBuilder)]
|
||||
//#[builder(doc)]
|
||||
#[builder(crate_module_path=::leptos::typed_builder)]
|
||||
#vis struct #props_name #impl_generics #where_clause {
|
||||
#prop_builder_fields
|
||||
}
|
||||
@@ -554,7 +555,11 @@ impl ToTokens for TypedBuilderOpts {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let output = quote! { #[builder(#default #setter)] };
|
||||
let output = if !default.is_empty() || !setter.is_empty() {
|
||||
quote! { #[builder(#default #setter)] }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
tokens.append_all(output);
|
||||
}
|
||||
|
||||
@@ -818,6 +818,10 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
/// - 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 functions must have unique paths**. Unique paths are automatically generated for each
|
||||
/// server function. If you choose to specify a path in the fourth argument, you must ensure that these
|
||||
/// are unique. You cannot define two server functions with the same URL prefix and endpoint path,
|
||||
/// even if they have different URL encodings, e.g. a POST method at `/api/foo` and a GET method at `/api/foo`.
|
||||
///
|
||||
/// ## Server Function Encodings
|
||||
///
|
||||
|
||||
@@ -19,7 +19,7 @@ pub fn impl_params(ast: &syn::DeriveInput) -> proc_macro::TokenStream {
|
||||
let span = field.span();
|
||||
|
||||
quote_spanned! {
|
||||
span => #ident: <#ty>::into_param(map.get(#field_name_string).map(|n| n.as_str()), #field_name_string)?
|
||||
span => #ident: <#ty>::into_param(map.get(#field_name_string).map(::std::string::String::as_str), #field_name_string)?
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
||||
@@ -79,8 +79,8 @@ impl ToTokens for Model {
|
||||
#[doc = ""]
|
||||
#docs
|
||||
#prop_docs
|
||||
#[derive(::leptos::typed_builder::TypedBuilder)]
|
||||
#[builder(doc)]
|
||||
#[derive(::leptos::typed_builder_macro::TypedBuilder)]
|
||||
#[builder(doc, crate_module_path=::leptos::typed_builder)]
|
||||
#vis struct #name #generics #where_clause {
|
||||
#prop_builder_fields
|
||||
}
|
||||
@@ -191,7 +191,11 @@ impl ToTokens for TypedBuilderOpts {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let output = quote! { #[builder(#default #setter)] };
|
||||
let output = if !default.is_empty() || !setter.is_empty() {
|
||||
quote! { #[builder(#default #setter)] }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
tokens.append_all(output);
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ mod effect;
|
||||
mod hydration;
|
||||
mod memo;
|
||||
mod node;
|
||||
pub mod oco;
|
||||
mod resource;
|
||||
mod runtime;
|
||||
mod selector;
|
||||
@@ -107,6 +108,7 @@ pub use effect::*;
|
||||
pub use hydration::{FragmentData, SharedContext};
|
||||
pub use memo::*;
|
||||
pub use node::Disposer;
|
||||
pub use oco::*;
|
||||
pub use resource::*;
|
||||
use runtime::*;
|
||||
pub use runtime::{
|
||||
|
||||
681
leptos_reactive/src/oco.rs
Normal file
681
leptos_reactive/src/oco.rs
Normal file
@@ -0,0 +1,681 @@
|
||||
//! This module contains the `Oco` (Owned Clones Once) smart pointer,
|
||||
//! which is used to store immutable references to values.
|
||||
//! This is useful for storing, for example, strings.
|
||||
|
||||
use std::{
|
||||
borrow::{Borrow, Cow},
|
||||
ffi::{CStr, OsStr},
|
||||
fmt,
|
||||
hash::Hash,
|
||||
ops::{Add, Deref},
|
||||
path::Path,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
/// "Owned Clones Once" - a smart pointer that can be either a reference,
|
||||
/// an owned value, or a reference counted pointer. This is useful for
|
||||
/// storing immutable values, such as strings, in a way that is cheap to
|
||||
/// clone and pass around.
|
||||
///
|
||||
/// The `Clone` implementation is amortized `O(1)`. Cloning the [`Oco::Borrowed`]
|
||||
/// variant simply copies the references (`O(1)`). Cloning the [`Oco::Counted`]
|
||||
/// variant increments a reference count (`O(1)`). Cloning the [`Oco::Owned`]
|
||||
/// variant upgrades it to [`Oco::Counted`], which requires an `O(n)` clone of the
|
||||
/// data, but all subsequent clones will be `O(1)`.
|
||||
pub enum Oco<'a, T: ?Sized + ToOwned + 'a> {
|
||||
/// A static reference to a value.
|
||||
Borrowed(&'a T),
|
||||
/// A reference counted pointer to a value.
|
||||
Counted(Rc<T>),
|
||||
/// An owned value.
|
||||
Owned(<T as ToOwned>::Owned),
|
||||
}
|
||||
|
||||
impl<T: ?Sized + ToOwned> Oco<'_, T> {
|
||||
/// Converts the value into an owned value.
|
||||
pub fn into_owned(self) -> <T as ToOwned>::Owned {
|
||||
match self {
|
||||
Oco::Borrowed(v) => v.to_owned(),
|
||||
Oco::Counted(v) => v.as_ref().to_owned(),
|
||||
Oco::Owned(v) => v,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the value is [`Oco::Borrowed`].
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::rc::Rc;
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// assert!(Oco::<str>::Borrowed("Hello").is_borrowed());
|
||||
/// assert!(!Oco::<str>::Counted(Rc::from("Hello")).is_borrowed());
|
||||
/// assert!(!Oco::<str>::Owned("Hello".to_string()).is_borrowed());
|
||||
/// ```
|
||||
pub const fn is_borrowed(&self) -> bool {
|
||||
matches!(self, Oco::Borrowed(_))
|
||||
}
|
||||
|
||||
/// Checks if the value is [`Oco::Counted`].
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::rc::Rc;
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// assert!(Oco::<str>::Counted(Rc::from("Hello")).is_counted());
|
||||
/// assert!(!Oco::<str>::Borrowed("Hello").is_counted());
|
||||
/// assert!(!Oco::<str>::Owned("Hello".to_string()).is_counted());
|
||||
/// ```
|
||||
pub const fn is_counted(&self) -> bool {
|
||||
matches!(self, Oco::Counted(_))
|
||||
}
|
||||
|
||||
/// Checks if the value is [`Oco::Owned`].
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::rc::Rc;
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// assert!(Oco::<str>::Owned("Hello".to_string()).is_owned());
|
||||
/// assert!(!Oco::<str>::Borrowed("Hello").is_owned());
|
||||
/// assert!(!Oco::<str>::Counted(Rc::from("Hello")).is_owned());
|
||||
/// ```
|
||||
pub const fn is_owned(&self) -> bool {
|
||||
matches!(self, Oco::Owned(_))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + ToOwned> Deref for Oco<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
match self {
|
||||
Oco::Borrowed(v) => v,
|
||||
Oco::Owned(v) => v.borrow(),
|
||||
Oco::Counted(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + ToOwned> Borrow<T> for Oco<'_, T> {
|
||||
#[inline(always)]
|
||||
fn borrow(&self) -> &T {
|
||||
self.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + ToOwned> AsRef<T> for Oco<'_, T> {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &T {
|
||||
self.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for Oco<'_, str> {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.as_str().as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for Oco<'_, OsStr> {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.as_os_str().as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// pub fn as_{slice}(&self) -> &{slice}
|
||||
// --------------------------------------
|
||||
|
||||
impl Oco<'_, str> {
|
||||
/// Returns a `&str` slice of this [`Oco`].
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// let oco = Oco::<str>::Borrowed("Hello");
|
||||
/// let s: &str = oco.as_str();
|
||||
/// assert_eq!(s, "Hello");
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn as_str(&self) -> &str {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Oco<'_, CStr> {
|
||||
/// Returns a `&CStr` slice of this [`Oco`].
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// use std::ffi::CStr;
|
||||
///
|
||||
/// let oco =
|
||||
/// Oco::<CStr>::Borrowed(CStr::from_bytes_with_nul(b"Hello\0").unwrap());
|
||||
/// let s: &CStr = oco.as_c_str();
|
||||
/// assert_eq!(s, CStr::from_bytes_with_nul(b"Hello\0").unwrap());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn as_c_str(&self) -> &CStr {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Oco<'_, OsStr> {
|
||||
/// Returns a `&OsStr` slice of this [`Oco`].
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// use std::ffi::OsStr;
|
||||
///
|
||||
/// let oco = Oco::<OsStr>::Borrowed(OsStr::new("Hello"));
|
||||
/// let s: &OsStr = oco.as_os_str();
|
||||
/// assert_eq!(s, OsStr::new("Hello"));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn as_os_str(&self) -> &OsStr {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Oco<'_, Path> {
|
||||
/// Returns a `&Path` slice of this [`Oco`].
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// use std::path::Path;
|
||||
///
|
||||
/// let oco = Oco::<Path>::Borrowed(Path::new("Hello"));
|
||||
/// let s: &Path = oco.as_path();
|
||||
/// assert_eq!(s, Path::new("Hello"));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn as_path(&self) -> &Path {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Oco<'_, [T]>
|
||||
where
|
||||
[T]: ToOwned,
|
||||
{
|
||||
/// Returns a `&[T]` slice of this [`Oco`].
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// let oco = Oco::<[u8]>::Borrowed(b"Hello");
|
||||
/// let s: &[u8] = oco.as_slice();
|
||||
/// assert_eq!(s, b"Hello");
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------
|
||||
// Cloning (has to be implemented manually because of the `Rc<T>: From<&<T as ToOwned>::Owned>` bound)
|
||||
// ------------------------------------------------------------------------------------------------------
|
||||
|
||||
impl Clone for Oco<'_, str> {
|
||||
/// Returns a new [`Oco`] with the same value as this one.
|
||||
/// If the value is [`Oco::Owned`], this will convert it into
|
||||
/// [`Oco::Counted`], so that the next clone will be O(1).
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// let oco = Oco::<str>::Owned("Hello".to_string());
|
||||
/// let oco2 = oco.clone();
|
||||
/// assert_eq!(oco, oco2);
|
||||
/// assert!(oco2.is_counted());
|
||||
/// ```
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Oco::Borrowed(v) => Oco::Borrowed(v),
|
||||
Oco::Counted(v) => Oco::Counted(v.clone()),
|
||||
Oco::Owned(v) => Oco::Counted(Rc::<str>::from(v.as_str())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Oco<'_, CStr> {
|
||||
/// Returns a new [`Oco`] with the same value as this one.
|
||||
/// If the value is [`Oco::Owned`], this will convert it into
|
||||
/// [`Oco::Counted`], so that the next clone will be O(1).
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// use std::ffi::CStr;
|
||||
///
|
||||
/// let oco = Oco::<CStr>::Owned(
|
||||
/// CStr::from_bytes_with_nul(b"Hello\0").unwrap().to_owned(),
|
||||
/// );
|
||||
/// let oco2 = oco.clone();
|
||||
/// assert_eq!(oco, oco2);
|
||||
/// assert!(oco2.is_counted());
|
||||
/// ```
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Oco::Borrowed(v) => Oco::Borrowed(v),
|
||||
Oco::Counted(v) => Oco::Counted(v.clone()),
|
||||
Oco::Owned(v) => Oco::Counted(Rc::<CStr>::from(v.as_c_str())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Oco<'_, OsStr> {
|
||||
/// Returns a new [`Oco`] with the same value as this one.
|
||||
/// If the value is [`Oco::Owned`], this will convert it into
|
||||
/// [`Oco::Counted`], so that the next clone will be O(1).
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// use std::ffi::OsStr;
|
||||
///
|
||||
/// let oco = Oco::<OsStr>::Owned(OsStr::new("Hello").to_owned());
|
||||
/// let oco2 = oco.clone();
|
||||
/// assert_eq!(oco, oco2);
|
||||
/// assert!(oco2.is_counted());
|
||||
/// ```
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Oco::Borrowed(v) => Oco::Borrowed(v),
|
||||
Oco::Counted(v) => Oco::Counted(v.clone()),
|
||||
Oco::Owned(v) => Oco::Counted(Rc::<OsStr>::from(v.as_os_str())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Oco<'_, Path> {
|
||||
/// Returns a new [`Oco`] with the same value as this one.
|
||||
/// If the value is [`Oco::Owned`], this will convert it into
|
||||
/// [`Oco::Counted`], so that the next clone will be O(1).
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// use std::path::Path;
|
||||
///
|
||||
/// let oco = Oco::<Path>::Owned(Path::new("Hello").to_owned());
|
||||
/// let oco2 = oco.clone();
|
||||
/// assert_eq!(oco, oco2);
|
||||
/// assert!(oco2.is_counted());
|
||||
/// ```
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Oco::Borrowed(v) => Oco::Borrowed(v),
|
||||
Oco::Counted(v) => Oco::Counted(v.clone()),
|
||||
Oco::Owned(v) => Oco::Counted(Rc::<Path>::from(v.as_path())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Clone for Oco<'_, [T]>
|
||||
where
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
{
|
||||
/// Returns a new [`Oco`] with the same value as this one.
|
||||
/// If the value is [`Oco::Owned`], this will convert it into
|
||||
/// [`Oco::Counted`], so that the next clone will be O(1).
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use leptos_reactive::oco::Oco;
|
||||
/// let oco = Oco::<[i32]>::Owned(vec![1, 2, 3]);
|
||||
/// let oco2 = oco.clone();
|
||||
/// assert_eq!(oco, oco2);
|
||||
/// assert!(oco2.is_counted());
|
||||
/// ```
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Oco::Borrowed(v) => Oco::Borrowed(v),
|
||||
Oco::Counted(v) => Oco::Counted(v.clone()),
|
||||
Oco::Owned(v) => Oco::Counted(Rc::<[T]>::from(v.as_slice())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Default for Oco<'_, T>
|
||||
where
|
||||
T: ToOwned,
|
||||
T::Owned: Default,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Oco::Owned(T::Owned::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, A: ?Sized, B: ?Sized> PartialEq<Oco<'b, B>> for Oco<'a, A>
|
||||
where
|
||||
A: PartialEq<B>,
|
||||
A: ToOwned,
|
||||
B: ToOwned,
|
||||
{
|
||||
fn eq(&self, other: &Oco<'b, B>) -> bool {
|
||||
**self == **other
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + ToOwned + Eq> Eq for Oco<'_, T> {}
|
||||
|
||||
impl<'a, 'b, A: ?Sized, B: ?Sized> PartialOrd<Oco<'b, B>> for Oco<'a, A>
|
||||
where
|
||||
A: PartialOrd<B>,
|
||||
A: ToOwned,
|
||||
B: ToOwned,
|
||||
{
|
||||
fn partial_cmp(&self, other: &Oco<'b, B>) -> Option<std::cmp::Ordering> {
|
||||
(**self).partial_cmp(&**other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Ord> Ord for Oco<'_, T>
|
||||
where
|
||||
T: ToOwned,
|
||||
{
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
(**self).cmp(&**other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Hash> Hash for Oco<'_, T>
|
||||
where
|
||||
T: ToOwned,
|
||||
{
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
(**self).hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + fmt::Debug> fmt::Debug for Oco<'_, T>
|
||||
where
|
||||
T: ToOwned,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
(**self).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + fmt::Display> fmt::Display for Oco<'_, T>
|
||||
where
|
||||
T: ToOwned,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
(**self).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> From<&'a T> for Oco<'a, T>
|
||||
where
|
||||
T: ToOwned,
|
||||
{
|
||||
fn from(v: &'a T) -> Self {
|
||||
Oco::Borrowed(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> From<Cow<'a, T>> for Oco<'a, T>
|
||||
where
|
||||
T: ToOwned,
|
||||
{
|
||||
fn from(v: Cow<'a, T>) -> Self {
|
||||
match v {
|
||||
Cow::Borrowed(v) => Oco::Borrowed(v),
|
||||
Cow::Owned(v) => Oco::Owned(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> From<Oco<'a, T>> for Cow<'a, T>
|
||||
where
|
||||
T: ToOwned,
|
||||
{
|
||||
fn from(value: Oco<'a, T>) -> Self {
|
||||
match value {
|
||||
Oco::Borrowed(v) => Cow::Borrowed(v),
|
||||
Oco::Owned(v) => Cow::Owned(v),
|
||||
Oco::Counted(v) => Cow::Owned(v.as_ref().to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> From<Rc<T>> for Oco<'_, T>
|
||||
where
|
||||
T: ToOwned,
|
||||
{
|
||||
fn from(v: Rc<T>) -> Self {
|
||||
Oco::Counted(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> From<Box<T>> for Oco<'_, T>
|
||||
where
|
||||
T: ToOwned,
|
||||
{
|
||||
fn from(v: Box<T>) -> Self {
|
||||
Oco::Counted(v.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Oco<'_, str> {
|
||||
fn from(v: String) -> Self {
|
||||
Oco::Owned(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Oco<'_, str>> for String {
|
||||
fn from(v: Oco<'_, str>) -> Self {
|
||||
match v {
|
||||
Oco::Borrowed(v) => v.to_owned(),
|
||||
Oco::Counted(v) => v.as_ref().to_owned(),
|
||||
Oco::Owned(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Vec<T>> for Oco<'_, [T]>
|
||||
where
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
{
|
||||
fn from(v: Vec<T>) -> Self {
|
||||
Oco::Owned(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, const N: usize> From<&'a [T; N]> for Oco<'a, [T]>
|
||||
where
|
||||
[T]: ToOwned,
|
||||
{
|
||||
fn from(v: &'a [T; N]) -> Self {
|
||||
Oco::Borrowed(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Oco<'a, str>> for Oco<'a, [u8]> {
|
||||
fn from(v: Oco<'a, str>) -> Self {
|
||||
match v {
|
||||
Oco::Borrowed(v) => Oco::Borrowed(v.as_bytes()),
|
||||
Oco::Owned(v) => Oco::Owned(v.into_bytes()),
|
||||
Oco::Counted(v) => Oco::Counted(v.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned from [`Oco::try_from`] for unsuccessful
|
||||
/// conversion from `Oco<'_, [u8]>` to `Oco<'_, str>`.
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
#[error("invalid utf-8 sequence: {_0}")]
|
||||
pub enum FromUtf8Error {
|
||||
/// Error for conversion of [`Oco::Borrowed`] and [`Oco::Counted`] variants
|
||||
/// (`&[u8]` to `&str`).
|
||||
#[error("{_0}")]
|
||||
StrFromBytes(
|
||||
#[source]
|
||||
#[from]
|
||||
std::str::Utf8Error,
|
||||
),
|
||||
/// Error for conversion of [`Oco::Owned`] variant (`Vec<u8>` to `String`).
|
||||
#[error("{_0}")]
|
||||
StringFromBytes(
|
||||
#[source]
|
||||
#[from]
|
||||
std::string::FromUtf8Error,
|
||||
),
|
||||
}
|
||||
|
||||
macro_rules! impl_slice_eq {
|
||||
([$($g:tt)*] $((where $($w:tt)+))?, $lhs:ty, $rhs: ty) => {
|
||||
impl<$($g)*> PartialEq<$rhs> for $lhs
|
||||
$(where
|
||||
$($w)*)?
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &$rhs) -> bool {
|
||||
PartialEq::eq(&self[..], &other[..])
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($g)*> PartialEq<$lhs> for $rhs
|
||||
$(where
|
||||
$($w)*)?
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &$lhs) -> bool {
|
||||
PartialEq::eq(&self[..], &other[..])
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_slice_eq!([], Oco<'_, str>, str);
|
||||
impl_slice_eq!(['a, 'b], Oco<'a, str>, &'b str);
|
||||
impl_slice_eq!([], Oco<'_, str>, String);
|
||||
impl_slice_eq!(['a, 'b], Oco<'a, str>, Cow<'b, str>);
|
||||
|
||||
impl_slice_eq!([T: PartialEq] (where [T]: ToOwned), Oco<'_, [T]>, [T]);
|
||||
impl_slice_eq!(['a, 'b, T: PartialEq] (where [T]: ToOwned), Oco<'a, [T]>, &'b [T]);
|
||||
impl_slice_eq!([T: PartialEq] (where [T]: ToOwned), Oco<'_, [T]>, Vec<T>);
|
||||
impl_slice_eq!(['a, 'b, T: PartialEq] (where [T]: ToOwned), Oco<'a, [T]>, Cow<'b, [T]>);
|
||||
|
||||
impl<'a, 'b> Add<&'b str> for Oco<'a, str> {
|
||||
type Output = Oco<'static, str>;
|
||||
|
||||
fn add(self, rhs: &'b str) -> Self::Output {
|
||||
Oco::Owned(String::from(self) + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Add<Cow<'b, str>> for Oco<'a, str> {
|
||||
type Output = Oco<'static, str>;
|
||||
|
||||
fn add(self, rhs: Cow<'b, str>) -> Self::Output {
|
||||
Oco::Owned(String::from(self) + rhs.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Add<Oco<'b, str>> for Oco<'a, str> {
|
||||
type Output = Oco<'static, str>;
|
||||
|
||||
fn add(self, rhs: Oco<'b, str>) -> Self::Output {
|
||||
Oco::Owned(String::from(self) + rhs.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromIterator<Oco<'a, str>> for String {
|
||||
fn from_iter<T: IntoIterator<Item = Oco<'a, str>>>(iter: T) -> Self {
|
||||
iter.into_iter().fold(String::new(), |mut acc, item| {
|
||||
acc.push_str(item.as_ref());
|
||||
acc
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn debug_fmt_should_display_quotes_for_strings() {
|
||||
let s: Oco<str> = Oco::Borrowed("hello");
|
||||
assert_eq!(format!("{:?}", s), "\"hello\"");
|
||||
let s: Oco<str> = Oco::Counted(Rc::from("hello"));
|
||||
assert_eq!(format!("{:?}", s), "\"hello\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_eq_should_compare_str_to_str() {
|
||||
let s: Oco<str> = Oco::Borrowed("hello");
|
||||
assert_eq!(s, "hello");
|
||||
assert_eq!("hello", s);
|
||||
assert_eq!(s, String::from("hello"));
|
||||
assert_eq!(String::from("hello"), s);
|
||||
assert_eq!(s, Cow::from("hello"));
|
||||
assert_eq!(Cow::from("hello"), s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_eq_should_compare_slice_to_slice() {
|
||||
let s: Oco<[i32]> = Oco::Borrowed([1, 2, 3].as_slice());
|
||||
assert_eq!(s, [1, 2, 3].as_slice());
|
||||
assert_eq!([1, 2, 3].as_slice(), s);
|
||||
assert_eq!(s, vec![1, 2, 3]);
|
||||
assert_eq!(vec![1, 2, 3], s);
|
||||
assert_eq!(s, Cow::<'_, [i32]>::Borrowed(&[1, 2, 3]));
|
||||
assert_eq!(Cow::<'_, [i32]>::Borrowed(&[1, 2, 3]), s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_should_concatenate_strings() {
|
||||
let s: Oco<str> = Oco::Borrowed("hello");
|
||||
assert_eq!(s.clone() + " world", "hello world");
|
||||
assert_eq!(s.clone() + Cow::from(" world"), "hello world");
|
||||
assert_eq!(s + Oco::from(" world"), "hello world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_str_should_return_a_str() {
|
||||
let s: Oco<str> = Oco::Borrowed("hello");
|
||||
assert_eq!(s.as_str(), "hello");
|
||||
let s: Oco<str> = Oco::Counted(Rc::from("hello"));
|
||||
assert_eq!(s.as_str(), "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_slice_should_return_a_slice() {
|
||||
let s: Oco<[i32]> = Oco::Borrowed([1, 2, 3].as_slice());
|
||||
assert_eq!(s.as_slice(), [1, 2, 3].as_slice());
|
||||
let s: Oco<[i32]> = Oco::Counted(Rc::from([1, 2, 3]));
|
||||
assert_eq!(s.as_slice(), [1, 2, 3].as_slice());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_for_str_should_return_an_empty_string() {
|
||||
let s: Oco<str> = Default::default();
|
||||
assert!(s.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_for_slice_should_return_an_empty_slice() {
|
||||
let s: Oco<[i32]> = Default::default();
|
||||
assert!(s.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_for_any_option_should_return_none() {
|
||||
let s: Oco<Option<i32>> = Default::default();
|
||||
assert!(s.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cloned_owned_string_should_become_counted_str() {
|
||||
let s: Oco<str> = Oco::Owned(String::from("hello"));
|
||||
assert!(s.clone().is_counted());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cloned_borrowed_str_should_remain_borrowed_str() {
|
||||
let s: Oco<str> = Oco::Borrowed("hello");
|
||||
assert!(s.clone().is_borrowed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cloned_counted_str_should_remain_counted_str() {
|
||||
let s: Oco<str> = Oco::Counted(Rc::from("hello"));
|
||||
assert!(s.clone().is_counted());
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
//! Types that handle asynchronous data loading via `<Suspense/>`.
|
||||
|
||||
use crate::{
|
||||
create_isomorphic_effect, create_rw_signal, create_signal, queue_microtask,
|
||||
signal::SignalGet, store_value, ReadSignal, RwSignal, SignalSet,
|
||||
SignalUpdate, StoredValue, WriteSignal,
|
||||
create_isomorphic_effect, create_rw_signal, create_signal, oco::Oco,
|
||||
queue_microtask, signal::SignalGet, store_value, ReadSignal, RwSignal,
|
||||
SignalSet, SignalUpdate, StoredValue, WriteSignal,
|
||||
};
|
||||
use futures::Future;
|
||||
use std::{
|
||||
borrow::Cow, cell::RefCell, collections::VecDeque, pin::Pin, rc::Rc,
|
||||
};
|
||||
use std::{cell::RefCell, collections::VecDeque, pin::Pin, rc::Rc};
|
||||
|
||||
/// Tracks [`Resource`](crate::Resource)s that are read under a suspense context,
|
||||
/// i.e., within a [`Suspense`](https://docs.rs/leptos_core/latest/leptos_core/fn.Suspense.html) component.
|
||||
@@ -172,7 +170,7 @@ impl Default for SuspenseContext {
|
||||
/// Represents a chunk in a stream of HTML.
|
||||
pub enum StreamChunk {
|
||||
/// A chunk of synchronous HTML.
|
||||
Sync(Cow<'static, str>),
|
||||
Sync(Oco<'static, str>),
|
||||
/// A future that resolves to be a list of additional chunks.
|
||||
Async {
|
||||
/// The HTML chunks this contains.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.5.0"
|
||||
version = "0.5.0-beta2"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
||||
@@ -51,7 +51,6 @@ use leptos::{
|
||||
*,
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cell::{Cell, RefCell},
|
||||
fmt::Debug,
|
||||
rc::Rc,
|
||||
@@ -100,7 +99,7 @@ pub struct MetaTagsContext {
|
||||
els: Rc<
|
||||
RefCell<
|
||||
IndexMap<
|
||||
Cow<'static, str>,
|
||||
Oco<'static, str>,
|
||||
(HtmlElement<AnyElement>, Option<web_sys::Element>),
|
||||
>,
|
||||
>,
|
||||
@@ -130,7 +129,7 @@ impl MetaTagsContext {
|
||||
pub fn register(
|
||||
&self,
|
||||
|
||||
id: Cow<'static, str>,
|
||||
id: Oco<'static, str>,
|
||||
builder_el: HtmlElement<AnyElement>,
|
||||
) {
|
||||
cfg_if! {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::use_head;
|
||||
use leptos::{nonce::use_nonce, *};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Injects an [HTMLLinkElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement) into the document
|
||||
/// head, accepting any of the valid attributes for that tag.
|
||||
@@ -28,62 +27,62 @@ use std::borrow::Cow;
|
||||
pub fn Link(
|
||||
/// The [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-id) attribute.
|
||||
#[prop(optional, into)]
|
||||
id: Option<Cow<'static, str>>,
|
||||
id: Option<Oco<'static, str>>,
|
||||
/// The [`as`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-as) attribute.
|
||||
#[prop(optional, into)]
|
||||
as_: Option<Cow<'static, str>>,
|
||||
as_: Option<Oco<'static, str>>,
|
||||
/// The [`crossorigin`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-crossorigin) attribute.
|
||||
#[prop(optional, into)]
|
||||
crossorigin: Option<Cow<'static, str>>,
|
||||
crossorigin: Option<Oco<'static, str>>,
|
||||
/// The [`disabled`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-disabled) attribute.
|
||||
#[prop(optional, into)]
|
||||
disabled: Option<bool>,
|
||||
/// The [`fetchpriority`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-fetchpriority) attribute.
|
||||
#[prop(optional, into)]
|
||||
fetchpriority: Option<Cow<'static, str>>,
|
||||
fetchpriority: Option<Oco<'static, str>>,
|
||||
/// The [`href`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-href) attribute.
|
||||
#[prop(optional, into)]
|
||||
href: Option<Cow<'static, str>>,
|
||||
href: Option<Oco<'static, str>>,
|
||||
/// The [`hreflang`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-hreflang) attribute.
|
||||
#[prop(optional, into)]
|
||||
hreflang: Option<Cow<'static, str>>,
|
||||
hreflang: Option<Oco<'static, str>>,
|
||||
/// The [`imagesizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-imagesizes) attribute.
|
||||
#[prop(optional, into)]
|
||||
imagesizes: Option<Cow<'static, str>>,
|
||||
imagesizes: Option<Oco<'static, str>>,
|
||||
/// The [`imagesrcset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-imagesrcset) attribute.
|
||||
#[prop(optional, into)]
|
||||
imagesrcset: Option<Cow<'static, str>>,
|
||||
imagesrcset: Option<Oco<'static, str>>,
|
||||
/// The [`integrity`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-integrity) attribute.
|
||||
#[prop(optional, into)]
|
||||
integrity: Option<Cow<'static, str>>,
|
||||
integrity: Option<Oco<'static, str>>,
|
||||
/// The [`media`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-media) attribute.
|
||||
#[prop(optional, into)]
|
||||
media: Option<Cow<'static, str>>,
|
||||
media: Option<Oco<'static, str>>,
|
||||
/// The [`prefetch`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-prefetch) attribute.
|
||||
#[prop(optional, into)]
|
||||
prefetch: Option<Cow<'static, str>>,
|
||||
prefetch: Option<Oco<'static, str>>,
|
||||
/// The [`referrerpolicy`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-referrerpolicy) attribute.
|
||||
#[prop(optional, into)]
|
||||
referrerpolicy: Option<Cow<'static, str>>,
|
||||
referrerpolicy: Option<Oco<'static, str>>,
|
||||
/// The [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-rel) attribute.
|
||||
#[prop(optional, into)]
|
||||
rel: Option<Cow<'static, str>>,
|
||||
rel: Option<Oco<'static, str>>,
|
||||
/// The [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-sizes) attribute.
|
||||
#[prop(optional, into)]
|
||||
sizes: Option<Cow<'static, str>>,
|
||||
sizes: Option<Oco<'static, str>>,
|
||||
/// The [`title`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-title) attribute.
|
||||
#[prop(optional, into)]
|
||||
title: Option<Cow<'static, str>>,
|
||||
title: Option<Oco<'static, str>>,
|
||||
/// The [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-type) attribute.
|
||||
#[prop(optional, into)]
|
||||
type_: Option<Cow<'static, str>>,
|
||||
type_: Option<Oco<'static, str>>,
|
||||
/// The [`blocking`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-blocking) attribute.
|
||||
#[prop(optional, into)]
|
||||
blocking: Option<Cow<'static, str>>,
|
||||
blocking: Option<Oco<'static, str>>,
|
||||
) -> impl IntoView {
|
||||
let meta = use_head();
|
||||
let next_id = meta.tags.get_next_id();
|
||||
let id: Cow<'static, str> =
|
||||
let id: Oco<'static, str> =
|
||||
id.unwrap_or_else(|| format!("leptos-link-{}", next_id.0).into());
|
||||
|
||||
let builder_el = leptos::leptos_dom::html::as_meta_tag({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::use_head;
|
||||
use leptos::{nonce::use_nonce, *};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Injects an [HTMLScriptElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement) into the document
|
||||
/// head, accepting any of the valid attributes for that tag.
|
||||
@@ -25,47 +24,47 @@ use std::borrow::Cow;
|
||||
pub fn Script(
|
||||
/// An ID for the `<script>` tag.
|
||||
#[prop(optional, into)]
|
||||
id: Option<Cow<'static, str>>,
|
||||
id: Option<Oco<'static, str>>,
|
||||
/// The [`async`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-async) attribute.
|
||||
#[prop(optional, into)]
|
||||
async_: Option<Cow<'static, str>>,
|
||||
async_: Option<Oco<'static, str>>,
|
||||
/// The [`crossorigin`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-crossorigin) attribute.
|
||||
#[prop(optional, into)]
|
||||
crossorigin: Option<Cow<'static, str>>,
|
||||
crossorigin: Option<Oco<'static, str>>,
|
||||
/// The [`defer`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-defer) attribute.
|
||||
#[prop(optional, into)]
|
||||
defer: Option<Cow<'static, str>>,
|
||||
defer: Option<Oco<'static, str>>,
|
||||
/// The [`fetchpriority `](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-fetchpriority ) attribute.
|
||||
#[prop(optional, into)]
|
||||
fetchpriority: Option<Cow<'static, str>>,
|
||||
fetchpriority: Option<Oco<'static, str>>,
|
||||
/// The [`integrity`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-integrity) attribute.
|
||||
#[prop(optional, into)]
|
||||
integrity: Option<Cow<'static, str>>,
|
||||
integrity: Option<Oco<'static, str>>,
|
||||
/// The [`nomodule`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-nomodule) attribute.
|
||||
#[prop(optional, into)]
|
||||
nomodule: Option<Cow<'static, str>>,
|
||||
nomodule: Option<Oco<'static, str>>,
|
||||
/// The [`nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-nonce) attribute.
|
||||
#[prop(optional, into)]
|
||||
nonce: Option<Cow<'static, str>>,
|
||||
nonce: Option<Oco<'static, str>>,
|
||||
/// The [`referrerpolicy`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-referrerpolicy) attribute.
|
||||
#[prop(optional, into)]
|
||||
referrerpolicy: Option<Cow<'static, str>>,
|
||||
referrerpolicy: Option<Oco<'static, str>>,
|
||||
/// The [`src`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-src) attribute.
|
||||
#[prop(optional, into)]
|
||||
src: Option<Cow<'static, str>>,
|
||||
src: Option<Oco<'static, str>>,
|
||||
/// The [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type) attribute.
|
||||
#[prop(optional, into)]
|
||||
type_: Option<Cow<'static, str>>,
|
||||
type_: Option<Oco<'static, str>>,
|
||||
/// The [`blocking`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-blocking) attribute.
|
||||
#[prop(optional, into)]
|
||||
blocking: Option<Cow<'static, str>>,
|
||||
blocking: Option<Oco<'static, str>>,
|
||||
/// The content of the `<script>` tag.
|
||||
#[prop(optional)]
|
||||
children: Option<Box<dyn FnOnce() -> Fragment>>,
|
||||
) -> impl IntoView {
|
||||
let meta = use_head();
|
||||
let next_id = meta.tags.get_next_id();
|
||||
let id: Cow<'static, str> =
|
||||
let id: Oco<'static, str> =
|
||||
id.unwrap_or_else(|| format!("leptos-link-{}", next_id.0).into());
|
||||
|
||||
let builder_el = leptos::leptos_dom::html::as_meta_tag({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::use_head;
|
||||
use leptos::{nonce::use_nonce, *};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Injects an [HTMLStyleElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLStyleElement) into the document
|
||||
/// head, accepting any of the valid attributes for that tag.
|
||||
@@ -25,26 +24,26 @@ use std::borrow::Cow;
|
||||
pub fn Style(
|
||||
/// An ID for the `<script>` tag.
|
||||
#[prop(optional, into)]
|
||||
id: Option<Cow<'static, str>>,
|
||||
id: Option<Oco<'static, str>>,
|
||||
/// The [`media`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style#attr-media) attribute.
|
||||
#[prop(optional, into)]
|
||||
media: Option<Cow<'static, str>>,
|
||||
media: Option<Oco<'static, str>>,
|
||||
/// The [`nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style#attr-nonce) attribute.
|
||||
#[prop(optional, into)]
|
||||
nonce: Option<Cow<'static, str>>,
|
||||
nonce: Option<Oco<'static, str>>,
|
||||
/// The [`title`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style#attr-title) attribute.
|
||||
#[prop(optional, into)]
|
||||
title: Option<Cow<'static, str>>,
|
||||
title: Option<Oco<'static, str>>,
|
||||
/// The [`blocking`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style#attr-blocking) attribute.
|
||||
#[prop(optional, into)]
|
||||
blocking: Option<Cow<'static, str>>,
|
||||
blocking: Option<Oco<'static, str>>,
|
||||
/// The content of the `<style>` tag.
|
||||
#[prop(optional)]
|
||||
children: Option<Box<dyn FnOnce() -> Fragment>>,
|
||||
) -> impl IntoView {
|
||||
let meta = use_head();
|
||||
let next_id = meta.tags.get_next_id();
|
||||
let id: Cow<'static, str> =
|
||||
let id: Oco<'static, str> =
|
||||
id.unwrap_or_else(|| format!("leptos-link-{}", next_id.0).into());
|
||||
|
||||
let builder_el = leptos::leptos_dom::html::as_meta_tag({
|
||||
|
||||
@@ -16,11 +16,11 @@ pub struct TitleContext {
|
||||
|
||||
impl TitleContext {
|
||||
/// Converts the title into a string that can be used as the text content of a `<title>` tag.
|
||||
pub fn as_string(&self) -> Option<String> {
|
||||
let title = self.text.borrow().as_ref().map(|f| f.get());
|
||||
pub fn as_string(&self) -> Option<Oco<'static, str>> {
|
||||
let title = self.text.borrow().as_ref().map(TextProp::get);
|
||||
title.map(|title| {
|
||||
if let Some(formatter) = &*self.formatter.borrow() {
|
||||
(formatter.0)(title)
|
||||
(formatter.0)(title.into_owned()).into()
|
||||
} else {
|
||||
title
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router"
|
||||
version = "0.5.0"
|
||||
version = "0.5.0-beta2"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
||||
@@ -409,6 +409,13 @@ where
|
||||
|
||||
let on_response = Rc::new(move |resp: &web_sys::Response| {
|
||||
let resp = resp.clone().expect("couldn't get Response");
|
||||
|
||||
// If the response was redirected then a JSON will not be available in the response, instead
|
||||
// it will be an actual page, so we don't want to try to parse it.
|
||||
if resp.redirected() {
|
||||
return;
|
||||
}
|
||||
|
||||
spawn_local(async move {
|
||||
let body = JsFuture::from(
|
||||
resp.text().expect("couldn't get .text() from Response"),
|
||||
|
||||
@@ -24,6 +24,20 @@ impl ToHref for String {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHref for Cow<'_, str> {
|
||||
fn to_href(&self) -> Box<dyn Fn() -> String + '_> {
|
||||
let s = self.to_string();
|
||||
Box::new(move || s.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHref for Oco<'_, str> {
|
||||
fn to_href(&self) -> Box<dyn Fn() -> String + '_> {
|
||||
let s = self.to_string();
|
||||
Box::new(move || s.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> ToHref for F
|
||||
where
|
||||
F: Fn() -> String + 'static,
|
||||
@@ -65,7 +79,7 @@ pub fn A<H>(
|
||||
/// `[aria-current=page]` selector, you should prefer that, as it enables significant
|
||||
/// SSR optimizations.
|
||||
#[prop(optional, into)]
|
||||
active_class: Option<Cow<'static, str>>,
|
||||
active_class: Option<Oco<'static, str>>,
|
||||
/// An object of any type that will be pushed to router state
|
||||
#[prop(optional)]
|
||||
state: Option<State>,
|
||||
@@ -78,7 +92,7 @@ pub fn A<H>(
|
||||
class: Option<AttributeValue>,
|
||||
/// Sets the `id` attribute on the underlying `<a>` tag, making it easier to target.
|
||||
#[prop(optional, into)]
|
||||
id: Option<String>,
|
||||
id: Option<Oco<'static, str>>,
|
||||
/// The nodes or elements to be shown inside the link.
|
||||
children: Children,
|
||||
) -> impl IntoView
|
||||
@@ -95,37 +109,35 @@ where
|
||||
#[allow(unused)] state: Option<State>,
|
||||
#[allow(unused)] replace: bool,
|
||||
class: Option<AttributeValue>,
|
||||
#[allow(unused)] active_class: Option<Cow<'static, str>>,
|
||||
id: Option<String>,
|
||||
#[allow(unused)] active_class: Option<Oco<'static, str>>,
|
||||
id: Option<Oco<'static, str>>,
|
||||
children: Children,
|
||||
) -> View {
|
||||
#[cfg(not(any(feature = "hydrate", feature = "csr")))]
|
||||
{
|
||||
_ = state;
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "hydrate", feature = "csr")))]
|
||||
{
|
||||
_ = replace;
|
||||
}
|
||||
|
||||
let location = use_location();
|
||||
let is_active = create_memo(move |_| match href.get() {
|
||||
None => false,
|
||||
|
||||
Some(to) => {
|
||||
let path = to
|
||||
.split(['?', '#'])
|
||||
.next()
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
let loc = location.pathname.get().to_lowercase();
|
||||
if exact {
|
||||
loc == path
|
||||
} else {
|
||||
loc.starts_with(&path)
|
||||
}
|
||||
}
|
||||
let is_active = create_memo(move |_| {
|
||||
href.with(|href| {
|
||||
href.as_deref().is_some_and(|to| {
|
||||
let path = to
|
||||
.split(['?', '#'])
|
||||
.next()
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
location.pathname.with(|loc| {
|
||||
let loc = loc.to_lowercase();
|
||||
if exact {
|
||||
loc == path
|
||||
} else {
|
||||
loc.starts_with(&path)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::{
|
||||
use leptos::{leptos_dom::Transparent, *};
|
||||
use std::{
|
||||
any::Any,
|
||||
borrow::Cow,
|
||||
cell::{Cell, RefCell},
|
||||
rc::Rc,
|
||||
};
|
||||
@@ -309,7 +310,7 @@ impl RouteContext {
|
||||
|
||||
pub(crate) fn resolve_path_tracked(&self, to: &str) -> Option<String> {
|
||||
resolve_path(&self.inner.base_path, to, Some(&self.inner.path.get()))
|
||||
.map(String::from)
|
||||
.map(Cow::into_owned)
|
||||
}
|
||||
|
||||
/// The nested child route, if any.
|
||||
|
||||
@@ -544,6 +544,7 @@ pub(crate) fn create_branch(routes: &[RouteData], index: usize) -> Branch {
|
||||
score: routes.last().unwrap().score() * 10000 - (index as i32),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
tracing::instrument(level = "info", skip_all,)
|
||||
@@ -567,7 +568,7 @@ fn create_routes(route_def: &RouteDefinition, base: &str) -> Vec<RouteData> {
|
||||
id: route_def.id,
|
||||
matcher: Matcher::new_with_partial(&pattern, !is_leaf),
|
||||
pattern,
|
||||
original_path: original_path.to_string(),
|
||||
original_path: original_path.into_owned(),
|
||||
});
|
||||
}
|
||||
acc
|
||||
|
||||
@@ -4,9 +4,9 @@ use crate::{
|
||||
};
|
||||
use leptos::{
|
||||
create_memo, request_animation_frame, signal_prelude::*, use_context, Memo,
|
||||
Oco,
|
||||
};
|
||||
use std::{borrow::Cow, rc::Rc, str::FromStr};
|
||||
|
||||
use std::{rc::Rc, str::FromStr};
|
||||
/// Constructs a signal synchronized with a specific URL query parameter.
|
||||
///
|
||||
/// The function creates a bidirectional sync mechanism between the state encapsulated in a signal and a URL query parameter.
|
||||
@@ -45,7 +45,7 @@ use std::{borrow::Cow, rc::Rc, str::FromStr};
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn create_query_signal<T>(
|
||||
key: impl Into<Cow<'static, str>>,
|
||||
key: impl Into<Oco<'static, str>>,
|
||||
) -> (Memo<Option<T>>, SignalSetter<Option<T>>)
|
||||
where
|
||||
T: FromStr + ToString + PartialEq,
|
||||
@@ -163,7 +163,7 @@ pub fn use_resolved_path(
|
||||
if path.starts_with('/') {
|
||||
Some(path)
|
||||
} else {
|
||||
route.resolve_path_tracked(&path).map(String::from)
|
||||
route.resolve_path_tracked(&path)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::borrow::Cow;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub fn expand_optionals(pattern: &str) -> Vec<Cow<str>> {
|
||||
pub fn expand_optionals(pattern: &str) -> Vec<Cow<'_, str>> {
|
||||
use js_sys::RegExp;
|
||||
use once_cell::unsync::Lazy;
|
||||
use wasm_bindgen::JsValue;
|
||||
@@ -58,7 +58,7 @@ pub fn expand_optionals(pattern: &str) -> Vec<Cow<str>> {
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "ssr")]
|
||||
pub fn expand_optionals(pattern: &str) -> Vec<Cow<str>> {
|
||||
pub fn expand_optionals(pattern: &str) -> Vec<Cow<'_, str>> {
|
||||
use regex::Regex;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
|
||||
@@ -555,14 +555,22 @@ where
|
||||
let status = resp.status();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let status = status.as_u16();
|
||||
if (500..=599).contains(&status) {
|
||||
if (400..=599).contains(&status) {
|
||||
let text = resp.text().await.unwrap_or_default();
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let status_text = resp.status_text();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let status_text = status.to_string();
|
||||
return Err(serde_json::from_str(&text)
|
||||
.unwrap_or(ServerFnError::ServerError(status_text)));
|
||||
return Err(match serde_json::from_str(&text) {
|
||||
Ok(e) => e,
|
||||
Err(_) => {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let status_text = resp.status_text();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let status_text = status.to_string();
|
||||
ServerFnError::ServerError(if text.is_empty() {
|
||||
format!("{} {}", status, status_text)
|
||||
} else {
|
||||
format!("{} {}: {}", status, status_text, text)
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Decoding the body of the request
|
||||
@@ -582,12 +590,6 @@ where
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let binary = binary.as_ref();
|
||||
|
||||
if status == 400 {
|
||||
return Err(ServerFnError::ServerError(
|
||||
"No server function was found at this URL.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
ciborium::de::from_reader(binary)
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
} else {
|
||||
@@ -596,10 +598,6 @@ where
|
||||
.await
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))?;
|
||||
|
||||
if status == 400 {
|
||||
return Err(ServerFnError::ServerError(text));
|
||||
}
|
||||
|
||||
let mut deserializer = JSONDeserializer::from_str(&text);
|
||||
T::deserialize(&mut deserializer)
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
|
||||
Reference in New Issue
Block a user