♻️ (html): API para id's en Props y componentes
This commit is contained in:
parent
8d0103c257
commit
62219584b0
31 changed files with 541 additions and 405 deletions
|
|
@ -389,10 +389,10 @@ body {
|
|||
.intro-text-body .block {
|
||||
position: relative;
|
||||
}
|
||||
.intro-text-body .block__title {
|
||||
.intro-text-body .block-title {
|
||||
margin: 1em 0 .8em;
|
||||
}
|
||||
.intro-text-body .block__title span {
|
||||
.intro-text-body .block-title span {
|
||||
display: inline-block;
|
||||
padding: 10px 30px 14px;
|
||||
margin: 30px 20px 0;
|
||||
|
|
@ -403,7 +403,7 @@ body {
|
|||
border-color: orangered;
|
||||
transform: rotate(-3deg) translateY(-25%);
|
||||
}
|
||||
.intro-text-body .block__title:before {
|
||||
.intro-text-body .block-title:before {
|
||||
content: "";
|
||||
height: 5px;
|
||||
position: absolute;
|
||||
|
|
@ -416,7 +416,7 @@ body {
|
|||
transform: rotate(2deg) translateY(-50%);
|
||||
transform-origin: top left;
|
||||
}
|
||||
.intro-text-body .block__title:after {
|
||||
.intro-text-body .block-title:after {
|
||||
content: "";
|
||||
height: 120%;
|
||||
position: absolute;
|
||||
|
|
@ -427,22 +427,22 @@ body {
|
|||
background: var(--intro-bg-block-1);
|
||||
transform: rotate(2deg);
|
||||
}
|
||||
.intro-text-body .block:nth-of-type(6n+1) .block__title:after {
|
||||
.intro-text-body .block:nth-of-type(6n+1) .block-title:after {
|
||||
background: var(--intro-bg-block-1);
|
||||
}
|
||||
.intro-text-body .block:nth-of-type(6n+2) .block__title:after {
|
||||
.intro-text-body .block:nth-of-type(6n+2) .block-title:after {
|
||||
background: var(--intro-bg-block-2);
|
||||
}
|
||||
.intro-text-body .block:nth-of-type(6n+3) .block__title:after {
|
||||
.intro-text-body .block:nth-of-type(6n+3) .block-title:after {
|
||||
background: var(--intro-bg-block-3);
|
||||
}
|
||||
.intro-text-body .block:nth-of-type(6n+4) .block__title:after {
|
||||
.intro-text-body .block:nth-of-type(6n+4) .block-title:after {
|
||||
background: var(--intro-bg-block-4);
|
||||
}
|
||||
.intro-text-body .block:nth-of-type(6n+5) .block__title:after {
|
||||
.intro-text-body .block:nth-of-type(6n+5) .block-title:after {
|
||||
background: var(--intro-bg-block-5);
|
||||
}
|
||||
.intro-text-body .block:nth-of-type(6n+6) .block__title:after {
|
||||
.intro-text-body .block:nth-of-type(6n+6) .block-title:after {
|
||||
background: var(--intro-bg-block-6);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@
|
|||
font-display: swap;
|
||||
}
|
||||
|
||||
// Font-relative top offset to keep the skip-link hidden at any font size.
|
||||
.skip-link {
|
||||
top: -3em;
|
||||
}
|
||||
|
||||
// Required field indicator in forms.
|
||||
.form-required {
|
||||
color: var(--bs-danger);
|
||||
|
|
|
|||
|
|
@ -45,9 +45,7 @@ use crate::theme::{ButtonAction, ButtonColor, ButtonSize};
|
|||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Button {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS del botón.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve el comportamiento del botón al activarse.
|
||||
kind: ButtonAction,
|
||||
|
|
@ -73,7 +71,7 @@ impl Component for Button {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
|
|
@ -86,7 +84,6 @@ impl Component for Button {
|
|||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
Ok(html! {
|
||||
button
|
||||
id=[self.id()]
|
||||
type=(self.kind())
|
||||
(self.props())
|
||||
name=[self.name().get()]
|
||||
|
|
@ -140,14 +137,14 @@ impl Button {
|
|||
|
||||
// **< Button BUILDER >*************************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del botón.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del botón.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
|
|
|
|||
|
|
@ -20,9 +20,7 @@ use crate::theme::*;
|
|||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Container {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS del contenedor.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve el tipo semántico del contenedor.
|
||||
container_kind: container::Kind,
|
||||
|
|
@ -38,7 +36,7 @@ impl Component for Container {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
|
|
@ -58,32 +56,32 @@ impl Component for Container {
|
|||
};
|
||||
Ok(match self.container_kind() {
|
||||
container::Kind::Default => html! {
|
||||
div id=[self.id()] (self.props()) style=[style] {
|
||||
div (self.props()) style=[style] {
|
||||
(output)
|
||||
}
|
||||
},
|
||||
container::Kind::Main => html! {
|
||||
main id=[self.id()] (self.props()) style=[style] {
|
||||
main (self.props()) style=[style] {
|
||||
(output)
|
||||
}
|
||||
},
|
||||
container::Kind::Header => html! {
|
||||
header id=[self.id()] (self.props()) style=[style] {
|
||||
header (self.props()) style=[style] {
|
||||
(output)
|
||||
}
|
||||
},
|
||||
container::Kind::Footer => html! {
|
||||
footer id=[self.id()] (self.props()) style=[style] {
|
||||
footer (self.props()) style=[style] {
|
||||
(output)
|
||||
}
|
||||
},
|
||||
container::Kind::Section => html! {
|
||||
section id=[self.id()] (self.props()) style=[style] {
|
||||
section (self.props()) style=[style] {
|
||||
(output)
|
||||
}
|
||||
},
|
||||
container::Kind::Article => html! {
|
||||
article id=[self.id()] (self.props()) style=[style] {
|
||||
article (self.props()) style=[style] {
|
||||
(output)
|
||||
}
|
||||
},
|
||||
|
|
@ -134,14 +132,14 @@ impl Container {
|
|||
|
||||
// **< Container BUILDER >**********************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del contenedor.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del contenedor.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
///
|
||||
/// También acepta clases predefinidas para:
|
||||
///
|
||||
|
|
|
|||
|
|
@ -38,9 +38,7 @@ use crate::theme::*;
|
|||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Dropdown {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS del menú desplegable.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve el título del menú desplegable.
|
||||
title: L10n,
|
||||
|
|
@ -70,7 +68,7 @@ impl Component for Dropdown {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
|
|
@ -90,7 +88,7 @@ impl Component for Dropdown {
|
|||
let title = self.title().using(cx);
|
||||
|
||||
Ok(html! {
|
||||
div id=[self.id()] (self.props()) {
|
||||
div (self.props()) {
|
||||
@if !title.is_empty() {
|
||||
@let btn_base = {
|
||||
let mut classes = "btn".to_string();
|
||||
|
|
@ -178,14 +176,14 @@ impl Component for Dropdown {
|
|||
impl Dropdown {
|
||||
// **< Dropdown BUILDER >***********************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del menú desplegable.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del menú desplegable.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
|
|
|
|||
|
|
@ -45,9 +45,7 @@ pub enum ItemKind {
|
|||
/// asociada, manteniendo una interfaz común para renderizar todos los elementos del menú.
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Item {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS del elemento.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve el tipo de elemento representado.
|
||||
item_kind: ItemKind,
|
||||
|
|
@ -59,7 +57,7 @@ impl Component for Item {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
|
|
@ -67,7 +65,7 @@ impl Component for Item {
|
|||
ItemKind::Void => html! {},
|
||||
|
||||
ItemKind::Label(label) => html! {
|
||||
li id=[self.id()] (self.props()) {
|
||||
li (self.props()) {
|
||||
span class="dropdown-item-text" {
|
||||
(label.using(cx))
|
||||
}
|
||||
|
|
@ -101,7 +99,7 @@ impl Component for Item {
|
|||
let tabindex = disabled.then_some("-1");
|
||||
|
||||
html! {
|
||||
li id=[self.id()] (self.props()) {
|
||||
li (self.props()) {
|
||||
a
|
||||
class=(classes)
|
||||
href=[href]
|
||||
|
|
@ -127,7 +125,7 @@ impl Component for Item {
|
|||
let disabled_attr = disabled.then_some("disabled");
|
||||
|
||||
html! {
|
||||
li id=[self.id()] (self.props()) {
|
||||
li (self.props()) {
|
||||
button
|
||||
class=(classes)
|
||||
type="button"
|
||||
|
|
@ -141,7 +139,7 @@ impl Component for Item {
|
|||
}
|
||||
|
||||
ItemKind::Header(label) => html! {
|
||||
li id=[self.id()] (self.props()) {
|
||||
li (self.props()) {
|
||||
h6 class="dropdown-header" {
|
||||
(label.using(cx))
|
||||
}
|
||||
|
|
@ -149,7 +147,7 @@ impl Component for Item {
|
|||
},
|
||||
|
||||
ItemKind::Divider => html! {
|
||||
li id=[self.id()] (self.props()) { hr class="dropdown-divider" {} }
|
||||
li (self.props()) { hr class="dropdown-divider" {} }
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -260,14 +258,14 @@ impl Item {
|
|||
|
||||
// **< Item BUILDER >***************************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del elemento.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del elemento.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
|
|
|
|||
|
|
@ -108,9 +108,7 @@ impl Item {
|
|||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Field {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS del contenedor del grupo.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve el nombre base compartido por todas las casillas del grupo.
|
||||
name: AttrName,
|
||||
|
|
@ -132,21 +130,31 @@ impl Component for Field {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
self.alter_prop(PropsOp::prepend_classes("form-field form-field-checkboxes"));
|
||||
}
|
||||
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
fn setup(&mut self, cx: &Context) {
|
||||
// Asegura `name` e `id`.
|
||||
// Si falta uno se deriva del otro; si faltan ambos se genera un valor único.
|
||||
let name = self
|
||||
.name()
|
||||
.get()
|
||||
.unwrap_or_else(|| cx.required_id::<Self>(self.id(), 3));
|
||||
self.alter_name(&name);
|
||||
let container_id = self.id().unwrap_or_else(|| util::join!("edit-", &name));
|
||||
self.alter_prop(PropsOp::ensure_id(container_id));
|
||||
|
||||
// Clases CSS del contenedor del grupo de casillas.
|
||||
self.alter_prop(PropsOp::prepend_classes("form-field form-field-checkboxes"));
|
||||
}
|
||||
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
// En `setup()` se garantiza que `name` e `id` están definidos antes del renderizado.
|
||||
let name = self.name().get().unwrap();
|
||||
let container_id = self.id().unwrap();
|
||||
|
||||
Ok(html! {
|
||||
div id=(&container_id) (self.props()) {
|
||||
div (self.props()) {
|
||||
@if let Some(label) = self.label().lookup(cx) {
|
||||
label class="form-label" { (label) }
|
||||
}
|
||||
|
|
@ -188,14 +196,14 @@ impl Component for Field {
|
|||
impl Field {
|
||||
// **< Field BUILDER >**************************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del grupo de casillas.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del contenedor del grupo de casillas.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
|
|
|
|||
|
|
@ -43,9 +43,7 @@ use crate::theme::form;
|
|||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Checkbox {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS del contenedor del control.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve la variante visual del control.
|
||||
checkbox_kind: form::CheckboxKind,
|
||||
|
|
@ -73,10 +71,21 @@ impl Component for Checkbox {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
fn setup(&mut self, cx: &Context) {
|
||||
// Asegura `name` e `id`.
|
||||
// Si falta uno se deriva del otro; si faltan ambos se genera un valor único.
|
||||
let name = self
|
||||
.name()
|
||||
.get()
|
||||
.unwrap_or_else(|| cx.required_id::<Self>(self.id(), 1));
|
||||
self.alter_name(&name);
|
||||
let container_id = self.id().unwrap_or_else(|| util::join!("edit-", &name));
|
||||
self.alter_prop(PropsOp::ensure_id(container_id));
|
||||
|
||||
// Clases CSS del contenedor de la casilla de verificación.
|
||||
let mut classes = "form-field form-check".to_string();
|
||||
if *self.checkbox_kind() == form::CheckboxKind::Switch {
|
||||
classes.push_str(" form-switch");
|
||||
|
|
@ -91,15 +100,15 @@ impl Component for Checkbox {
|
|||
}
|
||||
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let name = self
|
||||
.name()
|
||||
.get()
|
||||
.unwrap_or_else(|| cx.required_id::<Self>(self.id(), 1));
|
||||
let container_id = self.id().unwrap_or_else(|| util::join!("edit-", &name));
|
||||
// En `setup()` se garantiza que `name` e `id` están definidos antes del renderizado.
|
||||
let name = self.name().get().unwrap();
|
||||
let container_id = self.id().unwrap();
|
||||
|
||||
let checkbox_id = util::join!(&container_id, "-checkbox");
|
||||
let is_switch = *self.checkbox_kind() == form::CheckboxKind::Switch;
|
||||
|
||||
Ok(html! {
|
||||
div id=(&container_id) (self.props()) {
|
||||
div (self.props()) {
|
||||
input
|
||||
type="checkbox"
|
||||
role=[is_switch.then_some("switch")]
|
||||
|
|
@ -145,14 +154,14 @@ impl Checkbox {
|
|||
|
||||
// **< Checkbox BUILDER >***********************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del control.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del contenedor del control.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
|
|
|
|||
|
|
@ -47,9 +47,7 @@ use crate::theme::form;
|
|||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Form {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS del formulario.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve la URL/ruta de destino del formulario.
|
||||
action: AttrValue,
|
||||
|
|
@ -68,7 +66,7 @@ impl Component for Form {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
|
|
@ -82,7 +80,6 @@ impl Component for Form {
|
|||
};
|
||||
Ok(html! {
|
||||
form
|
||||
id=[self.id()]
|
||||
(self.props())
|
||||
action=[self.action().get()]
|
||||
method=[method]
|
||||
|
|
@ -97,14 +94,14 @@ impl Component for Form {
|
|||
impl Form {
|
||||
// **< Form BUILDER >***************************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del formulario.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del formulario.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
|
|
|
|||
|
|
@ -24,9 +24,7 @@ use pagetop::prelude::*;
|
|||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Fieldset {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS del `fieldset`.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve la leyenda del `fieldset`.
|
||||
legend: Attr<L10n>,
|
||||
|
|
@ -44,7 +42,7 @@ impl Component for Fieldset {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
|
|
@ -55,7 +53,7 @@ impl Component for Fieldset {
|
|||
}
|
||||
|
||||
Ok(html! {
|
||||
fieldset id=[self.id()] (self.props()) disabled[*self.disabled()] {
|
||||
fieldset (self.props()) disabled[*self.disabled()] {
|
||||
@if let Some(legend) = self.legend().lookup(cx) {
|
||||
legend { (legend) }
|
||||
}
|
||||
|
|
@ -71,14 +69,14 @@ impl Component for Fieldset {
|
|||
impl Fieldset {
|
||||
// **< Fieldset BUILDER >***********************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del `fieldset` (grupo de controles).
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del `fieldset`.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
|
|
|
|||
|
|
@ -126,9 +126,7 @@ impl fmt::Display for Mode {
|
|||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Field {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS del contenedor del campo.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve el tipo de campo.
|
||||
kind: Kind,
|
||||
|
|
@ -170,10 +168,18 @@ impl Component for Field {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
if let Some(container_id) = self
|
||||
.id()
|
||||
.or_else(|| self.name().get().map(|n| util::join!("edit-", n)))
|
||||
{
|
||||
self.alter_prop(PropsOp::ensure_id(container_id));
|
||||
}
|
||||
|
||||
// Clases CSS del contenedor del campo de texto.
|
||||
if *self.floating_label() {
|
||||
self.alter_prop(PropsOp::prepend_classes("form-floating"));
|
||||
}
|
||||
|
|
@ -184,9 +190,7 @@ impl Component for Field {
|
|||
}
|
||||
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let container_id = self
|
||||
.id()
|
||||
.or_else(|| self.name().get().map(|n| util::join!("edit-", n)));
|
||||
let container_id = self.id();
|
||||
let input_id = container_id.as_deref().map(|id| util::join!(id, "-input"));
|
||||
let input_class = if *self.plaintext() {
|
||||
"form-control-plaintext"
|
||||
|
|
@ -217,7 +221,7 @@ impl Component for Field {
|
|||
None => html! {},
|
||||
};
|
||||
Ok(html! {
|
||||
div id=[container_id.as_deref()] (self.props()) {
|
||||
div (self.props()) {
|
||||
@if !*self.floating_label() {
|
||||
(label)
|
||||
}
|
||||
|
|
@ -313,14 +317,14 @@ impl Field {
|
|||
|
||||
// **< Field BUILDER >**************************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del contenedor del campo.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del contenedor del campo.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
|
|
|
|||
|
|
@ -96,9 +96,7 @@ impl Item {
|
|||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Field {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS del contenedor del grupo.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve el nombre compartido por todos los botones de opción del grupo.
|
||||
name: AttrName,
|
||||
|
|
@ -122,21 +120,31 @@ impl Component for Field {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
self.alter_prop(PropsOp::prepend_classes("form-field form-field-radios"));
|
||||
}
|
||||
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
fn setup(&mut self, cx: &Context) {
|
||||
// Asegura `name` e `id`.
|
||||
// Si falta uno se deriva del otro; si faltan ambos se genera un valor único.
|
||||
let name = self
|
||||
.name()
|
||||
.get()
|
||||
.unwrap_or_else(|| cx.required_id::<Self>(self.id(), 3));
|
||||
self.alter_name(&name);
|
||||
let container_id = self.id().unwrap_or_else(|| util::join!("edit-", &name));
|
||||
self.alter_prop(PropsOp::ensure_id(container_id));
|
||||
|
||||
// Clases CSS del contenedor del grupo de opciones.
|
||||
self.alter_prop(PropsOp::prepend_classes("form-field form-field-radios"));
|
||||
}
|
||||
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
// En `setup()` se garantiza que `name` e `id` están definidos antes del renderizado.
|
||||
let name = self.name().get().unwrap();
|
||||
let container_id = self.id().unwrap();
|
||||
|
||||
Ok(html! {
|
||||
div id=(&container_id) (self.props()) {
|
||||
div (self.props()) {
|
||||
@if let Some(label) = self.label().lookup(cx) {
|
||||
label class="form-label" {
|
||||
(label)
|
||||
|
|
@ -190,14 +198,14 @@ impl Component for Field {
|
|||
impl Field {
|
||||
// **< Field BUILDER >**************************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del grupo de opciones.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del contenedor del grupo de opciones.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
|
|
|
|||
|
|
@ -31,9 +31,7 @@ use pagetop::prelude::*;
|
|||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Range {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS del contenedor del control deslizante.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve el nombre del campo.
|
||||
name: AttrName,
|
||||
|
|
@ -61,20 +59,26 @@ impl Component for Range {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
if let Some(container_id) = self
|
||||
.id()
|
||||
.or_else(|| self.name().get().map(|n| util::join!("edit-", n)))
|
||||
{
|
||||
self.alter_prop(PropsOp::ensure_id(container_id));
|
||||
};
|
||||
|
||||
// Clases CSS del contenedor del control deslizante.
|
||||
self.alter_prop(PropsOp::prepend_classes("form-field form-field-range"));
|
||||
}
|
||||
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let container_id = self
|
||||
.id()
|
||||
.or_else(|| self.name().get().map(|n| util::join!("edit-", n)));
|
||||
let container_id = self.id();
|
||||
let range_id = container_id.as_deref().map(|id| util::join!(id, "-range"));
|
||||
Ok(html! {
|
||||
div id=[container_id.as_deref()] (self.props()) {
|
||||
div (self.props()) {
|
||||
@if let Some(label) = self.label().lookup(cx) {
|
||||
label for=[range_id.as_deref()] class="form-label" { (label) }
|
||||
}
|
||||
|
|
@ -100,14 +104,14 @@ impl Component for Range {
|
|||
impl Range {
|
||||
// **< Range BUILDER >**************************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del contenedor del control deslizante.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del contenedor del control deslizante.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
|
|
|
|||
|
|
@ -191,9 +191,7 @@ pub enum Entry {
|
|||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Field {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS del contenedor de la lista de selección.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve el nombre del campo.
|
||||
name: AttrName,
|
||||
|
|
@ -225,10 +223,18 @@ impl Component for Field {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
if let Some(container_id) = self
|
||||
.id()
|
||||
.or_else(|| self.name().get().map(|n| util::join!("edit-", n)))
|
||||
{
|
||||
self.alter_prop(PropsOp::ensure_id(container_id));
|
||||
};
|
||||
|
||||
// Clases CSS del contenedor de la lista de selección.
|
||||
if *self.floating_label() {
|
||||
self.alter_multiple(false);
|
||||
self.alter_rows(None::<u16>);
|
||||
|
|
@ -238,9 +244,7 @@ impl Component for Field {
|
|||
}
|
||||
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let container_id = self
|
||||
.id()
|
||||
.or_else(|| self.name().get().map(|n| util::join!("edit-", n)));
|
||||
let container_id = self.id();
|
||||
let select_id = container_id.as_deref().map(|id| util::join!(id, "-select"));
|
||||
let label = match self.label().lookup(cx) {
|
||||
Some(text) => html! {
|
||||
|
|
@ -259,7 +263,7 @@ impl Component for Field {
|
|||
None => html! {},
|
||||
};
|
||||
Ok(html! {
|
||||
div id=[container_id.as_deref()] (self.props()) {
|
||||
div (self.props()) {
|
||||
@if !*self.floating_label() {
|
||||
(label)
|
||||
}
|
||||
|
|
@ -318,14 +322,14 @@ impl Component for Field {
|
|||
impl Field {
|
||||
// **< Field BUILDER >***************************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del contenedor del campo.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del contenedor de la lista de selección.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
|
|
|
|||
|
|
@ -34,9 +34,7 @@ use crate::theme::form;
|
|||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Textarea {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS del contenedor del área de texto.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve el nombre del campo.
|
||||
name: AttrName,
|
||||
|
|
@ -74,10 +72,18 @@ impl Component for Textarea {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
if let Some(container_id) = self
|
||||
.id()
|
||||
.or_else(|| self.name().get().map(|n| util::join!("edit-", n)))
|
||||
{
|
||||
self.alter_prop(PropsOp::ensure_id(container_id));
|
||||
};
|
||||
|
||||
// Clases CSS del contenedor del área de texto.
|
||||
if *self.floating_label() {
|
||||
self.alter_rows(None::<u16>);
|
||||
self.alter_prop(PropsOp::prepend_classes("form-floating"));
|
||||
|
|
@ -86,9 +92,7 @@ impl Component for Textarea {
|
|||
}
|
||||
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let container_id = self
|
||||
.id()
|
||||
.or_else(|| self.name().get().map(|n| util::join!("edit-", n)));
|
||||
let container_id = self.id();
|
||||
let textarea_id = container_id
|
||||
.as_deref()
|
||||
.map(|id| util::join!(id, "-textarea"));
|
||||
|
|
@ -116,7 +120,7 @@ impl Component for Textarea {
|
|||
None => html! {},
|
||||
};
|
||||
Ok(html! {
|
||||
div id=[container_id.as_deref()] (self.props()) {
|
||||
div (self.props()) {
|
||||
@if !*self.floating_label() {
|
||||
(label)
|
||||
}
|
||||
|
|
@ -152,14 +156,14 @@ impl Component for Textarea {
|
|||
impl Textarea {
|
||||
// **< Textarea BUILDER >***********************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del contenedor del campo.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del contenedor del campo.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ pub enum IconKind {
|
|||
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Icon {
|
||||
/// Devuelve los atributos HTML y clases CSS del icono.
|
||||
/// Devuelve los atributos HTML y clases CSS del componente.
|
||||
props: Props,
|
||||
icon_kind: IconKind,
|
||||
aria_label: AttrL10n,
|
||||
|
|
@ -26,6 +26,10 @@ impl Component for Icon {
|
|||
Self::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
if !matches!(self.icon_kind(), IconKind::None) {
|
||||
self.alter_prop(PropsOp::prepend_classes("icon"));
|
||||
|
|
@ -98,7 +102,14 @@ impl Icon {
|
|||
|
||||
// **< Icon BUILDER >***************************************************************************
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del icono.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@ use crate::theme::*;
|
|||
/// - Aplicar el texto alternativo `alt` con **localización** mediante [`L10n`].
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Image {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS de la imagen.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve las dimensiones de la imagen.
|
||||
size: image::Size,
|
||||
|
|
@ -31,10 +29,11 @@ impl Component for Image {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
// Clases CSS por defecto para la imagen, según el origen seleccionado.
|
||||
self.alter_prop(PropsOp::prepend_classes(self.source().to_class()));
|
||||
}
|
||||
|
||||
|
|
@ -46,7 +45,6 @@ impl Component for Image {
|
|||
image::Source::Logo(logo) => {
|
||||
return Ok(html! {
|
||||
span
|
||||
id=[self.id()]
|
||||
(self.props())
|
||||
style=[dimensions]
|
||||
role=[(!is_decorative).then_some("img")]
|
||||
|
|
@ -65,7 +63,6 @@ impl Component for Image {
|
|||
img
|
||||
src=[source]
|
||||
alt=(alt_text)
|
||||
id=[self.id()]
|
||||
(self.props())
|
||||
style=[dimensions] {}
|
||||
})
|
||||
|
|
@ -80,14 +77,14 @@ impl Image {
|
|||
|
||||
// **< Image BUILDER >**************************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) de la imagen.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS de la imagen.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
///
|
||||
/// También acepta clases predefinidas para:
|
||||
///
|
||||
|
|
|
|||
|
|
@ -32,9 +32,7 @@ use crate::theme::*;
|
|||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Nav {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS del menú.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve el estilo visual seleccionado.
|
||||
nav_kind: nav::Kind,
|
||||
|
|
@ -50,10 +48,11 @@ impl Component for Nav {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
// Clases CSS por defecto para el menú, según el estilo y la distribución seleccionados.
|
||||
self.alter_prop(PropsOp::prepend_classes({
|
||||
let mut classes = "nav".to_string();
|
||||
self.nav_kind().push_class(&mut classes);
|
||||
|
|
@ -69,7 +68,7 @@ impl Component for Nav {
|
|||
}
|
||||
|
||||
Ok(html! {
|
||||
ul id=[self.id()] (self.props()) {
|
||||
ul (self.props()) {
|
||||
(items)
|
||||
}
|
||||
})
|
||||
|
|
@ -94,14 +93,14 @@ impl Nav {
|
|||
|
||||
// **< Nav BUILDER >****************************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del menú.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del menú.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
|
|
|
|||
|
|
@ -78,9 +78,7 @@ impl ItemKind {
|
|||
/// asociada, manteniendo una interfaz común para renderizar todos los elementos del menú.
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Item {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS del elemento.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve el tipo de elemento representado.
|
||||
item_kind: ItemKind,
|
||||
|
|
@ -92,7 +90,7 @@ impl Component for Item {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
|
|
@ -104,7 +102,7 @@ impl Component for Item {
|
|||
ItemKind::Void => html! {},
|
||||
|
||||
ItemKind::Label(label) => html! {
|
||||
li id=[self.id()] (self.props()) {
|
||||
li (self.props()) {
|
||||
span class="nav-link disabled" aria-disabled="true" {
|
||||
(label.using(cx))
|
||||
}
|
||||
|
|
@ -137,7 +135,7 @@ impl Component for Item {
|
|||
let aria_disabled = (*disabled).then_some("true");
|
||||
|
||||
html! {
|
||||
li id=[self.id()] (self.props()) {
|
||||
li (self.props()) {
|
||||
a
|
||||
class=(classes)
|
||||
href=[href]
|
||||
|
|
@ -153,7 +151,7 @@ impl Component for Item {
|
|||
}
|
||||
|
||||
ItemKind::Html(html) => html! {
|
||||
li id=[self.id()] (self.props()) {
|
||||
li (self.props()) {
|
||||
(html.render(cx))
|
||||
}
|
||||
},
|
||||
|
|
@ -170,7 +168,7 @@ impl Component for Item {
|
|||
.unwrap_or_else(|| "Dropdown".to_string())
|
||||
});
|
||||
html! {
|
||||
li id=[self.id()] (self.props()) {
|
||||
li (self.props()) {
|
||||
a
|
||||
class="nav-link dropdown-toggle"
|
||||
data-bs-toggle="dropdown"
|
||||
|
|
@ -283,14 +281,14 @@ impl Item {
|
|||
|
||||
// **< Item BUILDER >***************************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del elemento.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del elemento.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ use crate::theme::*;
|
|||
/// - El eslogan ([`with_slogan()`](Self::with_slogan)) es opcional; por defecto no tiene contenido.
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Brand {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve la imagen de marca (si la hay).
|
||||
image: Embed<Image>,
|
||||
/// Devuelve el título de la identidad de marca.
|
||||
|
|
@ -32,10 +30,6 @@ impl Component for Brand {
|
|||
Self::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let image = self.image().render(cx);
|
||||
let title = self.title().using(cx);
|
||||
|
|
@ -56,13 +50,6 @@ impl Component for Brand {
|
|||
impl Brand {
|
||||
// **< Brand BUILDER >**************************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) de la marca.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Asigna o quita la imagen de marca. Si se pasa `None`, no se mostrará.
|
||||
#[builder_fn]
|
||||
pub fn with_image(mut self, image: Option<Image>) -> Self {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use super::FnActionWithComponent;
|
|||
pub struct AfterRender<C: Component> {
|
||||
f: FnActionWithComponent<C>,
|
||||
referer_type_id: Option<UniqueId>,
|
||||
referer_id: AttrId,
|
||||
referer_id: Option<String>,
|
||||
weight: Weight,
|
||||
}
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ impl<C: Component> ActionDispatcher for AfterRender<C> {
|
|||
|
||||
/// Devuelve el identificador del componente.
|
||||
fn referer_id(&self) -> Option<String> {
|
||||
self.referer_id.get()
|
||||
self.referer_id.clone()
|
||||
}
|
||||
|
||||
/// Devuelve el peso para definir el orden de ejecución.
|
||||
|
|
@ -34,7 +34,7 @@ impl<C: Component> AfterRender<C> {
|
|||
AfterRender {
|
||||
f,
|
||||
referer_type_id: Some(UniqueId::of::<C>()),
|
||||
referer_id: AttrId::default(),
|
||||
referer_id: None,
|
||||
weight: 0,
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,8 @@ impl<C: Component> AfterRender<C> {
|
|||
/// Afina el registro para ejecutar la acción [`FnActionWithComponent`] sólo para el componente
|
||||
/// `C` con identificador `id`.
|
||||
pub fn filter_by_referer_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.referer_id.alter_id(id);
|
||||
let id = id.as_ref().trim().to_ascii_lowercase().replace(' ', "_");
|
||||
self.referer_id = if id.is_empty() { None } else { Some(id) };
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use super::FnActionWithComponent;
|
|||
pub struct BeforeRender<C: Component> {
|
||||
f: FnActionWithComponent<C>,
|
||||
referer_type_id: Option<UniqueId>,
|
||||
referer_id: AttrId,
|
||||
referer_id: Option<String>,
|
||||
weight: Weight,
|
||||
}
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ impl<C: Component> ActionDispatcher for BeforeRender<C> {
|
|||
|
||||
/// Devuelve el identificador del componente.
|
||||
fn referer_id(&self) -> Option<String> {
|
||||
self.referer_id.get()
|
||||
self.referer_id.clone()
|
||||
}
|
||||
|
||||
/// Devuelve el peso para definir el orden de ejecución.
|
||||
|
|
@ -34,7 +34,7 @@ impl<C: Component> BeforeRender<C> {
|
|||
BeforeRender {
|
||||
f,
|
||||
referer_type_id: Some(UniqueId::of::<C>()),
|
||||
referer_id: AttrId::default(),
|
||||
referer_id: None,
|
||||
weight: 0,
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,8 @@ impl<C: Component> BeforeRender<C> {
|
|||
/// Afina el registro para ejecutar la acción [`FnActionWithComponent`] sólo para el componente
|
||||
/// `C` con identificador `id`.
|
||||
pub fn filter_by_referer_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.referer_id.alter_id(id);
|
||||
let id = id.as_ref().trim().to_ascii_lowercase().replace(' ', "_");
|
||||
self.referer_id = if id.is_empty() { None } else { Some(id) };
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use super::FnActionTransformMarkup;
|
|||
pub struct TransformMarkup<C: Component> {
|
||||
f: FnActionTransformMarkup<C>,
|
||||
referer_type_id: Option<UniqueId>,
|
||||
referer_id: AttrId,
|
||||
referer_id: Option<String>,
|
||||
weight: Weight,
|
||||
}
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ impl<C: Component> ActionDispatcher for TransformMarkup<C> {
|
|||
|
||||
/// Devuelve el identificador del componente.
|
||||
fn referer_id(&self) -> Option<String> {
|
||||
self.referer_id.get()
|
||||
self.referer_id.clone()
|
||||
}
|
||||
|
||||
/// Devuelve el peso para definir el orden de ejecución.
|
||||
|
|
@ -34,7 +34,7 @@ impl<C: Component> TransformMarkup<C> {
|
|||
TransformMarkup {
|
||||
f,
|
||||
referer_type_id: Some(UniqueId::of::<C>()),
|
||||
referer_id: AttrId::default(),
|
||||
referer_id: None,
|
||||
weight: 0,
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,8 @@ impl<C: Component> TransformMarkup<C> {
|
|||
/// Afina el registro para ejecutar la acción [`FnActionTransformMarkup`] sólo para el
|
||||
/// componente `C` con identificador `id`.
|
||||
pub fn filter_by_referer_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.referer_id.alter_id(id);
|
||||
let id = id.as_ref().trim().to_ascii_lowercase().replace(' ', "_");
|
||||
self.referer_id = if id.is_empty() { None } else { Some(id) };
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ use crate::prelude::*;
|
|||
/// opcional y un cuerpo que sólo se renderiza si existen componentes hijos (*children*).
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Block {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve los atributos HTML y clases CSS del bloque.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve el título del bloque.
|
||||
title: L10n,
|
||||
|
|
@ -22,11 +20,15 @@ impl Component for Block {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
self.props.alter_prop(PropsOp::prepend_classes("block"));
|
||||
fn setup(&mut self, cx: &Context) {
|
||||
// Asegura que el bloque tiene un identificador único.
|
||||
self.alter_prop(PropsOp::ensure_id(cx.build_id::<Self>(1)));
|
||||
|
||||
// Todos los bloques tienen la clase CSS `block` por defecto.
|
||||
self.alter_prop(PropsOp::prepend_classes("block"));
|
||||
}
|
||||
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
|
|
@ -36,14 +38,12 @@ impl Component for Block {
|
|||
return Ok(html! {});
|
||||
}
|
||||
|
||||
let id = cx.required_id::<Self>(self.id(), 1);
|
||||
|
||||
Ok(html! {
|
||||
div id=(&id) (self.props()) {
|
||||
div (self.props()) {
|
||||
@if let Some(title) = self.title().lookup(cx) {
|
||||
h2 class="block__title" { span { (title) } }
|
||||
h2 class="block-title" { span { (title) } }
|
||||
}
|
||||
div class="block__body" { (block_body) }
|
||||
div class="block-body" { (block_body) }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -52,14 +52,14 @@ impl Component for Block {
|
|||
impl Block {
|
||||
// **< Block BUILDER >**************************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del bloque.
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_id(id);
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del bloque.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ use crate::locale::{LangId, LanguageIdentifier, RequestLocale};
|
|||
use crate::web::HttpRequest;
|
||||
use crate::{CowStr, builder_fn, util};
|
||||
|
||||
use std::any::Any;
|
||||
use std::cell::Cell;
|
||||
use std::any::{Any, TypeId};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
||||
|
|
@ -271,7 +271,7 @@ pub trait Contextual: LangId {
|
|||
/// assert_eq!(id, 42);
|
||||
///
|
||||
/// // Genera un identificador para un componente de tipo `Menu`.
|
||||
/// let unique_id = cx.required_id::<Menu>(None, 1);
|
||||
/// let unique_id = cx.build_id::<Menu>(1);
|
||||
/// assert_eq!(unique_id, "menu-1"); // Si es el primero generado.
|
||||
/// }
|
||||
/// ```
|
||||
|
|
@ -286,7 +286,7 @@ pub struct Context {
|
|||
javascripts: Assets<JavaScript>, // Scripts JavaScript.
|
||||
regions : ChildrenInRegions, // Regiones de componentes para renderizar.
|
||||
params : HashMap<&'static str, (Box<dyn Any>, &'static str)>, // Parámetros en ejecución.
|
||||
id_counter : Cell<usize>, // Cell permite incrementar desde &self en required_id().
|
||||
id_counters: RefCell<HashMap<TypeId, usize>>, // RefCell permite mutar desde build_id(&self).
|
||||
messages : Vec<StatusMessage>, // Mensajes de usuario acumulados.
|
||||
}
|
||||
|
||||
|
|
@ -314,7 +314,7 @@ impl Context {
|
|||
javascripts: Assets::<JavaScript>::new(),
|
||||
regions : ChildrenInRegions::default(),
|
||||
params : HashMap::default(),
|
||||
id_counter : Cell::new(0),
|
||||
id_counters: RefCell::new(HashMap::new()),
|
||||
messages : Vec::new(),
|
||||
}
|
||||
}
|
||||
|
|
@ -374,31 +374,42 @@ impl Context {
|
|||
route
|
||||
}
|
||||
|
||||
/// Garantiza un identificador único para un componente `C`, generándolo si no se proporciona
|
||||
/// ninguno.
|
||||
/// Construye un identificador HTML único para el tipo de componente `C`.
|
||||
///
|
||||
/// Si `id` es `None`, crea un identificador usando los últimos segmentos del *path* completo
|
||||
/// del tipo `C`, separados por `-` y en minúsculas, seguidos de un contador incremental interno
|
||||
/// del contexto. Por ejemplo, para un componente `MyApp::ui::Menu` con `parts = 2` podría
|
||||
/// devolver un identificador como `ui-menu-1` si ha sido el primero en generarse.
|
||||
/// Toma los `segments` finales del *path* completo del tipo, los une con `-` y los convierte a
|
||||
/// minúsculas, y añade un contador independiente por tipo. Por ejemplo, para `MyApp::ui::Menu`
|
||||
/// con `segments = 2` devuelve `ui-menu-1` la primera vez que se invoca para ese tipo,
|
||||
/// `ui-menu-2` la segunda, etc.
|
||||
///
|
||||
/// Con `parts = 1` se usa el nombre corto del tipo. Si `parts` es `0` o supera el número de
|
||||
/// segmentos del *path*, entonces se usará el *path* completo.
|
||||
/// Con `segments = 1` se usa sólo el nombre corto del tipo. Si `segments` es `0` o supera el
|
||||
/// número de segmentos del *path*, se usan todos.
|
||||
///
|
||||
/// Es útil para asignar identificadores HTML cuando el componente no recibe uno explícito.
|
||||
pub fn required_id<C: Component>(&self, id: Option<String>, parts: usize) -> String {
|
||||
if let Some(id) = id {
|
||||
return id;
|
||||
}
|
||||
let segments: Vec<&str> = TypeInfo::FullName.of::<C>().split("::").collect();
|
||||
let parts = if parts == 0 || parts >= segments.len() {
|
||||
segments.len()
|
||||
/// Es útil para asignar identificadores cuando el componente no recibe uno explícito. El
|
||||
/// contador es local a este contexto y se reinicia para cada nueva petición.
|
||||
pub fn build_id<C: Component>(&self, segments: usize) -> String {
|
||||
let path: Vec<&str> = TypeInfo::FullName.of::<C>().split("::").collect();
|
||||
let segments = if segments == 0 || segments >= path.len() {
|
||||
path.len()
|
||||
} else {
|
||||
parts
|
||||
segments
|
||||
};
|
||||
self.id_counter.set(self.id_counter.get() + 1);
|
||||
let prefix = segments[segments.len() - parts..].join("-").to_lowercase();
|
||||
util::join!(prefix, "-", self.id_counter.get().to_string())
|
||||
let count = {
|
||||
let mut map = self.id_counters.borrow_mut();
|
||||
let n = map.entry(TypeId::of::<C>()).or_insert(0);
|
||||
*n += 1;
|
||||
*n
|
||||
};
|
||||
let prefix = path[path.len() - segments..].join("-").to_lowercase();
|
||||
util::join!(prefix, "-", count.to_string())
|
||||
}
|
||||
|
||||
/// Devuelve `id` si contiene un valor, o genera uno único con [`build_id`](Self::build_id)
|
||||
/// si es `None`.
|
||||
pub fn required_id<C: Component>(&self, id: Option<String>, segments: usize) -> String {
|
||||
match id {
|
||||
Some(id) => id,
|
||||
None => self.build_id::<C>(segments),
|
||||
}
|
||||
}
|
||||
|
||||
/// Acumula un [`StatusMessage`] en el contexto para notificar al visitante.
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ pub use logo::PageTopSvg;
|
|||
// **< HTML ATTRIBUTES >****************************************************************************
|
||||
|
||||
mod attr;
|
||||
pub use attr::{Attr, AttrId, AttrName, AttrValue};
|
||||
pub use attr::{Attr, AttrName, AttrValue};
|
||||
|
||||
mod props;
|
||||
pub use props::{Props, PropsOp};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use crate::{AutoDefault, builder_fn};
|
|||
///
|
||||
/// Este tipo **no impone ninguna normalización ni semántica concreta**; dichas reglas se definen en
|
||||
/// implementaciones concretas como `Attr<L10n>` y `Attr<String>`, o en tipos específicos como
|
||||
/// [`AttrId`] y [`AttrName`].
|
||||
/// [`AttrName`].
|
||||
#[derive(AutoDefault, Clone, Debug)]
|
||||
pub struct Attr<T>(Option<T>);
|
||||
|
||||
|
|
@ -128,73 +128,6 @@ impl Attr<String> {
|
|||
}
|
||||
}
|
||||
|
||||
// **< AttrId >*************************************************************************************
|
||||
|
||||
/// Identificador normalizado para el atributo `id` o similar de HTML.
|
||||
///
|
||||
/// Este tipo encapsula `Option<String>` garantizando un valor normalizado para su uso:
|
||||
///
|
||||
/// - Se eliminan los espacios al principio y al final.
|
||||
/// - Se convierte a minúsculas.
|
||||
/// - Se sustituyen los espacios (`' '`) intermedios por guiones bajos (`_`).
|
||||
/// - Si el resultado es una cadena vacía, se guarda `None`.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let id = AttrId::new(" main Section ");
|
||||
/// assert_eq!(id.as_str(), Some("main_section"));
|
||||
///
|
||||
/// let empty = AttrId::default();
|
||||
/// assert_eq!(empty.get(), None);
|
||||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug)]
|
||||
pub struct AttrId(Attr<String>);
|
||||
|
||||
impl AttrId {
|
||||
/// Crea un nuevo `AttrId` normalizando el valor.
|
||||
pub fn new(id: impl AsRef<str>) -> Self {
|
||||
Self::default().with_id(id)
|
||||
}
|
||||
|
||||
// **< AttrId BUILDER >*************************************************************************
|
||||
|
||||
/// Establece un identificador nuevo normalizando el valor.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
let id = id.as_ref().trim();
|
||||
if id.is_empty() {
|
||||
self.0 = Attr::default();
|
||||
} else {
|
||||
self.0 = Attr::some(id.to_ascii_lowercase().replace(' ', "_"));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
// **< AttrId GETTERS >*************************************************************************
|
||||
|
||||
/// Devuelve el identificador normalizado, si existe.
|
||||
pub fn get(&self) -> Option<String> {
|
||||
self.0.get()
|
||||
}
|
||||
|
||||
/// Devuelve el identificador normalizado (sin clonar), si existe.
|
||||
pub fn as_str(&self) -> Option<&str> {
|
||||
self.0.as_str()
|
||||
}
|
||||
|
||||
/// Devuelve el identificador normalizado (propiedad), si existe.
|
||||
pub fn into_inner(self) -> Option<String> {
|
||||
self.0.into_inner()
|
||||
}
|
||||
|
||||
/// `true` si no hay valor.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
// **< AttrName >***********************************************************************************
|
||||
|
||||
/// Nombre normalizado para el atributo `name` o similar de HTML.
|
||||
|
|
|
|||
|
|
@ -7,32 +7,57 @@ use std::fmt::Write;
|
|||
|
||||
/// Operaciones disponibles sobre atributos HTML y clases CSS en [`Props`].
|
||||
///
|
||||
/// Cada variante es autocontenida, lleva todos los datos que necesita para ejecutarse. El método
|
||||
/// recomendado para construirlas es usar los constructores asociados ([`set()`](Self::set),
|
||||
/// [`remove()`](Self::remove), [`add_classes()`](Self::add_classes), etc.).
|
||||
/// Cada variante lleva los datos necesarios para ejecutarse. El método recomendado para usarlas es
|
||||
/// recurrir a los constructores asociados como [`set()`](Self::set), [`set_id()`](Self::set_id),
|
||||
/// [`remove()`](Self::remove), [`add_classes()`](Self::add_classes), etc.
|
||||
///
|
||||
/// Las variantes `*Classes` operan siempre sobre la lista de clases CSS para el componente.
|
||||
/// Las variantes `*Id` operan sobre el atributo `id` del componente. Cuando se usa `"id"` como
|
||||
/// nombre de atributo en `Set`, el valor se normaliza igual que en `SetId` o `EnsureId`.
|
||||
///
|
||||
/// Cuando se usa `"class"` como nombre de atributo en `Set` o `Remove` la operación se aplica a la
|
||||
/// lista de clases completa. Así, `Set("class", ...)` reemplaza la lista de clases completa por las
|
||||
/// nuevas clases indicadas, y `Remove("class")` vacía la lista de clases.
|
||||
/// Las variantes `*Classes` operan siempre sobre la lista de clases CSS para el componente. Cuando
|
||||
/// se usa `"class"` como nombre en `Set` o `Remove` la operación se aplica a la lista de clases
|
||||
/// completa. Así, `Set("class", ...)` reemplaza la lista de clases completa por las nuevas clases
|
||||
/// indicadas, y `Remove("class")` vacía la lista de clases.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum PropsOp {
|
||||
/// Añade el atributo o sustituye su valor si ya existe. Usar `"class"` como nombre reemplaza la
|
||||
/// lista completa de clases por las nuevas indicadas; la operación se ignora si el valor
|
||||
/// contiene caracteres no ASCII.
|
||||
/// Establece el identificador del elemento normalizando el valor: recorta espacios, convierte a
|
||||
/// minúsculas y sustituye los espacios intermedios por `_`. Si el resultado es vacío, elimina
|
||||
/// el identificador.
|
||||
SetId(CowStr),
|
||||
/// Establece el identificador del elemento si aún no hay ninguno definido, de modo que no
|
||||
/// sobrescribe un valor asignado con anterioridad. Aplica la misma normalización que
|
||||
/// [`SetId`](Self::SetId); si el resultado es vacío, la operación tampoco tiene efecto.
|
||||
EnsureId(CowStr),
|
||||
/// Añade el atributo o sustituye su valor si ya existe. Usar `"id"` como nombre aplica la misma
|
||||
/// normalización que [`SetId`](Self::SetId). Usar `"class"` como nombre reemplaza la lista
|
||||
/// completa de clases por las nuevas indicadas; la operación se ignora si el valor contiene
|
||||
/// caracteres no ASCII.
|
||||
Set(CowStr, CowStr),
|
||||
/// Elimina el atributo indicado. Usar `"class"` como nombre vacía la lista de clases.
|
||||
/// Elimina el atributo indicado, incluido `"id"`. Si se usa `"class"` como nombre se vacía la
|
||||
/// lista de clases.
|
||||
Remove(CowStr),
|
||||
/// Añade las clases que no existan al final de la lista.
|
||||
/// Añade las clases que no existan al final de la lista. La operación se ignora si el valor
|
||||
/// contiene caracteres no ASCII.
|
||||
AddClasses(CowStr),
|
||||
/// Añade las clases que no existan al principio de la lista.
|
||||
/// Añade las clases que no existan al principio de la lista. La operación se ignora si el valor
|
||||
/// contiene caracteres no ASCII.
|
||||
PrependClasses(CowStr),
|
||||
/// Elimina las clases indicadas de la lista.
|
||||
/// Elimina las clases indicadas de la lista. La operación se ignora si el valor contiene
|
||||
/// caracteres no ASCII.
|
||||
RemoveClasses(CowStr),
|
||||
}
|
||||
|
||||
impl PropsOp {
|
||||
/// Crea la variante [`SetId`](Self::SetId) con el identificador indicado.
|
||||
pub fn set_id(id: impl Into<CowStr>) -> Self {
|
||||
Self::SetId(id.into())
|
||||
}
|
||||
|
||||
/// Crea la variante [`EnsureId`](Self::EnsureId) con el identificador indicado.
|
||||
pub fn ensure_id(id: impl Into<CowStr>) -> Self {
|
||||
Self::EnsureId(id.into())
|
||||
}
|
||||
|
||||
/// Crea la variante [`Set`](Self::Set) con nombre y valor del atributo.
|
||||
pub fn set(name: impl Into<CowStr>, value: impl Into<CowStr>) -> Self {
|
||||
Self::Set(name.into(), value.into())
|
||||
|
|
@ -61,11 +86,10 @@ impl PropsOp {
|
|||
|
||||
// **< Props >**************************************************************************************
|
||||
|
||||
/// Colección de pares `atributo="valor"` y clases CSS para aplicar en componentes.
|
||||
/// Colección de identificador, atributos HTML y clases CSS para aplicar en componentes.
|
||||
///
|
||||
/// Permite añadir dinámicamente pares `atributo="valor"` y clases CSS al elemento raíz de un
|
||||
/// componente. Al renderizar los atributos en `html!` primero emite el atributo `class` (si hay
|
||||
/// clases) y luego el resto de atributos.
|
||||
/// Al renderizar en `html!` emite primero `id` (si existe), luego `class` (si hay clases) y después
|
||||
/// el resto de atributos.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
|
|
@ -85,6 +109,35 @@ impl PropsOp {
|
|||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// # Identificadores
|
||||
///
|
||||
/// [`SetId`](PropsOp::SetId) (usando [`PropsOp::set_id`]) normaliza el valor asignado al
|
||||
/// identificador del componente: recorta espacios, convierte a minúsculas y sustituye los espacios
|
||||
/// intermedios por `_`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let props = Props::default().with_id("My Button");
|
||||
/// let markup = html! { button (props) { "OK" } };
|
||||
/// assert_eq!(markup.into_string(), r#"<button id="my_button">OK</button>"#);
|
||||
/// ```
|
||||
///
|
||||
/// [`EnsureId`](PropsOp::EnsureId) (usando [`PropsOp::ensure_id`]) sólo asigna si no
|
||||
/// hay identificador previo:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use pagetop::prelude::*;
|
||||
/// // Con `id` previo: `EnsureId` no tiene efecto.
|
||||
/// let props = Props::default()
|
||||
/// .with_id("explicit")
|
||||
/// .with_prop(PropsOp::ensure_id("default"));
|
||||
/// assert_eq!(props.get_id(), Some("explicit".to_string()));
|
||||
///
|
||||
/// // Sin `id` previo: `EnsureId` asigna el valor.
|
||||
/// let props = Props::default().with_prop(PropsOp::ensure_id("default"));
|
||||
/// assert_eq!(props.get_id(), Some("default".to_string()));
|
||||
/// ```
|
||||
///
|
||||
/// # Clases CSS
|
||||
///
|
||||
/// ```rust
|
||||
|
|
@ -122,7 +175,7 @@ impl PropsOp {
|
|||
/// }
|
||||
///
|
||||
/// impl MyButton {
|
||||
/// /// Modifica los atributos HTML o las clases CSS del elemento raíz.
|
||||
/// /// Modifica identificador, clases CSS o atributos HTML del elemento raíz.
|
||||
/// #[builder_fn]
|
||||
/// pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
/// self.props.alter_prop(op);
|
||||
|
|
@ -132,6 +185,7 @@ impl PropsOp {
|
|||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug)]
|
||||
pub struct Props {
|
||||
id: Option<String>,
|
||||
attrs: Vec<(CowStr, CowStr)>,
|
||||
classes: Vec<String>,
|
||||
}
|
||||
|
|
@ -149,12 +203,23 @@ impl Props {
|
|||
|
||||
// **< Props BUILDER >**************************************************************************
|
||||
|
||||
/// Modifica los atributos o clases según la operación indicada.
|
||||
/// Establece el identificador del componente; equivale a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.apply_id(id.into().as_ref());
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica el identificador, los atributos o las clases según la operación indicada.
|
||||
///
|
||||
/// - [`SetId(value)`](PropsOp::SetId) establece el identificador normalizando el valor.
|
||||
/// - [`EnsureId(value)`](PropsOp::EnsureId) establece el identificador (con la misma
|
||||
/// normalización) sólo si no hay ninguno definido.
|
||||
/// - [`Set(name, value)`](PropsOp::Set) añade el atributo o reemplaza su valor.
|
||||
/// `Set("id", ...)` aplica la misma normalización que `SetId`.
|
||||
/// `Set("class", ...)` reemplaza la lista de clases completa.
|
||||
/// - [`Remove(name)`](PropsOp::Remove) elimina el atributo. `Remove("class")` vacía la lista de
|
||||
/// clases.
|
||||
/// - [`Remove(name)`](PropsOp::Remove) elimina el atributo. `Remove("id")` elimina el
|
||||
/// identificador. `Remove("class")` vacía la lista de clases.
|
||||
/// - [`AddClasses(clases)`](PropsOp::AddClasses) añade clases al final (sin duplicados).
|
||||
/// - [`PrependClasses(clases)`](PropsOp::PrependClasses) añade clases al principio (sin
|
||||
/// duplicados).
|
||||
|
|
@ -162,8 +227,18 @@ impl Props {
|
|||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
match op {
|
||||
PropsOp::SetId(value) => {
|
||||
self.apply_id(value.as_ref());
|
||||
}
|
||||
PropsOp::EnsureId(value) => {
|
||||
if self.id.is_none() {
|
||||
self.apply_id(value.as_ref());
|
||||
}
|
||||
}
|
||||
PropsOp::Set(name, value) => {
|
||||
if name.as_ref() == "class" {
|
||||
if name.as_ref() == "id" {
|
||||
self.apply_id(value.as_ref());
|
||||
} else if name.as_ref() == "class" {
|
||||
if let Some(normalized) =
|
||||
util::normalize_ascii_or_empty(value.as_ref(), "Props::with_prop")
|
||||
{
|
||||
|
|
@ -177,7 +252,9 @@ impl Props {
|
|||
}
|
||||
}
|
||||
PropsOp::Remove(name) => {
|
||||
if name.as_ref() == "class" {
|
||||
if name.as_ref() == "id" {
|
||||
self.id = None;
|
||||
} else if name.as_ref() == "class" {
|
||||
self.classes.clear();
|
||||
} else {
|
||||
self.attrs.retain(|(k, _)| k != &name);
|
||||
|
|
@ -219,18 +296,26 @@ impl Props {
|
|||
|
||||
// **< Props GETTERS >**************************************************************************
|
||||
|
||||
/// Devuelve el valor del atributo indicado, si existe.
|
||||
pub fn get_prop(&self, name: impl AsRef<str>) -> Option<&str> {
|
||||
let name = name.as_ref();
|
||||
self.attrs
|
||||
.iter()
|
||||
.find(|(k, _)| k.as_ref() == name)
|
||||
.map(|(_, v)| v.as_ref())
|
||||
/// Devuelve el identificador normalizado del elemento, si existe.
|
||||
#[inline]
|
||||
pub fn get_id(&self) -> Option<String> {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
/// Devuelve `true` si no hay ningún atributo definido.
|
||||
pub fn is_props_empty(&self) -> bool {
|
||||
self.attrs.is_empty()
|
||||
/// Devuelve el valor del atributo indicado, si existe.
|
||||
///
|
||||
/// Los nombres `"id"` y `"class"` son equivalentes a llamar a [`get_id()`](Self::get_id) y
|
||||
/// [`get_classes()`](Self::get_classes) respectivamente.
|
||||
pub fn get_prop(&self, name: impl AsRef<str>) -> Option<String> {
|
||||
match name.as_ref() {
|
||||
"id" => self.id.clone(),
|
||||
"class" => self.get_classes(),
|
||||
name => self
|
||||
.attrs
|
||||
.iter()
|
||||
.find(|(k, _)| k.as_ref() == name)
|
||||
.map(|(_, v)| v.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Devuelve la lista de clases como cadena de texto, si hay clases definidas.
|
||||
|
|
@ -242,11 +327,31 @@ impl Props {
|
|||
}
|
||||
}
|
||||
|
||||
/// Devuelve `true` si no hay ningún identificador definido.
|
||||
#[inline]
|
||||
pub fn is_id_empty(&self) -> bool {
|
||||
self.id.is_none()
|
||||
}
|
||||
|
||||
/// Devuelve `true` si no hay ningún atributo extra definido, sin tener en cuenta el
|
||||
/// identificador ni las clases.
|
||||
#[inline]
|
||||
pub fn is_attrs_empty(&self) -> bool {
|
||||
self.attrs.is_empty()
|
||||
}
|
||||
|
||||
/// Devuelve `true` si no hay ninguna clase definida.
|
||||
#[inline]
|
||||
pub fn is_classes_empty(&self) -> bool {
|
||||
self.classes.is_empty()
|
||||
}
|
||||
|
||||
/// Devuelve `true` si no hay ningún identificador, atributo ni clase definidos.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.id.is_none() && self.attrs.is_empty() && self.classes.is_empty()
|
||||
}
|
||||
|
||||
/// Devuelve `true` si la clase o **todas** las clases indicadas están presentes.
|
||||
pub fn has_class(&self, classes: impl AsRef<str>) -> bool {
|
||||
let Ok(normalized) = util::normalize_ascii(classes.as_ref()) else {
|
||||
|
|
@ -269,9 +374,17 @@ impl Props {
|
|||
.any(|class| self.classes.iter().any(|c| c == class))
|
||||
}
|
||||
|
||||
// **< Props PRIVADO >**************************************************************************
|
||||
// **< Props PRIVATE >**************************************************************************
|
||||
|
||||
fn apply_id(&mut self, id: &str) {
|
||||
let id = id.trim();
|
||||
self.id = if id.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(id.to_ascii_lowercase().replace(' ', "_"))
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn insert_classes<'a, I>(&mut self, classes: I, mut pos: usize)
|
||||
where
|
||||
I: IntoIterator<Item = &'a str>,
|
||||
|
|
@ -293,6 +406,11 @@ impl Props {
|
|||
#[doc(hidden)]
|
||||
impl Render for Props {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
if let Some(id) = self.id.as_deref() {
|
||||
w.push_str(" id=\"");
|
||||
let _ = write!(Escaper::new(w), "{}", id);
|
||||
w.push('"');
|
||||
}
|
||||
if let Some((first, rest)) = self.classes.split_first() {
|
||||
w.push_str(" class=\"");
|
||||
let _ = write!(Escaper::new(w), "{}", first);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ use crate::base::action;
|
|||
use crate::core::component::{AssetsOp, ChildOp, Context, ContextError, Contextual};
|
||||
use crate::core::theme::{DefaultRegion, Region, RegionRef, TemplateRef, ThemeRef};
|
||||
use crate::html::{Assets, Favicon, JavaScript, StyleSheet};
|
||||
use crate::html::{Attr, AttrId, Props, PropsOp};
|
||||
use crate::html::{Attr, Props, PropsOp};
|
||||
use crate::html::{DOCTYPE, Markup, html};
|
||||
use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier};
|
||||
use crate::web::HttpRequest;
|
||||
|
|
@ -89,7 +89,6 @@ pub struct Page {
|
|||
description : Attr<L10n>,
|
||||
metadata : Vec<(&'static str, &'static str)>,
|
||||
properties : Vec<(&'static str, &'static str)>,
|
||||
body_id : AttrId,
|
||||
body_props : Props,
|
||||
context : Context,
|
||||
}
|
||||
|
|
@ -106,7 +105,6 @@ impl Page {
|
|||
description : Attr::<L10n>::default(),
|
||||
metadata : Vec::default(),
|
||||
properties : Vec::default(),
|
||||
body_id : AttrId::default(),
|
||||
body_props : Props::default(),
|
||||
context : Context::new(Some(request)),
|
||||
}
|
||||
|
|
@ -142,14 +140,7 @@ impl Page {
|
|||
self
|
||||
}
|
||||
|
||||
/// Establece el atributo `id` del elemento `<body>`.
|
||||
#[builder_fn]
|
||||
pub fn with_body_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.body_id.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica los atributos HTML o las clases CSS del elemento `<body>`.
|
||||
/// Modifica identificador, clases CSS o atributos HTML del elemento `<body>`.
|
||||
#[builder_fn]
|
||||
pub fn with_body_props(mut self, op: PropsOp) -> Self {
|
||||
self.body_props.alter_prop(op);
|
||||
|
|
@ -178,12 +169,7 @@ impl Page {
|
|||
&self.properties
|
||||
}
|
||||
|
||||
/// Devuelve el identificador del elemento `<body>`.
|
||||
pub fn body_id(&self) -> &AttrId {
|
||||
&self.body_id
|
||||
}
|
||||
|
||||
/// Devuelve los atributos HTML y clases CSS del elemento `<body>`.
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del elemento `<body>`.
|
||||
pub fn body_props(&self) -> &Props {
|
||||
&self.body_props
|
||||
}
|
||||
|
|
@ -261,7 +247,7 @@ impl Page {
|
|||
head {
|
||||
(head)
|
||||
}
|
||||
body id=[self.body_id().get()] (self.body_props()) {
|
||||
body (self.body_props()) {
|
||||
(body)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use pagetop::prelude::*;
|
|||
|
||||
#[derive(AutoDefault, Clone)]
|
||||
struct TestComp {
|
||||
id: AttrId,
|
||||
props: Props,
|
||||
text: String,
|
||||
}
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ impl Component for TestComp {
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn prepare(&self, _cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
|
|
@ -29,7 +29,7 @@ impl TestComp {
|
|||
/// Crea un componente con id y texto de salida fijos.
|
||||
fn tagged(id: &str, text: &str) -> Self {
|
||||
let mut c = Self::default();
|
||||
c.id.alter_id(id);
|
||||
c.props.alter_prop(PropsOp::set_id(id.to_string()));
|
||||
c.text = text.to_string();
|
||||
c
|
||||
}
|
||||
|
|
@ -303,7 +303,8 @@ async fn embed_get_allows_mutating_component() {
|
|||
let embed = Embed::with(TestComp::tagged("orig", "texto"));
|
||||
// El `;` final convierte el `if let` en sentencia y libera el guard antes que `embed`.
|
||||
if let Some(mut comp) = embed.get() {
|
||||
comp.id.alter_id("modificado");
|
||||
comp.props
|
||||
.alter_prop(PropsOp::set_id("modificado".to_string()));
|
||||
};
|
||||
assert_eq!(embed.id(), Some("modificado".to_string()));
|
||||
}
|
||||
|
|
@ -331,7 +332,8 @@ async fn embed_clone_is_deep() {
|
|||
let clone = original.clone();
|
||||
// Mutar el clon no debe afectar al original.
|
||||
if let Some(mut comp) = clone.get() {
|
||||
comp.id.alter_id("clone-id");
|
||||
comp.props
|
||||
.alter_prop(PropsOp::set_id("clone-id".to_string()));
|
||||
}
|
||||
assert_eq!(original.id(), Some("orig".to_string()));
|
||||
assert_eq!(clone.id(), Some("clone-id".to_string()));
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ async fn props_default_renders_nothing() {
|
|||
#[pagetop::test]
|
||||
async fn props_new_creates_first_attr() {
|
||||
let p = Props::new("hx-get", "/api");
|
||||
assert_eq!(p.get_prop("hx-get"), Some("/api"));
|
||||
assert_eq!(p.get_prop("hx-get"), Some("/api".to_string()));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
|
|
@ -59,14 +59,14 @@ async fn props_set_adds_new_attrs() {
|
|||
let p = Props::default()
|
||||
.with_prop(PropsOp::set("hx-get", "/api"))
|
||||
.with_prop(PropsOp::set("hx-swap", "outerHTML"));
|
||||
assert_eq!(p.get_prop("hx-get"), Some("/api"));
|
||||
assert_eq!(p.get_prop("hx-swap"), Some("outerHTML"));
|
||||
assert_eq!(p.get_prop("hx-get"), Some("/api".to_string()));
|
||||
assert_eq!(p.get_prop("hx-swap"), Some("outerHTML".to_string()));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn props_set_replaces_existing_value() {
|
||||
let p = Props::new("hx-get", "/old").with_prop(PropsOp::set("hx-get", "/new"));
|
||||
assert_eq!(p.get_prop("hx-get"), Some("/new"));
|
||||
assert_eq!(p.get_prop("hx-get"), Some("/new".to_string()));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
|
|
@ -98,13 +98,13 @@ async fn props_remove_existing_attr() {
|
|||
.with_prop(PropsOp::set("b", "2"))
|
||||
.with_prop(PropsOp::remove("a"));
|
||||
assert_eq!(p.get_prop("a"), None);
|
||||
assert_eq!(p.get_prop("b"), Some("2"));
|
||||
assert_eq!(p.get_prop("b"), Some("2".to_string()));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn props_remove_nonexistent_key_is_noop() {
|
||||
let p = Props::new("a", "1").with_prop(PropsOp::remove("missing"));
|
||||
assert_eq!(p.get_prop("a"), Some("1"));
|
||||
assert_eq!(p.get_prop("a"), Some("1".to_string()));
|
||||
assert_eq!(p.get_prop("missing"), None);
|
||||
}
|
||||
|
||||
|
|
@ -222,22 +222,42 @@ async fn props_splice_empty_string_emits_nothing() {
|
|||
assert_eq!(html! { span ("") { "x" } }.into_string(), "<span>x</span>");
|
||||
}
|
||||
|
||||
// **< is_props_empty / is_classes_empty >**********************************************************
|
||||
// **< is_attrs_empty / is_classes_empty / is_empty >***********************************************
|
||||
|
||||
#[pagetop::test]
|
||||
async fn props_is_props_empty_on_default() {
|
||||
assert!(Props::default().is_props_empty());
|
||||
async fn props_is_attrs_empty_on_default() {
|
||||
assert!(Props::default().is_attrs_empty());
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn props_is_props_empty_false_after_set() {
|
||||
assert!(!Props::new("hx-get", "/api").is_props_empty());
|
||||
async fn props_is_attrs_empty_false_after_set() {
|
||||
assert!(!Props::new("hx-get", "/api").is_attrs_empty());
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn props_is_props_empty_true_after_removing_last_attr() {
|
||||
async fn props_is_attrs_empty_true_after_removing_last_attr() {
|
||||
let p = Props::new("only", "one").with_prop(PropsOp::remove("only"));
|
||||
assert!(p.is_props_empty());
|
||||
assert!(p.is_attrs_empty());
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn props_is_empty_on_default() {
|
||||
assert!(Props::default().is_empty());
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn props_is_empty_false_with_id() {
|
||||
assert!(!Props::default().with_id("main").is_empty());
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn props_is_empty_false_with_attr() {
|
||||
assert!(!Props::new("hx-get", "/api").is_empty());
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn props_is_empty_false_with_class() {
|
||||
assert!(!Props::classes("btn").is_empty());
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
|
|
@ -256,6 +276,45 @@ async fn props_is_classes_empty_true_after_remove_class() {
|
|||
assert!(p.is_classes_empty());
|
||||
}
|
||||
|
||||
// **< get_prop("id") / get_prop("class") >*********************************************************
|
||||
|
||||
#[pagetop::test]
|
||||
async fn get_prop_id_returns_none_by_default() {
|
||||
assert_eq!(Props::default().get_prop("id"), None);
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn get_prop_id_returns_normalized_value() {
|
||||
let p = Props::default().with_id("My Button");
|
||||
assert_eq!(p.get_prop("id"), Some("my_button".to_string()));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn get_prop_id_matches_get_id() {
|
||||
let p = Props::default().with_id("Header");
|
||||
assert_eq!(p.get_prop("id"), p.get_id());
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn get_prop_class_returns_none_by_default() {
|
||||
assert_eq!(Props::default().get_prop("class"), None);
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn get_prop_class_returns_joined_classes() {
|
||||
let p = Props::classes("btn btn-primary").with_prop(PropsOp::add_classes("active"));
|
||||
assert_eq!(
|
||||
p.get_prop("class"),
|
||||
Some("btn btn-primary active".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn get_prop_class_matches_get_classes() {
|
||||
let p = Props::classes("btn active");
|
||||
assert_eq!(p.get_prop("class"), p.get_classes());
|
||||
}
|
||||
|
||||
// **< Regression & edge cases >********************************************************************
|
||||
|
||||
#[pagetop::test]
|
||||
|
|
@ -284,9 +343,9 @@ async fn props_chained_set_and_remove_yields_expected_state() {
|
|||
.with_prop(PropsOp::set("c", "3"))
|
||||
.with_prop(PropsOp::remove("b"))
|
||||
.with_prop(PropsOp::set("a", "updated"));
|
||||
assert_eq!(p.get_prop("a"), Some("updated"));
|
||||
assert_eq!(p.get_prop("a"), Some("updated".to_string()));
|
||||
assert_eq!(p.get_prop("b"), None);
|
||||
assert_eq!(p.get_prop("c"), Some("3"));
|
||||
assert_eq!(p.get_prop("c"), Some("3".to_string()));
|
||||
assert_eq!(
|
||||
html! { span (p) {} }.into_string(),
|
||||
r#"<span a="updated" c="3"></span>"#
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue