@devzwo/ngx-magic-scroll 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,139 @@
1
+ # Changelog
2
+
3
+ ## Release v1.1.2 (2026-11-09)
4
+
5
+ ### Bug Fixes & Behavioral Changes
6
+
7
+ - Fix #17: defer scroll-spy and `scrollTo()` fragment updates while a user Router navigation is in flight, preserving the latest reading position without canceling guards, resolvers or lazy route loading.
8
+
9
+ ## Release v1.1.1 (2026-10-18)
10
+
11
+ ### Features
12
+
13
+ - Bundle offline Markdown documentation (`ai/usage.md`, `getting-started.md`, concepts, recipes, migrations, changelog) directly in the npm distribution package.
14
+ - Provide machine-readable AI Agent Guide (`ai/usage.md`) and dynamic `llms.txt` ecosystem index for AI coding assistants.
15
+ - Add version-specific migration guides covering `v0.0.1-rc.1 -> v0.0.1-rc.2`, `v0.0.1-rc.2 -> v1.0.0`, and `v1.0.x -> v1.1.0` with verified Angular 22 before/after code transformations.
16
+ - Export raw Markdown documentation endpoints (`.md`) across all documentation routes.
17
+ - Add automated validation scripts for package archive structure, Markdown link resolution, version consistency, and Angular `ngc` recipe/migration snippet compilation.
18
+
19
+ ### Bug Fixes & Behavioral Changes
20
+
21
+ - Fix duplicate H1 headings rendered on documentation pages in Starlight.
22
+
23
+ ### Deprecations
24
+
25
+ - None.
26
+
27
+ ### Breaking Changes
28
+
29
+ - None.
30
+
31
+ ## Release v1.1.0 (2026-10-18)
32
+
33
+ ### Features
34
+
35
+ - Add opt-in `preserveOnLayoutChange` configuration option to maintain section-relative scroll positions during asynchronous layout reflows (e.g., accordions, lazy images, form expansions).
36
+ - Preserve section-relative pixel displacement (`displacement`) across automatic layout corrections and data updates to keep the reader's exact reading position intact.
37
+ - Add accessible focus management (`focus: true`) to `scrollTo()` with temporary `tabindex="-1"` handling and completion coordination for smooth scrolling.
38
+ - Optimize layout reflow tracking using region-scoped `ResizeObserver` and `requestAnimationFrame` coalescing to prevent layout thrashing.
39
+ - Guard against scroll interruptions by deferring layout preservation during active smooth scrolling and skipping redundant sub-pixel (`< 1px`) scroll adjustments.
40
+ - Update demo application with layout preservation controls, accordion examples, accessible navigation toggles, and refreshed architecture documentation.
41
+
42
+ ### Bug Fixes & Behavioral Changes
43
+
44
+ - None.
45
+
46
+ ### Deprecations
47
+
48
+ - None.
49
+
50
+ ### Breaking Changes
51
+
52
+ - None.
53
+
54
+ ### Migration Guide
55
+
56
+ - See [Migration Guide: v1.0 to v1.1](./docs/migrations/v1.0-to-v1.1.md) for upgrade instructions and before/after code examples.
57
+
58
+ ## Release v1.0.0
59
+
60
+ ### Features
61
+
62
+ - Handle subsequent Angular router fragment navigations automatically after initial page load without requiring consumer coordination.
63
+ - Queue pending scroll targets and retry navigation via `MutationObserver` until asynchronous data and matching DOM elements are rendered.
64
+ - Tag scroll-spy navigation origins to distinguish internal fragment updates from external navigation and prevent navigation loops.
65
+ - Support browser history navigation (back/forward popstate) with scroll position restoration while preserving custom router history state.
66
+ - Support multiple anchor prefixes via `anchorPrefixes` array configuration.
67
+ - Add strict explicit anchor selection mode (`explicitOnly`) to restrict anchor resolution exclusively to `[scrollAnchor]` directives.
68
+ - Add parameter help tooltips and interactive anchor strategy toggles to the demo application.
69
+ - Integrate Mermaid architecture and lifecycle flow diagrams into the documentation.
70
+ - Add mascot branding in demo header and update favicons across docs and demo app.
71
+ - Expand test matrix and Playwright configuration with dedicated per-browser test suites.
72
+
73
+ ### Bug Fixes & Behavioral Changes
74
+
75
+ - None.
76
+
77
+ ### Deprecations
78
+
79
+ - None.
80
+
81
+ ### Breaking Changes
82
+
83
+ - None.
84
+
85
+ ### Migration Guide
86
+
87
+ - See [Migration Guide: v0.0.1-rc.2 to v1.0.0](./docs/migrations/v0.0.1-rc.2-to-1.0.0.md) for upgrade instructions and before/after code examples.
88
+
89
+ ## Pre-Release v0.0.1-rc.2
90
+
91
+ ### Features
92
+
93
+ - Track visible live sections in real time so position preservation during asynchronous data updates or resizing targets the current reading position.
94
+ - Keep the currently read section active in the scroll-spy until the next section begins, prioritizing fully contained sections within the reading area.
95
+ - Add `bottomOffset` and `activationThreshold` configuration options for granular control over active reading boundaries and trigger lines.
96
+ - Add active zone overlay in demo application to visualize active boundaries and exclusion zones.
97
+
98
+ ### Bug Fixes & Behavioral Changes
99
+
100
+ - Cancel pending initial scroll restoration on user interaction (wheel, touch, navigation keys) prior to data readiness, while ignoring inputs in editable elements.
101
+ - Ensure programmatic `scrollTo()` continues to work as expected after user interruption.
102
+ - Improve server-side rendering (SSR) resilience when `ResizeObserver` or `defaultView` is not available.
103
+
104
+ ### Deprecations
105
+
106
+ - None.
107
+
108
+ ### Breaking Changes
109
+
110
+ - None.
111
+
112
+ ### Migration Guide
113
+
114
+ - See [Migration Guide: v0.0.1-rc.1 to v0.0.1-rc.2](./docs/migrations/v0.0.1-rc.1-to-0.0.1-rc.2.md) for upgrade instructions and before/after code examples.
115
+
116
+ ## Pre-Release v0.0.1-rc.1
117
+
118
+ The first public release preview. The public API is under review.
119
+
120
+ ### Features
121
+
122
+ - Anchor scrolling through `magicScroll`, with automatic scroll-container detection, configurable offsets and header measurement.
123
+ - URL synchronization and initial scroll-position restoration.
124
+ - Scroll-position preservation when data or container dimensions change.
125
+ - Inheritable defaults through `provideMagicScroll`, with overrides per region or action and separate behavior for restoration and user interaction.
126
+ - Support for signals, plain input data, Angular resources and provider-neutral data sources.
127
+ - Optional anchor markers, explicit IDs through `scrollAnchor` and independent scoping for nested scroll regions.
128
+
129
+ ### Bug Fixes & Behavioral Changes
130
+
131
+ - None.
132
+
133
+ ### Deprecations
134
+
135
+ - None.
136
+
137
+ ### Breaking Changes
138
+
139
+ - None.
package/README.md CHANGED
@@ -73,6 +73,8 @@ Child providers inherit unspecified settings. Override an individual region with
73
73
 
