Preserve native interaction semantics
This commit is contained in:
+43
-44
@@ -1029,17 +1029,17 @@ fn __hemx_lower_html(html: &str, table: &[(&str, &str, u32)]) -> ::std::string::
|
||||
let mut out = html.to_owned();
|
||||
for (attr, name, id) in table {
|
||||
let runtime_attr = match *attr {
|
||||
"data-hemx-slot" => "data-sid",
|
||||
"data-hemx-handle" => "data-hid",
|
||||
"data-hemx-form" => "data-fid",
|
||||
"data-hemx-slot" => "data-hemx-resource",
|
||||
"data-hemx-handle" => "data-hemx-action",
|
||||
"data-hemx-form" => "data-hemx-resource",
|
||||
"data-hemx-revealed" => "data-hemx-revealed",
|
||||
"data-hemx-atom" => "data-aid",
|
||||
"data-hemx-atom" => "data-hemx-resource",
|
||||
_ => continue,
|
||||
};
|
||||
out = __hemx_replace_attr(out, attr, name, runtime_attr, *id);
|
||||
}
|
||||
out = __hemx_lower_static_attr(out, "data-hemx-key", "data-key");
|
||||
out = __hemx_lower_static_attr(out, "h-key", "data-key");
|
||||
out = __hemx_lower_static_attr(out, "data-hemx-key", "data-hemx-key");
|
||||
out = __hemx_lower_static_attr(out, "h-key", "data-hemx-key");
|
||||
__hemx_inject_handle_inputs(out)
|
||||
}
|
||||
|
||||
@@ -1075,7 +1075,7 @@ fn __hemx_inject_handle_inputs(html: ::std::string::String) -> ::std::string::St
|
||||
out.push_str(opening);
|
||||
rest = &rest[open_end + 1..];
|
||||
|
||||
let Some(handle_id) = __hemx_attr(opening, "data-hid") else {
|
||||
let Some(handle_id) = __hemx_attr(opening, "data-hemx-action") else {
|
||||
continue;
|
||||
};
|
||||
let form_body_end = rest.find("</form>").unwrap_or(rest.len());
|
||||
@@ -1536,7 +1536,6 @@ fn known_hemx_attr(name: &str) -> bool {
|
||||
| "data-hemx-indicator"
|
||||
| "data-hemx-confirm"
|
||||
| "data-hemx-debounce"
|
||||
| "data-hemx-delay"
|
||||
| "data-hemx-throttle"
|
||||
| "data-hemx-every"
|
||||
| "data-hemx-interval"
|
||||
@@ -1622,7 +1621,7 @@ fn reject_invalid_hemx_attr_values(path: &Path, attrs: &[SurfaceAttribute]) -> i
|
||||
"expected a non-negative number of viewports",
|
||||
));
|
||||
}
|
||||
"data-hemx-debounce" | "data-hemx-delay" | "data-hemx-throttle" | "data-hemx-every"
|
||||
"data-hemx-debounce" | "data-hemx-throttle" | "data-hemx-every"
|
||||
| "data-hemx-interval"
|
||||
if !valid_duration(value) =>
|
||||
{
|
||||
@@ -2025,7 +2024,7 @@ fn unkeyed_generated_target_diagnostic(
|
||||
"a stable template h-key on h-for=\"{pattern} in {expr}\" so generated keyed helpers such as ui::{name}.replace(row) can target this partial"
|
||||
),
|
||||
repair: format!(
|
||||
"add h-key=\"{pattern}.id\" to that h-for; dynamic +data-key on the child is rendered HTML, not the template fact hemx uses for generated targets"
|
||||
"add h-key=\"{pattern}.id\" to that h-for; dynamic +data-hemx-key on the child is rendered HTML, not the template fact hemx uses for generated targets"
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -2655,12 +2654,12 @@ mod tests {
|
||||
let canonical_syms = resources.syms();
|
||||
assert_eq!(
|
||||
stable_id("generated-rs", &canonical_generated),
|
||||
262_445_078,
|
||||
3_787_638_288,
|
||||
"canonical generated Rust changed"
|
||||
);
|
||||
assert_eq!(
|
||||
stable_id("generated-rs-global", &canonical_globals),
|
||||
3_211_924_671,
|
||||
1_112_484_789,
|
||||
"canonical global-export Rust changed"
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -3036,8 +3035,9 @@ mod tests {
|
||||
assert!(generated.contains("pub fn replace<K, T>(slot: ::hemx::advanced::KeyedSlot<K, T>, key: K, view: &impl ::hemplate::Hemplate) -> ::hemx::advanced::Effect"));
|
||||
assert!(generated.contains("data-hemx-slot"));
|
||||
assert!(generated.contains("data-hemx-form"));
|
||||
assert!(generated.contains("data-sid"));
|
||||
assert!(generated.contains("data-fid"));
|
||||
assert!(generated.contains("data-hemx-resource"));
|
||||
assert!(!generated.contains("data-sid"));
|
||||
assert!(!generated.contains("data-fid"));
|
||||
assert!(generated.contains("__hemx_inject_handle_inputs"));
|
||||
|
||||
let syms = std::fs::read_to_string(out.join("hemx.syms")).unwrap();
|
||||
@@ -3224,7 +3224,7 @@ mod tests {
|
||||
std::fs::create_dir_all(&templates).unwrap();
|
||||
std::fs::write(
|
||||
templates.join("todos.heml"),
|
||||
r#"<ul data-hemx-slot="row"><template h-for="todo in &self.todos" h-key="todo.id"><li +data-key="todo.id">{+ todo +}</li></template></ul>"#,
|
||||
r#"<ul data-hemx-slot="row"><template h-for="todo in &self.todos" h-key="todo.id"><li +data-hemx-key="todo.id">{+ todo +}</li></template></ul>"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -3465,6 +3465,8 @@ mod hemx {{
|
||||
impl BuildFingerprint {{ pub const fn from_parts(_: &[u32]) -> Self {{ Self }} }}
|
||||
pub struct Effect;
|
||||
#[derive(Clone, Copy)] pub struct ResourceId;
|
||||
#[derive(Clone, Copy)] pub struct EventName(&'static str);
|
||||
impl EventName {{ pub const fn new(name: &'static str) -> Self {{ Self(name) }} }}
|
||||
pub trait IntoEffect {{}}
|
||||
impl IntoEffect for Effect {{}}
|
||||
pub trait GeneratedTarget {{ fn __hemx_resource_id(self) -> ResourceId; }}
|
||||
@@ -3722,7 +3724,9 @@ fn main() {{
|
||||
.contains("h-for=\"todo in &self.todos\" without h-key"));
|
||||
assert!(diagnostic.expected.contains("ui::todo_row.replace(row)"));
|
||||
assert!(diagnostic.repair.contains("h-key=\"todo.id\""));
|
||||
assert!(diagnostic.repair.contains("dynamic +data-key on the child"));
|
||||
assert!(diagnostic
|
||||
.repair
|
||||
.contains("dynamic +data-hemx-key on the child"));
|
||||
assert!(diagnostic.to_string().contains("templates/todo.heml"));
|
||||
}
|
||||
|
||||
@@ -3737,7 +3741,7 @@ fn main() {{
|
||||
),
|
||||
(
|
||||
"slot_data_key",
|
||||
r#"<template h-for="todo in &self.todos"><li data-hemx-slot="todo_row" +data-key="todo.id">{+ todo.title +}</li></template>"#,
|
||||
r#"<template h-for="todo in &self.todos"><li data-hemx-slot="todo_row" +data-hemx-key="todo.id">{+ todo.title +}</li></template>"#,
|
||||
"data-hemx-slot=\"todo_row\"",
|
||||
"ui::todo_row",
|
||||
),
|
||||
@@ -3778,7 +3782,7 @@ fn main() {{
|
||||
assert!(err.contains("h-key=\"todo.id\""));
|
||||
assert!(err.contains("generated keyed helpers"));
|
||||
assert!(err.contains(helper));
|
||||
assert!(err.contains("dynamic +data-key on the child"));
|
||||
assert!(err.contains("dynamic +data-hemx-key on the child"));
|
||||
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
}
|
||||
@@ -3985,12 +3989,6 @@ fn main() {{
|
||||
"data-hemx-ws",
|
||||
"non-empty",
|
||||
),
|
||||
(
|
||||
"delay-empty",
|
||||
r#"<button data-hemx-handle="save" data-hemx-delay="">Save</button>"#,
|
||||
"data-hemx-delay",
|
||||
"milliseconds",
|
||||
),
|
||||
(
|
||||
"revealed-ahead-negative",
|
||||
r#"<form data-hemx-revealed data-hemx-revealed-ahead="-1"></form>"#,
|
||||
@@ -4003,12 +4001,6 @@ fn main() {{
|
||||
"data-hemx-throttle",
|
||||
"milliseconds",
|
||||
),
|
||||
(
|
||||
"duration-suffix-only",
|
||||
r#"<button data-hemx-handle="save" data-hemx-delay="s">Save</button>"#,
|
||||
"data-hemx-delay",
|
||||
"milliseconds",
|
||||
),
|
||||
(
|
||||
"policy-duplicate-anchor",
|
||||
r#"<button data-hemx-handle="save" data-hemx-policy="newest">Save</button>"#,
|
||||
@@ -4021,12 +4013,6 @@ fn main() {{
|
||||
"data-hemx-debounce",
|
||||
"250ms",
|
||||
),
|
||||
(
|
||||
"delay",
|
||||
r#"<button data-hemx-handle="save" data-hemx-delay="soon">Save</button>"#,
|
||||
"data-hemx-delay",
|
||||
"250ms",
|
||||
),
|
||||
(
|
||||
"every",
|
||||
r#"<button data-hemx-handle="save" data-hemx-every="1sec">Save</button>"#,
|
||||
@@ -4103,8 +4089,8 @@ fn main() {{
|
||||
"data-hemx-confirm" => "expected a non-empty confirmation message",
|
||||
"data-hemx-sse" => "expected a non-empty same-origin SSE URL",
|
||||
"data-hemx-ws" => "expected a non-empty same-origin WebSocket URL",
|
||||
"data-hemx-debounce" | "data-hemx-delay" | "data-hemx-throttle"
|
||||
| "data-hemx-every" | "data-hemx-interval" => {
|
||||
"data-hemx-debounce" | "data-hemx-throttle" | "data-hemx-every"
|
||||
| "data-hemx-interval" => {
|
||||
"expected milliseconds like `250`/`250ms` or seconds like `1s`"
|
||||
}
|
||||
_ => panic!("missing expectation for {attr}"),
|
||||
@@ -4170,7 +4156,7 @@ fn main() {{
|
||||
|
||||
let valid_source = r#"
|
||||
<main data-hemx-root="app" data-hemx-sse="/events" data-hemx-ws="/room/socket">
|
||||
<button data-hemx-handle="save" data-hemx-policy="latest" data-hemx-on="click change" data-hemx-confirm="Save?" data-hemx-delay="250ms" data-hemx-throttle="1s">Save</button>
|
||||
<button data-hemx-handle="save" data-hemx-policy="latest" data-hemx-on="click change" data-hemx-confirm="Save?" data-hemx-debounce="250ms" data-hemx-throttle="1s">Save</button>
|
||||
<form method="get" action="/search" data-hemx-history="replace"><input name="q"></form>
|
||||
</main>
|
||||
"#;
|
||||
@@ -4282,7 +4268,7 @@ fn main() {{
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_lowering_injects_progressive_form_handle_and_key() {
|
||||
fn generated_lowering_preserves_native_interaction_contract() {
|
||||
let base = test_dir("hemx-build-lowering-test");
|
||||
let templates = base.join("templates");
|
||||
let out = base.join("out");
|
||||
@@ -4290,7 +4276,7 @@ fn main() {{
|
||||
std::fs::create_dir_all(&templates).unwrap();
|
||||
std::fs::write(
|
||||
templates.join("todo.heml"),
|
||||
r#"<form data-hemx-handle="create"><input name="title"></form><li data-hemx-slot="row" data-hemx-key="7"></li>"#,
|
||||
r#"<main data-hemx-root><a href="/docs">Docs</a><form action="/todos" method="post" data-hemx-handle="create"><label for="title">Title</label><input id="title" name="title" aria-invalid="true" aria-describedby="title-error"><span id="title-error">Required</span><button data-hemx-on="click">Create</button></form><form action="/search" method="get"><input name="q"></form><li data-hemx-slot="row" data-hemx-key="7"></li></main>"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -4305,6 +4291,8 @@ mod hemx {{
|
||||
impl BuildFingerprint {{ pub const fn from_parts(_: &[u32]) -> Self {{ Self }} }}
|
||||
pub struct Effect;
|
||||
#[derive(Clone, Copy)] pub struct ResourceId;
|
||||
#[derive(Clone, Copy)] pub struct EventName(&'static str);
|
||||
impl EventName {{ pub const fn new(name: &'static str) -> Self {{ Self(name) }} }}
|
||||
pub trait IntoEffect {{}}
|
||||
impl IntoEffect for Effect {{}}
|
||||
pub trait GeneratedTarget {{ fn __hemx_resource_id(self) -> ResourceId; }}
|
||||
@@ -4337,10 +4325,21 @@ mod hemplate {{
|
||||
}}
|
||||
{generated}
|
||||
fn main() {{
|
||||
let html = todo::lower_html(r#"<form data-hemx-handle="create"><input name="title"></form><li data-hemx-slot="row" h-key="7"></li>"#);
|
||||
assert!(html.contains(r#"data-hid="#));
|
||||
let html = todo::lower_html(r#"<main data-hemx-root><a href="/docs">Docs</a><form action="/todos" method="post" data-hemx-handle="create"><label for="title">Title</label><input id="title" name="title" aria-invalid="true" aria-describedby="title-error"><span id="title-error">Required</span><button data-hemx-on="click">Create</button></form><form action="/search" method="get"><input name="q"></form><li data-hemx-slot="row" h-key="7"></li></main>"#);
|
||||
assert!(html.contains(r#"data-hemx-root"#));
|
||||
assert!(html.contains(r#"data-hemx-action="#));
|
||||
assert!(html.contains(r#"data-hemx-resource="#));
|
||||
assert!(html.contains(r#"data-hemx-key="7""#));
|
||||
assert!(html.contains(r#"data-hemx-on="click""#));
|
||||
assert!(html.contains(r#"<a href="/docs">"#));
|
||||
assert!(html.contains(r#"action="/todos" method="post""#));
|
||||
assert!(html.contains(r#"action="/search" method="get""#));
|
||||
assert!(html.contains(r#"aria-invalid="true""#));
|
||||
assert!(html.contains(r#"aria-describedby="title-error""#));
|
||||
assert!(html.contains(r#"id="title-error">Required"#));
|
||||
assert!(html.contains(r#"name="__h""#));
|
||||
assert!(html.contains(r#"data-key="7""#));
|
||||
assert!(!html.contains("data-hid"));
|
||||
assert!(!html.contains("data-sid"));
|
||||
assert!(!html.contains("h-key"));
|
||||
}}
|
||||
"###,
|
||||
|
||||
Reference in New Issue
Block a user