mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 09:54:41 -05:00
231 lines
6.1 KiB
Rust
231 lines
6.1 KiB
Rust
use crate::{
|
|
path::{StorePath, StorePathSegment},
|
|
store_field::StoreField,
|
|
KeyMap, StoreFieldTrigger,
|
|
};
|
|
use reactive_graph::{
|
|
signal::{
|
|
guards::{Mapped, MappedMut, WriteGuard},
|
|
ArcTrigger,
|
|
},
|
|
traits::{
|
|
DefinedAt, Get as _, IsDisposed, Notify, ReadUntracked, Track,
|
|
UntrackableGuard, Write,
|
|
},
|
|
wrappers::read::Signal,
|
|
};
|
|
use std::{iter, marker::PhantomData, ops::DerefMut, panic::Location};
|
|
|
|
/// Accesses a single field of a reactive structure.
|
|
#[derive(Debug)]
|
|
pub struct Subfield<Inner, Prev, T> {
|
|
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
|
defined_at: &'static Location<'static>,
|
|
path_segment: StorePathSegment,
|
|
inner: Inner,
|
|
read: fn(&Prev) -> &T,
|
|
write: fn(&mut Prev) -> &mut T,
|
|
ty: PhantomData<T>,
|
|
}
|
|
|
|
impl<Inner, Prev, T> Clone for Subfield<Inner, Prev, T>
|
|
where
|
|
Inner: Clone,
|
|
{
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
|
defined_at: self.defined_at,
|
|
path_segment: self.path_segment,
|
|
inner: self.inner.clone(),
|
|
read: self.read,
|
|
write: self.write,
|
|
ty: self.ty,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Inner, Prev, T> Copy for Subfield<Inner, Prev, T> where Inner: Copy {}
|
|
|
|
impl<Inner, Prev, T> Subfield<Inner, Prev, T> {
|
|
/// Creates an accessor for a single field of the inner structure.
|
|
#[track_caller]
|
|
pub fn new(
|
|
inner: Inner,
|
|
path_segment: StorePathSegment,
|
|
read: fn(&Prev) -> &T,
|
|
write: fn(&mut Prev) -> &mut T,
|
|
) -> Self {
|
|
Self {
|
|
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
|
defined_at: Location::caller(),
|
|
inner,
|
|
path_segment,
|
|
read,
|
|
write,
|
|
ty: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Inner, Prev, T> StoreField for Subfield<Inner, Prev, T>
|
|
where
|
|
Inner: StoreField<Value = Prev>,
|
|
Prev: 'static,
|
|
{
|
|
type Value = T;
|
|
type Reader = Mapped<Inner::Reader, T>;
|
|
type Writer = MappedMut<WriteGuard<Vec<ArcTrigger>, Inner::Writer>, T>;
|
|
|
|
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
|
|
self.inner
|
|
.path()
|
|
.into_iter()
|
|
.chain(iter::once(self.path_segment))
|
|
}
|
|
|
|
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
|
self.inner.get_trigger(path)
|
|
}
|
|
|
|
fn get_trigger_unkeyed(&self, path: StorePath) -> StoreFieldTrigger {
|
|
self.inner.get_trigger_unkeyed(path)
|
|
}
|
|
|
|
fn reader(&self) -> Option<Self::Reader> {
|
|
let inner = self.inner.reader()?;
|
|
Some(Mapped::new_with_guard(inner, self.read))
|
|
}
|
|
|
|
fn writer(&self) -> Option<Self::Writer> {
|
|
let mut parent = self.inner.writer()?;
|
|
|
|
// we will manually include all the parent and ancestor `children` triggers
|
|
// in triggers_for_current_path() below. we want to untrack the parent writer
|
|
// so that it doesn't notify on the parent's `this` trigger, which would notify our
|
|
// siblings too
|
|
parent.untrack();
|
|
let triggers = self.triggers_for_current_path();
|
|
let guard = WriteGuard::new(triggers, parent);
|
|
Some(MappedMut::new(guard, self.read, self.write))
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn keys(&self) -> Option<KeyMap> {
|
|
self.inner.keys()
|
|
}
|
|
|
|
#[track_caller]
|
|
fn track_field(&self) {
|
|
let mut full_path = self.path().into_iter().collect::<StorePath>();
|
|
let trigger = self.get_trigger(self.path().into_iter().collect());
|
|
trigger.this.track();
|
|
trigger.children.track();
|
|
|
|
// tracks `this` for all ancestors: i.e., it will track any change that is made
|
|
// directly to one of its ancestors, but not a change made to a *child* of an ancestor
|
|
// (which would end up with every subfield tracking its own siblings, because they are
|
|
// children of its parent)
|
|
while !full_path.is_empty() {
|
|
full_path.pop();
|
|
let inner = self.get_trigger(full_path.clone());
|
|
inner.this.track();
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Inner, Prev, T> DefinedAt for Subfield<Inner, Prev, T>
|
|
where
|
|
Inner: StoreField<Value = Prev>,
|
|
{
|
|
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
|
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
|
{
|
|
Some(self.defined_at)
|
|
}
|
|
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
|
{
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Inner, Prev, T> IsDisposed for Subfield<Inner, Prev, T>
|
|
where
|
|
Inner: IsDisposed,
|
|
{
|
|
fn is_disposed(&self) -> bool {
|
|
self.inner.is_disposed()
|
|
}
|
|
}
|
|
|
|
impl<Inner, Prev, T> Notify for Subfield<Inner, Prev, T>
|
|
where
|
|
Inner: StoreField<Value = Prev>,
|
|
Prev: 'static,
|
|
{
|
|
#[track_caller]
|
|
fn notify(&self) {
|
|
let trigger = self.get_trigger(self.path().into_iter().collect());
|
|
trigger.this.notify();
|
|
trigger.children.notify();
|
|
}
|
|
}
|
|
|
|
impl<Inner, Prev, T> Track for Subfield<Inner, Prev, T>
|
|
where
|
|
Inner: StoreField<Value = Prev> + Track + 'static,
|
|
Prev: 'static,
|
|
T: 'static,
|
|
{
|
|
#[track_caller]
|
|
fn track(&self) {
|
|
self.track_field();
|
|
}
|
|
}
|
|
|
|
impl<Inner, Prev, T> ReadUntracked for Subfield<Inner, Prev, T>
|
|
where
|
|
Inner: StoreField<Value = Prev>,
|
|
Prev: 'static,
|
|
{
|
|
type Value = <Self as StoreField>::Reader;
|
|
|
|
fn try_read_untracked(&self) -> Option<Self::Value> {
|
|
self.reader()
|
|
}
|
|
}
|
|
|
|
impl<Inner, Prev, T> Write for Subfield<Inner, Prev, T>
|
|
where
|
|
T: 'static,
|
|
Inner: StoreField<Value = Prev>,
|
|
Prev: 'static,
|
|
{
|
|
type Value = T;
|
|
|
|
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
|
|
self.writer()
|
|
}
|
|
|
|
fn try_write_untracked(
|
|
&self,
|
|
) -> Option<impl DerefMut<Target = Self::Value>> {
|
|
self.writer().map(|mut writer| {
|
|
writer.untrack();
|
|
writer
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<Inner, Prev, T> From<Subfield<Inner, Prev, T>> for Signal<T>
|
|
where
|
|
Inner: StoreField<Value = Prev> + Track + Send + Sync + 'static,
|
|
Prev: 'static,
|
|
T: Send + Sync + Clone + 'static,
|
|
{
|
|
fn from(subfield: Subfield<Inner, Prev, T>) -> Self {
|
|
Signal::derive(move || subfield.get())
|
|
}
|
|
}
|