74
74
  Navigation, reload and the first successful asynchronous load use `behavior.restoration`. Later data changes and resize/header corrections adjust instantly to stabilize the layout. `restoreOnDataChange: false` and `preserveOnResize: false` opt out of data corrections and resize/header corrections respectively. Visibility rules are: `none`, `top`, `full` and `always` (at least one edge visible).
75
75
 
76
+ Scroll-spy and `scrollTo()` URL updates wait while a user Router navigation is in flight, preventing the fragment write from canceling pending guards, resolvers or lazy loading. If that navigation is canceled, the latest fragment is restored; after a successful navigation it is written only when the route path stays the same and no new fragment was chosen.
77
+
76
78
  ## Anchor selection and explicit anchors
77
79
 
78
80
  By default (`anchorSelection: 'all'`), all descendant IDs participate unless constrained by `anchorPrefixes`.
@@ -117,11 +119,11 @@ container manually. The existing scroll implementation remains internal to the f
117
119
 
118
120
  ## Demo and verification
119
121
 
120
- Four routed examples demonstrate document synchronization, asynchronous navigation restoration, Material `mat-drawer-container` with pagination, and master–detail document scrolling with a measured sticky header. Options are presented inside these demos.
122
+ Routed examples demonstrate document synchronization, asynchronous navigation restoration, Material `mat-drawer-container` with pagination, master–detail document scrolling with a measured sticky header, and delayed lazy loading. Options are presented inside these demos.
121
123
 
122
- `pnpm test:ci` enforces 100% statements, branches, functions and lines per library implementation file. The existing 25 E2E scenarios remain unchanged after the migration to the facade and pass in Chromium, Firefox and WebKit (75 runs).
124
+ `pnpm test:ci` enforces 100% statements, branches, functions and lines per library implementation file. The 27 E2E scenarios cover fragment synchronization, in-flight navigation protection, query parameter preservation, asynchronous restoration, browser back, reload, resize, drawer toggling and pagination.
123
125
 
