ESL Anchornav
The ESL Anchornav component allows users to quickly jump to specific page content via predefined anchors. The list of anchors is collected from the page dynamically, so any page updates will be processed and the component updates the navigation list.
How it works
The component collects anchors on the page, builds a list of anchors using the user-defined renderer function, and appends it to the inner items container element (it will be an element with esl-anchornav-items attribute). After that, the component observes the position of the collected anchors to detect currently active anchor and marks it with active class marker.
For example, markup may be the following:
<esl-anchornav>Anchors: <nav esl-anchornav-items></nav></esl-anchornav>
If for some reason you do not add an element with this attribute to the component content, it will not be a mistake. A div with the esl-anchornav-items attribute will be created and added to the component content in this case.
You can assign anchors to any element on the page. By default, the component searches for elements with the esl-anchor attribute (i.e. selector [esl-anchor]). Another mandatory requirement for an element is that it must have id and title attributes (the title is the text to be displayed in the list).
If you need custom validation or filtering of collected anchors, override the protected getDataFrom() method. It may return undefined to skip an anchor that does not match your custom contract.
Items renderer
For all collected anchors it is used renderer function which builds the inner content of the anchors list. Here is a default renderer:
ESLAnchornav.setRenderer((data: ESLAnchorData, index: number, anchornav: ESLAnchornav) => `<a class="esl-anchornav-item" href="#${data.id}">${data.title}</a>`);
You can define your own renderer. You can define several renderers with different names and use them on different components.
Hierarchical Navigation
ESL Anchornav supports hierarchical (nested) navigation structures. To enable hierarchy, use the group-by attribute:
<esl-anchornav group-by="level"></esl-anchornav>
When hierarchy is enabled:
- Anchors are organized into a tree structure based on data from the
esl-anchorattribute - The
ESLAnchorDatainterface includesdata,parent, andchildrenproperties - You can override
updateActiveClasses()method to implement custom active state logic (e.g., parent item activation)
Example: Hierarchical Anchors
<!-- Page content with hierarchical anchors -->
<h2 esl-anchor="level: 0" id="section1" title="Section 1">Section 1</h2>
<h3 esl-anchor="level: 1" id="subsection1-1" title="Subsection 1.1">Subsection 1.1</h3>
<h3 esl-anchor="level: 1" id="subsection1-2" title="Subsection 1.2">Subsection 1.2</h3>
<h2 esl-anchor="level: 0" id="section2" title="Section 2">Section 2</h2>
Note: The esl-anchor attribute uses JSON-like syntax to define data properties. For example, esl-anchor="level: 1" sets the level property in the anchor's data object.
Custom Hierarchical Renderer
When rendering hierarchical navigation, you must use nav.renderItem() for nested items to ensure proper registration:
Important: Always use nav.renderItem(child, index) when rendering child items. Direct renderer calls will not register items properly and active state will not work.
Custom Hierarchy Logic
To implement custom hierarchy logic (e.g., using parent property from anchor data), you can register a custom hierarchy builder:
import {ESLAnchornav} from '@exadel/esl/modules/esl-anchornav/core';
import type {ESLAnchorData, ESLAnchornavHierarchyBuilder} from '@exadel/esl/modules/esl-anchornav/core';
const buildByParent: ESLAnchornavHierarchyBuilder = (flatAnchors: ESLAnchorData[]) => {
// Build hierarchy using 'parent' property from anchor data
const map = new Map<string, ESLAnchorData>();
flatAnchors.forEach(anchor => {
anchor.children = [];
map.set(anchor.id, anchor);
});
const roots: ESLAnchorData[] = [];
flatAnchors.forEach(anchor => {
const parentId = anchor.data.parent; // Access data object
if (parentId && map.has(parentId)) {
anchor.parent = parentId;
map.get(parentId)!.children!.push(anchor);
} else {
roots.push(anchor);
}
});
return roots;
};
// Register the builder
ESLAnchornav.setHierarchyBuilder('parent', buildByParent);
Then use it with the group-by attribute:
<esl-anchornav group-by="parent"></esl-anchornav>
Markup example:
<h2 esl-anchor id="section1" title="Section 1">Section 1</h2>
<h3 esl-anchor="parent: section1" id="subsection1-1" title="Subsection 1.1">Subsection 1.1</h3>
Alternatively, you can override the buildHierarchy() method in a subclass for component-specific logic.
Empty State Handling
The component automatically sets the empty boolean attribute when no anchors are found. You can use the empty-class attribute to apply CSS classes to handle this state, and empty-class-target to specify which element should receive these classes (defaults to the component itself):
<!-- Hide the component when empty -->
<esl-anchornav empty-class="d-none"></esl-anchornav>
<!-- Hide parent container when navigation is empty -->
<div class="sidebar">
<esl-anchornav empty-class="d-none" empty-class-target="::parent"></esl-anchornav>
</div>
<!-- Or use CSS with the empty attribute -->
<style>
esl-anchornav[empty] { display: none; }
</style>
ESLAnchornav
Public API
setRenderer- a static method to set item renderer. Can be called with just a renderer function (sets as 'default') or with a name and renderergetRenderer- a static method to get item renderer with specified namesetHierarchyBuilder- a static method to set hierarchy builder. Can be called with just a builder function (sets as 'level') or with a name and buildergetHierarchyBuilder- a static method to get hierarchy builder with specified namerenderItem(data, index?, renderer?)- renders a single anchor item and registers it in the internal items map. Must be used when rendering nested items to ensure proper registration and active state trackingactive(ESLAnchorData) - active anchor data objectempty(boolean, readonly) - indicates whether the component has no anchors to displayoffset(number) - anchornav top offset in pixels, used when detecting active anchors (0 by default)update()- performs a full refresh cycle: recollects anchors list and updates the UI state. Use when the set/order of anchors may have changed
Attributes | Properties:
-
renderer- item renderer which is used to build inner markup (defaults to'default') -
active-class- CSS classes to set on active item (defaults to'active') -
empty-class- CSS classes to set on container when there are no anchors -
empty-class-target- selector (ESLTraversingQuery syntax) to find the container to applyempty-classmarker. Defaults to the component itself if empty -
anchor-selector- selector (ESLTraversingQuery syntax) used to find anchors (defaults to[esl-anchor]) -
group-by- grouping mode for building hierarchy:'level'to group by anchorleveldata from theesl-anchorattribute, empty string for flat list (defaults to'') -
empty(readonly) - boolean attribute to mark that no anchors were found on the page
Events
esl:anchornav:activechanged- event to dispatch onESLAnchornavwhen active item changedesl:anchornav:updated- event to dispatch onESLAnchornavupdated state
ESLAnchornavSticked
To implement the sticky behavior of a component, you can use the ESLAnchornavSticked mixin. Register the mixin and add the esl-anchornav-sticked attribute to the anchor element container.