maomi/prop.rs
1//! The properties utilities.
2//!
3//! The properties of components can be set through templates by component users.
4//!
5//! ### Basic Usage
6//!
7//! The following example show the basic usage of properties.
8//!
9//! ```rust
10//! use maomi::prelude::*;
11//!
12//! #[component]
13//! struct MyComponent {
14//! template: template! {
15//! /* ... */
16//! },
17//! // define a property with the detailed type
18//! my_prop: Prop<usize>,
19//! }
20//!
21//! impl Component for MyComponent {
22//! fn new() -> Self {
23//! Self {
24//! template: Default::default(),
25//! // init the property with a default value
26//! my_prop: Prop::new(123),
27//! }
28//! }
29//! }
30//!
31//! #[component]
32//! struct MyComponentUser {
33//! template: template! {
34//! // set the property value
35//! <MyComponent my_prop=&{ 456 } />
36//! },
37//! }
38//! ```
39//!
40//! ### Two-way Property
41//!
42//! Most property values are passing from the component user to the component.
43//! The component should not modify its own properties,
44//! otherwise the next updates of the component user will change them back.
45//! However, some properties (like `value` property in `<input>` ) should be passing back from the component to the component user.
46//! `BindingProp` is designed to solve this problem.
47//!
48//! A `BindingProp` accepts a `BindingValue` .
49//! A `BindingValue` contains a value shared between the component and the component user.
50//! It can be visited on both ends.
51//!
52//! ```rust
53//! use maomi::prelude::*;
54//! use maomi::prop::{BindingProp, BindingValue};
55//!
56//! #[component]
57//! struct MyComponent {
58//! template: template! {
59//! /* ... */
60//! },
61//! // define a two-way property with the detailed type
62//! my_prop: BindingProp<String>,
63//! }
64//!
65//! impl Component for MyComponent {
66//! fn new() -> Self {
67//! Self {
68//! template: Default::default(),
69//! // init the two-way property
70//! my_prop: BindingProp::new(String::new()),
71//! }
72//! }
73//! }
74//!
75//! #[component]
76//! struct MyComponentUser {
77//! template: template! {
78//! // associate a binding value
79//! <MyComponent my_prop={ &self.comp_value } />
80//! },
81//! comp_value: BindingValue<String>,
82//! }
83//!
84//! impl Component for MyComponentUser {
85//! fn new() -> Self {
86//! Self {
87//! template: Default::default(),
88//! // init the binding value
89//! comp_value: BindingValue::new(String::new()),
90//! }
91//! }
92//! }
93//! ```
94//!
95//! ### List Property
96//!
97//! `ListProp` is one special kind of properties.
98//! It can accepts one attribute more than once.
99//! This helps some special cases like `class:xxx` syntax in `maomi_dom` crate.
100//!
101//! ```rust
102//! use maomi::prelude::*;
103//! use maomi::prop::ListProp;
104//!
105//! #[component]
106//! struct MyComponent {
107//! template: template! {
108//! /* ... */
109//! },
110//! // define a list property with the detailed item type
111//! my_prop: ListProp<String>,
112//! }
113//!
114//! impl Component for MyComponent {
115//! fn new() -> Self {
116//! Self {
117//! template: Default::default(),
118//! // init the list property
119//! my_prop: ListProp::new(),
120//! }
121//! }
122//! }
123//!
124//! #[component]
125//! struct MyComponentUser {
126//! template: template! {
127//! // set the list property value
128//! <MyComponent my_prop:String="abc" my_prop:String="def" />
129//! // this is the same as following
130//! <MyComponent my_prop={ &["abc".to_string(), "def".to_string()] } />
131//! },
132//! }
133//! ```
134
135use std::{borrow::Borrow, cell::RefCell, fmt::Display, ops::Deref, rc::Rc};
136
137/// The property updater.
138///
139/// This trait is implemented by `Prop` .
140/// Custom property types that implements this trait can also be set through templates.
141pub trait PropertyUpdate<S: ?Sized> {
142 /// Must be `bool` if used in components and updated through templates.
143 type UpdateContext;
144
145 /// The updater.
146 ///
147 /// If used in components and updated through templates,
148 /// `ctx` must be set to true if updated.
149 fn compare_and_set_ref(dest: &mut Self, src: &S, ctx: &mut Self::UpdateContext);
150}
151
152/// A property of components that can be set through templates.
153#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
154pub struct Prop<T> {
155 inner: T,
156}
157
158impl<T> Prop<T> {
159 /// Create the property with initial value.
160 #[inline]
161 pub fn new(inner: T) -> Self {
162 Self { inner }
163 }
164}
165
166impl<T> Deref for Prop<T> {
167 type Target = T;
168
169 #[inline]
170 fn deref(&self) -> &Self::Target {
171 &self.inner
172 }
173}
174
175impl<T> AsRef<T> for Prop<T> {
176 #[inline]
177 fn as_ref(&self) -> &T {
178 &self.inner
179 }
180}
181
182impl<T> Borrow<T> for Prop<T> {
183 #[inline]
184 fn borrow(&self) -> &T {
185 &self.inner
186 }
187}
188
189impl<T: Display> Display for Prop<T> {
190 #[inline]
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 self.inner.fmt(f)
193 }
194}
195
196/// Indicate that `&S` is assignable to `Prop<Self>` .
197///
198/// Every type that implements `PartialEq` and can be borrowed as `&S` automatically implements this trait.
199/// For example:
200/// * `usize` implements `PropAsRef<usize>` ;
201/// * `String` implements `PropAsRef<String>` and `PropAsRef<str>` .
202pub trait PropAsRef<S: ?Sized + PartialEq> {
203 /// Borrow `&Self` as `&S` .
204 fn property_as_ref(&self) -> &S;
205 /// Clone `&S` as a new `Self` .
206 fn property_to_owned(s: &S) -> Self
207 where
208 Self: Sized;
209}
210
211impl<S: ?Sized + PartialEq + ToOwned<Owned = T>, T: Borrow<S>> PropAsRef<S> for T {
212 #[inline]
213 fn property_as_ref(&self) -> &S {
214 self.borrow()
215 }
216
217 #[inline]
218 fn property_to_owned(s: &S) -> Self
219 where
220 Self: Sized,
221 {
222 s.to_owned()
223 }
224}
225
226impl<S: ?Sized + PartialEq, T: PropAsRef<S>> PropertyUpdate<S> for Prop<T> {
227 type UpdateContext = bool;
228
229 #[inline]
230 fn compare_and_set_ref(dest: &mut Self, src: &S, ctx: &mut bool) {
231 if dest.inner.property_as_ref() == src {
232 return;
233 }
234 dest.inner = PropAsRef::property_to_owned(src);
235 *ctx = true;
236 }
237}
238
239/// A two-way property that can share a `BindingValue` between a component and its user.
240#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
241pub struct BindingProp<T> {
242 value: BindingValue<T>,
243}
244
245impl<T> BindingProp<T> {
246 /// Create the property with initial value.
247 #[inline]
248 pub fn new(default_value: T) -> Self {
249 Self {
250 value: BindingValue::new(default_value),
251 }
252 }
253
254 /// Set the value.
255 #[inline]
256 pub fn set(&mut self, v: T) {
257 self.value.set(v);
258 }
259
260 /// Get a reference of the value.
261 #[inline]
262 pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
263 self.value.with(f)
264 }
265
266 /// Get a reference of the value.
267 #[inline]
268 pub fn update<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
269 self.value.update(f)
270 }
271}
272
273impl<T: Clone> BindingProp<T> {
274 /// Get the cloned value.
275 pub fn get(&self) -> T {
276 self.value.get()
277 }
278}
279
280impl<T> PropertyUpdate<BindingValue<T>> for BindingProp<T> {
281 type UpdateContext = bool;
282
283 #[inline]
284 fn compare_and_set_ref(dest: &mut Self, src: &BindingValue<T>, ctx: &mut Self::UpdateContext) {
285 if BindingValue::ptr_eq(&dest.value, src) {
286 return;
287 }
288 dest.value = src.clone_ref();
289 *ctx = true;
290 }
291}
292
293/// A value that can be associated to a `BindingProp` .
294///
295/// Note that the `BindingValue` should be exclusively associated to one `BindingProp` .
296/// Panics if the value is associated to more than one `BindingProp` .
297#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
298pub struct BindingValue<T> {
299 inner: Rc<RefCell<T>>,
300}
301
302impl<T> BindingValue<T> {
303 /// Create the property with initial value.
304 #[inline]
305 pub fn new(default_value: T) -> Self {
306 Self {
307 inner: Rc::new(RefCell::new(default_value)),
308 }
309 }
310
311 #[doc(hidden)]
312 #[inline]
313 pub fn ptr_eq(a: &Self, b: &Self) -> bool {
314 Rc::ptr_eq(&a.inner, &b.inner)
315 }
316
317 #[doc(hidden)]
318 #[inline]
319 pub fn clone_ref(&self) -> Self {
320 if Rc::strong_count(&self.inner) > 1 {
321 panic!("A `BindingValue` cannot be associated to more than one `BindingProp`");
322 }
323 Self {
324 inner: self.inner.clone(),
325 }
326 }
327
328 /// Set the value.
329 ///
330 /// Updates of the value will NOT be applied to template!
331 /// To change the value and apply in templates, create a new `BindingValue` instead.
332 #[inline]
333 pub fn set(&mut self, v: T) {
334 *self.inner.borrow_mut() = v;
335 }
336
337 /// Get a reference of the value.
338 #[inline]
339 pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
340 f(&(*self.inner).borrow())
341 }
342
343 /// Get a reference of the value.
344 ///
345 /// Updates of the value will NOT be applied to template!
346 /// To change the value and apply in templates, create a new `BindingValue` instead.
347 #[inline]
348 pub fn update<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
349 f(&mut (*self.inner).borrow_mut())
350 }
351}
352
353impl<T: Clone> BindingValue<T> {
354 /// Get the cloned value.
355 pub fn get(&self) -> T {
356 (*self.inner).borrow().clone()
357 }
358}
359
360/// The list property initializer.
361pub trait ListPropertyInit {
362 /// Must be `bool` if used in components and updated through templates.
363 type UpdateContext;
364
365 /// Initialize with item count provided.
366 ///
367 /// Will be called once before any list value set.
368 fn init_list(dest: &mut Self, count: usize, ctx: &mut Self::UpdateContext)
369 where
370 Self: Sized;
371}
372
373/// The list property updater.
374///
375/// This trait is implemented by `ListProp` .
376/// Custom event types that implements this trait can also be used in templates with `:xxx=` syntax.
377pub trait ListPropertyUpdate<S: ?Sized>: ListPropertyInit {
378 /// The item value type.
379 ///
380 /// Must match the corresponding `ListPropertyItem::Value` .
381 type ItemValue: ?Sized;
382
383 /// The updater.
384 ///
385 /// If used in components and updated through templates,
386 /// `ctx` must be set to true if updated.
387 fn compare_and_set_item_ref<U: ListPropertyItem<Self, S, Value = Self::ItemValue>>(
388 dest: &mut Self,
389 index: usize,
390 src: &S,
391 ctx: &mut Self::UpdateContext,
392 ) where
393 Self: Sized;
394}
395
396/// The item updater for a specified list property `L` .
397pub trait ListPropertyItem<L: ListPropertyUpdate<S>, S: ?Sized> {
398 /// The item value type.
399 ///
400 /// Must match the corresponding `ListPropertyUpdate::ItemValue` .
401 type Value: ?Sized;
402
403 /// Get the item value.
404 ///
405 /// If used in components and updated through templates,
406 /// `ctx` must be set to true if updated.
407 fn item_value<'a>(
408 dest: &mut L,
409 index: usize,
410 s: &'a S,
411 ctx: &mut L::UpdateContext,
412 ) -> &'a Self::Value;
413}
414
415/// A list property that can be used in templates.
416///
417/// List properties can be updated in `:xxx=` syntax,
418/// while the `item_name` is a type that implements `ListPropertyItem` .
419// TODO add better examples and documentation for it
420#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
421pub struct ListProp<T: Default> {
422 inner: Box<[T]>,
423}
424
425impl<T: Default> ListProp<T> {
426 /// Create the property with no item.
427 #[inline]
428 pub fn new() -> Self {
429 Self {
430 inner: Box::new([]),
431 }
432 }
433}
434
435impl<'a, T: Default> IntoIterator for &'a ListProp<T> {
436 type IntoIter = std::slice::Iter<'a, T>;
437 type Item = &'a T;
438
439 #[inline]
440 fn into_iter(self) -> Self::IntoIter {
441 self.inner.into_iter()
442 }
443}
444
445impl<T: Default> Deref for ListProp<T> {
446 type Target = [T];
447
448 #[inline]
449 fn deref(&self) -> &Self::Target {
450 &self.inner
451 }
452}
453
454impl<T: Default> AsRef<[T]> for ListProp<T> {
455 #[inline]
456 fn as_ref(&self) -> &[T] {
457 &self.inner
458 }
459}
460
461impl<T: Default> Borrow<[T]> for ListProp<T> {
462 #[inline]
463 fn borrow(&self) -> &[T] {
464 &self.inner
465 }
466}
467
468impl<T: Default + PartialEq + Clone> PropertyUpdate<[T]> for ListProp<T> {
469 type UpdateContext = bool;
470
471 #[inline]
472 fn compare_and_set_ref(dest: &mut Self, src: &[T], ctx: &mut Self::UpdateContext) {
473 if &*dest.inner == src {
474 return;
475 }
476 dest.inner = src.iter().cloned().collect();
477 *ctx = true;
478 }
479}
480
481impl<T: Default> ListPropertyInit for ListProp<T> {
482 type UpdateContext = bool;
483
484 #[inline]
485 fn init_list(dest: &mut Self, count: usize, _ctx: &mut bool) {
486 let mut v = Vec::with_capacity(count);
487 v.resize_with(count, T::default);
488 dest.inner = v.into_boxed_slice();
489 }
490}
491
492impl<S: ?Sized + PartialEq, T: Default + PropAsRef<S>> ListPropertyUpdate<S> for ListProp<T> {
493 type ItemValue = ();
494
495 #[inline]
496 fn compare_and_set_item_ref<U: ListPropertyItem<Self, S, Value = ()>>(
497 dest: &mut Self,
498 index: usize,
499 src: &S,
500 ctx: &mut Self::UpdateContext,
501 ) where
502 Self: Sized,
503 {
504 U::item_value(dest, index, src, ctx);
505 }
506}
507
508impl<S: ?Sized + PartialEq, T: Default + PropAsRef<S>> ListPropertyItem<ListProp<T>, S> for T {
509 type Value = ();
510
511 #[inline]
512 fn item_value<'a>(
513 dest: &mut ListProp<T>,
514 index: usize,
515 src: &'a S,
516 ctx: &mut bool,
517 ) -> &'a Self::Value {
518 if dest.inner[index].property_as_ref() == src {
519 return &();
520 }
521 dest.inner[index] = PropAsRef::property_to_owned(src);
522 *ctx = true;
523 &()
524 }
525}