124
- [Documentation and API](https://devzwo.github.io/ngx-magic-scroll/) · [Source and issues](https://github.com/devZWO/ngx-magic-scroll)
126
+ [Documentation and API](https://devzwo.github.io/ngx-magic-scroll/) · [AI Agent Guide](ai/usage.md) · [llms.txt Index](https://devzwo.github.io/ngx-magic-scroll/llms.txt) · [Changelog](CHANGELOG.md) · [Source and issues](https://github.com/devZWO/ngx-magic-scroll)
125
127
 
126
128
  MIT, copyright 2026 devZWO GmbH.
127
129
 
package/ai/usage.md ADDED
@@ -0,0 +1,97 @@
1
+ ---
2
+ title: AI Agent Integration Guide
3
+ description: Concise reference and integration rules for AI coding assistants using @devzwo/ngx-magic-scroll.
4
+ ---
5
+
6
+ This guide provides compact, high-density reference instructions for AI agents and automated code generators integrating or upgrading `@devzwo/ngx-magic-scroll`.
7
+
8
+ ## Package Metadata & Prerequisites
9
+
10
+ - **Package**: `@devzwo/ngx-magic-scroll`
11
+ - **Framework Support**: Angular `^22.2.0`, RxJS `^7.8.0`
12
+ - **Prerequisites**: Angular Router enabled (`provideRouter(...)` in `app.config.ts`) for fragment synchronization and history restoration.
13
+ - **Conventions**: Angular standalone components (do not set `standalone: true` in `@Component`), signal inputs/outputs, and native control flow (`@if`, `@for`).
14
+
15
+ ## Minimal Integration Example
16
+
17
+ <!-- recipe-check: ai-usage-minimal.ts -->
18
+
19
+ ```ts
20
+ import { Component } from '@angular/core';
21
+ import {
22
+ MagicScrollDirective,
23
+ ScrollAnchorDirective,
24
+ provideMagicScroll,
25
+ } from '@devzwo/ngx-magic-scroll';
26
+
27
+ @Component({
28
+ selector: 'app-agent-doc-sample',
29
+ imports: [MagicScrollDirective, ScrollAnchorDirective],
30
+ providers: [provideMagicScroll()],
31
+ template: `
32
+ <nav aria-label="Page Sections">
33
+ <button (click)="scroll.scrollTo('intro')">Intro</button>
34
+ <button (click)="scroll.scrollTo('details', { focus: true })">Details</button>
35
+ </nav>
36
+
37
+ <main magicScroll #scroll="magicScroll">
38
+ <section id="intro">
39
+ <h2>Introduction</h2>
40
+ </section>
41
+ <section id="details" scrollAnchor>
42
+ <h2>Details</h2>
43
+ </section>
44
+ </main>
45
+ `,
46
+ })
47
+ export class AgentDocSampleComponent {}
48
+ ```
49
+
50
+ ## Recipe Decision Guide
51
+
52
+ Select the appropriate recipe and configuration based on application requirements:
53
+
54
+ | Requirement / Scenario | Recommended Recipe | Key Configuration / API |
55
+ | :-------------------------------- | :---------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------- |
56
+ | **Static Navigation & Spy** | [Anchor Navigation](../docs/recipes/scroll-to-anchor.md) | `[magicScroll]`, `#scroll="magicScroll"`, `scroll.activeAnchor()`, `scroll.scrollTo(id, { focus: true })` |
57
+ | **Async Data (Angular Resource)** | [rxResource Integration](../docs/recipes/rx-resource.md) | `[scrollSource]="resource"`, `[scrollAnchor]="item.id"` |
58
+ | **Async Data (Signal State)** | [NgRx Signal Store](../docs/recipes/ngrx-signal-store.md) / [TanStack Query](../docs/recipes/tanstack-query.md) | `[scrollSource]="store.items"` or `[scrollSource]="query.data"` |
59
+ | **Appended / Paginated Lists** | [Paginated Lists](../docs/recipes/paginated-lists.md) | `[scrollSource]="items()"`, automatic top-anchor retention during append |
60
+ | **Cross-Route Return** | [Return to List](../docs/recipes/return-to-list.md) | Browser history popstate auto-restoration |
61
+ | **Sticky Header / Layout Offset** | [Sticky Master-Detail](../docs/recipes/sticky-master-detail.md) | `[headerOffset]="measuredHeight()"` or `[topOffset]="64"` |
62
+ | **Drawer / Nested Container** | [Material Drawer](../docs/recipes/material-drawer.md) | `[magicScroll]` directly on the scrolling overflow container |
63
+ | **Dynamic Reflow / Accordion** | [Position Preservation](../docs/concepts/position-preservation.md) | `provideMagicScroll({ preserveOnLayoutChange: true })` |
64
+
65
+ ## Core Integration Rules & Common Pitfalls
66
+
67
+ ### 1. `scrollSource` Reactivity
68
+
69
+ - **Rule**: Pass live reactive signals or resource references to `[scrollSource]`, never evaluated static values in non-reactive contexts.
70
+ - **Pitfall**: Passing `[scrollSource]="resource.value()"` creates a static snapshot if not evaluated inside a reactive signal context; pass `[scrollSource]="resource"` directly when using `ResourceRef` or `rxResource`.
71
+ - **Readiness**: `undefined` indicates loading. Position restoration and fragment navigation wait until data resolves to a defined value (array, object, or `null`).
72
+
73
+ ### 2. Anchor Scoping & ID Uniqueness
74
+
75
+ - **Containment**: Every anchor element belongs strictly to its closest parent `[magicScroll]` region. A parent scroll instance cannot scroll to anchors inside a nested scroll region.
76
+ - **Stability**: Anchor `id` attributes must be globally unique across the document and remain stable between renders.
77
+ - **Selective Anchors**: Use `[anchorPrefixes]="['section-']"` or `explicitOnly: true` with `[scrollAnchor]` to avoid treating arbitrary DOM IDs as scroll anchors.
78
+
79
+ ### 3. Accessible Focus Transfer
80
+
81
+ - When triggering navigation from button clicks or shortcuts, pass `{ focus: true }` to `scrollTo(id, { focus: true })`.
82
+ - Non-focusable elements automatically receive temporary `tabindex="-1"`, cleaned up on blur or directive destruction.
83
+ - Smooth scrolling coordinates focus transfer on scroll completion (`scrollend`).
84
+
85
+ ### 4. Layout Preservation During Reflows
86
+
87
+ - Enable `preserveOnLayoutChange: true` to prevent content jumps when accordions toggle, images load, or forms expand above the user's viewport.
88
+ - The library tracks section-relative displacement to preserve the exact reading position without layout thrashing.
89
+
90
+ ## Upgrade & Migration Workflow
91
+
92
+ When updating `@devzwo/ngx-magic-scroll` in an existing project:
93
+
94
+ 1. **Verify Installed Version**: Inspect `package.json` for `@devzwo/ngx-magic-scroll`.
95
+ 2. **Review Changelog**: Check [Changelog](../CHANGELOG.md) for breaking changes, new features, and deprecation notices.
96
+ 3. **Check Version Migration Guides**: Consult [Migration Guides](../docs/migrations/v1.0-to-v1.1.md) for step-by-step code transformations and before/after snippets.
97
+ 4. **Run Project Validation**: Verify template compilation and unit tests with `ng build` / `pnpm test`.
@@ -0,0 +1,129 @@
1
+ ---
2
+ title: Scroll regions and anchor identity
3
+ description: Understand anchor selection, region ownership and the container that actually scrolls.
4
+ sidebar:
5
+ order: 1
6
+ ---
7
+
8
+ An anchor identifies a section of content independently of its pixel position. A scroll region defines which anchors `magicScroll` manages; the scroll container determines which viewport moves. Keeping these three roles separate helps you design layouts that remain navigable as content changes.
9
+
10
+ ## Stable IDs identify content
11
+
12
+ The URL fragment `#project-12` refers to the element with ID `project-12`. On restoration, the library finds that element's current position. Inserting content above it can change its coordinates without changing what the link means.
13
+
14
+ Use IDs derived from the identity of your content, such as a project ID. An array index is unsuitable when sorting, filtering or inserting items can make it refer to a different item. IDs must be unique across the entire document, including across separate regions.
15
+
16
+ Restoration returns to an anchor's position with the configured top offset. It does not remember the exact pixel distance the user had scrolled into that section.
17
+
18
+ ## Configuring activation boundaries and scroll offsets
19
+
20
+ `magicScroll` computes both scroll alignment and active section detection using layout and offset configuration:
21
+
22
+ - **`headerOffset`**: Static spacing in pixels (e.g. `headerOffset: 24`), adding visual breathing room above section headings.
23
+ - **`headerSelector`**: CSS selector for a fixed or sticky navigation bar (e.g. `headerSelector: '.app-header'`). Its dynamically measured height is automatically added to the boundary offset so obscured elements do not trigger activation or get covered during scroll restoration.
24
+ - **`bottomOffset`**: Static bottom spacing in pixels (e.g. `bottomOffset: 40`), accounting for sticky footers, floating action bars, or aesthetic margins.
25
+ - **`activationThreshold`**: Relative focal trigger threshold from `0.0` (top boundary) to `1.0` (bottom boundary), defining the trigger line (`topBoundary + (bottomBoundary - topBoundary) * activationThreshold`) where sections transition to active status.
26
+
27
+ Active section detection evaluates elements within the visible reading area between the effective top boundary (`containerTop + headerHeight + headerOffset`) and bottom boundary (`(containerBottom | viewportHeight) - bottomOffset`), prioritizing fully visible sections before falling back to reading-position tracking for tall content relative to the configured `activationThreshold`.
28
+
29
+ ```mermaid
30
+ flowchart TB
31
+ subgraph Viewport ["Scroll Viewport / Container"]
32
+ direction TB
33
+ TopZone["<b>Top Exclusion Area</b><br/><i>[0 .. topBoundary]</i><br/>Sticky Header (headerSelector) + headerOffset"]
34
+ ReadingZone["<b>Active Reading Band</b><br/><i>[topBoundary .. bottomBoundary]</i><br/>Full containment evaluation area"]
35
+ TriggerLine["<b>─── Dynamic Trigger Line ───</b><br/><i>topBoundary + (ReadingBand * activationThreshold)</i>"]
36
+ BottomZone["<b>Bottom Exclusion Area</b><br/><i>[bottomBoundary .. containerBottom]</i><br/>Sticky Footer / Padding (bottomOffset)"]
37
+
38
+ TopZone --- ReadingZone
39
+ ReadingZone --- TriggerLine
40
+ TriggerLine --- BottomZone
41
+ end
42
+ ```
43
+
44
+ You can configure offsets globally in application providers or locally per scroll region:
45
+
46
+ ```ts
47
+ // Global configuration
48
+ provideMagicScroll({
49
+ headerSelector: '.app-header',
50
+ headerOffset: 24,
51
+ bottomOffset: 48,
52
+ activationThreshold: 0.3,
53
+ });
54
+ ```
55
+
56
+ ```html
57
+ <!-- Per-region override -->
58
+ <div magicScroll [scrollOptions]="{ headerOffset: 16, bottomOffset: 32, activationThreshold: 0.5 }">
59
+ ...
60
+ </div>
61
+ ```
62
+
63
+ ## A region selects its anchors
64
+
65
+ By default (`anchorSelection: 'all'`), all descendant elements with non-empty IDs participate. This includes IDs on headings or form controls, so a region can contain more anchors than your section navigation exposes.
66
+
67
+ Use `anchorPrefixes` to restrict automatic selection, and `scrollAnchor` to explicitly include an element even when its ID does not match the prefix:
68
+
69
+ ```html
70
+ <div magicScroll [scrollOptions]="{ anchorPrefixes: ['project-', 'milestone-'] }">
71
+ <section id="overview" scrollAnchor>Overview...</section>
72
+ <section id="project-planning">
73
+ <h2 id="planning-heading">Planning</h2>
74
+ </section>
75
+ <section id="milestone-alpha">Alpha release...</section>
76
+ <section [scrollAnchor]="'project-delivery'">Delivery...</section>
77
+ </div>
78
+ ```
79
+
80
+ The anchors here are `overview`, `project-planning`, `milestone-alpha` and `project-delivery`. The heading's ID (`planning-heading`) is excluded because it matches neither prefix. The bound marker `[scrollAnchor]` also assigns the element's ID; import `ScrollAnchorDirective` when using either marker form.
81
+
82
+ ### Multiple prefixes and strict explicit selection
83
+
84
+ `magicScroll` provides versatile selection strategies:
85
+
86
+ - **`anchorPrefixes`**: Specify an array of prefixes (e.g. `['project-', 'milestone-']`). Any descendant element whose ID begins with any of these prefixes participates, along with explicit `scrollAnchor` elements. Passing an empty array `anchorPrefixes: []` disables all prefix matching, allowing only explicit `scrollAnchor` markers to participate.
87
+ - **`anchorSelection: 'explicit'`**: Enables strict explicit-only mode. In this mode, **only** elements explicitly marked with `scrollAnchor` or `[scrollAnchor]` participate in scroll-spy, initial restoration and `scrollTo()`. All other IDs—including auto-generated IDs from component libraries such as Angular Material (`mat-input-*`, `cdk-describedby-*`) or nested form controls—are strictly ignored, even if they match configured prefixes.
88
+ - **`anchorSelection: 'all'` (default)**: Descendant elements matching any configured `anchorPrefixes` (or all descendant IDs if no prefix is set) as well as explicit `scrollAnchor` markers participate.
89
+
90
+ ```ts
91
+ // Example: Strict explicit selection across the application
92
+ provideMagicScroll({
93
+ anchorSelection: 'explicit',
94
+ });
95
+ ```
96
+
97
+ ```html
98
+ <!-- Per-region override supporting multiple prefixes -->
99
+ <div magicScroll [scrollOptions]="{ anchorPrefixes: ['doc-', 'appendix-'] }">...</div>
100
+ ```
101
+
102
+ The same selection applies to scroll-spy tracking, initial restoration and `scrollTo()`. An element can exist in the DOM and still be unavailable to this region because it is excluded by the selection rules. Setting `anchorPrefixes: ['']` on a region clears an inherited prefix and includes all its descendant IDs again.
103
+
104
+ ## Each anchor belongs to its closest region
105
+
106
+ With nested `magicScroll` regions, each anchor belongs to the closest region containing it. The outer region excludes anchors inside the inner region, even if their IDs match its prefix or have explicit markers.
107
+
108
+ For example, an outer article can manage its section headings while an inner list manages its items. Calling the outer region's `scrollTo()` with an inner item ID returns `false`. Call the directive instance that owns the target instead.
109
+
110
+ Region ownership separates target selection. It does not create independent URL state: a route has one fragment, and regions connected to that route share it. Plan which region should control shareable navigation when multiple regions are active.
111
+
112
+ ## The region and scroll container can be different elements
113
+
114
+ `magicScroll` chooses the container from the layout:
115
+
116
+ 1. The region host itself, if its computed vertical overflow is `auto` or `scroll`.
117
+ 2. Otherwise, the closest ancestor with either overflow value.
118
+ 3. Otherwise, the document.
119
+
120
+ CSS supplies the height constraints and overflow that make that container scroll. Adding `magicScroll` alone does not create a scrolling viewport.
121
+
122
+ A detail region can therefore scroll with the document while a companion sidebar scrolls independently. If the sidebar sits outside the region, its IDs do not become detail anchors. Conversely, placing the region inside a constrained drawer makes the drawer content the scroll container.
123
+
124
+ ## Related documentation
125
+
126
+ - [URL state and navigation](./url-state-and-navigation.md) explains how selected anchors become URL state.
127
+ - [Anchor navigation](../recipes/scroll-to-anchor.md) implements prefix selection, markers and navigation highlighting.
128
+ - [Angular Material Drawer](../recipes/material-drawer.md) shows a constrained scroll container.
129
+ - [MagicScrollDirective](https://devzwo.github.io/ngx-magic-scroll/api/classes/magicscrolldirective/) and [ScrollAnchorDirective](https://devzwo.github.io/ngx-magic-scroll/api/classes/scrollanchordirective/) document the directive APIs.
@@ -0,0 +1,102 @@
1
+ ---
2
+ title: Data readiness and restoration
3
+ description: Understand when initial restoration runs and what your application must render first.
4
+ sidebar:
5
+ order: 3
6
+ ---
7
+
8
+ Initial restoration needs both an anchor identifier and a rendered target. `scrollSource` coordinates restoration with your application's data state. It observes readiness; your application loads the data and renders the elements that the fragment identifies.
9
+
10
+ ## The initial restoration sequence
11
+
12
+ A region follows this sequence:
13
+
14
+ 1. Capture the route fragment when the directive is created.
15
+ 2. Wait for its source to be ready, if a source is supplied.
16
+ 3. After rendering, attempt to scroll to the captured anchor.
17
+ 4. Finish initial restoration, whether or not the target was found.
18
+
19
+ Without `scrollSource`, the first rendered DOM is considered ready. This suits static content and data already available from a router resolver.
20
+
21
+ For asynchronous content, bind the signal or resource that governs the rendered anchors. Pass a signal itself to `scrollSource`, and read its value where you render the collection. This lets readiness track the same data that produces the targets.
22
+
23
+ ## Readiness is a data contract
24
+
25
+ | Source state | Initial restoration |
26
+ | ------------------------------------------------------------ | ------------------------------ |
27
+ | No `scrollSource` supplied | Attempt after the first render |
28
+ | Plain value or signal yielding `null` or `undefined` | Wait |
29
+ | Plain value or signal yielding `[]`, `0`, `false` or `''` | Ready |
30
+ | Resource or neutral source reporting `isLoading()` as `true` | Wait |
31
+ | Non-loading resource with an available, non-null value | Ready |
32
+ | Non-loading neutral source with non-null `data()` | Ready |
33
+
34
+ Angular resources expose `isLoading()`, `hasValue()` and `value()`. A resource with no value, including an error state without a value, cannot trigger restoration. The application owns loading indicators, error presentation and retries.
35
+
36
+ Readiness does not inspect whether the requested anchor exists. An empty collection is ready even though it contains no item anchors. Likewise, data can be ready while a separate conditional view still hides the target. Expose a source that reflects all prerequisites for rendering the intended content.
37
+
38
+ For signal forms, use the field's value signal, such as `form().value`, rather than the field tree itself. Source-specific adapters and types are covered by the recipes and API below.
39
+
40
+ ## The first ready render is decisive
41
+
42
+ Initial restoration is attempted once per directive instance. If the fragment is absent or its target is missing or ineligible at the first ready render, the attempt does not scroll or rewrite the URL. It is not automatically retried as initial restoration when that target appears later.
43
+
44
+ ### Intervening user interaction cancels restoration
45
+
46
+ If the user interacts with the page or scroll container before data readiness arrives—such as scrolling via mouse wheel, touch gestures, or navigation keys (`ArrowUp`, `ArrowDown`, `PageUp`, `PageDown`, `Space`, `Home`, `End`)—the pending initial restoration is cancelled. This prevents delayed data loading from abruptly overriding the user's conscious navigation.
47
+
48
+ Typing inside editable form controls (`input`, `textarea`, `select`, or `contenteditable` elements) is safely ignored and does not cancel restoration. Subsequent explicit navigation using `scrollTo()` continues to function normally without restriction.
49
+
50
+ ```mermaid
51
+ graph TD
52
+ subgraph User & Browser Events
53
+ UserScroll[User Wheel / Touch / Keyboard]
54
+ BrowserScroll[Scroll Event]
55
+ DataUpdate[Data Ready / Resize Event]
56
+ end
57
+
58
+ subgraph MagicScrollDirective
59
+ IntentDetector{User Intent?}
60
+ LiveTracker[RAF Live Section Calculator]
61
+ DebounceTimer[Debounce Timer]
62
+ PreserveHandler[preserve Realignment]
63
+ InitialRestore[Initial Restoration]
64
+ end
65
+
66
+ subgraph State
67
+ LiveAnchor[liveAnchor Signal]
68
+ URLFragment[URL Route Fragment]
69
+ end
70
+
71
+ UserScroll -->|Intervention| IntentDetector
72
+ IntentDetector -->|Cancel pending| InitialRestore
73
+ BrowserScroll --> LiveTracker
74
+ LiveTracker --> LiveAnchor
75
+ LiveTracker --> DebounceTimer
76
+ DebounceTimer --> URLFragment
77
+ DataUpdate --> PreserveHandler
78
+ LiveAnchor -->|Source of Truth| PreserveHandler
79
+ ```
80
+
81
+ Later ready source changes can still trigger position preservation using the current active section. That is a separate behavior, described in [Position preservation and layout changes](./position-preservation.md), and should not be used as a strategy for loading a missing initial target.
82
+
83
+ An explicit `scrollTo()` call also requires an existing target. It does not wait for `scrollSource` to become ready.
84
+
85
+ ## Restore application state before declaring readiness
86
+
87
+ Consider `/projects?count=16#project-12`:
88
+
89
+ - The fragment identifies project 12.
90
+ - The query parameter tells the application to load the first 16 items.
91
+ - The source becomes ready after that required range is available.
92
+ - The rendered region can then restore project 12.
93
+
94
+ If only the first eight items are exposed as ready, the initial attempt cannot find project 12. The library does not fetch additional pages, restore filters or materialize virtualized items.
95
+
96
+ Restore the relevant filters, ordering and data range in your application before readiness. If an item was deleted or filtered out, choose an application fallback rather than expecting automatic target discovery.
97
+
98
+ ## Related documentation
99
+
100
+ - [Paginated lists](../recipes/paginated-lists.md) restores the loaded range before the anchor.
101
+ - [Angular rxResource](../recipes/rx-resource.md), [NgRx Signal Store](../recipes/ngrx-signal-store.md) and [TanStack Query](../recipes/tanstack-query.md) connect different data owners.
102
+ - [ScrollSource](https://devzwo.github.io/ngx-magic-scroll/api/type-aliases/scrollsource/), [ScrollResource](https://devzwo.github.io/ngx-magic-scroll/api/interfaces/scrollresource/) and [ScrollDataSource](https://devzwo.github.io/ngx-magic-scroll/api/interfaces/scrolldatasource/) define supported source contracts.
@@ -0,0 +1,149 @@
1
+ ---
2
+ title: Position preservation and layout changes
3
+ description: Understand how later data updates, resizing and header changes preserve section-relative reading positions.
4
+ sidebar:
5
+ order: 4
6
+ ---
7
+
8
+ After initial restoration, content and layout can shift dynamically due to asynchronous data insertions, container resizing, or sticky header height changes. `ngx-magic-scroll` implements an **anchor-relative pixel displacement policy** that preserves the active section's pixel displacement, preventing disruptive jumps back to section headings during automatic reflows.
9
+
10
+ ## Different causes have different policies
11
+
12
+ | Cause | Target | Displacement policy | Default behavior |
13
+ | ---------------------------------------------- | ---------------------------- | -------------------------------------------------------- | --------------------------------------------------------------- |
14
+ | Initial ready render | Fragment from URL snapshot | Aligned to heading (`displacement = 0`) | Restore once, instantly (unless user cancelled via interaction) |
15
+ | Explicit `scrollTo()` | Requested anchor | Aligned to heading (`displacement = 0`) | Scroll smoothly |
16
+ | External router fragment change | Route destination anchor | Aligned to heading (`displacement = 0`) | Restore instantly |
17
+ | Later ready source change | Current live visible section | Preserves saved pixel displacement (`savedDisplacement`) | Correct instantly without resetting to section top |
18
+ | Window resize or measured header-height change | Current live visible section | Preserves saved pixel displacement (`savedDisplacement`) | Realign instantly to maintain section pixel offset |
19
+ | Region content reflow (layout expansion/image) | Current live visible section | Preserves saved pixel displacement (`savedDisplacement`) | Opt-in (`preserveOnLayoutChange: true`); coalesced via rAF |
20
+
21
+ The distinction between initial URL fragments, live visible sections, and intentional user navigation ensures seamless reading continuity. When reading halfway through a 1200px card, layout reflows preserve the section's relative pixel position rather than resetting to the card header.
22
+
23
+ ### Live section tracking vs URL debouncing
24
+
25
+ While scrolling, the active visible section geometry and anchor-relative pixel displacement are captured in real time, whereas updates to the browser's URL router are debounced by `debounceTime` (default 500ms) to avoid cluttering browser history during continuous motion. When automatic realignment or data updates occur during this debounce interval, `ngx-magic-scroll` preserves the real-time visible section and displacement rather than falling back to a stale URL anchor.
26
+
27
+ ```mermaid
28
+ sequenceDiagram
29
+ autonumber
30
+ actor User
31
+ participant Viewport as Scroll Container
32
+ participant Directive as MagicScrollDirective
33
+ participant Provider as NearestAnchorProvider
34
+ participant Service as ScrollService
35
+
36
+ Note over User,Directive: User scrolls into long section
37
+ User->>Viewport: Scroll to midpoint of long card
38
+ Viewport->>Directive: onScroll event
39
+ Directive->>Provider: calculateNearestAnchor()
40
+ Provider-->>Directive: liveAnchor ('card-1')
41
+ Directive->>Directive: Capture savedDisplacement = anchorRect.top - topBoundary
42
+
43
+ Note over Viewport,Directive: Automatic layout reflow occurs
44
+ alt Data prepended / Container resize / Header resize
45
+ Viewport->>Directive: Data update / ResizeObserver trigger
46
+ Directive->>Directive: preserve() called
47
+ Directive->>Directive: Compute delta = (currentAnchorRect.top - topBoundary) - savedDisplacement
48
+ Directive->>Service: Adjust scrollable.scrollTop by delta (clamped to bounds)
49
+ Service->>Viewport: scrollable.scrollTo(targetScrollTop, instant)
50
+ Note over User,Viewport: Section displacement preserved!
51
+ end
52
+ ```
53
+
54
+ ## Section-relative displacement mechanics
55
+
56
+ ### 1. Displacement capture during user scrolling
57
+
58
+ As the user scrolls through the container, `onScroll` computes the reading boundary and measures the active section's top offset:
59
+
60
+ ```text
61
+ topBoundary = containerTop + measuredHeaderHeight + headerOffset
62
+ savedDisplacement = activeAnchorElement.getBoundingClientRect().top - topBoundary
63
+ ```
64
+
65
+ For example, when reading 400px deep into a card, `savedDisplacement` is `-400px`.
66
+
67
+ ### 2. Delta calculation and native scroll anchoring compatibility
68
+
69
+ When automatic reflow occurs (e.g. items prepended above the section, or sticky header expanded):
70
+
71
+ ```text
72
+ currentDisplacement = currentAnchorElement.getBoundingClientRect().top - topBoundary
73
+ delta = currentDisplacement - savedDisplacement
74
+ targetScrollTop = clamp(currentScrollTop + delta, 0, maxScrollTop)
75
+ ```
76
+
77
+ - **Without browser scroll anchoring:** If content is inserted above the section, `currentDisplacement` changes by the height of the prepended items. `delta` equals the shift and `scrollTop` is adjusted instantly, keeping the section visually locked under the reader's view.
78
+ - **With native browser scroll anchoring (`overflow-anchor: auto`):** If the browser has already compensated `scrollTop` for inserted content, `currentDisplacement` remains equal to `savedDisplacement`. `delta` computes to `0`, inherently preventing double-compensation without browser-sniffing heuristics.
79
+ - **Intra-section limitation:** Anchor-relative displacement preserves the active section anchor's pixel offset relative to the reading boundary (`savedDisplacement`). It does not track intra-section elements or percentage progress through a section. Consequently, when text wraps or content changes _inside_ the active section itself, the exact paragraph or sentence being read may still move; retaining a specific paragraph across internal changes requires finer-grained child anchors.
80
+
81
+ ### 3. Reset policy for deliberate navigation
82
+
83
+ Explicit programmatic calls (`scrollTo('card-2')`) and external router fragment navigations reset `savedDisplacement` to `0`. Intentional navigation always aligns the destination section's heading cleanly to the configured top offset.
84
+
85
+ ### 4. Deterministic element removal handling
86
+
87
+ If the active section element is removed from the DOM during a data change or layout update, `preserve()` gracefully skips realignment and maintains the current viewport position without arbitrary jumps.
88
+
89
+ ## Region content reflow with preserveOnLayoutChange
90
+
91
+ In dynamic web applications, presentation-level elements within a scroll region frequently change dimensions without any updates to the fetched data source (`scrollSource`). Common examples include:
92
+
93
+ - Expanding an accordion or detail disclosure panel above the reader's active section.
94
+ - Asynchronous image loads settling to their natural height without pre-set aspect ratios.
95
+ - Conditional validation messages or interactive preview cards appearing dynamically.
96
+
97
+ When `preserveOnLayoutChange: true` is enabled, `MagicScrollDirective` attaches a region-scoped `ResizeObserver` to the host element. Layout shifts are coalesced per animation frame (`requestAnimationFrame`) to eliminate layout thrashing and feedback loops.
98
+
99
+ ```mermaid
100
+ graph TD
101
+ A[Region Content Reflow / Image Loaded / Accordion Expanded] -->|ResizeObserver Callback| B{preserveOnLayoutChange Enabled?}
102
+ B -->|No| C[Ignore Reflow]
103
+ B -->|Yes| D{Initialized & Not Interrupted?}
104
+ D -->|No / User Scrolling| E[Suppress Realignment]
105
+ D -->|Yes| F{rAF Already Queued?}
106
+ F -->|Yes| G[Coalesce into Existing Frame]
107
+ F -->|No| H[Queue requestAnimationFrame]
108
+ H --> I[Execute preserve()]
109
+ I --> J[Calculate Delta against liveAnchor & savedDisplacement]
110
+ J --> K{Delta === 0 or Anchored by Browser?}
111
+ K -->|Yes| L[No-op: Native overflow-anchor already compensated]
112
+ K -->|No| M[Scrollable.scrollTo: Adjust scrollTop instantly]
113
+ ```
114
+
115
+ ### Supported scenarios vs unsupported modifications
116
+
117
+ - **Supported (Dimension Changes)**: Any internal DOM expansion or contraction that changes box dimensions (such as toggling accordions, inserting child cards, or loading images) alters the region's layout and triggers instant, seamless realignment.
118
+ - **Unsupported (Position-Only Shifts)**: Pure CSS coordinate changes (such as `transform: translateY(...)` or CSS `top` transitions) that do not alter the box geometry or layout dimensions of elements in the DOM cannot be observed by `ResizeObserver`.
119
+ - **User Gesture Priority**: If the reader is actively scrolling via touch gestures, wheel events, or navigation keys, automated layout realignments yield immediately to user navigation.
120
+
121
+ ## Header measurement defines the top offset
122
+
123
+ The effective top offset boundary is:
124
+
125
+ ```text
126
+ measured header height + headerOffset
127
+ ```
128
+
129
+ With no configured or matching header, the measured height is zero and `headerOffset` supplies the entire offset. With a header, it adds a gap below that header. Use a `headerSelector` that uniquely identifies the intended element; its height is measured and observed automatically via `ResizeObserver`.
130
+
131
+ `offset()` exposes the sum, and `headerHeight()` exposes the measurement alone. A sticky companion panel can use the same sum for its CSS `top` so navigation and scroll alignment share one boundary.
132
+
133
+ A per-call `topOffset` replaces the entire effective offset for that action. It is not an additional gap. Viewport boundary clamping ensures scroll targets are bounded within `[0, scrollHeight - clientHeight]`.
134
+
135
+ ## Choose a policy for your layout
136
+
137
+ Use `restoreOnDataChange: false` when data updates should let the viewport move naturally without anchor preservation. Use `preserveOnResize: false` when automatic realignment during resizing or header changes is unwanted. Disabling layout corrections still leaves the measured header height and effective offset available.
138
+
139
+ Initial restoration and explicit interactions have separate behavior defaults: `behavior.restoration` is `instant`, and `behavior.interaction` is `smooth`. Automatic data and layout corrections remain instant.
140
+
141
+ Provider defaults are inherited through Angular's injector hierarchy. Region `scrollOptions` override those defaults, and supported per-call options override the region for one `scrollTo()` action.
142
+
143
+ ## Related documentation
144
+
145
+ - [Data readiness and restoration](./data-readiness-and-restoration.md) explains initial restoration, user cancellation, and the event lifecycle diagram.
146
+ - [Sticky master–detail](../recipes/sticky-master-detail.md) shares the measured offset with a sticky sidebar.
147
+ - [Paginated lists](../recipes/paginated-lists.md) shows data updates and optional correction disabling.
148
+ - [MagicScrollOptions](https://devzwo.github.io/ngx-magic-scroll/api/interfaces/magicscrolloptions/) documents configuration defaults and visibility rules.
149
+ - [MagicScrollToOptions](https://devzwo.github.io/ngx-magic-scroll/api/type-aliases/magicscrolltooptions/) documents per-action overrides.