feat: Oco (Owned Clones Once) smart pointer (#1480)

This commit is contained in:
Danik Vitek 2023-08-26 18:43:51 +03:00 committed by GitHub
parent 6c3e2fe53e
commit 793c191619
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1020 additions and 228 deletions

2
.gitignore vendored
View file

@ -9,3 +9,5 @@ Cargo.lock
.idea
.direnv
.envrc
.vscode

View file

@ -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>(

View file

@ -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()))
}
}

View file

@ -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(),

View file

@ -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"))]

View file

@ -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
},

View file

@ -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| {

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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()

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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 {

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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!(),

View file

@ -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()
}

View file

@ -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
View 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());
}
}

View file

@ -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.

View file

@ -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! {

View file

@ -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({

View file

@ -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({

View file

@ -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({

View file

@ -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
}

View file

@ -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")]

View file

@ -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.

View file

@ -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

View file

@ -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)
}
})
}

View file

@ -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! {