Skip to content

Commit fb8eb82

Browse files
committed
feat(pat navigation): Add scroll-marker functionality.
The pattern now sets current and in-view classes on the navigation and the content when scrolling to hash-link targets.
1 parent 3c55864 commit fb8eb82

File tree

2 files changed

+81
-5
lines changed

2 files changed

+81
-5
lines changed

src/pat/navigation/documentation.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,21 @@ Marks navigation paths with "in-path" and "current" classes.
55

66
## Documentation
77

8-
The "in-path" and "current" classes and the "item-wrapper" are configurable.
8+
When clicking on a navigation item this one will always get the `current-class`.
9+
When loading a page and the browser's url matches a navigation item's href value, this one will get the `current-class`.
10+
Any navigation items in it's path will get the `in-path-class`.
11+
12+
### scroll-marker functionality
13+
14+
When a content items with an id which matches a hash-url in the navigation are scrolled into view then the corresponding navigation items will get the `in-view-class`.
15+
One matching navigation item will get the `current-class` and the corresponding content item will get the `current-content-class`, depending on the algorithm in use as described in the [pat-scroll-marker documentation](../scroll-marker/documentation.md).
16+
The default is that the content item in view, which top position is neares to the middle of the scrolling container will be the current item.
17+
18+
### Automatic loading of items
919

1020
You can automatically load the navigation item marked with the `current` class by adding the class `navigation-load-current` along with `pat-navigation` on the pattern element.
1121
This would invoke a `click` event on the current navigation item and that can be used to load content via `pat-inject`.
1222

13-
For examples see index.html.
1423

1524
### Option reference
1625

@@ -19,6 +28,12 @@ The available options are:
1928

2029
| Field | Default | Options | Description |
2130
| --------------- | -------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
22-
| `item-wrapper` | `li` | CSS selector | The DOM element which wraps each menu item. This is used to set the "current" and "in-path" classes also on the wrapper element. If empty, no wrapper element is used. |
23-
| `in-path-class` | `navigation-in-path` | CSS class name | Class name, which is set on item-wrapper elements if nested menu items are within the current path. |
24-
| `current-class` | `current` | CSS class name | Class name, which is set on item-wrapper or items if they are the currently selected menu option. |
31+
| item-wrapper | li | CSS selector | The DOM element which wraps each menu item. This is used to set the "current" and "in-path" classes also on the wrapper element. If empty, no wrapper element is used. |
32+
| in-path-class | navigation-in-path | CSS class name | Class name, which is set on item-wrapper elements if nested menu items are within the current path. |
33+
| current-class | current | CSS class name | Class name, which is set on item-wrapper or items if they are the currently selected menu option or - for hash links - when it's corresponding content item is in view. |
34+
| in-view-class | in-view | | String | CSS class for a navigation item when it's corresponding content item is in view. |
35+
| current-content-class | current | | String | CSS class for a content item when it is the current one. |
36+
| scroll-marker-side | top | top, bottom, middle, auto | String | Side of element that scrolls. This is used to calculate the current item. The defined side of the element will be used to calculate the distance baseline. If this is set to "auto" then one of the "top" or "bottom" positions are used, depending on which one is nearer to the distance baseline. |
37+
| scroll-marker-distance | 50% | | String | Distance from side of scroll box. any amount in px or %. This is used to calculate the current item. The nearest element to the distance-baseline measured from the top of the container will get the current class. |
38+
| scroll-marker-visibility | | null, most-visible | String | Visibility of element in scroll box. This is used to calculate the current item. If "most-visible" is set, the element which is most visible in the scroll container gets the current class. If more elements have the same visibility ratio, the other conditions are used to calculate the current one. |
39+

src/pat/navigation/navigation.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
11
import $ from "jquery";
22
import { BasePattern } from "../../core/basepattern";
33
import Parser from "../../core/parser";
4+
import ScrollMarker from "../scroll-marker/scroll-marker";
45
import logging from "../../core/logging";
56
import events from "../../core/events";
67
import registry from "../../core/registry";
8+
import utils from "../../core/utils";
79

810
const log = logging.getLogger("navigation");
911

1012
export const parser = new Parser("navigation");
1113
parser.addArgument("item-wrapper", "li");
1214
parser.addArgument("in-path-class", "navigation-in-path");
15+
parser.addArgument("in-view-class", "in-view");
1316
parser.addArgument("current-class", "current");
17+
parser.addArgument("current-content-class", "navigation-current");
18+
19+
// Side of element that scrolls. top/bottom/middle/auto (default 'top')
20+
parser.addArgument("scroll-marker-side", "top", ["top", "bottom", "middle", "auto"]);
21+
// Distance from side of scroll box. any amount in px or % (default '50%')
22+
parser.addArgument("scroll-marker-distance", "50%");
23+
// Visibility of element in scroll box. most-visible or null (default null)
24+
parser.addArgument("scroll-marker-visibility", null, [null, "most-visible"]);
1425

1526
class Pattern extends BasePattern {
1627
static name = "navigation";
@@ -24,6 +35,26 @@ class Pattern extends BasePattern {
2435

2536
this.init_listeners();
2637
this.init_markings();
38+
39+
this.scroll_marker = new ScrollMarker(this.el, {
40+
"current-class": this.options["current-class"],
41+
"current-content-class": this.options["current-content-class"],
42+
"in-view-class": this.options["in-view-class"],
43+
"side": this.options["scroll-marker-side"],
44+
"distance": this.options["scroll-marker-distance"],
45+
"visibility": this.options["scroll-marker-visibility"],
46+
});
47+
48+
this.debounced_scroll_marker_enabler = utils.debounce(() => {
49+
log.debug("Enable scroll-marker.");
50+
this.scroll_marker.set_current_disabled = false;
51+
events.remove_event_listener(
52+
this.scroll_marker.scroll_container === window
53+
? document
54+
: this.scroll_marker.scroll_container,
55+
"pat-navigation__scroll_marker_enable"
56+
);
57+
}, 200);
2758
}
2859

2960
/**
@@ -42,6 +73,20 @@ class Pattern extends BasePattern {
4273
this.clear_items();
4374
// Mark the current item
4475
this.mark_current(ev.target);
76+
77+
// Disable scroll marker to set the current class after
78+
// clicking in the menu and scrolling to the target.
79+
log.debug("Disable scroll-marker.");
80+
this.scroll_marker.set_current_disabled = true;
81+
this.debounced_scroll_marker_enabler();
82+
events.add_event_listener(
83+
this.scroll_marker.scroll_container === window
84+
? document
85+
: this.scroll_marker.scroll_container,
86+
"scroll",
87+
"pat-navigation__scroll_marker_enable",
88+
this.debounced_scroll_marker_enabler
89+
);
4590
}
4691
}
4792
);
@@ -124,6 +169,22 @@ class Pattern extends BasePattern {
124169
wrapper?.classList.add(this.options["current-class"]);
125170
this.mark_in_path(wrapper || item);
126171
log.debug("Statically set current item marked as current", item);
172+
173+
// Clear all previous current content items.
174+
for (const it of [
175+
...document.querySelectorAll(
176+
`.${this.options["current-content-class"]}`
177+
),
178+
]) {
179+
it.classList.remove(this.options["current-content-class"]);
180+
}
181+
// Mark the current content item, if it is a hash link.
182+
if (item.matches("a[href^='#']")) {
183+
const content_item = document.querySelector(item.getAttribute("href"));
184+
if (content_item) {
185+
content_item.classList.add(this.options["current-content-class"]);
186+
}
187+
}
127188
}
128189
}
129190

0 commit comments

Comments
 (0)