Repository: jimmynotjim/scrollNav
Branch: master
Commit: 4d78f2d6c560
Files: 70
Total size: 119.4 KB
Directory structure:
gitextract_xvjymejg/
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .jestrc.json
├── .prettierrc.json
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE-MIT
├── README.md
├── bower.json
├── dist/
│ ├── scrollnav.min.mjs
│ └── scrollnav.min.umd.js
├── jest.setup.js
├── jest.transform.js
├── package.json
├── src/
│ ├── scrollNav.v2-7-3.js
│ ├── scrollTo.js
│ ├── scrollnav.js
│ ├── setupClickHandlers.js
│ ├── setupResizeHandler.js
│ ├── setupScrollHandler.js
│ ├── teardownClickHandlers.js
│ ├── teardownResizeHandler.js
│ ├── teardownScrollHandler.js
│ └── util/
│ ├── calculateScrollDuration.js
│ ├── createList.js
│ ├── createNav.js
│ ├── easing.js
│ ├── extend.js
│ ├── getActiveSection.js
│ ├── getOrSetID.js
│ ├── getTargetYPosition.js
│ ├── getYPosition.js
│ ├── insertNav.js
│ ├── insertVisualDebugger.js
│ ├── nextUntil.js
│ ├── populateSectionData.js
│ ├── updateActiveNavItem.js
│ └── updatePositionData.js
└── test/
├── .eslintrc.json
├── fixtures/
│ ├── navMarkup.js
│ ├── noSectionsMarkup.js
│ ├── pennerEasing.js
│ ├── sectionData.js
│ └── sectionMarkup.js
├── tests/
│ ├── scrollTo.test.js
│ ├── scrollnav.test.js
│ ├── setupClickHandlers.test.js
│ ├── setupResizeHandler.test.js
│ ├── setupScrollHander.test.js
│ ├── teardownClickHandlers.test.js
│ ├── teardownResizeHandler.test.js
│ ├── teardownScrollHander.test.js
│ └── util/
│ ├── calculateScrollDuration.test.js
│ ├── createList.test.js
│ ├── createNav.test.js
│ ├── easing.test.js
│ ├── extend.test.js
│ ├── getActiveSection.test.js
│ ├── getOrSetID.test.js
│ ├── getTargetYPosition.test.js
│ ├── getYPosition.test.js
│ ├── insertNav.test.js
│ ├── insertVisualDebugger.test.js
│ ├── nextUntil.test.js
│ ├── populateSectionData.test.js
│ ├── updateActiveNavItem.test.js
│ └── updatePositionData.test.js
└── util/
└── simulateEvent.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintignore
================================================
test/coverage
================================================
FILE: .eslintrc.json
================================================
{
"env": {
"browser": true,
"es6": true
},
"plugins": ["prettier"],
"extends": ["eslint:recommended", "prettier"],
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2017
},
"globals": {
"module": false
},
"rules": {
"prettier/prettier": "error",
"linebreak-style": ["error", "unix"],
"quotes": ["error", "single"],
"semi": ["error", "always"]
}
}
================================================
FILE: .gitignore
================================================
# System Files
*.swp
.DS_Store
# Node Files
/node_modules/
npm-debug.log
bower_components
## Test Coverage Files
test/coverage/
================================================
FILE: .jestrc.json
================================================
{
"collectCoverage": true,
"coverageDirectory": "test/coverage",
"collectCoverageFrom": ["src/**/*.js"],
"coveragePathIgnorePatterns": ["/node_modules", "/src/scrollNav.v2-7-3.js"],
"transform": {
"^.+\\.js$": "<rootDir>/jest.transform.js"
},
"setupFiles": ["<rootDir>/jest.setup.js"]
}
================================================
FILE: .prettierrc.json
================================================
{
"singleQuote": true
}
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- "8"
- "10"
install:
- npm install
- npm install codecov
- codecov
================================================
FILE: CHANGELOG.md
================================================
# Changelog
Releases are be numbered in the semantic versioning format:
`<major>.<minor>.<patch>`
And constructed with these guidelines:
* Breaking backwards compatibility bumps the major
* New additions without breaking backwards compatibility bumps the minor
* Bug fixes and misc changes bump the patch
For more information on semantic versioning, please visit http://semver.org/.
## v3.0.2 - December 25, 2018
Improved the documentation for browser support and removed polyfills covered by
polyfills.io.
## v3.0.1 - December 22, 2018
Update file name casing for consistency.
## v3.0.0 - December 22, 2018
Version 3 is a complete re-write of scrollnav to move the project away from
jQuery, update to the latest es6 features, and remove all production
dependencies. This update also removes a number of user options to simplify
the API and reduces the amount of DOM manipulation to reduce likelihood of
browser bugs. Includes a full set of test suites for all modules (unless
noted). Please read through the [readme](https://github.com/jimmynotjim/scrollnav/blob/master/README.md) for further
details.
## v2.7.3 - March 19, 2018
* Added BEM-style active classes
## v2.7.2 - March 18, 2018
* Fixed grunt:jshint error
* Fixed readme typo
* Replaced andSelf() calls with addBack()
* Updated semver version to fix vulnerability
## v2.7.1 - February 18, 2015
* Fixed package.json
## v2.7.0 - February 11, 2015
* Add `activeClass` setting for custom styles
* Add `__focused-section` when a section is scrolled to
## v2.6.0 - February 19, 2015
* Added in-view and active logic to sub-sections
## v2.5.0 - January 18, 2015
* Add scrollToHash setting
## v2.4.0 - November 13, 2014
* Adds a new resetPos public function for updating the section positions when the page's content changes. Refer to the Readme for more details.
## v2.3.1 - October 30, 2014
* Patched an active section bug where no sections were active when a section matched the exact pixel offset calculated
## v2.3.0 - October 9, 2014
* Updated min Node to 0.10.\*
* Updated all Node plugins to latest
## v2.2.0 - March 14, 2014
* Added option to change the classname used throughout the plugin.
* Updated jQuery dep to 1.9.\*
## v2.1.1 - December 13, 2013
Added `scrollnav` namespace to event listeners to avoid crashing in to user defined event listeners.
## v2.1.0 - November 15, 2013
v2.1 adds support for public methods, a new destroy method, custom callbacks for init, render and destroy, and small bugfixes.
* Reorganized core to allow for new public methods
* Added destroy method to core to allow for breaking down the plugin and it's dom \* changes
* Added the option to add callback functions to init, render and destroy
* Squashed a bug when wrapping sub-sections in their div
* Updated core init in test for easier reuse
* Fixed Grunt test:browser task to only run in the browser
* Added destroy tests to core module and a new module for the callbacks
## v2.0.2 - October 31, 2013
Double releasing to include changelog and readme updates in Bower. One day I'll figure all this out :)
## v2.0.1 - October 31, 2013
Bugfixes for the following:
* package.json now includes semver: Ooops, sorry
* Moved jQuery dependency from package.json to bower.json and updated jQuery dep to v1.8.\*
* Removed unneeded build dirs from bower install
* Updated gitignore to ignore bower_components
## v2.0.0 - October 11, 2013
v2 is a complete re-write of scrollnav and includes better code organization, new options, updated options, grunt integration, and bugfixes. This update will most like break your options and markup from v1, please read through the following changes and reference the [readme](https://github.com/jimmynotjim/scrollnav/blob/master/README.md) for further details.
* Updated to wrap each section in a `section` tag and support for sub-sections (as well as an option to change the wrapping element).
* Fixed the active state when scrolling, no longer switches to the next nav item as the page finishes scrolling.
* There are now two classes added to the nav item when scrolling, `active` and `in-view`. All sections within the view bounds are marked as `in-view` and the topmost section is marked as `active`.
* Added grunt workflow. Edits to the plugin should be made in `/src/scrollnav.js` with tests to support them and jshint, qunit, concat and uglify should be run before submitting PRs.
* Added parsing of url for an element hash. This fixes inbound urls with a hash not scrolling to the section if plugin loading is delayed.
* Added arrow key navigation option. When enabled, users can jump from section to section using the up/down arrow keys.
* Added option to change insertion target.
* Added Bower support for easy project dependency management.
* Updated the class names of scrollnav elements to follow BEM conventions.
* Updated existing settings option naming for the following (_this may break your current setups_):
* `titleText` -> `headlineText`
* `location` -> `insertLocation`
## v1.2.0
Added topLinkText option to customize the top link text (Thanks to [Wizcover][11]).
## v1.1.4
Updated Readme to include new options.
## v1.1.3
Added option for insertion location.
## v1.1.2
Added option to remove Top Link from the nav.
## v1.1.1
Fixed html rather than text being added to nav elements where heading contains nested elems.
## v1.1.0
Added options for showing Headline Text and animation speed.
## v1.0.1
Added ARIA support to nav elem (Thanks to [Jeff Coburn][10]).
## v1.0.0
Initial Release of scrollnav
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing Guideline
Thanks for contributing to scrollnav. Without you and the Open Source community
this project wouldn't be possible. Before you get started, please familiarize
yourself with the project and its standards.
## Issue Reports
If you're looking for a way to contribute to this project, your best bet is
to [start with the current issues][1]. Whether it's a new feature you'd like
added, a bug you've found, or a question about using the plugin, there's always
a chance you're not alone. Take a look through both the open and closed issues
before creating a new one yourself.
### Improvements/features
Before working on a new feature please submit an issue suggesting the feature
and ensure that it is greenlighted. I'd hate for you to spend your time
building a new feature that doesn't fit the project's scope or intentions.
### Bugs
Before submitting a bug report, please create a reproducable test, either as a
failing unit test on your own fork, or by forking the starter demo from Codepen
and adjusting as necessary. It's a whole lot easier to address issues if I can
understand the situation. If it's not obvious what's broken in your demo,
include step-by-step instructions on how to reproduce the problem within the
issue description.
## Pull Requests
You've found a bug or had a feature greenlighted and you're ready to dig in and
get to work. Congrats! This project was built and continues to improve from
direct contributions from folks like you. Before you get too far, please be
sure to read through and understand the steps below.
### Important notes
Please don't edit files in the `dist` subdirectory as they are generated via
NPM script. You'll find source code in the `src` subdirectory.
### Code style
This project utilizes [Prettier][11] to ensure a consistent code style. Write
your changes however you prefer but be sure to run `npm run format` prior to
submitting a Pull Request. Don't worry about making a lot of small commits,
your changes will be squashed within Github when the Pull Request is merged.
### Testing
This project uses [Jest][12] for its unit tests. Prior to submitting a
Pull Request, please ensure new functionality is tested and adjustments to
previous functionality continue to pass. A failing test is better than no
test at all and gives me a place to start reviewing.
Additionally, it's a good idea to ensure your changes work in actual browsers.
Including a demo on [Codepen][13] is a great way to ensure your changes are
reviewed and merged in a timely manner. I've [created a base Pen][2] for you
to fork and edit.
### Modifying the code
First, ensure that you are working with [Node.js][14] version 8 or later and
[npm][15] version 5 or later. Older versions of Node are supported but it's
best to work with the Long Term Support (LTS), currently version 8.
1. Fork and clone the repo.
1. Run `npm install` to install the development dependencies.
1. Create a new branch that's appropriately named. Please don't work directly
in your `master` branch, it makes testing locally difficult.
1. Add failing tests for the change you want to make in the `test/tests`
directory. Run `npm test` to see the tests fail.
1. Edit the source files in the `src` directory.
1. Run `npm test` to see if the tests pass. Repeat steps 2-4 until done.
1. Run `npm run build` to generate the transpiled files in the `dist`
directory.
1. Update the documentation to reflect any changes in functionality.
1. Commit and push your the branch to Github.
1. Navigate to your branch on Github and then to the
`dist/scrollnav.min.js` file.
1. Select the "Raw" button and copy the file URL.
1. Fork the starter demo in Codepen and then click settings.
1. Under "Add External Scripts/Pens" replace the existing
`dist/scrollnav.min.js` file URL with your raw file URL.
1. Test the interactions and ensure your intended changes work as expected.
1. Submit a Pull Request making sure to write a descriptive comment and
include your forked Codepen URL.
**Note: If none of this makes sense, it's ok. Any contribution is a useful one,
even if it's not accepted. Please feel free to reach out to me to ask for help
or clarification, I'm [@jimmynotim on Twitter][21].**
To view all available build tasks run `npm run` without any arguments.
[1]: https://github.com/jimmynotjim/scrollnav/issues
[2]: https://codepen.io/jimmynotjim/pen/OZKeyd
[11]: https://prettier.io/
[12]: https://facebook.github.io/jest/
[13]: https://codepen.io/
[14]: http://nodejs.org/
[15]: http://npmjs.org/
[21]: https://twitter.com/jimmynotjim
================================================
FILE: LICENSE-MIT
================================================
Copyright (c) 2018 James Wilson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
> This project is currently archived. I unfortunately don't have the time to maintain it. Thanks to everyone that contributed along the way, it wouldn't have been possible without you.
# [scrollnav.js][1]
[](https://www.npmjs.com/package/scrollnav)
[](https://travis-ci.org/jimmynotjim/scrollnav)
[](https://codecov.io/gh/jimmynotjim/scrollnav)
[](https://github.com/jimmynotjim/scrollnav/blob/master/LICENSE-MIT)
## Introduction
scrollnav.js is a small (2.4kb gzipped), dependency free JavaScript plugin
for auto generating single page navigation with active highlighting. Useful
for creating a Table of Contents for a large document (think Wikis),
navigation for a single page website, or anything else you might think of.
scrollnav works by scanning a block of content for section landmarks
(typically heading elements) and generating a list of links from those
landmarks. It then tracks the scroll location of the document and highlights
the appropriate link. While previous versions injected wrappers within the
content, the current version (ver 3) takes a much lighter approach, only
changing the DOM as necessary. Visit the live demo at [scrollnav.com][1]
to see for yourself.
## Browser Compatibility
To keep scrollnav small, default support starts with
[ES6 compatible browsers](https://caniuse.com/#feat=arrow-functions). To support
[ES5 compatible browsers](https://caniuse.com/#feat=es5) you must provide your
own polyfills or rely on a third party library like pollyfills.io. I personally
use the following
[polyfill.io](https://github.com/Financial-Times/polyfill-service) feature
parameters to support scrollnav in IE 10 & 11.
```html
<script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=default,NodeList.prototype.forEach,Array.prototype.includes"></script>
```
To add your own polyfills you will need to build the project from source.
## Getting Started
The compiled, production ready plugin is available in the `dist` directory.
Please don't use the `src` directory unless you plan to build the entire
source.
### Install
#### Download
[scrollnav@v3.0.2](https://unpkg.com/scrollnav@3.0.2/dist/scrollnav.min.umd.js)
```html
<script src="[your assets directory]/scrollnav.min.umd.js"></script>
```
#### CDN
```html
<script src="https://unpkg.com/scrollnav@3.0.2/dist/scrollnav.min.umd.js"></script>
```
#### Package manager
[Yarn][13]: `yarn add scrollnav`
It's the new hotness, it's also better at managing dependencies than all it's predecesors.
[NPM][12]: `npm install scrollnav`
Good'ol NPM, it's always there, except when it isn't. Things have settled down a bit, but it was dicey there for a while. Even still, there's a reason even Yarn uses the NPM registry.
[Bower][11]: `bower install scrollnav --save`
The folks from Bower no longer recommend using Bower. Luckily they've provided a guide on [how to migrate to Yarn](https://bower.io/blog/2017/how-to-migrate-away-from-bower/). If you don't want to or can't migrate, scrollnav will continue to be available on Bower as long as it continues to run.
### Usage
scrollnav works by scanning the given [HTML Node Element][25] for section
landmarks, by default `h2` elements, that it then uses to generate the nav.
If we were to look at a typical document, it might look like this:
```html
<div class="main-content">
<h2>First section</h2>
...
<h2>Second section</h2>
...
<h2>Third section</h2>
...
</div>
```
#### Initialize
First, initialize scrollnav with the HTML Element. In this example we'll use
`.querySelector()` but you could also use `.getElementByID()` or
`.getElementByClassName()`.
```js
const content = document.querySelector('.main-content');
scrollnav.init(content);
```
scrollnav will then loop through the the `h2` elements, add an ID if they don't
already have one, build the nav, and then inject it just before the content
Node. The result for our example document would look like this:
```html
<nav class="scroll-nav">
<ol class="scroll-nav__list">
<li class="scroll-nav__item">
<a class="scroll-nav__link" href="#scroll-nav__1">
First heading
<a>
</li>
...
</ol>
</nav>
<div class="main-content">
<h2 id="scroll-nav__1">First Heading</h2>
...
</div>
```
#### Styles
To keep the plugin simple there are no styles added to the navigation, that's
all up to you ([view the demo site][1] for exmaples of the most common use
cases). The nav structure provides [BEM Methodology][23] class names for each
of the elements to provide consistent styling hooks (for a good overview read
[MindBEMding - getting your head 'round BEM syntax][24]). As the user scrolls
the document, scrollnav adds a `scroll-nav__item--active` modifier for the
item's relative section that currently intersects with the activation
threshold ([enable `debug` mode](#additional-options) to highlight the
threshold).
### Settings and options
scrollnav includes some default settings that work for most situations, but if
your project requires a bit more customization, scrollnav can most likely meet
those. To modify either, pass in a single object (include settings and options
as one object) as the second argument like this:
```js
scrollnav.init(content, {
key: value
});
```
#### Default settings
The following settings are editable to overwrite the default.
```js
{
sections: 'selector',
// string
//
// Sets the querySelector for the content's section landmarks, by default
// it's 'h2'.
insertTarget: targetNode,
// HTML Node
//
// Sets the target Node for injecting the navigation, by default it's the
// content Node passed to scrollnav.
insertLocation: 'relativeLocation'
// string
//
// Sets the injection location relative to the insertTarget, by default it's
// 'before'.
//
// available options are 'append', 'prepend', 'after', or 'before'
easingStyle: 'easingName',
// string
//
// Sets the easing type for the scroll animation that is triggered by the
// click event on a nav item, by default it's 'easeOutQuad'.
//
// available options are 'linear' 'easeInQuad', 'easeOutQuad',
// 'easeInOutQuad', 'easeInCubic', 'easeOutCubic', 'easeInOutCubic',
// 'easeInQuart', 'easeOutQuart', 'easeInOutQuart', 'easeInQuint',
// 'easeOutQuint', easeInOutQuint
updateHistory: true
// boolean
//
// Sets the history behavior when a nav item is clicked, by default it's true
}
```
#### Additional options
These additional options are editable but are not set by default.
```js
{
subSections: '...',
// string
//
// Sets the querySelector for the content's sub-section landmarks.
onScroll: function() {...},
// function
//
// Sets the callback to be triggered after the window scrolls when triggered
// by the click event on a nav item.
onInit: function() {...},
// function
//
// Sets the callback to be triggered after the .init() method has completed.
onUpdatePositions: function() {...},
// function
//
// Sets the callback to be triggered after the .updatePositions() method
// has completed.
onDestroy: function() {...},
// function
//
// Sets the callback to be triggered after the .destroy() method has
// completed.
debug: false
// boolean
//
// Enables scrollnav's built in debug mode to log errors to the console and
// display the active area threshold on screen, helpful for when you've hit a
// snag you can't easily identify.
}
```
#### Available methods
In addition to the `.init()` method scrollnav provides two additional public
methods.
#### destroy()
To remove the current instance of scrollnav call the destroy method. If you
need to trigger a callback after scrollnav has been removed, use the
`onDestroy` option described above (passed either in the original init or with
the destroy method).
```js
scrollnav.destroy();
```
#### updatePositions()
scrollnav doesn't track outside DOM changes. If your page's content is dynamic
and updates after scrollnav is initialized you'll need to recalcuate the
position data with the updatePositions method. If you need to trigger a
callback after the position data has been recalculated, use the
`onUpdatePositions` option described above (passed either in the original init
or with the updatePositions method).
```js
scrollnav.updatePositions();
```
## Issues
Please read and understand the [Contributing Guidelines][4] prior to [opening
an issue][2]. Ensuring your issue conforms to the guidelines gives it a better
chance I'll be able to help address it.
## Questions
For questions about using scrollnav in your own project, your best bet is to
post it to [Stack Overflow][21]. The community there is great at lending a hand
and can often respond faster than I can, plus it becomes searchable for future
developers who may run into the same question. If you're still stuck, please
feel free to reach out to me to ask for help or clarification, I'm [@jimmynotim
on Twitter][22].
## Changelog
v3.0.2 is the current stable release. For detailed changes in each release
please refer to the [release notes][5]. Please be sure you understand the
changes before updating, v3 is a complete re-write of the plugin (as is v2
compared to v1 before it).
## Contributions
scrollnav is built and maintained by [James Wilson (@jimmynotjim)][31].
I wouldn't be able to continue this project without a lot of help from the
Open Source community. I welcome feedback and enhancements, but first, please
make sure to read the [Contributing Guide][4].
Thank you to everyone who has already contributed to scrollnav!
* [Chris Garcia (@pixelbandito)][43]
* [Eric Clemmons (@ericclemmons)][32]
* [Felix Borzik (@Borzik)][39]
* [Jeff Byrnes (@jeffbyrnes)][33]
* [Jeff Coburn (@coburnicus)][34]
* [Jen Germann (@germanny)][35]
* [Jim Schmid (@sheeep)][44]
* [Marc Amos (@marcamos)][38]
* [Masud Rahman (@frutiger)][40]
* [Meghdad Hadidi (@MeghdadHadidi)][37]
* [Michael Benin (@michael-benin-CN)][45]
* [Rob Loach (@RobLoach)][41]
* [Thomas Guillary @thomasguillory][46]
* [Will Moore (@willthemoor)][42]
* [Wizcover (@wizcover)][36]
## License
scrollnav is Copyright © 2012-2018 James Wilson, released under the
[MIT license][3]. This means you can re-create, edit or share the plugin as
long as you maintain the same open licensing.
[1]: http://scrollnav.com
[2]: https://github.com/jimmynotjim/scrollnav/issues
[3]: https://github.com/jimmynotjim/scrollnav/blob/master/LICENSE-MIT
[4]: https://github.com/jimmynotjim/scrollnav/blob/master/CONTRIBUTING.md
[5]: https://github.com/jimmynotjim/scrollnav/blob/master/CHANGELOG.md
[11]: https://bower.io/
[12]: https://www.npmjs.com/package/scrollnav
[13]: https://yarnpkg.com/en/package/scrollnav
[21]: https://stackoverflow.com/questions
[22]: https://twitter.com/jimmynotjim
[23]: http://bem.info/method/
[24]: http://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/
[25]: https://developer.mozilla.org/en-US/docs/Web/API/Element
[31]: http://github.com/jimmynotjim
[32]: https://github.com/ericclemmons
[33]: https://github.com/jeffbyrnes
[34]: https://github.com/coburnicus
[35]: https://github.com/germanny
[36]: https://github.com/wizcover
[37]: https://github.com/MeghdadHadidi
[38]: http://github.com/marcamos
[39]: http://github.com/borzik
[40]: http://github.com/frutiger
[41]: http://github.com/RobLoach
[42]: http://github.com/willthemoor
[43]: http://github.com/pixelbandito
[44]: http://github.com/sheeep
[45]: http://github.com/michael-benin-CN
[46]: http://github.com/thomasguillory
================================================
FILE: bower.json
================================================
{
"name": "scrollnav",
"version": "3.0.2",
"author": {
"name": "James Wilson",
"email": "jimmynotjim@me.com",
"url": "http://jimmynotjim.com"
},
"repository": {
"type": "git",
"url": "https://github.com/jimmynotjim/scrollnav.git"
},
"main": ["./dist/scrollnav.min.umd.js"],
"dependencies": {},
"ignore": [
"node_modules",
"libs",
"src",
"test",
".*",
"*.json",
"CONTRIBUTING.md",
"*.txt"
],
"keywords": [
"scrollnav",
"javascript",
"es6",
"jquery",
"navigation",
"scrolling",
"sticky nav",
"scrolling nav"
]
}
================================================
FILE: dist/scrollnav.min.mjs
================================================
function f(f,h){var y,w={};for(y in f)Object.prototype.hasOwnProperty.call(f,y)&&(w[y]=f[y]);for(y in h)Object.prototype.hasOwnProperty.call(h,y)&&(w[y]=h[y]);return w}function h(f,h){if("object"!=typeof f)return Promise.reject(new Error("First argument must be an object"));if("object"!=typeof(h=h||document.body))return Promise.reject(new Error("Second argument must be an object"));var y=h.getBoundingClientRect();return f.getBoundingClientRect().top-y.top}function y(f,w,E){void 0===E&&(E="scroll-nav");var L=[];return E+="__",f.forEach(function(f,O){var x=[],j=function(f,h){if("object"!=typeof f)return Promise.reject(new Error("First argument must be an object"));var y=f.id;if(!y){if("string"!=typeof h)return Promise.reject(new Error("Second argument must be a string"));f.id=y=h}return y}(f,E+(O+1));w.subSections&&f.matches(w.sections)&&(x=y(function(f,h,y){var w=[];for(f=f.nextElementSibling;f&&!f.matches(h);)!y||f.matches(y)?(w.push(f),f=f.nextElementSibling):f=f.nextElementSibling;return w}(f,w.sections,w.subSections),w,j));L.push({id:j,text:f.innerText||f.textContent,offsetTop:h(f),subSections:x})}),L}function w(f){var h=document.createElement("nav");return h.className="scroll-nav",h.innerHTML=function f(h,y){void 0===y&&(y=!1);var w="scroll-nav"+(y?"__sub-":"__"),E="\n "+h.map(function(h){return'<li class="'+w+'item" data-sn-section="'+h.id+'">\n <a class="'+w+'link" href="#'+h.id+'">'+h.text+"</a>\n "+(h.subSections&&h.subSections.length?""+f(h.subSections,!0):"")+"\n </li>"}).join("")+"\n ";return'\n <ol class="'+w+'list">\n '+E+"\n </ol>\n "}(f),h}function E(f){return f.forEach(function(f){var y=document.querySelector("#"+f.id);f.offsetTop=h(y),f.subSections.length&&(f.subSections=E(f.subSections))}),f}function L(f,h){var y=f.getAttribute("href");return"#"===y.charAt(0)&&(y=y.substr(1)),function f(h,y){var w;h.forEach(function(h){h.id===y&&(w=h),h.subSections&&void 0===w&&(w=f(h.subSections,y))});return w}(h,y).offsetTop}var O,x,j,_=function(f){return function(h){return Math.pow(h,f)}},I=function(f){return function(h){return 1-Math.abs(Math.pow(h-1,f))}},Q=function(f){return function(h){return h<.5?_(f)(2*h)/2:I(f)(2*h-1)/2+.5}},C={linear:Q(1),easeInQuad:_(2),easeOutQuad:I(2),easeInOutQuad:Q(2),easeInCubic:_(3),easeOutCubic:I(3),easeInOutCubic:Q(3),easeInQuart:_(4),easeOutQuart:I(4),easeInOutQuart:Q(4),easeInQuint:_(5),easeOutQuint:I(5),easeInOutQuint:Q(5)};function M(f,h){return new Promise(function(y,w){if("number"!=typeof f)return w(new Error("First argument must be a number"));if("string"!=typeof(h=h||"linear"))return w(new Error("Second argument must be a string"));var E,L=window.pageYOffset,O=f-L,x=function(f){var h=Math.abs(f/2);return Math.min(Math.max(h,250),1200)}(O),j=20,_=0;!function f(){E=C[h]((_+=j)/x),window.scroll(0,E*O+L),_<x?setTimeout(f,j):y(window.pageYOffset)}()})}function q(f){function h(){var h=window.scrollY||window.pageYOffset||document.body.scrollTop,y=h+.4*window.innerHeight,w=function f(h,y,w){var E,L;h.forEach(function(f){f.offsetTop>w?!E&&f.offsetTop<y&&(E=f):E=f}),E&&E.subSections.length&&(L=f(E.subSections,y,w))&&(E=L);return E}(f.data,h,y);return function(f,h){var y=h.querySelector("[data-sn-active]");if(f){var w=h.querySelector("[data-sn-section="+f.id+"]");w&&w!==y&&(y&&(y.classList.remove("scroll-nav__item--active"),y.removeAttribute("data-sn-active")),w.classList.add("scroll-nav__item--active"),w.setAttribute("data-sn-active",!0))}else y&&(y.classList.remove("scroll-nav__item--active"),y.removeAttribute("data-sn-active"))}(w,f.nav),w}return window.addEventListener("scroll",h),h}function B(f){return f instanceof Element}export default{init:function(h,_){if(this.settings=f({sections:"h2",insertTarget:h,insertLocation:"before",easingStyle:"easeOutQuad",updateHistory:!0},_),B(h))if(!this.settings.insertTarget||B(this.settings.insertTarget))if(["append","prepend","after","before"].includes(this.settings.insertLocation)){var I,Q,C,F,R=h.querySelectorAll(this.settings.sections);if(R.length)return this.data=y(R,this.settings),this.nav=w(this.data),Q=(I=this).settings.insertTarget,"append"===(C=I.settings.insertLocation)?Q.appendChild(I.nav):"prepend"===C?Q.insertBefore(I.nav,Q.firstChild):"before"===C?Q.parentNode.insertBefore(I.nav,Q):"after"===C&&Q.parentNode.insertBefore(I.nav,Q.nextSibling),O=function(f){var h=f.settings;function y(y){y.preventDefault();var w=.39*window.innerHeight;return M(L(y.target,f.data)-w,h.easingStyle).then(function(){h.updateHistory&&history.replaceState({},"",y.target.getAttribute("href")),h.onScroll&&h.onScroll()})}return f.nav.querySelectorAll("a").forEach(function(f){f.addEventListener("click",y)}),y}(this),x=q(this),j=function(f){function h(){f.data=E(f.data)}return window.addEventListener("resize",h),h}(this),this.settings.debug&&((F=document.createElement("div")).className="snDebugger",F.setAttribute("style","\n position: fixed;\n top: 40%;\n height: 0px;\n border-bottom:5px solid red;\n border-top: 5px solid blue;\n width: 100%;\n opacity: .5;\n pointer-events: none;\n "),document.body.appendChild(F)),this.settings.onInit?this.settings.onInit():void 0;this.settings.debug&&console.error('\n scrollnav build failed, could not find any "'+this.settings.sections+'"\n elements inside of "'+h+'"\n ')}else this.settings.debug&&console.error('\n scrollnav build failed, options.insertLocation "'+this.settings.insertLocation+'" is not a valid option\n ');else this.settings.debug&&console.error('\n scrollnav build failed, options.insertTarget "'+h+'" is not an HTML Element\n ');else this.settings.debug&&console.error('\n scrollnav build failed, content argument "'+h+'" is not an HTML Element\n ')},destroy:function(h){if(this.settings=f(this.settings,h),function(f,h){f.querySelectorAll("a").forEach(function(f){f.removeEventListener("click",h)})}(this.nav,O),function(f){window.removeEventListener("scroll",f)}(x),function(f){window.removeEventListener("resize",f)}(j),this.nav.remove(),this.settings.onDestroy)return this.settings.onDestroy()},updatePositions:function(h){if(this.settings=f(this.settings,h),this.data=E(this.data),this.settings.onUpdatePositions)return this.settings.onUpdatePositions()}};
//# sourceMappingURL=scrollnav.min.mjs.map
================================================
FILE: dist/scrollnav.min.umd.js
================================================
!function(f,h){"object"==typeof exports&&"undefined"!=typeof module?module.exports=h():"function"==typeof define&&define.amd?define(h):f.scrollnav=h()}(this,function(){function f(f,h){var y,w={};for(y in f)Object.prototype.hasOwnProperty.call(f,y)&&(w[y]=f[y]);for(y in h)Object.prototype.hasOwnProperty.call(h,y)&&(w[y]=h[y]);return w}function h(f,h){if("object"!=typeof f)return Promise.reject(new Error("First argument must be an object"));if("object"!=typeof(h=h||document.body))return Promise.reject(new Error("Second argument must be an object"));var y=h.getBoundingClientRect();return f.getBoundingClientRect().top-y.top}function y(f,w,E){void 0===E&&(E="scroll-nav");var L=[];return E+="__",f.forEach(function(f,O){var x=[],j=function(f,h){if("object"!=typeof f)return Promise.reject(new Error("First argument must be an object"));var y=f.id;if(!y){if("string"!=typeof h)return Promise.reject(new Error("Second argument must be a string"));f.id=y=h}return y}(f,E+(O+1));w.subSections&&f.matches(w.sections)&&(x=y(function(f,h,y){var w=[];for(f=f.nextElementSibling;f&&!f.matches(h);)!y||f.matches(y)?(w.push(f),f=f.nextElementSibling):f=f.nextElementSibling;return w}(f,w.sections,w.subSections),w,j));L.push({id:j,text:f.innerText||f.textContent,offsetTop:h(f),subSections:x})}),L}function w(f){var h=document.createElement("nav");return h.className="scroll-nav",h.innerHTML=function f(h,y){void 0===y&&(y=!1);var w="scroll-nav"+(y?"__sub-":"__"),E="\n "+h.map(function(h){return'<li class="'+w+'item" data-sn-section="'+h.id+'">\n <a class="'+w+'link" href="#'+h.id+'">'+h.text+"</a>\n "+(h.subSections&&h.subSections.length?""+f(h.subSections,!0):"")+"\n </li>"}).join("")+"\n ";return'\n <ol class="'+w+'list">\n '+E+"\n </ol>\n "}(f),h}function E(f){return f.forEach(function(f){var y=document.querySelector("#"+f.id);f.offsetTop=h(y),f.subSections.length&&(f.subSections=E(f.subSections))}),f}function L(f,h){var y=f.getAttribute("href");return"#"===y.charAt(0)&&(y=y.substr(1)),function f(h,y){var w;h.forEach(function(h){h.id===y&&(w=h),h.subSections&&void 0===w&&(w=f(h.subSections,y))});return w}(h,y).offsetTop}var O,x,j,_=function(f){return function(h){return Math.pow(h,f)}},I=function(f){return function(h){return 1-Math.abs(Math.pow(h-1,f))}},Q=function(f){return function(h){return h<.5?_(f)(2*h)/2:I(f)(2*h-1)/2+.5}},C={linear:Q(1),easeInQuad:_(2),easeOutQuad:I(2),easeInOutQuad:Q(2),easeInCubic:_(3),easeOutCubic:I(3),easeInOutCubic:Q(3),easeInQuart:_(4),easeOutQuart:I(4),easeInOutQuart:Q(4),easeInQuint:_(5),easeOutQuint:I(5),easeInOutQuint:Q(5)};function M(f,h){return new Promise(function(y,w){if("number"!=typeof f)return w(new Error("First argument must be a number"));if("string"!=typeof(h=h||"linear"))return w(new Error("Second argument must be a string"));var E,L=window.pageYOffset,O=f-L,x=function(f){var h=Math.abs(f/2);return Math.min(Math.max(h,250),1200)}(O),j=20,_=0;!function f(){E=C[h]((_+=j)/x),window.scroll(0,E*O+L),_<x?setTimeout(f,j):y(window.pageYOffset)}()})}function q(f){function h(){var h=window.scrollY||window.pageYOffset||document.body.scrollTop,y=h+.4*window.innerHeight,w=function f(h,y,w){var E,L;h.forEach(function(f){f.offsetTop>w?!E&&f.offsetTop<y&&(E=f):E=f}),E&&E.subSections.length&&(L=f(E.subSections,y,w))&&(E=L);return E}(f.data,h,y);return function(f,h){var y=h.querySelector("[data-sn-active]");if(f){var w=h.querySelector("[data-sn-section="+f.id+"]");w&&w!==y&&(y&&(y.classList.remove("scroll-nav__item--active"),y.removeAttribute("data-sn-active")),w.classList.add("scroll-nav__item--active"),w.setAttribute("data-sn-active",!0))}else y&&(y.classList.remove("scroll-nav__item--active"),y.removeAttribute("data-sn-active"))}(w,f.nav),w}return window.addEventListener("scroll",h),h}function B(f){return f instanceof Element}return{init:function(h,_){if(this.settings=f({sections:"h2",insertTarget:h,insertLocation:"before",easingStyle:"easeOutQuad",updateHistory:!0},_),B(h))if(!this.settings.insertTarget||B(this.settings.insertTarget))if(["append","prepend","after","before"].includes(this.settings.insertLocation)){var I,Q,C,F,R=h.querySelectorAll(this.settings.sections);if(R.length)return this.data=y(R,this.settings),this.nav=w(this.data),Q=(I=this).settings.insertTarget,"append"===(C=I.settings.insertLocation)?Q.appendChild(I.nav):"prepend"===C?Q.insertBefore(I.nav,Q.firstChild):"before"===C?Q.parentNode.insertBefore(I.nav,Q):"after"===C&&Q.parentNode.insertBefore(I.nav,Q.nextSibling),O=function(f){var h=f.settings;function y(y){y.preventDefault();var w=.39*window.innerHeight;return M(L(y.target,f.data)-w,h.easingStyle).then(function(){h.updateHistory&&history.replaceState({},"",y.target.getAttribute("href")),h.onScroll&&h.onScroll()})}return f.nav.querySelectorAll("a").forEach(function(f){f.addEventListener("click",y)}),y}(this),x=q(this),j=function(f){function h(){f.data=E(f.data)}return window.addEventListener("resize",h),h}(this),this.settings.debug&&((F=document.createElement("div")).className="snDebugger",F.setAttribute("style","\n position: fixed;\n top: 40%;\n height: 0px;\n border-bottom:5px solid red;\n border-top: 5px solid blue;\n width: 100%;\n opacity: .5;\n pointer-events: none;\n "),document.body.appendChild(F)),this.settings.onInit?this.settings.onInit():void 0;this.settings.debug&&console.error('\n scrollnav build failed, could not find any "'+this.settings.sections+'"\n elements inside of "'+h+'"\n ')}else this.settings.debug&&console.error('\n scrollnav build failed, options.insertLocation "'+this.settings.insertLocation+'" is not a valid option\n ');else this.settings.debug&&console.error('\n scrollnav build failed, options.insertTarget "'+h+'" is not an HTML Element\n ');else this.settings.debug&&console.error('\n scrollnav build failed, content argument "'+h+'" is not an HTML Element\n ')},destroy:function(h){if(this.settings=f(this.settings,h),function(f,h){f.querySelectorAll("a").forEach(function(f){f.removeEventListener("click",h)})}(this.nav,O),function(f){window.removeEventListener("scroll",f)}(x),function(f){window.removeEventListener("resize",f)}(j),this.nav.remove(),this.settings.onDestroy)return this.settings.onDestroy()},updatePositions:function(h){if(this.settings=f(this.settings,h),this.data=E(this.data),this.settings.onUpdatePositions)return this.settings.onUpdatePositions()}}});
//# sourceMappingURL=scrollnav.min.umd.js.map
================================================
FILE: jest.setup.js
================================================
import '@babel/polyfill';
window.scrollTo = window.scroll = (xVal, yVal) => {
window.pageXOffset = xVal;
window.pageYOffset = yVal;
};
window.scrollBy = (xVal, yVal) => {
const xStart = window.getPageXOffset;
const yStart = window.getPageYOffset;
window.pageXOffset = xStart + xVal;
window.pageYOffset = yStart + yVal;
};
================================================
FILE: jest.transform.js
================================================
module.exports = require('babel-jest').createTransformer({
presets: ['@babel/preset-env']
});
================================================
FILE: package.json
================================================
{
"name": "scrollnav",
"version": "3.0.2",
"title": "scrollnav",
"author": {
"name": "James Wilson",
"email": "jimmynotjim@me.com",
"url": "http://jimmynotjim.com"
},
"description": "A small, dependency free JavaScript plugin for auto generating single page navigation",
"keywords": [
"scrollnav",
"javascript",
"es6",
"jquery",
"navigation",
"scrolling",
"sticky nav",
"scrolling nav"
],
"homepage": "http://scrollnav.com",
"repository": {
"type": "git",
"url": "https://github.com/jimmynotjim/scrollnav.git"
},
"files": [
"dist"
],
"main": "dist/scrollnav.min.umd.js",
"bugs": "https://github.com/jimmynotjim/scrollnav/issues",
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
},
"scripts": {
"build": "microbundle --compress false --name scrollnav",
"format": "npm-run-all format:*",
"format:source": "prettier --write 'src/**/*.js'",
"format:test": "prettier --write 'test/**/*.js' !/coverage",
"lint": "npm-run-all lint:*",
"lint:source": "eslint 'src/**/*.js'",
"lint:test": "eslint 'test/**/*.js'",
"precommit": "npm run format && npm run lint && npm run build",
"test": "jest --config .jestrc.json --no-cache",
"watch": "microbundle --watch"
},
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/polyfill": "^7.2.5",
"@babel/preset-env": "^7.2.3",
"@jarmee/jest-dom-custom-matchers": "^1.0.0",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^23.6.0",
"eslint": "^5.10.0",
"eslint-config-prettier": "^3.3.0",
"eslint-plugin-prettier": "^3.0.0",
"husky": "^1.2.1",
"jest": "^23.6.0",
"microbundle": "^0.9.0",
"npm-run-all": "^4.1.5",
"prettier": "^1.15.3"
},
"source": "src/scrollnav.js",
"husky": {
"hooks": {
"pre-commit": "npm run precommit"
}
}
}
================================================
FILE: src/scrollNav.v2-7-3.js
================================================
/*
* scrollNav
* http://scrollnav.com
*
* Copyright (c) 2013-2016 James Wilson
* Licensed under the MIT license.
*/
/* eslint-disable */
(function($) {
// Animate scrolling to section location
var scroll_to = function(value, speed, offset, animated) {
if ($(value).length > 0) {
var destination = $(value).offset().top;
speed = animated ? speed : 0;
// Add a class to the scrolled-to section
$('.' + S.settings.className + '__focused-section').removeClass(
S.settings.className + '__focused-section'
);
$(value).addClass(S.settings.className + '__focused-section');
$('html:not(:animated),body:not(:animated)').animate(
{ scrollTop: destination - offset },
speed
);
}
};
// Get url hash if one exists
var get_hash = function() {
return window.location.hash;
};
var S = {
classes: {
loading: 'sn-loading',
failed: 'sn-failed',
success: 'sn-active'
},
defaults: {
sections: 'h2',
subSections: false,
sectionElem: 'section',
className: 'scroll-nav',
showHeadline: true,
headlineText: 'Scroll To',
showTopLink: true,
topLinkText: 'Top',
fixedMargin: 40,
scrollOffset: 40,
animated: true,
speed: 500,
insertLocation: 'insertBefore',
arrowKeys: false,
scrollToHash: true,
onInit: null,
onRender: null,
onDestroy: null,
onResetPos: null
},
_set_body_class: function(state) {
// Set and swap our loading hooks to the body
var $body = $('body');
if (state === 'loading') {
$body.addClass(S.classes.loading);
} else if (state === 'success') {
$body.removeClass(S.classes.loading).addClass(S.classes.success);
} else {
$body.removeClass(S.classes.loading).addClass(S.classes.failed);
}
},
_find_sections: function($el) {
// Find the html for each section
var target_elems = S.settings.sections;
var raw_html = [];
if (S.settings.showTopLink) {
var $firstElem = $el.children().first();
if (!$firstElem.is(target_elems)) {
raw_html.push($firstElem.nextUntil(target_elems).addBack());
}
}
$el.find(target_elems).each(function() {
raw_html.push(
$(this)
.nextUntil(target_elems)
.addBack()
);
});
S.sections = {
raw: raw_html
};
},
_setup_sections: function(sections) {
// Wrap each section and add it's details to the section array
var section_data = [];
$(sections).each(function(i) {
var sub_data = [];
var $this_section = $(this);
var section_id = 'scrollNav-' + (i + 1);
var isFirst = function() {
return i === 0;
};
var hasHeading = function() {
return !$this_section.eq(0).is(S.settings.sections);
};
var text =
S.settings.showTopLink && isFirst() && hasHeading()
? S.settings.topLinkText
: $this_section.filter(S.settings.sections).text();
$this_section.wrapAll(
'<' +
S.settings.sectionElem +
' id="' +
section_id +
'" class="' +
S.settings.className +
'__section" />'
);
if (S.settings.subSections) {
var $sub_sections = $this_section.filter(S.settings.subSections);
if ($sub_sections.length > 0) {
$sub_sections.each(function(i) {
var sub_id = section_id + '-' + (i + 1);
var sub_text = $(this).text();
var $this_sub = $this_section.filter(
$(this)
.nextUntil($sub_sections)
.addBack()
);
$this_sub.wrapAll(
'<div id="' +
sub_id +
'" class="' +
S.settings.className +
'__sub-section" />'
);
sub_data.push({ id: sub_id, text: sub_text });
});
}
}
section_data.push({
id: section_id,
text: text,
sub_sections: sub_data
});
});
S.sections.data = section_data;
},
_tear_down_sections: function(sections) {
$(sections).each(function() {
var sub_sections = this.sub_sections;
$('#' + this.id)
.children()
.unwrap();
if (sub_sections.length > 0) {
$(sub_sections).each(function() {
$('#' + this.id)
.children()
.unwrap();
});
}
});
},
_setup_nav: function(sections) {
// Populate an ordered list from the section array we built
var $headline = $('<span />', {
class: S.settings.className + '__heading',
text: S.settings.headlineText
});
var $wrapper = $('<div />', {
class: S.settings.className + '__wrapper'
});
var $nav = $('<nav />', {
class: S.settings.className,
role: 'navigation'
});
var $nav_list = $('<ol />', { class: S.settings.className + '__list' });
$.each(sections, function(i) {
var $item =
i === 0
? $('<li />', {
class:
S.settings.className +
'__item ' +
S.settings.className +
'__item--active active'
})
: $('<li />', { class: S.settings.className + '__item' });
var $link = $('<a />', {
href: '#' + this.id,
class: S.settings.className + '__link',
text: this.text
});
var $sub_nav_list;
if (this.sub_sections.length > 0) {
$item.addClass('is-parent-item');
$sub_nav_list = $('<ol />', {
class: S.settings.className + '__sub-list'
});
$.each(this.sub_sections, function() {
var $sub_item = $('<li />', {
class: S.settings.className + '__sub-item'
});
var $sub_link = $('<a />', {
href: '#' + this.id,
class: S.settings.className + '__sub-link',
text: this.text
});
$sub_nav_list.append($sub_item.append($sub_link));
});
}
$nav_list.append($item.append($link).append($sub_nav_list));
});
if (S.settings.showHeadline) {
$nav.append($wrapper.append($headline).append($nav_list));
} else {
$nav.append($wrapper.append($nav_list));
}
S.nav = $nav;
},
_insert_nav: function() {
// Add the nav to our page
var insert_location = S.settings.insertLocation;
var $insert_target = S.settings.insertTarget;
S.nav[insert_location]($insert_target);
},
_setup_pos: function() {
// Find the offset positions of each section
var $nav = S.nav;
var vp_height = $(window).height();
var nav_offset = $nav.offset().top;
var set_offset = function(section) {
var $this_section = $('#' + section.id);
var this_height = $this_section.height();
section.top_offset = $this_section.offset().top;
section.bottom_offset = section.top_offset + this_height;
};
$.each(S.sections.data, function() {
set_offset(this);
$.each(this.sub_sections, function() {
set_offset(this);
});
});
S.dims = {
vp_height: vp_height,
nav_offset: nav_offset
};
},
_check_pos: function() {
// Set nav to fixed after scrolling past the header and add an in-view class to any
// sections currently within the bounds of our view and active class to the first
// in-view section
var $nav = S.nav;
var win_top = $(window).scrollTop();
var boundry_top = win_top + S.settings.scrollOffset;
var boundry_bottom = win_top + S.dims.vp_height - S.settings.scrollOffset;
var sections_active = [];
var sub_sections_active = [];
if (win_top > S.dims.nav_offset - S.settings.fixedMargin) {
$nav.addClass('fixed');
} else {
$nav.removeClass('fixed');
}
var in_view = function(section) {
return (
(section.top_offset >= boundry_top &&
section.top_offset <= boundry_bottom) ||
(section.bottom_offset > boundry_top &&
section.bottom_offset < boundry_bottom) ||
(section.top_offset < boundry_top &&
section.bottom_offset > boundry_bottom)
);
};
$.each(S.sections.data, function() {
if (in_view(this)) {
sections_active.push(this);
}
$.each(this.sub_sections, function() {
if (in_view(this)) {
sub_sections_active.push(this);
}
});
});
$nav
.find('.' + S.settings.className + '__item')
.removeClass(S.settings.className + '__item--active')
.removeClass('active')
.removeClass('in-view');
$nav
.find('.' + S.settings.className + '__sub-item')
.removeClass(S.settings.className + '__sub-item--active')
.removeClass('active')
.removeClass('in-view');
$.each(sections_active, function(i) {
if (i === 0) {
$nav
.find('a[href="#' + this.id + '"]')
.parents('.' + S.settings.className + '__item')
.addClass(S.settings.className + '__item--active')
.addClass('active')
.addClass('in-view');
} else {
$nav
.find('a[href="#' + this.id + '"]')
.parents('.' + S.settings.className + '__item')
.addClass('in-view');
}
});
S.sections.active = sections_active;
$.each(sub_sections_active, function(i) {
if (i === 0) {
$nav
.find('a[href="#' + this.id + '"]')
.parents('.' + S.settings.className + '__sub-item')
.addClass(S.settings.className + '__sub-item--active')
.addClass('active')
.addClass('in-view');
} else {
$nav
.find('a[href="#' + this.id + '"]')
.parents('.' + S.settings.className + '__sub-item')
.addClass('in-view');
}
});
},
_init_scroll_listener: function() {
// Set a scroll listener to update the fixed and active classes
$(window).on('scroll.scrollNav', function() {
S._check_pos();
});
},
_rm_scroll_listeners: function() {
$(window).off('scroll.scrollNav');
},
_init_resize_listener: function() {
// Set a resize listener to update position values and the fixed and active classes
$(window).on('resize.scrollNav', function() {
S._setup_pos();
S._check_pos();
});
},
_rm_resize_listener: function() {
$(window).off('resize.scrollNav');
},
_init_click_listener: function() {
// Scroll to section on click
$('.' + S.settings.className)
.find('a')
.on('click.scrollNav', function(e) {
e.preventDefault();
var value = $(this).attr('href');
var speed = S.settings.speed;
var offset = S.settings.scrollOffset;
var animated = S.settings.animated;
scroll_to(value, speed, offset, animated);
});
},
_rm_click_listener: function() {
$('.' + S.settings.className)
.find('a')
.off('click.scrollNav');
},
_init_keyboard_listener: function(sections) {
// Scroll to section on arrow key press
if (S.settings.arrowKeys) {
$(document).on('keydown.scrollNav', function(e) {
if (e.keyCode === 40 || e.keyCode === 38) {
var findSection = function(key) {
var i = 0;
var l = sections.length;
for (i; i < l; i++) {
if (sections[i].id === S.sections.active[0].id) {
var array_offset = key === 40 ? i + 1 : i - 1;
var id =
sections[array_offset] === undefined
? undefined
: sections[array_offset].id;
return id;
}
}
};
var target_section = findSection(e.keyCode);
if (target_section !== undefined) {
e.preventDefault();
var value = '#' + target_section;
var speed = S.settings.speed;
var offset = S.settings.scrollOffset;
var animated = S.settings.animated;
scroll_to(value, speed, offset, animated);
}
}
});
}
},
_rm_keyboard_listener: function() {
$(document).off('keydown.scrollNav');
},
init: function(options) {
return this.each(function() {
var $el = $(this);
// Merge default settings with user defined options
S.settings = $.extend({}, S.defaults, options);
// If the insert target isn't set, use the initialized element
S.settings.insertTarget = S.settings.insertTarget
? $(S.settings.insertTarget)
: $el;
if ($el.length > 0) {
// Initialize
// Fire custom init callback
if (S.settings.onInit) {
S.settings.onInit.call(this);
}
S._set_body_class('loading');
S._find_sections($el);
if ($el.find(S.settings.sections).length > 0) {
// BUILD!!!!
S._setup_sections(S.sections.raw);
S._setup_nav(S.sections.data);
if (S.settings.insertTarget.length > 0) {
//Add to page
S._insert_nav();
S._setup_pos();
S._check_pos();
S._init_scroll_listener();
S._init_resize_listener();
S._init_click_listener();
S._init_keyboard_listener(S.sections.data);
S._set_body_class('success');
if (S.settings.scrollToHash) {
scroll_to(get_hash());
}
// Fire custom render callback
if (S.settings.onRender) {
S.settings.onRender.call(this);
}
} else {
console.log(
'Build failed, scrollNav could not find "' +
S.settings.insertTarget +
'"'
);
S._set_body_class('failed');
}
} else {
console.log(
'Build failed, scrollNav could not find any "' +
S.settings.sections +
's" inside of "' +
$el.selector +
'"'
);
S._set_body_class('failed');
}
} else {
console.log(
'Build failed, scrollNav could not find "' + $el.selector + '"'
);
S._set_body_class('failed');
}
});
},
destroy: function() {
return this.each(function() {
// Unbind event listeners
S._rm_scroll_listeners();
S._rm_resize_listener();
S._rm_click_listener();
S._rm_keyboard_listener();
// Remove any of the loading hooks
$('body').removeClass('sn-loading sn-active sn-failed');
// Remove the nav from the dom
$('.' + S.settings.className).remove();
// Teardown sections
S._tear_down_sections(S.sections.data);
// Fire custom destroy callback
if (S.settings.onDestroy) {
S.settings.onDestroy.call(this);
}
// Remove the saved settings
S.settings = [];
S.sections = undefined;
});
},
resetPos: function() {
S._setup_pos();
S._check_pos();
// Fire custom reset position callback
if (S.settings.onResetPos) {
S.settings.onResetPos.call(this);
}
}
};
$.fn.scrollNav = function() {
var options;
var method = arguments[0];
if (S[method]) {
// Method exists, so use it
method = S[method];
options = Array.prototype.slice.call(arguments, 1);
} else if (typeof method === 'object' || !method) {
// No method passed, default to init
method = S.init;
options = arguments;
} else {
// Method doesn't exist
$.error('Method ' + method + ' does not exist in the scrollNav plugin');
return this;
}
return method.apply(this, options);
};
})(jQuery);
================================================
FILE: src/scrollTo.js
================================================
import calculateScrollDuration from './util/calculateScrollDuration';
import { easing } from './util/easing';
/* istanbul ignore next */
export default function scrollTo(targetPosition, easingStyle) {
return new Promise((resolve, reject) => {
if (typeof targetPosition !== 'number') {
return reject(new Error('First argument must be a number'));
}
easingStyle = easingStyle || 'linear';
if (typeof easingStyle !== 'string') {
return reject(new Error('Second argument must be a string'));
}
const startingPosition = window.pageYOffset;
const distance = targetPosition - startingPosition;
const duration = calculateScrollDuration(distance);
const framerate = 50;
const increment = 1000 / framerate;
let ellapsedTime = 0;
let easedTime;
let next;
function animateScroll() {
ellapsedTime += increment;
easedTime = easing[easingStyle](ellapsedTime / duration);
next = easedTime * distance + startingPosition;
window.scroll(0, next);
if (ellapsedTime < duration) {
setTimeout(animateScroll, increment);
} else {
resolve(window.pageYOffset);
}
}
animateScroll();
});
}
================================================
FILE: src/scrollnav.js
================================================
/*
* scrollnav
* http://scrollnav.com
*
* Copyright (c) 2013-2018 James Wilson
* Licensed under the MIT license.
*/
import extend from './util/extend';
import populateSectionData from './util/populateSectionData';
import createNav from './util/createNav';
import insertNav from './util/insertNav';
import updatePositionData from './util/updatePositionData';
import insertVisualDebugger from './util/insertVisualDebugger';
import setupClickHandlers from './setupClickHandlers';
import setupScrollHandler from './setupScrollHandler';
import setupResizeHandler from './setupResizeHandler';
import teardownClickHandlers from './teardownClickHandlers';
import teardownScrollHandler from './teardownScrollHandler';
import teardownResizeHandler from './teardownResizeHandler';
let clickHandler;
let scrollHandler;
let resizeHandler;
function isElement(element) {
return element instanceof Element;
}
function init(elem, options) {
const defaults = {
sections: 'h2',
insertTarget: elem,
insertLocation: 'before',
easingStyle: 'easeOutQuad',
updateHistory: true
};
this.settings = extend(defaults, options);
const locationOptions = ['append', 'prepend', 'after', 'before'];
if (!isElement(elem)) {
if (this.settings.debug) {
// eslint-disable-next-line no-console
console.error(`
scrollnav build failed, content argument "${elem}" is not an HTML Element
`);
}
return;
}
if (this.settings.insertTarget && !isElement(this.settings.insertTarget)) {
if (this.settings.debug) {
// eslint-disable-next-line no-console
console.error(`
scrollnav build failed, options.insertTarget "${elem}" is not an HTML Element
`);
}
return;
}
if (!locationOptions.includes(this.settings.insertLocation)) {
if (this.settings.debug) {
// eslint-disable-next-line no-console
console.error(`
scrollnav build failed, options.insertLocation "${
this.settings.insertLocation
}" is not a valid option
`);
}
return;
}
const sectionsDom = elem.querySelectorAll(this.settings.sections);
if (!sectionsDom.length) {
if (this.settings.debug) {
// eslint-disable-next-line no-console
console.error(`
scrollnav build failed, could not find any "${this.settings.sections}"
elements inside of "${elem}"
`);
}
return;
}
this.data = populateSectionData(sectionsDom, this.settings);
this.nav = createNav(this.data);
insertNav(this);
clickHandler = setupClickHandlers(this);
scrollHandler = setupScrollHandler(this);
resizeHandler = setupResizeHandler(this);
if (this.settings.debug) insertVisualDebugger();
if (this.settings.onInit) return this.settings.onInit();
}
function destroy(options) {
this.settings = extend(this.settings, options);
teardownClickHandlers(this.nav, clickHandler);
teardownScrollHandler(scrollHandler);
teardownResizeHandler(resizeHandler);
this.nav.remove();
if (this.settings.onDestroy) return this.settings.onDestroy();
}
function updatePositions(options) {
this.settings = extend(this.settings, options);
this.data = updatePositionData(this.data);
if (this.settings.onUpdatePositions) return this.settings.onUpdatePositions();
}
const scrollnav = {
init: init,
destroy: destroy,
updatePositions: updatePositions
};
export default scrollnav;
================================================
FILE: src/setupClickHandlers.js
================================================
import getTargetYPosition from './util/getTargetYPosition';
import scrollTo from './scrollTo';
export default function setupClickHandlers(scrollnav) {
const settings = scrollnav.settings;
function clickHandler(event) {
event.preventDefault();
const activeArea = window.innerHeight * 0.39;
const targetYPosition = getTargetYPosition(event.target, scrollnav.data);
const scrollYTarget = targetYPosition - activeArea;
/* istanbul ignore next */
return scrollTo(scrollYTarget, settings.easingStyle).then(() => {
if (settings.updateHistory) {
history.replaceState({}, '', event.target.getAttribute('href'));
}
if (settings.onScroll) {
settings.onScroll();
}
});
}
const links = scrollnav.nav.querySelectorAll('a');
links.forEach(link => {
link.addEventListener('click', clickHandler);
});
return clickHandler;
}
================================================
FILE: src/setupResizeHandler.js
================================================
import updatePositionData from './util/updatePositionData';
export default function setupResizeHandler(scrollnav) {
function resizeHandler() {
scrollnav.data = updatePositionData(scrollnav.data);
}
window.addEventListener('resize', resizeHandler);
return resizeHandler;
}
================================================
FILE: src/setupScrollHandler.js
================================================
import getActiveSection from './util/getActiveSection';
import updateActiveNavItem from './util/updateActiveNavItem';
export default function setupScrollHandler(scrollnav) {
function scrollHandler() {
const top = window.scrollY || window.pageYOffset || document.body.scrollTop;
const boundryTop = top;
const boundryBottom = top + window.innerHeight * 0.4;
const activeSection = getActiveSection(
scrollnav.data,
boundryTop,
boundryBottom
);
updateActiveNavItem(activeSection, scrollnav.nav);
return activeSection;
}
window.addEventListener('scroll', scrollHandler);
return scrollHandler;
}
================================================
FILE: src/teardownClickHandlers.js
================================================
export default function teardownClickHandlers(nav, clickHandler) {
const links = nav.querySelectorAll('a');
links.forEach(link => {
link.removeEventListener('click', clickHandler);
});
}
================================================
FILE: src/teardownResizeHandler.js
================================================
export default function teardownResizelHandler(resizeHandler) {
window.removeEventListener('resize', resizeHandler);
}
================================================
FILE: src/teardownScrollHandler.js
================================================
export default function teardownScrollHandler(scrollHandler) {
window.removeEventListener('scroll', scrollHandler);
}
================================================
FILE: src/util/calculateScrollDuration.js
================================================
export default function calculateScrollDuration(distance) {
const halfDistance = Math.abs(distance / 2);
return Math.min(Math.max(halfDistance, 250), 1200);
}
================================================
FILE: src/util/createList.js
================================================
export default function createList(data, isSubList = false) {
const suffix = isSubList ? '__sub-' : '__';
const baseClass = 'scroll-nav' + suffix;
const itemsMarkup = `
${data
.map(
item =>
`<li class="${baseClass}item" data-sn-section="${item.id}">
<a class="${baseClass}link" href="#${item.id}">${item.text}</a>
${
item.subSections && item.subSections.length
? `${createList(item.subSections, true)}`
: ''
}
</li>`
)
.join('')}
`;
const list = `
<ol class="${baseClass}list">
${itemsMarkup}
</ol>
`;
return list;
}
================================================
FILE: src/util/createNav.js
================================================
import createList from './createList';
export default function createNav(data) {
const nav = document.createElement('nav');
nav.className = 'scroll-nav';
nav.innerHTML = createList(data);
return nav;
}
================================================
FILE: src/util/easing.js
================================================
/* Borrowed from https://gist.github.com/gre/1650294#gistcomment-1806616 */
const easeIn = p => t => Math.pow(t, p);
const easeOut = p => t => 1 - Math.abs(Math.pow(t - 1, p));
const easeInOut = p => t =>
t < 0.5 ? easeIn(p)(t * 2) / 2 : easeOut(p)(t * 2 - 1) / 2 + 0.5;
const easing = {
linear: easeInOut(1),
easeInQuad: easeIn(2),
easeOutQuad: easeOut(2),
easeInOutQuad: easeInOut(2),
easeInCubic: easeIn(3),
easeOutCubic: easeOut(3),
easeInOutCubic: easeInOut(3),
easeInQuart: easeIn(4),
easeOutQuart: easeOut(4),
easeInOutQuart: easeInOut(4),
easeInQuint: easeIn(5),
easeOutQuint: easeOut(5),
easeInOutQuint: easeInOut(5)
};
export { easing };
================================================
FILE: src/util/extend.js
================================================
/* Borrowed from https://gist.github.com/cferdinandi/4f8a0e17921c5b46e6c4 */
export default function extend(defaults, options) {
const extended = {};
let prop;
for (prop in defaults) {
if (Object.prototype.hasOwnProperty.call(defaults, prop)) {
extended[prop] = defaults[prop];
}
}
for (prop in options) {
if (Object.prototype.hasOwnProperty.call(options, prop)) {
extended[prop] = options[prop];
}
}
return extended;
}
================================================
FILE: src/util/getActiveSection.js
================================================
export default function getActiveSection(data, boundryTop, boundryBottom) {
let activeSection;
data.forEach(section => {
if (section.offsetTop > boundryBottom) {
if (!activeSection && section.offsetTop < boundryTop) {
activeSection = section;
}
} else {
activeSection = section;
}
});
if (activeSection && activeSection.subSections.length) {
let activeSubSection;
activeSubSection = getActiveSection(
activeSection.subSections,
boundryTop,
boundryBottom
);
if (activeSubSection) {
activeSection = activeSubSection;
}
}
return activeSection;
}
================================================
FILE: src/util/getOrSetID.js
================================================
export default function getOrSetID(elem, setID) {
if (typeof elem !== 'object') {
return Promise.reject(new Error('First argument must be an object'));
}
let id = elem.id;
if (!id) {
if (typeof setID !== 'string') {
return Promise.reject(new Error('Second argument must be a string'));
}
id = setID;
elem.id = id;
}
return id;
}
================================================
FILE: src/util/getTargetYPosition.js
================================================
export default function getTargetYPosition(target, data) {
let id = target.getAttribute('href');
if (id.charAt(0) === '#') {
id = id.substr(1);
}
const targetSection = filterData(data, id);
return targetSection.offsetTop;
}
function filterData(data, id) {
let targetSection;
data.forEach(section => {
if (section.id === id) {
targetSection = section;
}
if (section.subSections && targetSection === undefined) {
targetSection = filterData(section.subSections, id);
}
});
return targetSection;
}
================================================
FILE: src/util/getYPosition.js
================================================
export default function getYPosition(elem, parent) {
if (typeof elem !== 'object') {
return Promise.reject(new Error('First argument must be an object'));
}
parent = parent || document.body;
if (typeof parent !== 'object') {
return Promise.reject(new Error('Second argument must be an object'));
}
const bodyRect = parent.getBoundingClientRect();
const elemRect = elem.getBoundingClientRect();
return elemRect.top - bodyRect.top;
}
================================================
FILE: src/util/insertNav.js
================================================
export default function insertNav(scrollnav) {
const target = scrollnav.settings.insertTarget;
const location = scrollnav.settings.insertLocation;
if (location === 'append') {
target.appendChild(scrollnav.nav);
} else if (location === 'prepend') {
target.insertBefore(scrollnav.nav, target.firstChild);
} else if (location === 'before') {
target.parentNode.insertBefore(scrollnav.nav, target);
} else if (location === 'after') {
target.parentNode.insertBefore(scrollnav.nav, target.nextSibling);
}
}
================================================
FILE: src/util/insertVisualDebugger.js
================================================
export default function insertVisualDebugger() {
const snDebugger = document.createElement('div');
snDebugger.className = 'snDebugger';
snDebugger.setAttribute(
'style',
`
position: fixed;
top: 40%;
height: 0px;
border-bottom:5px solid red;
border-top: 5px solid blue;
width: 100%;
opacity: .5;
pointer-events: none;
`
);
document.body.appendChild(snDebugger);
}
================================================
FILE: src/util/nextUntil.js
================================================
/* Borrowed from https://github.com/cferdinandi/nextUntil */
export default function nextUntil(elem, selector, filter) {
var siblings = [];
elem = elem.nextElementSibling;
while (elem) {
if (elem.matches(selector)) break;
if (filter && !elem.matches(filter)) {
elem = elem.nextElementSibling;
continue;
}
siblings.push(elem);
elem = elem.nextElementSibling;
}
return siblings;
}
================================================
FILE: src/util/populateSectionData.js
================================================
import getOrSetID from './getOrSetID';
import getYPosition from './getYPosition';
import nextUntil from './nextUntil';
export default function populateSectionData(
sections,
settings,
prefix = 'scroll-nav'
) {
const sectionData = [];
prefix = prefix + '__';
sections.forEach((elem, i) => {
let subSectionData = [];
const id = getOrSetID(elem, prefix + (i + 1));
if (settings.subSections && elem.matches(settings.sections)) {
const subSectionDom = nextUntil(
elem,
settings.sections,
settings.subSections
);
subSectionData = populateSectionData(subSectionDom, settings, id);
}
sectionData.push({
id: id,
text: elem.innerText || elem.textContent,
offsetTop: getYPosition(elem),
subSections: subSectionData
});
});
return sectionData;
}
================================================
FILE: src/util/updateActiveNavItem.js
================================================
export default function updateActiveNavItem(activeSection, nav) {
const previousActive = nav.querySelector('[data-sn-active]');
if (!activeSection) {
if (previousActive) {
previousActive.classList.remove('scroll-nav__item--active');
previousActive.removeAttribute('data-sn-active');
}
return;
}
const newActive = nav.querySelector(
'[data-sn-section=' + activeSection.id + ']'
);
if (newActive && newActive !== previousActive) {
if (previousActive) {
previousActive.classList.remove('scroll-nav__item--active');
previousActive.removeAttribute('data-sn-active');
}
newActive.classList.add('scroll-nav__item--active');
newActive.setAttribute('data-sn-active', true);
}
}
================================================
FILE: src/util/updatePositionData.js
================================================
import getYPosition from './getYPosition';
export default function updatePositionData(data) {
data.forEach(section => {
const sectionDom = document.querySelector(`#${section.id}`);
section.offsetTop = getYPosition(sectionDom);
if (section.subSections.length) {
section.subSections = updatePositionData(section.subSections);
}
});
return data;
}
================================================
FILE: test/.eslintrc.json
================================================
{
"env": {
"jest": true
}
}
================================================
FILE: test/fixtures/navMarkup.js
================================================
const onlyH2Nav = `
<nav class="scroll-nav" role="navigation">
<ul class="scroll-nav__list">
<li class="scroll-nav__item" data-sn-section="first-heading">
<a href="#first-heading" class="scroll-nav__link">First heading</a>
</li>
<li class="scroll-nav__item" data-sn-section="second-heading">
<a href="#second-heading" class="scroll-nav__link">Second heading</a>
</li>
<li class="scroll-nav__item" data-sn-section="third-heading">
<a href="#third-heading" class="scroll-nav__link">Third heading</a>
</li>
</ul>
</nav>`;
const allNav = `
<nav class="scroll-nav" role="navigation">
<ol class="scroll-nav__list">
<li class="scroll-nav__item" data-sn-section="first-heading">
<a href="#first-heading" class="scroll-nav__link">First heading</a>
</li>
<li class="scroll-nav__item" data-sn-section="second-heading">
<a href="#second-heading" class="scroll-nav__link">Second heading</a>
<ol class="scroll-nav__sub-list">
<li class="scroll-nav__sub-item" data-sn-section="second-heading__1">
<a class="scroll-nav__sub-link" href="#second-heading__1">
First sub-heading of the second heading
</a>
</li>
</ol>
</li>
<li class="scroll-nav__item" data-sn-section="third-heading">
<a href="#third-heading" class="scroll-nav__link">Third heading</a>
</li>
</ol>
</nav>`;
export { onlyH2Nav, allNav };
================================================
FILE: test/fixtures/noSectionsMarkup.js
================================================
const html = `
<div class="test-content">
<p>Lorem ipsum dolor sit amet, in vis quis verear persecuti, sed ne equidem
singulis incorrupte. Has quem erant mentitum te, cu aeterno feugiat
antiopam eam. Cum ea soleat discere. Ut cum eripuit pertinax. Admodum
inimicus nec ex, mel quod antiopam neglegentur an, ad mutat euismod
qualisque nam.</p>
<p>Pri et option adversarium. Id vix euismod inermis consequat, his verear
veritus aliquando ad, ea recusabo ullamcorper mel. Sit ne quas aliquid
blandit, est ullum regione deserunt an, sea paulo lucilius ut. Et stet
menandri qualisque mea, quando tacimates suavitate te mei, ius offendit
legendos philosophia eu. Ferri dicant equidem vel cu, duo facilisis
definiebas appellantur ei, meis mutat invenire ut vel.</p>
<p>Sed id nulla ignota, ad has idque nusquam interesset. Sea id atomorum
salutandi ocurreret, vix et mucius impedit quaestio. An tritani veritus
oporteat quo. Id labore delicata eos, verear legendos antiopam mel te,
impedit corpora ea cum.</p>
<p>Mea ad insolens menandri, eam te odio aliquam, singulis qualisque vix
cu. Invidunt consequat ex his, appareat invidunt duo at, sed cu ipsum
vocent. Duo an quaerendum consequuntur, et duo error liberavisse, vix inani
explicari cu. Harum mandamus scribentur mea ad, pri at antiopam inimicus
accusamus. Pro graecis vituperatoribus in, delicata splendide ullamcorper
ex duo.</p>
<p>In est nonumy populo. Usu no natum percipitur reprehendunt. Vitae
nominavi in cum, eleifend delicatissimi no cum. Sea an omnis denique
gloriatur. Probo comprehensam at sit, amet ipsum repudiandae duo ei.<p>
</div>`;
export { html };
================================================
FILE: test/fixtures/pennerEasing.js
================================================
/* Source https://github.com/CharlotteGore/functional-easing */
/* eslint-disable */
Math.linear = function(t, b, c, d) {
return (c * t) / d + b;
};
Math.easeInQuad = function(t, b, c, d) {
return c * (t /= d) * t + b;
};
Math.easeOutQuad = function(t, b, c, d) {
return -c * (t /= d) * (t - 2) + b;
};
Math.easeInOutQuad = function(t, b, c, d) {
if ((t /= d / 2) < 1) return (c / 2) * t * t + b;
return (-c / 2) * (--t * (t - 2) - 1) + b;
return 0;
};
Math.easeInCubic = function(t, b, c, d) {
return c * (t /= d) * t * t + b;
};
Math.easeOutCubic = function(t, b, c, d) {
return c * ((t = t / d - 1) * t * t + 1) + b;
};
Math.easeInOutCubic = function(t, b, c, d) {
if ((t /= d / 2) < 1) return (c / 2) * t * t * t + b;
return (c / 2) * ((t -= 2) * t * t + 2) + b;
};
Math.easeInQuart = function(t, b, c, d) {
return c * (t /= d) * t * t * t + b;
};
Math.easeOutQuart = function(t, b, c, d) {
return -c * ((t = t / d - 1) * t * t * t - 1) + b;
};
Math.easeInOutQuart = function(t, b, c, d) {
if ((t /= d / 2) < 1) return (c / 2) * t * t * t * t + b;
return (-c / 2) * ((t -= 2) * t * t * t - 2) + b;
};
Math.easeInQuint = function(t, b, c, d) {
return c * (t /= d) * t * t * t * t + b;
};
Math.easeOutQuint = function(t, b, c, d) {
return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
};
Math.easeInOutQuint = function(t, b, c, d) {
if ((t /= d / 2) < 1) return (c / 2) * t * t * t * t * t + b;
return (c / 2) * ((t -= 2) * t * t * t * t + 2) + b;
};
================================================
FILE: test/fixtures/sectionData.js
================================================
const onlyH2Data = [
{
id: 'first-heading',
text: 'First heading',
offsetTop: 100,
subSections: []
},
{
id: 'second-heading',
text: 'Second heading',
offsetTop: 200,
subSections: []
},
{
id: 'third-heading',
text: 'Third heading',
offsetTop: 300,
subSections: []
}
];
const allData = [
{
id: 'first-heading',
text: 'First heading',
offsetTop: 100,
subSections: []
},
{
id: 'second-heading',
text: 'Second heading',
offsetTop: 200,
subSections: [
{
id: 'second-heading__1',
text: 'First sub-heading of the second heading',
offsetTop: 225,
subSections: []
}
]
},
{
id: 'third-heading',
text: 'Third heading',
offsetTop: 300,
subSections: []
}
];
export { onlyH2Data, allData };
================================================
FILE: test/fixtures/sectionMarkup.js
================================================
const html = `
<div class="test-content">
<p>Lorem ipsum dolor sit amet, in vis quis verear persecuti, sed ne equidem
singulis incorrupte. Has quem erant mentitum te, cu aeterno feugiat
antiopam eam. Cum ea soleat discere. Ut cum eripuit pertinax. Admodum
inimicus nec ex, mel quod antiopam neglegentur an, ad mutat euismod
qualisque nam.</p>
<h2 id="first-heading">First heading</h2>
<p>Pri et option adversarium. Id vix euismod inermis consequat, his verear
veritus aliquando ad, ea recusabo ullamcorper mel. Sit ne quas aliquid
blandit, est ullum regione deserunt an, sea paulo lucilius ut. Et stet
menandri qualisque mea, quando tacimates suavitate te mei, ius offendit
legendos philosophia eu. Ferri dicant equidem vel cu, duo facilisis
definiebas appellantur ei, meis mutat invenire ut vel.</p>
<h2 id="second-heading">Second heading</h2>
<p>Sed id nulla ignota, ad has idque nusquam interesset. Sea id atomorum
salutandi ocurreret, vix et mucius impedit quaestio. An tritani veritus
oporteat quo. Id labore delicata eos, verear legendos antiopam mel te,
impedit corpora ea cum.</p>
<h3>First sub-heading of the second heading</h3>
<p>Mea ad insolens menandri, eam te odio aliquam, singulis qualisque vix
cu. Invidunt consequat ex his, appareat invidunt duo at, sed cu ipsum
vocent. Duo an quaerendum consequuntur, et duo error liberavisse, vix inani
explicari cu. Harum mandamus scribentur mea ad, pri at antiopam inimicus
accusamus. Pro graecis vituperatoribus in, delicata splendide ullamcorper
ex duo.</p>
<h2 id="third-heading">Third heading</h2>
<p>In est nonumy populo. Usu no natum percipitur reprehendunt. Vitae
nominavi in cum, eleifend delicatissimi no cum. Sea an omnis denique
gloriatur. Probo comprehensam at sit, amet ipsum repudiandae duo ei.<p>
</div>`;
export { html };
================================================
FILE: test/tests/scrollTo.test.js
================================================
import scrollTo from '../../src/scrollTo';
jest.useFakeTimers();
describe('scrollTo', () => {
it('should return an error if the first param is not a number', () => {
return expect(scrollTo('string')).rejects.toThrow(
'First argument must be a number'
);
});
it('should return an error if the second param is not a string', () => {
return expect(scrollTo(200, 400)).rejects.toThrow(
'Second argument must be a string'
);
});
xit('should resolve a Promise after scrolling', async () => {
expect.assertions(1);
const callback = jest.fn();
window.pageYOffset = 2200;
await scrollTo(200, null).then(() => {
callback();
});
jest.runAllTimers();
expect(callback).toBeCalled();
});
});
================================================
FILE: test/tests/scrollnav.test.js
================================================
/* eslint no-console: 0 */
import jestDomCustomMatchers from '@jarmee/jest-dom-custom-matchers';
import scrollnav from '../../src/scrollnav';
import { html as sectionMarkup } from '../fixtures/sectionMarkup';
import { html as noSectionsMarkup } from '../fixtures/noSectionsMarkup';
expect.extend(jestDomCustomMatchers);
describe('scrollnav', () => {
describe('init method', () => {
beforeEach(() => {
document.body.innerHTML = sectionMarkup;
});
describe('element argument', () => {
it(`should not create a nav element if the first argument is not an
HTML Element`, () => {
const content = 'content';
scrollnav.init(content);
const nav = document.querySelector('nav');
expect(nav).toBe(null);
});
it(`should not create a nav element if the first argument is not an
HTML Element`, () => {
const content = () => {
return;
};
scrollnav.init(content);
const nav = document.querySelector('nav');
expect(nav).toBe(null);
});
it(`should not create a nav element if the first argument is not an
HTML Element`, () => {
const content = new Object();
scrollnav.init(content);
const nav = document.querySelector('nav');
expect(nav).toBe(null);
});
it(`should log an error if the first argument is not an HTML Element and
the debug option is true`, () => {
console.error = jest.fn();
const content = 'content';
scrollnav.init(content, { debug: true });
expect(console.error).toHaveBeenCalled();
});
it(`should not log an error if the first argument is an HTML Element and
the debug option is true`, () => {
console.error = jest.fn();
const content = document.querySelector('.test-content');
scrollnav.init(content, { debug: true });
expect(console.error).not.toHaveBeenCalled();
});
});
describe('sections option', () => {
it(`should not create a nav element if there are no section elements
within the element argument`, () => {
document.body.innerHTML = noSectionsMarkup;
const content = document.querySelector('.test-content');
scrollnav.init(content);
const nav = document.querySelector('nav');
expect(nav).toBe(null);
});
it(`should log an error if there are no section elements within the element
argument and the debug option is true`, () => {
console.error = jest.fn();
document.body.innerHTML = noSectionsMarkup;
const content = document.querySelector('.test-content');
scrollnav.init(content, { debug: true });
expect(console.error).toHaveBeenCalled();
});
it(`should not log an error if there are section elements within the element
argument and the debug option is true`, () => {
console.error = jest.fn();
const content = document.querySelector('.test-content');
scrollnav.init(content, { debug: true });
expect(console.error).not.toHaveBeenCalled();
});
});
describe('insertTarget option', () => {
it(`should not create a nav element if the insertTarget option is not an
HTML Element`, () => {
const content = document.querySelector('.test-content');
const insertTarget = 'insertTarget';
scrollnav.init(content, { insertTarget: insertTarget });
const nav = document.querySelector('nav');
expect(nav).toBe(null);
});
it(`should not create a nav element if the insertTarget option is not an
HTML Element`, () => {
const content = document.querySelector('.test-content');
const insertTarget = () => {
return;
};
scrollnav.init(content, { insertTarget: insertTarget });
const nav = document.querySelector('nav');
expect(nav).toBe(null);
});
it(`should not create a nav element if the insertTarget option is not an
HTML Element`, () => {
const content = document.querySelector('.test-content');
const insertTarget = new Object();
scrollnav.init(content, { insertTarget: insertTarget });
const nav = document.querySelector('nav');
expect(nav).toBe(null);
});
it(`should log an error if the insertTarget option is not an HTML Element
and the debug option is true`, () => {
console.error = jest.fn();
const content = document.querySelector('.test-content');
const insertTarget = 'insertTarget';
scrollnav.init(content, {
debug: true,
insertTarget: insertTarget
});
expect(console.error).toHaveBeenCalled();
});
it(`should not log an error if the insertTarget option is an HTML Element
and the debug option is true`, () => {
console.error = jest.fn();
const content = document.querySelector('.test-content');
scrollnav.init(content, {
debug: true,
insertTarget: content
});
expect(console.error).not.toHaveBeenCalled();
});
});
describe('insertLocation option', () => {
it(`should not create a nav element if the insertLocation option is
invalid`, () => {
const content = document.querySelector('.test-content');
scrollnav.init(content, { insertLocation: 'appendTo' });
const nav = document.querySelector('nav');
expect(nav).toBe(null);
});
it('should log an error if the insertLocation option is invalid', () => {
console.error = jest.fn();
const content = document.querySelector('.test-content');
scrollnav.init(content, {
debug: true,
insertLocation: 'appendTo'
});
expect(console.error).toHaveBeenCalled();
});
it('should not log an error if the insertLocation option is valid', () => {
console.error = jest.fn();
const content = document.querySelector('.test-content');
scrollnav.init(content, {
debug: true,
insertLocation: 'after'
});
expect(console.error).not.toHaveBeenCalled();
});
it('should not log an error if the insertLocation option is valid', () => {
console.error = jest.fn();
const content = document.querySelector('.test-content');
scrollnav.init(content, {
debug: true,
insertLocation: 'before'
});
expect(console.error).not.toHaveBeenCalled();
});
it('should not log an error if the insertLocation option is valid', () => {
console.error = jest.fn();
const content = document.querySelector('.test-content');
scrollnav.init(content, {
debug: true,
insertLocation: 'append'
});
expect(console.error).not.toHaveBeenCalled();
});
it('should not log an error if the insertLocation option is valid', () => {
console.error = jest.fn();
const content = document.querySelector('.test-content');
scrollnav.init(content, {
debug: true,
insertLocation: 'prepend'
});
expect(console.error).not.toHaveBeenCalled();
});
});
it('should create a nav element with the correct class name', () => {
const content = document.querySelector('.test-content');
scrollnav.init(content);
const nav = document.querySelector('nav');
expect(nav).toHaveClass('scroll-nav');
});
it('should inject the nav element before the content container', () => {
const content = document.querySelector('.test-content');
scrollnav.init(content);
const nav = document.querySelector('nav');
expect(nav.nextElementSibling).toBeHTMLElement('div');
expect(nav.nextElementSibling).toHaveClass('test-content');
});
it('should contain the list of links', () => {
const content = document.querySelector('.test-content');
scrollnav.init(content);
const nav = document.querySelector('nav');
const list = nav.children;
expect(list.length).toBe(1);
expect(list[0]).toBeHTMLElement('ol');
expect(list[0].children.length).toBe(3);
});
it('should return a callback after the nav is added', () => {
const callback = jest.fn();
const content = document.querySelector('.test-content');
scrollnav.init(content, { onInit: callback });
expect(callback).toBeCalled();
});
});
describe('destroy method', () => {
beforeEach(() => {
document.body.innerHTML = sectionMarkup;
const content = document.querySelector('.test-content');
scrollnav.init(content);
});
it('should remove the nav from the document', () => {
scrollnav.destroy();
const nav = document.querySelector('nav');
expect(nav).toBe(null);
});
it('should return a callback after the the nav is removed', () => {
const callback = jest.fn();
scrollnav.destroy({ onDestroy: callback });
expect(callback).toBeCalled();
});
});
describe('updatePositions method', () => {
let sections;
beforeEach(() => {
document.body.innerHTML = sectionMarkup;
const content = document.querySelector('.test-content');
sections = document.querySelectorAll('h2');
sections.forEach((elem, i) => {
elem.getBoundingClientRect = () => {
return {
bottom: 800,
height: 100,
left: 0,
right: 800,
top: 100 * (i + 1),
width: 800
};
};
});
scrollnav.init(content);
});
it('should update the position data', () => {
sections.forEach((elem, i) => {
elem.getBoundingClientRect = () => {
return {
bottom: 800,
height: 100,
left: 0,
right: 800,
top: 200 * (i + 1),
width: 800
};
};
});
scrollnav.updatePositions();
expect(scrollnav.data[0].offsetTop).toEqual(200);
});
it('should return a callback after the positions are updated', () => {
const callback = jest.fn();
sections.forEach((elem, i) => {
elem.getBoundingClientRect = () => {
return {
bottom: 800,
height: 100,
left: 0,
right: 800,
top: 200 * (i + 1),
width: 800
};
};
});
scrollnav.updatePositions({ onUpdatePositions: callback });
expect(callback).toBeCalled();
});
});
});
================================================
FILE: test/tests/setupClickHandlers.test.js
================================================
import setupClickHandlers from '../../src/setupClickHandlers';
import { onlyH2Data, allData } from '../fixtures/sectionData';
import { onlyH2Nav, allNav } from '../fixtures/navMarkup';
import simulateEvent from '../util/simulateEvent';
describe('setupClickHandlers', () => {
it('should not trigger a callback after scrolling to a section', async () => {
expect.assertions(1);
const callback = jest.fn();
document.body.innerHTML = onlyH2Nav;
const nav = document.querySelector('nav');
const links = nav.querySelectorAll('a');
const scrollnav = {
data: onlyH2Data,
nav: document.querySelector('nav'),
settings: {}
};
setupClickHandlers(scrollnav);
await simulateEvent('click', links[0]);
expect(callback).not.toBeCalled();
});
xit('should trigger a callback after scrolling to a section', async () => {
expect.assertions(1);
const callback = jest.fn();
document.body.innerHTML = onlyH2Nav;
const nav = document.querySelector('nav');
const links = nav.querySelectorAll('a');
const scrollnav = {
data: onlyH2Data,
nav: document.querySelector('nav'),
settings: {
onScroll: callback
}
};
setupClickHandlers(scrollnav);
await simulateEvent('click', links[0]);
expect(callback).toBeCalled();
});
xit('should trigger a callback after scrolling to a sub-section', async () => {
expect.assertions(1);
const callback = jest.fn();
document.body.innerHTML = allNav;
const nav = document.querySelector('nav');
const links = nav.querySelectorAll('a');
const scrollnav = {
data: allData,
nav: nav,
settings: {
onScroll: callback
}
};
setupClickHandlers(scrollnav);
await simulateEvent('click', links[2]);
expect(callback).toBeCalled();
});
});
================================================
FILE: test/tests/setupResizeHandler.test.js
================================================
import setupResizeHandler from '../../src/setupResizeHandler';
import { html } from '../fixtures/sectionMarkup';
import { onlyH2Data } from '../fixtures/sectionData';
import simulateEvent from '../util/simulateEvent';
describe('setupResizeHandler', () => {
let sections;
let scrollnav;
beforeAll(() => {
document.body.innerHTML = html;
sections = document.querySelectorAll('h2');
scrollnav = {
data: onlyH2Data
};
setupResizeHandler(scrollnav);
sections.forEach((elem, i) => {
elem.getBoundingClientRect = () => {
return {
bottom: 800,
height: 100,
left: 0,
right: 800,
top: 200 * (i + 1),
width: 800
};
};
});
});
it('should update the data when the window is resized', () => {
simulateEvent('resize', window);
const data = scrollnav.data;
expect(data[0].offsetTop).toEqual(200);
expect(data[1].offsetTop).toEqual(400);
expect(data[2].offsetTop).toEqual(600);
});
});
================================================
FILE: test/tests/setupScrollHander.test.js
================================================
import jestDomCustomMatchers from '@jarmee/jest-dom-custom-matchers';
import setupScrollHandler from '../../src/setupScrollHandler';
import { onlyH2Data } from '../fixtures/sectionData';
import { html as sectionMarkup } from '../fixtures/sectionMarkup';
import { onlyH2Nav } from '../fixtures/navMarkup';
import simulateEvent from '../util/simulateEvent';
expect.extend(jestDomCustomMatchers);
describe(setupScrollHandler, function() {
let nav;
let items;
let scrollnav;
beforeEach(() => {
document.body.innerHTML = onlyH2Nav + sectionMarkup;
nav = document.querySelector('nav');
items = nav.querySelectorAll('li');
scrollnav = {
data: onlyH2Data,
nav: nav
};
});
it('should not activate any items on load', () => {
window.innerHeight = 28;
setupScrollHandler(scrollnav);
expect(items[0]).not.toHaveClass('scroll-nav__item--active');
expect(items[0]).not.toHaveAttribute('data-sn-active');
});
it('should activate the first item if it is within the boundry', () => {
window.innerHeight = 340;
setupScrollHandler(scrollnav);
simulateEvent('scroll', window);
expect(items[0]).toHaveClass('scroll-nav__item--active');
expect(items[0]).toHaveAttribute('data-sn-active');
});
it('should activate the second item and not the first if they are both within the boundry', () => {
window.innerHeight = 625;
setupScrollHandler(scrollnav);
simulateEvent('scroll', window);
expect(items[0]).not.toHaveClass('scroll-nav__item--active');
expect(items[0]).not.toHaveAttribute('data-sn-active');
expect(items[1]).toHaveClass('scroll-nav__item--active');
expect(items[1]).toHaveAttribute('data-sn-active');
});
});
================================================
FILE: test/tests/teardownClickHandlers.test.js
================================================
import setupClickHandlers from '../../src/setupClickHandlers';
import teardownClickHandlers from '../../src/teardownClickHandlers';
import { onlyH2Data } from '../fixtures/sectionData';
import { onlyH2Nav } from '../fixtures/navMarkup';
import simulateEvent from '../util/simulateEvent';
describe('teardownClickHandlers', () => {
let nav;
let links;
beforeAll(() => {
document.body.innerHTML = onlyH2Nav;
nav = document.querySelector('nav');
links = nav.querySelectorAll('a');
});
it('should not trigger a callback after click', () => {
expect.assertions(1);
const callback = jest.fn();
const scrollnav = {
data: onlyH2Data,
nav: nav,
settings: {
onScroll: callback
}
};
const clickHandler = setupClickHandlers(scrollnav);
teardownClickHandlers(nav, clickHandler);
simulateEvent('click', links[0]);
expect(callback).not.toBeCalled();
});
});
================================================
FILE: test/tests/teardownResizeHandler.test.js
================================================
import setupResizeHandler from '../../src/setupResizeHandler';
import teardownResizeHandler from '../../src/teardownResizeHandler';
import { html } from '../fixtures/sectionMarkup';
import { onlyH2Data } from '../fixtures/sectionData';
import simulateEvent from '../util/simulateEvent';
describe('setupResizeHandler', () => {
let sections;
let scrollnav;
let resizeHandler;
beforeAll(() => {
document.body.innerHTML = html;
sections = document.querySelectorAll('h2');
scrollnav = {
data: onlyH2Data
};
resizeHandler = setupResizeHandler(scrollnav);
sections.forEach((elem, i) => {
elem.getBoundingClientRect = () => {
return {
bottom: 800,
height: 100,
left: 0,
right: 800,
top: 200 * (i + 1),
width: 800
};
};
});
});
it('should not update the data when the window is resized', () => {
teardownResizeHandler(resizeHandler);
simulateEvent('resize', window);
const data = scrollnav.data;
expect(data[0].offsetTop).toEqual(100);
expect(data[1].offsetTop).toEqual(200);
expect(data[2].offsetTop).toEqual(300);
});
});
================================================
FILE: test/tests/teardownScrollHander.test.js
================================================
import jestDomCustomMatchers from '@jarmee/jest-dom-custom-matchers';
import setupScrollHandler from '../../src/setupScrollHandler';
import teardownScrollHandler from '../../src/teardownScrollHandler';
import { onlyH2Data } from '../fixtures/sectionData';
import { html as sectionMarkup } from '../fixtures/sectionMarkup';
import { onlyH2Nav } from '../fixtures/navMarkup';
import simulateEvent from '../util/simulateEvent';
expect.extend(jestDomCustomMatchers);
describe(setupScrollHandler, function() {
let nav;
let items;
let scrollnav;
beforeEach(() => {
document.body.innerHTML = onlyH2Nav + sectionMarkup;
nav = document.querySelector('nav');
items = nav.querySelectorAll('li');
scrollnav = {
data: onlyH2Data,
nav: nav
};
});
it('should not activate the first item if it is within the boundry', () => {
window.innerHeight = 340;
const scrollHandler = setupScrollHandler(scrollnav);
teardownScrollHandler(scrollHandler);
simulateEvent('scroll', window);
expect(items[0]).not.toHaveClass('scroll-nav__item--active');
expect(items[0]).not.toHaveAttribute('data-sn-active');
});
it('should not activate the second item and or the first if they are both within the boundry', () => {
window.innerHeight = 625;
const scrollHandler = setupScrollHandler(scrollnav);
teardownScrollHandler(scrollHandler);
simulateEvent('scroll', window);
expect(items[0]).not.toHaveClass('scroll-nav__item--active');
expect(items[0]).not.toHaveAttribute('data-sn-active');
expect(items[1]).not.toHaveClass('scroll-nav__item--active');
expect(items[1]).not.toHaveAttribute('data-sn-active');
});
});
================================================
FILE: test/tests/util/calculateScrollDuration.test.js
================================================
import calculateScrollDuration from '../../../src/util/calculateScrollDuration.js';
describe('calculateScrollDuration', () => {
const MIN_DURATION = 250;
const MAX_DURATION = 1200;
it('should return the minimum duration', () => {
const duration = calculateScrollDuration(200);
expect(duration).toBe(MIN_DURATION);
});
it('should return the maximum duration', () => {
const duration = calculateScrollDuration(3000);
expect(duration).toBe(MAX_DURATION);
});
it('should return a duration that is half the distance', () => {
const duration = calculateScrollDuration(1000);
expect(duration).toBe(500);
});
});
================================================
FILE: test/tests/util/createList.test.js
================================================
import jestDomCustomMatchers from '@jarmee/jest-dom-custom-matchers';
import createList from '../../../src/util/createList';
import { onlyH2Data, allData } from '../../fixtures/sectionData';
expect.extend(jestDomCustomMatchers);
describe('createList', () => {
let testContainer;
beforeAll(() => {
testContainer = document.createElement('div');
});
it('should create an ol element with the correct class name', () => {
testContainer.innerHTML = createList(onlyH2Data);
const list = testContainer.children[0];
expect(list).toBeHTMLElement('ol');
expect(list).toHaveClass('scroll-nav__list');
});
it(`should include three li elements with the correct class name
and data attributes`, () => {
testContainer.innerHTML = createList(onlyH2Data);
const items = testContainer.children[0].children;
expect(items.length).toBe(3);
expect(items[0]).toBeHTMLElement('li');
expect(items[0]).toHaveClass('scroll-nav__item');
expect(items[0]).toHaveAttribute('data-sn-section', 'first-heading');
});
it(`should include a link with the correct class name, href,
and inner text`, () => {
testContainer.innerHTML = createList(onlyH2Data);
const links = testContainer.querySelectorAll('a');
expect(links.length).toBe(3);
expect(links[0]).toHaveClass('scroll-nav__link');
expect(links[0]).toContainText('First heading');
expect(links[0]).toHaveAttribute('href', '#first-heading');
});
it('should not include child ol element if the data does not exist', () => {
testContainer.innerHTML = createList(onlyH2Data);
const subSections = testContainer.querySelectorAll('ol ol');
expect(subSections.length).toBe(0);
});
it('should include child ol element with the correct class name', () => {
testContainer.innerHTML = createList(allData);
const subSections = testContainer.querySelectorAll('ol ol');
expect(subSections.length).toBe(1);
expect(subSections[0]).toBeHTMLElement('ol');
expect(subSections[0]).toHaveClass('scroll-nav__sub-list');
});
it(`should include an item in the child ol element with the correct class
name`, () => {
testContainer.innerHTML = createList(allData);
const subItems = testContainer.querySelectorAll('ol ol li');
expect(subItems.length).toBe(1);
expect(subItems[0]).toBeHTMLElement('li');
expect(subItems[0]).toHaveClass('scroll-nav__sub-item');
expect(subItems[0]).toHaveAttribute('data-sn-section', 'second-heading__1');
});
it(`should include a link with the correct class name, href,
and inner text`, () => {
testContainer.innerHTML = createList(allData);
const subLinks = testContainer.querySelectorAll('ol ol a');
expect(subLinks.length).toBe(1);
expect(subLinks[0]).toBeHTMLElement('a');
expect(subLinks[0]).toHaveClass('scroll-nav__sub-link');
expect(subLinks[0]).toContainText(
'First sub-heading of the second heading'
);
expect(subLinks[0]).toHaveAttribute('href', '#second-heading__1');
});
});
================================================
FILE: test/tests/util/createNav.test.js
================================================
import jestDomCustomMatchers from '@jarmee/jest-dom-custom-matchers';
import createNav from '../../../src/util/createNav';
import { onlyH2Data } from '../../fixtures/sectionData';
expect.extend(jestDomCustomMatchers);
describe('createNav', () => {
it('should create a nav element with the correct class name', () => {
const nav = createNav(onlyH2Data);
expect(nav).toBeHTMLElement('nav');
expect(nav).toHaveClass('scroll-nav');
});
it('should contain the list of links', () => {
const nav = createNav(onlyH2Data);
const list = nav.children;
expect(list.length).toBe(1);
expect(list[0]).toBeHTMLElement('ol');
expect(list[0].children.length).toBe(3);
});
});
================================================
FILE: test/tests/util/easing.test.js
================================================
require('../../fixtures/pennerEasing');
import { easing } from '../../../src/util/easing';
describe('easing', () => {
const VAL_1 = 1;
const VAL_2 = 0.5;
const VAL_3 = 0.25;
const B = 0;
const C = 1;
const D = 1;
it('should equal Penner linear', () => {
expect(easing.linear(VAL_1)).toBe(Math.linear(VAL_1, B, C, D));
expect(easing.linear(VAL_2)).toBe(Math.linear(VAL_2, B, C, D));
expect(easing.linear(VAL_3)).toBe(Math.linear(VAL_3, B, C, D));
});
it('should equal Penner easeInQuad', () => {
expect(easing.easeInQuad(VAL_1)).toBe(Math.easeInQuad(VAL_1, B, C, D));
expect(easing.easeInQuad(VAL_2)).toBe(Math.easeInQuad(VAL_2, B, C, D));
expect(easing.easeInQuad(VAL_3)).toBe(Math.easeInQuad(VAL_3, B, C, D));
});
it('should equal Penner easeOutQuad', () => {
expect(easing.easeOutQuad(VAL_1)).toBe(Math.easeOutQuad(VAL_1, B, C, D));
expect(easing.easeOutQuad(VAL_2)).toBe(Math.easeOutQuad(VAL_2, B, C, D));
expect(easing.easeOutQuad(VAL_3)).toBe(Math.easeOutQuad(VAL_3, B, C, D));
});
it('should equal Penner easeInOutQuad', () => {
expect(easing.easeInOutQuad(VAL_1)).toBe(
Math.easeInOutQuad(VAL_1, B, C, D)
);
expect(easing.easeInOutQuad(VAL_2)).toBe(
Math.easeInOutQuad(VAL_2, B, C, D)
);
expect(easing.easeInOutQuad(VAL_3)).toBe(
Math.easeInOutQuad(VAL_3, B, C, D)
);
});
it('should equal Penner easeInCubic', () => {
expect(easing.easeInCubic(VAL_1)).toBe(Math.easeInCubic(VAL_1, B, C, D));
expect(easing.easeInCubic(VAL_2)).toBe(Math.easeInCubic(VAL_2, B, C, D));
expect(easing.easeInCubic(VAL_3)).toBe(Math.easeInCubic(VAL_3, B, C, D));
});
it('should equal Penner easeOutCubic', () => {
expect(easing.easeOutCubic(VAL_1)).toBe(Math.easeOutCubic(VAL_1, B, C, D));
expect(easing.easeOutCubic(VAL_2)).toBe(Math.easeOutCubic(VAL_2, B, C, D));
expect(easing.easeOutCubic(VAL_3)).toBe(Math.easeOutCubic(VAL_3, B, C, D));
});
it('should equal Penner easeInOutCubic', () => {
expect(easing.easeInOutCubic(VAL_1)).toBe(
Math.easeInOutCubic(VAL_1, B, C, D)
);
expect(easing.easeInOutCubic(VAL_2)).toBe(
Math.easeInOutCubic(VAL_2, B, C, D)
);
expect(easing.easeInOutCubic(VAL_3)).toBe(
Math.easeInOutCubic(VAL_3, B, C, D)
);
});
it('should equal Penner easeInQuart', () => {
expect(easing.easeInQuart(VAL_1)).toBe(Math.easeInQuart(VAL_1, B, C, D));
expect(easing.easeInQuart(VAL_2)).toBe(Math.easeInQuart(VAL_2, B, C, D));
expect(easing.easeInQuart(VAL_3)).toBe(Math.easeInQuart(VAL_3, B, C, D));
});
it('should equal Penner easeOutQuart', () => {
expect(easing.easeOutQuart(VAL_1)).toBe(Math.easeOutQuart(VAL_1, B, C, D));
expect(easing.easeOutQuart(VAL_2)).toBe(Math.easeOutQuart(VAL_2, B, C, D));
expect(easing.easeOutQuart(VAL_3)).toBe(Math.easeOutQuart(VAL_3, B, C, D));
});
it('should equal Penner easeInOutQuart', () => {
expect(easing.easeInOutQuart(VAL_1)).toBe(
Math.easeInOutQuart(VAL_1, B, C, D)
);
expect(easing.easeInOutQuart(VAL_2)).toBe(
Math.easeInOutQuart(VAL_2, B, C, D)
);
expect(easing.easeInOutQuart(VAL_3)).toBe(
Math.easeInOutQuart(VAL_3, B, C, D)
);
});
it('should equal Penner easeInQuint', () => {
expect(easing.easeInQuint(VAL_1)).toBe(Math.easeInQuint(VAL_1, B, C, D));
expect(easing.easeInQuint(VAL_2)).toBe(Math.easeInQuint(VAL_2, B, C, D));
expect(easing.easeInQuint(VAL_3)).toBe(Math.easeInQuint(VAL_3, B, C, D));
});
it('should equal Penner easeOutQuint', () => {
expect(easing.easeOutQuint(VAL_1)).toBe(Math.easeOutQuint(VAL_1, B, C, D));
expect(easing.easeOutQuint(VAL_2)).toBe(Math.easeOutQuint(VAL_2, B, C, D));
expect(easing.easeOutQuint(VAL_3)).toBe(Math.easeOutQuint(VAL_3, B, C, D));
});
it('should equal Penner easeInOutQuint', () => {
expect(easing.easeInOutQuint(VAL_1)).toBe(
Math.easeInOutQuint(VAL_1, B, C, D)
);
expect(easing.easeInOutQuint(VAL_2)).toBe(
Math.easeInOutQuint(VAL_2, B, C, D)
);
expect(easing.easeInOutQuint(VAL_3)).toBe(
Math.easeInOutQuint(VAL_3, B, C, D)
);
});
});
================================================
FILE: test/tests/util/extend.test.js
================================================
import extend from '../../../src/util/extend';
describe('extend', () => {
const defaults = {
number: 1,
bool: true,
magic: 'real',
animal: 'whale',
croutons: 'delicious'
};
const options = {
number: 2,
magic: 'real',
animal: 'porpoise',
bool: false,
random: 42
};
it('should merge two objects', () => {
const expected = {
animal: 'porpoise',
bool: false,
croutons: 'delicious',
magic: 'real',
number: 2,
random: 42
};
expect(extend(defaults, options)).toEqual(expected);
});
});
================================================
FILE: test/tests/util/getActiveSection.test.js
================================================
import getActiveSection from '../../../src/util/getActiveSection';
import { onlyH2Data, allData } from '../../fixtures/sectionData';
describe('getActiveSection', () => {
it('should return the first section when it falls within the range', () => {
const activeSection = getActiveSection(onlyH2Data, 50, 180);
expect(activeSection).toEqual(onlyH2Data[0]);
});
it('should return the second section when it falls within the range', () => {
const activeSection = getActiveSection(onlyH2Data, 150, 280);
expect(activeSection).not.toEqual(onlyH2Data[0]);
expect(activeSection).toEqual(onlyH2Data[1]);
});
it('should return undefined when no section falls within the range', () => {
const activeSection = getActiveSection(onlyH2Data, 0, 80);
expect(activeSection).toEqual(undefined);
});
it(`should return the sub-section and not it's parent when it falls within the
range`, () => {
const activeSection = getActiveSection(allData, 150, 280);
expect(activeSection).not.toEqual(allData[1]);
expect(activeSection).toEqual(allData[1].subSections[0]);
});
});
================================================
FILE: test/tests/util/getOrSetID.test.js
================================================
import getOrSetID from '../../../src/util/getOrSetID';
describe('getOrSetID', () => {
let elem;
beforeEach(() => {
elem = {};
});
it('should return an error if first param is not an object', () => {
return expect(getOrSetID('elem')).rejects.toThrow(
'First argument must be an object'
);
});
it('should return an error if second param is not a string', () => {
return expect(getOrSetID(elem, elem)).rejects.toThrow(
'Second argument must be a string'
);
});
it('should return the id property of an element that has an id', () => {
elem.id = 'test-1-id';
expect(getOrSetID(elem)).toEqual('test-1-id');
});
it('should not change the id property of an element that has an id', () => {
elem.id = 'test-1-id';
getOrSetID(elem, 'test-1-fail');
expect(elem.id).toEqual('test-1-id');
});
it(`should return the passed id argument of an element
that does not have a defined id proptery`, () => {
elem.id;
expect(elem.id).toEqual(undefined);
expect(getOrSetID(elem, 'test-2-id')).toEqual('test-2-id');
});
it(`should set the id property of an element
that does not have a defined id property`, () => {
elem.id;
expect(elem.id).toEqual(undefined);
getOrSetID(elem, 'test-2-id');
expect(elem.id).toEqual('test-2-id');
});
});
================================================
FILE: test/tests/util/getTargetYPosition.test.js
================================================
import getTargetYPosition from '../../../src/util/getTargetYPosition';
import { onlyH2Data, allData } from '../../fixtures/sectionData';
import { onlyH2Nav, allNav } from '../../fixtures/navMarkup';
describe('getTargetYPosition', () => {
let nav;
let links;
beforeAll(() => {
window.innerHeight = 1000;
document.body.innerHTML = onlyH2Nav;
nav = document.querySelector('nav');
links = nav.querySelectorAll('a');
});
it('should return the Y position of the section element', () => {
document.body.innerHTML = onlyH2Nav;
nav = document.querySelector('nav');
links = nav.querySelectorAll('a');
const targetYPosition = getTargetYPosition(links[0], onlyH2Data);
expect(targetYPosition).toEqual(onlyH2Data[0].offsetTop);
});
it('should return the Y position of the sub-section element', () => {
document.body.innerHTML = allNav;
nav = document.querySelector('nav');
links = nav.querySelectorAll('ol ol a');
const targetYPosition = getTargetYPosition(links[0], allData);
expect(targetYPosition).toEqual(allData[1].subSections[0].offsetTop);
});
});
================================================
FILE: test/tests/util/getYPosition.test.js
================================================
import getYPosition from '../../../src/util/getYPosition';
describe('getYPosition', () => {
let elem;
const WINDOW_TOP = 0;
const PARENT_TOP = 140;
const ELEM_TOP = 260;
beforeAll(() => {
document.body.getBoundingClientRect = () => {
return {
bottom: 1000,
height: 1000,
left: 0,
right: 800,
top: WINDOW_TOP,
width: 800
};
};
});
beforeEach(() => {
elem = {};
elem.getBoundingClientRect = () => {
return {
bottom: 700,
height: 40,
left: 0,
right: 800,
top: ELEM_TOP,
width: 800
};
};
});
it('should return an error if first param is not an object', () => {
return expect(getYPosition('elem')).rejects.toThrow(
'First argument must be an object'
);
});
it('should return an error if second param is not an object', () => {
return expect(getYPosition(elem, 'parent')).rejects.toThrow(
'Second argument must be an object'
);
});
it(`should return the Y position of the element relative to the window
body if no parent is passed`, () => {
expect(getYPosition(elem)).toEqual(ELEM_TOP - WINDOW_TOP);
});
it(`should return the Y position of the element relative to the window body
if the window body is passed`, () => {
expect(getYPosition(elem, window.body)).toEqual(ELEM_TOP - WINDOW_TOP);
});
it(`should return the Y position of the element relative to a parent elem
if the parent elem is passed`, () => {
const parentElem = {
getBoundingClientRect: () => {
return {
bottom: 640,
height: 500,
left: 0,
right: 800,
top: PARENT_TOP,
width: 800
};
}
};
expect(getYPosition(elem, parentElem)).toEqual(ELEM_TOP - PARENT_TOP);
});
});
================================================
FILE: test/tests/util/insertNav.test.js
================================================
import insertNav from '../../../src/util/insertNav';
import { html as content } from '../../fixtures/sectionMarkup';
describe('insertNav', () => {
let testContent;
let newDiv;
let scrollnav;
beforeEach(() => {
document.body.innerHTML = content;
testContent = document.querySelector('.test-content');
newDiv = document.createElement('div');
newDiv.className = 'new-div';
scrollnav = {
settings: { insertTarget: testContent },
nav: newDiv
};
});
it('should append the new element as the last child of the existing element', () => {
const lastChild = testContent.lastChild;
scrollnav.settings.insertLocation = 'append';
insertNav(scrollnav);
const foundDiv = document.querySelector('.new-div');
expect(foundDiv.parentNode).toEqual(testContent);
expect(foundDiv.nextSibling).toEqual(null);
expect(foundDiv.previousSibling).toEqual(lastChild);
});
it('should prepend the new element as the first child of the existing element', () => {
const firstChild = testContent.firstChild;
scrollnav.settings.insertLocation = 'prepend';
insertNav(scrollnav);
const foundDiv = document.querySelector('.new-div');
expect(foundDiv.parentNode).toEqual(testContent);
expect(foundDiv.nextSibling).toEqual(firstChild);
expect(foundDiv.previousSibling).toEqual(null);
});
it('should insert the new element as the previous sibling of the existing element', () => {
scrollnav.settings.insertLocation = 'before';
insertNav(scrollnav);
const foundDiv = document.querySelector('.new-div');
expect(foundDiv.parentNode).toEqual(document.body);
expect(foundDiv.nextSibling).toEqual(testContent);
});
it('should insert the new element as the next sibling of the existing element', () => {
scrollnav.settings.insertLocation = 'after';
insertNav(scrollnav);
const foundDiv = document.querySelector('.new-div');
expect(foundDiv.parentNode).toEqual(document.body);
expect(foundDiv.previousSibling).toEqual(testContent);
});
});
================================================
FILE: test/tests/util/insertVisualDebugger.test.js
================================================
import jestDomCustomMatchers from '@jarmee/jest-dom-custom-matchers';
import insertVisualDebugger from '../../../src/util/insertVisualDebugger';
expect.extend(jestDomCustomMatchers);
describe('insertVisualDebugger', () => {
it('should insert the debugger with the correct dimensions', () => {
insertVisualDebugger();
const debuggerDom = document.querySelector('.snDebugger');
expect(debuggerDom).toBeHTMLElement('div');
expect(debuggerDom).toHaveClass('snDebugger');
});
});
================================================
FILE: test/tests/util/nextUntil.test.js
================================================
import nextUntil from '../../../src/util/nextUntil';
import { html } from '../../fixtures/sectionMarkup.js';
describe('nextUntil', () => {
let sections;
let subSections;
beforeAll(() => {
document.body.innerHTML = html;
sections = document.querySelectorAll('h2');
subSections = document.querySelectorAll('h3');
});
it('should return the subsections that belong to a section', () => {
const data = nextUntil(sections[1], 'h2', 'h3');
expect(data.length).toBe(1);
expect(data).toEqual([subSections[0]]);
});
});
================================================
FILE: test/tests/util/populateSectionData.test.js
================================================
import populateSectionData from '../../../src/util/populateSectionData';
import { html } from '../../fixtures/sectionMarkup';
import { onlyH2Data, allData } from '../../fixtures/sectionData';
describe('populateSectionData', () => {
let sections;
let subSections;
const settings = {};
beforeAll(() => {
document.body.innerHTML = html;
sections = document.querySelectorAll('h2');
subSections = document.querySelectorAll('h3');
settings.sections = 'h2';
sections.forEach((elem, i) => {
elem.getBoundingClientRect = () => {
return {
bottom: 800,
height: 100,
left: 0,
right: 800,
top: 100 * (i + 1),
width: 800
};
};
});
subSections.forEach(elem => {
elem.getBoundingClientRect = () => {
return {
bottom: 800,
height: 25,
left: 0,
right: 800,
top: 225,
width: 800
};
};
});
});
it('should find and add all h2 elements to the array', () => {
const data = populateSectionData(sections, settings);
expect(data.length).toBe(3);
expect(data).toEqual(onlyH2Data);
});
it('should find and add all h3 elements when showSubItems is enabled', () => {
settings.subSections = 'h3';
const data = populateSectionData(sections, settings);
expect(data[1].subSections.length).toBe(1);
expect(data[1].subSections).toEqual(allData[1].subSections);
});
});
================================================
FILE: test/tests/util/updateActiveNavItem.test.js
================================================
import jestDomCustomMatchers from '@jarmee/jest-dom-custom-matchers';
import updateActiveNavItem from '../../../src/util/updateActiveNavItem';
import { onlyH2Data } from '../../fixtures/sectionData';
import { onlyH2Nav } from '../../fixtures/navMarkup';
expect.extend(jestDomCustomMatchers);
describe('updateActiveNavItem', () => {
let nav;
let items;
beforeAll(() => {
document.body.innerHTML = onlyH2Nav;
nav = document.querySelector('nav');
items = nav.querySelectorAll('li');
});
it('should add the active class and data attribute', () => {
updateActiveNavItem(onlyH2Data[0], nav);
expect(items[0]).toHaveClass('scroll-nav__item--active');
expect(items[0]).toHaveAttribute('data-sn-active', true);
});
it('should remove the active class and data attribute', () => {
items[0].classList.add('scroll-nav__item--active');
items[0].setAttribute('data-sn-active', true);
updateActiveNavItem(undefined, nav);
expect(items[0]).not.toHaveClass('scroll-nav__item--active');
expect(items[0]).not.toHaveAttribute('data-sn-active', true);
});
it('should switch the active class and data attributes', () => {
items[0].classList.add('scroll-nav__item--active');
items[0].setAttribute('data-sn-active', true);
updateActiveNavItem(onlyH2Data[2], nav);
expect(items[0]).not.toHaveClass('scroll-nav__item--active');
expect(items[0]).not.toHaveAttribute('data-sn-active', true);
expect(items[2]).toHaveClass('scroll-nav__item--active');
expect(items[2]).toHaveAttribute('data-sn-active', true);
});
it('should do nothing if there is no previous active item', () => {
updateActiveNavItem(undefined, nav);
expect(items[0]).not.toHaveClass('scroll-nav__item--active');
expect(items[0]).not.toHaveAttribute('data-sn-active', true);
expect(items[2]).not.toHaveClass('scroll-nav__item--active');
expect(items[2]).not.toHaveAttribute('data-sn-active', true);
});
it(`should do nothing if the previous active item is the same as the current
active item`, () => {
items[0].classList.add('scroll-nav__item--active');
items[0].setAttribute('data-sn-active', true);
updateActiveNavItem(onlyH2Data[0], nav);
expect(items[0]).toHaveClass('scroll-nav__item--active');
expect(items[0]).toHaveAttribute('data-sn-active', true);
});
});
================================================
FILE: test/tests/util/updatePositionData.test.js
================================================
import updatePositionData from '../../../src/util/updatePositionData';
import { html } from '../../fixtures/sectionMarkup';
import { onlyH2Data, allData } from '../../fixtures/sectionData';
describe('updatePositionData', () => {
let sections;
let subSections;
beforeAll(() => {
document.body.innerHTML = html;
sections = document.querySelectorAll('h2');
subSections = document.querySelectorAll('h3');
subSections[0].id = 'second-heading__1';
sections.forEach((elem, i) => {
elem.getBoundingClientRect = () => {
return {
bottom: 800,
height: 100,
left: 0,
right: 800,
top: 200 * (i + 1),
width: 800
};
};
});
subSections.forEach(elem => {
elem.getBoundingClientRect = () => {
return {
bottom: 800,
height: 25,
left: 0,
right: 800,
top: 450,
width: 800
};
};
});
});
it('should get the correct positions of each section', () => {
const data = updatePositionData(onlyH2Data);
expect(data[0].offsetTop).toEqual(200);
expect(data[1].offsetTop).toEqual(400);
expect(data[2].offsetTop).toEqual(600);
});
it('should get the correct positions of each sub-section', () => {
const data = updatePositionData(allData);
expect(data[0].offsetTop).toEqual(200);
expect(data[1].offsetTop).toEqual(400);
expect(data[1].subSections[0].offsetTop).toEqual(450);
expect(data[2].offsetTop).toEqual(600);
});
});
================================================
FILE: test/util/simulateEvent.js
================================================
/**
* @param {string} eventType - The type of event.
* @param {HTMLNode} target - Target of the event.
* @param {Object} eventOption - Options to add to the event.
* @returns {HTMLNode} The target of the event.
*/
export default function simulateEvent(eventType, target, eventOption) {
const event = document.createEvent('Event');
if (eventOption && eventOption.keyCode) {
event.keyCode = eventOption.keyCode;
}
event.initEvent(eventType, true, true);
return target.dispatchEvent(event);
}
gitextract_xvjymejg/
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .jestrc.json
├── .prettierrc.json
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE-MIT
├── README.md
├── bower.json
├── dist/
│ ├── scrollnav.min.mjs
│ └── scrollnav.min.umd.js
├── jest.setup.js
├── jest.transform.js
├── package.json
├── src/
│ ├── scrollNav.v2-7-3.js
│ ├── scrollTo.js
│ ├── scrollnav.js
│ ├── setupClickHandlers.js
│ ├── setupResizeHandler.js
│ ├── setupScrollHandler.js
│ ├── teardownClickHandlers.js
│ ├── teardownResizeHandler.js
│ ├── teardownScrollHandler.js
│ └── util/
│ ├── calculateScrollDuration.js
│ ├── createList.js
│ ├── createNav.js
│ ├── easing.js
│ ├── extend.js
│ ├── getActiveSection.js
│ ├── getOrSetID.js
│ ├── getTargetYPosition.js
│ ├── getYPosition.js
│ ├── insertNav.js
│ ├── insertVisualDebugger.js
│ ├── nextUntil.js
│ ├── populateSectionData.js
│ ├── updateActiveNavItem.js
│ └── updatePositionData.js
└── test/
├── .eslintrc.json
├── fixtures/
│ ├── navMarkup.js
│ ├── noSectionsMarkup.js
│ ├── pennerEasing.js
│ ├── sectionData.js
│ └── sectionMarkup.js
├── tests/
│ ├── scrollTo.test.js
│ ├── scrollnav.test.js
│ ├── setupClickHandlers.test.js
│ ├── setupResizeHandler.test.js
│ ├── setupScrollHander.test.js
│ ├── teardownClickHandlers.test.js
│ ├── teardownResizeHandler.test.js
│ ├── teardownScrollHander.test.js
│ └── util/
│ ├── calculateScrollDuration.test.js
│ ├── createList.test.js
│ ├── createNav.test.js
│ ├── easing.test.js
│ ├── extend.test.js
│ ├── getActiveSection.test.js
│ ├── getOrSetID.test.js
│ ├── getTargetYPosition.test.js
│ ├── getYPosition.test.js
│ ├── insertNav.test.js
│ ├── insertVisualDebugger.test.js
│ ├── nextUntil.test.js
│ ├── populateSectionData.test.js
│ ├── updateActiveNavItem.test.js
│ └── updatePositionData.test.js
└── util/
└── simulateEvent.js
SYMBOL INDEX (49 symbols across 25 files)
FILE: dist/scrollnav.min.mjs
function f (line 1) | function f(f,h){var y,w={};for(y in f)Object.prototype.hasOwnProperty.ca...
function h (line 1) | function h(f,h){if("object"!=typeof f)return Promise.reject(new Error("F...
function y (line 1) | function y(f,w,E){void 0===E&&(E="scroll-nav");var L=[];return E+="__",f...
function w (line 1) | function w(f){var h=document.createElement("nav");return h.className="sc...
function E (line 1) | function E(f){return f.forEach(function(f){var y=document.querySelector(...
function L (line 1) | function L(f,h){var y=f.getAttribute("href");return"#"===y.charAt(0)&&(y...
function M (line 1) | function M(f,h){return new Promise(function(y,w){if("number"!=typeof f)r...
function q (line 1) | function q(f){function h(){var h=window.scrollY||window.pageYOffset||doc...
function B (line 1) | function B(f){return f instanceof Element}
function y (line 1) | function y(y){y.preventDefault();var w=.39*window.innerHeight;return M(L...
function h (line 1) | function h(){f.data=E(f.data)}
FILE: dist/scrollnav.min.umd.js
function f (line 1) | function f(f,h){var y,w={};for(y in f)Object.prototype.hasOwnProperty.ca...
function h (line 1) | function h(f,h){if("object"!=typeof f)return Promise.reject(new Error("F...
function y (line 1) | function y(f,w,E){void 0===E&&(E="scroll-nav");var L=[];return E+="__",f...
function w (line 1) | function w(f){var h=document.createElement("nav");return h.className="sc...
function E (line 1) | function E(f){return f.forEach(function(f){var y=document.querySelector(...
function L (line 1) | function L(f,h){var y=f.getAttribute("href");return"#"===y.charAt(0)&&(y...
function M (line 1) | function M(f,h){return new Promise(function(y,w){if("number"!=typeof f)r...
function q (line 1) | function q(f){function h(){var h=window.scrollY||window.pageYOffset||doc...
function B (line 1) | function B(f){return f instanceof Element}
function y (line 1) | function y(y){y.preventDefault();var w=.39*window.innerHeight;return M(L...
function h (line 1) | function h(){f.data=E(f.data)}
FILE: src/scrollTo.js
function scrollTo (line 5) | function scrollTo(targetPosition, easingStyle) {
FILE: src/scrollnav.js
function isElement (line 26) | function isElement(element) {
function init (line 30) | function init(elem, options) {
function destroy (line 98) | function destroy(options) {
function updatePositions (line 109) | function updatePositions(options) {
FILE: src/setupClickHandlers.js
function setupClickHandlers (line 4) | function setupClickHandlers(scrollnav) {
FILE: src/setupResizeHandler.js
function setupResizeHandler (line 3) | function setupResizeHandler(scrollnav) {
FILE: src/setupScrollHandler.js
function setupScrollHandler (line 4) | function setupScrollHandler(scrollnav) {
FILE: src/teardownClickHandlers.js
function teardownClickHandlers (line 1) | function teardownClickHandlers(nav, clickHandler) {
FILE: src/teardownResizeHandler.js
function teardownResizelHandler (line 1) | function teardownResizelHandler(resizeHandler) {
FILE: src/teardownScrollHandler.js
function teardownScrollHandler (line 1) | function teardownScrollHandler(scrollHandler) {
FILE: src/util/calculateScrollDuration.js
function calculateScrollDuration (line 1) | function calculateScrollDuration(distance) {
FILE: src/util/createList.js
function createList (line 1) | function createList(data, isSubList = false) {
FILE: src/util/createNav.js
function createNav (line 3) | function createNav(data) {
FILE: src/util/extend.js
function extend (line 3) | function extend(defaults, options) {
FILE: src/util/getActiveSection.js
function getActiveSection (line 1) | function getActiveSection(data, boundryTop, boundryBottom) {
FILE: src/util/getOrSetID.js
function getOrSetID (line 1) | function getOrSetID(elem, setID) {
FILE: src/util/getTargetYPosition.js
function getTargetYPosition (line 1) | function getTargetYPosition(target, data) {
function filterData (line 12) | function filterData(data, id) {
FILE: src/util/getYPosition.js
function getYPosition (line 1) | function getYPosition(elem, parent) {
FILE: src/util/insertNav.js
function insertNav (line 1) | function insertNav(scrollnav) {
FILE: src/util/insertVisualDebugger.js
function insertVisualDebugger (line 1) | function insertVisualDebugger() {
FILE: src/util/nextUntil.js
function nextUntil (line 3) | function nextUntil(elem, selector, filter) {
FILE: src/util/populateSectionData.js
function populateSectionData (line 5) | function populateSectionData(
FILE: src/util/updateActiveNavItem.js
function updateActiveNavItem (line 1) | function updateActiveNavItem(activeSection, nav) {
FILE: src/util/updatePositionData.js
function updatePositionData (line 3) | function updatePositionData(data) {
FILE: test/util/simulateEvent.js
function simulateEvent (line 7) | function simulateEvent(eventType, target, eventOption) {
Condensed preview — 70 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (131K chars).
[
{
"path": ".eslintignore",
"chars": 14,
"preview": "test/coverage\n"
},
{
"path": ".eslintrc.json",
"chars": 411,
"preview": "{\n \"env\": {\n \"browser\": true,\n \"es6\": true\n },\n \"plugins\": [\"prettier\"],\n \"extends\": [\"eslint:recommended\", \"p"
},
{
"path": ".gitignore",
"chars": 130,
"preview": "# System Files\n*.swp\n.DS_Store\n\n# Node Files\n/node_modules/\nnpm-debug.log\nbower_components\n\n## Test Coverage Files\ntest/"
},
{
"path": ".jestrc.json",
"chars": 305,
"preview": "{\n \"collectCoverage\": true,\n \"coverageDirectory\": \"test/coverage\",\n \"collectCoverageFrom\": [\"src/**/*.js\"],\n \"covera"
},
{
"path": ".prettierrc.json",
"chars": 26,
"preview": "{\n \"singleQuote\": true\n}\n"
},
{
"path": ".travis.yml",
"chars": 105,
"preview": "language: node_js\nnode_js:\n - \"8\"\n - \"10\"\ninstall:\n - npm install\n - npm install codecov\n - codecov\n"
},
{
"path": "CHANGELOG.md",
"chars": 5565,
"preview": "# Changelog\n\nReleases are be numbered in the semantic versioning format:\n\n`<major>.<minor>.<patch>`\n\nAnd constructed wit"
},
{
"path": "CONTRIBUTING.md",
"chars": 4589,
"preview": "# Contributing Guideline\n\nThanks for contributing to scrollnav. Without you and the Open Source community\nthis project w"
},
{
"path": "LICENSE-MIT",
"chars": 1056,
"preview": "Copyright (c) 2018 James Wilson\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this so"
},
{
"path": "README.md",
"chars": 11839,
"preview": "> This project is currently archived. I unfortunately don't have the time to maintain it. Thanks to everyone that contri"
},
{
"path": "bower.json",
"chars": 618,
"preview": "{\n \"name\": \"scrollnav\",\n \"version\": \"3.0.2\",\n \"author\": {\n \"name\": \"James Wilson\",\n \"email\": \"jimmynotjim@me.co"
},
{
"path": "dist/scrollnav.min.mjs",
"chars": 6363,
"preview": "function f(f,h){var y,w={};for(y in f)Object.prototype.hasOwnProperty.call(f,y)&&(w[y]=f[y]);for(y in h)Object.prototype"
},
{
"path": "dist/scrollnav.min.umd.js",
"chars": 6528,
"preview": "!function(f,h){\"object\"==typeof exports&&\"undefined\"!=typeof module?module.exports=h():\"function\"==typeof define&&define"
},
{
"path": "jest.setup.js",
"chars": 336,
"preview": "import '@babel/polyfill';\n\nwindow.scrollTo = window.scroll = (xVal, yVal) => {\n window.pageXOffset = xVal;\n window.pag"
},
{
"path": "jest.transform.js",
"chars": 96,
"preview": "module.exports = require('babel-jest').createTransformer({\n presets: ['@babel/preset-env']\n});\n"
},
{
"path": "package.json",
"chars": 1883,
"preview": "{\n \"name\": \"scrollnav\",\n \"version\": \"3.0.2\",\n \"title\": \"scrollnav\",\n \"author\": {\n \"name\": \"James Wilson\",\n \"em"
},
{
"path": "src/scrollNav.v2-7-3.js",
"chars": 16790,
"preview": "/*\n * scrollNav\n * http://scrollnav.com\n *\n * Copyright (c) 2013-2016 James Wilson\n * Licensed under the MIT license.\n *"
},
{
"path": "src/scrollTo.js",
"chars": 1206,
"preview": "import calculateScrollDuration from './util/calculateScrollDuration';\nimport { easing } from './util/easing';\n\n/* istanb"
},
{
"path": "src/scrollnav.js",
"chars": 3409,
"preview": "/*\n * scrollnav\n * http://scrollnav.com\n *\n * Copyright (c) 2013-2018 James Wilson\n * Licensed under the MIT license.\n *"
},
{
"path": "src/setupClickHandlers.js",
"chars": 899,
"preview": "import getTargetYPosition from './util/getTargetYPosition';\nimport scrollTo from './scrollTo';\n\nexport default function "
},
{
"path": "src/setupResizeHandler.js",
"chars": 287,
"preview": "import updatePositionData from './util/updatePositionData';\n\nexport default function setupResizeHandler(scrollnav) {\n f"
},
{
"path": "src/setupScrollHandler.js",
"chars": 649,
"preview": "import getActiveSection from './util/getActiveSection';\nimport updateActiveNavItem from './util/updateActiveNavItem';\n\ne"
},
{
"path": "src/teardownClickHandlers.js",
"chars": 197,
"preview": "export default function teardownClickHandlers(nav, clickHandler) {\n const links = nav.querySelectorAll('a');\n links.fo"
},
{
"path": "src/teardownResizeHandler.js",
"chars": 121,
"preview": "export default function teardownResizelHandler(resizeHandler) {\n window.removeEventListener('resize', resizeHandler);\n}"
},
{
"path": "src/teardownScrollHandler.js",
"chars": 120,
"preview": "export default function teardownScrollHandler(scrollHandler) {\n window.removeEventListener('scroll', scrollHandler);\n}\n"
},
{
"path": "src/util/calculateScrollDuration.js",
"chars": 164,
"preview": "export default function calculateScrollDuration(distance) {\n const halfDistance = Math.abs(distance / 2);\n\n return Mat"
},
{
"path": "src/util/createList.js",
"chars": 680,
"preview": "export default function createList(data, isSubList = false) {\n const suffix = isSubList ? '__sub-' : '__';\n const base"
},
{
"path": "src/util/createNav.js",
"chars": 212,
"preview": "import createList from './createList';\n\nexport default function createNav(data) {\n const nav = document.createElement('"
},
{
"path": "src/util/easing.js",
"chars": 679,
"preview": "/* Borrowed from https://gist.github.com/gre/1650294#gistcomment-1806616 */\n\nconst easeIn = p => t => Math.pow(t, p);\nco"
},
{
"path": "src/util/extend.js",
"chars": 466,
"preview": "/* Borrowed from https://gist.github.com/cferdinandi/4f8a0e17921c5b46e6c4 */\n\nexport default function extend(defaults, o"
},
{
"path": "src/util/getActiveSection.js",
"chars": 642,
"preview": "export default function getActiveSection(data, boundryTop, boundryBottom) {\n let activeSection;\n\n data.forEach(section"
},
{
"path": "src/util/getOrSetID.js",
"chars": 370,
"preview": "export default function getOrSetID(elem, setID) {\n if (typeof elem !== 'object') {\n return Promise.reject(new Error("
},
{
"path": "src/util/getTargetYPosition.js",
"chars": 550,
"preview": "export default function getTargetYPosition(target, data) {\n let id = target.getAttribute('href');\n if (id.charAt(0) =="
},
{
"path": "src/util/getYPosition.js",
"chars": 459,
"preview": "export default function getYPosition(elem, parent) {\n if (typeof elem !== 'object') {\n return Promise.reject(new Err"
},
{
"path": "src/util/insertNav.js",
"chars": 531,
"preview": "export default function insertNav(scrollnav) {\n const target = scrollnav.settings.insertTarget;\n const location = scro"
},
{
"path": "src/util/insertVisualDebugger.js",
"chars": 435,
"preview": "export default function insertVisualDebugger() {\n const snDebugger = document.createElement('div');\n snDebugger.classN"
},
{
"path": "src/util/nextUntil.js",
"chars": 428,
"preview": "/* Borrowed from https://github.com/cferdinandi/nextUntil */\n\nexport default function nextUntil(elem, selector, filter) "
},
{
"path": "src/util/populateSectionData.js",
"chars": 845,
"preview": "import getOrSetID from './getOrSetID';\nimport getYPosition from './getYPosition';\nimport nextUntil from './nextUntil';\n\n"
},
{
"path": "src/util/updateActiveNavItem.js",
"chars": 743,
"preview": "export default function updateActiveNavItem(activeSection, nav) {\n const previousActive = nav.querySelector('[data-sn-a"
},
{
"path": "src/util/updatePositionData.js",
"chars": 376,
"preview": "import getYPosition from './getYPosition';\n\nexport default function updatePositionData(data) {\n data.forEach(section =>"
},
{
"path": "test/.eslintrc.json",
"chars": 36,
"preview": "{\n \"env\": {\n \"jest\": true\n }\n}\n"
},
{
"path": "test/fixtures/navMarkup.js",
"chars": 1505,
"preview": "const onlyH2Nav = `\n <nav class=\"scroll-nav\" role=\"navigation\">\n <ul class=\"scroll-nav__list\">\n <li class=\"scro"
},
{
"path": "test/fixtures/noSectionsMarkup.js",
"chars": 1722,
"preview": "const html = `\n <div class=\"test-content\">\n <p>Lorem ipsum dolor sit amet, in vis quis verear persecuti, sed ne equi"
},
{
"path": "test/fixtures/pennerEasing.js",
"chars": 1502,
"preview": "/* Source https://github.com/CharlotteGore/functional-easing */\n/* eslint-disable */\nMath.linear = function(t, b, c, d) "
},
{
"path": "test/fixtures/sectionData.js",
"chars": 846,
"preview": "const onlyH2Data = [\n {\n id: 'first-heading',\n text: 'First heading',\n offsetTop: 100,\n subSections: []\n }"
},
{
"path": "test/fixtures/sectionMarkup.js",
"chars": 1915,
"preview": "const html = `\n <div class=\"test-content\">\n <p>Lorem ipsum dolor sit amet, in vis quis verear persecuti, sed ne equi"
},
{
"path": "test/tests/scrollTo.test.js",
"chars": 760,
"preview": "import scrollTo from '../../src/scrollTo';\n\njest.useFakeTimers();\n\ndescribe('scrollTo', () => {\n it('should return an e"
},
{
"path": "test/tests/scrollnav.test.js",
"chars": 10762,
"preview": "/* eslint no-console: 0 */\nimport jestDomCustomMatchers from '@jarmee/jest-dom-custom-matchers';\nimport scrollnav from '"
},
{
"path": "test/tests/setupClickHandlers.test.js",
"chars": 1853,
"preview": "import setupClickHandlers from '../../src/setupClickHandlers';\nimport { onlyH2Data, allData } from '../fixtures/sectionD"
},
{
"path": "test/tests/setupResizeHandler.test.js",
"chars": 1030,
"preview": "import setupResizeHandler from '../../src/setupResizeHandler';\nimport { html } from '../fixtures/sectionMarkup';\nimport "
},
{
"path": "test/tests/setupScrollHander.test.js",
"chars": 1727,
"preview": "import jestDomCustomMatchers from '@jarmee/jest-dom-custom-matchers';\nimport setupScrollHandler from '../../src/setupScr"
},
{
"path": "test/tests/teardownClickHandlers.test.js",
"chars": 936,
"preview": "import setupClickHandlers from '../../src/setupClickHandlers';\nimport teardownClickHandlers from '../../src/teardownClic"
},
{
"path": "test/tests/teardownResizeHandler.test.js",
"chars": 1181,
"preview": "import setupResizeHandler from '../../src/setupResizeHandler';\nimport teardownResizeHandler from '../../src/teardownResi"
},
{
"path": "test/tests/teardownScrollHander.test.js",
"chars": 1694,
"preview": "import jestDomCustomMatchers from '@jarmee/jest-dom-custom-matchers';\nimport setupScrollHandler from '../../src/setupScr"
},
{
"path": "test/tests/util/calculateScrollDuration.test.js",
"chars": 652,
"preview": "import calculateScrollDuration from '../../../src/util/calculateScrollDuration.js';\n\ndescribe('calculateScrollDuration',"
},
{
"path": "test/tests/util/createList.test.js",
"chars": 3047,
"preview": "import jestDomCustomMatchers from '@jarmee/jest-dom-custom-matchers';\nimport createList from '../../../src/util/createLi"
},
{
"path": "test/tests/util/createNav.test.js",
"chars": 703,
"preview": "import jestDomCustomMatchers from '@jarmee/jest-dom-custom-matchers';\nimport createNav from '../../../src/util/createNav"
},
{
"path": "test/tests/util/easing.test.js",
"chars": 4188,
"preview": "require('../../fixtures/pennerEasing');\n\nimport { easing } from '../../../src/util/easing';\n\ndescribe('easing', () => {\n"
},
{
"path": "test/tests/util/extend.test.js",
"chars": 583,
"preview": "import extend from '../../../src/util/extend';\n\ndescribe('extend', () => {\n const defaults = {\n number: 1,\n bool:"
},
{
"path": "test/tests/util/getActiveSection.test.js",
"chars": 1115,
"preview": "import getActiveSection from '../../../src/util/getActiveSection';\nimport { onlyH2Data, allData } from '../../fixtures/s"
},
{
"path": "test/tests/util/getOrSetID.test.js",
"chars": 1347,
"preview": "import getOrSetID from '../../../src/util/getOrSetID';\n\ndescribe('getOrSetID', () => {\n let elem;\n\n beforeEach(() => {"
},
{
"path": "test/tests/util/getTargetYPosition.test.js",
"chars": 1122,
"preview": "import getTargetYPosition from '../../../src/util/getTargetYPosition';\nimport { onlyH2Data, allData } from '../../fixtur"
},
{
"path": "test/tests/util/getYPosition.test.js",
"chars": 1864,
"preview": "import getYPosition from '../../../src/util/getYPosition';\n\ndescribe('getYPosition', () => {\n let elem;\n const WINDOW_"
},
{
"path": "test/tests/util/insertNav.test.js",
"chars": 2065,
"preview": "import insertNav from '../../../src/util/insertNav';\nimport { html as content } from '../../fixtures/sectionMarkup';\n\nde"
},
{
"path": "test/tests/util/insertVisualDebugger.test.js",
"chars": 499,
"preview": "import jestDomCustomMatchers from '@jarmee/jest-dom-custom-matchers';\nimport insertVisualDebugger from '../../../src/uti"
},
{
"path": "test/tests/util/nextUntil.test.js",
"chars": 550,
"preview": "import nextUntil from '../../../src/util/nextUntil';\nimport { html } from '../../fixtures/sectionMarkup.js';\n\ndescribe('"
},
{
"path": "test/tests/util/populateSectionData.test.js",
"chars": 1493,
"preview": "import populateSectionData from '../../../src/util/populateSectionData';\nimport { html } from '../../fixtures/sectionMar"
},
{
"path": "test/tests/util/updateActiveNavItem.test.js",
"chars": 2363,
"preview": "import jestDomCustomMatchers from '@jarmee/jest-dom-custom-matchers';\nimport updateActiveNavItem from '../../../src/util"
},
{
"path": "test/tests/util/updatePositionData.test.js",
"chars": 1556,
"preview": "import updatePositionData from '../../../src/util/updatePositionData';\nimport { html } from '../../fixtures/sectionMarku"
},
{
"path": "test/util/simulateEvent.js",
"chars": 509,
"preview": "/**\n * @param {string} eventType - The type of event.\n * @param {HTMLNode} target - Target of the event.\n * @param {Obje"
}
]
About this extraction
This page contains the full source code of the jimmynotjim/scrollNav GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 70 files (119.4 KB), approximately 33.0k tokens, and a symbol index with 49 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.