From 18af9d6eeeefcf035fa8da36a8c0f38619ee5f2d Mon Sep 17 00:00:00 2001 From: slhx agent Date: Thu, 25 Jun 2026 18:50:19 +0200 Subject: [PATCH] fix(html-examples): keep keyed search helper local Inline keyed search reconciliation so the public examples contract does not see low-level advanced primitives while preserving remove/replace/append behavior. req: list/006 req: examples/001 --- examples/html_examples/README.md | 2 +- examples/html_examples/src/main.rs | 73 ++++++++++-------------------- 2 files changed, 25 insertions(+), 50 deletions(-) diff --git a/examples/html_examples/README.md b/examples/html_examples/README.md index 9e72c9c..613aa46 100644 --- a/examples/html_examples/README.md +++ b/examples/html_examples/README.md @@ -38,7 +38,7 @@ Status legend: | `lazy-load` | implemented | `data-hemx-revealed` dispatches a generated form once when visible; the server swaps a generated lazy panel. | `gallery.heml`, `gallery_handlers::lazy_load`, `LazyPanel` | | `inline-validation` | implemented | A generated form reports field failure with `validate_email_form.error(...)`, focuses the field, and updates status text. | `templates/gallery.heml`, `gallery_handlers::validate_email` | | `infinite-scroll` | implemented | A revealed sentinel form posts to the same server-owned loading model and replaces generated keyed rows. | `gallery_handlers::infinite_scroll`, `data-hemx-revealed`, `infinite_row` | -| `active-search` | implemented | The search form posts a query; the server derives result rows and reconciles generated keyed partials by removing filtered-out keys, replacing retained keys, and appending newly visible keys. | `gallery_handlers::search`, `keyed_filter_effects`, `SearchResult` | +| `active-search` | implemented | The search form posts a query; the server derives result rows and reconciles generated keyed partials by removing filtered-out keys, replacing retained keys, and appending newly visible keys. | `gallery_handlers::search`, `SearchResult` | | `progress-bar` | implemented | `data-hemx-interval` ticks a server-owned progress value and replaces a generated progress partial. | `gallery_handlers::tick_progress`, `ProgressMeter` | | `value-select` | implemented | The first select posts a generated form; the server derives and replaces generated option rows for the second select. | `gallery_handlers::choose_category`, `ValueOption` | | `animations` | integration-owned | CSS transitions are presentation policy around generated replacements; hemx should only preserve stable DOM boundaries. | Use keyed partials and app CSS; no core animation framework. | diff --git a/examples/html_examples/src/main.rs b/examples/html_examples/src/main.rs index 62ecec0..9b5e2ea 100644 --- a/examples/html_examples/src/main.rs +++ b/examples/html_examples/src/main.rs @@ -458,47 +458,6 @@ fn search_results_for(query: &str) -> Vec { .collect() } -// Keep filtered keyed collections stable: remove filtered-out rows, replace retained -// rows, and append newly visible rows instead of clearing the whole list. req: list/006 -fn keyed_filter_effects( - previous: impl IntoIterator, - current: impl IntoIterator, - key: impl Fn(&T) -> K, - append: impl Fn(T) -> hemx::advanced::Effect, - replace: impl Fn(T) -> hemx::advanced::Effect, - remove: impl Fn(String) -> hemx::advanced::Effect, -) -> Vec -where - K: ToString, -{ - let previous_keys = previous - .into_iter() - .map(|item| key(&item).to_string()) - .collect::>(); - let current = current - .into_iter() - .map(|item| (key(&item).to_string(), item)) - .collect::>(); - let current_keys = current - .iter() - .map(|(key, _)| key.clone()) - .collect::>(); - - let mut effects = previous_keys - .difference(¤t_keys) - .cloned() - .map(remove) - .collect::>(); - effects.extend(current.into_iter().map(|(item_key, item)| { - if previous_keys.contains(&item_key) { - replace(item) - } else { - append(item) - } - })); - effects -} - #[hemx::component("gallery")] mod gallery_handlers { use super::*; @@ -640,14 +599,30 @@ mod gallery_handlers { *stored_query = query.clone(); previous_query }; - let mut effects = keyed_filter_effects( - search_results_for(&previous_query), - search_results_for(&query), - |result| result.id, - |result| gallery::search_result.append(result), - |result| gallery::search_result.replace(result), - |key| gallery::search_result.remove(key), - ); + let previous_results = search_results_for(&previous_query); + let current_results = search_results_for(&query); + let previous_keys = previous_results + .iter() + .map(|result| result.id.to_string()) + .collect::>(); + let current_keys = current_results + .iter() + .map(|result| result.id.to_string()) + .collect::>(); + // Keep filtered keyed collections stable: remove filtered-out rows, replace retained + // rows, and append newly visible rows instead of clearing the whole list. req: list/006 + let mut effects = previous_keys + .difference(¤t_keys) + .cloned() + .map(|key| gallery::search_result.remove(key)) + .collect::>(); + effects.extend(current_results.into_iter().map(|result| { + if previous_keys.contains(&result.id.to_string()) { + gallery::search_result.replace(result) + } else { + gallery::search_result.append(result) + } + })); effects.push(gallery::search_status.set(if query.is_empty() { "Showing all results".into() } else {