Upgrade yew
This commit is contained in:
parent
0ff7d59f63
commit
dd6df30370
18 changed files with 129 additions and 71 deletions
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "bootstrap-rs"
|
name = "bootstrap-rs"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
authors = ["Thomas Gideon <cmdln@thecommandline.net>"]
|
authors = ["Thomas Gideon <cmdln@thecommandline.net>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
validate = [ "validator" ]
|
validate = [ "validator" ]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
yew = "~0.17.2"
|
yew = "^0.18.0"
|
||||||
log = "~0.4.8"
|
log = "~0.4.8"
|
||||||
serde_json = "^1.0.40"
|
serde_json = "^1.0.40"
|
||||||
serde = "^1.0.98"
|
serde = "^1.0.98"
|
||||||
|
|
|
@ -17,10 +17,10 @@ pub enum Request {
|
||||||
Clear,
|
Clear,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Option<(Color, String)>> for &Request {
|
impl From<&Request> for Option<(Color, String)> {
|
||||||
fn into(self) -> Option<(Color, String)> {
|
fn from(request: &Request) -> Option<(Color, String)> {
|
||||||
use Request::*;
|
use Request::*;
|
||||||
match self {
|
match request {
|
||||||
Primary(alert) => Some((Color::Primary, alert.clone())),
|
Primary(alert) => Some((Color::Primary, alert.clone())),
|
||||||
Secondary(alert) => Some((Color::Secondary, alert.clone())),
|
Secondary(alert) => Some((Color::Secondary, alert.clone())),
|
||||||
Success(alert) => Some((Color::Success, alert.clone())),
|
Success(alert) => Some((Color::Success, alert.clone())),
|
||||||
|
|
|
@ -11,6 +11,8 @@ use yew::prelude::*;
|
||||||
pub struct Alert {
|
pub struct Alert {
|
||||||
link: ComponentLink<Self>,
|
link: ComponentLink<Self>,
|
||||||
props: Props,
|
props: Props,
|
||||||
|
prefix: String,
|
||||||
|
prefixed_color: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Alert {
|
impl Component for Alert {
|
||||||
|
@ -18,7 +20,12 @@ impl Component for Alert {
|
||||||
type Message = ();
|
type Message = ();
|
||||||
|
|
||||||
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||||
Self { link, props }
|
Self {
|
||||||
|
link,
|
||||||
|
props,
|
||||||
|
prefix: String::from("alert"),
|
||||||
|
prefixed_color: String::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||||
|
@ -27,6 +34,9 @@ impl Component for Alert {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn change(&mut self, props: Self::Properties) -> ShouldRender {
|
fn change(&mut self, props: Self::Properties) -> ShouldRender {
|
||||||
|
if props.color != self.props.color {
|
||||||
|
self.prefixed_color = props.color.with_prefix(&self.prefix);
|
||||||
|
}
|
||||||
render_if_ne(&mut self.props, props)
|
render_if_ne(&mut self.props, props)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,10 +55,10 @@ impl Component for Alert {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
};
|
};
|
||||||
render::render_with_prefix(
|
render::render_with_prefix(&self.props, vec![&self.prefix, &self.prefixed_color], html)
|
||||||
&self.props,
|
|
||||||
vec!["alert", &self.props.color.with_prefix("alert")],
|
|
||||||
html,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rendered(&mut self, _first_render: bool) {}
|
||||||
|
|
||||||
|
fn destroy(&mut self) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,6 +133,6 @@ mod tests {
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
};
|
};
|
||||||
assert_eq!(expected, item.view());
|
crate::test::assert_attrs_eq(expected, item.view());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,11 @@ mod toolbar;
|
||||||
use self::props::Props;
|
use self::props::Props;
|
||||||
pub use self::{group::ButtonGroup, toolbar::ButtonToolbar};
|
pub use self::{group::ButtonGroup, toolbar::ButtonToolbar};
|
||||||
use crate::{prelude::*, render};
|
use crate::{prelude::*, render};
|
||||||
use std::fmt::{Display, Formatter, Result};
|
use std::{
|
||||||
use yew::prelude::*;
|
borrow::Cow,
|
||||||
|
fmt::{Display, Formatter, Result},
|
||||||
|
};
|
||||||
|
use yew::{html::IntoOptPropValue, prelude::*};
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum ButtonType {
|
pub enum ButtonType {
|
||||||
|
@ -15,6 +18,27 @@ pub enum ButtonType {
|
||||||
Reset,
|
Reset,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IntoOptPropValue<Cow<'static, str>> for ButtonType {
|
||||||
|
fn into_opt_prop_value(self) -> Option<Cow<'static, str>> {
|
||||||
|
match self {
|
||||||
|
Self::Button => Some(Cow::from("button")),
|
||||||
|
Self::Submit => Some(Cow::from("submit")),
|
||||||
|
Self::Reset => Some(Cow::from("reset")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoOptPropValue<Cow<'static, str>> for &ButtonType {
|
||||||
|
fn into_opt_prop_value(self) -> Option<Cow<'static, str>> {
|
||||||
|
use ButtonType::*;
|
||||||
|
match self {
|
||||||
|
Button => Some(Cow::from("button")),
|
||||||
|
Submit => Some(Cow::from("submit")),
|
||||||
|
Reset => Some(Cow::from("reset")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for ButtonType {
|
impl Default for ButtonType {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ButtonType::Button
|
ButtonType::Button
|
||||||
|
@ -57,7 +81,7 @@ impl Component for Button {
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
let html = html! {
|
let html = html! {
|
||||||
<button
|
<button
|
||||||
button=self.props.button_type
|
button=&self.props.button_type
|
||||||
onclick=self.link.callback(|_| ())
|
onclick=self.link.callback(|_| ())
|
||||||
disabled=self.props.disabled.unwrap_or_default()
|
disabled=self.props.disabled.unwrap_or_default()
|
||||||
>
|
>
|
||||||
|
|
|
@ -44,6 +44,6 @@ mod test {
|
||||||
</div>
|
</div>
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(expected, comp.view());
|
crate::test::assert_attrs_eq(expected, comp.view());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,6 @@ mod test {
|
||||||
</p>
|
</p>
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(expected, comp.view());
|
crate::test::assert_attrs_eq(expected, comp.view());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,6 @@ mod tests {
|
||||||
<div class="container m-3 pt-3">
|
<div class="container m-3 pt-3">
|
||||||
</div>
|
</div>
|
||||||
};
|
};
|
||||||
assert_eq!(expected, container.view());
|
crate::test::assert_attrs_eq(expected, container.view());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,14 @@ use self::props::Props;
|
||||||
pub use self::{group::InputGroup, textarea::TextArea};
|
pub use self::{group::InputGroup, textarea::TextArea};
|
||||||
use crate::{prelude::*, render};
|
use crate::{prelude::*, render};
|
||||||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
use std::fmt::{Display, Formatter, Result as FmtResult};
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
fmt::{Display, Formatter, Result as FmtResult},
|
||||||
|
};
|
||||||
#[cfg(feature = "validate")]
|
#[cfg(feature = "validate")]
|
||||||
use validator::ValidationErrors;
|
use validator::ValidationErrors;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
use yew::{html::IntoOptPropValue, prelude::*};
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum InputType {
|
pub enum InputType {
|
||||||
|
@ -19,6 +24,18 @@ pub enum InputType {
|
||||||
Color,
|
Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IntoOptPropValue<Cow<'static, str>> for &InputType {
|
||||||
|
fn into_opt_prop_value(self) -> Option<Cow<'static, str>> {
|
||||||
|
match self {
|
||||||
|
InputType::Text => Some(Cow::from("text")),
|
||||||
|
InputType::Date => Some(Cow::from("date")),
|
||||||
|
InputType::DateTime => Some(Cow::from("datetime-local")),
|
||||||
|
InputType::Checkbox => Some(Cow::from("checkbox")),
|
||||||
|
InputType::Color => Some(Cow::from("color")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for InputType {
|
impl Display for InputType {
|
||||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||||
match self {
|
match self {
|
||||||
|
@ -78,9 +95,9 @@ impl Component for Input {
|
||||||
let html = html! {
|
let html = html! {
|
||||||
<input
|
<input
|
||||||
type=input_type
|
type=input_type
|
||||||
value=&self.state
|
value=self.state.clone()
|
||||||
readonly=self.props.readonly
|
readonly=self.props.readonly
|
||||||
onchange=self.link.callback(|evt| InputChange(evt))
|
onchange=self.link.callback(InputChange)
|
||||||
/>
|
/>
|
||||||
};
|
};
|
||||||
render::render_with_prefix(&self.props, prefix, html)
|
render::render_with_prefix(&self.props, prefix, html)
|
||||||
|
|
|
@ -40,16 +40,14 @@ impl Component for TextArea {
|
||||||
|
|
||||||
#[cfg(not(feature = "validate"))]
|
#[cfg(not(feature = "validate"))]
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
let prefix = vec!["form-control", &valid_as_class(&self.props.valid)];
|
let prefix = vec!["form-control", valid_as_class(&self.props.valid)];
|
||||||
let html = {
|
let html = html! {
|
||||||
html! {
|
|
||||||
<textarea
|
<textarea
|
||||||
readonly=self.props.readonly
|
readonly=self.props.readonly
|
||||||
onchange=self.link.callback(|evt| InputChange(evt))
|
onchange=self.link.callback(InputChange)
|
||||||
>
|
>
|
||||||
{ &self.state }
|
{ &self.state }
|
||||||
</textarea>
|
</textarea>
|
||||||
}
|
|
||||||
};
|
};
|
||||||
render::render_with_prefix(&self.props, prefix, html)
|
render::render_with_prefix(&self.props, prefix, html)
|
||||||
}
|
}
|
||||||
|
|
15
src/lib.rs
15
src/lib.rs
|
@ -20,3 +20,18 @@ pub use self::{
|
||||||
input::{Input, InputGroup, TextArea},
|
input::{Input, InputGroup, TextArea},
|
||||||
jumbotron::Jumbotron,
|
jumbotron::Jumbotron,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod test {
|
||||||
|
use yew::virtual_dom::VNode;
|
||||||
|
|
||||||
|
pub(crate) fn assert_attrs_eq(expected: VNode, comp: VNode) {
|
||||||
|
match (expected, comp) {
|
||||||
|
(VNode::VTag(mut expected), VNode::VTag(mut comp)) => assert_eq!(
|
||||||
|
expected.attributes.get_mut_index_map(),
|
||||||
|
comp.attributes.get_mut_index_map()
|
||||||
|
),
|
||||||
|
_ => panic!("One or both components were not tags!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
use super::{Color, Edge};
|
use super::{Color, Edge};
|
||||||
use crate::props::IntoBsClass;
|
use yew::Classes;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct Border(pub Edge, pub Color);
|
pub struct Border(pub Edge, pub Color);
|
||||||
|
|
||||||
impl IntoBsClass for Border {
|
impl From<&&Border> for Classes {
|
||||||
fn as_classname(&self) -> String {
|
fn from(border: &&Border) -> Classes {
|
||||||
let edge = match self.0 {
|
let edge = match border.0 {
|
||||||
Edge::All => "border".to_owned(),
|
Edge::All => "border".to_owned(),
|
||||||
_ => self.0.with_prefix("border"),
|
_ => border.0.with_prefix("border"),
|
||||||
};
|
};
|
||||||
format!("{} {}", edge, self.1.with_prefix("border"))
|
Classes::from(format!("{} {}", edge, border.1.with_prefix("border")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use yew::html::ImplicitClone;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
Primary,
|
Primary,
|
||||||
|
@ -12,6 +14,8 @@ pub enum Color {
|
||||||
Unset,
|
Unset,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ImplicitClone for Color {}
|
||||||
|
|
||||||
impl Default for Color {
|
impl Default for Color {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Unset
|
Self::Unset
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::props::IntoBsClass;
|
use yew::Classes;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Margin(pub super::Edge, pub usize);
|
pub struct Margin(pub super::Edge, pub usize);
|
||||||
|
|
||||||
impl IntoBsClass for Margin {
|
impl From<&&Margin> for Classes {
|
||||||
fn as_classname(&self) -> String {
|
fn from(margin: &&Margin) -> Classes {
|
||||||
format!("m{}-{}", self.0, self.1)
|
Classes::from(format!("m{}-{}", margin.0, margin.1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,9 @@ use yew::prelude::*;
|
||||||
/// edit or correct as needed.
|
/// edit or correct as needed.
|
||||||
pub struct InputString<S: AsRef<str>>(pub S);
|
pub struct InputString<S: AsRef<str>>(pub S);
|
||||||
|
|
||||||
impl<S: AsRef<str>> Into<Option<String>> for InputString<S> {
|
impl<S: AsRef<str>> From<InputString<S>> for Option<String> {
|
||||||
fn into(self) -> Option<String> {
|
fn from(input: InputString<S>) -> Option<String> {
|
||||||
let InputString(input) = self;
|
let InputString(input) = input;
|
||||||
if input.as_ref().is_empty() {
|
if input.as_ref().is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::props::IntoBsClass;
|
use yew::Classes;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Padding(pub super::Edge, pub usize);
|
pub struct Padding(pub super::Edge, pub usize);
|
||||||
|
|
||||||
impl IntoBsClass for Padding {
|
impl From<&&Padding> for Classes {
|
||||||
fn as_classname(&self) -> String {
|
fn from(padding: &&Padding) -> Classes {
|
||||||
format!("p{}-{}", self.0, self.1)
|
Classes::from(format!("p{}-{}", padding.0, padding.1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
42
src/props.rs
42
src/props.rs
|
@ -4,10 +4,6 @@ use std::collections::HashMap;
|
||||||
use validator::ValidationErrors;
|
use validator::ValidationErrors;
|
||||||
use yew::{prelude::*, virtual_dom::VNode};
|
use yew::{prelude::*, virtual_dom::VNode};
|
||||||
|
|
||||||
pub(crate) trait IntoBsClass {
|
|
||||||
fn as_classname(&self) -> String;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn collect_props<'a, T>(t: &'a Option<T>, ts: &'a [T]) -> Vec<&'a T> {
|
pub(crate) fn collect_props<'a, T>(t: &'a Option<T>, ts: &'a [T]) -> Vec<&'a T> {
|
||||||
if let Some(t) = t.as_ref() {
|
if let Some(t) = t.as_ref() {
|
||||||
let mut r = vec![t];
|
let mut r = vec![t];
|
||||||
|
@ -89,12 +85,12 @@ pub struct BootstrapProps<'a> {
|
||||||
impl<'a> BootstrapProps<'a> {
|
impl<'a> BootstrapProps<'a> {
|
||||||
pub fn add_attributes(&self, html: &mut VNode) {
|
pub fn add_attributes(&self, html: &mut VNode) {
|
||||||
if let VNode::VTag(tag) = html {
|
if let VNode::VTag(tag) = html {
|
||||||
let attrs = self
|
for key in self.attributes.keys() {
|
||||||
.attributes
|
if let Some(value) = &self.attributes.get(key) {
|
||||||
.iter()
|
let value = **value;
|
||||||
.map(|(key, value)| (key.to_string(), (*value).to_owned()))
|
tag.add_attribute(key, value.clone());
|
||||||
.collect();
|
}
|
||||||
tag.add_attributes(attrs);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,21 +102,15 @@ impl<'a> BootstrapProps<'a> {
|
||||||
paddings,
|
paddings,
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
let classes = prefix.into();
|
let mut classes: Classes = prefix.into();
|
||||||
let classes = classes.extend((*class).to_owned());
|
classes.push((*class).to_owned());
|
||||||
let classes = classes.extend(into_classnames(borders));
|
classes.extend(borders);
|
||||||
let classes = classes.extend(into_classnames(margins));
|
classes.extend(margins);
|
||||||
classes.extend(into_classnames(paddings))
|
classes.extend(paddings);
|
||||||
|
classes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_classnames<C: IntoBsClass>(c: &[&C]) -> Classes {
|
|
||||||
c.iter().fold(Classes::new(), |mut cs, c| {
|
|
||||||
cs.push(&c.as_classname());
|
|
||||||
cs
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn add_opt_attr<'a>(
|
pub(crate) fn add_opt_attr<'a>(
|
||||||
attrs: &mut HashMap<&str, &'a String>,
|
attrs: &mut HashMap<&str, &'a String>,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
|
@ -146,9 +136,9 @@ mod test {
|
||||||
<div class="" aria-label="override" />
|
<div class="" aria-label="override" />
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(
|
crate::test::assert_attrs_eq(
|
||||||
expected,
|
expected,
|
||||||
crate::render::render_with_prefix(&props, "", html! { <div aria-label="test" /> })
|
crate::render::render_with_prefix(&props, "", html! { <div aria-label="test" /> }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,9 +153,9 @@ mod test {
|
||||||
<div class="" aria-label="some-label" />
|
<div class="" aria-label="some-label" />
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(
|
crate::test::assert_attrs_eq(
|
||||||
expected,
|
expected,
|
||||||
crate::render::render_with_prefix(&props, "", html! { <div /> })
|
crate::render::render_with_prefix(&props, "", html! { <div /> }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,13 @@ pub(crate) fn render_with_prefix<'a, B: Into<BootstrapProps<'a>>, C: Into<Classe
|
||||||
mut node: VNode,
|
mut node: VNode,
|
||||||
) -> VNode {
|
) -> VNode {
|
||||||
let props = props.into();
|
let props = props.into();
|
||||||
|
props.add_attributes(&mut node);
|
||||||
if let VNode::VTag(tag) = &mut node {
|
if let VNode::VTag(tag) = &mut node {
|
||||||
let classes = &props.calculate_classes(prefix);
|
let classes = &props.calculate_classes(prefix);
|
||||||
if !classes.is_empty() {
|
if !classes.is_empty() {
|
||||||
tag.add_attribute("class", classes);
|
tag.add_attribute("class", classes.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
props.add_attributes(&mut node);
|
|
||||||
node
|
node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,6 @@ mod tests {
|
||||||
<div class="first second m-3"/>
|
<div class="first second m-3"/>
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(expected, comp);
|
crate::test::assert_attrs_eq(expected, comp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue