use std::marker::PhantomData;
use wasm_bindgen::{JsCast, UnwrapThrowExt};
use crate::core::{MessageContext, MessageResult, Mut, View, ViewElement, ViewMarker};
use crate::modifiers::{Modifier, WithModifier};
use crate::vecmap::VecMap;
use crate::{AttributeValue, DomView, IntoAttributeValue, ViewCtx};
type CowStr = std::borrow::Cow<'static, str>;
#[derive(Debug, PartialEq, Clone)]
pub enum AttributeModifier {
Set(CowStr, AttributeValue),
Remove(CowStr),
}
impl AttributeModifier {
pub fn name(&self) -> &CowStr {
let (Self::Set(name, _) | Self::Remove(name)) = self;
name
}
pub fn into_name(self) -> CowStr {
let (Self::Set(name, _) | Self::Remove(name)) = self;
name
}
}
impl<K: Into<CowStr>, V: IntoAttributeValue> From<(K, V)> for AttributeModifier {
fn from((name, value): (K, V)) -> Self {
match value.into_attr_value() {
Some(value) => Self::Set(name.into(), value),
None => Self::Remove(name.into()),
}
}
}
#[derive(Default)]
pub struct Attributes {
modifiers: Vec<AttributeModifier>,
updated: VecMap<CowStr, ()>,
idx: usize,
}
impl Attributes {
pub(crate) fn new(size_hint: usize) -> Self {
Self {
modifiers: Vec::with_capacity(size_hint),
..Default::default()
}
}
pub fn apply_changes(this: Modifier<'_, Self>, element: &web_sys::Element) {
let Modifier { modifier, flags } = this;
if !flags.in_hydration() && flags.was_created() {
for modifier in &modifier.modifiers {
match modifier {
AttributeModifier::Remove(n) => remove_attribute(element, n),
AttributeModifier::Set(n, v) => set_attribute(element, n, &v.serialize()),
}
}
} else if !modifier.updated.is_empty() {
for m in modifier.modifiers.iter().rev() {
match m {
AttributeModifier::Remove(name) if modifier.updated.remove(name).is_some() => {
remove_attribute(element, name);
}
AttributeModifier::Set(name, value)
if modifier.updated.remove(name).is_some() =>
{
set_attribute(element, name, &value.serialize());
}
_ => {}
}
}
for (name, ()) in modifier.updated.drain() {
remove_attribute(element, &name);
}
}
debug_assert!(modifier.updated.is_empty());
}
#[inline]
pub fn rebuild<E: WithModifier<Self>>(mut element: E, prev_len: usize, f: impl FnOnce(E)) {
element.modifier().modifier.idx -= prev_len;
f(element);
}
#[inline]
pub fn push(this: &mut Modifier<'_, Self>, modifier: impl Into<AttributeModifier>) {
debug_assert!(
this.flags.was_created(),
"This should never be called, when the underlying element wasn't (re)created. Use `Attributes::insert` instead."
);
this.flags.set_needs_update();
this.modifier.modifiers.push(modifier.into());
this.modifier.idx += 1;
}
#[inline]
pub fn insert(this: &mut Modifier<'_, Self>, modifier: impl Into<AttributeModifier>) {
debug_assert!(
!this.flags.was_created(),
"This should never be called, when the underlying element was (re)created, use `Attributes::push` instead."
);
this.flags.set_needs_update();
let modifier = modifier.into();
this.modifier.updated.insert(modifier.name().clone(), ());
this.modifier.modifiers.insert(this.modifier.idx, modifier);
this.modifier.idx += 1;
}
#[inline]
pub fn mutate<R>(
this: &mut Modifier<'_, Self>,
f: impl FnOnce(&mut AttributeModifier) -> R,
) -> R {
debug_assert!(
!this.flags.was_created(),
"This should never be called, when the underlying element was (re)created."
);
this.flags.set_needs_update();
let modifier = &mut this.modifier.modifiers[this.modifier.idx];
let old = modifier.name().clone();
let rv = f(modifier);
let new = modifier.name();
if *new != old {
this.modifier.updated.insert(new.clone(), ());
}
this.modifier.updated.insert(old, ());
this.modifier.idx += 1;
rv
}
pub fn skip(this: &mut Modifier<'_, Self>, count: usize) {
debug_assert!(
!this.flags.was_created(),
"This should never be called, when the underlying element was (re)created."
);
this.modifier.idx += count;
}
pub fn delete(this: &mut Modifier<'_, Self>, count: usize) {
debug_assert!(
!this.flags.was_created(),
"This should never be called, when the underlying element was (re)created."
);
let start = this.modifier.idx;
this.flags.set_needs_update();
for modifier in this.modifier.modifiers.drain(start..(start + count)) {
this.modifier.updated.insert(modifier.into_name(), ());
}
}
pub fn update(
this: &mut Modifier<'_, Self>,
prev: &AttributeModifier,
next: &AttributeModifier,
) {
if this.flags.was_created() {
Self::push(this, next.clone());
} else if next != prev {
Self::mutate(this, |modifier| *modifier = next.clone());
} else {
Self::skip(this, 1);
}
}
pub fn update_with_same_key<Value: IntoAttributeValue + PartialEq + Clone>(
this: &mut Modifier<'_, Self>,
key: impl Into<CowStr>,
prev: &Value,
next: &Value,
) {
if this.flags.was_created() {
Self::push(this, (key, next.clone()));
} else if next != prev {
Self::mutate(this, |modifier| *modifier = (key, next.clone()).into());
} else {
Self::skip(this, 1);
}
}
}
fn html_input_attr_assertions(element: &web_sys::Element, name: &str) {
debug_assert!(
!(element.is_instance_of::<web_sys::HtmlInputElement>() && name == "checked"),
"Using `checked` as attribute on a checkbox is not supported, \
use the `el.checked()` or `el.default_checked()` modifier instead."
);
debug_assert!(
!(element.is_instance_of::<web_sys::HtmlInputElement>() && name == "disabled"),
"Using `disabled` as attribute on an input element is not supported, \
use the `el.checked()` modifier instead."
);
}
fn element_attr_assertions(element: &web_sys::Element, name: &str) {
debug_assert_ne!(
name, "class",
"Using `class` as attribute is not supported, use the `el.class()` modifier instead"
);
debug_assert_ne!(
name, "style",
"Using `style` as attribute is not supported, use the `el.style()` modifier instead"
);
html_input_attr_assertions(element, name);
}
fn set_attribute(element: &web_sys::Element, name: &str, value: &str) {
element_attr_assertions(element, name);
if name == "value" {
if let Some(input_element) = element.dyn_ref::<web_sys::HtmlInputElement>() {
input_element.set_value(value);
} else {
element.set_attribute("value", value).unwrap_throw();
}
} else {
element.set_attribute(name, value).unwrap_throw();
}
}
fn remove_attribute(element: &web_sys::Element, name: &str) {
element_attr_assertions(element, name);
element.remove_attribute(name).unwrap_throw();
}
pub struct Attr<V, State, Action> {
inner: V,
modifier: AttributeModifier,
phantom: PhantomData<fn() -> (State, Action)>,
}
impl<V, State, Action> Attr<V, State, Action> {
pub fn new(el: V, name: CowStr, value: Option<AttributeValue>) -> Self {
let modifier = match value {
Some(value) => AttributeModifier::Set(name, value),
None => AttributeModifier::Remove(name),
};
Self {
inner: el,
modifier,
phantom: PhantomData,
}
}
}
impl<V, State, Action> ViewMarker for Attr<V, State, Action> {}
impl<V, State, Action> View<State, Action, ViewCtx> for Attr<V, State, Action>
where
State: 'static,
Action: 'static,
V: DomView<State, Action, Element: WithModifier<Attributes>>,
for<'a> <V::Element as ViewElement>::Mut<'a>: WithModifier<Attributes>,
{
type Element = V::Element;
type ViewState = V::ViewState;
fn build(&self, ctx: &mut ViewCtx, app_state: &mut State) -> (Self::Element, Self::ViewState) {
let (mut element, state) =
ctx.with_size_hint::<Attributes, _>(1, |ctx| self.inner.build(ctx, app_state));
Attributes::push(&mut element.modifier(), self.modifier.clone());
(element, state)
}
fn rebuild(
&self,
prev: &Self,
view_state: &mut Self::ViewState,
ctx: &mut ViewCtx,
element: Mut<'_, Self::Element>,
app_state: &mut State,
) {
Attributes::rebuild(element, 1, |mut element| {
self.inner.rebuild(
&prev.inner,
view_state,
ctx,
element.reborrow_mut(),
app_state,
);
Attributes::update(&mut element.modifier(), &prev.modifier, &self.modifier);
});
}
fn teardown(
&self,
view_state: &mut Self::ViewState,
ctx: &mut ViewCtx,
element: Mut<'_, Self::Element>,
) {
self.inner.teardown(view_state, ctx, element);
}
fn message(
&self,
view_state: &mut Self::ViewState,
message: &mut MessageContext,
element: Mut<'_, Self::Element>,
app_state: &mut State,
) -> MessageResult<Action> {
self.inner.message(view_state, message, element, app_state)
}
}