@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 +139 -0
- package/README.md +5 -3
- package/ai/usage.md +97 -0
- package/docs/concepts/anchors.md +129 -0
- package/docs/concepts/data-readiness-and-restoration.md +102 -0
- package/docs/concepts/position-preservation.md +149 -0
- package/docs/concepts/url-state-and-navigation.md +152 -0
- package/docs/getting-started.md +144 -0
- package/docs/migrations/v0.0.1-rc.1-to-0.0.1-rc.2.md +177 -0
- package/docs/migrations/v0.0.1-rc.2-to-1.0.0.md +176 -0
- package/docs/migrations/v1.0-to-v1.1.md +145 -0
- package/docs/recipes/material-drawer.md +99 -0
- package/docs/recipes/ngrx-signal-store.md +132 -0
- package/docs/recipes/paginated-lists.md +206 -0
- package/docs/recipes/return-to-list.md +110 -0
- package/docs/recipes/rx-resource.md +87 -0
- package/docs/recipes/scroll-to-anchor.md +148 -0
- package/docs/recipes/sticky-master-detail.md +138 -0
- package/docs/recipes/tanstack-query.md +96 -0
- package/fesm2022/devzwo-ngx-magic-scroll.mjs +80 -25
- package/fesm2022/devzwo-ngx-magic-scroll.mjs.map +1 -1
- package/package.json +1 -1
- package/types/devzwo-ngx-magic-scroll.d.ts +3 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: URL state and navigation
|
|
3
|
+
description: Understand how scrolling, explicit navigation and restoration use the route fragment.
|
|
4
|
+
sidebar:
|
|
5
|
+
order: 2
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
The route fragment is the shared anchor identifier used by scroll tracking, explicit navigation and restoration. These operations use it at different times: scrolling records an anchor, `scrollTo()` requests movement, and a newly created region captures the fragment for initial restoration.
|
|
9
|
+
|
|
10
|
+
## Scrolling records an anchor
|
|
11
|
+
|
|
12
|
+
After scrolling settles, the region determines which section the user is currently reading based on an evaluated visible reading area and a hybrid evaluation model:
|
|
13
|
+
|
|
14
|
+
- **Top boundary (`topBoundary`)**: `containerTop + headerHeight + headerOffset`
|
|
15
|
+
- For document scrolling, `containerTop` is `0`.
|
|
16
|
+
- For nested scrollable containers (`overflow: auto | scroll`), `containerTop` is the container's top edge (`getBoundingClientRect().top`).
|
|
17
|
+
- With `headerSelector`, sticky header height is measured and added dynamically.
|
|
18
|
+
- With `headerOffset`, custom pixel padding or trigger line offsets are included.
|
|
19
|
+
- **Bottom boundary (`bottomBoundary`)**: `(isDocument ? window.innerHeight : containerBottom) - bottomOffset`
|
|
20
|
+
- With `bottomOffset`, bottom margins or space occupied by sticky footers and bottom floating bars are excluded from the active evaluation window.
|
|
21
|
+
- **Trigger line (`triggerLine`)**: `topBoundary + (bottomBoundary - topBoundary) * activationThreshold`
|
|
22
|
+
- With `activationThreshold` (`0.0` to `1.0`, default `0.0`), developers can position the active trigger focal line anywhere across the visible reading band (e.g. `0.3` for upper-third reading focus, `0.5` for center trigger).
|
|
23
|
+
|
|
24
|
+
### Evaluation Priority
|
|
25
|
+
|
|
26
|
+
1. **Fully Contained Sections (Full Containment Elevation)**: If an upcoming section is 100% visible within the reading area (`top >= topBoundary - 1` and `bottom <= bottomBoundary + 1`), it becomes active immediately—even if a small tail of the previous section is still visible at the top. If multiple sections are fully visible, the topmost fully contained section is selected.
|
|
27
|
+
2. **Tall Section Reading Retention (Reading Line Fallback)**: For sections that are taller than the viewport (or long multi-paragraph articles), the section remains active while scrolling through its body (`top <= triggerLine + 1`) until a subsequent section crosses the trigger line or becomes fully visible.
|
|
28
|
+
3. **Top of Page Fallback**: When scrolled above all anchors, the first anchor is selected.
|
|
29
|
+
|
|
30
|
+
```mermaid
|
|
31
|
+
flowchart TD
|
|
32
|
+
ScrollEvent[User scrolls in viewport/container] --> Debounce[Wait for debounceTime ms<br/><i>default: 500ms</i>]
|
|
33
|
+
Debounce --> ReadLayout[Measure Layout & Active Configuration]
|
|
34
|
+
|
|
35
|
+
subgraph BoundaryCalculation [Boundary & Trigger Line Calculation]
|
|
36
|
+
ReadLayout --> TopB["topBoundary = containerTop<br/>+ headerHeight(headerSelector)<br/>+ headerOffset"]
|
|
37
|
+
ReadLayout --> BotB["bottomBoundary = containerBottom<br/>- bottomOffset"]
|
|
38
|
+
TopB & BotB --> TrigLine["triggerLine = topBoundary +<br/>(bottomBoundary - topBoundary) * activationThreshold"]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
TrigLine --> CheckContainment{"Any section fully contained in reading band?<br/><i>top >= topBoundary - 1 && bottom <= bottomBoundary + 1</i>"}
|
|
42
|
+
|
|
43
|
+
CheckContainment -- Yes --> SelectContained[Select topmost fully contained section]
|
|
44
|
+
CheckContainment -- No --> CheckTrigger{"Any section top <= triggerLine + 1?"}
|
|
45
|
+
|
|
46
|
+
CheckTrigger -- Yes --> SelectTriggered[Select most recently crossed section<br/><i>max top <= triggerLine</i>]
|
|
47
|
+
CheckTrigger -- No --> SelectFirst[Select first anchor in document]
|
|
48
|
+
|
|
49
|
+
SelectContained --> StateUpdate[Active Section Selected]
|
|
50
|
+
SelectTriggered --> StateUpdate
|
|
51
|
+
SelectFirst --> StateUpdate
|
|
52
|
+
|
|
53
|
+
StateUpdate --> ChangedCheck{"Selected anchor == activeAnchor()?"}
|
|
54
|
+
ChangedCheck -- No --> SyncFragment["Update activeAnchor() signal<br/>& update URL fragment (replaceUrl)"]
|
|
55
|
+
ChangedCheck -- Yes --> Noop[Preserve current URL fragment]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Scroll-spy updates are debounced by 500 ms by default (`debounceTime`). During continuous movement, the fragment can therefore lag behind the viewport until scrolling pauses. If there are no eligible candidates, the existing fragment remains unchanged.
|
|
59
|
+
|
|
60
|
+
`activeAnchor()` exposes the current fragment as a signal and reacts to route-fragment changes. Use it to highlight active navigation links and scroll-spy indicators.
|
|
61
|
+
|
|
62
|
+
## Explicit navigation requests movement
|
|
63
|
+
|
|
64
|
+
For a user action within an existing region, call `scrollTo()` on that region:
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<button (click)="scroll.scrollTo('project-12')">Show project 12</button>
|
|
68
|
+
<div magicScroll #scroll="magicScroll">
|
|
69
|
+
<section id="project-12">Project 12...</section>
|
|
70
|
+
</div>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The target must already be rendered and selected by the region. By default, the call initiates smooth scrolling and updates the fragment. Its boolean return value reports whether scrolling was initiated, not whether the animation has finished.
|
|
74
|
+
|
|
75
|
+
Optionally, passing `{ focus: true }` transfers DOM focus safely to the target anchor without secondary scroll jumps, managing temporary `tabindex="-1"` for non-focusable landmark containers with automatic cleanup on blur or directive destruction.
|
|
76
|
+
|
|
77
|
+
A call returns `false` when the target is missing, excluded, owned by another region or skipped by the configured visibility rule. A skipped call does not itself update the fragment. Data readiness does not queue explicit calls for later execution.
|
|
78
|
+
|
|
79
|
+
## Initial restoration reads the entry fragment
|
|
80
|
+
|
|
81
|
+
When a region is created, it captures the route's current fragment. Once its content is ready and rendered, it attempts to restore that anchor once. This supports opening a section link, reloading it or returning to a routed list whose region is recreated.
|
|
82
|
+
|
|
83
|
+
See [Data readiness and restoration](./data-readiness-and-restoration.md) for the timing and missing-target behavior.
|
|
84
|
+
|
|
85
|
+
## Subsequent router fragment navigation
|
|
86
|
+
|
|
87
|
+
When external router navigation changes the URL fragment subsequently (e.g. via `[routerLink]`, programmatic `router.navigate()`, or browser history Back/Forward navigation), `MagicScrollDirective` automatically restores the scroll position to the target anchor using `behavior.restoration`.
|
|
88
|
+
|
|
89
|
+
To prevent feedback loops between user-driven router navigations and the automatic scroll-spy, the library uses internal origin discrimination:
|
|
90
|
+
|
|
91
|
+
```mermaid
|
|
92
|
+
graph TD
|
|
93
|
+
RouterLink[RouterLink / History / router.navigate] -->|External Navigation| Router[Angular Router]
|
|
94
|
+
Router -->|Route Fragment Signal| Directive[MagicScrollDirective]
|
|
95
|
+
ScrollEvent[Scroll Event] -->|Debounced Scroll Spy| linkedRoute[linkedRouteFragment]
|
|
96
|
+
linkedRoute -->|Internal Navigation with Origin Tag| Router
|
|
97
|
+
Directive -->|Filter out Internal Origin| DestinationQueue[Pending Destination Handler]
|
|
98
|
+
DestinationQueue -->|Wait for Source Ready & DOM| ScrollExec[ScrollService.scroll]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## URL updates yield to in-flight navigations
|
|
102
|
+
|
|
103
|
+
A scroll-spy update or `scrollTo()` can become due while the Router is still resolving a user navigation. The fragment writer does not start a competing navigation in that case: it retains the latest requested fragment and lets the user's navigation finish without `SupersededByNewNavigation`.
|
|
104
|
+
|
|
105
|
+
```mermaid
|
|
106
|
+
sequenceDiagram
|
|
107
|
+
participant User
|
|
108
|
+
participant Router
|
|
109
|
+
participant Guard as Async guard / resolver / lazy loader
|
|
110
|
+
participant Writer as linkedRouteFragment
|
|
111
|
+
participant View as Scroll region
|
|
112
|
+
|
|
113
|
+
User->>Router: Navigate to another URL
|
|
114
|
+
Router->>Guard: Start pending route work
|
|
115
|
+
Guard-->>Router: Wait for decision or loading
|
|
116
|
+
View->>Writer: Write the latest visible anchor
|
|
117
|
+
Writer->>Writer: Defer fragment and remember route path
|
|
118
|
+
Note over Writer,Router: No competing navigateByUrl is started
|
|
119
|
+
alt Navigation is canceled, errors or is skipped
|
|
120
|
+
Router-->>Writer: NavigationCancel / Error / Skipped
|
|
121
|
+
Writer->>Router: Publish latest fragment with replaceUrl
|
|
122
|
+
else Navigation ends on the same path without a new fragment
|
|
123
|
+
Router-->>Writer: NavigationEnd (query may differ)
|
|
124
|
+
Writer->>Router: Publish latest fragment with replaceUrl
|
|
125
|
+
else Different path, new fragment or destroyed region
|
|
126
|
+
Router-->>Writer: NavigationEnd or region destruction
|
|
127
|
+
Writer->>Writer: Discard deferred fragment
|
|
128
|
+
end
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
On `NavigationCancel`, `NavigationError` or `NavigationSkipped`, the last deferred value is published once if it still differs from the URL fragment. After `NavigationEnd`, it is published only when the path (compared without query parameters and fragment) is unchanged; a route change discards it. A fragment explicitly set by the user navigation wins, and a destroyed region never publishes later. The queued write still uses `replaceUrl`, and `scrollTo()` performs its scroll immediately while only its URL update is deferred. Deferred local writes are not treated as external fragment changes by phase-3 restoration.
|
|
132
|
+
|
|
133
|
+
If a test Router mock omits `currentNavigation()` or `events`, the library keeps its previous immediate-write behavior and emits one `console.warn` per `linkedRouteFragment` instance to make the missing in-flight protection visible.
|
|
134
|
+
|
|
135
|
+
## URL updates preserve the current history entry
|
|
136
|
+
|
|
137
|
+
Library fragment updates preserve query parameters and use Angular Router's `replaceUrl`. Scrolling through sections therefore updates the current history entry instead of creating a new entry for every anchor.
|
|
138
|
+
|
|
139
|
+
For a list-to-detail round trip, these roles are complementary:
|
|
140
|
+
|
|
141
|
+
1. The list's fragment records the item to return to.
|
|
142
|
+
2. Navigation to the detail page creates the next history entry.
|
|
143
|
+
3. Browser Back returns to the list URL, and the recreated region restores its fragment.
|
|
144
|
+
|
|
145
|
+
If clicking an item should define the return target, record that item's fragment before leaving and await the URL update. The debounced scroll spy may still name another item at click time. Query parameters can separately preserve filters or the loaded data range; the fragment identifies the section within that restored state.
|
|
146
|
+
|
|
147
|
+
## Related documentation
|
|
148
|
+
|
|
149
|
+
- [Return to a list](../recipes/return-to-list.md) implements the history sequence and an explicit return link.
|
|
150
|
+
- [Anchor navigation](../recipes/scroll-to-anchor.md) builds navigation highlighting and per-action overrides.
|
|
151
|
+
- [Scroll regions and anchor identity](./anchors.md) explains eligible targets and shared fragment state.
|
|
152
|
+
- [MagicScrollDirective](https://devzwo.github.io/ngx-magic-scroll/api/classes/magicscrolldirective/) documents `activeAnchor()` and `scrollTo()`.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Getting started
|
|
3
|
+
description: Install and use ngx-magic-scroll in your Angular application.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
This guide shows how to install `@devzwo/ngx-magic-scroll` and add anchor scrolling, URL fragment synchronization and scroll restoration to your Angular application.
|
|
7
|
+
|
|
8
|
+
## Prerequisites
|
|
9
|
+
|
|
10
|
+
`ngx-magic-scroll` requires Angular 22.2 and RxJS 7.8 within the supported major versions. Your application must provide Angular Router.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
Install the library in your Angular project:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @devzwo/ngx-magic-scroll
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add @devzwo/ngx-magic-scroll
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Configure Angular Router
|
|
25
|
+
|
|
26
|
+
If your application already provides Angular Router, keep your existing configuration. Otherwise, register it in `app.config.ts`:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { ApplicationConfig } from '@angular/core';
|
|
30
|
+
import { provideRouter } from '@angular/router';
|
|
31
|
+
|
|
32
|
+
export const appConfig: ApplicationConfig = {
|
|
33
|
+
providers: [provideRouter([])],
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Use your application's routes in place of the empty array when adding routed pages.
|
|
38
|
+
|
|
39
|
+
## Your first scroll region
|
|
40
|
+
|
|
41
|
+
Import `MagicScrollDirective` in your component and add `magicScroll` to the element containing your sections. Give each section a stable, unique ID:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { Component } from '@angular/core';
|
|
45
|
+
import { MagicScrollDirective } from '@devzwo/ngx-magic-scroll';
|
|
46
|
+
|
|
47
|
+
@Component({
|
|
48
|
+
selector: 'app-projects',
|
|
49
|
+
imports: [MagicScrollDirective],
|
|
50
|
+
template: `
|
|
51
|
+
<div magicScroll>
|
|
52
|
+
<section id="project-1">
|
|
53
|
+
<h2>Project 1</h2>
|
|
54
|
+
<p>Project details...</p>
|
|
55
|
+
</section>
|
|
56
|
+
<section id="project-2">
|
|
57
|
+
<h2>Project 2</h2>
|
|
58
|
+
<p>Project details...</p>
|
|
59
|
+
</section>
|
|
60
|
+
</div>
|
|
61
|
+
`,
|
|
62
|
+
})
|
|
63
|
+
export class ProjectsComponent {}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
All descendant IDs participate by default. The directive automatically finds the scroll container, updates the URL fragment while scrolling and restores an initial fragment after rendering. For example, opening the page with `#project-2` scrolls to the second section.
|
|
67
|
+
|
|
68
|
+
No additional configuration is needed for static content or data already loaded by a router resolver. The page needs enough content to scroll for the movement to be visible.
|
|
69
|
+
|
|
70
|
+
## Scroll to a section
|
|
71
|
+
|
|
72
|
+
Export the directive as a template reference and call `scrollTo()` from a button:
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<button (click)="scroll.scrollTo('project-2')">Show project 2</button>
|
|
76
|
+
|
|
77
|
+
<div magicScroll #scroll="magicScroll">
|
|
78
|
+
<section id="project-1">Project 1...</section>
|
|
79
|
+
<section id="project-2">Project 2...</section>
|
|
80
|
+
</div>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The target must already be rendered. User interactions scroll smoothly by default.
|
|
84
|
+
|
|
85
|
+
## Asynchronous content
|
|
86
|
+
|
|
87
|
+
When your content loads asynchronously, bind its signal or Angular resource to `scrollSource`. This lets the library wait for the data before restoring the initial fragment.
|
|
88
|
+
|
|
89
|
+
For example, a signal can start with `undefined` and receive the loaded projects later:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { signal } from '@angular/core';
|
|
93
|
+
|
|
94
|
+
interface Project {
|
|
95
|
+
id: number;
|
|
96
|
+
name: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Inside your component; set this signal when your data has loaded.
|
|
100
|
+
readonly projects = signal<Project[] | undefined>(undefined);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```html
|
|
104
|
+
<div magicScroll [scrollSource]="projects">
|
|
105
|
+
@for (project of projects(); track project.id) {
|
|
106
|
+
<section [id]="'project-' + project.id">
|
|
107
|
+
<h2>{{ project.name }}</h2>
|
|
108
|
+
</section>
|
|
109
|
+
}
|
|
110
|
+
</div>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Pass the signal itself as `scrollSource`; call it in the template to render its value. You can also pass an Angular `resource` or `rxResource` directly. Your application remains responsible for fetching data and displaying loading and error states.
|
|
114
|
+
|
|
115
|
+
## Configure defaults
|
|
116
|
+
|
|
117
|
+
Optionally register `provideMagicScroll()` alongside your existing providers in `app.config.ts`:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { provideMagicScroll } from '@devzwo/ngx-magic-scroll';
|
|
121
|
+
|
|
122
|
+
// Add to appConfig.providers:
|
|
123
|
+
provideMagicScroll({
|
|
124
|
+
anchorPrefixes: ['project-'],
|
|
125
|
+
headerSelector: '.app-header',
|
|
126
|
+
headerOffset: 16,
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This limits automatic anchors to IDs starting with `project-` and leaves a 16-pixel gap below your header. The library measures the header's height automatically. Use a selector matching your application's header.
|
|
131
|
+
|
|
132
|
+
Override defaults for an individual scroll region with `scrollOptions`:
|
|
133
|
+
|
|
134
|
+
```html
|
|
135
|
+
<div magicScroll [scrollOptions]="{ headerOffset: 24 }">
|
|
136
|
+
<section id="project-1">Project 1...</section>
|
|
137
|
+
</div>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Next steps
|
|
141
|
+
|
|
142
|
+
- [Concepts](./concepts/anchors.md): understand anchor ownership and scroll containers, then explore URL state, restoration and layout changes.
|
|
143
|
+
- [Anchor navigation](./recipes/scroll-to-anchor.md): explicit anchors, navigation highlighting and per-action options.
|
|
144
|
+
- [API reference](https://devzwo.github.io/ngx-magic-scroll/api/readme/): directives, configuration and supported data source types.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Migration Guide: v0.0.1-rc.1 to v0.0.1-rc.2'
|
|
3
|
+
description: 'Upgrade instructions and code transformations for updating @devzwo/ngx-magic-scroll from v0.0.1-rc.1 to v0.0.1-rc.2.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
This guide details the new configuration options, behavioral improvements, and SSR enhancements when upgrading `@devzwo/ngx-magic-scroll` from `v0.0.1-rc.1` to `v0.0.1-rc.2`.
|
|
7
|
+
|
|
8
|
+
## Overview of Changes
|
|
9
|
+
|
|
10
|
+
Release `v0.0.1-rc.2` introduces key improvements to scroll-spy precision, restoration lifecycle handling, and server-side rendering resilience:
|
|
11
|
+
|
|
12
|
+
- **Active Reading Area & Trigger Lines**: Configurable `bottomOffset` and `activationThreshold` options to fine-tune section detection and exclusion zones.
|
|
13
|
+
- **User Interaction Cancellation**: User input (wheel, touch, keyboard scroll keys) prior to data readiness gracefully cancels pending initial scroll restoration without breaking programmatic navigation.
|
|
14
|
+
- **Server-Side Rendering (SSR) Resilience**: Improved guards for environments where `ResizeObserver` or `defaultView` is unavailable.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 1. Configuring Active Reading Boundaries and Trigger Lines
|
|
19
|
+
|
|
20
|
+
### Previous Behavior (v0.0.1-rc.1)
|
|
21
|
+
|
|
22
|
+
In `v0.0.1-rc.1`, scroll-spy calculations used a fixed top reference line (factoring in `headerOffset` and `headerSelector`). When scrolling through long pages with floating action bars, footers, or tall cards, active section detection could activate too late or trigger outside the reader's primary viewing zone.
|
|
23
|
+
|
|
24
|
+
### Updated Configuration (v0.0.1-rc.2)
|
|
25
|
+
|
|
26
|
+
`v0.0.1-rc.2` adds two options to `MagicScrollOptions`:
|
|
27
|
+
|
|
28
|
+
- `bottomOffset`: Defines a bottom exclusion boundary in pixels (e.g. for sticky bottom bars or mobile navigation).
|
|
29
|
+
- `activationThreshold`: A relative factor between `0.0` (top) and `1.0` (bottom) defining where within the effective reading area the active anchor trigger line sits.
|
|
30
|
+
|
|
31
|
+
#### Before (v0.0.1-rc.1)
|
|
32
|
+
|
|
33
|
+
<!-- recipe-check: migration-rc1-rc2-before.ts -->
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { Component } from '@angular/core';
|
|
37
|
+
import {
|
|
38
|
+
MagicScrollDirective,
|
|
39
|
+
ScrollAnchorDirective,
|
|
40
|
+
provideMagicScroll,
|
|
41
|
+
} from '@devzwo/ngx-magic-scroll';
|
|
42
|
+
|
|
43
|
+
@Component({
|
|
44
|
+
selector: 'app-migration-rc1-sample-before',
|
|
45
|
+
imports: [MagicScrollDirective, ScrollAnchorDirective],
|
|
46
|
+
providers: [
|
|
47
|
+
provideMagicScroll({
|
|
48
|
+
headerOffset: 64,
|
|
49
|
+
}),
|
|
50
|
+
],
|
|
51
|
+
template: `
|
|
52
|
+
<div magicScroll>
|
|
53
|
+
<section id="intro" scrollAnchor>
|
|
54
|
+
<h2>Introduction</h2>
|
|
55
|
+
</section>
|
|
56
|
+
<section id="specs" scrollAnchor>
|
|
57
|
+
<h2>Specifications</h2>
|
|
58
|
+
</section>
|
|
59
|
+
</div>
|
|
60
|
+
`,
|
|
61
|
+
})
|
|
62
|
+
export class MigrationRc1SampleBeforeComponent {}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### After (v0.0.1-rc.2)
|
|
66
|
+
|
|
67
|
+
<!-- recipe-check: migration-rc1-rc2-after.ts -->
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { Component } from '@angular/core';
|
|
71
|
+
import {
|
|
72
|
+
MagicScrollDirective,
|
|
73
|
+
ScrollAnchorDirective,
|
|
74
|
+
provideMagicScroll,
|
|
75
|
+
} from '@devzwo/ngx-magic-scroll';
|
|
76
|
+
|
|
77
|
+
@Component({
|
|
78
|
+
selector: 'app-migration-rc1-sample-after',
|
|
79
|
+
imports: [MagicScrollDirective, ScrollAnchorDirective],
|
|
80
|
+
providers: [
|
|
81
|
+
provideMagicScroll({
|
|
82
|
+
headerOffset: 64,
|
|
83
|
+
bottomOffset: 48,
|
|
84
|
+
activationThreshold: 0.25,
|
|
85
|
+
}),
|
|
86
|
+
],
|
|
87
|
+
template: `
|
|
88
|
+
<div
|
|
89
|
+
magicScroll
|
|
90
|
+
[scrollOptions]="{
|
|
91
|
+
headerOffset: 64,
|
|
92
|
+
bottomOffset: 48,
|
|
93
|
+
activationThreshold: 0.25,
|
|
94
|
+
}"
|
|
95
|
+
>
|
|
96
|
+
<section id="intro" scrollAnchor>
|
|
97
|
+
<h2>Introduction</h2>
|
|
98
|
+
</section>
|
|
99
|
+
<section id="specs" scrollAnchor>
|
|
100
|
+
<h2>Specifications</h2>
|
|
101
|
+
</section>
|
|
102
|
+
</div>
|
|
103
|
+
`,
|
|
104
|
+
})
|
|
105
|
+
export class MigrationRc1SampleAfterComponent {}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 2. Interaction-Aware Scroll Restoration Lifecycle
|
|
111
|
+
|
|
112
|
+
### Behavioral Improvement
|
|
113
|
+
|
|
114
|
+
In `v0.0.1-rc.1`, when a page loaded with a pending URL fragment and asynchronous data source (`scrollSource`), initial position restoration waited for data readiness. If the user scrolled manually before data arrived, the subsequent delayed restoration could jump the page unexpectedly.
|
|
115
|
+
|
|
116
|
+
In `v0.0.1-rc.2`:
|
|
117
|
+
|
|
118
|
+
- User interactions (mouse wheel, touch drag, keyboard navigation keys like `PageDown`, `ArrowDown`, `Space`) cancel pending initial restoration.
|
|
119
|
+
- Focus or keystrokes inside editable fields (`<input>`, `<textarea>`, `contenteditable`) do not cancel restoration.
|
|
120
|
+
- Programmatic calls to `scrollTo()` remain fully functional and are never blocked by prior user cancellation.
|
|
121
|
+
|
|
122
|
+
<!-- recipe-check: migration-rc1-rc2-thresholds.ts -->
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { Component, signal } from '@angular/core';
|
|
126
|
+
import {
|
|
127
|
+
MagicScrollDirective,
|
|
128
|
+
ScrollAnchorDirective,
|
|
129
|
+
provideMagicScroll,
|
|
130
|
+
} from '@devzwo/ngx-magic-scroll';
|
|
131
|
+
|
|
132
|
+
interface Item {
|
|
133
|
+
readonly id: string;
|
|
134
|
+
readonly title: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@Component({
|
|
138
|
+
selector: 'app-migration-rc1-async-sample',
|
|
139
|
+
imports: [MagicScrollDirective, ScrollAnchorDirective],
|
|
140
|
+
providers: [provideMagicScroll()],
|
|
141
|
+
template: `
|
|
142
|
+
<div magicScroll [scrollSource]="items()">
|
|
143
|
+
@for (item of items(); track item.id) {
|
|
144
|
+
<article [id]="item.id" scrollAnchor>
|
|
145
|
+
<h3>{{ item.title }}</h3>
|
|
146
|
+
</article>
|
|
147
|
+
}
|
|
148
|
+
</div>
|
|
149
|
+
`,
|
|
150
|
+
})
|
|
151
|
+
export class MigrationRc1AsyncSampleComponent {
|
|
152
|
+
// undefined simulates loading; user scrolling during this phase cancels initial jump safely
|
|
153
|
+
readonly items = signal<Item[] | undefined>(undefined);
|
|
154
|
+
|
|
155
|
+
constructor() {
|
|
156
|
+
setTimeout(() => {
|
|
157
|
+
this.items.set([
|
|
158
|
+
{ id: 'item-1', title: 'First Item' },
|
|
159
|
+
{ id: 'item-2', title: 'Second Item' },
|
|
160
|
+
]);
|
|
161
|
+
}, 150);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 3. Server-Side Rendering (SSR) Resilience
|
|
169
|
+
|
|
170
|
+
`v0.0.1-rc.2` enhances SSR compatibility by guarding observer instantiation (`ResizeObserver`, `MutationObserver`) and window references (`defaultView`). No code changes are required in your templates or providers; the directive safely skips DOM-only observers in SSR environments.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 4. Deprecations and Breaking Changes
|
|
175
|
+
|
|
176
|
+
- **Breaking Changes**: None. Default parameters preserve compatibility with `v0.0.1-rc.1`.
|
|
177
|
+
- **Deprecations**: None.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Migration Guide: v0.0.1-rc.2 to v1.0.0'
|
|
3
|
+
description: 'Upgrade instructions and code transformations for updating @devzwo/ngx-magic-scroll from v0.0.1-rc.2 to v1.0.0.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
This guide details the major features, navigation automation, anchor selection modes, and recommended upgrade steps when moving `@devzwo/ngx-magic-scroll` from `v0.0.1-rc.2` to `v1.0.0`.
|
|
7
|
+
|
|
8
|
+
## Overview of Changes
|
|
9
|
+
|
|
10
|
+
Release `v1.0.0` marks the first stable production release of `@devzwo/ngx-magic-scroll`. Key enhancements include:
|
|
11
|
+
|
|
12
|
+
- **Automatic Subsequent Router Fragment Navigation**: Subsequent Angular router navigation with URL fragments is handled automatically without manual router event subscriptions.
|
|
13
|
+
- **Asynchronous Mutation Retry Queue**: Pending scroll targets are queued and retried automatically via `MutationObserver` until matching DOM elements appear.
|
|
14
|
+
- **Multi-Prefix Anchor Matching**: `anchorPrefixes` accepts an array of string prefixes to match multiple naming schemes across complex views.
|
|
15
|
+
- **Strict Explicit Anchor Mode**: `anchorSelection: 'explicit'` mode restricts anchor discovery exclusively to elements marked with `[scrollAnchor]`.
|
|
16
|
+
- **Browser Popstate & History Restoration**: Full coordination with browser back and forward navigation for restoring prior scroll positions.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. Automatic Router Fragment Navigation & Retry Queues
|
|
21
|
+
|
|
22
|
+
### Previous Behavior (v0.0.1-rc.2)
|
|
23
|
+
|
|
24
|
+
In `v0.0.1-rc.2`, the directive focused on initial URL fragment restoration on page load. Navigating between fragments on the same route often required manual event listening or custom imperative calls to `scrollTo()`. Furthermore, if an anchor was rendered asynchronously after a route transition, navigation could fail if the element wasn't immediately present.
|
|
25
|
+
|
|
26
|
+
### Updated Behavior (v1.0.0)
|
|
27
|
+
|
|
28
|
+
In `v1.0.0`, `MagicScrollDirective` automatically subscribes to Angular Router fragment changes throughout the component lifecycle. If the target element is not in the DOM immediately (e.g. while data is fetching or templates are rendering), the directive queues the navigation request and retries automatically via `MutationObserver` until the element is mounted.
|
|
29
|
+
|
|
30
|
+
<!-- recipe-check: migration-rc2-v1-navigation.ts -->
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { Component } from '@angular/core';
|
|
34
|
+
import { RouterLink } from '@angular/router';
|
|
35
|
+
import {
|
|
36
|
+
MagicScrollDirective,
|
|
37
|
+
ScrollAnchorDirective,
|
|
38
|
+
provideMagicScroll,
|
|
39
|
+
} from '@devzwo/ngx-magic-scroll';
|
|
40
|
+
|
|
41
|
+
@Component({
|
|
42
|
+
selector: 'app-migration-rc2-router-nav',
|
|
43
|
+
imports: [RouterLink, MagicScrollDirective, ScrollAnchorDirective],
|
|
44
|
+
providers: [provideMagicScroll()],
|
|
45
|
+
template: `
|
|
46
|
+
<nav>
|
|
47
|
+
<!-- Subsequent fragment clicks automatically scroll and update active anchor in v1.0.0 -->
|
|
48
|
+
<a routerLink="." fragment="overview">Overview</a>
|
|
49
|
+
<a routerLink="." fragment="pricing">Pricing</a>
|
|
50
|
+
<a routerLink="." fragment="faq">FAQ</a>
|
|
51
|
+
</nav>
|
|
52
|
+
|
|
53
|
+
<div magicScroll>
|
|
54
|
+
<section id="overview" scrollAnchor>
|
|
55
|
+
<h2>Overview</h2>
|
|
56
|
+
</section>
|
|
57
|
+
<section id="pricing" scrollAnchor>
|
|
58
|
+
<h2>Pricing</h2>
|
|
59
|
+
</section>
|
|
60
|
+
<section id="faq" scrollAnchor>
|
|
61
|
+
<h2>FAQ</h2>
|
|
62
|
+
</section>
|
|
63
|
+
</div>
|
|
64
|
+
`,
|
|
65
|
+
})
|
|
66
|
+
export class MigrationRc2RouterNavComponent {}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 2. Multi-Prefix Anchor Matching
|
|
72
|
+
|
|
73
|
+
### Previous Configuration (v0.0.1-rc.2)
|
|
74
|
+
|
|
75
|
+
In `v0.0.1-rc.2`, anchor discovery matched either all DOM IDs or elements starting with a single configured prefix.
|
|
76
|
+
|
|
77
|
+
### Updated Configuration (v1.0.0)
|
|
78
|
+
|
|
79
|
+
`v1.0.0` supports `anchorPrefixes` as an array of prefixes (`string[]`). Elements matching any of the prefixes are registered automatically as anchors.
|
|
80
|
+
|
|
81
|
+
<!-- recipe-check: migration-rc2-v1-prefixes.ts -->
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { Component } from '@angular/core';
|
|
85
|
+
import { MagicScrollDirective, provideMagicScroll } from '@devzwo/ngx-magic-scroll';
|
|
86
|
+
|
|
87
|
+
@Component({
|
|
88
|
+
selector: 'app-migration-rc2-prefixes-sample',
|
|
89
|
+
imports: [MagicScrollDirective],
|
|
90
|
+
providers: [
|
|
91
|
+
provideMagicScroll({
|
|
92
|
+
// Match anchors with either prefix across the application
|
|
93
|
+
anchorPrefixes: ['section-', 'module-'],
|
|
94
|
+
}),
|
|
95
|
+
],
|
|
96
|
+
template: `
|
|
97
|
+
<div magicScroll>
|
|
98
|
+
<!-- Automatically registered via 'section-' prefix -->
|
|
99
|
+
<section id="section-header">
|
|
100
|
+
<h2>Header Section</h2>
|
|
101
|
+
</section>
|
|
102
|
+
|
|
103
|
+
<!-- Automatically registered via 'module-' prefix -->
|
|
104
|
+
<section id="module-analytics">
|
|
105
|
+
<h2>Analytics Module</h2>
|
|
106
|
+
</section>
|
|
107
|
+
|
|
108
|
+
<!-- Ignored by scroll-spy because prefix does not match -->
|
|
109
|
+
<div id="unrelated-dom-id">
|
|
110
|
+
<p>Regular container</p>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
`,
|
|
114
|
+
})
|
|
115
|
+
export class MigrationRc2PrefixesSampleComponent {}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 3. Strict Explicit Anchor Selection Mode
|
|
121
|
+
|
|
122
|
+
### Previous Configuration (v0.0.1-rc.2)
|
|
123
|
+
|
|
124
|
+
In `v0.0.1-rc.2`, all child elements with an `id` attribute were candidates for anchor navigation unless filtered by prefixes.
|
|
125
|
+
|
|
126
|
+
### Updated Configuration (v1.0.0)
|
|
127
|
+
|
|
128
|
+
`v1.0.0` introduces `anchorSelection: 'explicit'`. When enabled, only elements with the `[scrollAnchor]` or `scrollAnchor` directive are recognized, ignoring standard DOM IDs entirely:
|
|
129
|
+
|
|
130
|
+
<!-- recipe-check: migration-rc2-v1-explicit.ts -->
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { Component } from '@angular/core';
|
|
134
|
+
import {
|
|
135
|
+
MagicScrollDirective,
|
|
136
|
+
ScrollAnchorDirective,
|
|
137
|
+
provideMagicScroll,
|
|
138
|
+
} from '@devzwo/ngx-magic-scroll';
|
|
139
|
+
|
|
140
|
+
@Component({
|
|
141
|
+
selector: 'app-migration-rc2-explicit-sample',
|
|
142
|
+
imports: [MagicScrollDirective, ScrollAnchorDirective],
|
|
143
|
+
providers: [
|
|
144
|
+
provideMagicScroll({
|
|
145
|
+
// Only recognize elements explicitly tagged with scrollAnchor
|
|
146
|
+
anchorSelection: 'explicit',
|
|
147
|
+
}),
|
|
148
|
+
],
|
|
149
|
+
template: `
|
|
150
|
+
<div magicScroll>
|
|
151
|
+
<!-- Explicit anchor directive included -->
|
|
152
|
+
<section id="features" scrollAnchor>
|
|
153
|
+
<h2>Features</h2>
|
|
154
|
+
</section>
|
|
155
|
+
|
|
156
|
+
<!-- Explicit anchor with dynamic input ID binding -->
|
|
157
|
+
<section [scrollAnchor]="'reviews'">
|
|
158
|
+
<h2>Reviews</h2>
|
|
159
|
+
</section>
|
|
160
|
+
|
|
161
|
+
<!-- Excluded from anchor tracking even though it has an ID -->
|
|
162
|
+
<div id="internal-widget">
|
|
163
|
+
<p>Widget content</p>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
`,
|
|
167
|
+
})
|
|
168
|
+
export class MigrationRc2ExplicitSampleComponent {}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 4. Deprecations and Breaking Changes
|
|
174
|
+
|
|
175
|
+
- **Breaking Changes**: None. Upgrading to `v1.0.0` preserves backwards compatibility with existing `v0.0.1-rc.2` options.
|
|
176
|
+
- **Deprecations**: None.
|