feat(runtime): support viewport-ahead reveals
This commit is contained in:
@@ -67,7 +67,7 @@ Keep it stable. Prefer pointers to canonical sources over copied structure, file
|
|||||||
- The public component-reuse explanation lives in `docs/recipes/reusable-partials.md`; do not grow a client component framework to explain partial composition.
|
- The public component-reuse explanation lives in `docs/recipes/reusable-partials.md`; do not grow a client component framework to explain partial composition.
|
||||||
- The stable public `.heml` authoring surface lives in `docs/hemplate-syntax.md`; Hemlate examples must use that real hemplate syntax, not Vue/Handlebars sketches. hemx-build consumes hemplate Surface facts and must not grow an independent `.heml` parser or CSS-path identity model. Plain CSS/SCSS owns appearance; generated class constants are ergonomic references, not a styling framework or behavior selector system. Generated resources, form/handle metadata, atoms, and event constants come from hemx-build facts, not hand-written app plumbing. Forms remain HTML-shaped, checked against user-authored Rust domain types, parsed through `FormValue`, and manipulated through generated form/control ids rather than selectors. Proc-macros stay local/side-effect-free while build.rs owns global codegen and hard build failures. No-op global codegen must preserve generated artifact timestamps so downstream Rust compilation remains fresh only when canonical output changes. req: boundary/001 req: boundary/002 req: boundary/003 req: boundary/004 req: surface/001 req: surface/002 req: surface/003 req: surface/004 req: surface/005 req: surface/006 req: surface/007 req: surface/008 req: surface/009 req: surface/010 req: codegen/001 req: codegen/003 req: codegen/004 req: codegen/005 req: codegen/006 req: form/001 req: form/004 req: form/007 req: form/008 req: form_effects/001 req: form_effects/002 req: form_effects/003 req: build/001 req: build/002 req: build/003 req: build/004 req: build/005 req: build/006 req: build/007 req: build/008 req: build/009 req: style/001 req: style/002 req: style/003 req: style/004 req: style/005 req: style/006
|
- The stable public `.heml` authoring surface lives in `docs/hemplate-syntax.md`; Hemlate examples must use that real hemplate syntax, not Vue/Handlebars sketches. hemx-build consumes hemplate Surface facts and must not grow an independent `.heml` parser or CSS-path identity model. Plain CSS/SCSS owns appearance; generated class constants are ergonomic references, not a styling framework or behavior selector system. Generated resources, form/handle metadata, atoms, and event constants come from hemx-build facts, not hand-written app plumbing. Forms remain HTML-shaped, checked against user-authored Rust domain types, parsed through `FormValue`, and manipulated through generated form/control ids rather than selectors. Proc-macros stay local/side-effect-free while build.rs owns global codegen and hard build failures. No-op global codegen must preserve generated artifact timestamps so downstream Rust compilation remains fresh only when canonical output changes. req: boundary/001 req: boundary/002 req: boundary/003 req: boundary/004 req: surface/001 req: surface/002 req: surface/003 req: surface/004 req: surface/005 req: surface/006 req: surface/007 req: surface/008 req: surface/009 req: surface/010 req: codegen/001 req: codegen/003 req: codegen/004 req: codegen/005 req: codegen/006 req: form/001 req: form/004 req: form/007 req: form/008 req: form_effects/001 req: form_effects/002 req: form_effects/003 req: build/001 req: build/002 req: build/003 req: build/004 req: build/005 req: build/006 req: build/007 req: build/008 req: build/009 req: style/001 req: style/002 req: style/003 req: style/004 req: style/005 req: style/006
|
||||||
- Optional `.heml` editor overlays must share authority with `hemx-build` diagnostics and `docs/hemplate-syntax.md`; `hemx-lsp` owns editor protocol glue for diagnostics/completion/hover and derive-known template facts, while VS Code/Cursor/Neovim keep normal HTML/tree-sitter tooling. Simple `h-for` completion facts cover one Rust identifier bound directly to a `self` vector field; malformed bindings and non-vector fields must not fabricate locals. Do not create a second template language, selector model, formatter, Rust type system, or custom editor framework. Compiler diagnostics with directive/target metadata select that source attribute instead of line 0 column 0. Cross-file template/handler references visible to build validation must fail at `cargo check` with useful spans; global completeness checks stay component-scoped unless caught at mount/tests. req: diagnostics/004 req: diagnostics/005 req: diagnostics/006 req: diagnostics/007 req: diagnostics/008 req: diag/009 req: diag/010 req: invariant/004 req: invariant/006 req: check/001 req: check/003
|
- Optional `.heml` editor overlays must share authority with `hemx-build` diagnostics and `docs/hemplate-syntax.md`; `hemx-lsp` owns editor protocol glue for diagnostics/completion/hover and derive-known template facts, while VS Code/Cursor/Neovim keep normal HTML/tree-sitter tooling. Simple `h-for` completion facts cover one Rust identifier bound directly to a `self` vector field; malformed bindings and non-vector fields must not fabricate locals. Do not create a second template language, selector model, formatter, Rust type system, or custom editor framework. Compiler diagnostics with directive/target metadata select that source attribute instead of line 0 column 0. Cross-file template/handler references visible to build validation must fail at `cargo check` with useful spans; global completeness checks stay component-scoped unless caught at mount/tests. req: diagnostics/004 req: diagnostics/005 req: diagnostics/006 req: diagnostics/007 req: diagnostics/008 req: diag/009 req: diag/010 req: invariant/004 req: invariant/006 req: check/001 req: check/003
|
||||||
- JS runtime changes must preserve root-scoped lookup, delegated listeners, canonical hemx `EffectBatch` application, dynamic polling/revealed binding, fail-closed request handling, transactional/recoverable failure behavior, root-scoped error outlets, and tiny pending/failure/trigger-timing conventions without selectors, handler-name parsing, VDOM, expressions, or per-node listeners. Runtime `.d.ts` types are developer convenience only, not core tooling authority. req: invariant/002 req: runtime/001 req: runtime/002 req: runtime/003 req: runtime/005 req: runtime/006 req: runtime/007 req: failure/001 req: failure/002 req: failure/003 req: failure/004 req: failure/005 req: failure/006 req: convention/001 req: convention/002 req: convention/003 req: convention/004 req: convention/005 req: convention/006 req: convention/007 req: convention/008 req: convention/009 req: convention/010 req: convention/011 req: convention/012 req: convention/013 req: convention/014 req: convention/015 req: convention/016 req: convention/017 req: ts/001
|
- JS runtime changes must preserve root-scoped lookup, delegated listeners, canonical hemx `EffectBatch` application, dynamic polling and viewport-aware revealed binding, fail-closed request handling, transactional/recoverable failure behavior, root-scoped error outlets, and tiny pending/failure/trigger-timing conventions without selectors, handler-name parsing, VDOM, expressions, or per-node listeners. Runtime `.d.ts` types are developer convenience only, not core tooling authority. req: invariant/002 req: runtime/001 req: runtime/002 req: runtime/003 req: runtime/005 req: runtime/006 req: runtime/007 req: failure/001 req: failure/002 req: failure/003 req: failure/004 req: failure/005 req: failure/006 req: convention/001 req: convention/002 req: convention/003 req: convention/004 req: convention/005 req: convention/006 req: convention/007 req: convention/008 req: convention/009 req: convention/010 req: convention/011 req: convention/012 req: convention/013 req: convention/014 req: convention/015 req: convention/016 req: convention/017 req: ts/001
|
||||||
- Opaque island JavaScript is a leaf adapter for high-frequency local behavior only; client-local handlers keep the server-handler shape while `hemx-wasm` owns concrete opt-in syntax. Use native events/generated helpers at the boundary and do not introduce a component runtime, client state graph, VDOM, selector interop, or second UI model. req: canonical_authoring/017 req: client_local/001 req: client_local/003 req: client_local/004 req: interop/001 req: interop/002 req: interop/003 req: interop/006 req: interop/007 req: interop/008 req: interop/009 req: interop/010 req: interop/011 req: interop/012
|
- Opaque island JavaScript is a leaf adapter for high-frequency local behavior only; client-local handlers keep the server-handler shape while `hemx-wasm` owns concrete opt-in syntax. Use native events/generated helpers at the boundary and do not introduce a component runtime, client state graph, VDOM, selector interop, or second UI model. req: canonical_authoring/017 req: client_local/001 req: client_local/003 req: client_local/004 req: interop/001 req: interop/002 req: interop/003 req: interop/006 req: interop/007 req: interop/008 req: interop/009 req: interop/010 req: interop/011 req: interop/012
|
||||||
- Host capability adapters must stay at the `hemx-host` boundary: typed capabilities use fire/request/stream/schedule shapes; adapters may call host APIs and return host events, but they must not mutate DOM or own app/domain state. req: host/001 req: host/002
|
- Host capability adapters must stay at the `hemx-host` boundary: typed capabilities use fire/request/stream/schedule shapes; adapters may call host APIs and return host events, but they must not mutate DOM or own app/domain state. req: host/001 req: host/002
|
||||||
- Local/offline app behavior should be commands/events/projections; do not add `hemx-local`, stored DOM patches, stored `EffectBatch` truth, or a core client state graph without a proven reusable contract. Atoms are explicit addressable/bootstrap/sync resources, not the default state container or a reactive framework. Replay, reconciliation, export, and deletion rules stay explicit product decisions, and exemplars should show UI effects as app-state output. The local-first multiplayer kanban remains an advanced north-star integration milestone, not beginner/API surface scope. req: canonical_authoring/018 req: canonical_authoring/019 req: state/001 req: state/002 req: state/003 req: state/004 req: state/005 req: state/006 req: state/007 req: local/001 req: local/002 req: local/003 req: local/004 req: milestone/001 req: milestone/002 req: milestone/003
|
- Local/offline app behavior should be commands/events/projections; do not add `hemx-local`, stored DOM patches, stored `EffectBatch` truth, or a core client state graph without a proven reusable contract. Atoms are explicit addressable/bootstrap/sync resources, not the default state container or a reactive framework. Replay, reconciliation, export, and deletion rules stay explicit product decisions, and exemplars should show UI effects as app-state output. The local-first multiplayer kanban remains an advanced north-star integration milestone, not beginner/API surface scope. req: canonical_authoring/018 req: canonical_authoring/019 req: state/001 req: state/002 req: state/003 req: state/004 req: state/005 req: state/006 req: state/007 req: local/001 req: local/002 req: local/003 req: local/004 req: milestone/001 req: milestone/002 req: milestone/003
|
||||||
|
|||||||
+2
-2
@@ -1005,13 +1005,13 @@ async fn delete(app: State<App>, todo_id: TodoId) -> impl IntoEffect
|
|||||||
|
|
||||||
010 Supported cosmetic convention attributes include `data-hemx-pending-class`, `data-hemx-indicator`, `data-hemx-confirm`, and `data-hemx-disable-while-pending`.
|
010 Supported cosmetic convention attributes include `data-hemx-pending-class`, `data-hemx-indicator`, `data-hemx-confirm`, and `data-hemx-disable-while-pending`.
|
||||||
|
|
||||||
011 Supported timing/trigger convention attributes include `data-hemx-debounce`, `data-hemx-delay`, `data-hemx-throttle`, `data-hemx-every`, `data-hemx-interval`, `data-hemx-revealed`, `data-hemx-policy`, and `data-hemx-on`.
|
011 Supported timing/trigger convention attributes include `data-hemx-debounce`, `data-hemx-delay`, `data-hemx-throttle`, `data-hemx-every`, `data-hemx-interval`, `data-hemx-revealed`, `data-hemx-revealed-ahead`, `data-hemx-policy`, and `data-hemx-on`.
|
||||||
|
|
||||||
012 Runtime-supported delegated events are `click`, `submit`, `input`, `change`, `dragstart`, `dragover`, and `drop`; unsupported static event names are build errors.
|
012 Runtime-supported delegated events are `click`, `submit`, `input`, `change`, `dragstart`, `dragover`, and `drop`; unsupported static event names are build errors.
|
||||||
|
|
||||||
013 Static empty confirmation messages are build errors: they silently disable the guard in browsers. Custom confirm UI belongs to integration crates.
|
013 Static empty confirmation messages are build errors: they silently disable the guard in browsers. Custom confirm UI belongs to integration crates.
|
||||||
|
|
||||||
014 `data-hemx-revealed` dispatches once when the element enters view, re-arms after browser page restoration, and has an immediate fallback when `IntersectionObserver` is unavailable.
|
014 `data-hemx-revealed` dispatches once when the element enters its configured non-negative viewport-ahead margin, tracks viewport resizing, re-arms after browser page restoration, and has an immediate fallback when `IntersectionObserver` is unavailable.
|
||||||
|
|
||||||
015 Duplicate timers/observers per root are avoided.
|
015 Duplicate timers/observers per root are avoided.
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ Status legend:
|
|||||||
| `edit-row` | implemented | A table row is a keyed `.heml` partial with generated edit/save forms; no selector target strings. | `templates/partials/editable_row.heml`, `editable_row_handlers::edit_row`, `save_row` |
|
| `edit-row` | implemented | A table row is a keyed `.heml` partial with generated edit/save forms; no selector target strings. | `templates/partials/editable_row.heml`, `editable_row_handlers::edit_row`, `save_row` |
|
||||||
| `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` |
|
| `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` |
|
| `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` |
|
| `infinite-scroll` | implemented | A revealed sentinel form posts to the same server-owned loading model and replaces generated keyed rows; `data-hemx-revealed-ahead` opts into viewport-ahead loading without moving the observed element. | `gallery_handlers::infinite_scroll`, `data-hemx-revealed`, `data-hemx-revealed-ahead`, `infinite_row` |
|
||||||
| `active-search` | implemented | The search form uses GET URL state; 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` |
|
| `active-search` | implemented | The search form uses GET URL state; 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 | The Tick progress button advances server-owned progress and replaces a generated progress partial with visible percentage text. | `gallery_handlers::tick_progress`, `ProgressMeter` |
|
| `progress-bar` | implemented | The Tick progress button advances server-owned progress and replaces a generated progress partial with visible percentage text. | `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` |
|
| `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` |
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
{+ row +}
|
{+ row +}
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
<form data-hemx-handle="infinite_scroll" data-hemx-form="infinite_scroll" data-hemx-revealed="true">
|
<form data-hemx-handle="infinite_scroll" data-hemx-form="infinite_scroll" data-hemx-revealed="true" data-hemx-revealed-ahead="1">
|
||||||
<input type="hidden" name="request" value="more">
|
<input type="hidden" name="request" value="more">
|
||||||
<button type="submit">Reveal more rows</button>
|
<button type="submit">Reveal more rows</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
+24
-2
@@ -1623,6 +1623,7 @@ fn known_hemx_attr(name: &str) -> bool {
|
|||||||
| "data-hemx-every"
|
| "data-hemx-every"
|
||||||
| "data-hemx-interval"
|
| "data-hemx-interval"
|
||||||
| "data-hemx-revealed"
|
| "data-hemx-revealed"
|
||||||
|
| "data-hemx-revealed-ahead"
|
||||||
| "data-hemx-disable-while-pending"
|
| "data-hemx-disable-while-pending"
|
||||||
| "data-hemx-policy"
|
| "data-hemx-policy"
|
||||||
| "data-hemx-nav"
|
| "data-hemx-nav"
|
||||||
@@ -1727,6 +1728,20 @@ fn reject_invalid_hemx_attr_values(path: &Path, attrs: &[SurfaceAttribute]) -> i
|
|||||||
"expected a non-empty same-origin SSE URL",
|
"expected a non-empty same-origin SSE URL",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
"data-hemx-revealed-ahead"
|
||||||
|
if !value
|
||||||
|
.trim()
|
||||||
|
.parse::<f64>()
|
||||||
|
.ok()
|
||||||
|
.is_some_and(|value| value.is_finite() && value >= 0.0) =>
|
||||||
|
{
|
||||||
|
return Err(invalid_hemx_value(
|
||||||
|
path,
|
||||||
|
&attr.name,
|
||||||
|
value,
|
||||||
|
"expected a non-negative number of viewports",
|
||||||
|
));
|
||||||
|
}
|
||||||
"data-hemx-debounce" | "data-hemx-delay" | "data-hemx-throttle" | "data-hemx-every"
|
"data-hemx-debounce" | "data-hemx-delay" | "data-hemx-throttle" | "data-hemx-every"
|
||||||
| "data-hemx-interval"
|
| "data-hemx-interval"
|
||||||
if !valid_duration(value) =>
|
if !valid_duration(value) =>
|
||||||
@@ -2774,12 +2789,12 @@ mod tests {
|
|||||||
let canonical_syms = resources.syms();
|
let canonical_syms = resources.syms();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stable_id("generated-rs", &canonical_generated),
|
stable_id("generated-rs", &canonical_generated),
|
||||||
3_748_714_336,
|
2_620_423_950,
|
||||||
"canonical generated Rust changed"
|
"canonical generated Rust changed"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stable_id("generated-rs-global", &canonical_globals),
|
stable_id("generated-rs-global", &canonical_globals),
|
||||||
2_783_766_475,
|
2_559_847_213,
|
||||||
"canonical global-export Rust changed"
|
"canonical global-export Rust changed"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -4223,6 +4238,12 @@ fn main() {{
|
|||||||
"data-hemx-delay",
|
"data-hemx-delay",
|
||||||
"milliseconds",
|
"milliseconds",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"revealed-ahead-negative",
|
||||||
|
r#"<form data-hemx-revealed data-hemx-revealed-ahead="-1"></form>"#,
|
||||||
|
"data-hemx-revealed-ahead",
|
||||||
|
"non-negative",
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"throttle-invalid",
|
"throttle-invalid",
|
||||||
r#"<button data-hemx-handle="save" data-hemx-throttle="ms">Save</button>"#,
|
r#"<button data-hemx-handle="save" data-hemx-throttle="ms">Save</button>"#,
|
||||||
@@ -4333,6 +4354,7 @@ fn main() {{
|
|||||||
"data-hemx-client-state-version" => {
|
"data-hemx-client-state-version" => {
|
||||||
"expected a positive client state ABI version"
|
"expected a positive client state ABI version"
|
||||||
}
|
}
|
||||||
|
"data-hemx-revealed-ahead" => "expected a non-negative number of viewports",
|
||||||
"data-hemx-on" => "expected runtime-supported events: `click`, `submit`, `input`, `change`, `keydown`, `dragstart`, `dragover`, or `drop`",
|
"data-hemx-on" => "expected runtime-supported events: `click`, `submit`, `input`, `change`, `keydown`, `dragstart`, `dragover`, or `drop`",
|
||||||
"data-hemx-confirm" => "expected a non-empty confirmation message",
|
"data-hemx-confirm" => "expected a non-empty confirmation message",
|
||||||
"data-hemx-sse" => "expected a non-empty same-origin SSE URL",
|
"data-hemx-sse" => "expected a non-empty same-origin SSE URL",
|
||||||
|
|||||||
+27
-9
@@ -881,8 +881,14 @@
|
|||||||
everyTimers.delete(el);
|
everyTimers.delete(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function revealedRootMargin(el) {
|
||||||
|
const ahead = Number(el.getAttribute("data-hemx-revealed-ahead") || "0");
|
||||||
|
const viewports = Number.isFinite(ahead) && ahead >= 0 ? ahead : 0;
|
||||||
|
return `0px 0px ${viewports * window.innerHeight}px 0px`;
|
||||||
|
}
|
||||||
|
|
||||||
function bindRevealed(root) {
|
function bindRevealed(root) {
|
||||||
let observer = revealObservers.get(root);
|
let observers = revealObservers.get(root);
|
||||||
forEachElement(root, (el) => {
|
forEachElement(root, (el) => {
|
||||||
if (!el.hasAttribute("data-hemx-revealed") || revealed.has(el)) return;
|
if (!el.hasAttribute("data-hemx-revealed") || revealed.has(el)) return;
|
||||||
if (typeof IntersectionObserver === "undefined") {
|
if (typeof IntersectionObserver === "undefined") {
|
||||||
@@ -890,6 +896,12 @@
|
|||||||
schedule(el, "revealed");
|
schedule(el, "revealed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!observers) {
|
||||||
|
observers = new Map();
|
||||||
|
revealObservers.set(root, observers);
|
||||||
|
}
|
||||||
|
const rootMargin = revealedRootMargin(el);
|
||||||
|
let observer = observers.get(rootMargin);
|
||||||
if (!observer) {
|
if (!observer) {
|
||||||
observer = new IntersectionObserver((entries) => {
|
observer = new IntersectionObserver((entries) => {
|
||||||
entries.forEach((entry) => {
|
entries.forEach((entry) => {
|
||||||
@@ -898,8 +910,8 @@
|
|||||||
observer.unobserve(entry.target);
|
observer.unobserve(entry.target);
|
||||||
schedule(entry.target, "revealed");
|
schedule(entry.target, "revealed");
|
||||||
});
|
});
|
||||||
});
|
}, { rootMargin });
|
||||||
revealObservers.set(root, observer);
|
observers.set(rootMargin, observer);
|
||||||
}
|
}
|
||||||
observer.observe(el);
|
observer.observe(el);
|
||||||
});
|
});
|
||||||
@@ -1123,25 +1135,30 @@
|
|||||||
const source = sseSources.get(root);
|
const source = sseSources.get(root);
|
||||||
if (source) source.close();
|
if (source) source.close();
|
||||||
sseSources.delete(root);
|
sseSources.delete(root);
|
||||||
const observer = revealObservers.get(root);
|
const observers = revealObservers.get(root);
|
||||||
if (observer) observer.disconnect();
|
if (observers) observers.forEach((observer) => observer.disconnect());
|
||||||
revealObservers.delete(root);
|
revealObservers.delete(root);
|
||||||
stopDescendantPolling(root);
|
stopDescendantPolling(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreRevealed(event) {
|
function rebindRevealed(resetDispatched) {
|
||||||
if (!event.persisted) return;
|
|
||||||
roots().forEach((root) => {
|
roots().forEach((root) => {
|
||||||
const observer = revealObservers.get(root);
|
const observers = revealObservers.get(root);
|
||||||
if (observer) observer.disconnect();
|
if (observers) observers.forEach((observer) => observer.disconnect());
|
||||||
revealObservers.delete(root);
|
revealObservers.delete(root);
|
||||||
|
if (resetDispatched) {
|
||||||
forEachElement(root, (el) => {
|
forEachElement(root, (el) => {
|
||||||
if (attrEquals(el, "data-hemx-on", "revealed") || el.hasAttribute("data-hemx-revealed")) revealed.delete(el);
|
if (attrEquals(el, "data-hemx-on", "revealed") || el.hasAttribute("data-hemx-revealed")) revealed.delete(el);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
bindRevealed(root);
|
bindRevealed(root);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function restoreRevealed(event) {
|
||||||
|
if (event.persisted) rebindRevealed(true);
|
||||||
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
roots().forEach((root) => {
|
roots().forEach((root) => {
|
||||||
root.setAttribute("data-hemx-request-timeout-ms", String(REQUEST_TIMEOUT_MS));
|
root.setAttribute("data-hemx-request-timeout-ms", String(REQUEST_TIMEOUT_MS));
|
||||||
@@ -1189,6 +1206,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("pageshow", restoreRevealed);
|
window.addEventListener("pageshow", restoreRevealed);
|
||||||
|
window.addEventListener("resize", () => rebindRevealed(false));
|
||||||
|
|
||||||
addEventListener("popstate", (event) => {
|
addEventListener("popstate", (event) => {
|
||||||
const root = roots()[0];
|
const root = roots()[0];
|
||||||
|
|||||||
@@ -146,7 +146,10 @@ fn runtime_supports_tiny_delay_and_revealed_scheduling() {
|
|||||||
assert!(source.contains("const revealObservers = new WeakMap()"));
|
assert!(source.contains("const revealObservers = new WeakMap()"));
|
||||||
assert!(source.contains("const revealed = new WeakSet()"));
|
assert!(source.contains("const revealed = new WeakSet()"));
|
||||||
assert!(source.contains("data-hemx-revealed"));
|
assert!(source.contains("data-hemx-revealed"));
|
||||||
|
assert!(source.contains("data-hemx-revealed-ahead")); // req: convention/011 test
|
||||||
assert!(source.contains("typeof IntersectionObserver === \"undefined\""));
|
assert!(source.contains("typeof IntersectionObserver === \"undefined\""));
|
||||||
|
assert!(source.contains("rootMargin"));
|
||||||
|
assert!(source.contains("addEventListener(\"resize\", () => rebindRevealed(false))"));
|
||||||
assert!(source.contains("schedule(entry.target, \"revealed\")"));
|
assert!(source.contains("schedule(entry.target, \"revealed\")"));
|
||||||
assert!(source.contains("window.addEventListener(\"pageshow\", restoreRevealed)"));
|
assert!(source.contains("window.addEventListener(\"pageshow\", restoreRevealed)"));
|
||||||
assert!(source.contains("revealed.delete(el)")); // req: convention/014 test
|
assert!(source.contains("revealed.delete(el)")); // req: convention/014 test
|
||||||
|
|||||||
Reference in New Issue
Block a user