diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..ce4b4cd --- /dev/null +++ b/.eslintrc @@ -0,0 +1,245 @@ +{ + "env": { + "browser": true, + "commonjs": true, + "node": true + }, + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + "extends": "eslint:recommended", + "rules": { + "no-irregular-whitespace": [ + "error", + { + "skipComments": true + } + ], + "accessor-pairs": "error", + "array-bracket-spacing": [ + "error", + "never" + ], + "array-callback-return": "error", + "arrow-body-style": "error", + "arrow-parens": "error", + "arrow-spacing": "error", + "block-scoped-var": "error", + "block-spacing": [ + "error", + "always" + ], + "brace-style": [ + "error", + "1tbs", + { + "allowSingleLine": true + } + ], + "callback-return": "error", + "camelcase": "error", + "comma-spacing": [ + "error", + { + "after": true, + "before": false + } + ], + "comma-style": [ + "error", + "last" + ], + "complexity": "error", + "computed-property-spacing": "off", + "consistent-return": "off", + "consistent-this": "off", + "curly": "error", + "default-case": "error", + "dot-location": [ + "error", + "property" + ], + "dot-notation": "error", + "eol-last": "off", + "eqeqeq": "off", + "func-names": "off", + "func-style": [ + "error", + "expression" + ], + "generator-star-spacing": "error", + "global-require": "off", + "guard-for-in": "error", + "handle-callback-err": "error", + "id-blacklist": "error", + "id-length": "off", + "id-match": "error", + "indent": [ + "error", + 2 + ], + "init-declarations": "error", + "jsx-quotes": "error", + "key-spacing": "off", + "keyword-spacing": [ + "error", + { + "after": true, + "before": true + } + ], + "linebreak-style": [ + "error", + "unix" + ], + "lines-around-comment": "off", + "max-depth": "error", + "max-len": "off", + "max-nested-callbacks": "error", + "max-params": "error", + "max-statements": "off", + "new-cap": "error", + "new-parens": "error", + "newline-after-var": "off", + "newline-before-return": "off", + "newline-per-chained-call": "off", + "no-alert": "error", + "no-array-constructor": "error", + "no-bitwise": "off", + "no-caller": "error", + "no-catch-shadow": "error", + "no-cond-assign": [ + "error", + "except-parens" + ], + "no-confusing-arrow": "error", + "no-continue": "error", + "no-div-regex": "error", + "no-else-return": "error", + "no-empty-function": "error", + "no-eq-null": "error", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-label": "error", + "no-extra-parens": "off", + "no-floating-decimal": "error", + "no-implicit-globals": "error", + "no-implied-eval": "error", + "no-inline-comments": "off", + "no-inner-declarations": [ + "error", + "functions" + ], + "no-invalid-this": "off", + "no-iterator": "error", + "no-label-var": "error", + "no-labels": "error", + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-loop-func": "error", + "no-magic-numbers": "off", + "no-mixed-requires": "error", + "no-multi-spaces": "off", + "no-multi-str": "error", + "no-multiple-empty-lines": "error", + "no-native-reassign": "error", + "no-negated-condition": "off", + "no-nested-ternary": "off", + "no-new": "error", + "no-new-func": "error", + "no-new-object": "error", + "no-new-require": "error", + "no-new-wrappers": "error", + "no-octal-escape": "error", + "no-param-reassign": "off", + "no-path-concat": "error", + "no-plusplus": "off", + "no-process-env": "error", + "no-process-exit": "error", + "no-proto": "error", + "no-restricted-globals": "error", + "no-restricted-imports": "error", + "no-restricted-modules": "error", + "no-restricted-syntax": "error", + "no-return-assign": "error", + "no-script-url": "error", + "no-self-compare": "error", + "no-sequences": "error", + "no-shadow": "off", + "no-shadow-restricted-names": "error", + "no-spaced-func": "error", + "no-sync": "error", + "no-ternary": "off", + "no-throw-literal": "error", + "no-trailing-spaces": "error", + "no-undef-init": "error", + "no-undefined": "off", + "no-underscore-dangle": "off", + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": "error", + "no-unused-expressions": "off", + "no-use-before-define": "error", + "no-useless-call": "error", + "no-useless-concat": "error", + "no-useless-constructor": "error", + "no-var": "off", + "no-void": "error", + "no-warning-comments": "error", + "no-whitespace-before-property": "error", + "no-with": "error", + "object-curly-spacing": [ + "error", + "always" + ], + "object-shorthand": "off", + "one-var": "off", + "one-var-declaration-per-line": "error", + "operator-assignment": [ + "error", + "always" + ], + "operator-linebreak": "off", + "padded-blocks": "off", + "prefer-arrow-callback": "off", + "prefer-const": "error", + "prefer-reflect": "off", + "prefer-rest-params": "off", + "prefer-spread": "error", + "prefer-template": "off", + "quote-props": "off", + "quotes": [ + "error", + "single" + ], + "radix": "error", + "require-jsdoc": "error", + "require-yield": "error", + "semi": "error", + "semi-spacing": "error", + "sort-imports": "error", + "sort-vars": "error", + "space-before-blocks": "error", + "space-before-function-paren": "off", + "space-in-parens": "off", + "space-infix-ops": "error", + "space-unary-ops": "error", + "spaced-comment": [ + "error", + "always" + ], + "strict": "off", + "template-curly-spacing": "error", + "valid-jsdoc": "off", + "valid-typeof": "error", + "vars-on-top": "off", + "wrap-iife": "error", + "wrap-regex": "off", + "yield-star-spacing": "error", + "yoda": [ + "error", + "never" + ] + } +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..bdb0cab --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f076526 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules +.npm-debug.log +tmp +.sass-cache +.publish +coverage +*.sublime-* \ No newline at end of file diff --git a/README.md b/README.md index 3e058fd..c64d836 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,61 @@ -# menuspy +# MenuSpy + A JavaScript library to make navigation menus highlight active item based on the scroll position. + +* Features +* Usage +* Options +* Examples + +## Usage + +Include MenuSpy + +```html + +``` + +`MenuSpy` will be available in the global scope. + +Or install with NPM and require as a module + +``` +npm install menuspy +``` + +```js +var MenuSpy = require('menuspy'); +``` + +Initialize the plugin on your menu element + +```html +
+ +
+``` + +```js +var elm = document.querySelector('#main-header'); +var ms = new MenuSpy(elm); +``` + +The `MenuSpy()` constructor accepts two arguments: the container element and an options object. + + +## Options + +| Option | Type | Default | Description | +| ------------------ | -------- | ----------------------------------- | ------------------------------------------------------------------------ | +| `menuItemSelector` | String | `a[href^="#"]` | Menu items selector. | +| `menuItemSelector` | String | `active` | Class applied on menu item relative to the currently visible section. | +| `threshold` | Integer | `15` | Ammount of space between your menu and the next section to be activated. | +| `hashTimeout` | Integer | `600` | Timeout to apply browser's hash location. | +| `callback` | Function | `function(anchorElm, targetElm) {}` | A function to be called every time a new menu item activates. | \ No newline at end of file diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..d81a780 --- /dev/null +++ b/bower.json @@ -0,0 +1,26 @@ +{ + "name": "MenuSpy", + "version": "1.0.0", + "homepage": "https://github.com/lcdsantos/menuspy", + "authors": [ + "Leonardo Santos " + ], + "description": "A JavaScript library to make navigation menus highlight active item based on the scroll position.", + "main": "dist/menuspy.js", + "keywords": [ + "nav", + "navigation", + "menu", + "menuspy", + "scrollspy" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests", + "examples" + ] +} diff --git a/dist/menuspy.js b/dist/menuspy.js new file mode 100644 index 0000000..e36cd8f --- /dev/null +++ b/dist/menuspy.js @@ -0,0 +1,169 @@ +/*! MenuSpy v1.0.0 (Aug 29 2016) - http://lcdsantos.github.io/menuspy/ - Copyright (c) 2016 Leonardo Santos; MIT License */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.MenuSpy = factory()); +}(this, (function () { 'use strict'; + +var utils = { + extend: function extend(a, b) { + for (var key in b) { + if (b.hasOwnProperty(key)) { + a[key] = b[key]; + } + } + + return a; + }, + + offset: function offset(el) { + var rect = el.getBoundingClientRect(); + + return { + top: rect.top + document.body.scrollTop, + left: rect.left + document.body.scrollLeft + }; + }, + + scrollTop: function scrollTop() { + return window.pageYOffset || document.documentElement.scrollTop; + }, + + addClass: function addClass(el, className) { + if (el.classList) { + el.classList.add(className); + } else { + var classes = el.className.split(' '); + var existingIndex = classes.indexOf(className); + + if (existingIndex === -1) { + classes.push(className); + } + + el.className = classes.join(' '); + } + }, + + removeClass: function removeClass(el, className) { + if (el.classList) { + el.classList.remove(className); + } else { + el.className = el.className.replace(new RegExp(("(^|\\b)" + (className.split(' ').join('|')) + "(\\b|$)"), 'gi'), ' '); + } + }, + + debounce: function debounce(fn, delay) { + var timeout = null; + return function() { + var args = arguments; + var context = this; + if (!timeout) { + timeout = setTimeout(function () { + timeout = 0; + return fn.apply(context, args); + }, delay); + } + }; + } +}; + +var MenuSpy = function MenuSpy(element, options) { + var this$1 = this; + + if (!element) { + return; + } + + var defaults = { + menuItemSelector: 'a[href^="#"]', + activeClass : 'active', + threshold : 15, + hashTimeout : 600, + callback : null + }; + + this.element = element; + this.options = utils.extend(defaults, options); + + this.assignValues(); + window.addEventListener('resize', utils.debounce(function () { return this$1.assignValues(); })); + + this.debouncedHashFn = utils.debounce(function () { + if (history.replaceState) { + history.replaceState(null, null, ("#" + (this$1.lastId))); + } else { + var st = utils.scrollTop(); + window.location.hash = this$1.lastId; + window.scrollTo(0, st); + } + }, this.options.hashTimeout); + + this.cacheItems(); + this.scrollFn(); +}; + +MenuSpy.prototype.assignValues = function assignValues () { + this.currScrollTop = 0; + this.lastId = ''; + this.menuHeight = this.element.offsetHeight + this.options.threshold; + this.menuItems = [].slice.call(this.element.querySelectorAll(this.options.menuItemSelector)); +}; + +MenuSpy.prototype.cacheItems = function cacheItems () { + this.scrollItems = this.menuItems.map(function (a) { + var elm = document.querySelector(a.getAttribute('href')); + var offset = utils.offset(elm).top; + + return { elm: elm, offset: offset }; + }); +}; + +MenuSpy.prototype.tick = function tick () { + var fromTop = this.currScrollTop + this.menuHeight; + var inViewElms = this.scrollItems + .filter(function (item) { return item.offset < fromTop; }) + .map(function (item) { return item.elm; }); + + this.activateItem(inViewElms.pop()); +}; + +MenuSpy.prototype.activateItem = function activateItem (inViewElm) { + var this$1 = this; + + var id = inViewElm ? inViewElm.id : ''; + var activeClass = this.options.activeClass; + var callback = this.options.callback; + + if (this.lastId !== id) { + this.lastId = id; + + this.menuItems.forEach(function (item) { + utils.removeClass(item.parentNode, activeClass); + + if (item.getAttribute('href') === ("#" + id)) { + utils.addClass(item.parentNode, activeClass); + + if (typeof callback === 'function') { + callback.call(this$1, item, inViewElm); + } + + this$1.debouncedHashFn(); + } + }); + } +}; + +MenuSpy.prototype.scrollFn = function scrollFn () { + var st = utils.scrollTop(); + + if (this.currScrollTop !== st) { + this.currScrollTop = st; + this.tick(); + } + + window.requestAnimationFrame(this.scrollFn.bind(this)); +}; + +return MenuSpy; + +}))); \ No newline at end of file diff --git a/dist/menuspy.min.js b/dist/menuspy.min.js new file mode 100644 index 0000000..1d5b05f --- /dev/null +++ b/dist/menuspy.min.js @@ -0,0 +1,2 @@ +/*! MenuSpy v1.0.0 (Aug 29 2016) - http://lcdsantos.github.io/menuspy/ - Copyright (c) 2016 Leonardo Santos; MIT License */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.MenuSpy=e()}(this,function(){"use strict";var t={extend:function(t,e){for(var s in e)e.hasOwnProperty(s)&&(t[s]=e[s]);return t},offset:function(t){var e=t.getBoundingClientRect();return{top:e.top+document.body.scrollTop,left:e.left+document.body.scrollLeft}},scrollTop:function(){return window.pageYOffset||document.documentElement.scrollTop},addClass:function(t,e){if(t.classList)t.classList.add(e);else{var s=t.className.split(" "),o=s.indexOf(e);o===-1&&s.push(e),t.className=s.join(" ")}},removeClass:function(t,e){t.classList?t.classList.remove(e):t.className=t.className.replace(new RegExp("(^|\\b)"+e.split(" ").join("|")+"(\\b|$)","gi")," ")},debounce:function(t,e){var s=null;return function(){var o=arguments,n=this;s||(s=setTimeout(function(){return s=0,t.apply(n,o)},e))}}},e=function(e,s){var o=this;if(e){var n={menuItemSelector:'a[href^="#"]',activeClass:"active",threshold:15,hashTimeout:600,callback:null};this.element=e,this.options=t.extend(n,s),this.assignValues(),window.addEventListener("resize",t.debounce(function(){return o.assignValues()})),this.debouncedHashFn=t.debounce(function(){if(history.replaceState)history.replaceState(null,null,"#"+o.lastId);else{var e=t.scrollTop();window.location.hash=o.lastId,window.scrollTo(0,e)}},this.options.hashTimeout),this.cacheItems(),this.scrollFn()}};return e.prototype.assignValues=function(){this.currScrollTop=0,this.lastId="",this.menuHeight=this.element.offsetHeight+this.options.threshold,this.menuItems=[].concat(this.element.querySelectorAll(this.options.menuItemSelector))},e.prototype.cacheItems=function(){this.scrollItems=this.menuItems.map(function(e){var s=document.querySelector(e.getAttribute("href")),o=t.offset(s).top;return{elm:s,offset:o}})},e.prototype.tick=function(){var t=this.currScrollTop+this.menuHeight,e=this.scrollItems.filter(function(e){return e.offset + + + + MenuSpy + + + + +
+ +
+ +
+
+
+

Home

+
+
+ +
+
+

Section 1

+
+
+ +
+
+

Section 2

+
+
+ +
+
+

Section 3

+
+
+
+ + + + + + + \ No newline at end of file diff --git a/examples/lavalamp.html b/examples/lavalamp.html new file mode 100644 index 0000000..a01ae66 --- /dev/null +++ b/examples/lavalamp.html @@ -0,0 +1,69 @@ + + + + + MenuSpy + + + + +
+ +
+ +
+
+
+

Home

+
+
+ +
+
+

Section 1

+
+
+ +
+
+

Section 2

+
+
+ +
+
+

Section 3

+
+
+
+ + + + + + + \ No newline at end of file diff --git a/examples/sidemenu.html b/examples/sidemenu.html new file mode 100644 index 0000000..0593c18 --- /dev/null +++ b/examples/sidemenu.html @@ -0,0 +1,59 @@ + + + + + MenuSpy + + + + +
+ +
+ +
+
+
+

Home

+
+
+ +
+
+

Section 1

+
+
+ +
+
+

Section 2

+
+
+ +
+
+

Section 3

+
+
+
+ + + + + + + \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..8051170 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,69 @@ +// Karma configuration + +module.exports = function(config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '', + + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine'], + + + // list of files / patterns to load in the browser + files: [ + 'dist/menuspy.js', + 'test/**/*.spec.js' + ], + + + // list of files to exclude + exclude: [ + ], + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + }, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress'], + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['PhantomJS'], + + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity + }) +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1b13c88 --- /dev/null +++ b/package.json @@ -0,0 +1,48 @@ +{ + "name": "menuspy", + "version": "1.0.0", + "title": "MenuSpy", + "author": { + "email": "leocs.1991@gmail.com", + "name": "Leonardo Santos" + }, + "license": "MIT", + "description": "A JavaScript library to make navigation menus highlight active item based on the scroll position.", + "main": "dist/menuspy.js", + "keywords": [ + "nav", + "navigation", + "menu", + "menuspy", + "scrollspy" + ], + "docs": "http://leocs.me/menuspy/", + "demo": "http://leocs.me/menuspy/demo", + "bugs": "https://github.com/lcdsantos/menuspy/issues", + "homepage": "http://leocs.me/menuspy/", + "repository": { + "type": "git", + "url": "https://github.com/lcdsantos/menuspy.git" + }, + "devDependencies": { + "eslint": "^3.4.0", + "jasmine-core": "^2.5.0", + "karma": "^1.2.0", + "karma-chrome-launcher": "^2.0.0", + "karma-jasmine": "^1.0.2", + "karma-phantomjs-launcher": "^1.0.2", + "rollup-plugin-buble": "^0.13.0", + "rollup-plugin-eslint": "^2.0.2", + "rollup-watch": "^2.5.0", + "uglify-js": "^2.7.3" + }, + "scripts": { + "start": "npm run dev", + "dev": "npm run build:rollup -- --watch", + "build": "npm run build:rollup && npm run build:minify", + "build:rollup": "rollup -c", + "build:minify": "uglifyjs dist/menuspy.js --comments /^/*!/ --compress --mangle --output dist/menuspy.min.js", + "lint": "eslint \"src/**\"", + "test": "karma start" + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..e5d6150 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,17 @@ +import buble from 'rollup-plugin-buble'; +import eslint from 'rollup-plugin-eslint'; + +const pkg = require('./package.json'); +const banner = `/*! ${pkg.title} v${pkg.version} (${new Date().toString().substr(4, 11)}) - ${pkg.homepage} - Copyright (c) ${new Date().getFullYear()} Leonardo Santos; MIT License */`; + +export default { + entry: 'src/menuspy.js', + dest: 'dist/menuspy.js', + format: 'umd', + moduleName: 'MenuSpy', + plugins: [ + eslint(), + buble() + ], + banner: banner +}; \ No newline at end of file diff --git a/src/menuspy.js b/src/menuspy.js new file mode 100644 index 0000000..5d8a724 --- /dev/null +++ b/src/menuspy.js @@ -0,0 +1,98 @@ +import utils from './utils.js'; + +class MenuSpy { + constructor(element, options) { + if (!element) { + return; + } + + const defaults = { + menuItemSelector: 'a[href^="#"]', + activeClass : 'active', + threshold : 15, + hashTimeout : 600, + callback : null + }; + + this.element = element; + this.options = utils.extend(defaults, options); + + this.assignValues(); + window.addEventListener('resize', utils.debounce(() => this.assignValues())); + + this.debouncedHashFn = utils.debounce(() => { + if (history.replaceState) { + history.replaceState(null, null, `#${this.lastId}`); + } else { + const st = utils.scrollTop(); + window.location.hash = this.lastId; + window.scrollTo(0, st); + } + }, this.options.hashTimeout); + + this.cacheItems(); + this.scrollFn(); + } + + assignValues() { + this.currScrollTop = 0; + this.lastId = ''; + this.menuHeight = this.element.offsetHeight + this.options.threshold; + this.menuItems = [].slice.call(this.element.querySelectorAll(this.options.menuItemSelector)); + } + + cacheItems() { + this.scrollItems = this.menuItems.map((a) => { + const elm = document.querySelector(a.getAttribute('href')); + const offset = utils.offset(elm).top; + + return { elm, offset }; + }); + } + + tick() { + const fromTop = this.currScrollTop + this.menuHeight; + const inViewElms = this.scrollItems + .filter((item) => item.offset < fromTop) + .map((item) => item.elm); + + this.activateItem(inViewElms.pop()); + } + + activateItem(inViewElm) { + const id = inViewElm ? inViewElm.id : ''; + const activeClass = this.options.activeClass; + const callback = this.options.callback; + + if (this.lastId !== id) { + this.lastId = id; + + this.menuItems.forEach((item) => { + utils.removeClass(item.parentNode, activeClass); + + if (item.getAttribute('href') === `#${id}`) { + utils.addClass(item.parentNode, activeClass); + + if (typeof callback === 'function') { + callback.call(this, item, inViewElm); + } + + this.debouncedHashFn(); + } + }); + } + } + + scrollFn() { + const st = utils.scrollTop(); + + if (this.currScrollTop !== st) { + this.currScrollTop = st; + this.tick(); + } + + window.requestAnimationFrame(this.scrollFn.bind(this)); + } +} + +export default MenuSpy; \ No newline at end of file diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..692ac8a --- /dev/null +++ b/src/utils.js @@ -0,0 +1,63 @@ +const utils = { + extend(a, b) { + for (const key in b) { + if (b.hasOwnProperty(key)) { + a[key] = b[key]; + } + } + + return a; + }, + + offset(el) { + const rect = el.getBoundingClientRect(); + + return { + top: rect.top + document.body.scrollTop, + left: rect.left + document.body.scrollLeft + }; + }, + + scrollTop() { + return window.pageYOffset || document.documentElement.scrollTop; + }, + + addClass(el, className) { + if (el.classList) { + el.classList.add(className); + } else { + const classes = el.className.split(' '); + const existingIndex = classes.indexOf(className); + + if (existingIndex === -1) { + classes.push(className); + } + + el.className = classes.join(' '); + } + }, + + removeClass(el, className) { + if (el.classList) { + el.classList.remove(className); + } else { + el.className = el.className.replace(new RegExp(`(^|\\b)${className.split(' ').join('|')}(\\b|$)`, 'gi'), ' '); + } + }, + + debounce(fn, delay) { + let timeout = null; + return function() { + const args = arguments; + const context = this; + if (!timeout) { + timeout = setTimeout(() => { + timeout = 0; + return fn.apply(context, args); + }, delay); + } + }; + } +}; + +export default utils; \ No newline at end of file diff --git a/test/basic.spec.js b/test/basic.spec.js new file mode 100644 index 0000000..3951497 --- /dev/null +++ b/test/basic.spec.js @@ -0,0 +1,5 @@ +describe('Basic suite', function() { + it('should be defined', function() { + expect(MenuSpy).toBeDefined(); + }); +}); \ No newline at end of file