fix(html-examples): tighten email and progress feedback
Require a complete dotted email before inline validation reports success, preserve the typed email after valid revalidation, and update progress via the actual progress element so visible progress is not stuck at 100%. req: htmx_equivalents/005 req: examples/001 req: test/006
This commit is contained in:
@@ -170,6 +170,23 @@ impl hemx::KeyedPartial for ContactCard {
|
||||
}
|
||||
}
|
||||
|
||||
struct ProgressMeter {
|
||||
percent: u8,
|
||||
}
|
||||
|
||||
impl Hemplate for ProgressMeter {
|
||||
fn render_into(&self, out: &mut String) -> Result<(), hemplate::error::HemplateError> {
|
||||
use std::fmt::Write as _;
|
||||
write!(
|
||||
out,
|
||||
"<progress value=\"{}\" max=\"100\">{}% complete</progress>",
|
||||
self.percent, self.percent
|
||||
)
|
||||
.expect("write to String cannot fail");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Hemplate, Clone)]
|
||||
struct EditableRow {
|
||||
id: Id,
|
||||
@@ -372,6 +389,19 @@ fn lazy_panel_text(loads: usize) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_demo_email(email: &str) -> bool {
|
||||
let Some((local, domain)) = email.split_once('@') else {
|
||||
return false;
|
||||
};
|
||||
!local.is_empty()
|
||||
&& domain
|
||||
.split('.')
|
||||
.filter(|part| !part.is_empty())
|
||||
.take(2)
|
||||
.count()
|
||||
>= 2
|
||||
}
|
||||
|
||||
fn contact_card_view(record: ContactRecord) -> ContactCard {
|
||||
ContactCard {
|
||||
id: Id(record.id),
|
||||
@@ -451,7 +481,7 @@ mod gallery_handlers {
|
||||
let _ = input.request;
|
||||
let mut progress = state.progress.lock().unwrap();
|
||||
*progress = (*progress + 25).min(100);
|
||||
gallery::progress_meter.set(format!("{}% complete", *progress))
|
||||
gallery::progress_meter.replace(&ProgressMeter { percent: *progress })
|
||||
}
|
||||
|
||||
#[hemx::handler]
|
||||
@@ -521,20 +551,20 @@ mod gallery_handlers {
|
||||
Form(input): Form<ValidateEmail>,
|
||||
) -> impl IntoEffect {
|
||||
let email = input.email.trim().to_owned();
|
||||
if !email.contains('@') {
|
||||
if !is_demo_email(&email) {
|
||||
*state.email.lock().unwrap() = email;
|
||||
*state.email_status.lock().unwrap() = "Email needs an @ sign".into();
|
||||
*state.email_status.lock().unwrap() = "Email needs a name and dotted domain".into();
|
||||
return vec![
|
||||
gallery::validate_email_form.focus("email"),
|
||||
gallery::validate_email_form.error("email", "Use a real email address"),
|
||||
gallery::email_status.set("Email needs an @ sign"),
|
||||
gallery::email_status.set("Email needs a name and dotted domain"),
|
||||
];
|
||||
}
|
||||
*state.email.lock().unwrap() = email.clone();
|
||||
*state.email_status.lock().unwrap() = format!("{email} is valid");
|
||||
vec![
|
||||
gallery::validate_email_form.error("email", ""),
|
||||
gallery::email_status.set(format!("{email} is valid")),
|
||||
gallery::validate_email_form.clear(),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -770,7 +800,7 @@ mod tests {
|
||||
}
|
||||
assert!(html.contains("data-hemx-form=\"validate_email\""));
|
||||
assert!(html.contains("data-hemx-revealed=\"true\""));
|
||||
assert!(html.contains("data-hemx-interval=\"500ms\""));
|
||||
assert!(html.contains("data-hemx-handle=\"tick_progress\""));
|
||||
assert!(html.contains("data-hemx-slot=\"loaded_row\""));
|
||||
assert!(html.contains("data-hemx-slot=\"infinite_row\""));
|
||||
assert!(html.contains("data-hemx-slot=\"value_option\""));
|
||||
@@ -831,8 +861,19 @@ mod tests {
|
||||
.batch,
|
||||
);
|
||||
assert!(invalid.payload_contains("Use a real email address"));
|
||||
assert!(invalid.payload_contains("Email needs a name and dotted domain"));
|
||||
assert!(invalid.updates_text(gallery::email_status));
|
||||
|
||||
let partial = inspect_batch(
|
||||
InteractionRequest::from(form(gallery::validate_email, &[("email", "xyz@")]))
|
||||
.dispatch_async(handlers(state.clone()))
|
||||
.await
|
||||
.unwrap()
|
||||
.batch,
|
||||
);
|
||||
assert!(partial.payload_contains("Email needs a name and dotted domain"));
|
||||
assert!(!partial.payload_contains("xyz@ is valid"));
|
||||
|
||||
let valid = inspect_batch(
|
||||
InteractionRequest::from(form(
|
||||
gallery::validate_email,
|
||||
@@ -845,6 +886,7 @@ mod tests {
|
||||
);
|
||||
assert!(valid.payload_contains("xyz@example.com is valid"));
|
||||
assert!(!valid.payload_contains("Use a real email address"));
|
||||
assert!(!valid.payload_contains("hemx:form-reset"));
|
||||
assert!(valid.updates_text(gallery::email_status));
|
||||
|
||||
let infinite = inspect_batch(
|
||||
@@ -864,8 +906,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.batch,
|
||||
);
|
||||
assert!(progress.updates_text(gallery::progress_meter));
|
||||
assert!(progress.payload_contains("25% complete"));
|
||||
assert!(progress.payload_contains("<progress value=\"25\" max=\"100\">25% complete</progress>"));
|
||||
|
||||
let values = inspect_batch(
|
||||
InteractionRequest::from(form(gallery::choose_category, &[("category", "numbers")]))
|
||||
|
||||
@@ -78,12 +78,13 @@
|
||||
|
||||
<section id="progress-bar" data-htmx-example="progress-bar" aria-labelledby="progress-heading">
|
||||
<h2 id="progress-heading">progress-bar</h2>
|
||||
<form data-hemx-handle="tick_progress" data-hemx-form="tick_progress" data-hemx-interval="500ms">
|
||||
<form data-hemx-handle="tick_progress" data-hemx-form="tick_progress">
|
||||
<input type="hidden" name="request" value="tick">
|
||||
<button type="submit">Tick progress</button>
|
||||
</form>
|
||||
<progress max="100" +value="self.progress">{+ self.progress_label +}</progress>
|
||||
<p data-hemx-slot="progress_meter">{+ self.progress_label +}</p>
|
||||
<p data-hemx-slot="progress_meter">
|
||||
<progress max="100" +value="self.progress">{+ self.progress_label +}</progress>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="value-select" data-htmx-example="value-select" aria-labelledby="value-heading">
|
||||
|
||||
Reference in New Issue
Block a user