Repository: barbajs/barba Branch: main Commit: a7c99a5557be Files: 210 Total size: 353.7 KB Directory structure: gitextract_lsxzgmju/ ├── .all-contributorsrc ├── .circleci/ │ └── config.yml ├── .editorconfig ├── .eslintrc.js ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.yml │ │ └── config.yml │ └── stale.yml ├── .gitignore ├── .lintstagedrc ├── .markdownlint.json ├── .vscode/ │ └── settings.json ├── AUTHORS ├── CI.md ├── LICENSE.md ├── NOTES.md ├── README.md ├── TODO.md ├── commitlint.config.js ├── cypress/ │ ├── fixtures/ │ │ └── example.json │ ├── plugins/ │ │ └── index.js │ └── support/ │ ├── commands.js │ └── e2e.js ├── cypress.config.ts ├── documentation/ │ └── theme/ │ ├── assets/ │ │ └── css/ │ │ └── override.css │ └── layouts/ │ └── default.hbs ├── jest.config.js ├── lerna.json ├── package.json ├── packages/ │ ├── core/ │ │ ├── .npmignore │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── __e2e__/ │ │ │ ├── container.spec.js │ │ │ ├── default.spec.js │ │ │ ├── hooks.spec.js │ │ │ ├── href.spec.js │ │ │ └── views.spec.js │ │ ├── __mocks__/ │ │ │ ├── barba.ts │ │ │ └── transitions.ts │ │ ├── __tests__/ │ │ │ ├── core/ │ │ │ │ ├── core.click.test.ts │ │ │ │ ├── core.enter.test.ts │ │ │ │ ├── core.force.test.ts │ │ │ │ ├── core.go.test.ts │ │ │ │ ├── core.init.test.ts │ │ │ │ ├── core.logger.test.ts │ │ │ │ ├── core.once.test.ts │ │ │ │ ├── core.page.test.ts │ │ │ │ ├── core.plugin.test.ts │ │ │ │ ├── core.popstate.test.ts │ │ │ │ ├── core.prefetch.test.ts │ │ │ │ ├── core.requestError.test.ts │ │ │ │ └── core.test.ts │ │ │ ├── hooks/ │ │ │ │ └── hooks.test.ts │ │ │ ├── modules/ │ │ │ │ ├── cache.test.ts │ │ │ │ ├── error.test.ts │ │ │ │ ├── headers.test.ts │ │ │ │ ├── ignore.test.ts │ │ │ │ ├── logger.test.ts │ │ │ │ ├── prevent/ │ │ │ │ │ ├── prevent.blank.test.ts │ │ │ │ │ ├── prevent.corsDomain.test.ts │ │ │ │ │ ├── prevent.corsPort.test.ts │ │ │ │ │ ├── prevent.download.test.ts │ │ │ │ │ ├── prevent.exists.test.ts │ │ │ │ │ ├── prevent.newTab.test.ts │ │ │ │ │ ├── prevent.preventAll.test.ts │ │ │ │ │ ├── prevent.preventSelf.test.ts │ │ │ │ │ ├── prevent.sameUrl.test.ts │ │ │ │ │ └── prevent.test.ts │ │ │ │ ├── store/ │ │ │ │ │ ├── store.add.test.ts │ │ │ │ │ ├── store.addPriority.test.ts │ │ │ │ │ ├── store.check.test.ts │ │ │ │ │ ├── store.log.test.ts │ │ │ │ │ ├── store.resolve.once.test.ts │ │ │ │ │ ├── store.resolve.page.test.ts │ │ │ │ │ ├── store.test.ts │ │ │ │ │ ├── store.update.sort.test.ts │ │ │ │ │ └── store.update.test.ts │ │ │ │ ├── transitions/ │ │ │ │ │ ├── transitions.async.test.ts │ │ │ │ │ ├── transitions.context.test.ts │ │ │ │ │ ├── transitions.get.test.ts │ │ │ │ │ ├── transitions.once.test.ts │ │ │ │ │ ├── transitions.page.test.ts │ │ │ │ │ └── transitions.test.ts │ │ │ │ └── views.test.ts │ │ │ └── utils/ │ │ │ ├── dom.test.ts │ │ │ ├── history.test.ts │ │ │ ├── request.test.ts │ │ │ └── url.test.ts │ │ ├── __web__/ │ │ │ ├── container.html │ │ │ ├── href.html │ │ │ ├── index.html │ │ │ ├── page.html │ │ │ ├── scripts/ │ │ │ │ ├── container.js │ │ │ │ ├── default.js │ │ │ │ ├── href.js │ │ │ │ ├── transitions/ │ │ │ │ │ └── hooks.js │ │ │ │ ├── views/ │ │ │ │ │ └── hooks.js │ │ │ │ └── views.js │ │ │ └── views.html │ │ ├── jest.config.js │ │ ├── package.json │ │ └── src/ │ │ ├── core.ts │ │ ├── defs/ │ │ │ ├── barba.ts │ │ │ ├── cache.ts │ │ │ ├── dom.ts │ │ │ ├── global.ts │ │ │ ├── headers.ts │ │ │ ├── history.ts │ │ │ ├── hooks.ts │ │ │ ├── ignore.ts │ │ │ ├── index.ts │ │ │ ├── is-promise.d.ts │ │ │ ├── prevent.ts │ │ │ ├── request.ts │ │ │ ├── rules.ts │ │ │ ├── schemas.ts │ │ │ ├── transition.ts │ │ │ ├── url.ts │ │ │ └── view.ts │ │ ├── hooks.ts │ │ ├── index.ts │ │ ├── modules/ │ │ │ ├── Cache.ts │ │ │ ├── Error.ts │ │ │ ├── Headers.ts │ │ │ ├── Ignore.ts │ │ │ ├── Logger.ts │ │ │ ├── Prevent.ts │ │ │ ├── Store.ts │ │ │ ├── Transitions.ts │ │ │ └── Views.ts │ │ ├── polyfills/ │ │ │ └── index.ts │ │ ├── schemas/ │ │ │ ├── attribute.ts │ │ │ └── page.ts │ │ ├── typings.ts │ │ └── utils/ │ │ ├── dom.ts │ │ ├── helpers.ts │ │ ├── history.ts │ │ ├── index.ts │ │ ├── request.ts │ │ ├── run-async.ts │ │ └── url.ts │ ├── css/ │ │ ├── .npmignore │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── __e2e__/ │ │ │ ├── default.spec.js │ │ │ ├── named.spec.js │ │ │ └── once.spec.js │ │ ├── __tests__/ │ │ │ ├── css.classes.test.ts │ │ │ ├── css.hooks.test.ts │ │ │ ├── css.init.test.ts │ │ │ ├── css.prefix.test.ts │ │ │ └── css.states.test.ts │ │ ├── __web__/ │ │ │ ├── index.html │ │ │ ├── named.html │ │ │ ├── once.html │ │ │ ├── page.html │ │ │ ├── scripts/ │ │ │ │ ├── default.js │ │ │ │ └── named.js │ │ │ └── styles/ │ │ │ ├── default.css │ │ │ ├── named.css │ │ │ └── once.css │ │ ├── jest.config.js │ │ ├── package.json │ │ └── src/ │ │ ├── css.ts │ │ ├── defs/ │ │ │ └── index.ts │ │ ├── index.ts │ │ └── typings.ts │ ├── prefetch/ │ │ ├── .npmignore │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── __e2e__/ │ │ │ └── prefetch.spec.js │ │ ├── __tests__/ │ │ │ └── prefetch.init.test.ts │ │ ├── __web__/ │ │ │ └── index.html │ │ ├── jest.config.js │ │ ├── package.json │ │ └── src/ │ │ ├── defs/ │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── polyfills/ │ │ │ ├── index.ts │ │ │ └── requestIdleCallback.ts │ │ ├── prefetch.ts │ │ └── typings.ts │ └── router/ │ ├── .npmignore │ ├── AUTHORS │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── __e2e__/ │ │ └── default.spec.js │ ├── __tests__/ │ │ └── router.test.ts │ ├── __web__/ │ │ ├── default.html │ │ ├── index.html │ │ ├── page.html │ │ └── scripts/ │ │ └── default.js │ ├── jest.config.js │ ├── package.json │ └── src/ │ ├── defs/ │ │ └── index.ts │ ├── index.ts │ ├── router.ts │ └── typings.ts ├── prettier.config.js ├── tsconfig.json ├── tslint.json └── typedoc.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .all-contributorsrc ================================================ { "projectName": "barba", "projectOwner": "barbajs", "repoType": "github", "repoHost": "https://github.com", "files": ["README.md"], "imageSize": 100, "commit": true, "commitTemplate": "docs(root): :busts_in_silhouette: <%= (newContributor ? 'add' : 'update') %> @<%= username %> as a contributor", "contributorsPerLine": 7, "contributors": [ { "login": "luruke", "name": "Luigi De Rosa", "avatar_url": "https://avatars0.githubusercontent.com/u/61326?v=4", "profile": "http://luruke.com", "contributions": [ "ideas", "code", "doc", "question", "bug", "test", "review", "infra" ] }, { "login": "thierrymichel", "name": "Thierry Michel", "avatar_url": "https://avatars2.githubusercontent.com/u/806883?v=4", "profile": "http://thierrymichel.net", "contributions": [ "ideas", "code", "doc", "question", "bug", "test", "review", "infra" ] }, { "login": "xavierfoucrier", "name": "Xavier Foucrier", "avatar_url": "https://avatars1.githubusercontent.com/u/2471223?v=4", "profile": "https://xavierfoucrier.dev", "contributions": [ "ideas", "code", "doc", "question", "test", "review", "bug", "infra" ] }, { "login": "markog85", "name": "Marco Grimaldi", "avatar_url": "https://avatars2.githubusercontent.com/u/858150?v=4", "profile": "http://www.thenerodesign.com", "contributions": ["design"] }, { "login": "Ihatetomatoes", "name": "Petr TIchy", "avatar_url": "https://avatars1.githubusercontent.com/u/735672?v=4", "profile": "https://ihatetomatoes.net", "contributions": ["blog", "tutorial", "video"] }, { "login": "c0mrx", "name": "Cody Marcoux", "avatar_url": "https://avatars0.githubusercontent.com/u/22644154?v=4", "profile": "https://studio123.ca", "contributions": ["question"] }, { "login": "wiseoldman", "name": "Phil.", "avatar_url": "https://avatars1.githubusercontent.com/u/3285136?v=4", "profile": "https://philiphussak.com", "contributions": ["question"] }, { "login": "gfnool", "name": "Giorgio Finulli", "avatar_url": "https://avatars0.githubusercontent.com/u/5812801?v=4", "profile": "http://www.fnool.com", "contributions": ["question"] }, { "login": "Wouter125", "name": "Wouter", "avatar_url": "https://avatars2.githubusercontent.com/u/6507123?v=4", "profile": "https://www.thisisnota.studio", "contributions": ["bug", "question"] }, { "login": "mikehwagz", "name": "Mike Wagz", "avatar_url": "https://avatars2.githubusercontent.com/u/12376535?v=4", "profile": "https://selfaware.studio", "contributions": ["ideas", "question", "test"] }, { "login": "theredstapler", "name": "Red Stapler", "avatar_url": "https://avatars0.githubusercontent.com/u/16864380?v=4", "profile": "https://www.youtube.com/c/redstapler_channel", "contributions": ["tutorial", "video"] }, { "login": "19h47", "name": "Jérémy Levron", "avatar_url": "https://avatars1.githubusercontent.com/u/11242861?v=4", "profile": "http://www.19h47.fr", "contributions": ["question"] }, { "login": "anhskohbo", "name": "Nguyen Van Anh", "avatar_url": "https://avatars2.githubusercontent.com/u/1529454?v=4", "profile": "http://anhskohbo.github.io/", "contributions": ["code"] }, { "login": "dlwebdev", "name": "Daniel Weber", "avatar_url": "https://avatars1.githubusercontent.com/u/668910?v=4", "profile": "http://www.thedanielweber.com", "contributions": ["code"] }, { "login": "jmporchet", "name": "Jean-Marie Porchet", "avatar_url": "https://avatars3.githubusercontent.com/u/3099008?v=4", "profile": "http://www.jmporchet.ch", "contributions": ["code"] }, { "login": "docherty", "name": "James", "avatar_url": "https://avatars1.githubusercontent.com/u/325490?v=4", "profile": "https://www.jamesdocherty.com/", "contributions": ["code"] }, { "login": "nicholasruggeri", "name": "Nicholas", "avatar_url": "https://avatars0.githubusercontent.com/u/999162?v=4", "profile": "http://ruggeri.io", "contributions": ["code"] }, { "login": "patrick91", "name": "Patrick Arminio", "avatar_url": "https://avatars1.githubusercontent.com/u/667029?v=4", "profile": "http://patrick.wtf", "contributions": ["code"] }, { "login": "angelogulina", "name": "A (from Sicily)", "avatar_url": "https://avatars0.githubusercontent.com/u/4223655?v=4", "profile": "https://angelogulina.it", "contributions": ["code"] }, { "login": "pavel-mazhuga", "name": "Pavel Mazhuga", "avatar_url": "https://avatars3.githubusercontent.com/u/29140681?v=4", "profile": "https://github.com/pavel-mazhuga", "contributions": ["question"] }, { "login": "DMDc0de", "name": "Daniele De Matteo", "avatar_url": "https://avatars0.githubusercontent.com/u/7113516?v=4", "profile": "http://dmdcode.it", "contributions": ["question"] }, { "login": "aswinkumar863", "name": "aswinkumar863", "avatar_url": "https://avatars0.githubusercontent.com/u/32381261?v=4", "profile": "https://github.com/aswinkumar863", "contributions": ["question"] }, { "login": "BounceIncHQ", "name": "BounceIncHQ", "avatar_url": "https://avatars0.githubusercontent.com/u/39249876?v=4", "profile": "https://github.com/BounceIncHQ", "contributions": ["question"] }, { "login": "gordonwes", "name": "gordonwes", "avatar_url": "https://avatars3.githubusercontent.com/u/10758596?v=4", "profile": "https://github.com/gordonwes", "contributions": ["question"] }, { "login": "evfleet", "name": "Evan Fleet", "avatar_url": "https://avatars2.githubusercontent.com/u/7504632?v=4", "profile": "https://github.com/evfleet", "contributions": ["question", "bug"] }, { "login": "jd4Aligator", "name": "Jörg", "avatar_url": "https://avatars2.githubusercontent.com/u/32126746?v=4", "profile": "http://www.aligator-kom.de", "contributions": ["example"] }, { "login": "StudioZAAK", "name": "ZAAK", "avatar_url": "https://avatars3.githubusercontent.com/u/12050808?v=4", "profile": "http://www.zaak.ch", "contributions": ["example", "question"] }, { "login": "leapincorp", "name": "Masahiro Tonomura", "avatar_url": "https://avatars1.githubusercontent.com/u/42055102?v=4", "profile": "https://leap-in.com", "contributions": ["example"] }, { "login": "CassiusHR", "name": "CassiusHR", "avatar_url": "https://avatars1.githubusercontent.com/u/24419585?v=4", "profile": "https://github.com/CassiusHR", "contributions": ["question"] }, { "login": "shanewmurphy", "name": "Shane Murphy", "avatar_url": "https://avatars2.githubusercontent.com/u/3694619?v=4", "profile": "http://www.shanemurphy.me", "contributions": ["question"] }, { "login": "watzing", "name": "Dylan Reeves", "avatar_url": "https://avatars3.githubusercontent.com/u/1294637?v=4", "profile": "http://www.dylanreeves.com", "contributions": ["question", "example"] }, { "login": "quentinneyraud", "name": "Quentin Neyraud", "avatar_url": "https://avatars2.githubusercontent.com/u/9378568?v=4", "profile": "http://www.quentinneyraud.fr", "contributions": ["question"] }, { "login": "tortilaman", "name": "tortilaman", "avatar_url": "https://avatars2.githubusercontent.com/u/5018268?v=4", "profile": "https://github.com/tortilaman", "contributions": ["question"] }, { "login": "psntr", "name": "psntr", "avatar_url": "https://avatars2.githubusercontent.com/u/20617539?v=4", "profile": "https://github.com/psntr", "contributions": ["question"] }, { "login": "thisbailiwick", "name": "Kevin Clark", "avatar_url": "https://avatars3.githubusercontent.com/u/12637253?v=4", "profile": "http://thisbailiwick.com", "contributions": ["question"] }, { "login": "Tedowski", "name": "Tadeas Kosek", "avatar_url": "https://avatars2.githubusercontent.com/u/26543624?v=4", "profile": "http://takodesign.one", "contributions": ["question"] }, { "login": "gustavo-a", "name": "Gustavo de Andrade", "avatar_url": "https://avatars2.githubusercontent.com/u/26806307?v=4", "profile": "https://github.com/gustavo-a", "contributions": ["question"] }, { "login": "crobbinsdg", "name": "Clinton", "avatar_url": "https://avatars0.githubusercontent.com/u/25391588?v=4", "profile": "https://durkangroup.com/", "contributions": ["question"] }, { "login": "magicspon", "name": "Dave Stockley", "avatar_url": "https://avatars3.githubusercontent.com/u/3268717?v=4", "profile": "https://www.spon.io", "contributions": ["question"] }, { "login": "khaiknievel", "name": "khaiknievel", "avatar_url": "https://avatars1.githubusercontent.com/u/5792500?v=4", "profile": "http://khaiknievel.carbonmade.com", "contributions": ["question", "bug"] }, { "login": "kekkorider", "name": "Francesco Michelini", "avatar_url": "https://avatars3.githubusercontent.com/u/5191941?v=4", "profile": "http://www.francescomichelini.com/", "contributions": ["question", "example"] }, { "login": "FistMeNaruto", "name": "Domantas Petrauskas", "avatar_url": "https://avatars1.githubusercontent.com/u/13431677?v=4", "profile": "https://github.com/FistMeNaruto", "contributions": ["question"] }, { "login": "kjbrum", "name": "Kyle Brumm", "avatar_url": "https://avatars3.githubusercontent.com/u/1709677?v=4", "profile": "http://kylebrumm.com", "contributions": ["question"] }, { "login": "obelmont", "name": "Oliver Belmont", "avatar_url": "https://avatars3.githubusercontent.com/u/6540497?v=4", "profile": "https://github.com/obelmont", "contributions": ["question"] }, { "login": "lunelson", "name": "Lu Nelson", "avatar_url": "https://avatars1.githubusercontent.com/u/1242864?v=4", "profile": "https://lunelson.xyz/", "contributions": ["question"] }, { "login": "bramcordie", "name": "Bram Cordie", "avatar_url": "https://avatars1.githubusercontent.com/u/1107185?v=4", "profile": "http://bierdb.be", "contributions": ["question", "ideas"] }, { "login": "metalmini", "name": "Michael Schouman", "avatar_url": "https://avatars1.githubusercontent.com/u/510652?v=4", "profile": "http://portfolio.schouman.info", "contributions": ["question"] }, { "login": "JumpLink", "name": "Pascal Garber", "avatar_url": "https://avatars2.githubusercontent.com/u/1073989?v=4", "profile": "https://www.jumplink.eu", "contributions": ["question", "ideas"] }, { "login": "bfred-it", "name": "Federico Brigante", "avatar_url": "https://avatars3.githubusercontent.com/u/1402241?v=4", "profile": "https://twitter.com/bfred_it", "contributions": ["question"] }, { "login": "factorzero", "name": "Corey Lee", "avatar_url": "https://avatars1.githubusercontent.com/u/1465865?v=4", "profile": "http://coreylee.tokyo/", "contributions": ["question"] }, { "login": "mbsimonovic", "name": "Milan Simonovic", "avatar_url": "https://avatars3.githubusercontent.com/u/888008?v=4", "profile": "http://www.imls.uzh.ch/research/vonmering/people/milan-simonovic.html", "contributions": ["question"] }, { "login": "Djules", "name": "Julien Vasseur", "avatar_url": "https://avatars1.githubusercontent.com/u/196644?v=4", "profile": "http://djul.es", "contributions": ["question"] }, { "login": "panwron", "name": "Maciej Wrona", "avatar_url": "https://avatars2.githubusercontent.com/u/8494786?v=4", "profile": "https://github.com/panwron", "contributions": ["question"] }, { "login": "terion-name", "name": "Terion", "avatar_url": "https://avatars0.githubusercontent.com/u/1060205?v=4", "profile": "http://terion.name", "contributions": ["ideas"] }, { "login": "cartogram", "name": "Matt Seccafien", "avatar_url": "https://avatars2.githubusercontent.com/u/462077?v=4", "profile": "https://github.com/cartogram", "contributions": ["ideas"] }, { "login": "max-schu", "name": "Max Schulmeister", "avatar_url": "https://avatars2.githubusercontent.com/u/15388185?v=4", "profile": "http://www.maxschulmeister.com", "contributions": ["ideas"] }, { "login": "tipsy", "name": "David", "avatar_url": "https://avatars3.githubusercontent.com/u/1521451?v=4", "profile": "https://davidaase.com", "contributions": ["ideas"] }, { "login": "pierrehenri220", "name": "Pierre-Henri Lavigne", "avatar_url": "https://avatars3.githubusercontent.com/u/19267400?v=4", "profile": "https://github.com/pierrehenri220", "contributions": ["ideas"] }, { "login": "lsbyerley", "name": "lsbyerley", "avatar_url": "https://avatars0.githubusercontent.com/u/3066258?v=4", "profile": "https://github.com/lsbyerley", "contributions": ["ideas"] }, { "login": "theamnesic", "name": "Guillaume M.", "avatar_url": "https://avatars2.githubusercontent.com/u/242203?v=4", "profile": "http://gmorisseau.com/", "contributions": ["ideas"] }, { "login": "oscarotero", "name": "Oscar Otero", "avatar_url": "https://avatars3.githubusercontent.com/u/377873?v=4", "profile": "https://oscarotero.com", "contributions": ["ideas"] }, { "login": "nicooprat", "name": "Nico Prat", "avatar_url": "https://avatars0.githubusercontent.com/u/645641?v=4", "profile": "http://twitter.com/nicooprat", "contributions": ["ideas"] }, { "login": "dwightjack", "name": "Marco Solazzi", "avatar_url": "https://avatars2.githubusercontent.com/u/104721?v=4", "profile": "http://marco.solazzi.me/", "contributions": ["bug"] }, { "login": "atoupet-toki", "name": "atoupet-toki", "avatar_url": "https://avatars3.githubusercontent.com/u/38693082?v=4", "profile": "https://github.com/atoupet-toki", "contributions": ["bug"] }, { "login": "josias-r", "name": "Josias", "avatar_url": "https://avatars1.githubusercontent.com/u/11424820?v=4", "profile": "https://github.com/josias-r", "contributions": ["bug"] }, { "login": "OksanaRomaniv", "name": "Oksana Romaniv", "avatar_url": "https://avatars1.githubusercontent.com/u/5724727?v=4", "profile": "https://github.com/OksanaRomaniv", "contributions": ["bug"] }, { "login": "oguilleux", "name": "Olivier Guilleux", "avatar_url": "https://avatars3.githubusercontent.com/u/5804006?v=4", "profile": "https://www.olivier-guilleux.com", "contributions": ["bug"] }, { "login": "Liroo", "name": "Liroo Pierre ᵈᵉᵛ", "avatar_url": "https://avatars3.githubusercontent.com/u/11197281?v=4", "profile": "http://liroopierre.com", "contributions": ["code"] }, { "login": "lmartins", "name": "Luis Martins", "avatar_url": "https://avatars2.githubusercontent.com/u/151981?v=4", "profile": "https://github.com/lmartins", "contributions": ["bug"] }, { "login": "matthewjumpsoffbuildings", "name": "Matthew", "avatar_url": "https://avatars0.githubusercontent.com/u/41524?v=4", "profile": "https://arraytheband.com.au", "contributions": ["ideas", "question"] }, { "login": "Slgoetz", "name": "Simon Goetz", "avatar_url": "https://avatars0.githubusercontent.com/u/393000?v=4", "profile": "http://slgoetz.com", "contributions": ["bug"] }, { "login": "luis-pt", "name": "Luís Carvalho", "avatar_url": "https://avatars3.githubusercontent.com/u/14956453?v=4", "profile": "https://luis.pt", "contributions": ["question"] }, { "login": "mrsamse", "name": "Samuel Berisha", "avatar_url": "https://avatars2.githubusercontent.com/u/20925205?v=4", "profile": "https://github.com/mrsamse", "contributions": ["question"] }, { "login": "andersonleite", "name": "Anderson Leite", "avatar_url": "https://avatars.githubusercontent.com/u/52427?v=4", "profile": "https://github.com/andersonleite", "contributions": ["question", "bug"] }, { "login": "JayBox325", "name": "Jay Collett", "avatar_url": "https://avatars.githubusercontent.com/u/13233809?v=4", "profile": "https://jaycollett.co/", "contributions": ["question", "bug"] }, { "login": "timgates42", "name": "Tim Gates", "avatar_url": "https://avatars.githubusercontent.com/u/47873678?v=4", "profile": "https://github.com/timgates42", "contributions": ["bug"] }, { "login": "nicolas-cusan", "name": "Nicolas Cusan", "avatar_url": "https://avatars.githubusercontent.com/u/1353931?v=4", "profile": "https://www.nicolascusan.com/", "contributions": ["question", "bug", "code"] }, { "login": "geraldnako", "name": "Gerald Nako", "avatar_url": "https://avatars.githubusercontent.com/u/5095859?v=4", "profile": "https://geraldnako.com/", "contributions": ["bug", "code"] } ], "commitConvention": "angular" } ================================================ FILE: .circleci/config.yml ================================================ version: 2 defaults: &defaults working_directory: ~/repo docker: - image: cimg/node:20.4 # Not working: Job \"build\" has filters configured in the job definition. These filters are incompatible with workflows. # branches: # only: # - main jobs: build: <<: *defaults resource_class: large steps: - checkout - restore_cache: name: Restore Yarn Package Cache keys: - yarn-packages-{{ checksum "yarn.lock" }} - restore_cache: name: Restore Node modules keys: - dependency-cache-{{ checksum "package.json" }} - run: name: Install dependencies command: yarn install --frozen-lockfile - save_cache: name: Save Yarn Package Cache key: yarn-packages-{{ checksum "yarn.lock" }} paths: - ~/.cache/yarn - save_cache: key: dependency-cache-{{ checksum "package.json" }} paths: - node_modules - persist_to_workspace: root: ~/repo paths: - node_modules test: <<: *defaults resource_class: large steps: - checkout - attach_workspace: at: ~/repo - run: name: Lint command: yarn lint - run: name: Bundle command: yarn build - run: name: Test command: yarn unit:ci - run: name: Coverage command: yarn coverage - store_artifacts: path: coverage - persist_to_workspace: root: ~/repo paths: - node_modules docs-build: <<: *defaults steps: - checkout - attach_workspace: at: ~/repo - run: name: Build docs command: yarn doc - persist_to_workspace: root: ~/repo paths: [documentation] docs-deploy: <<: *defaults steps: - checkout - attach_workspace: at: ~/repo - run: name: Install and configure dependencies command: | yarn add gh-pages@2.0.1 -W git config user.email $GH_EMAIL git config user.name $GH_NAME - add_ssh_keys: fingerprints: - '2c:bd:91:1f:35:54:de:94:22:77:fc:3e:68:f9:6b:2f' - run: name: Deploy docs to gh-pages branch command: ./node_modules/.bin/gh-pages -d documentation/api -e api -m 'Deploy docs [ci skip]' workflows: version: 2 build_and_test: jobs: - build: filters: branches: only: - main - dev - test: requires: - build filters: branches: only: - main - dev - docs-build: requires: - test filters: branches: only: - main - dev - docs-deploy: requires: - docs-build filters: branches: only: main ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = space indent_size = 2 charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] indent_size = 4 trim_trailing_whitespace = false ================================================ FILE: .eslintrc.js ================================================ module.exports = { env: { browser: true, commonjs: true, es6: true, node: true, 'cypress/globals': true, }, extends: ['eslint:recommended', 'plugin:cypress/recommended'], parser: 'babel-eslint', plugins: ['cypress'], rules: { 'accessor-pairs': 2, 'array-callback-return': 2, 'array-bracket-spacing': 2, 'arrow-body-style': 2, 'arrow-parens': [2, 'as-needed'], 'arrow-spacing': 2, 'block-scoped-var': 2, 'block-spacing': 2, 'brace-style': 2, camelcase: [ 2, { properties: 'always', }, ], 'capitalized-comments': [ 2, 'always', { ignoreConsecutiveComments: true, }, ], 'class-methods-use-this': 2, 'comma-dangle': [1, 'always-multiline'], 'comma-spacing': 2, 'comma-style': 2, 'computed-property-spacing': 2, 'consistent-return': 2, 'consistent-this': 2, curly: 2, 'default-case': 2, 'dot-location': [1, 'property'], 'dot-notation': 2, 'eol-last': 2, eqeqeq: 2, 'func-call-spacing': 2, 'func-name-matching': 2, 'func-names': 2, 'global-require': 2, 'guard-for-in': 2, 'id-length': [ 2, { min: 1, max: 24, }, ], indent: [ 2, 2, { SwitchCase: 1, VariableDeclarator: { var: 2, let: 2, const: 3, }, }, ], 'key-spacing': 2, 'keyword-spacing': 2, 'linebreak-style': 2, 'lines-around-comment': [ 2, { beforeBlockComment: true, afterBlockComment: false, allowBlockStart: true, allowBlockEnd: true, allowObjectStart: true, allowObjectEnd: true, allowArrayStart: true, allowArrayEnd: true, }, ], 'lines-around-directive': 2, 'max-depth': 2, 'max-len': [ 2, { code: 120, }, ], 'max-nested-callbacks': 2, 'max-statements-per-line': [ 2, { max: 2, }, ], 'new-cap': 2, 'new-parens': 2, 'newline-before-return': 2, 'newline-per-chained-call': 2, 'no-alert': 2, 'no-array-constructor': 2, 'no-await-in-loop': 2, 'no-caller': 2, 'no-console': 0, 'no-continue': 0, 'no-div-regex': 2, 'no-duplicate-imports': 2, 'no-else-return': 2, 'no-empty-function': 2, 'no-eq-null': 2, 'no-eval': 2, 'no-extend-native': 2, 'no-extra-bind': 2, 'no-extra-label': 2, 'no-extra-parens': [ 2, 'all', { nestedBinaryExpressions: false, }, ], 'no-floating-decimal': 2, 'no-global-assign': 2, 'no-implicit-coercion': 2, 'no-implicit-globals': 2, 'no-implied-eval': 2, 'no-invalid-this': 2, 'no-iterator': 2, 'no-labels': 2, 'no-lone-blocks': 2, 'no-lonely-if': 2, 'no-loop-func': 2, 'no-magic-numbers': [ 0, { ignore: [0, 1], ignoreArrayIndexes: true, }, ], 'no-mixed-operators': 2, 'no-mixed-spaces-and-tabs': 2, 'no-multi-assign': 2, 'no-multi-spaces': 2, 'no-multi-str': 2, 'no-multiple-empty-lines': [ 2, { max: 3, }, ], 'no-negated-condition': 2, 'no-nested-ternary': 2, 'no-new-func': 2, 'no-new-object': 2, 'no-new-wrappers': 2, 'no-new': 2, 'no-octal-escape': 2, 'no-param-reassign': 2, 'no-plusplus': [ 2, { allowForLoopAfterthoughts: true, }, ], 'no-proto': 2, 'no-prototype-builtins': 2, 'no-restricted-properties': 2, 'no-return-assign': 2, 'no-return-await': 2, 'no-script-url': 2, 'no-self-compare': 2, 'no-sequences': 2, 'no-tabs': 2, 'no-template-curly-in-string': 2, 'no-throw-literal': 2, 'no-trailing-spaces': 2, 'no-unsafe-negation': 2, 'no-unmodified-loop-condition': 2, 'no-unneeded-ternary': 2, 'no-unused-expressions': [ 2, { allowShortCircuit: true, }, ], 'no-useless-call': 2, 'no-useless-computed-key': 2, 'no-useless-concat': 2, 'no-useless-constructor': 2, 'no-useless-escape': 2, 'no-useless-rename': 2, 'no-useless-return': 2, 'no-var': 2, 'no-void': 2, 'no-warning-comments': 1, 'no-whitespace-before-property': 2, 'no-with': 2, 'object-shorthand': 2, 'object-property-newline': 0, 'operator-assignment': 2, 'operator-linebreak': 2, 'padded-blocks': [2, 'never'], 'padding-line-between-statements': [ 2, { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, { blankLine: 'any', prev: ['const', 'let', 'var'], next: ['const', 'let', 'var'], }, ], 'prefer-arrow-callback': 2, 'prefer-const': 2, 'prefer-destructuring': 2, 'prefer-promise-reject-errors': 2, 'prefer-rest-params': 2, 'prefer-spread': 2, 'prefer-template': 2, 'quote-props': [2, 'consistent-as-needed'], quotes: [2, 'single'], radix: 2, 'require-await': 2, 'require-jsdoc': 2, 'rest-spread-spacing': 2, semi: 2, 'semi-spacing': 2, 'space-before-blocks': 2, 'space-in-parens': 2, 'space-infix-ops': 2, 'space-unary-ops': [ 2, { words: true, nonwords: false, }, ], 'spaced-comment': 2, strict: 2, 'symbol-description': 2, 'template-curly-spacing': 2, 'valid-jsdoc': 2, 'vars-on-top': 2, 'wrap-iife': 2, 'wrap-regex': 2, yoda: 2, }, }; ================================================ FILE: .github/CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at team@barba.dev. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing This project uses: - [TypeScript](http://www.typescriptlang.org/) - [Lerna](https://lerna.js.org/) - [Commitizen](http://commitizen.github.io/cz-cli/) - [EditorConfig](https://editorconfig.org/) - [TSLint](https://palantir.github.io/tslint/) - [Prettier](https://prettier.io/) - [markdownlint](https://github.com/DavidAnson/markdownlint) > Please, be sure to properly configure your editor… ## Install Make sure you have `yarn` and `node >= 10.16.0` - `git clone git@github.com:barbajs/barba.git` - `cd barba` - `yarn install` ## Testing Run `yarn test` For watching mode, run `yarn run watch` > Do not pay attention to the few `console.error` logs…
> In watch mode, you can select a specific package by pressing `l` > :arrow_down: > `space` > :leftwards_arrow_with_hook: ## Comitting Run `yarn run commit` or install `commitizen` globally and run `cz`. This project follows: - [Conventional Commits](https://conventionalcommits.org) specification for commit "structure" - [gitmoji](https://gitmoji.carloscuesta.me/) for commit messages ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: xavierfoucrier ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.yml ================================================ name: "🐛 Bug report" description: "Report an issue with BarbaJS" title: "..." labels: - bug body: - type: markdown attributes: value: | By opening an issue, you consider that there is a problem with the library itself, rather than your code. If you are requesting for help, please **use the Slack workspace** in order to ask the whole community. Join using the invite link, read [the developer documentation](https://barba.js.org/docs/getstarted/useful-links/#Developer). Alternatively, I can provide a custom code support for you or your company through [sponsoring/quote](https://github.com/sponsors/xavierfoucrier) depending on the amount of work, don't hesitate to reach me by mail or private message on Slack ✌️🪴. - type: input attributes: label: What version are you using? description: Please provide the version of BarbaJS your are using in your project. placeholder: e.g. v2.9.7 validations: required: true - type: dropdown attributes: label: What package are you using? description: | Please specify the package with which you are having a problem. options: - "@barba/core" - "@barba/router" - "@barba/prefetch" - "@barba/css" - "@barba/head" - "@barba/preset" validations: required: true - type: textarea attributes: label: Describe your issue description: | You can use Markdown in this field. validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ contact_links: - name: ✨ Official BarbaJS website url: https://barba.js.org/ about: New to BarbaJS? Have a look at the website before opening an issue. - name: 💬 Slack workspace url: https://join.slack.com/t/barbajs/shared_invite/enQtNTU3NTAyMjkxMzAyLTkxYWUwZmM1YWQxMmNlYmE0ZjY4NDQxMGUxYjkwYWFlMzEzOWM4OTRhMWRmYTQyYzFlMmQ3OGFmYmI3MWY0OWY about: Want to discuss or chat with the community? Join using the invite link! ================================================ FILE: .github/stale.yml ================================================ # Number of days of inactivity before an issue becomes stale daysUntilStale: 60 # Number of days of inactivity before a stale issue is closed daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - pinned - security # Label to use when marking an issue as stale staleLabel: wontfix # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: false ================================================ FILE: .gitignore ================================================ # Created by https://www.gitignore.io/api/node,macos,linux,windows,visualstudiocode ### Linux ### *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* ### macOS ### # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Node ### # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # parcel-bundler cache (https://parceljs.org/) .cache # next.js build output .next # nuxt.js build output .nuxt # vuepress build output .vuepress/dist # Serverless directories .serverless ### VisualStudioCode ### .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ### Windows ### # Windows thumbnail cache files Thumbs.db ehthumbs.db ehthumbs_vista.db # Dump file *.stackdump # Folder config file [Dd]esktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msix *.msm *.msp # Windows shortcuts *.lnk # End of https://www.gitignore.io/api/node,macos,linux,windows,visualstudiocode ### Custom ### .tmp dist report.html .rts2* # Keep microbundle's mangle.json out until it needs to be there? mangle.json /cypress/screenshots /cypress/videos /documentation/api /PERSONAL.md ================================================ FILE: .lintstagedrc ================================================ { "src/**/*.ts": ["prettier --write", "npm run lint"], "*.{json,md}": ["prettier --write"] } ================================================ FILE: .markdownlint.json ================================================ { "default": true, "fenced-code-language": false, "first-line-h1": false, "line-length": false, "no-inline-html": false, "no-trailing-punctuation": { "punctuation": ",;:" }, "ol-prefix": { "style": "ordered" }, "ul-indent": { "indent": 2 }, "ul-style": false } ================================================ FILE: .vscode/settings.json ================================================ { "editor.formatOnSave": true, "sasslint.enable": false } ================================================ FILE: AUTHORS ================================================ Luigi De Rosa (https://luruke.com/) Thierry Michel (https://www.epic.net/) Xavier Foucrier (https://xavierfoucrier.dev/) ================================================ FILE: CI.md ================================================ # CI ## @snitch ``` language: node_js node_js: - "8" after_success: 'npm run coverage' ``` ## sassy-beam `.travis.yml` ```yml language: node_js cache: directories: - ~/.npm notifications: email: false node_js: - '10' - '9' - '8' after_success: - npm run travis-deploy-once "npm run semantic-release" branches: except: - /^v\d+\.\d+\.\d+$/ ``` `package.json` ```json "release": { "prepare": [ "@semantic-release/npm", { "path": "@semantic-release/exec", "cmd": "npm run doc" }, { "path": "@semantic-release/git", "assets": [ "docs" ], "message": "docs(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" } ] }, ``` ## [Lerna FAQ](https://github.com/lerna/lerna/blob/master/FAQ.md) `circle.yml` - https://circleci.com/blog/deploying-documentation-to-github-pages-with-continuous-integration/ - https://circleci.com/blog/publishing-npm-packages-using-circleci-2-0/ - https://circleci.com/blog/continuous-package-publishing-part-ii-automated-npm-publishing-with-circleci-and-packagecloud/ ================================================ FILE: LICENSE.md ================================================ MIT License Copyright (c) 2024 Luigi De Rosa, Thierry Michel, Xavier Foucrier 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: NOTES.md ================================================ # Notes ## Lerna/npm publish - `yarn run release` - `yarn run release:next` ### Beta `publish` ```sh yarn test lerna publish --canary --preid next --dist-tag next lerna publish --github-release --force-publish=* lerna publish --github-release ``` To be tested: `--github-release` ### Test `publish` with [verdaccio](https://www.npmjs.com/package/verdaccio) ```sh npm adduser --registry http://localhost:4873 lerna publish --registry http://localhost:4873 --canary --preid next ``` ================================================ FILE: README.md ================================================ # barba.js – ![Stability](https://img.shields.io/badge/stability-stable-brightgreen.svg?style=flat-square) [![CircleCI](https://img.shields.io/circleci/project/github/barbajs/barba/main.svg?style=flat-square)](https://circleci.com/gh/barbajs/barba/tree/main) [![Coverage Status](https://img.shields.io/coveralls/github/barbajs/barba/main.svg?style=flat-square)](https://coveralls.io/github/barbajs/barba?branch=main) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=flat-square)](http://commitizen.github.io/cz-cli/) [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg?style=flat-square)](https://conventionalcommits.org) [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg?style=flat-square)](https://lerna.js.org/) [![License](https://img.shields.io/badge/license-MIT-green.svg?style=flat-square)](https://github.com/barbajs/barba/blob/main/LICENSE.md) [![All Contributors](https://img.shields.io/badge/all_contributors-73-orange.svg?style=flat-square)](#contributors) [![Slack workspace](https://img.shields.io/badge/slack-workspace-purple.svg?style=flat-square&logo=slack)](https://join.slack.com/t/barbajs/shared_invite/enQtNTU3NTAyMjkxMzAyLTkxYWUwZmM1YWQxMmNlYmE0ZjY4NDQxMGUxYjkwYWFlMzEzOWM4OTRhMWRmYTQyYzFlMmQ3OGFmYmI3MWY0OWY) Create **badass, fluid and smooth transitions** between your website’s pages. [![barbajs](https://raw.githubusercontent.com/barbajs/.github/main/profile/barbajs.svg "BarbaJS")](https://barba.js.org/) ## Intro **Barba.js** — aka *Barba* — is a small *(7kb minified and compressed)* and easy-to-use library that helps you create fluid and smooth transitions between your website's pages. It makes your website run like a **SPA** *(Single Page Application)* and help reduce the delay between your pages, minimize browser HTTP requests and enhance your user's web experience. ## Features Barba is user friendly, smart, extensible and futureproof. The library provides a bunch of **useful features that will make your website shine** like any other website, ever! * Simplified API - *written in **TypeScript** and works with `Promises`* * Cross-browser support - *[progressive enhancement](https://barba.js.org/docs/getstarted/browser-support/) for modern browsers* * DOM flexibility - *custom [markup](https://barba.js.org/docs/getstarted/markup/#DOM-structure), [namespaces](https://barba.js.org/docs/getstarted/markup/#Namespace) and `data` attribute [schema](https://barba.js.org/docs/getstarted/markup/#Schema)* * Hook system - *regulars lifecycle [methods](https://barba.js.org/docs/advanced/hooks/) for `Transitions` and `Views`* * Transition resolution - *[rules](https://barba.js.org/docs/advanced/transitions/#Rules) that let Barba pick the right transition* * Sync mode - *indicates whether leave and enter hooks should [“play together”](https://barba.js.org/docs/advanced/transitions/#Sync-mode)* * Page related code - *[custom logic](https://barba.js.org/docs/advanced/views/) attached to a specific `View`* * Modern browser strategies - *keep your site run [as fast as possible](https://barba.js.org/docs/advanced/strategies/)* * Cook like an expert - *improve your [development workflow](https://barba.js.org/docs/advanced/recipes/)* * Built-in utilities - *brought to you with a bunch of [useful methods](https://barba.js.org/docs/advanced/utils/)* * Plugin system - *supplied with [useful plugins](https://barba.js.org/docs/plugins/intro/)* * Wide community - *more than 980+ [amazing developers](https://barba.js.org/docs/getstarted/useful-links/#Developer) can help build your website!* ## Documentation Here you will find the documentation describing **how to use** the library. 1. [Website](https://barba.js.org/) - official Barba website 2. [User guide](https://barba.js.org/docs/getstarted/intro/) - how to install and use the plugin 3. [Lessons, courses and videos](https://barba.js.org/docs/getstarted/useful-links/#Learn) - for in-depth learning 4. [Showcase](https://barba.js.org/showcase/) - selected works made with Barba 5. [Developer API](https://barba.js.org/api/) - by developers, for developers > [!NOTE] > This guide assumes intermediate knowledge of HTML, CSS, and JavaScript. It is worth mentioning that all code examples use ES6+ syntax. If you are not comfortable with this syntax, we would encourage you to grasp the basics then come back. > > In case of emergency, check the ["legacy" code example](https://barba.js.org/docs/getstarted/legacy/). ## Sponsor If you like this library and want to give some recognition, it is now possible to **become a [Github sponsor](https://www.github.com/sponsors/xavierfoucrier)** and support this project by sponsoring BarbaJS maintainer on Github. Even if it's a small contribution, you participate in the effort of making **open source projects maintained for anyone**, and developers to be rewarded for their work/time. ## Contribute If you want to report a bug or request a new feature/improvement, please **read the project [contributors guidelines](.github/CONTRIBUTING.md) before**. Thanks for taking time to contribute to Barba :tada: :+1: ## Contributors
Luigi De Rosa
Luigi De Rosa

🤔 💻 📖 💬 🐛 ⚠️ 👀 🚇
Thierry Michel
Thierry Michel

🤔 💻 📖 💬 🐛 ⚠️ 👀 🚇
Xavier Foucrier
Xavier Foucrier

🤔 💻 📖 💬 ⚠️ 👀 🐛 🚇
Marco Grimaldi
Marco Grimaldi

🎨
Petr TIchy
Petr TIchy

📝 📹
Cody Marcoux
Cody Marcoux

💬
Phil.
Phil.

💬
Giorgio Finulli
Giorgio Finulli

💬
Wouter
Wouter

🐛 💬
Mike Wagz
Mike Wagz

🤔 💬 ⚠️
Red Stapler
Red Stapler

📹
Jérémy Levron
Jérémy Levron

💬
Nguyen Van Anh
Nguyen Van Anh

💻
Daniel Weber
Daniel Weber

💻
Jean-Marie Porchet
Jean-Marie Porchet

💻
James
James

💻
Nicholas
Nicholas

💻
Patrick Arminio
Patrick Arminio

💻
A (from Sicily)
A (from Sicily)

💻
Pavel Mazhuga
Pavel Mazhuga

💬
Daniele De Matteo
Daniele De Matteo

💬
aswinkumar863
aswinkumar863

💬
BounceIncHQ
BounceIncHQ

💬
gordonwes
gordonwes

💬
Evan Fleet
Evan Fleet

💬 🐛
Jörg
Jörg

💡
ZAAK
ZAAK

💡 💬
Masahiro Tonomura
Masahiro Tonomura

💡
CassiusHR
CassiusHR

💬
Shane Murphy
Shane Murphy

💬
Dylan Reeves
Dylan Reeves

💬 💡
Quentin Neyraud
Quentin Neyraud

💬
tortilaman
tortilaman

💬
psntr
psntr

💬
Kevin Clark
Kevin Clark

💬
Tadeas Kosek
Tadeas Kosek

💬
Gustavo de Andrade
Gustavo de Andrade

💬
Clinton
Clinton

💬
Dave Stockley
Dave Stockley

💬
khaiknievel
khaiknievel

💬 🐛
Francesco Michelini
Francesco Michelini

💬 💡
Domantas Petrauskas
Domantas Petrauskas

💬
Kyle Brumm
Kyle Brumm

💬
Oliver Belmont
Oliver Belmont

💬
Lu Nelson
Lu Nelson

💬
Bram Cordie
Bram Cordie

💬 🤔
Michael Schouman
Michael Schouman

💬
Pascal Garber
Pascal Garber

💬 🤔
Federico Brigante
Federico Brigante

💬
Corey Lee
Corey Lee

💬
Milan Simonovic
Milan Simonovic

💬
Julien Vasseur
Julien Vasseur

💬
Maciej Wrona
Maciej Wrona

💬
Terion
Terion

🤔
Matt Seccafien
Matt Seccafien

🤔
Max Schulmeister
Max Schulmeister

🤔
David
David

🤔
Pierre-Henri Lavigne
Pierre-Henri Lavigne

🤔
lsbyerley
lsbyerley

🤔
Guillaume M.
Guillaume M.

🤔
Oscar Otero
Oscar Otero

🤔
Nico Prat
Nico Prat

🤔
Marco Solazzi
Marco Solazzi

🐛
atoupet-toki
atoupet-toki

🐛
Josias
Josias

🐛
Oksana Romaniv
Oksana Romaniv

🐛
Olivier Guilleux
Olivier Guilleux

🐛
Liroo Pierre ᵈᵉᵛ
Liroo Pierre ᵈᵉᵛ

💻
Luis Martins
Luis Martins

🐛
Matthew
Matthew

🤔 💬
Simon Goetz
Simon Goetz

🐛
Luís Carvalho
Luís Carvalho

💬
Samuel Berisha
Samuel Berisha

💬
Anderson Leite
Anderson Leite

💬 🐛
Jay Collett
Jay Collett

💬 🐛
Tim Gates
Tim Gates

🐛
Nicolas Cusan
Nicolas Cusan

💬 🐛 💻
Gerald Nako
Gerald Nako

🐛 💻
## License The project is developed under the **MIT** license: - **Permissions**: This software and derivatives may be used for commercial purposes, you may distribute this software, this software may be modified and you may use and modify the software without distributing it. - **Conditions**: Include a copy of the license and copyright notice with the code. - **Limitations**: Software is provided without warranty and the software author/license owner cannot be held liable for damages. Read the [full license](LICENSE.md) for more information about your rights. ================================================ FILE: TODO.md ================================================ # Todos - [x][m] Router : allow leading slash ? - [x][m] Reload current page - [x][m] Prevent `xlink:href` on link enter (eg: SVG) - [x][s] No transitions ? weird behavior… - [ ][w] BS, how to fix it (definitely) - [ ][m] (npm)ignore .DS_Store files --- - [ ] @barba/ - [ ] **transitions** (basic, ready-to-use. e.g.: fade, slide, …) - [ ] **css** (add CSS classes via hooks) - [ ] **loader** ??? (programatically fetch and cache pages - also see [quicklink](https://github.com/GoogleChromeLabs/quicklink)) - [ ] Tests: - [ ] unit tests - [ ] e2e tests (puppetteer? / cypress?) - [ ] Documentation - [ ] Manual (instructions, examples, …) - [ ] Auto generated from code - [ ] Builds / releases - [ ] auto-update version(s) - [ ] auto-update changelogs - [ ] auto-update file gzip size (website and readme) - [ ] CI (tests, coverage on push, PR, …) - [ ] Force git commit to follow a certain schema - [ ] License (find correct open source license) - > Luigi said: anyone can do what they want, but: > > - they can't resell it as it is > - I have the right in any moment to not allow a specific use - NB: This seems conflicting with open source core principles Following are the most "restrictive" :) - [GNU AGPLv3](https://choosealicense.com/licenses/agpl-3.0/) - [Open Software License 3.0](https://choosealicense.com/licenses/osl-3.0/) - [European Union Public License 1.2](https://choosealicense.com/licenses/eupl-1.2/) - [ ] Community - [x] Github issue template - [x] Forum / chat -> [Slack](https://barbajs.slack.com) - [ ] Other - [ ] Make jest work with `babel.config.js` (global + optional local merge) - https://github.com/babel/babel/issues/7208 - https://github.com/facebook/jest/issues/6053#issuecomment-383632515 - https://babeljs.io/docs/en/options - https://babeljs.io/docs/en/config-files - ``` @barba/core: import barba from '../src'; @barba/core: ^^^^^ @barba/core: SyntaxError: Unexpected identifier @barba/core: at ScriptTransformer._transformAndBuildScript (../../node_modules/jest-runtime/build/script_transformer.js:403:17) @barba/core: babel.config.js:root @barba/core: .babelrc.js:core ``` - [ ] Repo transfer? - [doc](https://help.github.com/articles/transferring-a-repository/) - [hack post](https://francisco.io/blog/transferring-github-stars/) - VSCode extensions recommendations? settings? ================================================ FILE: commitlint.config.js ================================================ module.exports = { extends: ['ccgls'], }; ================================================ FILE: cypress/fixtures/example.json ================================================ { "name": "Using fixtures to represent data", "email": "hello@cypress.io", "body": "Fixtures are a great way to mock data for responses to routes" } ================================================ FILE: cypress/plugins/index.js ================================================ // *********************************************************** // This example plugins/index.js can be used to load plugins // // You can change the location of this file or turn off loading // the plugins file with the 'pluginsFile' configuration option. // // You can read more here: // https://on.cypress.io/plugins-guide // *********************************************************** // This function is called when a project is opened or re-opened (e.g. due to // the project's config changing) /* eslint-disable consistent-return, no-unused-vars */ module.exports = (on, config) => { // https://github.com/cypress-io/cypress/issues/1872#issuecomment-450807452 on('before:browser:launch', (browser = {}, args) => { if (browser.name === 'chrome') { // ^ make sure this is your browser name, you may // be using 'canary' or 'chromium' for example, so change it to match! args.push('--proxy-bypass-list=<-loopback>'); return args; } }); }; /* eslint-enable consistent-return, no-unused-vars */ ================================================ FILE: cypress/support/commands.js ================================================ // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite // existing commands. // // For more comprehensive examples of custom // commands please read more here: // https://on.cypress.io/custom-commands // *********************************************** // // // -- This is a parent command -- // Cypress.Commands.add("login", (email, password) => { ... }) // // // -- This is a child command -- // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) // // // -- This is a dual command -- // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) // // // -- This is will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) // Automatically prepend "root path" depending on package // TODO: it breaks "run all" Cypress.Commands.overwrite('visit', (originalFn, url, options) => { const r = /^packages\/([a-z]*)\/(__e2e__)\/.*\.spec.js$/; const root = Cypress.spec.relative.replace(r, '$1/__web__'); originalFn(root + url, options); }); Cypress.Commands.add('prepare', (url, title, namespace) => { // 1. Go to home cy.visit(url); // 2. Should have wrapper and container cy.get('[data-barba=wrapper]') .as('wrapper') .should('exist'); cy.get('[data-barba=container]').should('exist'); // 3. Titles have correct content cy.title().should('contain', title); cy.get('[data-test=title]') .as('h1') // Alias to @h1 .should('contain', title); // 4. Aliases current container cy.get('[data-test-container=current]').as('current'); // 4. Check namespace if provided if (namespace) { cy.get('@current').should('have.attr', 'data-barba-namespace', namespace); } }); Cypress.Commands.add('final', (url, title, namespace) => { // 0. Wrapper is the same cy.get('@wrapper').should('have.attr', 'data-test-wrapper', 'current'); // 1. URL has changed cy.url().should('include', url); // 2. H1 has changed cy.get('@h1').should('contain', title); // 3. Page title has changed cy.title().should('contain', title); // 4. Current container has been removed cy.get('@current').should('not.exist'); // 5. Next container exists cy.get('[data-test-container=next]') .as('next') .should('exist'); // 6. Check namespace if provided if (namespace) { cy.get('@next').should('have.attr', 'data-barba-namespace', namespace); } }); ================================================ FILE: cypress/support/e2e.js ================================================ // *********************************************************** // This example support/index.js is processed and // loaded automatically before your test files. // // This is a great place to put global configuration and // behavior that modifies Cypress. // // You can change the location of this file or turn off // automatically serving support files with the // 'supportFile' configuration option. // // You can read more here: // https://on.cypress.io/configuration // *********************************************************** // Import commands.js using ES2015 syntax: import './commands'; // Alternatively you can use CommonJS syntax: // require('./commands') ================================================ FILE: cypress.config.ts ================================================ import { defineConfig } from 'cypress' export default defineConfig({ fixturesFolder: './cypress/fixtures', screenshotsFolder: './cypress/screenshots', videosFolder: './cypress/videos', projectId: 'ngfcig', e2e: { // We've imported your old cypress plugins here. // You may want to clean this up later by importing these. setupNodeEvents(on, config) { return require('./cypress/plugins/index.js')(on, config) }, baseUrl: 'http://localhost:8111/packages/', specPattern: './packages/**/__e2e__/**/*.spec.js', }, }) ================================================ FILE: documentation/theme/assets/css/override.css ================================================ .tsd-signature-symbol { hyphens: auto; } /* Hack: hide layout instructions */ .tsd-panel > hr:first-child, .tsd-panel > hr + p, .tsd-panel > hr + p + hr { display: none; } blockquote { border-left: 4px solid #e0e2e5; margin-left: 0; padding-left: 20px; color: #69737d; } ================================================ FILE: documentation/theme/layouts/default.hbs ================================================ {{#ifCond model.name '==' project.name}}{{project.name}}{{else}}{{model.name}} | {{project.name}}{{/ifCond}} {{> header}}
{{{contents}}}
{{> footer}}
{{> analytics}} ================================================ FILE: jest.config.js ================================================ module.exports = { collectCoverageFrom: [ 'packages/**/src/**/*.ts', '!packages/**/src/**/*.d.ts', '!packages/**/src/typings.ts', '!packages/**/src/**/index.ts', '!packages/**/src/polyfills/**.ts', '!packages/core/src/utils/helpers.ts', ], coverageThreshold: { global: { statements: 95, branches: 95, functions: 95, lines: 95, }, }, preset: 'ts-jest', resetMocks: true, testEnvironment: 'jest-environment-jsdom-global', testMatch: ['**/__tests__/**/*.test.ts'], transformIgnorePatterns: ['.*(node_modules)(?!.*@barba.*).*$'], verbose: true, watchPlugins: ['jest-watch-lerna-packages'], }; ================================================ FILE: lerna.json ================================================ { "command": { "publish": { "conventionalCommits": true, "message": "chore(release): 🔖 publish", "npmClient": "npm" }, "version": { "allowBranch": "main" } }, "npmClient": "yarn", "packages": [ "packages/*" ], "version": "independent", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } ================================================ FILE: package.json ================================================ { "name": "root", "private": true, "devDependencies": { "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", "@commitlint/config-lerna-scopes": "^19.0.0", "@types/jest": "24.9.1", "@types/lodash": "^4.17.7", "all-contributors-cli": "^6.26.1", "babel-eslint": "^10.1.0", "commitizen": "^4.3.0", "commitlint-config-ccgls": "1.4.8", "coveralls": "^3.1.1", "cypress": "^13.13.2", "cz-ccgls": "^0.4.6", "eslint": "^8.57.0", "eslint-plugin-cypress": "^2.15.2", "gzip-size": "^7.0.0", "http-server": "14.1.1", "husky": "^9.1.4", "jest": "26.3.0", "jest-environment-jsdom-global": "^4.0.0", "jest-watch-lerna-packages": "^1.1.0", "lerna": "^8.1.7", "lint-staged": "15.2.7", "lodash": "^4.17.11", "microbundle": "0.15.1", "npm-run-all": "4.1.5", "prettier": "^3.3.3", "rimraf": "6.0.1", "source-map-explorer": "2.5.3", "start-server-and-test": "^1.15.5", "ts-jest": "26.3.0", "tslint": "^6.1.3", "typedoc": "0.15.8", "typedoc-plugin-external-module-name": "^4.0.6", "typedoc-plugin-sourcefile-url": "^1.0.6", "typescript": "^4.9.5", "typescript-tslint-plugin": "1.0.2", "wait-for-expect": "3.0.2", "xhr-mock": "^2.4.1" }, "license": "MIT", "workspaces": [ "packages/*" ], "types": "typings", "scripts": { "prepare": "husky", "build": "lerna run build", "build:watch": "lerna run build:watch --parallel", "clean": "rimraf docs && lerna clean", "clear": "lerna run clear", "commit": "npx git-cz || exit 0", "commit-retry": "npx git-cz --retry || exit 0", "coverage": "cat ./coverage/lcov.info | coveralls", "doc": "typedoc packages/*/src --tsconfig typedoc.json --sourcefile-url-prefix 'https://github.com/barbajs/barba/tree/main/'", "lint": "tslint packages/*/src/** packages/*/__tests__/**", "ls": "lerna ls", "prepublish": "yarn run build", "release": "lerna publish && npm run tag:next", "release:next": "lerna publish --dist-tag next", "report": "lerna run report", "size": "lerna run size", "tag:latest": "lerna run tag:latest --npm-client=npm", "tag:next": "lerna run tag:next --npm-client=npm", "test": "npm-run-all lint build unit e2e", "unit": "yarn jest --coverage", "unit:ci": "yarn jest --coverage --maxWorkers=2", "unit:watch": "yarn jest --watch --verbose false", "e2e": "start-server-and-test cy:server :8111 cy:run", "e2e:watch": "start-server-and-test cy:server :8111 cy:dev", "cy:server": "http-server . -p 8111", "cy:run": "cypress run --browser chrome --record --key 6f43f378-ecdc-4502-9635-b6f5c205429e", "cy:dev": "cypress open", "watch": "npm-run-all -p build:watch unit:watch" }, "husky": { "hooks": { "pre-commit": "lerna run --concurrency 1 --stream precommit", "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } }, "config": { "commitizen": { "path": "./node_modules/cz-ccgls" } } } ================================================ FILE: packages/core/.npmignore ================================================ ### Custom ### /__e2e__ /__mocks__ /__tests__ /__web__ /jest.config.js /mangle.json /*.md /.rts2* !/README.md !/CHANGELOG.md ================================================ FILE: packages/core/AUTHORS ================================================ Luigi De Rosa (https://luruke.com/) Thierry Michel (https://www.epic.net/) Xavier Foucrier (https://xavierfoucrier.dev/) ================================================ FILE: packages/core/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [2.10.3](https://github.com/barbajs/barba/compare/@barba/core@2.10.2...@barba/core@2.10.3) (2024-08-12) ### Bug Fixes - **core:** :ambulance: hotfix event propagation ([6d35aa4](https://github.com/barbajs/barba/commit/6d35aa431673e6d37161772483f891d611fef8eb)), closes [#746](https://github.com/barbajs/barba/issues/746) ## [2.10.2](https://github.com/barbajs/barba/compare/@barba/core@2.10.0...@barba/core@2.10.2) (2024-08-02) ### Bug Fixes - **core:** :bug: fix popstate when no link event is present ([680e2e7](https://github.com/barbajs/barba/commit/680e2e73c0a77186ae22112f1cefa2deef9eea04)) # [2.10.0](https://github.com/barbajs/barba/compare/@barba/core@2.9.7...@barba/core@2.10.0) (2024-05-10) ### Bug Fixes - **core:** :bug: cache first page ([a45bc26](https://github.com/barbajs/barba/commit/a45bc2692802cbb36259604e11ad8cd680e4701f)), closes [#602](https://github.com/barbajs/barba/issues/602) - **core:** :bug: fix `data-barba-history="replace"` attribute ([8e38a8f](https://github.com/barbajs/barba/commit/8e38a8f9097f68e92a1a18f42cf88edf1456d637)) - **core:** :bug: fix container replacement in the dom ([aec3143](https://github.com/barbajs/barba/commit/aec314365942c53497a05b857f7170e9bb0b0a44)), closes [#479](https://github.com/barbajs/barba/issues/479) - **core:** :bug: fix DOMParser error on SSR ([cadba05](https://github.com/barbajs/barba/commit/cadba0512eb5459e325f7af82f210a92e05e24d5)), closes [#512](https://github.com/barbajs/barba/issues/512) - **core:** :bug: fix request header name case ([167988d](https://github.com/barbajs/barba/commit/167988ddc67654675816ca39d8d28980c423d229)) - **core:** :bug: prevent `onLinkEnter` prefetch to cache relative URLs ([a7d15ac](https://github.com/barbajs/barba/commit/a7d15acb11f745107a4c11b5d783988a9eca7764)) - **core:** :bug: prevent assistive technology to read out the entire page ([4baef3e](https://github.com/barbajs/barba/commit/4baef3e8171c0e5b3dd98b8ca886e5ff811a7513)), closes [#681](https://github.com/barbajs/barba/issues/681) - **core:** :bug: prevent programmatic prefetch to cache relative URLs ([a021236](https://github.com/barbajs/barba/commit/a021236668082a14d3c3c718acbc30ae0f5e9bdb)) - **core:** :bug: properly match `self` transition ([f7bd01c](https://github.com/barbajs/barba/commit/f7bd01cfdb333c1d0afad109a7f74d01adcb78f1)) - **core:** :bug: properly prevent `self` transition ([4c97b59](https://github.com/barbajs/barba/commit/4c97b59c86bc9e5ce9309621334d44849916299c)), closes [#700](https://github.com/barbajs/barba/issues/700) - **core:** :pencil2: fix `this.barba` typo ([707c3de](https://github.com/barbajs/barba/commit/707c3de77152dad11e98cb10877a57965e561623)) - **core:** :pencil2: fix typos in tests ([a748356](https://github.com/barbajs/barba/commit/a748356daf77117a120783eee4750c16e7603ad2)) - **core:** :rotating_light: align asterisks for jsdoc ([ff837db](https://github.com/barbajs/barba/commit/ff837dbd2569d051164fd6f72cbad2919361cf84)) - **core:** :rotating_light: fix linter issues inside `core` ([5ae1432](https://github.com/barbajs/barba/commit/5ae1432d34a142154dc4a3928b7ab01a81ff232e)) - **core:** :rotating_light: fix maxlength line issue ([45df0d6](https://github.com/barbajs/barba/commit/45df0d696f69c2cfbeca6f764d1e5ae465b8d177)) - **core:** :rotating_light: sort imports alphabetically ([0ccbfda](https://github.com/barbajs/barba/commit/0ccbfdaf5c1a51c60240e4328b062ea8b63e3626)) - **core:** :rotating_light: sort variables alphabetically ([6c2c3d7](https://github.com/barbajs/barba/commit/6c2c3d70b6f63bb796a95d4778919707f63054a7)) - **core:** :white_check_mark: fix duplicate expect state ([085794b](https://github.com/barbajs/barba/commit/085794bac6fd32c2082c05c9d59c9135c0320b7f)) - **core:** :white_check_mark: fix request tests ([151abff](https://github.com/barbajs/barba/commit/151abff58dc8658ef251a96c3fb32db865322f6f)) - **core:** ✅ fix request tests ([79e207b](https://github.com/barbajs/barba/commit/79e207b4e7ff88c39a33f963d3833cfb9c2ba50b)) - **core:** 🐛 fix transition with popstate event and query string ([e27736f](https://github.com/barbajs/barba/commit/e27736fc305d0315cbf0a01f74a9209c8337a02e)), closes [#587](https://github.com/barbajs/barba/issues/587) - **core:** 🚨 align asterisks for jsdoc ([811b3ab](https://github.com/barbajs/barba/commit/811b3ab0ea67933348228792b39240df01e3fe12)) - **core:** timeout reload, go to next page instead ([2b8f59a](https://github.com/barbajs/barba/commit/2b8f59a11a1440c4dc9c89126c511fbec4f4bffd)) ### Features - **core:** :sparkles: add `cacheFirstPage` option ([4306972](https://github.com/barbajs/barba/commit/43069722744822853dbc459aea5ab5a4b8bd924f)) - **core:** :sparkles: add `CacheStatus` to reflect Promise status ([61c28c7](https://github.com/barbajs/barba/commit/61c28c75fff686d19b03da3198776be293399e1b)) - **core:** :sparkles: add `CacheTarget` type ([a4e9045](https://github.com/barbajs/barba/commit/a4e9045a7ad14cf40dae7bf3084cd8e9a9e89cd7)) - **core:** :sparkles: add `getTarget` method ([1d187de](https://github.com/barbajs/barba/commit/1d187de72e11b2360b1e1f6c9f19a38d3fd94e97)) - **core:** :sparkles: add `Headers` module ([141249d](https://github.com/barbajs/barba/commit/141249d3764e5791530e265793ec72722d67958d)) - **core:** :sparkles: add `IDomSibling` interface ([d3acdb8](https://github.com/barbajs/barba/commit/d3acdb8dec3a494b5a17643312f778921155d22e)) - **core:** :sparkles: add `IResponse` interface ([c2fa15a](https://github.com/barbajs/barba/commit/c2fa15acfe3d7c8b08659dd0da5d6b409e9609ad)) - **core:** :sparkles: add a method to retrieve absolute href from URL ([ad96797](https://github.com/barbajs/barba/commit/ad967972a41a324dd9128855225f41bae6142587)) - **core:** :sparkles: allow `CacheStatus` to be retrieved ([9970f21](https://github.com/barbajs/barba/commit/9970f216c4e629783a7f2a4f828bf8ca707fc70c)) - **core:** :sparkles: allow custom `data` when adding to history ([2eaab1c](https://github.com/barbajs/barba/commit/2eaab1cf345462692951b9b09418484823e55bbb)) - **core:** :sparkles: allow custom data in `barba.history` ([920880a](https://github.com/barbajs/barba/commit/920880a8573ee9fc1eb03fd75cce08c872be0d27)), closes [#630](https://github.com/barbajs/barba/issues/630) - **core:** :sparkles: allow programmatic `barba.history` management ([e1e4b5f](https://github.com/barbajs/barba/commit/e1e4b5f270cd9e08aa4981a89bb744a8834f41c1)), closes [#601](https://github.com/barbajs/barba/issues/601) - **core:** :sparkles: expose `getQuery` and `getHash` methods ([4456f4b](https://github.com/barbajs/barba/commit/4456f4b7d03cd9933a0ea43dd722298be82b9088)) - **core:** :sparkles: implement `getSibling` method ([4bf2802](https://github.com/barbajs/barba/commit/4bf280265f21e4e5b6a628982d4155be0a6467a9)) - **core:** :sparkles: pass the trigger event through `hooks` ([a2fef11](https://github.com/barbajs/barba/commit/a2fef11e7947667ff6386bd1e4ef680c30aaadeb)), closes [#622](https://github.com/barbajs/barba/issues/622) - **core:** :sparkles: properly manage server response ([ae94e2c](https://github.com/barbajs/barba/commit/ae94e2c929e20ef527f7c91fb2d006da7ae78a19)) - **core:** :sparkles: resolve `CacheRequest` with `IResponse` instead ([1061245](https://github.com/barbajs/barba/commit/106124553200a6f8c30fe8bdc562c12d38e56b2f)) - **core:** :sparkles: store `target` in the cache based on server response ([f321926](https://github.com/barbajs/barba/commit/f3219262bd161075d7478be39b60c4a5d3ae59b2)) - **core:** :sparkles: store response url through `IResponse` interface ([1963609](https://github.com/barbajs/barba/commit/19636096af3128d9ebbf73e7d845a593f13000e2)) - **core:** :sparkles: update cache `status` to reflect Promise state ([54edab1](https://github.com/barbajs/barba/commit/54edab19834f9e65024f18651c0634ca32f08e0b)) - **core:** ♻️ add headers type definitions ([5d2f13f](https://github.com/barbajs/barba/commit/5d2f13f098aed2a407dd664a66300c49c62362c2)) - **core:** ♻️ import `Headers` module ([1b6c418](https://github.com/barbajs/barba/commit/1b6c4181030c1e956fa43708f166f57315d6788d)) - **core:** ♻️ pass headers to the `request` utility ([60fa820](https://github.com/barbajs/barba/commit/60fa8204ca75c3a925c36d7e8f2c44a550b9c075)) - **core:** ✨ set custom request headers ([0edbffe](https://github.com/barbajs/barba/commit/0edbffea5d540ed35f1f85094d758c280a6e4d8d)) ## [2.9.7](https://github.com/barbajs/barba/compare/@barba/core@2.9.6...@barba/core@2.9.7) (2020-01-16) ### Bug Fixes - **core:** :bug: fix once only transition resolved for page ([0e52e61](https://github.com/barbajs/barba/commit/0e52e616e9964ad98f28239b1341a71cf1d29f4a)), closes [#483](https://github.com/barbajs/barba/issues/483) ## [2.9.6](https://github.com/barbajs/barba/compare/@barba/core@2.9.5...@barba/core@2.9.6) (2019-12-12) ### Bug Fixes - **core:** :bug: fix route (object) resolution ([1fb344f](https://github.com/barbajs/barba/commit/1fb344f9f07fbe58c5893b09f59d471704c0c521)) ## [2.9.5](https://github.com/barbajs/barba/compare/@barba/core@2.9.4...@barba/core@2.9.5) (2019-12-09) ### Bug Fixes - **core:** :bug: filter transition errors from request errors ([281c85f](https://github.com/barbajs/barba/commit/281c85fc7bbaf3d51ee58653468c48b2404152ea)), closes [#475](https://github.com/barbajs/barba/issues/475) ## [2.9.4](https://github.com/barbajs/barba/compare/@barba/core@2.9.3...@barba/core@2.9.4) (2019-12-09) ### Bug Fixes - **core:** :bug: fix cache management with ignore option ([d801813](https://github.com/barbajs/barba/commit/d801813976a130d41b3261e7de8f0bcdf5dcd581)), closes [#470](https://github.com/barbajs/barba/issues/470) ## [2.9.3](https://github.com/barbajs/barba/compare/@barba/core@2.9.2...@barba/core@2.9.3) (2019-12-09) ### Bug Fixes - **core:** :recycle: refactor history states ([08c83b8](https://github.com/barbajs/barba/commit/08c83b857d8ab3f7826631968afaabf76e1a2e87)), closes [#473](https://github.com/barbajs/barba/issues/473) [#472](https://github.com/barbajs/barba/issues/472) ## [2.9.2](https://github.com/barbajs/barba/compare/@barba/core@2.9.1...@barba/core@2.9.2) (2019-11-25) ### Bug Fixes - **root:** :art: improve typings for TS ([48f0637](https://github.com/barbajs/barba/commit/48f0637)) ## [2.9.1](https://github.com/barbajs/barba/compare/@barba/core@2.9.0...@barba/core@2.9.1) (2019-11-25) ### Bug Fixes - **core:** :rotating_light: fix TS errors, improve TS defs ([a48acd2](https://github.com/barbajs/barba/commit/a48acd2)), closes [#468](https://github.com/barbajs/barba/issues/468) # [2.9.0](https://github.com/barbajs/barba/compare/@barba/core@2.8.0...@barba/core@2.9.0) (2019-11-25) ### Bug Fixes - **core:** :bug: hooks: context can not be undefined ([f6bb536](https://github.com/barbajs/barba/commit/f6bb536)), closes [#468](https://github.com/barbajs/barba/issues/468) ### Features - **core:** :sparkles: add replace history via data-attribute ([272a43f](https://github.com/barbajs/barba/commit/272a43f)), closes [#460](https://github.com/barbajs/barba/issues/460) # [2.8.0](https://github.com/barbajs/barba/compare/@barba/core@2.7.2...@barba/core@2.8.0) (2019-11-06) ### Bug Fixes - **core:** :bug: compare ports for sameUrl prevent check ([e2a84e4](https://github.com/barbajs/barba/commit/e2a84e4)), closes [#463](https://github.com/barbajs/barba/issues/463) - **core:** :bug: popstate with unknown state (null) ([3369633](https://github.com/barbajs/barba/commit/3369633)), closes [#456](https://github.com/barbajs/barba/issues/456) [#466](https://github.com/barbajs/barba/issues/466) - **core:** :ok_hand: resolve once transitions ([20cafe1](https://github.com/barbajs/barba/commit/20cafe1)), closes [#439](https://github.com/barbajs/barba/issues/439) ### Features - **core:** :loud_sound: add/improve error logs ([e67a17b](https://github.com/barbajs/barba/commit/e67a17b)), closes [#447](https://github.com/barbajs/barba/issues/447) ## [2.7.2](https://github.com/barbajs/barba/compare/@barba/core@2.7.1...@barba/core@2.7.2) (2019-11-05) ### Bug Fixes - **core:** :heavy_minus_sign: remove 'path' for URL resolution ([3875d0c](https://github.com/barbajs/barba/commit/3875d0c)), closes [#465](https://github.com/barbajs/barba/issues/465) - **root:** :bug: fix context for views and add to transitions ([9054673](https://github.com/barbajs/barba/commit/9054673)), closes [#467](https://github.com/barbajs/barba/issues/467) ## [2.7.1](https://github.com/barbajs/barba/compare/@barba/core@2.7.0...@barba/core@2.7.1) (2019-10-27) **Note:** Version bump only for package @barba/core # [2.7.0](https://github.com/barbajs/barba/compare/@barba/core@2.6.1...@barba/core@2.7.0) (2019-10-27) ### Bug Fixes - **core:** :bug: fix container + dom sibling insertion ([7f349a7](https://github.com/barbajs/barba/commit/7f349a7)), closes [#449](https://github.com/barbajs/barba/issues/449) - **core:** :ok_hand: move after hooks ([42742ea](https://github.com/barbajs/barba/commit/42742ea)), closes [#455](https://github.com/barbajs/barba/issues/455) ### Features - **core:** :sparkles: add preventRunning option ([93f0b50](https://github.com/barbajs/barba/commit/93f0b50)), closes [#414](https://github.com/barbajs/barba/issues/414) ## [2.6.1](https://github.com/barbajs/barba/compare/@barba/core@2.6.0...@barba/core@2.6.1) (2019-10-22) ### Bug Fixes - **core:** :ambulance: fix URL with query/hash ([f5e639c](https://github.com/barbajs/barba/commit/f5e639c)), closes [#445](https://github.com/barbajs/barba/issues/445) - **core:** :recycle: improve url/href/path management ([159afdc](https://github.com/barbajs/barba/commit/159afdc)) # [2.6.0](https://github.com/barbajs/barba/compare/@barba/core@2.5.1...@barba/core@2.6.0) (2019-08-22) ### Features - **core:** :alembic: store scroll position in history ([0fb28e2](https://github.com/barbajs/barba/commit/0fb28e2)) ## [2.5.1](https://github.com/barbajs/barba/compare/@barba/core@2.5.0...@barba/core@2.5.1) (2019-08-22) ### Bug Fixes - **core:** :bug: keep container position ([5482154](https://github.com/barbajs/barba/commit/5482154)) # [2.5.0](https://github.com/barbajs/barba/compare/@barba/core@2.4.0...@barba/core@2.5.0) (2019-08-22) ### Features - **core:** :ok_hand: replace popstate with back/forward ([c665052](https://github.com/barbajs/barba/commit/c665052)) # [2.4.0](https://github.com/barbajs/barba/compare/@barba/core@2.3.16...@barba/core@2.4.0) (2019-08-02) ### Features - **core:** :sparkles: add history size ([3b8d7fd](https://github.com/barbajs/barba/commit/3b8d7fd)) ## [2.3.16](https://github.com/barbajs/barba/compare/@barba/core@2.3.15...@barba/core@2.3.16) (2019-08-01) ### Bug Fixes - **core:** :bug: Fix global hooks (before|afterEnter) on first load ([e948166](https://github.com/barbajs/barba/commit/e948166)), closes [#393](https://github.com/barbajs/barba/issues/393) ## [2.3.15](https://github.com/barbajs/barba/compare/@barba/core@2.3.14...@barba/core@2.3.15) (2019-07-17) ### Bug Fixes - **core:** :ok_hand: view hook on page load ([c631b54](https://github.com/barbajs/barba/commit/c631b54)), closes [#393](https://github.com/barbajs/barba/issues/393) ## [2.3.14](https://github.com/barbajs/barba/compare/@barba/core@2.3.13...@barba/core@2.3.14) (2019-07-16) ### Bug Fixes - **root:** :mute: remove print version ([be5aa73](https://github.com/barbajs/barba/commit/be5aa73)), closes [#415](https://github.com/barbajs/barba/issues/415) ## [2.3.13](https://github.com/barbajs/barba/compare/@barba/core@2.3.12...@barba/core@2.3.13) (2019-07-16) ### Bug Fixes - **core:** :bug: Clean url extend ([7667a8d](https://github.com/barbajs/barba/commit/7667a8d)) - **core:** :bug: fix link with no href attribute ([e434ecb](https://github.com/barbajs/barba/commit/e434ecb)) ## [2.3.12](https://github.com/barbajs/barba/compare/@barba/core@2.3.11...@barba/core@2.3.12) (2019-06-26) ### Bug Fixes - **core:** :ok_hand: improve support of SVG links ([19e7e5d](https://github.com/barbajs/barba/commit/19e7e5d)) ## [2.3.11](https://github.com/barbajs/barba/compare/@barba/core@2.3.10...@barba/core@2.3.11) (2019-06-25) ### Bug Fixes - **core:** :bug: make page hook async ([10e66d9](https://github.com/barbajs/barba/commit/10e66d9)) ## [2.3.10](https://github.com/barbajs/barba/compare/@barba/core@2.3.9...@barba/core@2.3.10) (2019-06-11) ### Bug Fixes - **core:** :bug: remove "force repaint" when new container is added ([5a34322](https://github.com/barbajs/barba/commit/5a34322)) - **core:** :construction: fix "glitch" when new container added ([1286bf9](https://github.com/barbajs/barba/commit/1286bf9)) ## [2.3.9](https://github.com/barbajs/barba/compare/@barba/core@2.3.8...@barba/core@2.3.9) (2019-04-29) **Note:** Version bump only for package @barba/core ## [2.3.8](https://github.com/barbajs/barba/compare/@barba/core@2.3.7...@barba/core@2.3.8) (2019-04-29) ### Bug Fixes - **core:** :bug: fix sameUrl with query params ([fa79a6a](https://github.com/barbajs/barba/commit/fa79a6a)), closes [#389](https://github.com/barbajs/barba/issues/389) ## [2.3.7](https://github.com/barbajs/barba/compare/@barba/core@2.3.6...@barba/core@2.3.7) (2019-04-29) ### Bug Fixes - **core:** :bug: fix glitch on containers add/remove ([374660c](https://github.com/barbajs/barba/commit/374660c)) ## [2.3.6](https://github.com/barbajs/barba/compare/@barba/core@2.3.5...@barba/core@2.3.6) (2019-04-29) **Note:** Version bump only for package @barba/core ## [2.3.5](https://github.com/barbajs/barba/compare/@barba/core@2.3.4...@barba/core@2.3.5) (2019-04-29) ### Bug Fixes - **core:** :construction: fix history ([36d3393](https://github.com/barbajs/barba/commit/36d3393)) ## [2.3.4](https://github.com/barbajs/barba/compare/@barba/core@2.3.3...@barba/core@2.3.4) (2019-04-29) ### Bug Fixes - **core:** :bug: fix object rule with no value ([4014fd8](https://github.com/barbajs/barba/commit/4014fd8)) ## [2.3.3](https://github.com/barbajs/barba/compare/@barba/core@2.3.2...@barba/core@2.3.3) (2019-04-23) ### Bug Fixes - **core:** :bug: update title with "next" ([f8c1940](https://github.com/barbajs/barba/commit/f8c1940)), closes [#384](https://github.com/barbajs/barba/issues/384) ## [2.3.2](https://github.com/barbajs/barba/compare/@barba/core@2.3.1...@barba/core@2.3.2) (2019-04-20) ### Bug Fixes - **core:** :bug: remove current container at the end ([f6fab91](https://github.com/barbajs/barba/commit/f6fab91)) ## [2.3.1](https://github.com/barbajs/barba/compare/@barba/core@2.3.0...@barba/core@2.3.1) (2019-04-16) ### Bug Fixes - **core:** :bug: do not cache rendered HTML ([0cc4b86](https://github.com/barbajs/barba/commit/0cc4b86)), closes [#383](https://github.com/barbajs/barba/issues/383) # [2.3.0](https://github.com/barbajs/barba/compare/@barba/core@2.2.0...@barba/core@2.3.0) (2019-04-14) ### Features - **core:** :sparkles: add programmatically prefetch ([7b95ffd](https://github.com/barbajs/barba/commit/7b95ffd)) # [2.2.0](https://github.com/barbajs/barba/compare/@barba/core@2.1.3...@barba/core@2.2.0) (2019-04-14) ### Features - **core:** :art: allow global hooks to be asynchronous ([be5dccf](https://github.com/barbajs/barba/commit/be5dccf)) ## [2.1.3](https://github.com/barbajs/barba/compare/@barba/core@2.1.2...@barba/core@2.1.3) (2019-04-13) ### Bug Fixes - **core:** :loud_sound: print version ([24dd2ea](https://github.com/barbajs/barba/commit/24dd2ea)) ### Reverts - **root:** :bug: revert failed release ([2b8a1ef](https://github.com/barbajs/barba/commit/2b8a1ef)) ## [2.1.3](https://github.com/barbajs/barba/compare/@barba/core@2.1.2...@barba/core@2.1.3) (2019-04-13) ### Bug Fixes - **core:** :loud_sound: print version ([24dd2ea](https://github.com/barbajs/barba/commit/24dd2ea)) ## [2.1.2](https://github.com/barbajs/barba/compare/@barba/core@2.1.1...@barba/core@2.1.2) (2019-04-13) ### Bug Fixes - **core:** :mute: remove debug logs ([f4ce952](https://github.com/barbajs/barba/commit/f4ce952)) ## [2.1.1](https://github.com/barbajs/barba/compare/@barba/core@2.1.0...@barba/core@2.1.1) (2019-04-13) ### Bug Fixes - **core:** :bug: fix cache not working ([a01e122](https://github.com/barbajs/barba/commit/a01e122)) - **core:** :bug: fix hook order ([716b062](https://github.com/barbajs/barba/commit/716b062)) - **core:** :bug: fix hooks order in sync mode true ([b3c92d1](https://github.com/barbajs/barba/commit/b3c92d1)) - **core:** :bug: fix popstate navigation ([f78ee11](https://github.com/barbajs/barba/commit/f78ee11)), closes [#359](https://github.com/barbajs/barba/issues/359) - **core:** :bug: fix sameUrl + anchors ([039f5d9](https://github.com/barbajs/barba/commit/039f5d9)), closes [#359](https://github.com/barbajs/barba/issues/359) - **core:** :bug: fix timeout error ([70b7805](https://github.com/barbajs/barba/commit/70b7805)), closes [#373](https://github.com/barbajs/barba/issues/373) - **core:** :bug: fix view.beforeEnter on barba ready ([5a09470](https://github.com/barbajs/barba/commit/5a09470)), closes [#360](https://github.com/barbajs/barba/issues/360) - **core:** :bug: fix wrong combo namespace / view hooks ([3c775a3](https://github.com/barbajs/barba/commit/3c775a3)), closes [#351](https://github.com/barbajs/barba/issues/351) - **core:** :checkered_flag: fix InvalidStateError on IE ([b9eece9](https://github.com/barbajs/barba/commit/b9eece9)), closes [#371](https://github.com/barbajs/barba/issues/371) - **core:** :green_apple: fix img[srcset] parsing ([8afe945](https://github.com/barbajs/barba/commit/8afe945)), closes [#362](https://github.com/barbajs/barba/issues/362) - **core:** :ok_hand: add Accept header ([94962ef](https://github.com/barbajs/barba/commit/94962ef)) - **core:** :ok_hand: fix `beforeEnter` view hook ([0d11e44](https://github.com/barbajs/barba/commit/0d11e44)) - **core:** :ok_hand: make main transition hooks public ([2c0cc28](https://github.com/barbajs/barba/commit/2c0cc28)) - **core:** :ok_hand: make onRequestError public ([61193ad](https://github.com/barbajs/barba/commit/61193ad)) # 2.1.0 (2019-03-17) ### Bug Fixes - **core:** :bug: append next container correctly on sync mode ([dc62bd3](https://github.com/barbajs/barba/commit/dc62bd3)), closes [#4](https://github.com/barbajs/barba/issues/4) - **core:** :bug: fix `xlink:href` with SVG ([19ecd81](https://github.com/barbajs/barba/commit/19ecd81)), closes [#1](https://github.com/barbajs/barba/issues/1) - **css:** :bug: fix css with next tick ([63642bf](https://github.com/barbajs/barba/commit/63642bf)) - **prefetch:** :bug: fix requestError ([33c213b](https://github.com/barbajs/barba/commit/33c213b)) - **root:** :bug: force publish ([ddb8798](https://github.com/barbajs/barba/commit/ddb8798)) - **root:** :ok_hand: replace error with warning when no transition ([661801e](https://github.com/barbajs/barba/commit/661801e)) - :bug: fix case issues ([c6adcb3](https://github.com/barbajs/barba/commit/c6adcb3)) ### Features - **core:** :sparkles: add prevent custom + update README ([2fb4ec6](https://github.com/barbajs/barba/commit/2fb4ec6)) - **css:** :recycle: add transitionend logic + big refactoring ([b775358](https://github.com/barbajs/barba/commit/b775358)) - **css:** :tada: initial commit ([aed8206](https://github.com/barbajs/barba/commit/aed8206)) - **root:** :sparkles: add logger + fixes ([6db3875](https://github.com/barbajs/barba/commit/6db3875)) - **router:** :sparkles: add multiple properties to `route` ([4e92c83](https://github.com/barbajs/barba/commit/4e92c83)) ================================================ FILE: packages/core/LICENSE ================================================ MIT License Copyright (c) 2024 Luigi De Rosa, Thierry Michel, Xavier Foucrier 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: packages/core/README.md ================================================ # @barba/core [![NPM version](https://img.shields.io/npm/v/@barba/core?style=flat-square)](https://www.npmjs.com/package/@barba/core) [![Dependencies](https://img.shields.io/librariesio/release/npm/@barba/core?style=flat-square)](https://github.com/barbajs/barba/network/dependencies) > TBD ([GitHub repo](https://github.com/barbajs/barba.js)) ## Install Using npm: ```sh npm install --save-dev @barba/core ``` or using yarn: ```sh yarn add @barba/core --dev ``` ================================================ FILE: packages/core/__e2e__/container.spec.js ================================================ /* eslint-disable cypress/no-unnecessary-waiting */ describe('Transition', () => { it('adds container at the right place', () => { cy.prepare('/container.html', 'container', 'container'); cy.get('[data-test=sibling]').as('sibling'); cy.get('[data-test=link]').click(); cy.final('/page.html', 'page', 'page'); cy.get('@wrapper') .children() .eq(1) .should('have.attr', 'data-test-container', 'next'); cy.get('@wrapper') .children() .eq(2) .should('have.attr', 'data-test', 'sibling'); }); it('adds container at the right place [sync]', () => { cy.prepare('/container.html', 'container', 'container'); cy.get('[data-test=sibling]').as('sibling'); cy.get('[data-test="link.sync"]').click(); cy.final('/page.html', 'page', 'page'); cy.get('@wrapper') .children() .eq(1) .should('have.attr', 'data-test-container', 'next'); cy.get('@wrapper') .children() .eq(2) .should('have.attr', 'data-test', 'sibling'); }); }); ================================================ FILE: packages/core/__e2e__/default.spec.js ================================================ /* eslint-disable cypress/no-unnecessary-waiting */ describe('Transition', () => { it('works', () => { cy.prepare('/index.html', 'home', 'home'); cy.wait(1000); // Wait for once complete // Click link cy.get('[data-test=link]').click(); cy.final('/page.html', 'page', 'page'); }); }); ================================================ FILE: packages/core/__e2e__/hooks.spec.js ================================================ /* eslint-disable no-mixed-operators, cypress/no-unnecessary-waiting */ const onceHooks = [ 'global:beforeEnter', // 'global:before', // 'before', 'global:beforeOnce', 'beforeOnce', 'global:once', 'once', 'global:afterOnce', 'afterOnce', // 'global:after', // 'after', 'global:afterEnter', ]; const hooksDefault = [ ...onceHooks, 'global:before', 'before', 'global:beforeLeave', 'beforeLeave', 'global:leave', 'leave', 'global:afterLeave', 'afterLeave', 'global:beforeEnter', 'beforeEnter', 'global:enter', 'enter', 'global:afterEnter', 'afterEnter', 'global:after', 'after', ]; const hooksSync = [ ...onceHooks, 'global:before', 'before', 'global:beforeLeave', 'beforeLeave', 'global:beforeEnter', 'beforeEnter', 'global:leave', 'global:enter', 'leave', 'enter', 'global:afterLeave', 'afterLeave', 'global:afterEnter', 'afterEnter', 'global:after', 'after', ]; beforeEach(() => { cy.prepare('/index.html', 'home', 'home'); cy.get('[data-test="hooks-list"]').as('hooks'); // Alias to @hooks }); afterEach(() => { cy.final('/page.html', 'page', 'page'); }); describe('Hooks', () => { it('have default order', () => { cy.wait(1000); // Wait for once complete // Click link cy.get('[data-test="link.hooks"]').click(); // Checks… hooksDefault.forEach((name, i) => { cy.get('@hooks') .find(`:nth-child(${i + 1})`) .should('contain', name); }); }); it('have sync order', () => { cy.wait(1000); // Wait for once complete // Click link cy.get('[data-test="link.hooks-sync"]').click(); // Checks… hooksSync.forEach((name, i) => { cy.get('@hooks') .find(`:nth-child(${i + 1})`) .should('contain', name); }); }); }); ================================================ FILE: packages/core/__e2e__/href.spec.js ================================================ /* eslint-disable cypress/no-unnecessary-waiting */ describe('Transition', () => { const origin = 'http://localhost:8111'; const prefix = 'packages/core/__web__'; const page = 'page.html'; const query = '?query=string'; const hash = '#hash'; it('default URL', () => { cy.prepare('/href.html', 'href', 'href'); cy.get('[data-test=link]').click(); cy.location().should(loc => { expect(loc.href).to.eq(`${origin}/${prefix}/${page}`); expect(loc.toString()).to.eq(`${origin}/${prefix}/${page}`); expect(loc.pathname).to.eq(`/${prefix}/${page}`); }); cy.final('/page.html', 'page', 'page'); }); it('query string URL', () => { cy.prepare('/href.html', 'href', 'href'); cy.get('[data-test=query]').click(); cy.location().should(loc => { expect(loc.href).to.eq(`${origin}/${prefix}/${page}${query}`); expect(loc.toString()).to.eq(`${origin}/${prefix}/${page}${query}`); expect(loc.pathname).to.eq(`/${prefix}/${page}`); expect(loc.search).to.eq(query); }); cy.final('/page.html', 'page', 'page'); }); it('hash URL', () => { cy.prepare('/href.html', 'href', 'href'); cy.get('[data-test=hash]').click(); cy.location().should(loc => { expect(loc.href).to.eq(`${origin}/${prefix}/${page}${hash}`); expect(loc.toString()).to.eq(`${origin}/${prefix}/${page}${hash}`); expect(loc.pathname).to.eq(`/${prefix}/${page}`); expect(loc.hash).to.eq(hash); }); cy.final('/page.html', 'page', 'page'); }); it('hash + query URL', () => { cy.prepare('/href.html', 'href', 'href'); cy.get('[data-test=complex]').click(); cy.location().should(loc => { expect(loc.href).to.eq(`${origin}/${prefix}/${page}${query}${hash}`); expect(loc.toString()).to.eq( `${origin}/${prefix}/${page}${query}${hash}` ); expect(loc.pathname).to.eq(`/${prefix}/${page}`); expect(loc.search).to.eq(query); expect(loc.hash).to.eq(hash); }); cy.final('/page.html', 'page', 'page'); }); }); ================================================ FILE: packages/core/__e2e__/views.spec.js ================================================ /* eslint-disable no-mixed-operators, cypress/no-unnecessary-waiting */ const loadHooks = ['beforeEnter', 'afterEnter']; const transitionHooks = [ 'beforeLeave', 'afterLeave', 'beforeEnter', 'afterEnter', ]; beforeEach(() => { cy.prepare('/views.html', 'home', 'home'); cy.get('[data-test="hooks-list"]').as('hooks'); // Alias to @hooks }); afterEach(() => { cy.final('/page.html', 'page', 'page'); }); describe('Views hooks', () => { it('have default order', () => { cy.wait(1000); // Wait for once complete // Checks… loadHooks.forEach((name, i) => { cy.get('@hooks') .find(`:nth-child(${i + 1})`) .should('contain', name); }); // Click link cy.get('[data-test="link.hooks"]').click(); // Checks… transitionHooks.forEach((name, i) => { const j = i + loadHooks.length; cy.get('@hooks') .find(`:nth-child(${j + 1})`) .should('contain', name); }); }); // it('have sync order', () => { // cy.wait(1000); // Wait for once complete // // Click link // cy.get('[data-test="link.hooks-sync"]').click(); // // Checks… // hooksSync.forEach((name, i) => { // cy.get('@hooks') // .find(`:nth-child(${i + 1})`) // .should('contain', name); // }); // }); }); ================================================ FILE: packages/core/__mocks__/barba.ts ================================================ import barba from '../src'; import { ITransitionPage } from '../src/defs'; /** * Init barba with basic DOM for testing */ export function init(transitions: ITransitionPage[] = []): any { const wrapper = document.createElement('div'); const container = document.createElement('div'); const link = document.createElement('a'); const span = document.createElement('span'); const namespace = 'current'; const mouseover = document.createEvent('HTMLEvents'); const click = document.createEvent('HTMLEvents'); wrapper.dataset.barba = 'wrapper'; container.dataset.barba = 'container'; container.dataset.barbaNamespace = namespace; document.title = 'Current page'; document.body.appendChild(wrapper); wrapper.appendChild(container); container.appendChild(link); link.appendChild(span); mouseover.initEvent('mouseover', true, false); click.initEvent('click', true, false); barba.init({ transitions }); return { wrapper, container, link, span, mouseover, click }; } ================================================ FILE: packages/core/__mocks__/transitions.ts ================================================ import { ITransitionPage } from '../src/defs'; const namespace = 'n'; const route = 'r'; const custom = () => true; const unamed: ITransitionPage[] = [ /* 0000 */ {}, // Nothing /* 0010 */ { namespace }, // Namespace /* 0011 */ { from: { namespace } }, /* 0012 */ { to: { namespace } }, /* 0013 */ { from: { namespace }, to: { namespace } }, /* 0100 */ { route }, // Route /* 0101 */ { from: { route } }, /* 0102 */ { to: { route } }, /* 0103 */ { from: { route }, to: { route } }, /* 0110 */ { route, namespace }, // Route + namespace /* 0111 */ { from: { route, namespace } }, // 10 /* 0112 */ { to: { route, namespace } }, /* 0113 */ { from: { route, namespace }, to: { route, namespace } }, /* 1000 */ { custom }, // Custom /* 1001 */ { from: { custom } }, /* 1002 */ { to: { custom } }, /* 1003 */ { from: { custom }, to: { custom } }, // 16 /* 1010 */ { custom, namespace }, // Custom + namespace /* 1011 */ { from: { custom, namespace } }, /* 1012 */ { to: { custom, namespace } }, /* 1013 */ { from: { custom, namespace }, to: { custom, namespace } }, /* 1100 */ { custom, route }, // Custom + route /* 1101 */ { from: { custom, route } }, /* 1102 */ { to: { custom, route } }, /* 1103 */ { from: { custom, route }, to: { custom, route } }, /* 1110 */ { custom, route, namespace }, // Custom + route + namespace /* 1111 */ { from: { custom, route, namespace } }, /* 1112 */ { to: { custom, route, namespace } }, /* 1113 */ { from: { custom, route, namespace }, to: { custom, route, namespace }, }, ]; const named: ITransitionPage[] = unamed.map((t, i) => { t.name = i.toString(); return t; }); export default named; ================================================ FILE: packages/core/__tests__/core/core.click.test.ts ================================================ import { init } from '../../__mocks__/barba'; import barba from '../../src'; const { link, span, click } = init(); // Mocks barba.page = jest.fn(); afterEach(() => { (global as any).jsdom.reconfigure({ url: 'http://localhost/' }); }); it('handle link click', () => { link.href = 'foo'; span.dispatchEvent(click); expect(barba.page).toHaveBeenCalledTimes(1); }); it('handle link click with same url', () => { link.href = 'http://localhost/'; span.dispatchEvent(click); expect(barba.page).toHaveBeenCalledTimes(0); }); it('handle link click with prevent', () => { link.href = 'foo'; link.dataset.barbaPrevent = ''; span.dispatchEvent(click); expect(barba.page).toHaveBeenCalledTimes(0); }); it('handle link click with transition running', () => { barba.go = jest.fn(); barba.transitions.isRunning = true; link.href = 'foo'; delete link.dataset.barbaPrevent; barba.preventRunning = false; span.dispatchEvent(click); barba.preventRunning = true; span.dispatchEvent(click); expect(barba.go).toHaveBeenCalledTimes(1); }); ================================================ FILE: packages/core/__tests__/core/core.enter.test.ts ================================================ import waitForExpect from 'wait-for-expect'; import xhrMock from 'xhr-mock'; import { init } from '../../__mocks__/barba'; import barba from '../../src'; import { IUrlFull } from '../../src/defs'; import { parse } from '../../src/utils/url'; const { link, span, mouseover } = init(); const sameUrl = 'http://localhost/'; const sameHtml = ` Current page
`; // Mocks let spyHas: jest.SpyInstance; let spySet: jest.SpyInstance; beforeEach(() => { spyHas = jest.spyOn(barba.cache, 'has'); spySet = jest.spyOn(barba.cache, 'set'); xhrMock.setup(); xhrMock.error(() => {}); // tslint:disable-line:no-empty }); afterEach(() => { link.removeAttribute('data-barba-prevent'); spyHas.mockRestore(); spySet.mockRestore(); xhrMock.teardown(); }); it('handle link enter', () => { link.href = 'foo'; span.dispatchEvent(mouseover); expect(spyHas).toHaveBeenCalledTimes(1); expect(spySet).toHaveBeenCalledTimes(1); }); it('handle link enter with same url', () => { barba.cache.set(sameUrl, Promise.resolve({ html: sameHtml, url: { href: sameUrl, ...parse(sameUrl) } as IUrlFull, }), 'init', 'pending'); spySet.mockRestore(); link.href = sameUrl; span.dispatchEvent(mouseover); expect(spyHas).toHaveBeenCalledTimes(1); expect(spySet).toHaveBeenCalledTimes(0); }); it('handle bad request', async () => { barba.logger.error = jest.fn(); xhrMock.get('http://localhost/bad', (req, res) => res.status(500)); xhrMock.error(() => {}); // tslint:disable-line:no-empty link.href = 'bad'; span.dispatchEvent(mouseover); expect(spyHas).toHaveBeenCalledTimes(1); expect(spySet).toHaveBeenCalledTimes(1); await waitForExpect(() => { expect(barba.logger.error).toHaveBeenCalledTimes(1); }); }); it('handle link enter with prevent link', () => { link.href = 'foo'; link.dataset.barbaPrevent = ''; span.dispatchEvent(mouseover); expect(spyHas).toHaveBeenCalledTimes(0); expect(spySet).toHaveBeenCalledTimes(0); }); it('handle link enter with prevent url', () => { barba.destroy(); barba.init({ prefetchIgnore: '/foo' }); spyHas = jest.spyOn(barba.cache, 'has'); spySet = jest.spyOn(barba.cache, 'set'); link.href = '/foo'; span.dispatchEvent(mouseover); expect(spyHas).toHaveBeenCalledTimes(0); expect(spySet).toHaveBeenCalledTimes(0); }); ================================================ FILE: packages/core/__tests__/core/core.force.test.ts ================================================ import barba from '../../src'; it('force url change', () => { Object.defineProperty(window, 'location', { writable: true, value: { assign: jest.fn() } }) const url = 'http://localhost/foo.html'; barba.force(url); expect(window.location.assign).toHaveBeenCalledTimes(1); }); ================================================ FILE: packages/core/__tests__/core/core.go.test.ts ================================================ /* tslint:disable:no-empty no-string-literal */ import { init } from '../../__mocks__/barba'; import barba from '../../src'; import { hooks } from '../../src/hooks'; import { schemaAttribute } from '../../src/schemas/attribute'; // Needed for "request" module (global as any).Headers = class {}; const namespace = 'next'; const nextUrl = 'http://localhost/foo'; // Mocks let spyHistory: jest.SpyInstance; beforeEach(() => { init(); // barba.init(); self.fetch = jest.fn().mockImplementation(() => ({ status: 200, text: () => Promise.resolve(`
`), })); }); afterEach(() => { barba.destroy(); spyHistory && spyHistory.mockRestore(); (global as any).jsdom.reconfigure({ url: 'http://localhost/' }); }); it('do go', async () => { barba.page = jest.fn(); await barba.go('http://localhost/foo'); expect(barba.page).toHaveBeenCalledWith( 'http://localhost/foo', 'barba', undefined, false ); }); it('force when manager running', async () => { barba.force = jest.fn(); barba.page = jest.fn(); hooks.do = jest.fn(); barba.transitions.store.add('transition', { leave() {}, enter() {} }); barba.transitions['_running'] = true; await barba.go(nextUrl, 'barba'); expect(barba.force).toHaveBeenCalledTimes(1); expect(hooks.do).not.toHaveBeenCalled(); expect(barba.page).not.toHaveBeenCalled(); barba.transitions['_running'] = false; }); it('prevent same url with no self transition', async () => { barba.page = jest.fn(); await barba.go('http://localhost/'); expect(barba.page).not.toHaveBeenCalled(); }); it('use self transition on same url [barba]', async () => { barba.page = jest.fn(); barba.transitions.store.add('transition', { name: 'self' }); await barba.go('http://localhost/'); expect(barba.page).toHaveBeenCalledWith( 'http://localhost/', 'barba', undefined, true ); }); it('use self transition on same url [popstate]', async () => { barba.page = jest.fn(); barba.transitions.store.add('transition', { name: 'self' }); const event = { state: { index: 0, }, stopPropagation() {}, preventDefault() {}, } as PopStateEvent; await barba.go('http://localhost/', 'popstate', event); expect(barba.page).toHaveBeenCalledWith( 'http://localhost/', 'popstate', event, true ); }); it('add history', async () => { spyHistory = jest.spyOn(barba.history, 'add'); await barba.go('http://localhost/foo'); expect(barba.history.add).toHaveBeenCalledWith( 'http://localhost/foo', 'barba' ); }); it('change history', async () => { spyHistory = jest.spyOn(barba.history, 'change'); await barba.prefetch('http://localhost/'); await barba.go('http://localhost/foo', 'barba'); await barba.go('http://localhost/', 'barba'); expect(barba.history.change).toHaveBeenCalledTimes(2); }); it('manage direction', async () => { barba.page = jest.fn(); const event = { state: { index: 1, }, stopPropagation() {}, preventDefault() {}, } as PopStateEvent; await barba.go('http://localhost/foo'); await barba.go('http://localhost/bar'); await barba.go('http://localhost/foo', 'popstate', event); expect(barba.page).toHaveBeenCalledWith( 'http://localhost/foo', 'back', event, false ); }); ================================================ FILE: packages/core/__tests__/core/core.init.test.ts ================================================ /* tslint:disable:no-string-literal */ import barba from '../../src'; import { PreventCheck } from '../../src/defs'; const wrapper = document.createElement('div'); const container = document.createElement('div'); const namespace = 'ns'; wrapper.dataset.barba = 'wrapper'; container.dataset.barba = 'container'; container.dataset.barbaNamespace = namespace; /** * Mock init */ function init() { barba.init(); } beforeEach(() => { document.body.appendChild(wrapper); wrapper.appendChild(container); }); afterEach(() => { document.body.innerHTML = ''; }); it('needs barba wrapper', () => { wrapper.remove(); expect(init).toThrow('No Barba wrapper found'); }); it('needs barba container', () => { container.remove(); expect(init).toThrow('No Barba container found'); }); it('needs valid prevent custom', () => { const start = () => { barba.init({ prevent: ('bad' as unknown) as PreventCheck }); }; expect(start).toThrow('Prevent should be a function'); barba.prevent.add = jest.fn(); expect(barba.prevent.add).not.toHaveBeenCalled(); }); it('adds prevent custom', () => { barba.init({ prevent: () => true }); expect(barba.prevent.tests.has('preventCustom')).toBeTruthy(); }); it('has DOM elements', () => { barba.init(); expect(barba.wrapper).toBe(wrapper); expect(barba.data.current.container).toBe(container); }); it('has current page content', () => { barba.init(); expect(barba.data.current).toBeDefined(); expect(barba.data.current.namespace).toBe(namespace); expect(barba.data.current.url).toEqual({ hash: undefined, href: 'http://localhost/', path: '/', port: 80, query: {}, }); expect(barba.data.current.container).toBe(container); expect(barba.data.current.html).toMatch(/^.+<\/html>$/); }); it('init history', () => { barba.init(); expect(barba.history.current).toEqual({ data: {}, ns: 'ns', scroll: { x: 0, y: 0, }, url: 'http://localhost/', }); }); it('calls once', () => { barba.once = jest.fn(); barba.init(); expect(barba.once).toHaveBeenCalledTimes(1); }); it('gets wrapper', () => { barba.init(); expect(barba.wrapper).toBe(wrapper); }); it('has other options', () => { wrapper.dataset.highway = 'main'; container.dataset.highway = 'container'; barba.init({ requestError() { return false; }, schema: { prefix: 'data-highway', wrapper: 'main' }, timeout: 0, transitions: [], views: [], }); const requestError = () => barba['_requestCustomError']( 'barba', 'click', 'foo.html', new Error('test') ); expect(barba.timeout).toBe(0); expect(requestError()).toBeFalsy(); expect(barba.wrapper).toBe(wrapper); }); it('cache the first rendered page', () => { barba.init({ cacheFirstPage: true, }); expect(barba.cache.has('http://localhost/')).toBeTruthy(); }); ================================================ FILE: packages/core/__tests__/core/core.logger.test.ts ================================================ import { init } from '../../__mocks__/barba'; import barba from '../../src'; import { Logger } from '../../src/modules/Logger'; init(); it('has default level log', () => { expect(Logger.getLevel()).toBe(0); }); it('has debug level log', () => { barba.init({ debug: true }); expect(Logger.getLevel()).toBe(4); }); it('has custom level log', () => { barba.init({ logLevel: 'error' }); expect(Logger.getLevel()).toBe(1); }); ================================================ FILE: packages/core/__tests__/core/core.once.test.ts ================================================ /* tslint:disable:no-empty */ import { init } from '../../__mocks__/barba'; import barba from '../../src'; import { ITransitionData } from '../../src/defs'; import { Logger } from '../../src/modules/Logger'; init(); const data: ITransitionData = { ...barba.data, trigger: 'barba', }; afterEach(() => { barba.destroy(); }); it('do once', async () => { const t = { once() {} }; const spyOnce = jest.spyOn(barba.transitions, 'doOnce'); barba.transitions.store.add('transition', t); await barba.once(data); expect(spyOnce).toHaveBeenCalledTimes(1); spyOnce.mockRestore(); }); ================================================ FILE: packages/core/__tests__/core/core.page.test.ts ================================================ /* tslint:disable:no-empty no-string-literal */ import xhrMock from 'xhr-mock'; import { init } from '../../__mocks__/barba'; import barba from '../../src'; import { IUrlFull } from '../../src/defs'; import { hooks } from '../../src/hooks'; import { Logger } from '../../src/modules/Logger'; import { schemaAttribute } from '../../src/schemas/attribute'; import { parse } from '../../src/utils/url'; // Silence is gold… :) Logger.setLevel('off'); // Needed for "request" module (global as any).Headers = class {}; const namespace = 'next'; const checkDoc = new RegExp( `^[\\s\\S]+body[\\s\\S]+${schemaAttribute.wrapper}[\\s\\S]+${schemaAttribute.container}[\\s\\S]+${namespace}[\\s\\S]+$` ); const t = { leave() {}, enter() {} }; const sameUrl = 'http://localhost/'; const nextUrl = 'http://localhost/foo'; init(); let spyCacheHas: jest.SpyInstance; let spyCacheGet: jest.SpyInstance; let spyCacheSet: jest.SpyInstance; let spyCacheUpdate: jest.SpyInstance; let spyCacheGetAction: jest.SpyInstance; let spyPage: jest.SpyInstance; const sameHtml = ` Current page
`; beforeEach(() => { barba.init(); xhrMock.setup(); xhrMock.get(sameUrl, (req, res) => res.status(200).body(sameHtml)); xhrMock.get(nextUrl, (req, res) => res.status(200).body(` New page
`) ); spyCacheHas = jest.spyOn(barba.cache, 'has'); spyCacheGet = jest.spyOn(barba.cache, 'get'); spyCacheSet = jest.spyOn(barba.cache, 'set'); spyCacheUpdate = jest.spyOn(barba.cache, 'update'); spyCacheGetAction = jest.spyOn(barba.cache, 'getAction'); spyPage = jest.spyOn(barba.transitions, 'doPage'); }); afterEach(() => { spyCacheHas.mockRestore(); spyCacheGet.mockRestore(); spyCacheSet.mockRestore(); spyCacheUpdate.mockRestore(); spyCacheGetAction.mockRestore(); spyPage.mockRestore(); xhrMock.teardown(); barba.destroy(); }); it('do page', async () => { barba.history.update = jest.fn(); hooks.do = jest.fn(); barba.transitions.store.add('transition', t); const data = { ...barba.data, trigger: 'barba', }; await barba.page(nextUrl, 'barba', undefined, false); expect(spyCacheHas).toHaveBeenCalledTimes(1); expect(spyCacheSet).toHaveBeenCalledTimes(1); expect(barba.history.update).toHaveBeenCalledTimes(1); expect(hooks.do).toHaveBeenNthCalledWith(1, 'page', data); expect(hooks.do).toHaveBeenNthCalledWith(2, 'before', data, t); expect(hooks.do).toHaveBeenNthCalledWith(3, 'beforeLeave', data, t); expect(hooks.do).toHaveBeenNthCalledWith(4, 'leave', data, t); expect(hooks.do).toHaveBeenNthCalledWith(5, 'afterLeave', data, t); expect(hooks.do).toHaveBeenNthCalledWith(6, 'nextAdded', data); expect(hooks.do).toHaveBeenNthCalledWith(7, 'beforeEnter', data, t); expect(hooks.do).toHaveBeenNthCalledWith(8, 'enter', data, t); expect(hooks.do).toHaveBeenNthCalledWith(9, 'afterEnter', data, t); expect(hooks.do).toHaveBeenNthCalledWith(10, 'currentRemoved', data); expect(hooks.do).toHaveBeenNthCalledWith(11, 'after', data, t); expect(barba.transitions.doPage).toHaveBeenCalledTimes(1); expect(document.title).toBe('New page'); }); it('do page [has cache]', async () => { barba.history.add = jest.fn(); barba.cache.set(sameUrl, Promise.resolve({ html: sameHtml, url: { href: sameUrl, ...parse(sameUrl) } as IUrlFull, }), 'init', 'pending'); spyCacheSet.mockRestore(); // NOTE: as we use "same URL" (localhost), we need a "self" transition // to avoid prevent "sameURL" barba.transitions.store.add('transition', { name: 'self' }); barba.transitions.store.add('transition', { leave() {}, enter() {} }); await barba.page(sameUrl, 'barba', undefined, false); expect(spyCacheHas).toHaveBeenCalledTimes(1); expect(spyCacheUpdate).toHaveBeenCalledTimes(1); expect(spyCacheSet).toHaveBeenCalledTimes(0); }); it('do page [waiting]', async () => { // Avoid updating data.next barba['_resetData'] = jest.fn(); barba.transitions.store.add('transition', { leave() {}, enter() {}, to: { namespace: 'ns' }, }); await barba.page(nextUrl, 'barba', undefined, false); expect(barba.data.next.html).toMatch(checkDoc); }); it('force on error', async () => { expect.assertions(2); barba.force = jest.fn(); barba.transitions.logger.error = jest.fn(); const errorLeave = new Error('Transition error [page][leave]'); barba.transitions.store.add('transition', { leave() { throw errorLeave; }, }); await barba.page(nextUrl, 'barba', undefined, false); expect(barba.transitions.logger.error).toHaveBeenCalledWith(errorLeave); expect(barba.force).toHaveBeenCalledTimes(1); }); ================================================ FILE: packages/core/__tests__/core/core.plugin.test.ts ================================================ /* tslint:disable:no-empty */ import { init } from '../../__mocks__/barba'; import barba from '../../src'; import { IBarbaOptions, IBarbaPlugin } from '../../src/defs'; init(); const plugin = { name: 'p1', version: 'alpha', install() {}, init() {}, }; const plugin2 = { name: 'p2', version: 'beta', install() {}, init() {}, }; beforeEach(() => { barba.plugins = []; }); it('store plugin', () => { barba.use(plugin); expect(barba.plugins).toHaveLength(1); expect(barba.plugins).toContain(plugin); }); it('store same plugin once', () => { barba.use(plugin); barba.use(plugin); expect(barba.plugins).toHaveLength(1); }); it('store different plugins', () => { barba.use(plugin); barba.use(plugin2); expect(barba.plugins).toHaveLength(2); expect(barba.plugins).toContain(plugin); expect(barba.plugins).toContain(plugin2); }); it('install method plugin', () => { plugin.install = jest.fn(); barba.use(plugin); expect(plugin.install).toHaveBeenCalledTimes(1); }); it('init plugin', () => { plugin.init = jest.fn(); barba.use(plugin); barba.init(); expect(plugin.init).toHaveBeenCalledTimes(1); }); it('warns invalid plugin', () => { barba.logger.warn = jest.fn(); barba.use({} as IBarbaPlugin); expect(barba.logger.warn).toHaveBeenCalledTimes(1); }); ================================================ FILE: packages/core/__tests__/core/core.popstate.test.ts ================================================ /* tslint:disable:no-empty no-string-literal */ import { init } from '../../__mocks__/barba'; import barba from '../../src'; init(); it('handle popstate change', () => { barba.go = jest.fn(); const popstate = document.createEvent('HTMLEvents'); popstate.initEvent('popstate', true, false); window.dispatchEvent(popstate); expect(barba.go).toHaveBeenCalledTimes(1); }); ================================================ FILE: packages/core/__tests__/core/core.prefetch.test.ts ================================================ import waitForExpect from 'wait-for-expect'; import xhrMock from 'xhr-mock'; import { init } from '../../__mocks__/barba'; import barba from '../../src'; init(); // Mocks let spySet: jest.SpyInstance; beforeEach(() => { spySet = jest.spyOn(barba.cache, 'set'); xhrMock.setup(); xhrMock.error(() => {}); // tslint:disable-line:no-empty }); afterEach(() => { spySet.mockRestore(); xhrMock.teardown(); }); it('prefetch url', () => { const url = 'http://localhost/foo.html'; barba.prefetch(url); expect(spySet).toHaveBeenCalledWith(url, Promise.resolve(), 'prefetch', 'pending'); }); it('prefetch and cache absolute url only', () => { const url = '/page.html'; barba.prefetch(url); expect(barba.cache.has('/page.html')).toBeFalsy(); expect(barba.cache.has('http://localhost/page.html')).toBeTruthy(); }); it('prefetch wrong url', async () => { const url = 'http://localhost/bad'; barba.logger.error = jest.fn(); xhrMock.get(url, (req, res) => res.status(500)); xhrMock.error(() => {}); // tslint:disable-line:no-empty barba.prefetch(url); expect(spySet).toHaveBeenCalledTimes(1); await waitForExpect(() => { expect(barba.logger.error).toHaveBeenCalledTimes(1); }); }); ================================================ FILE: packages/core/__tests__/core/core.requestError.test.ts ================================================ /* tslint:disable:no-string-literal */ import { init } from '../../__mocks__/barba'; import barba from '../../src'; init(); barba.force = jest.fn(); const trigger = 'barba'; const href = 'url'; const error = 'error'; const request = Promise.resolve(); it('forces URL on click', () => { barba.cache.set(href, request, 'enter', 'pending'); barba.onRequestError(trigger, href, error); expect(barba.force).not.toHaveBeenCalled(); barba.cache.update(href, { action: 'click' }); barba.onRequestError(trigger, href, error); expect(barba.force).toHaveBeenCalled(); }); it('calls custom request error', () => { barba['_requestCustomError'] = jest.fn(); barba.cache.set(href, request, 'click', 'pending'); barba.onRequestError(trigger, href, error); expect(barba['_requestCustomError']).toHaveBeenCalledWith( trigger, 'click', href, error ); expect(barba.force).toHaveBeenCalled(); }); it('does not force URL with falsy custom request error', () => { barba['_requestCustomError'] = jest.fn(() => false); barba.cache.set(href, request, 'click', 'pending'); barba.onRequestError(trigger, href, error); expect(barba['_requestCustomError']).toHaveBeenCalledWith( trigger, 'click', href, error ); expect(barba.force).not.toHaveBeenCalled(); }); ================================================ FILE: packages/core/__tests__/core/core.test.ts ================================================ import { init } from '../../__mocks__/barba'; import { version } from '../../package.json'; import barba from '../../src'; import { hooks } from '../../src/hooks'; import { Logger } from '../../src/modules/Logger'; import { schemaPage } from '../../src/schemas/page'; import { dom, helpers, request, url } from '../../src/utils'; it('has defaults', () => { expect(barba.version).toBe(version); expect(barba.schemaPage).toBe(schemaPage); expect(barba.hooks).toBe(hooks); expect(barba.Logger).toBe(Logger); expect(barba.logger).toBeInstanceOf(Logger); expect(barba.dom).toBe(dom); expect(barba.helpers).toBe(helpers); expect(barba.request).toBe(request); expect(barba.url).toBe(url); expect(barba.plugins).toHaveLength(0); }); ================================================ FILE: packages/core/__tests__/hooks/hooks.test.ts ================================================ import { HooksAll } from '../../src/defs'; import { hooks } from '../../src/hooks'; const [hookName] = hooks.all; afterEach(() => { hooks.clear(); }); it('has defaults', () => { expect(hooks.all).toBeDefined(); expect(hooks.registered.size).toBe(0); expect(hooks[hookName]).toBeDefined(); }); it('register hooks', () => { const ctx = {}; const fn = jest.fn(); const fn2 = jest.fn(); hooks[hookName](fn, ctx); expect(hooks.registered.has(hookName)).toBeTruthy(); expect(hooks.registered.get(hookName).size).toBe(1); hooks[hookName](fn2); expect(hooks.registered.get(hookName).size).toBe(2); const values = hooks.registered.get(hookName).values(); const v1 = values.next().value; const v2 = values.next().value; expect(v1.fn).toBe(fn); expect(v1.ctx).toBe(ctx); expect(v2.fn).toBe(fn2); expect(v2.ctx).toMatchObject({}); }); it('do nothing when no hooks', async () => { const doUnknown = jest.fn(() => hooks.do(('unknown' as unknown) as HooksAll)); const doUnregistered = jest.fn(() => hooks.do(hooks.all[1])); expect(doUnknown()).resolves.toBeUndefined(); expect(doUnregistered()).resolves.toBeUndefined(); }); it('do registered hooks', async () => { const fn = jest.fn(); const fn2 = jest.fn(); hooks[hookName](fn); hooks[hookName](fn2); await hooks.do(hookName); expect(fn).toHaveBeenCalledTimes(1); expect(fn2).toHaveBeenCalledTimes(1); }); it('do with arguments', async () => { const expected = 'arg'; const fn = jest.fn((...args) => args); hooks.init(); hooks[hookName](fn); await hooks.do(hookName, expected); expect(fn).toHaveReturnedWith([expected]); }); it('do with context', async () => { interface ICtx { prop: string; fn(): string; } const ctx: ICtx = { fn: jest.fn(function(this: ICtx) { return this.prop; }), prop: 'foo', }; hooks.init(); hooks[hookName](ctx.fn, ctx); await hooks.do(hookName); expect(ctx.fn).toHaveReturnedWith(ctx.prop); }); it('print help', () => { hooks.logger.info = jest.fn(); hooks.init(); hooks[hookName](); hooks.help(); expect(hooks.logger.info).toHaveBeenCalledTimes(2); }); it('catch error', async () => { expect.assertions(2); hooks.logger.debug = jest.fn(); hooks.logger.error = jest.fn(); const err = new Error('Test error'); hooks.init(); hooks[hookName](() => { throw err; }); await hooks.do(hookName); expect(hooks.logger.debug).toHaveBeenCalledWith(`Hook error [${hookName}]`); expect(hooks.logger.error).toHaveBeenCalledWith(err); }); ================================================ FILE: packages/core/__tests__/modules/cache.test.ts ================================================ import { Cache } from '../../src/modules/Cache'; const cache = new Cache(false); const key = 'key'; const request = Promise.resolve(); const action = 'enter'; const status = 'pending'; const target = key; const data = { action, request, status, target, }; it('sets, gets and has', () => { cache.set(key, request, action, status, target); expect(cache.has(key)).toBeTruthy(); expect(cache.get(key)).toEqual(data); }); it('set empty target', () => { cache.set(key, request, action, status); expect(cache.getTarget(key)).toBe(key); }); it('gets action, request, status and target', () => { expect(cache.getAction(key)).toBe(action); expect(cache.getRequest(key)).toBe(request); expect(cache.getStatus(key)).toBe(status); expect(cache.getTarget(key)).toBe(target); }); it('update', () => { cache.update(key, { action: 'click' }); expect(cache.getAction(key)).toBe('click'); }); it('deletes', () => { cache.delete(key); expect(cache.has(key)).toBeFalsy(); }); it('checks url', () => { cache.checkHref = jest.fn(); cache.has(key); expect(cache.checkHref).toHaveBeenCalled(); }); it('uses cacheIgnore', () => { cache.checkHref = jest.fn().mockImplementation(() => true); const res = cache.has(key); expect(res).toBeFalsy(); }); ================================================ FILE: packages/core/__tests__/modules/error.test.ts ================================================ import { BarbaError } from '../../src/modules/Error'; const err = new Error('Test error'); it('has defaults', () => { const e = new BarbaError(err); expect(e.name).toBe('BarbaError'); expect(e.label).toBe('Barba error'); expect(e.error).toBe(err); }); it('has params', () => { const e = new BarbaError(err, 'Label error', 'Message error'); expect(e.name).toBe('BarbaError'); expect(e.label).toBe('Label error'); expect(e.message).toBe('Message error'); expect(e.error).toBe(err); }); ================================================ FILE: packages/core/__tests__/modules/headers.test.ts ================================================ import { Headers } from '../../src/modules/Headers'; const headers = new Headers(); const name = 'header-name'; const value = 'header-value'; it('sets, gets and has', () => { headers.set(name, value); expect(headers.has(name)).toBeTruthy(); expect(headers.get(name)).toEqual(value); }); it('deletes a custom header', () => { headers.delete(name); expect(headers.has(name)).toBeFalsy(); }); it('gets all headers and clear them', () => { headers.set(name, value); headers.clear(); expect(headers.all().size).toEqual(0); }); ================================================ FILE: packages/core/__tests__/modules/ignore.test.ts ================================================ import { Ignore } from '../../src/modules/Ignore'; let ignore: Ignore; const url = 'http://localhost/'; const url2 = 'http://localhost/foo.html'; const url3 = 'http://localhost/bar/foo.html'; it('ignores none', () => { ignore = new Ignore(false); expect(ignore.checkHref(url)).toBeFalsy(); }); it('ignores all', () => { ignore = new Ignore(true); expect(ignore.checkHref(url)).toBeTruthy(); }); it('ignores URL', () => { ignore = new Ignore('/'); expect(ignore.checkHref(url)).toBeTruthy(); expect(ignore.checkHref(url2)).toBeFalsy(); }); it('ignores URLs', () => { ignore = new Ignore(['/', '/:segment/foo.html']); expect(ignore.checkHref(url)).toBeTruthy(); expect(ignore.checkHref(url2)).toBeFalsy(); expect(ignore.checkHref(url3)).toBeTruthy(); }); ================================================ FILE: packages/core/__tests__/modules/logger.test.ts ================================================ /* tslint:disable:no-string-literal */ import { Logger, LogLevels } from '../../src/modules/Logger'; let logger: Logger; const source = 'source'; const objects = ['foo', 'bar']; beforeEach(() => { logger = new Logger(source); }); // it('creates instances', () => { // expect(logger._source).toBeUndefined(); // expect(loggerSource._source).toBe(source); // }); it('has static log level', () => { expect(Logger.getLevel()).toBe(0); Logger.setLevel('error'); expect(Logger.getLevel()).toBe(1); Logger.setLevel('warning'); expect(Logger.getLevel()).toBe(2); Logger.setLevel('info'); expect(Logger.getLevel()).toBe(3); Logger.setLevel('debug'); expect(Logger.getLevel()).toBe(4); Logger.setLevel('off'); expect(Logger.getLevel()).toBe(0); }); it('logs all levels', () => { logger['_log'] = jest.fn(); logger.error(...objects); logger.warn(...objects); logger.info(...objects); logger.debug(...objects); expect(logger['_log']).toHaveBeenNthCalledWith(1, console.error, 1, objects); expect(logger['_log']).toHaveBeenNthCalledWith(2, console.warn, 2, objects); expect(logger['_log']).toHaveBeenNthCalledWith(3, console.info, 3, objects); expect(logger['_log']).toHaveBeenNthCalledWith(4, console.log, 4, objects); }); it('logs with source', () => { Logger.setLevel('debug'); global.console.log = jest.fn(); logger.debug(...objects); expect(global.console.log).toHaveBeenCalledWith(`[${source}] `, ...objects); }); it('logs all when level is debug', () => { const mock = jest.fn(); const levels = ['off', 'error', 'warning', 'info', 'debug']; global.console.log = mock; global.console.info = mock; global.console.warn = mock; global.console.error = mock; levels.forEach((level, i) => { Logger.setLevel(level as keyof typeof LogLevels); logger.debug(); logger.info(); logger.warn(); logger.error(); expect(mock).toHaveBeenCalledTimes(i); mock.mockReset(); }); }); ================================================ FILE: packages/core/__tests__/modules/prevent/prevent.blank.test.ts ================================================ import { IPreventCheckData, PreventCheck } from '../../../src/defs'; import { Prevent } from '../../../src/modules/Prevent'; const prevent = new Prevent(false); let check: PreventCheck; const el = document.createElement('a'); beforeEach(() => { check = jest.fn(data => prevent.tests.get('blank')(data)); [].slice.call(el.attributes).forEach(attr => el.removeAttribute(attr.name)); }); it('pass', () => { check({ el } as IPreventCheckData); expect(check).toHaveReturnedWith(false); }); it('pass with different target attribute', () => { el.setAttribute('target', '_self'); check({ el } as IPreventCheckData); expect(check).toHaveReturnedWith(false); }); it('prevent with target "_blank"', () => { el.setAttribute('target', '_blank'); check({ el } as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); ================================================ FILE: packages/core/__tests__/modules/prevent/prevent.corsDomain.test.ts ================================================ import { IPreventCheckData, PreventCheck } from '../../../src/defs'; import { Prevent } from '../../../src/modules/Prevent'; const prevent = new Prevent(false); let check: PreventCheck; const el = document.createElement('a'); beforeEach(() => { check = jest.fn(data => prevent.tests.get('corsDomain')(data)); [].slice.call(el.attributes).forEach(attr => el.removeAttribute(attr.name)); }); it('pass', () => { el.href = 'http://localhost'; check({ el } as IPreventCheckData); expect(check).toHaveReturnedWith(false); }); it('prevent with different protocol', () => { el.href = 'https://localhost'; check({ el } as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); it('prevent with different hostname', () => { el.href = 'https://domain.com'; check({ el } as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); ================================================ FILE: packages/core/__tests__/modules/prevent/prevent.corsPort.test.ts ================================================ import { IPreventCheckData, PreventCheck } from '../../../src/defs'; import { Prevent } from '../../../src/modules/Prevent'; const prevent = new Prevent(false); let check: PreventCheck; const el = document.createElement('a'); beforeEach(() => { check = jest.fn(data => prevent.tests.get('corsPort')(data)); [].slice.call(el.attributes).forEach(attr => el.removeAttribute(attr.name)); }); it('pass', () => { el.href = 'http://localhost'; check({ el } as IPreventCheckData); expect(check).toHaveReturnedWith(false); }); it('prevent with different port', () => { el.href = 'https://localhost:8888'; check({ el } as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); ================================================ FILE: packages/core/__tests__/modules/prevent/prevent.download.test.ts ================================================ import { IPreventCheckData, PreventCheck } from '../../../src/defs'; import { Prevent } from '../../../src/modules/Prevent'; const prevent = new Prevent(false); let check: PreventCheck; const el = document.createElement('a'); beforeEach(() => { check = jest.fn(data => prevent.tests.get('download')(data)); [].slice.call(el.attributes).forEach(attr => el.removeAttribute(attr.name)); }); it('pass', () => { check({ el } as IPreventCheckData); expect(check).toHaveReturnedWith(false); }); it('prevent with download attribute', () => { el.setAttribute('download', 'true'); check({ el } as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); ================================================ FILE: packages/core/__tests__/modules/prevent/prevent.exists.test.ts ================================================ import { IPreventCheckData, PreventCheck } from '../../../src/defs'; import { Prevent } from '../../../src/modules/Prevent'; const prevent = new Prevent(false); let check: PreventCheck; beforeEach(() => { check = jest.fn(data => prevent.tests.get('exists')(data)); }); it('pass (with element and href)', () => { check(({ el: true, href: true } as unknown) as IPreventCheckData); expect(check).toHaveReturnedWith(false); }); it('prevent with no element and no href', () => { check(({ el: false, href: false } as unknown) as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); it('prevent with element but no href', () => { check(({ el: true, href: false } as unknown) as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); it('prevent with no element but href', () => { check(({ el: false, href: true } as unknown) as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); ================================================ FILE: packages/core/__tests__/modules/prevent/prevent.newTab.test.ts ================================================ import { IPreventCheckData, PreventCheck } from '../../../src/defs'; import { Prevent } from '../../../src/modules/Prevent'; const prevent = new Prevent(false); let check: PreventCheck; beforeEach(() => { check = jest.fn(data => prevent.tests.get('newTab')(data)); }); it('pass', () => { check({ event: ({ altKey: false, ctrlKey: false, metaKey: false, shiftKey: false, which: 0, } as unknown) as Event, } as IPreventCheckData); expect(check).toHaveReturnedWith(false); }); it('prevent with "which"', () => { check({ event: ({ which: 2 } as unknown) as Event } as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); it('prevent with "metaKey"', () => { check({ event: ({ metaKey: true } as unknown) as Event, } as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); it('prevent with "ctrlKey"', () => { check({ event: ({ ctrlKey: true } as unknown) as Event, } as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); it('prevent with "shiftKey"', () => { check({ event: ({ shiftKey: true } as unknown) as Event, } as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); it('prevent with "altKey"', () => { check({ event: ({ altKey: true } as unknown) as Event } as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); ================================================ FILE: packages/core/__tests__/modules/prevent/prevent.preventAll.test.ts ================================================ import { IPreventCheckData, PreventCheck } from '../../../src/defs'; import { Prevent } from '../../../src/modules/Prevent'; const prevent = new Prevent(false); let check: PreventCheck; const el = document.createElement('a'); const parent = document.createElement('div'); parent.appendChild(el); beforeEach(() => { check = jest.fn(data => prevent.tests.get('preventAll')(data)); [].slice.call(el.attributes).forEach(attr => el.removeAttribute(attr.name)); }); it('pass', () => { check({ el } as IPreventCheckData); expect(check).toHaveReturnedWith(false); }); it('prevent with data-barba-prevent="all"', () => { parent.dataset.barbaPrevent = 'all'; check({ el } as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); ================================================ FILE: packages/core/__tests__/modules/prevent/prevent.preventSelf.test.ts ================================================ import { IPreventCheckData, PreventCheck } from '../../../src/defs'; import { Prevent } from '../../../src/modules/Prevent'; const prevent = new Prevent(false); let check: PreventCheck; const el = document.createElement('a'); beforeEach(() => { check = jest.fn(data => prevent.tests.get('preventSelf')(data)); [].slice.call(el.attributes).forEach(attr => el.removeAttribute(attr.name)); }); it('pass', () => { check({ el } as IPreventCheckData); expect(check).toHaveReturnedWith(false); }); it('prevent with data-barba-prevent', () => { el.dataset.barbaPrevent = ''; check({ el } as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); it('prevent with data-barba-prevent="self"', () => { el.dataset.barbaPrevent = 'self'; check({ el } as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); ================================================ FILE: packages/core/__tests__/modules/prevent/prevent.sameUrl.test.ts ================================================ import { IPreventCheckData, PreventCheck } from '../../../src/defs'; import { Prevent } from '../../../src/modules/Prevent'; const prevent = new Prevent(false); let check: PreventCheck; beforeEach(() => { check = jest.fn(data => prevent.tests.get('sameUrl')(data)); }); it('pass', () => { check({ href: 'somewhere' } as IPreventCheckData); expect(check).toHaveReturnedWith(false); }); it('prevent with same url', () => { check({ href: window.location.href } as IPreventCheckData); expect(check).toHaveReturnedWith(true); }); ================================================ FILE: packages/core/__tests__/modules/prevent/prevent.test.ts ================================================ import { Prevent } from '../../../src/modules/Prevent'; const prevent = new Prevent(false); it('adds test', () => { const name = 'fake'; const check = () => true; prevent.add(name, check); expect(prevent.tests.get(name)).toBe(check); }); ================================================ FILE: packages/core/__tests__/modules/store/store.add.test.ts ================================================ /* tslint:disable:no-string-literal */ import { Store } from '../../../src/modules/Store'; let store: Store; beforeEach(() => { store = new Store(); }); it('add rule', () => { const nb = store['_rules'].length; const r = { name: 'test', type: 'strings', }; store.add('rule', { value: r, }); expect(store['_rules']).toHaveLength(nb + 1); expect(store['_rules'][0]).toBe(r); }); it('add rule with position', () => { const nb = store['_rules'].length; const r = { name: 'test', type: 'strings', }; store.add('rule', { position: 1, value: r, }); expect(store['_rules']).toHaveLength(nb + 1); expect(store['_rules'][1]).toBe(r); }); it('add transition', () => { const nb = store.all.length; const t = {}; store.add('transition', t); expect(store.all).toHaveLength(nb + 1); expect(store.all[0]).toBe(t); }); ================================================ FILE: packages/core/__tests__/modules/store/store.addPriority.test.ts ================================================ /* tslint:disable:no-string-literal */ import { IRule, ITransitionPage } from '../../../src/defs'; import { Store } from '../../../src/modules/Store'; const store = new Store(); const rules: IRule[] = [ ({ name: 'tens', type: 'strings' } as unknown) as IRule, ({ name: 'hundreds', type: 'strings' } as unknown) as IRule, ]; store['_rules'] = rules; it('add priority 0', () => { expect(store['_addPriority']({}).priority).toBe(0); }); it('add priority 10', () => { expect( store['_addPriority'](({ tens: true } as unknown) as ITransitionPage) .priority ).toBe(10); }); it('add priority 11', () => { expect( store['_addPriority'](({ from: { tens: true }, } as unknown) as ITransitionPage).priority ).toBe(11); }); it('add priority 12', () => { expect( store['_addPriority'](({ to: { tens: true }, } as unknown) as ITransitionPage).priority ).toBe(12); }); it('add priority 13', () => { expect( store['_addPriority'](({ from: { tens: true }, to: { tens: true }, } as unknown) as ITransitionPage).priority ).toBe(13); }); it('add priority 100', () => { expect( store['_addPriority'](({ hundreds: true } as unknown) as ITransitionPage) .priority ).toBe(100); }); it('add priority 101', () => { expect( store['_addPriority'](({ from: { hundreds: true }, } as unknown) as ITransitionPage).priority ).toBe(101); }); it('add priority 102', () => { expect( store['_addPriority'](({ to: { hundreds: true }, } as unknown) as ITransitionPage).priority ).toBe(102); }); it('add priority 103', () => { expect( store['_addPriority'](({ from: { hundreds: true }, to: { hundreds: true }, } as unknown) as ITransitionPage).priority ).toBe(103); }); it('add priority 110', () => { expect( store['_addPriority'](({ hundreds: true, tens: true, } as unknown) as ITransitionPage).priority ).toBe(110); }); it('add priority 112', () => { expect( store['_addPriority'](({ from: { tens: true, hundreds: true }, } as unknown) as ITransitionPage).priority ).toBe(112); }); it('add priority 114', () => { expect( store['_addPriority'](({ to: { tens: true, hundreds: true }, } as unknown) as ITransitionPage).priority ).toBe(114); }); it('add priority 116', () => { expect( store['_addPriority'](({ from: { tens: true, hundreds: true }, to: { tens: true, hundreds: true }, } as unknown) as ITransitionPage).priority ).toBe(116); }); ================================================ FILE: packages/core/__tests__/modules/store/store.check.test.ts ================================================ /* tslint:disable:no-string-literal */ import { IRule, ISchemaPage, ITransitionData, ITransitionPage, } from '../../../src/defs'; import { Store } from '../../../src/modules/Store'; import { schemaPage } from '../../../src/schemas/page'; let match = {}; const store = new Store(); store.add('rule', { position: 1, value: { name: 'route', type: 'object', }, }); schemaPage.route = { name: '', params: {} }; const mockData: ITransitionData = { current: schemaPage, next: schemaPage, trigger: 'barba', }; beforeEach(() => { match = {}; }); /** * Check rules * * Based on rule type: strings | object | function * 1. transition has no rule "property": * - always returns true * 2. transition has rule "property": * - "strings" should be present on both side (transition + view) and match * - "function" should return true */ /** * Run "strings" check test */ function runStrings( transition: ITransitionPage, rule: IRule, data: ITransitionData, direction?: 'from' | 'to' ) { const fail = store['_check'](transition, rule, mockData, match, direction); expect(fail).toBeFalsy(); expect(match).toMatchObject({}); const ok = store['_check'](transition, rule, data, match, direction); expect(ok).toBeTruthy(); expect(match).toMatchObject(transition); } /** * Run "object" check test */ function runObject( transition: ITransitionPage, rule: IRule, data: ITransitionData, direction?: 'from' | 'to' ) { const fail = store['_check'](transition, rule, mockData, match, direction); expect(fail).toBeFalsy(); expect(match).toMatchObject({}); const ok = store['_check'](transition, rule, data, match, direction); expect(ok).toBeTruthy(); expect(match).toMatchObject(transition); } /** * Run "function" check test */ function runFunction( falsy: ITransitionPage, truthy: ITransitionPage, rule: IRule, direction?: 'from' | 'to' ) { const fail = store['_check'](falsy, rule, mockData, match, direction); expect(fail).toBeFalsy(); expect(match).toMatchObject({}); const ok = store['_check'](truthy, rule, mockData, match, direction); expect(ok).toBeTruthy(); expect(match).toMatchObject(truthy); } // "strings" type it('check valid for inexisting rule "strings"', () => { const [rule] = store['_rules'].filter(r => r.name === 'namespace'); const expected = store['_check']( {}, rule, ({ namespace: 'ns' } as unknown) as ITransitionData, match ); expect(expected).toBeTruthy(); expect(match).toMatchObject({}); }); it('check single "strings"', () => { const transition = { namespace: 'ns' }; const [rule] = store['_rules'].filter(r => r.name === 'namespace'); const data = { current: { namespace: 'ns' } }; runStrings(transition, rule, (data as unknown) as ITransitionData); }); it('check single "strings" with "from"', () => { const transition = { from: { namespace: 'ns' } }; const [rule] = store['_rules'].filter(r => r.name === 'namespace'); const data = { current: { namespace: 'ns' } }; runStrings(transition, rule, (data as unknown) as ITransitionData, 'from'); }); it('check single "strings" with "to"', () => { const transition = { to: { namespace: 'ns' } }; const [rule] = store['_rules'].filter(r => r.name === 'namespace'); const data = { next: { namespace: 'ns' } }; runStrings(transition, rule, (data as unknown) as ITransitionData, 'to'); }); it('check array "strings"', () => { const transition = { namespace: ['ns'] }; const [rule] = store['_rules'].filter(r => r.name === 'namespace'); const data = { current: { namespace: 'ns' } }; runStrings(transition, rule, (data as unknown) as ITransitionData); }); it('check array "strings" with "from"', () => { const transition = { from: { namespace: ['ns'] } }; const [rule] = store['_rules'].filter(r => r.name === 'namespace'); const data = { current: { namespace: 'ns' } }; runStrings(transition, rule, (data as unknown) as ITransitionData, 'from'); }); it('check array "strings" with "to"', () => { const transition = { to: { namespace: ['ns'] } }; const [rule] = store['_rules'].filter(r => r.name === 'namespace'); const data = { next: { namespace: 'ns' } }; runStrings(transition, rule, (data as unknown) as ITransitionData, 'to'); }); // "object" type it('check valid for inexisting rule "object"', () => { const [rule] = store['_rules'].filter(r => r.name === 'route'); const expected = store['_check']( {}, rule, ({ route: 'r' } as unknown) as ITransitionData, match ); expect(expected).toBeTruthy(); expect(match).toMatchObject({}); }); it('check invalid for non matching rule "object"', () => { const transition = { route: 'r' }; const [rule] = store['_rules'].filter(r => r.name === 'route'); const data = { current: { route: null } as ISchemaPage }; const expected = store['_check']( transition, rule, (data as unknown) as ITransitionData, match ); expect(expected).toBeFalsy(); expect(match).toMatchObject({}); }); it('check single "object"', () => { const transition = { route: 'r' }; const [rule] = store['_rules'].filter(r => r.name === 'route'); const data = { current: { route: { name: 'r' } } }; runObject(transition, rule, (data as unknown) as ITransitionData); }); it('check single "object" with "from"', () => { const transition = { from: { route: 'r' } }; const [rule] = store['_rules'].filter(r => r.name === 'route'); const data = { current: { route: { name: 'r' } } }; runObject(transition, rule, (data as unknown) as ITransitionData, 'from'); }); it('check single "object" with "to"', () => { const transition = { to: { route: 'r' } }; const [rule] = store['_rules'].filter(r => r.name === 'route'); const data = { next: { route: { name: 'r' } } }; runObject(transition, rule, (data as unknown) as ITransitionData, 'to'); }); it('check array "object"', () => { const transition = { route: ['r'] }; const [rule] = store['_rules'].filter(r => r.name === 'route'); const data = { current: { route: { name: 'r' } } }; runObject(transition, rule, (data as unknown) as ITransitionData); }); it('check array "object" with "from"', () => { const transition = { from: { route: ['r'] } }; const [rule] = store['_rules'].filter(r => r.name === 'route'); const data = { current: { route: { name: 'r' } } }; runObject(transition, rule, (data as unknown) as ITransitionData, 'from'); }); it('check array "object" with "to"', () => { const transition = { to: { route: ['r'] } }; const [rule] = store['_rules'].filter(r => r.name === 'route'); const data = { next: { route: { name: 'r' } } }; runObject(transition, rule, (data as unknown) as ITransitionData, 'to'); }); // "function" type it('check valid for inexisting rule "function"', () => { const [rule] = store['_rules'].filter(r => r.name === 'custom'); const expected = store['_check']( {}, rule, ({} as unknown) as ITransitionData, match ); expect(expected).toBeTruthy(); expect(match).toMatchObject({}); }); it('check "function"', () => { const falsy = { custom: () => false }; const truthy = { custom: () => true }; const [rule] = store['_rules'].filter(r => r.name === 'custom'); runFunction(falsy, truthy, rule); }); it('check "function" with "from"', () => { const falsy = { from: { custom: () => false } }; const truthy = { from: { custom: () => true } }; const [rule] = store['_rules'].filter(r => r.name === 'custom'); runFunction(falsy, truthy, rule, 'from'); }); it('check "function" with "to"', () => { const falsy = { to: { custom: () => false } }; const truthy = { to: { custom: () => true } }; const [rule] = store['_rules'].filter(r => r.name === 'custom'); runFunction(falsy, truthy, rule, 'to'); }); ================================================ FILE: packages/core/__tests__/modules/store/store.log.test.ts ================================================ import { Logger } from '../../../src/modules/Logger'; import { Store } from '../../../src/modules/Store'; import { schemaPage } from '../../../src/schemas/page'; it('has debug info', () => { const store = new Store([]); Logger.setLevel('info'); store.logger.info = jest.fn(); store.resolve({ current: schemaPage, next: schemaPage, trigger: 'barba', }); expect(store.logger.info).toHaveBeenCalledTimes(1); }); ================================================ FILE: packages/core/__tests__/modules/store/store.resolve.once.test.ts ================================================ import { ITransitionData, ITransitionOnce } from '../../../src/defs'; import { Store } from '../../../src/modules/Store'; const tOnce: ITransitionOnce = { once: () => Promise.resolve() }; const tOnceNs: ITransitionOnce = { namespace: 'ns', once: () => Promise.resolve(), }; const tOnceCustom: ITransitionOnce = { once: () => Promise.resolve(), custom({ current }) { return current.namespace === 'custom'; }, }; const store = new Store([tOnce, tOnceNs, tOnceCustom]); it('has no transition', async () => { const emptyStore = new Store(); emptyStore.logger.info = jest.fn(); emptyStore.resolve(({} as unknown) as ITransitionData, { once: true }); expect(emptyStore.logger.info).toHaveBeenCalledWith( 'No transition found [once]' ); }); it('get "once" transition', () => { const result = store.resolve( ({ current: { namespace: 'none' }, } as unknown) as ITransitionData, { once: true } ); expect(result).toBe(tOnce); }); it('get "once/ns" transition', () => { const result = store.resolve( ({ current: { namespace: 'ns' }, } as unknown) as ITransitionData, { once: true } ); expect(result).toBe(tOnceNs); }); it('get "once/custom" transition', () => { const result = store.resolve( ({ current: { namespace: 'custom' }, } as unknown) as ITransitionData, { once: true } ); expect(result).toBe(tOnceCustom); }); ================================================ FILE: packages/core/__tests__/modules/store/store.resolve.page.test.ts ================================================ /* tslint:disable:no-empty */ import { ITransitionData } from '../../../src/defs'; import { Store } from '../../../src/modules/Store'; const t = { enter() {} }; const tNs = { enter() {}, namespace: 'ns' }; const tNsFrom = { enter() {}, from: { namespace: 'nsFrom' } }; const tNsTo = { enter() {}, to: { namespace: 'nsTo' } }; const tNsFromTo = { enter() {}, from: { namespace: 'nsFrom' }, to: { namespace: 'nsTo' }, }; const tSelf = { enter() {}, name: 'self' }; const store = new Store([t, tNs, tNsFrom, tNsTo, tNsFromTo, tSelf]); it('has no transition', async () => { const emptyStore = new Store(); emptyStore.logger.info = jest.fn(); emptyStore.resolve(({} as unknown) as ITransitionData); expect(emptyStore.logger.info).toHaveBeenCalledWith( 'No transition found [page]' ); }); it('get "page" transition', () => { const result = store.resolve(({ current: true, next: true, } as unknown) as ITransitionData); expect(result).toBe(t); }); it('get "page/ns" transition', () => { const result = store.resolve(({ current: { namespace: 'ns' }, next: {}, } as unknown) as ITransitionData); expect(result).toBe(tNs); }); it('get "page/from" transition', () => { const result = store.resolve(({ current: { namespace: 'nsFrom' }, next: {}, } as unknown) as ITransitionData); expect(result).toBe(tNsFrom); }); it('get "page/to" transition', () => { const result = store.resolve(({ current: {}, next: { namespace: 'nsTo' }, } as unknown) as ITransitionData); expect(result).toBe(tNsTo); }); it('get "page/fromTo" transition', () => { const result = store.resolve(({ current: { namespace: 'nsFrom' }, next: { namespace: 'nsTo' }, } as unknown) as ITransitionData); expect(result).toBe(tNsFromTo); }); it('get "self" transition', () => { const result = store.resolve( ({ current: { namespace: 'nsFrom' }, next: { namespace: 'nsTo' }, } as unknown) as ITransitionData, { self: true } ); expect(result).toBe(tSelf); }); ================================================ FILE: packages/core/__tests__/modules/store/store.test.ts ================================================ import { Store } from '../../../src/modules/Store'; let store: Store; it('has defaults', () => { store = new Store([]); expect(store.all).toHaveLength(0); expect(store.once).toHaveLength(0); }); it('adds transitions', () => { store = new Store([{}]); expect(store.all).toHaveLength(1); }); ================================================ FILE: packages/core/__tests__/modules/store/store.update.sort.test.ts ================================================ import shuffle from 'lodash/shuffle'; import transitions from '../../../__mocks__/transitions'; import { Store } from '../../../src/modules/Store'; let store: Store; const expected = transitions.map(t => t.name).reverse(); const times = (x: number) => (f: () => void) => { if (x > 0) { f(); times(x - 1)(f); } }; const r = { name: 'route', type: 'strings', }; times(50)(() => { it('sort all', () => { store = new Store(shuffle(transitions)); store.add('rule', { position: 1, value: r, }); expect(store.all).toHaveLength(transitions.length); expect(store.all.map(t => t.name)).toEqual(expected); }); }); ================================================ FILE: packages/core/__tests__/modules/store/store.update.test.ts ================================================ /* tslint:disable:no-empty */ import { Store } from '../../../src/modules/Store'; let store: Store; let nb: number; beforeEach(() => { nb = 0; }); it('update all', () => { store = new Store([{}]); expect(store.all).toHaveLength(nb + 1); expect(store.once).toHaveLength(0); }); it('update once', () => { store = new Store([{ once() {} }]); expect(store.all).toHaveLength(nb + 1); expect(store.once).toHaveLength(nb + 1); }); it('update page', () => { store = new Store([{ enter() {} }]); expect(store.all).toHaveLength(nb + 1); expect(store.once).toHaveLength(0); }); it('update once and page', () => { store = new Store([{ once() {}, enter() {} }]); expect(store.all).toHaveLength(nb + 1); expect(store.once).toHaveLength(nb + 1); }); ================================================ FILE: packages/core/__tests__/modules/transitions/transitions.async.test.ts ================================================ import { ISchemaPage, ITransitionData } from '../../../src/defs'; import { hooks } from '../../../src/hooks'; import { Transitions } from '../../../src/modules/Transitions'; const transitions = new Transitions(); // Mocks const enter = jest.fn(); // Shared const div = document.createElement('div'); const wrapper = div.cloneNode() as HTMLElement; const currentContainer = div.cloneNode() as HTMLElement; const nextContainer = div.cloneNode() as HTMLElement; // Data const data: ITransitionData = { current: { container: currentContainer, } as ISchemaPage, next: { container: nextContainer, } as ISchemaPage, trigger: 'barba', }; const payload = 'payload'; /** * Just do page… */ async function doPage(leave: any) { await transitions.doPage({ data, page: Promise.resolve(), transition: { leave, enter }, wrapper, }); } beforeEach(() => { hooks.init(); }); it('returns with "promise"', async () => { expect.assertions(1); const leavePromise = () => Promise.resolve(payload); await doPage(leavePromise); expect(enter).toHaveBeenCalledWith(data, payload); }); it('returns with "callback"', async () => { expect.assertions(1); const ctx: any = { leaveCallback() { const done = this.async(); done(null, payload); }, }; await doPage(ctx.leaveCallback); expect(enter).toHaveBeenCalledWith(data, payload); }); it('returns with "sync"', async () => { expect.assertions(1); const leaveSync = () => payload; await doPage(leaveSync); expect(enter).toHaveBeenCalledWith(data, payload); }); ================================================ FILE: packages/core/__tests__/modules/transitions/transitions.context.test.ts ================================================ import { ISchemaPage, ITransitionData, ITransitionOnce, } from '../../../src/defs'; import { Logger } from '../../../src/modules/Logger'; import { Transitions } from '../../../src/modules/Transitions'; // Silence is gold… :) Logger.setLevel('off'); const transitions = new Transitions([]); // Data let data: ITransitionData; beforeEach(() => { data = { current: {} as ISchemaPage, next: {} as ISchemaPage, trigger: 'barba', }; }); it('calls methods', async () => { const t = { bar: jest.fn(), once() { this.bar(this.foo); }, foo: 'foo', }; await transitions.doOnce({ data, transition: (t as any) as ITransitionOnce, }); expect(t.bar).toHaveBeenCalledWith(t.foo); }); ================================================ FILE: packages/core/__tests__/modules/transitions/transitions.get.test.ts ================================================ import { ISchemaPage, ITransitionData } from '../../../src/defs'; import { Transitions } from '../../../src/modules/Transitions'; const once = { once: () => Promise.resolve(), }; const enter = { enter: () => Promise.resolve(), }; const transitions = new Transitions([once, enter]); const data: ITransitionData = { current: {} as ISchemaPage, next: {} as ISchemaPage, trigger: 'barba', }; it('gets page transition', () => { expect(transitions.get(data)).toBe(enter); }); it('gets once transition', () => { expect(transitions.get(data, { once: true })).toBe(once); }); ================================================ FILE: packages/core/__tests__/modules/transitions/transitions.once.test.ts ================================================ import { ISchemaPage, ITransitionData, ITransitionOnce, } from '../../../src/defs'; import { hooks } from '../../../src/hooks'; import { Logger } from '../../../src/modules/Logger'; import { Transitions } from '../../../src/modules/Transitions'; // Silence is gold… :) Logger.setLevel('off'); const transitions = new Transitions([]); // Mocks const beforeOnce = jest.fn(); const once = jest.fn(); const afterOnce = jest.fn(); hooks.do = jest.fn(); // Data let data: ITransitionData; beforeEach(() => { data = { current: {} as ISchemaPage, next: {} as ISchemaPage, trigger: 'barba', }; }); it('does not need once', async () => { expect.assertions(1); await transitions.doOnce({ data, transition: undefined as ITransitionOnce, }); expect(hooks.do).toHaveBeenNthCalledWith(2, 'once', data, {}); }); it('calls methods', async () => { expect.assertions(9); await transitions.doOnce({ data, transition: { beforeOnce, once, afterOnce }, }); expect(beforeOnce).toHaveBeenCalledTimes(1); expect(beforeOnce).toHaveBeenCalledWith(data); expect(once).toHaveBeenCalledTimes(1); expect(once).toHaveBeenCalledWith(data); expect(afterOnce).toHaveBeenCalledTimes(1); expect(afterOnce).toHaveBeenCalledWith(data); await transitions.doOnce({ data, transition: { once } }); expect(beforeOnce).toHaveBeenCalledTimes(1); expect(once).toHaveBeenCalledTimes(2); expect(afterOnce).toHaveBeenCalledTimes(1); }); it('calls hooks', async () => { expect.assertions(4); const t = { once: () => Promise.resolve() }; await transitions.doOnce({ data, transition: t }); expect(hooks.do).toHaveBeenCalledTimes(3); expect(hooks.do).toHaveBeenNthCalledWith(1, 'beforeOnce', data, t); expect(hooks.do).toHaveBeenNthCalledWith(2, 'once', data, t); expect(hooks.do).toHaveBeenNthCalledWith(3, 'afterOnce', data, t); }); it('catches error', async () => { expect.assertions(2); transitions.logger.debug = jest.fn(); transitions.logger.error = jest.fn(); const err = new Error('Test'); const onceError = () => { throw err; }; const t = { once: onceError }; await transitions.doOnce({ data, transition: t, }); expect(transitions.logger.debug).toHaveBeenCalledWith( 'Transition error [before/after/once]' ); expect(transitions.logger.error).toHaveBeenCalledWith(err); }); ================================================ FILE: packages/core/__tests__/modules/transitions/transitions.page.test.ts ================================================ /* tslint:disable:no-empty */ import { init } from '../../../__mocks__/barba'; import barba from '../../../src'; import { IResponse, ISchemaPage, ITransitionData } from '../../../src/defs'; import { hooks } from '../../../src/hooks'; import { Logger } from '../../../src/modules/Logger'; import { Transitions } from '../../../src/modules/Transitions'; // Silence is gold… :) Logger.setLevel('off'); const transitions = new Transitions([]); // Mocks const before = jest.fn(); const after = jest.fn(); const beforeLeave = jest.fn(); const leave = jest.fn(); const afterLeave = jest.fn(); const beforeEnter = jest.fn(); const enter = jest.fn(); const afterEnter = jest.fn(); hooks.do = jest.fn(); // Dom const { wrapper, container: currentContainer } = init(); // const div = document.createElement('div'); // const wrapper = div.cloneNode() as HTMLElement; // const currentContainer = div.cloneNode() as HTMLElement; // const nextContainer = div.cloneNode() as HTMLElement; const nextContainer = currentContainer.cloneNode() as HTMLElement; const nextHtml = `
Next page
`; // Data let data: ITransitionData; beforeEach(() => { nextContainer.remove(); wrapper.appendChild(currentContainer); data = { current: { container: currentContainer, html: undefined, url: {}, } as ISchemaPage, next: { container: nextContainer, html: undefined, url: {}, } as ISchemaPage, trigger: 'barba', }; }); const page = Promise.resolve({ html: nextHtml, url: {}, } as IResponse); it('leaves falsy', async () => { expect.assertions(1); await transitions.doPage({ data, page, transition: { leave: () => Promise.resolve(false) }, wrapper, }); expect(beforeEnter).not.toHaveBeenCalled(); }); for (let i = 0; i < 2; i++) { const sync = i === 0; it('calls methods', async () => { expect.assertions(20); const t = { after, afterEnter, afterLeave, before, beforeEnter, beforeLeave, enter, leave, sync, }; await transitions.doPage({ data, page, transition: t, wrapper, }); expect(before).toHaveBeenCalledTimes(1); expect(before).toHaveBeenCalledWith(data); expect(beforeLeave).toHaveBeenCalledTimes(1); expect(beforeLeave).toHaveBeenCalledWith(data); expect(leave).toHaveBeenCalledTimes(1); expect(leave).toHaveBeenCalledWith(data); expect(afterLeave).toHaveBeenCalledTimes(1); expect(afterLeave).toHaveBeenCalledWith(data); expect(beforeEnter).toHaveBeenCalledTimes(1); expect(beforeEnter).toHaveBeenCalledWith(data); expect(enter).toHaveBeenCalledTimes(1); expect(enter).toHaveBeenCalledWith(data, undefined); expect(afterEnter).toHaveBeenCalledTimes(1); expect(afterEnter).toHaveBeenCalledWith(data); expect(after).toHaveBeenCalledTimes(1); expect(after).toHaveBeenCalledWith(data); await transitions.doPage({ data, page: Promise.resolve(), transition: { leave, enter }, wrapper, }); expect(before).toHaveBeenCalledTimes(1); expect(leave).toHaveBeenCalledTimes(2); expect(enter).toHaveBeenCalledTimes(2); expect(after).toHaveBeenCalledTimes(1); }); } it('calls hooks (sync: false)', async () => { expect.assertions(11); const t = { enter, leave, sync: false, }; await transitions.doPage({ data, page, transition: t, wrapper, }); expect(hooks.do).toHaveBeenCalledTimes(10); expect(hooks.do).toHaveBeenNthCalledWith(1, 'before', data, t); expect(hooks.do).toHaveBeenNthCalledWith(2, 'beforeLeave', data, t); expect(hooks.do).toHaveBeenNthCalledWith(3, 'leave', data, t); expect(hooks.do).toHaveBeenNthCalledWith(4, 'afterLeave', data, t); expect(hooks.do).toHaveBeenNthCalledWith(5, 'nextAdded', data); expect(hooks.do).toHaveBeenNthCalledWith(6, 'beforeEnter', data, t); expect(hooks.do).toHaveBeenNthCalledWith(7, 'enter', data, t); expect(hooks.do).toHaveBeenNthCalledWith(8, 'afterEnter', data, t); expect(hooks.do).toHaveBeenNthCalledWith(9, 'currentRemoved', data); expect(hooks.do).toHaveBeenNthCalledWith(10, 'after', data, t); }); it('calls hooks (sync: true)', async () => { expect.assertions(11); const t = { enter, leave, sync: true, }; await transitions.doPage({ data, page, transition: t, wrapper, }); expect(hooks.do).toHaveBeenCalledTimes(10); expect(hooks.do).toHaveBeenNthCalledWith(1, 'before', data, t); expect(hooks.do).toHaveBeenNthCalledWith(2, 'nextAdded', data); expect(hooks.do).toHaveBeenNthCalledWith(3, 'beforeLeave', data, t); expect(hooks.do).toHaveBeenNthCalledWith(4, 'beforeEnter', data, t); expect(hooks.do).toHaveBeenNthCalledWith(5, 'leave', data, t); expect(hooks.do).toHaveBeenNthCalledWith(6, 'enter', data, t); expect(hooks.do).toHaveBeenNthCalledWith(7, 'afterLeave', data, t); expect(hooks.do).toHaveBeenNthCalledWith(8, 'afterEnter', data, t); expect(hooks.do).toHaveBeenNthCalledWith(9, 'currentRemoved', data); expect(hooks.do).toHaveBeenNthCalledWith(10, 'after', data, t); }); it('catches error (leave, sync: false)', async () => { expect.assertions(4); const leaveError = () => { throw new Error('test'); }; const t = { leave: leaveError }; try { await transitions.doPage({ data, page, transition: t, wrapper, }); } catch (e) { expect(e.name).toEqual('BarbaError'); expect(e.label).toEqual('Transition error [before/after/leave]'); expect(e.error).toEqual(new Error('test')); expect(transitions.isRunning).toBeFalsy(); } }); it('catches error (enter, sync: false)', async () => { expect.assertions(4); const enterError = () => { throw new Error('test'); }; const t = { leave() { return Promise.resolve('foo'); }, enter: enterError, }; try { await transitions.doPage({ data, page, transition: t, wrapper, }); } catch (e) { expect(e.name).toEqual('BarbaError'); expect(e.label).toEqual('Transition error [before/after/enter]'); expect(e.error).toEqual(new Error('test')); expect(transitions.isRunning).toBeFalsy(); } }); it('catches error (leave, sync: true)', async () => { expect.assertions(3); const leaveError = () => { throw new Error('test'); }; const t = { sync: true, leave: leaveError, enter() {} }; try { await transitions.doPage({ data, page, transition: t, wrapper, }); } catch (e) { expect(e.name).toEqual('BarbaError'); expect(e.label).toEqual('Transition error [sync]'); expect(e.error).toEqual(new Error('test')); } }); it('catches error (enter, sync: true)', async () => { expect.assertions(3); const enterError = () => { throw new Error('test'); }; const t = { sync: true, leave() {}, enter: enterError }; try { await transitions.doPage({ data, page, transition: t, wrapper, }); } catch (e) { expect(e.name).toEqual('BarbaError'); expect(e.label).toEqual('Transition error [sync]'); expect(e.error).toEqual(new Error('test')); } }); it('catches "global" error (before)', async () => { expect.assertions(2); const err = new Error('Test'); const beforeError = () => { throw err; }; const t = { sync: true, leave() {}, enter() {}, before: beforeError }; try { await transitions.doPage({ data, page, transition: t, wrapper, }); } catch (e) { expect(e.name).toEqual('Error'); expect(e).toEqual(err); } }); it('ignores "non transition" errors', async () => { expect.assertions(3); const tError = new Error('Weird transition error'); const leaveError1 = () => { throw new Error('Timeout error'); }; const leaveError2 = () => { throw new Error('Fetch error'); }; const enterError3 = () => { const err = new Error('Request error'); delete err.message; (err as any).status = 500; throw err; }; const leaveError4 = () => { delete tError.message; throw tError; }; const t1 = { sync: true, leave: leaveError1, enter() {} }; const t2 = { leave: leaveError2, enter() {} }; const t3 = { leave() {}, enter: enterError3 }; const t4 = { leave: leaveError4, enter() {} }; await transitions.doPage({ data, page, transition: t1, wrapper, }); await transitions.doPage({ data, page, transition: t2, wrapper, }); await transitions.doPage({ data, page, transition: t3, wrapper, }); try { await transitions.doPage({ data, page, transition: t4, wrapper, }); } catch (e) { expect(e.name).toEqual('BarbaError'); expect(e.label).toEqual('Transition error [before/after/leave]'); expect(e.error).toEqual(tError); } }); ================================================ FILE: packages/core/__tests__/modules/transitions/transitions.test.ts ================================================ import { Transitions } from '../../../src/modules/Transitions'; let transitions: Transitions; it('has defaults', () => { transitions = new Transitions(); expect(transitions.hasOnce).toBeFalsy(); expect(transitions.shouldWait).toBeFalsy(); expect(transitions.isRunning).toBeFalsy(); expect(transitions.store).toBeDefined(); }); it('has once', () => { transitions = new Transitions([{ once: () => Promise.resolve() }]); expect(transitions.hasOnce).toBeTruthy(); }); it('should wait', () => { transitions = new Transitions([{ to: { namespace: 'ns' } }]); expect(transitions.shouldWait).toBeTruthy(); transitions = new Transitions([{ sync: true }]); expect(transitions.shouldWait).toBeTruthy(); }); ================================================ FILE: packages/core/__tests__/modules/views.test.ts ================================================ import { IView } from '../../src/defs'; import { hooks } from '../../src/hooks'; import { Views } from '../../src/modules/Views'; let views: Views; // beforeEach(() => { // hooks.init(); // }); afterEach(() => { hooks.clear(); }); const beforeLeave = jest.fn(); const afterLeave = jest.fn(); const beforeEnter = jest.fn(); const afterEnter = jest.fn(); it('has defaults', () => { views = new Views([]); expect(views.byNamespace.size).toBe(0); }); it('init views', () => { views = new Views([ { namespace: 'foo', name: 'overriden' }, { namespace: 'foo', name: 'ok' }, { namespace: 'bar', name: 'ok' }, ]); expect(views.byNamespace.get('foo')).toEqual({ name: 'ok', namespace: 'foo', }); expect(views.byNamespace.get('bar')).toEqual({ name: 'ok', namespace: 'bar', }); }); it('register hooks', () => { // No views, no hooks views = new Views([]); expect(hooks.registered.get('beforeLeave')).toBeUndefined(); expect(hooks.registered.get('afterLeave')).toBeUndefined(); expect(hooks.registered.get('beforeEnter')).toBeUndefined(); expect(hooks.registered.get('afterEnter')).toBeUndefined(); views = new Views([{ namespace: 'baz' }]); expect(hooks.registered.get('beforeLeave').size).toBe(1); expect(hooks.registered.get('afterLeave').size).toBe(1); expect(hooks.registered.get('beforeEnter').size).toBe(1); expect(hooks.registered.get('afterEnter').size).toBe(1); }); /* tslint:disable:object-literal-sort-keys */ it('do existing hooks for existing namespace', async () => { views = new Views([ { namespace: 'success', beforeLeave, afterLeave, beforeEnter, afterEnter, }, ]); /* tslint:enable:object-literal-sort-keys */ const success = { current: { namespace: 'success' }, next: { namespace: 'success' }, }; await hooks.do('beforeLeave', success); await hooks.do('afterLeave', success); await hooks.do('beforeEnter', success); await hooks.do('afterEnter', success); expect(beforeLeave).toHaveBeenCalledWith(success); expect(afterLeave).toHaveBeenCalledWith(success); expect(beforeEnter).toHaveBeenCalledWith(success); expect(afterEnter).toHaveBeenCalledWith(success); }); it('do nothing for missing hooks', () => { views = new Views([{ namespace: 'success' }]); const success = { current: { namespace: 'success' }, next: { namespace: 'success' }, }; hooks.do('beforeLeave', success); hooks.do('afterLeave', success); hooks.do('beforeEnter', success); hooks.do('afterEnter', success); expect(beforeLeave).not.toHaveBeenCalled(); expect(afterLeave).not.toHaveBeenCalled(); expect(beforeEnter).not.toHaveBeenCalled(); expect(afterEnter).not.toHaveBeenCalled(); }); it('do nothing for missing namespace', () => { views = new Views([ { namespace: 'success', beforeLeave, afterLeave, beforeEnter, afterEnter }, ]); const fail = { current: { namespace: 'fail' }, next: { namespace: 'fail' } }; hooks.do('beforeLeave', fail); hooks.do('afterLeave', fail); hooks.do('beforeEnter', fail); hooks.do('afterEnter', fail); expect(beforeLeave).not.toHaveBeenCalled(); expect(afterLeave).not.toHaveBeenCalled(); expect(beforeEnter).not.toHaveBeenCalled(); expect(afterEnter).not.toHaveBeenCalled(); }); it('has right context', async () => { const v = { bar: jest.fn(), beforeEnter() { this.bar(this.foo); }, foo: 'foo', namespace: 'success', }; views = new Views([v]); const success = { next: { namespace: 'success' }, }; await hooks.do('beforeEnter', success); expect(v.bar).toHaveBeenCalledWith(v.foo); }); ================================================ FILE: packages/core/__tests__/utils/dom.test.ts ================================================ /* tslint:disable:no-string-literal */ import { schemaAttribute } from '../../src/schemas/attribute'; import { dom } from '../../src/utils'; // Init const attr = schemaAttribute; // Dom const namespace = 'ns'; const currentContainer = document.createElement('div'); const nextContainer = document.createElement('div'); const wrapper = document.createElement('div'); const link = document.createElement('a'); const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); // const svgLink = document.createElement('a'); const svgLink = document.createElementNS('http://www.w3.org/2000/svg', 'a'); const svgXlink = document.createElementNS('http://www.w3.org/2000/svg', 'a'); svg.appendChild(svgLink); svg.appendChild(svgXlink); currentContainer.setAttribute(attr.prefix, attr.container); currentContainer.setAttribute(`${attr.prefix}-${attr.namespace}`, namespace); wrapper.setAttribute(attr.prefix, attr.wrapper); // Expected const checkDoc = new RegExp( // tslint:disable-next-line:max-line-length `^[\\s\\S]+body[\\s\\S]+${dom['_attr'].wrapper}[\\s\\S]+${dom['_attr'].container}[\\s\\S]+${namespace}[\\s\\S]+$` ); const checkHref = 'http://localhost/page.html'; afterEach(() => { wrapper.innerHTML = ''; document.body.innerHTML = ''; }); it('has attributeSchema', () => { expect(dom['_attr']).toBe(schemaAttribute); }); it('stringifies DOM', () => { expect(typeof dom.toString(currentContainer)).toBe('string'); }); // see https://github.com/barbajs/barba/issues/362 // it('parses string to Doc', () => { // expect(dom.toDocument('
') instanceof HTMLDocument).toBeTruthy(); // }); it('parses string to Element', () => { expect(dom.toElement('
') instanceof HTMLDivElement).toBeTruthy(); }); it('get html', () => { wrapper.appendChild(currentContainer); document.body.appendChild(wrapper); expect(dom.getHtml()).toMatch(checkDoc); expect(dom.getHtml(document)).toMatch(checkDoc); }); it('get wrapper', () => { expect(dom.getWrapper()).toBeNull(); wrapper.appendChild(currentContainer); document.body.appendChild(wrapper); expect(dom.getWrapper()).toBe(wrapper); expect(dom.getWrapper(document.body)).toBe(wrapper); }); it('get container', () => { expect(dom.getContainer()).toBeNull(); wrapper.appendChild(currentContainer); document.body.appendChild(wrapper); expect(dom.getContainer()).toBe(currentContainer); expect(dom.getContainer(wrapper)).toBe(currentContainer); }); it('get namespace', () => { expect(dom.getNamespace()).toBeNull(); wrapper.appendChild(currentContainer); document.body.appendChild(wrapper); expect(dom.getNamespace()).toBe(namespace); expect(dom.getNamespace(wrapper)).toBe(namespace); }); it('get href [link / absolute]', () => { link.setAttribute('href', 'http://localhost/page.html'); expect(dom.getHref(link)).toBe(checkHref); }); it('get href [link / relative]', () => { link.setAttribute('href', 'page.html'); expect(dom.getHref(link)).toBe(checkHref); }); it('get href [svg / absolute]', () => { const val = 'http://localhost/page.html'; svgLink.setAttribute('href', val); svgXlink.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', val); expect(dom.getHref(svgLink)).toBe(checkHref); expect(dom.getHref(svgXlink)).toBe(checkHref); }); it('get href [svg / absolute no protocol]', () => { const val = '//localhost/page.html'; svgLink.setAttribute('href', val); svgXlink.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', val); expect(dom.getHref(svgLink)).toBe(checkHref); expect(dom.getHref(svgXlink)).toBe(checkHref); }); it('get href [svg / relative root]', () => { const val = '/page.html'; svgLink.setAttribute('href', val); svgXlink.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', val); expect(dom.getHref(svgLink)).toBe(checkHref); expect(dom.getHref(svgXlink)).toBe(checkHref); }); it('get href [svg / relative simple]', () => { const val = 'page.html'; svgLink.setAttribute('href', val); svgXlink.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', val); expect(dom.getHref(svgLink)).toBe(checkHref); expect(dom.getHref(svgXlink)).toBe(checkHref); }); it('get href [svg / relative same]', () => { const val = './page.html'; svgLink.setAttribute('href', val); svgXlink.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', val); expect(dom.getHref(svgLink)).toBe(checkHref); expect(dom.getHref(svgXlink)).toBe(checkHref); }); it('get href [svg / relative level]', () => { (global as any).jsdom.reconfigure({ url: 'http://localhost/foo/' }); const val = '../page.html'; svgLink.setAttribute('href', val); svgXlink.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', val); expect(dom.getHref(svgLink)).toBe(checkHref); expect(dom.getHref(svgXlink)).toBe(checkHref); }); it('get href [svg / relative level]', () => { const url = 'http://localhost/foo.html'; const val = '#hash'; (global as any).jsdom.reconfigure({ url }); svgLink.setAttribute('href', val); svgXlink.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', val); expect(dom.getHref(svgLink)).toBe(url + val); expect(dom.getHref(svgXlink)).toBe(url + val); }); it('remove container', () => { document.body.appendChild(wrapper); dom.removeContainer(currentContainer); expect(wrapper.children.length).toBe(0); }); it('add container', () => { wrapper.appendChild(currentContainer); document.body.appendChild(wrapper); dom.addContainer(nextContainer, wrapper); dom.removeContainer(currentContainer); expect(wrapper.children.length).toBe(1); }); it('add container with container sibling', () => { wrapper.appendChild(currentContainer); document.body.appendChild(wrapper); dom.addContainer(nextContainer, wrapper); expect(wrapper.children.length).toBe(2); expect(nextContainer.previousElementSibling).toBe(currentContainer); expect(dom.getContainer()).toBe(currentContainer); }); it('add container with before sibling', () => { const before = document.createElement('div'); wrapper.appendChild(before); wrapper.appendChild(currentContainer); document.body.appendChild(wrapper); dom.removeContainer(currentContainer); dom.addContainer(nextContainer, wrapper); expect(wrapper.children.length).toBe(2); expect(nextContainer.previousElementSibling).toBe(before); expect(dom.getSibling().before).not.toBeNull(); }); it('add container with after sibling', () => { const after = document.createElement('div'); wrapper.appendChild(currentContainer); wrapper.appendChild(after); document.body.appendChild(wrapper); dom.removeContainer(currentContainer); dom.addContainer(nextContainer, wrapper); expect(wrapper.children.length).toBe(2); expect(nextContainer.nextElementSibling).toBe(after); expect(dom.getSibling().after).not.toBeNull(); }); it('add container with parent sibling', () => { const parent = document.createElement('div'); parent.appendChild(currentContainer); wrapper.appendChild(parent); document.body.appendChild(wrapper); dom.removeContainer(currentContainer); dom.addContainer(nextContainer, wrapper); expect(wrapper.children.length).toBe(1); expect(nextContainer.parentNode).toBe(parent); expect(dom.getSibling().parent).not.toBeNull(); }); ================================================ FILE: packages/core/__tests__/utils/history.test.ts ================================================ import { history } from '../../src/utils/history'; const first = { data: {}, ns: 'ns1', scroll: { x: 0, y: 0, }, url: 'url1', }; const second = { data: {}, ns: 'ns2', scroll: { x: 0, y: 0, }, url: 'url2', }; const tmp = { ...second, ns: 'tmp', }; const triggerPush = document.createElement('a'); const triggerReplace = document.createElement('a'); triggerReplace.dataset.barbaHistory = 'replace'; const e = { state: { index: 1, }, } as PopStateEvent; const h = { b: (global as any).window.history.back = jest.fn(), ps: (global as any).window.history.pushState = jest.fn(), rs: (global as any).window.history.replaceState = jest.fn(), }; const data = { custom: 'data', }; afterEach(() => { history.clear(); }); it('has no history', () => { expect(history.current).toBeUndefined(); }); it('init state and has current', () => { history.init(first.url, first.ns); expect(history.current).toEqual(first); expect(history.previous).toBeNull(); expect(h.rs).toHaveBeenCalledTimes(1); }); it('adds state and has previous', () => { history.init(first.url, first.ns); history.change(second.url, 'barba'); expect(history.current).toEqual(tmp); expect(history.previous).toEqual(first); }); it('pushes history', () => { history.change(first.url, triggerPush); history.change(second.url, triggerReplace); history.change(second.url, 'popstate', e); expect(h.ps).toHaveBeenCalledTimes(1); }); it('replaces history', () => { history.change(first.url, triggerReplace); history.change(second.url, triggerPush); history.change(second.url, 'popstate', e); expect(h.rs).toHaveBeenCalledTimes(1); }); it('removes state', () => { history.init(first.url, first.ns); history.change(second.url, 'barba'); history.remove(); expect(history.current).toEqual(first); expect(history.previous).toBeNull(); history.change(second.url, 'barba'); history.remove(1); expect(history.current).toEqual(first); expect(history.previous).toBeNull(); }); it('gets state(s)', () => { history.init(first.url, first.ns); history.change(second.url, 'barba'); const state1 = history.get(0); const state2 = history.get(1); const state = history.current; expect(state1).toEqual(first); expect(state2).toEqual(tmp); expect(state).toEqual(tmp); }); it('gets directions', () => { history.init(first.url, first.ns); history.change(second.url, 'barba'); const back = history.change(first.url, 'popstate', { state: { index: 0 }, } as PopStateEvent); expect(back).toEqual('back'); const forward = history.change(second.url, 'popstate', { state: { index: 1 }, } as PopStateEvent); expect(forward).toEqual('forward'); const prev = history.change(second.url, 'popstate', { state: { index: 6 }, } as PopStateEvent); expect(prev).toEqual('back'); const next = history.change(second.url, 'popstate', { state: { index: 0 }, } as PopStateEvent); expect(next).toEqual('forward'); }); it('manage history with "unknown" state', async () => { history.change(second.url, 'barba'); expect(h.rs).toHaveBeenCalledTimes(0); expect(h.ps).toHaveBeenCalledTimes(1); }); it('manage history with previous state', async () => { history.change(second.url, 'popstate', e); expect(h.ps).toHaveBeenCalledTimes(0); expect(h.rs).toHaveBeenCalledTimes(0); }); it('manage history with data-barba-history="replace"', async () => { const link = document.createElement('a'); link.dataset.barbaHistory = 'replace'; history.add(second.url, link); expect(h.ps).toHaveBeenCalledTimes(0); expect(h.rs).toHaveBeenCalledTimes(1); }); it('manage history with programmatic push', async () => { history.add(first.url, 'barba', 'push'); expect(h.ps).toHaveBeenCalledTimes(1); expect(h.rs).toHaveBeenCalledTimes(0); }); it('manage history with programmatic replace', async () => { history.add(first.url, 'barba', 'replace'); expect(h.ps).toHaveBeenCalledTimes(0); expect(h.rs).toHaveBeenCalledTimes(1); }); it('manage history with programmatic push and custom data', async () => { history.add(first.url, 'barba', 'push', data); expect(history.current.data).toEqual(data); }); it('manage history with programmatic replace and custom data', async () => { history.add(first.url, 'barba', 'replace', data); expect(history.current.data).toEqual(data); }); it('store custom user data', async () => { history.init(first.url, first.ns); history.store(data); expect(history.current.data).toEqual(data); }); it('store custom user data per state', async () => { const state1 = { state: 1, }; const state2 = { state: 2, }; history.init(first.url, first.ns); history.store(state1); history.change(second.url, 'barba'); history.store(state2); expect(history.previous.data).toEqual(state1); expect(history.current.data).toEqual(state2); }); it('merge custom user data with existing one', async () => { const update = { additional: 'data', }; history.init(first.url, first.ns); history.store(data); history.store(update); expect(history.current.data).toEqual({ ...data, ...update, }); }); ================================================ FILE: packages/core/__tests__/utils/request.test.ts ================================================ import xhrMock from 'xhr-mock'; import { init } from '../../__mocks__/barba'; import barba from '../../src'; import { IResponse, IUrlFull } from '../../src/defs'; import { request } from '../../src/utils'; import { parse } from '../../src/utils/url'; init(); (global as any).Headers = class {}; (global as any).window.clearTimeout = jest.fn(); const requestError = jest.fn(); const url = 'url'; const content = 'content'; const response = { html: content, url: { href: url, ...parse(url) } as IUrlFull, } as IResponse; beforeEach(() => { xhrMock.setup(); }); afterEach(() => { delete (global as any).navigator.connection; xhrMock.teardown(); }); it('set correct headers', async () => { expect.assertions(2); xhrMock.get(url, (req, res) => { expect(req.header('Accept')).toEqual( 'text/html,application/xhtml+xml,application/xml' ); expect(req.header('x-barba')).toEqual('yes'); return res.status(200); }); await request(url, 2e3, requestError, barba.cache, barba.headers); }); it('set custom request headers', async () => { barba.headers.set('x-custom-header', 'custom-value'); xhrMock.get(url, (req, res) => { expect(req.header('x-custom-header')).toEqual('custom-value'); return res.status(200); }); await request(url, 2e3, requestError, barba.cache, barba.headers); }); it('throws fetch error', async () => { const error = new Error('Fetch error'); xhrMock.get(url, () => Promise.reject(error)); xhrMock.error(() => {}); // tslint:disable-line:no-empty await expect(request(url, 2e3, requestError, barba.cache, barba.headers)).rejects.toEqual(error); expect(requestError).toHaveBeenCalledWith(url, error); expect(barba.cache.getStatus(url)).toEqual('rejected'); }); it('throws result error with 404', async () => { const error = { status: 404, statusText: 'Not found', }; xhrMock.get(url, (req, res) => res.status(404).reason('Not found')); await expect(request(url, 2e3, requestError, barba.cache, barba.headers)).rejects.toEqual(error); expect(requestError).toHaveBeenCalledWith(url, error); expect(barba.cache.getStatus(url)).toEqual('rejected'); }); it('throws timeout error', async () => { const error = new Error('Timeout error [100]'); xhrMock.get(url, () => new Promise(() => {})); // tslint:disable-line:no-empty await expect(request(url, 100, requestError, barba.cache, barba.headers)).rejects.toEqual(error); expect(requestError).toHaveBeenCalledWith(url, error); expect(barba.cache.getStatus(url)).toEqual('rejected'); }, 1000); it('fetch text content', async () => { xhrMock.get(url, (req, res) => res.status(200).body(content)); await expect(request(url, undefined, requestError, barba.cache, barba.headers)).resolves.toStrictEqual(response); // expect((global as any).window.clearTimeout).toHaveBeenCalledTimes(1); expect(requestError).not.toHaveBeenCalled(); expect(barba.cache.getStatus(url)).toEqual('fulfilled'); }); // it('throws bad connection error', async () => { // const error = new Error('Bad connection or reduced data usage mode'); // global.navigator.connection = { // effectiveType: '2g', // saveData: false, // }; // await expect(request(url, 2e3, requestError)).rejects.toEqual(error); // }); // it('throws reduced data error', async () => { // const error = new Error('Bad connection or reduced data usage mode'); // global.navigator.connection = { // effectiveType: '3g', // saveData: true, // }; // await expect(request(url, 2e3, requestError)).rejects.toEqual(error); // }); ================================================ FILE: packages/core/__tests__/utils/url.test.ts ================================================ /* global jsdom */ import { url } from '../../src/utils'; it('get href', () => { const href = url.getHref(); expect(href).toBe(window.location.href); }); it('get absolute href', () => { const href1 = url.getAbsoluteHref('/foo.html'); const href2 = url.getAbsoluteHref('/foo.html', 'https://barba.js.org'); expect(href1).toBe('http://localhost/foo.html'); expect(href2).toBe('https://barba.js.org/foo.html'); }); it('get origin', () => { const origin = url.getOrigin(); expect(origin).toBe(window.location.origin); }); it('get port', () => { expect(url.getPort()).toBe(80); (global as any).jsdom.reconfigure({ url: 'https://localhost/' }); expect(url.getPort()).toBe(443); (global as any).jsdom.reconfigure({ url: 'https://localhost:6666/' }); expect(url.getPort()).toBe(6666); (global as any).jsdom.reconfigure({ url: 'http://localhost/' }); }); it('get path', () => { expect(url.getPath()).toBe('/'); (global as any).jsdom.reconfigure({ url: 'http://localhost/qux.html?foo=bar#hash', }); expect(url.getPath()).toBe('/qux.html'); const path = url.getPath('http://localhost/foo.html?foo=bar#hash'); expect(path).toBe('/foo.html'); }); it('get query', () => { const query = url.getQuery('http://localhost/foo.html?foo=bar#hash'); const queryAsString = url.getQuery('http://localhost/foo.html?foo=bar#hash', true); expect(query).toEqual({ foo: 'bar' }); expect(queryAsString).toEqual('{"foo":"bar"}'); }); it('get hash', () => { const hash = url.getHash('http://localhost/foo.html?foo=bar#hash'); expect(hash).toBe('hash'); }); it('parse minimal path', () => { const { path, query, hash } = url.parse('/'); expect(path).toBe('/'); expect(query).toEqual({}); expect(hash).toBeUndefined(); }); it('parse full path', () => { const { path, query, hash } = url.parse('/foo?baz=qux#bar'); expect(path).toBe('/foo'); expect(query).toEqual({ baz: 'qux' }); expect(hash).toBe('bar'); }); it('parse simple query', () => { const query = url.parseQuery('foo=bar'); expect(query).toEqual({ foo: 'bar' }); }); it('parse complex query', () => { const query = url.parseQuery('foo=bar&baz=qux'); expect(query).toEqual({ foo: 'bar', baz: 'qux' }); }); it('clean url', () => { // const result = url.clean('http://localhost/foo', window.location.origin); // expect(result).toBe('/foo'); const result = url.clean('http://localhost/foo?bar=baz#qux'); expect(result).toBe('http://localhost/foo?bar=baz'); }); ================================================ FILE: packages/core/__web__/container.html ================================================ container
    sibling element
    ================================================ FILE: packages/core/__web__/href.html ================================================ href ================================================ FILE: packages/core/__web__/index.html ================================================ home ================================================ FILE: packages/core/__web__/page.html ================================================ page

    page

    Go to home
    ================================================ FILE: packages/core/__web__/scripts/container.js ================================================ /* eslint-disable no-empty-function */ console.info('🚀 Barba e2e'); const { barba } = window; barba.init({ debug: true, transitions: [ { leave() {}, enter() {}, }, { sync: true, custom: ({ trigger }) => trigger.dataset && trigger.dataset.barbaTest === 'link.sync', leave() {}, enter() {}, }, ], }); ================================================ FILE: packages/core/__web__/scripts/default.js ================================================ console.info('🚀 Barba e2e'); import { hooks, hooksSync } from './transitions/hooks.js'; const { barba } = window; barba.init({ debug: true, transitions: [hooks, hooksSync], }); ================================================ FILE: packages/core/__web__/scripts/href.js ================================================ /* eslint-disable no-empty-function */ console.info('🚀 Barba e2e'); const { barba } = window; barba.init({ debug: true, transitions: [ { leave() {}, enter() {}, }, ], }); ================================================ FILE: packages/core/__web__/scripts/transitions/hooks.js ================================================ /* eslint-disable newline-before-return */ const { barba } = window; const list = document.querySelector('[data-test="hooks-list"]'); /** * Append list item * * @param {*} str List item content * @param {string} prefix Prefix for global * @returns {void} */ function append(str, prefix = '') { const item = document.createElement('li'); item.textContent = prefix + str; list.appendChild(item); } /** * Promisify hooks * * @returns {Promise} Promise */ function sleep() { return new Promise(resolve => { setTimeout(resolve, 100); }); } export const base = { beforeOnce() { append('beforeOnce'); return sleep(); }, once() { append('once'); return sleep(); }, afterOnce() { append('afterOnce'); return sleep(); }, before() { append('before'); return sleep(); }, beforeLeave() { append('beforeLeave'); return sleep(); }, leave() { append('leave'); return sleep(); }, afterLeave() { append('afterLeave'); return sleep(); }, beforeEnter() { append('beforeEnter'); return sleep(); }, enter() { append('enter'); return sleep(); }, afterEnter() { append('afterEnter'); return sleep(); }, after() { append('after'); return sleep(); }, }; barba.hooks.beforeOnce(() => { append('beforeOnce', 'global:'); }); barba.hooks.once(() => { append('once', 'global:'); }); barba.hooks.afterOnce(() => { append('afterOnce', 'global:'); }); barba.hooks.before(() => { append('before', 'global:'); }); barba.hooks.beforeLeave(() => { append('beforeLeave', 'global:'); }); barba.hooks.leave(() => { append('leave', 'global:'); }); barba.hooks.afterLeave(() => { append('afterLeave', 'global:'); }); barba.hooks.beforeEnter(() => { append('beforeEnter', 'global:'); }); barba.hooks.enter(() => { append('enter', 'global:'); }); barba.hooks.afterEnter(() => { append('afterEnter', 'global:'); }); barba.hooks.after(() => { append('after', 'global:'); }); export const hooks = { ...base, custom: ({ trigger }) => trigger === 'barba' || (trigger.dataset && trigger.dataset.test === 'link.hooks'), }; export const hooksSync = { ...base, sync: true, custom: ({ trigger }) => trigger.dataset && trigger.dataset.test === 'link.hooks-sync', }; ================================================ FILE: packages/core/__web__/scripts/views/hooks.js ================================================ /* eslint-disable newline-before-return */ const list = document.querySelector('[data-test="hooks-list"]'); /** * Append list item * * @param {*} str List item content * @param {string} prefix Prefix for global * @returns {void} */ function append(str, prefix = '') { const item = document.createElement('li'); item.textContent = prefix + str; list.appendChild(item); } export const home = { namespace: 'home', beforeLeave() { append('beforeLeave'); }, afterLeave() { append('afterLeave'); }, beforeEnter() { append('beforeEnter'); }, afterEnter() { append('afterEnter'); }, }; export const page = { namespace: 'page', beforeLeave() { append('beforeLeave'); }, afterLeave() { append('afterLeave'); }, beforeEnter() { append('beforeEnter'); }, afterEnter() { append('afterEnter'); }, }; ================================================ FILE: packages/core/__web__/scripts/views.js ================================================ console.info('🚀 Barba e2e'); import { home, page } from './views/hooks.js'; const { barba } = window; // const list = document.querySelector('[data-test="hooks-list"]'); // /** // * Append list item // * // * @param {*} str List item content // * @param {string} prefix Prefix for global // * @returns {void} // */ // function append(str, prefix = '') { // const item = document.createElement('li'); // item.textContent = prefix + str; // list.appendChild(item); // } // barba.hooks.beforeEnter(() => { // console.info('global.beforeEnter'); // append('global.beforeEnter'); // }); // barba.hooks.afterEnter(() => { // console.info('global.afterEnter'); // append('global.afterEnter'); // }); barba.init({ debug: true, views: [home, page], }); ================================================ FILE: packages/core/__web__/views.html ================================================ home ================================================ FILE: packages/core/jest.config.js ================================================ const jestBase = require('../../jest.config.js'); module.exports = { ...jestBase, }; ================================================ FILE: packages/core/package.json ================================================ { "name": "@barba/core", "version": "2.10.3", "description": "Create badass, fluid and smooth transition between your website's pages", "publishConfig": { "access": "public" }, "main": "dist/barba.js", "umd:main": "dist/barba.umd.js", "browser": "dist/barba.umd.js", "unpkg": "dist/barba.umd.js", "module": "dist/barba.mjs", "source": "src/index.ts", "types": "dist/core/src/typings", "mangle": { "regex": "^_" }, "files": [ "dist" ], "keywords": [ "page", "transition", "animation", "css", "router", "prefetch" ], "homepage": "https://github.com/barbajs/barba#readme", "bugs": { "url": "https://github.com/barbajs/barba/issues" }, "license": "MIT", "repository": { "type": "git", "url": "git+ssh://git@github.com/barbajs/barba.git" }, "scripts": { "build": "microbundle --name barba --external natives", "build:watch": "microbundle watch --name barba --external natives", "clear": "rimraf dist", "lint": "tslint src/**", "precommit": "lint-staged", "report": "source-map-explorer --html ./dist/barba.umd.js > report.html", "size": "echo '📦 router' && gzip-size ./dist/barba.umd.js", "tag:latest": "npm dist-tag add @barba/core@$npm_package_version latest", "tag:next": "npm dist-tag add @barba/core@$npm_package_version next" }, "dependencies": { "is-promise": "^4.0.0", "path-to-regexp": "^6.2.2" }, "gitHead": "33c213bc36a0996f6333185dfc695fcd0d37c9d9" } ================================================ FILE: packages/core/src/core.ts ================================================ /** * @barba/core *

    * ## Barba core object * * Main methods: * * - `.init()` for initialization with options * - `.use()` for plugins * * @module core */ /***/ import { version } from '../package.json'; // Definitions import { IBarbaOptions, IBarbaPlugin, IgnoreOption, ISchemaPage, ITransitionData, ITransitionOnce, ITransitionPage, Link, LinkEvent, RequestCustomError, RequestErrorOrResponse, SchemaAttributeValues, Trigger, Wrapper, } from './defs'; // Hooks import { hooks } from './hooks'; // Modules import { Cache } from './modules/Cache'; import { Headers } from './modules/Headers'; import { Logger } from './modules/Logger'; import { Prevent } from './modules/Prevent'; import { Transitions } from './modules/Transitions'; import { Views } from './modules/Views'; // Polyfills import './polyfills'; // Schemas import { schemaAttribute } from './schemas/attribute'; import { schemaPage } from './schemas/page'; // Utils import { dom, helpers, history, request, url } from './utils'; export class Core { /** * Version. */ public version: string = version; /** * Schemas. */ public schemaPage: ISchemaPage = schemaPage; /** * Logger class, allows plugins to create Logger. */ public Logger: typeof Logger = Logger; /** * Barba logger. */ public logger: Logger = new Logger('@barba/core'); /** * Plugins. */ public plugins: IBarbaPlugin[] = []; /** * Options */ public timeout: number; public cacheIgnore: IgnoreOption; public cacheFirstPage: boolean; public prefetchIgnore: IgnoreOption; public preventRunning: boolean; /** * Hooks */ public hooks = hooks; /** * Modules. */ // public history: History; public cache: Cache; public headers: Headers; public prevent: Prevent; public transitions: Transitions; public views: Views; /** * Utils. */ public dom = dom; public helpers = helpers; public history = history; public request = request; public url = url; private _data: ITransitionData; private _requestCustomError: RequestCustomError; private _wrapper: Wrapper; private _linkEvent: LinkEvent; /** * ### Init plugin with options. * * See [[IBarbaPlugin]] for more details. */ public use(plugin: IBarbaPlugin, options?: T): void { const installedPlugins = this.plugins; // Plugin installation if (installedPlugins.indexOf(plugin) > -1) { this.logger.warn(`Plugin [${plugin.name}] already installed.`); return; } if (typeof plugin.install !== 'function') { this.logger.warn(`Plugin [${plugin.name}] has no "install" method.`); return; } plugin.install(this, options); installedPlugins.push(plugin); } /** * ### Init barba with options. * * See [[IBarbaOptions]] for more details. * * Default values are: * * - transitions: `[]` * - views: `[]` * - schema: [[SchemaAttribute]] * - timeout: `2e3` * - cacheIgnore: `false` * - cacheFirstPage: `false` * - prefetchIgnore: `false` * - preventRunning: `false` * - prevent: `null`, * - debug: `false` * - logLevel: `'off'` */ public init( /** @ignore */ { transitions = [], views = [], schema = schemaAttribute, requestError, timeout = 2e3, cacheIgnore = false, cacheFirstPage = false, prefetchIgnore = false, /* istanbul ignore next */ preventRunning = false, prevent: preventCustom = null, debug = false, logLevel = 'off', }: IBarbaOptions = {} ) { // 0. Set logger level and print version Logger.setLevel(debug === true ? 'debug' : logLevel); this.logger.info(this.version); // 1. Manage options Object.keys(schema).forEach(k => { const attr = k as SchemaAttributeValues; /* istanbul ignore else */ if (schemaAttribute[attr]) { schemaAttribute[attr] = schema[attr]; } }); this._requestCustomError = requestError; this.timeout = timeout; this.cacheIgnore = cacheIgnore; this.cacheFirstPage = cacheFirstPage; this.prefetchIgnore = prefetchIgnore; this.preventRunning = preventRunning; // 2. Get and check wrapper this._wrapper = this.dom.getWrapper(); if (!this._wrapper) { throw new Error('[@barba/core] No Barba wrapper found'); } // 3. Init pages (get "current" data) this._resetData(); const { current } = this.data; if (!current.container) { throw new Error('[@barba/core] No Barba container found'); } // 4. Init other modules this.cache = new Cache(cacheIgnore); this.headers = new Headers(); this.prevent = new Prevent(prefetchIgnore); this.transitions = new Transitions(transitions); this.views = new Views(views); // Add prevent custom if (preventCustom !== null) { if (typeof preventCustom !== 'function') { throw new Error('[@barba/core] Prevent should be a function'); } this.prevent.add('preventCustom', preventCustom); } // 5. Init history this.history.init(current.url.href, current.namespace); // 6. Add to cache if (cacheFirstPage) { this.cache.set(current.url.href, Promise.resolve({ html: current.html, url: current.url, }), 'init', 'fulfilled'); } // 7. Bind context this._onLinkEnter = this._onLinkEnter.bind(this); this._onLinkClick = this._onLinkClick.bind(this); this._onStateChange = this._onStateChange.bind(this); this._bind(); // 8. Init plugins this.plugins.forEach(plugin => plugin.init()); // 9. Barba ready // Set next + trigger for once and `beforeEnter`/`afterEnter` view on page load. const onceData = this.data; onceData.trigger = 'barba'; onceData.next = onceData.current; onceData.current = { ...this.schemaPage }; this.hooks.do('ready', onceData); // 9. Finally, do once… this.once(onceData); // Clean data for first barba transition… this._resetData(); } public destroy(): void { this._resetData(); this._unbind(); this.history.clear(); this.hooks.clear(); this.plugins = []; } get data(): ITransitionData { return this._data; } get wrapper(): HTMLElement { return this._wrapper; } /** * ### Force a page change without Barba transition. */ public force(href: string): void { // DEV // Can be used waiting animation cancellation management… window.location.assign(href); } /** * ### Go for a Barba transition. * * Manage "self page" href: * * - if same url and no self transition, keep default behavior * - link: reload the page * - anchor: scroll to * - if same url with self transition, use it * - then start a page transition. */ public go( href: string, trigger: Trigger = 'barba', e?: LinkEvent | PopStateEvent ): Promise { this._linkEvent = null; // If animation running, force reload if (this.transitions.isRunning) { this.force(href); return; } let self = false; // Check prevent sameURL against current history // + state check // + update trigger with direction if (trigger === 'popstate') { self = this.history.current && this.url.getPath(this.history.current.url) === this.url.getPath(href) && this.url.getQuery(this.history.current.url, true) === this.url.getQuery(href, true); } else { self = this.prevent.run('sameUrl', null, null, href); } if (self && !this.transitions.hasSelf) { return; } trigger = this.history.change(this.cache.has(href) ? this.cache.get(href).target : href, trigger, e); if (e) { e.stopPropagation(); e.preventDefault(); } return this.page(href, trigger, e ?? undefined, self); } /** * ### Start an "once" transition. * * If some registered "once" transition, * get the "resolved" transition from the store and start it. */ public async once(readyData: ITransitionData): Promise { await this.hooks.do('beforeEnter', readyData); // Check if once transition if (this.transitions.hasOnce) { const transition = this.transitions.get(readyData, { once: true, }) as ITransitionOnce; await this.transitions.doOnce({ transition, data: readyData }); } await this.hooks.do('afterEnter', readyData); } /** * ### Start a "page" transition. * * 1. If no running transition, updates data with full URL properties and trigger. * 2. Get page from cache or init request. * 3. Wait if some transitions need "next" data (`sync: true`, `to: …`). * 4. Manage the history, depending on trigger. * 5. Get "data" and trigger "go" hook. * 6. Get the "resolved" transition from the store and start it. * 7. Update title and reset data (current, next = undefined). * * > If "self", use the "self" transition */ public async page( href: string, trigger: Trigger, event: LinkEvent | PopStateEvent, self: boolean ): Promise { this.data.next.url = { href, ...this.url.parse(href), }; this.data.trigger = trigger; this.data.event = event; let page; if (this.cache.has(href)) { page = this.cache.update(href, { action: 'click' }).request; } else { const pageRequest = this.request( href, this.timeout, this.onRequestError.bind(this, trigger), this.cache, this.headers ); // manage 301 server response: replace history pageRequest.then(response => { /* istanbul ignore next: bypass jest since xhr-mock doesn't support custom xhr.responseURL */ if (response.url.href !== href) { this.history.add(response.url.href, trigger, 'replace'); } }); page = this.cache.set(href, pageRequest, 'click', 'pending').request; } // Need to wait before getting the right transition if (this.transitions.shouldWait) { await helpers.update(page, this.data); } const data = this.data; // Hook: between trigger and transition // Can be used to resolve "route"… await this.hooks.do('page', data); try { const transition = this.transitions.get(data, { once: false, self, }) as ITransitionPage; await this.transitions.doPage({ data, page, transition, wrapper: this._wrapper, }); this._resetData(); } catch (error) { // Something went wrong (rejected promise, error, 404, 505, other…) // TODO: manage / use cases for cancellation // this.logger.debug('Transition cancelled'); // If transition error and no debug mode, force reload page. /* istanbul ignore else */ if (Logger.getLevel() === 0) { this.force(data.next.url.href); } } } /** * When a request error occurs. * * Allow the user to manage request error. (E.g: 404) */ public onRequestError(trigger: Trigger, ...args: any): boolean { // Cancel transition status this.transitions.isRunning = false; const [href, response]: [string, RequestErrorOrResponse] = args; const action = this.cache.getAction(href); this.cache.delete(href); // Custom requestError returning false will return here. if ( this._requestCustomError && this._requestCustomError(trigger, action, href, response) === false ) { return false; } // Force page change if (action === 'click') { this.force(href); } return false; } /** * Programmatically prefetch */ public prefetch(href: string) { // only prefetch absolute href href = this.url.getAbsoluteHref(href); // Already in cache /* istanbul ignore next */ if (this.cache.has(href)) { return; } this.cache.set( href, this.request( href, this.timeout, this.onRequestError.bind(this, 'barba'), this.cache, this.headers ).catch((error: RequestErrorOrResponse) => { this.logger.error(error); }), 'prefetch', 'pending' ); } /** * Bind event listeners. */ private _bind(): void { /* istanbul ignore else */ if (this.prefetchIgnore !== true) { document.addEventListener('mouseover', this._onLinkEnter); document.addEventListener('touchstart', this._onLinkEnter); } document.addEventListener('click', this._onLinkClick); window.addEventListener('popstate', this._onStateChange); } /** * Bind event listeners. */ private _unbind(): void { /* istanbul ignore else */ if (this.prefetchIgnore !== true) { document.removeEventListener('mouseover', this._onLinkEnter); document.removeEventListener('touchstart', this._onLinkEnter); } document.removeEventListener('click', this._onLinkClick); window.removeEventListener('popstate', this._onStateChange); } /** * When a element is entered. * * Get valid link element. * Cache URL if needed. */ private _onLinkEnter(e: LinkEvent): void { const link = this._getLinkElement(e); if (!link) { return; } const href = this.url.getAbsoluteHref(this.dom.getHref(link)); if (this.prevent.checkHref(href)) { return; } // Already in cache if (this.cache.has(href)) { return; } this.cache.set( href, this.request( href, this.timeout, this.onRequestError.bind(this, link), this.cache, this.headers ).catch((error: RequestErrorOrResponse) => { this.logger.error(error); }), 'enter', 'pending' ); } /** * When an element is clicked. * * Get valid link element. * Prevent same URL. * Go for a Barba transition. */ private _onLinkClick(e: LinkEvent): void { // This use `prevent.checkLink` under the hood to get eligible link. const link = this._getLinkElement(e); if (!link) { return; } if (this.transitions.isRunning && this.preventRunning) { e.preventDefault(); e.stopPropagation(); return; } this._linkEvent = e; this.go(this.dom.getHref(link), link, e); } /** * When History state changes. * * Get "href" from URL * Go for a Barba transition. */ private _onStateChange(e: PopStateEvent): void { this.go(this.url.getHref(), 'popstate', e); } /** * Get a valid link ancestor. * * Check for a "href" attribute. * Then check if eligible for Barba. */ private _getLinkElement(e: LinkEvent): Link { let el = e.target as Link; while (el && !this.dom.getHref(el)) { el = (el as HTMLElement).parentNode as Link; } // Check prevent if (!el || this.prevent.checkLink(el, e, this.dom.getHref(el))) { return; } return el; } /** * Reset pages data. * * Set "current" and unset "next". */ private _resetData() { const href = this.url.getHref(); const current = { container: this.dom.getContainer(), html: this.dom.getHtml(), namespace: this.dom.getNamespace(), url: { href, ...this.url.parse(href), }, }; this._data = { current, event: undefined, next: { ...this.schemaPage }, trigger: undefined, }; this.hooks.do('reset', this.data); } } const core = new Core(); export default core; ================================================ FILE: packages/core/src/defs/barba.ts ================================================ /** * @module typings/core */ // Core import { Core } from '../core'; // Modules import { LogLevels } from '../modules/Logger'; // Definitions import { IgnoreOption, ISchemaAttribute, ITransitionPage, IView, PreventCheck, RequestCustomError, } from './index'; export interface IBarbaOptions { /** Array of transitions. */ transitions?: ITransitionPage[]; /** Array of views. */ views?: IView[]; /** Request timeout. */ timeout?: number; /** Custom request error. */ requestError?: RequestCustomError | undefined; /** Disable cache or ignore some routes. */ cacheIgnore?: IgnoreOption; /** Disable cache on the first rendered page. */ cacheFirstPage?: boolean; /** Disable prefetch or ignore routes. */ prefetchIgnore?: IgnoreOption; /** Custom prevent check. */ prevent?: PreventCheck | null; /** Prevent click when transition is running. */ preventRunning?: boolean; /** Custom [data-attribute]. */ schema?: ISchemaAttribute; /** Enable debug mode. */ debug?: boolean; /** Log level. */ logLevel?: keyof typeof LogLevels; } export interface IBarbaPlugin { /** Plugin version */ version: string; /** Plugin name */ name: string; /** Install method */ install(barba: Core, options?: T): void; /** Init method */ init(): void; } ================================================ FILE: packages/core/src/defs/cache.ts ================================================ /** * @module typings/core */ import { IResponse } from './index'; export interface ICacheData { action?: CacheAction; request?: CacheRequest; status?: CacheStatus; target?: CacheTarget; } export type CacheRequest = Promise; export type CacheAction = 'init' | 'enter' | 'click' | 'prefetch'; export type CacheStatus = 'pending' | 'fulfilled' | 'rejected'; export type CacheTarget = string; ================================================ FILE: packages/core/src/defs/dom.ts ================================================ /** * @module typings/core */ export type Link = HTMLAnchorElement | SVGAElement; export type LinkEvent = MouseEvent | TouchEvent; export type Scope = HTMLElement | HTMLDocument; export type Trigger = Link | 'barba' | 'popstate' | 'back' | 'forward'; export type Wrapper = HTMLElement | null; export interface IDomSibling { before?: Element; after?: Element; parent?: Element; } ================================================ FILE: packages/core/src/defs/global.ts ================================================ /** * @module typings/core */ export interface IGenericObject { [key: string]: string; } ================================================ FILE: packages/core/src/defs/headers.ts ================================================ /** * @module typings/core */ export type HeaderList = Map; export interface IHeaderData { name?: string; value?: string; } ================================================ FILE: packages/core/src/defs/history.ts ================================================ /** * @module typings/history */ export type HistoryAction = 'push' | 'replace'; ================================================ FILE: packages/core/src/defs/hooks.ts ================================================ /** * @module typings/core */ import { ITransitionData, ITransitionPage, IViewData } from './index'; export type HooksBarba = | 'ready' | 'page' | 'reset' | 'currentAdded' | 'currentRemoved' | 'nextAdded' | 'nextRemoved'; export type HooksOnce = 'beforeOnce' | 'once' | 'afterOnce'; export type HooksPage = | 'before' | 'beforeLeave' | 'leave' | 'afterLeave' | 'beforeEnter' | 'enter' | 'afterEnter' | 'after'; export type HooksBefore = 'beforeOnce' | 'beforeLeave' | 'beforeEnter'; export type HooksAfter = 'afterOnce' | 'afterLeave' | 'afterEnter'; export type HooksTransition = HooksOnce | HooksPage; export type HooksView = HooksBefore | HooksAfter; export type HooksAll = HooksBarba | HooksTransition; // Allow optional "dynamically created" hooks export type HooksTransitionMap = { [key in HooksTransition]?: any }; export type HookFunction = ( data?: ITransitionData | IViewData, t?: ITransitionPage ) => Promise | void; export class HookMethods { public before: (fn: HookFunction, ctx?: any) => void; public beforeLeave: (fn: HookFunction, ctx?: any) => void; public leave: (fn: HookFunction, ctx?: any) => void; public afterLeave: (fn: HookFunction, ctx?: any) => void; public beforeEnter: (fn: HookFunction, ctx?: any) => void; public enter: (fn: HookFunction, ctx?: any) => void; public afterEnter: (fn: HookFunction, ctx?: any) => void; public after: (fn: HookFunction, ctx?: any) => void; } ================================================ FILE: packages/core/src/defs/ignore.ts ================================================ /** * @module typings/core */ export type IgnoreOption = boolean | string | string[]; ================================================ FILE: packages/core/src/defs/index.ts ================================================ export * from './barba'; // export * from './core'; export * from './headers'; export * from './cache'; export * from './dom'; export * from './global'; export * from './ignore'; export * from './history'; export * from './hooks'; export * from './prevent'; export * from './request'; export * from './rules'; export * from './schemas'; export * from './transition'; export * from './url'; export * from './view'; ================================================ FILE: packages/core/src/defs/is-promise.d.ts ================================================ declare module 'is-promise'; ================================================ FILE: packages/core/src/defs/prevent.ts ================================================ /** * @module typings/core */ // Definitions import { Link } from './index'; /** * Available data for all prevent checks. * * - `el`: the clicked link * - `event`: the associated event * - `href`: the href to use for fetching */ export interface IPreventCheckData { el: Link; event: Event; href: string; } /** * Prevent check. * * - Receives check data. * - If it returns `true`, Barba will prevent the action. */ export type PreventCheck = (data: IPreventCheckData) => boolean; ================================================ FILE: packages/core/src/defs/request.ts ================================================ /** * @module typings/core */ // Definitions import { IUrlFull, Trigger } from './index'; export interface IResponse { html: string; url: IUrlFull; } export type RequestError = ( url: string, errorOrResponse: RequestErrorOrResponse ) => boolean; // export type RequestErrorBinded = ( // trigger: Trigger, // action: string, // ...args: any // ) => boolean; export type RequestCustomError = ( trigger: Trigger, action: string, url: string, response: RequestErrorOrResponse ) => boolean; export interface IXhrResponse { status: number; statusText: string; } export type RequestErrorOrResponse = Error | IXhrResponse; ================================================ FILE: packages/core/src/defs/rules.ts ================================================ /** * @module typings/core */ export type RuleName = | IRules['strings'] | IRules['object'] | IRules['function']; export type RuleType = 'strings' | 'object' | 'function'; export interface IRule { name: RuleName; type: RuleType; } export interface IRules { strings: 'namespace'; object: 'route'; function: 'custom'; } ================================================ FILE: packages/core/src/defs/schemas.ts ================================================ /** * @module typings/core */ interface IRouteResolved { name: string; params: any; } import { IUrlFull } from './index'; export type SchemaAttributeValues = | 'prefix' | 'wrapper' | 'container' | 'prevent' | 'history' | 'namespace'; /** * ### Define HTML `data-attribute` used by Barba. * * @param prefix data-__prefix__ * @param wrapper data-prefix="__wrapper__" * @param container data-prefix="__container__" * @param prevent data-prefix-__prevent__ * @param history data-prefix-__history__ * @param namespace data-prefix-__namespace__ */ export interface ISchemaAttribute { prefix?: string; wrapper?: string; container?: string; prevent?: string; history?: string; namespace?: string; } /** * ### Define "page" data structure. * * Used by `data.current` and `data.next`.
    * Set to `undefined` until values are available. * * @param container Barba container element * @param html Full stringified HTML * @param namespace Namespace * @param url URL * @param route Route name (with `@barba/router`) */ export interface ISchemaPage { container: HTMLElement; html: string; namespace: string; url: IUrlFull; route?: IRouteResolved | null; } ================================================ FILE: packages/core/src/defs/transition.ts ================================================ /** * @module typings/core */ // Definitions import { LinkEvent } from '../defs'; import { ISchemaPage, Trigger } from './index'; // Data export interface ITransitionData { current: ISchemaPage; next: ISchemaPage; trigger: Trigger; event?: LinkEvent | PopStateEvent; } // Filter export interface ITransitionFilters { once?: boolean; self?: boolean; } // Rules export interface ITransitionRules { namespace?: string | string[]; route?: string | string[]; custom?(data: ITransitionData): boolean; } // Transitions export interface ITransitionPage extends ITransitionRules { name?: string; from?: ITransitionRules; to?: ITransitionRules; sync?: boolean; priority?: number; async?: () => (data?: any) => void; beforeOnce?(data: ITransitionData): void; once?(data: ITransitionData): Promise | void; afterOnce?(data: ITransitionData): void; before?(data: ITransitionData): void; beforeLeave?(data: ITransitionData): void; leave?(data: ITransitionData): Promise | void; afterLeave?(data: ITransitionData): void; beforeEnter?(data: ITransitionData): void; enter?(data: ITransitionData): Promise | void; afterEnter?(data: ITransitionData): void; after?(data: ITransitionData): void; } export interface ITransitionOnce extends ITransitionPage { once?(data: ITransitionData): Promise; } ================================================ FILE: packages/core/src/defs/url.ts ================================================ /** * @module typings/core */ // Definitions import { IGenericObject } from './index'; export interface IUrlBase { path: string | undefined; hash: string | undefined; query: IGenericObject; port: number; } export interface IUrlFull extends IUrlBase { href: string | undefined; } ================================================ FILE: packages/core/src/defs/view.ts ================================================ /** * @module typings/core */ // Definitions import { ISchemaPage, Trigger } from './index'; export interface IViewData { current: ISchemaPage; next: ISchemaPage; trigger: Trigger; } export interface IView { namespace: string; name?: string; beforeOnce?(data: IViewData): void; afterOnce?(data: IViewData): void; beforeLeave?(data: IViewData): void; afterLeave?(data: IViewData): void; beforeEnter?(data: IViewData): void; afterEnter?(data: IViewData): void; } ================================================ FILE: packages/core/src/hooks.ts ================================================ /** * @barba/core/modules/hooks *

    * ## Hooks manager. * * - Register and trigger hooks * * Hooks can be easily registered: * * ```js * hooks.leave(callback, context); * ``` * * @module core/modules/hooks * @preferred */ /***/ // Definitions import { HookFunction, HookMethods, HooksAll } from './defs'; // Modules import { Logger } from './modules/Logger'; // Utils import { runAsync } from './utils'; // Types interface IHookInfos { ctx: any; fn: HookFunction; } export class Hooks extends HookMethods { /** * Allow the use of `hooks[name](cb, ctx)`. */ [key: string]: any; // [key in HooksAll]?: any; public logger: Logger = new Logger('@barba/core'); /** * All available hooks. * * See [[HooksAll]] */ // TODO: get hooks from defs (DRY)? public all: HooksAll[] = [ 'ready', 'page', 'reset', 'currentAdded', 'currentRemoved', 'nextAdded', 'nextRemoved', 'beforeOnce', 'once', 'afterOnce', 'before', 'beforeLeave', 'leave', 'afterLeave', 'beforeEnter', 'enter', 'afterEnter', 'after', ]; /** * Registered hooks. * * - Unique hook name * - Associated data set(s) (callback + context) */ public registered: Map> = new Map(); constructor() { super(); this.init(); } public init() { this.registered.clear(); this.all.forEach(hook => { if (!this[hook]) { this[hook] = (fn: HookFunction, ctx?: any) => { if (!this.registered.has(hook)) { this.registered.set(hook, new Set()); } const set = this.registered.get(hook); set.add({ ctx: ctx || {}, fn, }); }; } }); } /** * Do hook. * * Trigger registered hooks. */ public do(name: HooksAll, ...args: any): Promise { if (this.registered.has(name)) { // Let's start a chain of promises let chain = Promise.resolve(); this.registered.get(name).forEach(hook => { // Chain async hooks promisified chain = chain.then(() => runAsync(hook.fn, hook.ctx)(...args)); }); return chain.catch(error => { this.logger.debug(`Hook error [${name}]`); this.logger.error(error); }); } return Promise.resolve(); } public clear(): void { this.all.forEach(hook => { delete this[hook]; }); this.init(); } /** * Help, print available and registered hooks. */ public help(): void { this.logger.info(`Available hooks: ${this.all.join(',')}`); const registered: string[] = []; this.registered.forEach((_value: any, key: string) => registered.push(key)); this.logger.info(`Registered hooks: ${registered.join(',')}`); } } const hooks = new Hooks(); export { hooks }; ================================================ FILE: packages/core/src/index.ts ================================================ import * as t from './typings'; export { default } from './core'; ================================================ FILE: packages/core/src/modules/Cache.ts ================================================ /** * @barba/core/modules/cache *

    * ## Cache for storing URL / HTML. * * @module core/modules/cache * @preferred */ /***/ // Definitions import { CacheAction, CacheRequest, CacheStatus, CacheTarget, ICacheData, IgnoreOption } from '../defs'; // Modules import { Ignore } from './Ignore'; export class Cache extends Ignore { private _state: Map = new Map(); constructor(ignore: IgnoreOption) { super(ignore); } /** * Set value to cache */ public set( href: string, request: CacheRequest, action: CacheAction, status: CacheStatus, target?: CacheTarget, ): ICacheData { this._state.set(href, { action, request, status, target: target ?? href, }); return { action, request, status, target, }; } /** * Get data from cache */ public get(href: string): ICacheData { return this._state.get(href); } /** * Get request from cache */ public getRequest(href: string): CacheRequest { return this._state.get(href).request; } /** * Get action from cache */ public getAction(href: string): CacheAction { return this._state.get(href).action; } /** * Get status from cache */ public getStatus(href: string): CacheStatus { return this._state.get(href).status; } /** * Get target from cache */ public getTarget(href: string): CacheTarget { return this._state.get(href).target; } /** * Check if value exists into cache */ public has(href: string): boolean { /* istanbul ignore else */ if (this.checkHref(href)) { return false; } return this._state.has(href); } /** * Delete value from cache */ public delete(href: string): boolean { return this._state.delete(href); } /** * Update cache value */ public update(href: string, data: ICacheData): ICacheData { const state = { ...this._state.get(href), ...data, }; this._state.set(href, state); return state; } } ================================================ FILE: packages/core/src/modules/Error.ts ================================================ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error export class BarbaError extends Error { /* istanbul ignore next */ constructor( public error: Error, public label = 'Barba error', ...params: any[] ) { // Pass remaining arguments (including vendor specific ones) to parent constructor super(...params); // Maintains proper stack trace for where our error was thrown (only available on V8) /* istanbul ignore else */ if (Error.captureStackTrace) { Error.captureStackTrace(this, BarbaError); } this.name = 'BarbaError'; } } ================================================ FILE: packages/core/src/modules/Headers.ts ================================================ /** * @barba/core/modules/headers *

    * ## Manage request Headers. * * @module core/modules/headers * @preferred */ /***/ // Definitions import { HeaderList, IHeaderData } from '../defs'; export class Headers { private _list: HeaderList = new Map(); /** * Set a new header */ public set(name: string, value: string): IHeaderData { this._list.set(name, value); return { name: value }; } /** * Get a specific header */ public get(name: string): string { return this._list.get(name); } /** * Get all headers */ public all(): HeaderList { return this._list; } /** * Check if header exists */ public has(name: string): boolean { return this._list.has(name); } /** * Delete a header */ public delete(name: string): boolean { return this._list.delete(name); } /** * Clear all headers */ public clear(): void { return this._list.clear(); } } ================================================ FILE: packages/core/src/modules/Ignore.ts ================================================ /** * @barba/core/modules/ignore *

    * ## Manage ignore options. * * - cache * - prefetch * * @module core/modules/ignore * @preferred */ /***/ // Definitions import { IgnoreOption } from '../defs'; // Utils import { pathToRegexp } from '../utils/helpers'; import { parse } from '../utils/url'; export class Ignore { private _ignoreAll: boolean; private _ignoreRegexes: RegExp[] = []; constructor(ignore: IgnoreOption) { if (typeof ignore === 'boolean') { this._ignoreAll = ignore; } else { const paths = Array.isArray(ignore) ? ignore : [ignore]; this._ignoreRegexes = paths.map(p => pathToRegexp(p)); } } public checkHref(href: string): boolean { if (typeof this._ignoreAll === 'boolean') { return this._ignoreAll; } const { path } = parse(href); return this._ignoreRegexes.some(regex => regex.exec(path) !== null); } } ================================================ FILE: packages/core/src/modules/Logger.ts ================================================ /** * @barba/core/modules/Logger *

    * ## Logger. * * - Display informations via the console * * @module core/modules/Logger * @preferred */ /***/ /** * Log levels, all lower level messages are printed * * 0. mute * 1. error = `console.error()` * 2. warning= `console.warn()` * 3. info = `console.info()` * 4. debug = `console.log()` */ export enum LogLevels { off = 0, error = 1, warning = 2, info = 3, debug = 4, } /** * Global log level */ let _level: number = LogLevels.off; export class Logger { /** * Get global log level. */ public static getLevel(): number { return _level; } /** * Set global log level. */ public static setLevel(name: keyof typeof LogLevels): number { _level = LogLevels[name]; return _level; } /** * Log "prefix". */ private _source: string; /** * Creates an instance of Logger. */ constructor(source: string) { this._source = source; } /** * Permanent, unremovable log. */ // public print(...objects: any[]): void { // this._log(console.info, LogLevels.off, objects); // } /** * Error log. */ public error(...objects: any[]): void { this._log(console.error, LogLevels.error, objects); } /** * Warn log. */ public warn(...objects: any[]): void { this._log(console.warn, LogLevels.warning, objects); } /** * Info log. */ public info(...objects: any[]): void { this._log(console.info, LogLevels.info, objects); } /** * Debug log. */ public debug(...objects: any[]): void { this._log(console.log, LogLevels.debug, objects); } /** * Internal logger. */ private _log(fn: () => void, level: number, objects: any[]): void { if (level <= Logger.getLevel()) { fn.apply(console, ([`[${this._source}] `].concat(objects) as unknown) as [ ]); } } } ================================================ FILE: packages/core/src/modules/Prevent.ts ================================================ /** * @barba/core/modules/prevent *

    * ## Prevent checks. * * - Gathers all the tests that allow Barba to work and play transitions * * @module core/modules/prevent * @preferred */ /***/ // Definitions import { IgnoreOption, Link, PreventCheck } from '../defs'; // Schemas import { schemaAttribute } from '../schemas/attribute'; // Utils import { url } from '../utils'; // Modules import { Ignore } from './Ignore'; /** * Make sure the browser supports `history.pushState`. */ const pushState: PreventCheck = () => !window.history.pushState; /** * Make sure there is an `el` and `href`. */ const exists: PreventCheck = ({ el, href }) => !el || !href; /** * If the user is pressing ctrl + click, the browser will open a new tab. */ const newTab: PreventCheck = ({ event }) => (event as KeyboardEvent).which > 1 || (event as KeyboardEvent).metaKey || (event as KeyboardEvent).ctrlKey || (event as KeyboardEvent).shiftKey || (event as KeyboardEvent).altKey; /** * If the link has `_blank` target. */ const blank: PreventCheck = ({ el }) => el.hasAttribute('target') && (el as Link).target === '_blank'; /** * If the domain is the same (in order to avoid pushState cross origin security problem). * Note: SVGAElement do not have `protocol` neither `hostname` properties. */ const corsDomain: PreventCheck = ({ el }) => ((el as HTMLAnchorElement).protocol !== undefined && window.location.protocol !== (el as HTMLAnchorElement).protocol) || ((el as HTMLAnchorElement).hostname !== undefined && window.location.hostname !== (el as HTMLAnchorElement).hostname); /** * If the port is the same. * Note: SVGAElement do not have `port` property. */ const corsPort: PreventCheck = ({ el }) => (el as HTMLAnchorElement).port !== undefined && url.getPort() !== url.getPort((el as HTMLAnchorElement).href); /** * If the link has download attribute. */ const download: PreventCheck = ({ el }) => el.getAttribute && typeof el.getAttribute('download') === 'string'; /** * If the links contains [data-barba-prevent] or [data-barba-prevent="self"]. */ const preventSelf: PreventCheck = ({ el }) => el.hasAttribute(`${schemaAttribute.prefix}-${schemaAttribute.prevent}`); /** * If some link ancestor contains [data-barba-prevent="all"]. */ const preventAll: PreventCheck = ({ el }) => Boolean( el.closest(`[${schemaAttribute.prefix}-${schemaAttribute.prevent}="all"]`) ); /** * If the link is the current URL. * * > Not in the test suite. */ const sameUrl: PreventCheck = ({ href }) => url.clean(href) === url.clean() && url.getPort(href) === url.getPort(); export class Prevent extends Ignore { public suite: string[] = []; public tests: Map = new Map(); constructor(ignore: IgnoreOption) { super(ignore); this.init(); } public init(): void { // Add defaults this.add('pushState', pushState); this.add('exists', exists); this.add('newTab', newTab); this.add('blank', blank); this.add('corsDomain', corsDomain); this.add('corsPort', corsPort); this.add('download', download); this.add('preventSelf', preventSelf); this.add('preventAll', preventAll); // Outside of the test suite this.add('sameUrl', sameUrl, false); } public add(name: string, check: PreventCheck, suite: boolean = true): void { this.tests.set(name, check); suite && this.suite.push(name); } /** * Run individual test */ public run(name: string, el: Link, event: Event, href: string): boolean { return this.tests.get(name)({ el, event, href, }); } /** * Run test suite */ public checkLink(el: Link, event: Event, href: string): boolean { return this.suite.some(name => this.run(name, el, event, href)); } } ================================================ FILE: packages/core/src/modules/Store.ts ================================================ /** * @barba/core/modules/store *

    * ## Transitions store. * * - Resolve transition * - Manage rules * * @module core/modules/store * @preferred */ /***/ // Definitions import { IRule, IRules, ITransitionData, ITransitionFilters, ITransitionOnce, ITransitionPage, RuleName, } from '../defs'; // Modules import { Logger } from './Logger'; export class Store { public logger: Logger = new Logger('@barba/core'); /** * All registered transitions. */ public all: ITransitionPage[] = []; /** * "Page only" registered transitions. */ public page: ITransitionPage[] = []; /** * "Once only" registered transitions. */ public once: ITransitionOnce[] = []; /** * Rules for transition resolution. * * Defaults: * * - namespace * - custom */ private _rules: IRule[] = [ { name: 'namespace', type: 'strings', }, { name: 'custom', type: 'function', }, ]; /** * Init store. */ constructor(transitions: ITransitionPage[] = []) { /* istanbul ignore else */ if (transitions) { // TODO: add check for valid transitions? criteria? (once || enter && leave) this.all = this.all.concat(transitions); } this.update(); } /** * Add rule or transition. */ public add(type: 'rule' | 'transition', data: any): void { switch (type) { case 'rule': // TODO: check for valid rule this._rules.splice(data.position || 0, 0, data.value); break; case 'transition': default: // TODO: check for valid transition this.all.push(data); break; } this.update(); } /** * Resolve transition. */ public resolve( data: ITransitionData, filters: ITransitionFilters = {} ): ITransitionOnce | ITransitionPage { // Filter on "once" let transitions = filters.once ? this.once : this.page; // Filter on "self" if (filters.self) { transitions = transitions.filter(t => t.name && t.name === 'self'); } else { transitions = transitions.filter(t => !t.name || t.name !== 'self'); } // All matching transition infos const matching = new Map(); // Active = first of valid transitions // sorted by directions (from/to, from || to, …) const active = transitions.find(t => { let valid = true; const match = {}; if (filters.self && t.name === 'self') { matching.set(t, match); return true; } // Check rules this._rules.reverse().forEach(rule => { if (valid) { valid = this._check(t, rule, data, match); // From/to check if (t.from && t.to) { valid = this._check(t, rule, data, match, 'from') && this._check(t, rule, data, match, 'to'); } if (t.from && !t.to) { valid = this._check(t, rule, data, match, 'from'); } if (!t.from && t.to) { valid = this._check(t, rule, data, match, 'to'); } } }); matching.set(t, match); return valid; }); const activeMatch = matching.get(active); const transitionType = []; if (filters.once) { transitionType.push('once'); } else { transitionType.push('page'); } if (filters.self) { transitionType.push('self'); } if (activeMatch) { // Log resolved transition const infos: any[] = [active]; // Log if matching criteria Object.keys(activeMatch).length > 0 && infos.push(activeMatch); this.logger.info( `Transition found [${transitionType.join(',')}]`, ...infos ); } else { this.logger.info(`No transition found [${transitionType.join(',')}]`); } return active; } /** * ### Update store. * * - Reorder transition by priorities * - Get wait indicator * - Get once transitions */ public update(): void { // Reorder by priorities this.all = this.all .map(t => this._addPriority(t)) .sort((a, b) => a.priority - b.priority) .reverse() .map(t => { delete t.priority; return t; }); this.page = this.all.filter( t => t.leave !== undefined || t.enter !== undefined ) as ITransitionPage[]; this.once = this.all.filter(t => t.once !== undefined) as ITransitionOnce[]; } /** * ### Check if transition apply. * * Based on rule, page data and optional direction: * * 1. transition has no rule "property": * - always returns true * 2. transition has rule "property": * - "strings" should be present on both side (transition + page) and match * - "function" should return true */ private _check( transition: ITransitionPage, rule: IRule, data: ITransitionData, match: any, direction?: 'from' | 'to' ): boolean { let isValid = true; let hasMatch = false; const t = transition; const { name, type } = rule; const strRule = name as IRules['strings']; const objRule = name as IRules['object']; const fnName = name as IRules['function']; const base = direction ? t[direction] : t; // = t || t.from || t.to const page = direction === 'to' ? data.next : data.current; // = current || next const exist = direction ? base && base[name] : base[name]; // If transition rule exists if (exist) { switch (type) { case 'strings': default: { // Array support const names: string[] = Array.isArray(base[strRule]) ? (base[strRule] as string[]) : [base[strRule] as string]; // For matching, prop should be present on both sides and match if (page[strRule] && names.indexOf(page[strRule]) !== -1) { hasMatch = true; } // If transition prop is different from current, not valid if (names.indexOf(page[strRule]) === -1) { isValid = false; } break; } case 'object': { // Array support const names: string[] = Array.isArray(base[objRule]) ? (base[objRule] as string[]) : [base[objRule] as string]; // For matching, prop should be present on both sides and match if (page[objRule]) { if ( page[objRule].name && names.indexOf(page[objRule].name) !== -1 ) { hasMatch = true; } // If transition prop is different from current, not valid if (names.indexOf(page[objRule].name) === -1) { isValid = false; } } else { isValid = false; } break; } case 'function': if (base[fnName](data)) { hasMatch = true; } else { isValid = false; } break; } if (hasMatch) { if (direction) { match[direction] = match[direction] || {}; match[direction][name] = t[direction][name]; } else { match[name] = t[name]; } } } return isValid; } /** * ### Calculate transition priority. * * Based on: * * - rule "position" (index) give tens, hundreds, thousands, … * - from/to properties give units (0, 1 or 2) */ private _calculatePriority( t: ITransitionPage, ruleName: RuleName, ruleIndex: number ): number { let priority = 0; if ( t[ruleName] || (t.from && t.from[ruleName]) || (t.to && t.to[ruleName]) ) { priority += Math.pow(10, ruleIndex); if (t.from && t.from[ruleName]) { priority += 1; } if (t.to && t.to[ruleName]) { priority += 2; } } return priority; } private _addPriority(t: ITransitionPage): ITransitionPage { t.priority = 0; let priority = 0; this._rules.forEach((rule, i) => { const { name } = rule; const index = i + 1; priority += this._calculatePriority(t, name, index); }); t.priority = priority; return t; } } ================================================ FILE: packages/core/src/modules/Transitions.ts ================================================ /** * @barba/core/modules/transitions *

    * ## Transitions manager. * * - Handle hooks and transition lifecycle * * @module core/modules/transitions * @preferred */ /***/ // Definitions import { HooksTransition, HooksTransitionMap, IResponse, ITransitionData, ITransitionFilters, ITransitionOnce, ITransitionPage, Wrapper, } from '../defs'; // Hooks import { hooks } from '../hooks'; // Utils import { dom, helpers, runAsync } from '../utils'; // Modules import { BarbaError } from './Error'; import { Logger } from './Logger'; import { Store } from './Store'; export class Transitions { public logger: Logger = new Logger('@barba/core'); public store: Store; private _running: boolean = false; constructor(transitions: ITransitionPage[] = []) { this.store = new Store(transitions); } /** * Get resolved transition * * - based on data */ public get( data: ITransitionData, filters?: ITransitionFilters ): ITransitionOnce | ITransitionPage { return this.store.resolve(data, filters); } /** * Animation running status. */ get isRunning(): boolean { return this._running; } set isRunning(status: boolean) { this._running = status; } /** * Check for registered once transition(s). */ get hasOnce(): boolean { return this.store.once.length > 0; } /** * Check for registered self transition. */ get hasSelf(): boolean { return this.store.all.some(t => t.name === 'self'); } /** * ### Wait indicator. * * Tells Barba to get next page data
    * before starting the resolution
    * because some registered transitions need
    * next page data to be resolved (eg: `sync: true`, `to: { namespace }`, …) */ get shouldWait(): boolean { return this.store.all.some(t => (t.to && !t.to.route) || t.sync); } /** * ### Do "once" transition. * * Hooks: see [[HooksOnce]]. */ public async doOnce({ data, transition, }: { data: ITransitionData; transition: ITransitionOnce; }) { const t = transition || {}; this._running = true; try { await this._doAsyncHook('beforeOnce', data, t); await this.once(data, t); await this._doAsyncHook('afterOnce', data, t); } catch (error) { this._running = false; this.logger.debug('Transition error [before/after/once]'); this.logger.error(error); } this._running = false; } /** * ### Do "page" transition. * * Hooks: see [[HooksPage]]. * * `sync: false` (default) order: * * 1. before * 2. beforeLeave * 3. leave * 4. afterLeave * 5. beforeEnter * 6. enter * 7. afterEnter * 8. after * * `sync: true` order: * * 1. before * 2. beforeLeave * 3. beforeEnter * 4. leave & enter * 5. afterLeave * 6. afterEnter * 7. after */ public async doPage({ data, transition, page, wrapper, }: { data: ITransitionData; transition: ITransitionPage; page: Promise; wrapper: Wrapper; }) { const t = transition || {}; const sync = t.sync === true || false; this._running = true; try { // Check sync mode, wait for next content if (sync) { await helpers.update(page, data); } await this._doAsyncHook('before', data, t); if (sync) { try { await this.add(data, wrapper); // Before actions await this._doAsyncHook('beforeLeave', data, t); await this._doAsyncHook('beforeEnter', data, t); // Actions await Promise.all([this.leave(data, t), this.enter(data, t)]); // After actions await this._doAsyncHook('afterLeave', data, t); await this._doAsyncHook('afterEnter', data, t); } catch (error) { // this.logger.debug('Transition error [sync]'); // this.logger.error(error); if (this._isTransitionError(error)) { throw new BarbaError( (error as Error), 'Transition error [sync]' ); } } } else { let leaveResult: any = false; try { // Leave await this._doAsyncHook('beforeLeave', data, t); leaveResult = await Promise.all([ this.leave(data, t), helpers.update(page, data), ]).then(values => values[0]); await this._doAsyncHook('afterLeave', data, t); // TODO: check here "valid" page result // before going further } catch (error) { // this.logger.debug('Transition error [before/after/leave]'); // this.logger.error(error); if (this._isTransitionError(error)) { throw new BarbaError( (error as Error), 'Transition error [before/after/leave]' ); } } try { // Enter /* istanbul ignore else */ if (leaveResult !== false) { await this.add(data, wrapper); await this._doAsyncHook('beforeEnter', data, t); await this.enter(data, t, leaveResult); await this._doAsyncHook('afterEnter', data, t); } } catch (error) { // this.logger.debug('Transition error [before/after/enter]'); // this.logger.error(error); if (this._isTransitionError(error)) { throw new BarbaError( (error as Error), 'Transition error [before/after/enter]' ); } } } // Remove current container await this.remove(data); await this._doAsyncHook('after', data, t); } catch (error: any) { this._running = false; // If "custom/specific" barba error. /* istanbul ignore else */ if (error.name && error.name === 'BarbaError') { this.logger.debug(error.label); this.logger.error(error.error); throw error; } this.logger.debug('Transition error [page]'); this.logger.error(error); throw error; } this._running = false; } /** * Once hook + async "once" transition. */ public async once(data: ITransitionData, t: ITransitionOnce): Promise { await hooks.do('once', data, t); return t.once ? runAsync(t.once, t)(data) : Promise.resolve(); } /** * Leave hook + async "leave" transition. */ public async leave(data: ITransitionData, t: ITransitionPage): Promise { await hooks.do('leave', data, t); return t.leave ? runAsync(t.leave, t)(data) : Promise.resolve(); } /** * Enter hook + async "enter" transition. */ public async enter( data: ITransitionData, t: ITransitionPage, leaveResult?: any ): Promise { await hooks.do('enter', data, t); return t.enter ? runAsync(t.enter, t)(data, leaveResult) : Promise.resolve(); } /** * Add next container. */ public async add(data: ITransitionData, wrapper: Wrapper): Promise { dom.addContainer(data.next.container, wrapper); hooks.do('nextAdded', data); } /** * Remove current container. */ public async remove(data: ITransitionData): Promise { dom.removeContainer(data.current.container); hooks.do('currentRemoved', data); } private _isTransitionError(error: any) { if (error.message) { // Errors from request return !/Timeout error|Fetch error/.test(error.message); } if (error.status) { // Errors from request return false; } return true; } /** * Do hooks + async transition methods. */ private async _doAsyncHook( hook: HooksTransition, data: ITransitionData, t: HooksTransitionMap ): Promise { await hooks.do(hook, data, t); return t[hook] ? runAsync(t[hook], t)(data) : Promise.resolve(); } } ================================================ FILE: packages/core/src/modules/Views.ts ================================================ /** * @barba/core/modules/views *

    * ## Views manager. * * @module core/modules/views * @preferred */ /***/ // Definitions import { HooksView, IView, IViewData } from '../defs'; // Hooks import { hooks } from '../hooks'; // Utils import { runAsync } from '../utils'; // Types type Hook = (data: IViewData) => Promise; export class Views { /** * Available hook names for views. */ public names: HooksView[] = [ 'beforeLeave', 'afterLeave', 'beforeEnter', 'afterEnter', ]; /** * Registered views by namespace. */ public byNamespace: Map = new Map(); /** * Init views. */ constructor(views: IView[]) { if (views.length === 0) { return; } // TODO: add check // for valid views? criteria? (namespace property, string ?) // or duplicate views.forEach(view => { this.byNamespace.set(view.namespace, view); }); this.names.forEach(name => { hooks[name](this._createHook(name)); }); } /** * Create the hook method. * * - get view based on namespace * - execute callback with transition data */ private _createHook(name: HooksView): Hook { return data => { const { namespace } = name.match(/enter/i) ? data.next : data.current; const view = this.byNamespace.get(namespace); // TODO: manage self… // if (view && data.trigger !== 'self') { if (view && view[name]) { return runAsync(view[name], view)(data); } return Promise.resolve(); }; } } ================================================ FILE: packages/core/src/polyfills/index.ts ================================================ // Element.prototype.matches polyfill // https://developer.mozilla.org/en-US/docs/Web/API/Element/matches#Polyfill if (!Element.prototype.matches) { Element.prototype.matches = (Element as any).prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; } // Element.prototype.closest polyfill // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill if (!Element.prototype.closest) { Element.prototype.closest = function closest(s: string) { let el = this; do { if (el.matches(s)) { return el; } el = (el.parentElement || el.parentNode) as Element; } while (el !== null && el.nodeType === 1); return null; }; } ================================================ FILE: packages/core/src/schemas/attribute.ts ================================================ /** * @barba/core/schemas *

    * ## Schemas description. * * @module core/schemas * @preferred */ /***/ // Definitions import { ISchemaAttribute } from '../defs'; /** * See [[ISchemaAttribute]] */ export const schemaAttribute: ISchemaAttribute = { container: 'container', history: 'history', namespace: 'namespace', prefix: 'data-barba', prevent: 'prevent', wrapper: 'wrapper', }; ================================================ FILE: packages/core/src/schemas/page.ts ================================================ /** * @module core/schemas */ // Definitions import { ISchemaPage } from '../defs'; /** * See [[ISchemaPage]] */ export const schemaPage: ISchemaPage = { container: null, html: '', namespace: '', url: { hash: '', href: '', path: '', port: null, query: {}, }, }; ================================================ FILE: packages/core/src/typings.ts ================================================ export * from './defs'; export { default } from './core'; ================================================ FILE: packages/core/src/utils/dom.ts ================================================ /** * @barba/core/utils/dom *

    * ## Dom utils * * - Access DOM contents * - DOM vs string conversions * * @module core/utils/dom * @preferred */ /***/ import path from 'path'; // Definitions import { IDomSibling, ISchemaAttribute, Link, Scope, Wrapper } from '../defs'; // Schemas import { schemaAttribute } from '../schemas/attribute'; export class Dom { private _attr: ISchemaAttribute = schemaAttribute; private _parser: DOMParser; private _sibling: IDomSibling = { after: null, before: null, parent: null }; /** * Convert HTMLDocument to string. */ public toString(el: HTMLElement): string { return el.outerHTML; } /** * Parse HTML string to HTMLDocument. */ // see https://github.com/barbajs/barba/issues/362 // Seems that using DOMParser.parseFromString causes this issue. public toDocument(htmlString: string): HTMLDocument { /* istanbul ignore else */ if (!this._parser) { this._parser = new DOMParser(); } return this._parser.parseFromString(htmlString, 'text/html'); } /** * Parse HTML string to DIVElement. * * DOMParser.parseFromString fails with img[srcset] on iOS. * see https://github.com/barbajs/barba/issues/362 */ public toElement(htmlString: string): HTMLDivElement { const div = document.createElement('div'); div.innerHTML = htmlString; return div; } /** * Get HTML content. */ public getHtml(doc: HTMLDocument = document): string { return this.toString(doc.documentElement); } /** * Get full document content. */ // getDocument(el = document.documentElement) { // return this.toStr(el); // }, /** * Get `[data-barba="wrapper"]`. */ public getWrapper(scope: Scope = document): Wrapper { return scope.querySelector( `[${this._attr.prefix}="${this._attr.wrapper}"]` ); } /** * Get `[data-barba="container"]`. */ public getContainer(scope: Scope = document): HTMLElement | null { return scope.querySelector( `[${this._attr.prefix}="${this._attr.container}"]` ); } /** * Remove container and store next sibling (if applicable). */ public removeContainer(container: HTMLElement) { if (document.body.contains(container)) { this._updateSibling(container); container.parentNode.removeChild(container); } } /** * Add container near previous container */ public addContainer(container: HTMLElement, wrapper: HTMLElement) { const siblingBefore = this.getContainer() || this._sibling.before; if (siblingBefore) { this._insertAfter(container, siblingBefore); } else if (this._sibling.after) { this._sibling.after.parentNode.insertBefore(container, this._sibling.after); } else if (this._sibling.parent) { this._sibling.parent.appendChild(container); } else { wrapper.appendChild(container); } } /** * Get current dom sibling */ public getSibling(): IDomSibling { return this._sibling; } /** * Get `[data-barba-namespace]`. */ public getNamespace(scope: Scope = document): string | null { const ns = scope.querySelector( `[${this._attr.prefix}-${this._attr.namespace}]` ); return ns ? ns.getAttribute(`${this._attr.prefix}-${this._attr.namespace}`) : null; } /** * Get URL from `href` value. */ public getHref(el: Link): string | null { // HTML tagName is UPPERCASE, xhtml tagName keeps existing case. if (el.tagName && el.tagName.toLowerCase() === 'a') { // HTMLAnchorElement, full URL available if (typeof el.href === 'string') { return el.href; } // Probably a SVGAElement… const href = el.getAttribute('href') || el.getAttribute('xlink:href'); /* istanbul ignore else */ if (href) { // When link comes from SVG, `href` returns an object, not a string. const attr: string = ((href as unknown) as SVGAnimatedString).baseVal || href; return this.resolveUrl(attr); } } return null; } // Copyright 2014 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE // https://github.com/lydell/resolve-url/blob/master/resolve-url.js /* istanbul ignore next */ public resolveUrl(...urls: string[]) { const numUrls = urls.length; if (numUrls === 0) { throw new Error('resolveUrl requires at least one argument; got none.'); } const base = document.createElement('base'); base.href = arguments[0]; if (numUrls === 1) { return base.href; } const head = document.getElementsByTagName('head')[0]; head.insertBefore(base, head.firstChild); const a = document.createElement('a'); let resolved; for (let index = 1; index < numUrls; index++) { a.href = arguments[index]; resolved = a.href; base.href = resolved; } head.removeChild(base); return resolved; } /** * Insert node after another node. */ private _insertAfter(newNode: Node, referenceNode: Node) { referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); } /** * Update current dom sibling regarding container */ private _updateSibling(container: HTMLElement): IDomSibling { this._sibling = { after: container.nextElementSibling, before: container.previousElementSibling, parent: container.parentElement }; return this._sibling; } } const dom = new Dom(); export { dom }; ================================================ FILE: packages/core/src/utils/helpers.ts ================================================ /** * @barba/core/utils/helpers *

    * ## Helpers * * - Update next page data * * @module core/utils/helpers * @preferred */ /***/ // Third-party import { pathToRegexp as ptr } from 'path-to-regexp'; // Definitions import { IResponse, ITransitionData } from '../defs'; // Utils import { dom } from './dom'; import { history } from './history'; /** * Update `data.next`, the title and the history */ export const update = async ( page: Promise, data: ITransitionData ): Promise => { // If not already updated if (!data.next.html) { const response = await page; const { next } = data; if (response) { // see: https://github.com/barbajs/barba/issues/362 // const nextDocument = dom.toDocument(html); const nextDocument = dom.toElement(response.html); next.namespace = dom.getNamespace(nextDocument); next.container = dom.getContainer(nextDocument); // see https://github.com/barbajs/barba/issues/362 // next.html = dom.getHtml(nextDocument); // next.html = nextDocument.innerHTML; next.url = response.url; next.html = response.html; // Update history namespace (not available when initially set) history.update({ ns: next.namespace }); // Update title. const { title } = dom.toDocument(response.html); document.title = title; } } }; /** * Next tick */ export const nextTick = () => new Promise(resolve => { window.requestAnimationFrame(resolve); // DEV: same result? // setTimeout(resolve, 0); }); /** * Turn a route string such as `/user/:name` into a regular expression. * * Used for: * * - routes to ignore * - route transition resolution * * @see https://www.npmjs.com/package/path-to-regexp */ const pathToRegexp = ptr; export { pathToRegexp }; ================================================ FILE: packages/core/src/utils/history.ts ================================================ import { HistoryAction, LinkEvent, Trigger } from '../defs'; // Schemas import { schemaAttribute } from '../schemas/attribute'; /** * @barba/core/utils/history *

    * ## History manager. * * - Keep track of the navigation history * * @module core/utils/history * @preferred */ /** * State item. * * @property from * @property index */ interface IHistoryItem { /** origin */ from: string; /** index */ index: number; /** states */ states: IStateItem[]; } /***/ interface ICoords { x: number; y: number; } /** * History item. * * @property namespace * @property scroll * @property URL */ interface IStateItem { /** data */ data: object; /** namespace */ ns: string | undefined; /** Scroll position */ scroll: ICoords; /** URL */ url: string; } export class History { private _session: string; private _states: IStateItem[] = []; private _pointer = -1; /** * Init with first state. */ public init(url: string, ns: string): void { this._session = 'barba'; const state: IStateItem = { data: {}, ns, scroll: { x: window.scrollX, y: window.scrollY, }, url, }; this._pointer = 0; this._states.push(state); const item: IHistoryItem = { from: this._session, index: this._pointer, states: [...this._states], }; window.history && window.history.replaceState(item, '', url); } public change( url: string, trigger: Trigger, e?: LinkEvent | PopStateEvent ): Trigger { if (e && (e as PopStateEvent).state) { // If popstate, move to existing state // and get back/forward direction. const { state }: { state: IHistoryItem } = e as PopStateEvent; const { index } = state; const diff = this._pointer - index; trigger = this._getDirection(diff); // Work with previous states this.replace(state.states); this._pointer = index; } else { // Add new state this.add(url, trigger); } return trigger; } /** * Add a new state. */ public add(url: string, trigger: Trigger, action?: HistoryAction, data?: object): void { // If no state, it will be updated later. const ns = 'tmp'; const method = action ?? this._getAction(trigger); const state: IStateItem = { data: data ?? {}, ns, scroll: { x: window.scrollX, y: window.scrollY, }, url, }; switch (method) { case 'push': this._pointer = this.size; this._states.push(state); break; case 'replace': this.set(this._pointer, state); break; /* istanbul ignore next */ default: } const item: IHistoryItem = { from: this._session, index: this._pointer, states: [...this._states], }; switch (method) { case 'push': window.history && window.history.pushState(item, '', url); break; case 'replace': window.history && window.history.replaceState(item, '', url); break; /* istanbul ignore next */ default: } } /** * Store custom user data per state. */ public store(data: object, i?: number): void { const index = i || this._pointer; const state = this.get(index); // merge data (allow data overwrite) state.data = { ...state.data, ...data }; // update states this.set(index, state); const item: IHistoryItem = { from: this._session, index: this._pointer, states: [...this._states], }; // update browser history window.history.replaceState(item, ''); } /** * Update state. */ public update(data: any, i?: number): void { const index = i || this._pointer; const existing = this.get(index); const state: IStateItem = { ...existing, ...data, }; this.set(index, state); } /** * Remove last state. */ public remove(i?: number): void { if (i) { this._states.splice(i, 1); } else { this._states.pop(); } this._pointer--; } /** * Delete all states. */ public clear(): void { this._states = []; this._pointer = -1; } /** * Replace all states. */ public replace(newStates: IStateItem[]): void { this._states = newStates; } /** * Get state by index. */ public get(index: number): IStateItem { return this._states[index]; } /** * Set state by index. */ public set(i: number, state: IStateItem) { return (this._states[i] = state); } /** * Get the current state. */ get current(): IStateItem { return this._states[this._pointer]; } /** * Get the previous state. */ get previous(): IStateItem | null { return this._pointer < 1 ? null : this._states[this._pointer - 1]; } /** * Get the state size. */ get size(): number { return this._states.length; } /** * Get the history action: push vs replace */ private _getAction(trigger: Trigger): HistoryAction { let action: HistoryAction = 'push'; // Manage `data-barba-history` attribute // to get the right action (push vs replace). const el = trigger as HTMLAnchorElement; const attr = `${schemaAttribute.prefix}-${schemaAttribute.history}`; if (el.hasAttribute && el.hasAttribute(attr)) { action = el.getAttribute(attr) as HistoryAction; } return action; } /** * Get the direction of popstate change */ private _getDirection(diff: number): Trigger { // Check if "session switch" if (Math.abs(diff) > 1) { // Ex 6-0 > 0 -> forward, 0-6 < 0 -> back return diff > 0 ? 'forward' : 'back'; } else { if (diff === 0) { return 'popstate'; } else { // Ex 6-5 > 0 -> back, 5-6 < 0 -> forward return diff > 0 ? 'back' : 'forward'; } } } } const history = new History(); export { history }; ================================================ FILE: packages/core/src/utils/index.ts ================================================ import * as helpers from './helpers'; import * as url from './url'; export * from './dom'; export * from './history'; export * from './request'; export * from './run-async'; export { helpers, url }; ================================================ FILE: packages/core/src/utils/request.ts ================================================ /** * @barba/core/utils/request *

    * ## Fetch pages for transitions. * * - Includes timeout * - Uses Fetch API * - Handles errors * * @module core/utils/request * @preferred */ /***/ // Definitions import { Cache } from '@barba/core/src/modules/Cache'; import { Headers } from '@barba/core/src/modules/Headers'; import { IResponse, RequestError } from '../defs'; import { parse } from './url'; /** * Init a page request. * Fetch the page and returns a promise with the text content. */ function request( url: string, ttl: number = 2e3, requestError: RequestError, cache: Cache, headers: Headers ): Promise { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.onreadystatechange = () => { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { /* istanbul ignore next: bypass jest since xhr-mock doesn't support custom xhr.responseURL */ const responseURL = xhr.responseURL !== '' && xhr.responseURL !== url ? xhr.responseURL : url; resolve({ html: xhr.responseText, url: { href: responseURL, ...parse(responseURL) }, }); cache.update(url, { status: 'fulfilled', target: responseURL }); } else if (xhr.status) { // HTTP code is not 200, reject with response. const response = { status: xhr.status, statusText: xhr.statusText, }; requestError(url, response); reject(response); cache.update(url, { status: 'rejected' }); } } }; xhr.ontimeout = () => { const error = new Error(`Timeout error [${ttl}]`); requestError(url, error); reject(error); cache.update(url, { status: 'rejected' }); }; xhr.onerror = () => { const error = new Error(`Fetch error`); requestError(url, error); reject(error); cache.update(url, { status: 'rejected' }); }; xhr.open('GET', url); xhr.timeout = ttl; xhr.setRequestHeader( 'Accept', 'text/html,application/xhtml+xml,application/xml' ); xhr.setRequestHeader('x-barba', 'yes'); headers.all().forEach((value, key) => { xhr.setRequestHeader(key, value); }); xhr.send(); }); } export { request }; ================================================ FILE: packages/core/src/utils/run-async.ts ================================================ import isPromise from 'is-promise'; // https://github.com/SBoudrias/run-async /* istanbul ignore next */ export function runAsync( func: (...args: any[]) => void | Promise, ctx: any = {} ): (...args: any[]) => Promise { return (...args: any[]) => { let async = false; const promise = new Promise((resolve, reject) => { // Add async to context ctx.async = () => { async = true; return (err: any, value: any) => { if (err) { reject(err); } else { resolve(value); } }; }; const answer = func.apply(ctx, args as []); if (!async) { if (isPromise(answer)) { (answer as Promise).then(resolve, reject); } else { resolve(answer); } } }); return promise; }; } ================================================ FILE: packages/core/src/utils/url.ts ================================================ /** * @barba/core/utils/url *

    * ## URL utils. * * - Collect and structure informations from URLs * * @module core/utils/url */ /***/ // Definitions import { IGenericObject, IUrlBase } from '../defs'; /** * Get location href. */ export const getHref = () => window.location.href; /** * Get absolute href from URL. */ export const getAbsoluteHref = (url: string, base: string = document.baseURI): string => new URL(url, base).href; /** * Get location origin. */ export const getOrigin = () => window.location.origin; /** * Get port based on URL or location. */ export const getPort = (url: string = window.location.href) => parse(url).port; /** * Get path from URL. */ export const getPath = (url: string = window.location.href) => parse(url).path; /** * Get query object from URL. */ export const getQuery = (url: string, stringify: boolean = false): IGenericObject|string => { return stringify ? JSON.stringify(parse(url).query) : parse(url).query; }; /** * Get hash from URL. */ export const getHash = (url: string): string => parse(url).hash; /** * Parse URL for path, query and hash and more. */ export const parse = (url: string): IUrlBase => { // Port let port; const matches = url.match(/:\d+/); if (matches === null) { if (/^http/.test(url)) { port = 80; } if (/^https/.test(url)) { port = 443; } } else { const portString = matches[0].substring(1); port = parseInt(portString, 10); } // Path let path = url.replace(getOrigin(), ''); let hash; let query = {}; // Hash const hashIndex = path.indexOf('#'); if (hashIndex >= 0) { hash = path.slice(hashIndex + 1); path = path.slice(0, hashIndex); } // Query const queryIndex = path.indexOf('?'); if (queryIndex >= 0) { query = parseQuery(path.slice(queryIndex + 1)); path = path.slice(0, queryIndex); } return { hash, path, port, query, }; }; /** * Parse a query string to object. */ export const parseQuery = (str: string) => str.split('&').reduce((acc: IGenericObject, el: string) => { const [key, value] = el.split('='); acc[key] = value; return acc; }, {}); /** * Clean URL, remove "hash" and/or "trailing slash". */ export const clean = (url: string = window.location.href) => url.replace(/(\/#.*|\/|#.*)$/, ''); ================================================ FILE: packages/css/.npmignore ================================================ ### Custom ### /__e2e__ /__mocks__ /__tests__ /__web__ /jest.config.js /mangle.json /*.md /.rts2* !/README.md !/CHANGELOG.md ================================================ FILE: packages/css/AUTHORS ================================================ Luigi De Rosa (https://luruke.com/) Thierry Michel (https://www.epic.net/) Xavier Foucrier (https://xavierfoucrier.dev/) ================================================ FILE: packages/css/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [2.1.16](https://github.com/barbajs/barba/compare/@barba/css@2.1.15...@barba/css@2.1.16) (2024-05-10) ### Bug Fixes - **core:** :pencil2: fix typos in tests ([a748356](https://github.com/barbajs/barba/commit/a748356daf77117a120783eee4750c16e7603ad2)) - **css:** :bug: fix broken transition with sync mode and popstate events ([ae6f5f7](https://github.com/barbajs/barba/commit/ae6f5f7359d5994efd5f1d3379fc27ac6992fbb4)), closes [#559](https://github.com/barbajs/barba/issues/559) ## [2.1.15](https://github.com/barbajs/barba/compare/@barba/css@2.1.14...@barba/css@2.1.15) (2019-11-25) ### Bug Fixes - **root:** :art: improve typings for TS ([48f0637](https://github.com/barbajs/barba/commit/48f0637)) ## [2.1.14](https://github.com/barbajs/barba/compare/@barba/css@2.1.13...@barba/css@2.1.14) (2019-11-25) **Note:** Version bump only for package @barba/css ## [2.1.13](https://github.com/barbajs/barba/compare/@barba/css@2.1.12...@barba/css@2.1.13) (2019-11-06) ### Bug Fixes - **core:** :ok_hand: resolve once transitions ([20cafe1](https://github.com/barbajs/barba/commit/20cafe1)), closes [#439](https://github.com/barbajs/barba/issues/439) ## [2.1.12](https://github.com/barbajs/barba/compare/@barba/css@2.1.11...@barba/css@2.1.12) (2019-11-05) ### Bug Fixes - **root:** :bug: fix context for views and add to transitions ([9054673](https://github.com/barbajs/barba/commit/9054673)), closes [#467](https://github.com/barbajs/barba/issues/467) ## [2.1.11](https://github.com/barbajs/barba/compare/@barba/css@2.1.10...@barba/css@2.1.11) (2019-08-02) ### Bug Fixes - **css:** :bug: do not trigger `enter` transition on first load ([f283cd2](https://github.com/barbajs/barba/commit/f283cd2)), closes [#393](https://github.com/barbajs/barba/issues/393) ## [2.1.10](https://github.com/barbajs/barba/compare/@barba/css@2.1.9...@barba/css@2.1.10) (2019-07-16) ### Bug Fixes - **root:** :mute: remove print version ([be5aa73](https://github.com/barbajs/barba/commit/be5aa73)), closes [#415](https://github.com/barbajs/barba/issues/415) ## [2.1.9](https://github.com/barbajs/barba/compare/@barba/css@2.1.8...@barba/css@2.1.9) (2019-06-11) **Note:** Version bump only for package @barba/css ## [2.1.8](https://github.com/barbajs/barba/compare/@barba/css@2.1.7...@barba/css@2.1.8) (2019-04-29) **Note:** Version bump only for package @barba/css ## [2.1.7](https://github.com/barbajs/barba/compare/@barba/css@2.1.6...@barba/css@2.1.7) (2019-04-29) **Note:** Version bump only for package @barba/css ## [2.1.6](https://github.com/barbajs/barba/compare/@barba/css@2.1.5...@barba/css@2.1.6) (2019-04-23) ### Bug Fixes - **css:** :bug: remove current container after leave ([a06ec61](https://github.com/barbajs/barba/commit/a06ec61)) ## [2.1.5](https://github.com/barbajs/barba/compare/@barba/css@2.1.4...@barba/css@2.1.5) (2019-04-14) ### Bug Fixes - **css:** :bug: trigger overriden global hooks ([5435702](https://github.com/barbajs/barba/commit/5435702)) ## [2.1.4](https://github.com/barbajs/barba/compare/@barba/css@2.1.3...@barba/css@2.1.4) (2019-04-14) ### Bug Fixes - **css:** :recycle: rewrite CSS plugin ([12ce8ac](https://github.com/barbajs/barba/commit/12ce8ac)) ## [2.1.3](https://github.com/barbajs/barba/compare/@barba/css@2.1.2...@barba/css@2.1.3) (2019-04-13) ### Bug Fixes - **css:** :loud_sound: print version ([3a222d3](https://github.com/barbajs/barba/commit/3a222d3)) ### Reverts - **root:** :bug: revert failed release ([2b8a1ef](https://github.com/barbajs/barba/commit/2b8a1ef)) ## [2.1.3](https://github.com/barbajs/barba/compare/@barba/css@2.1.2...@barba/css@2.1.3) (2019-04-13) ### Bug Fixes - **css:** :loud_sound: print version ([3a222d3](https://github.com/barbajs/barba/commit/3a222d3)) ## [2.1.2](https://github.com/barbajs/barba/compare/@barba/css@2.1.1...@barba/css@2.1.2) (2019-04-13) ### Bug Fixes - **core:** :mute: remove debug logs ([f4ce952](https://github.com/barbajs/barba/commit/f4ce952)) ## [2.1.1](https://github.com/barbajs/barba/compare/@barba/css@2.1.0...@barba/css@2.1.1) (2019-04-13) ### Bug Fixes - **css:** :bug: fix transitions public hooks ([af5fa22](https://github.com/barbajs/barba/commit/af5fa22)) # 2.1.0 (2019-03-17) ### Bug Fixes - **css:** :bug: fix css with next tick ([63642bf](https://github.com/barbajs/barba/commit/63642bf)) ### Features - **css:** :recycle: add transitionend logic + big refactoring ([b775358](https://github.com/barbajs/barba/commit/b775358)) - **css:** :tada: initial commit ([aed8206](https://github.com/barbajs/barba/commit/aed8206)) ================================================ FILE: packages/css/LICENSE ================================================ MIT License Copyright (c) 2024 Luigi De Rosa, Thierry Michel, Xavier Foucrier 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: packages/css/README.md ================================================ # @barba/css [![NPM version](https://img.shields.io/npm/v/@barba/css?style=flat-square)](https://www.npmjs.com/package/@barba/css) [![Dependencies](https://img.shields.io/librariesio/release/npm/@barba/css?style=flat-square)](https://github.com/barbajs/barba/network/dependencies) > TBD ([GitHub repo](https://github.com/barbajs/barba.js)) ## Install Using npm: ```sh npm install --save-dev @barba/css ``` or using yarn: ```sh yarn add @barba/css --dev ``` ================================================ FILE: packages/css/__e2e__/default.spec.js ================================================ describe('Transition', () => { it('works', () => { cy.prepare('/index.html', 'home', 'home'); // Click link cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting cy.get('[data-test=link]').click(); cy.final('/page.html', 'page', 'page'); cy.get('@next') .should('not.have.class', 'barba-enter') .should('not.have.class', 'barba-enter-to') .should('not.have.class', 'barba-enter-active'); }); }); ================================================ FILE: packages/css/__e2e__/named.spec.js ================================================ describe('Named transition', () => { it('works', () => { cy.prepare('/named.html', 'named', 'named'); // Click link cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting cy.get('[data-test=link]').click(); cy.final('/page.html', 'page', 'page'); cy.get('@next') .should('not.have.class', 'named-enter') .should('not.have.class', 'named-enter-to') .should('not.have.class', 'named-enter-active'); }); }); ================================================ FILE: packages/css/__e2e__/once.spec.js ================================================ describe('Once transition', () => { it('works', () => { cy.prepare('/once.html', 'once', 'once'); // Click link // cy.get('[data-test=link]').click(); // cy.final('/page.html', 'page', 'page'); cy.get('@current') .should('not.have.class', 'named-enter') .should('not.have.class', 'named-enter-to') .should('not.have.class', 'named-enter-active'); }); }); ================================================ FILE: packages/css/__tests__/css.classes.test.ts ================================================ import css from '../src'; // Dom const container = document.createElement('div'); const step = 'test'; it('add/remove class', () => { css.add(container, step); expect(container.classList.contains(`barba-${step}`)).toBeTruthy(); css.remove(container, step); expect(container.classList.contains(`barba-${step}`)).toBeFalsy(); }); ================================================ FILE: packages/css/__tests__/css.hooks.test.ts ================================================ /* tslint:disable:no-string-literal */ import barba from '@barba/core/src'; // Definitions import { ISchemaPage, ITransitionData } from '@barba/core/src/defs'; import css from '../src'; // Dom const wrapper = document.createElement('div'); const current = document.createElement('div'); const next = document.createElement('div'); wrapper.dataset.barba = 'wrapper'; current.dataset.barba = 'container'; document.body.appendChild(wrapper); document.body.appendChild(current); const t = { enter: () => Promise.resolve(), leave: () => Promise.resolve(), once: () => Promise.resolve(), }; const data: ITransitionData = { current: ({ container: current } as unknown) as ISchemaPage, next: ({ container: next } as unknown) as ISchemaPage, trigger: 'barba', }; barba.use(css); barba.init(); css.start = jest.fn(); css.next = jest.fn(); css.end = jest.fn(); it('do once hooks', async () => { await barba.hooks.do('beforeOnce', data, t); await barba.hooks.do('afterOnce', data, t); expect(css.start).toHaveBeenCalledWith(next, 'once'); expect(css.end).toHaveBeenCalledWith(next, 'once'); }); it('do leave hooks', async () => { await barba.hooks.do('beforeLeave', data, t); await barba.hooks.do('afterLeave', data, t); expect(css.start).toHaveBeenCalledWith(current, 'leave'); expect(css.end).toHaveBeenCalledWith(current, 'leave'); }); it('do enter hooks on first load', async () => { // Remove from history to simulate first page load. barba.history.remove(); await barba.hooks.do('beforeEnter', data, t); await barba.hooks.do('afterEnter', data, t); expect(css.start).toHaveBeenCalledWith(next, 'enter'); expect(css.end).toHaveBeenCalledWith(next, 'enter'); }); it('do enter hooks', async () => { await barba.hooks.do('beforeEnter', data, t); await barba.hooks.do('afterEnter', data, t); expect(css.start).toHaveBeenCalledWith(next, 'enter'); expect(css.end).toHaveBeenCalledWith(next, 'enter'); }); it('override transitions', async () => { await barba.transitions.once(data, t); await barba.transitions.leave(data, t); await barba.transitions.enter(data, t); expect(css.next).toHaveBeenNthCalledWith(1, next, 'once'); expect(css.next).toHaveBeenNthCalledWith(2, current, 'leave'); expect(css.next).toHaveBeenNthCalledWith(3, next, 'enter'); }); ================================================ FILE: packages/css/__tests__/css.init.test.ts ================================================ /* tslint:disable:no-string-literal */ import barba from '@barba/core/src'; import { version } from '../package.json'; import css from '../src'; // Dom const wrapper = document.createElement('div'); const container = document.createElement('div'); wrapper.dataset.barba = 'wrapper'; container.dataset.barba = 'container'; document.body.appendChild(wrapper); document.body.appendChild(container); it('has defaults', () => { expect(css.version).toBe(version); expect(css.prefix).toBe('barba'); expect(css.callbacks).toEqual({}); }); it('registers hooks', () => { barba.use(css); barba.init(); expect(barba.hooks.registered.get('before').size).toBe(1); expect(barba.hooks.registered.get('beforeOnce').size).toBe(2); expect(barba.hooks.registered.get('afterOnce').size).toBe(1); expect(barba.hooks.registered.get('beforeLeave').size).toBe(1); expect(barba.hooks.registered.get('afterLeave').size).toBe(1); expect(barba.hooks.registered.get('beforeEnter').size).toBe(1); expect(barba.hooks.registered.get('afterEnter').size).toBe(1); }); it('overrides transitions', () => { expect(barba.transitions['once']).toBe(css['_once']); expect(barba.transitions['leave']).toBe(css['_leave']); expect(barba.transitions['enter']).toBe(css['_enter']); }); ================================================ FILE: packages/css/__tests__/css.prefix.test.ts ================================================ /* tslint:disable:no-empty */ import barba from '@barba/core/src'; import css from '../src'; import { Css } from '../src/css'; // Dom const wrapper = document.createElement('div'); const container = document.createElement('div'); wrapper.dataset.barba = 'wrapper'; container.dataset.barba = 'container'; document.body.appendChild(wrapper); document.body.appendChild(container); // Transitions const name = 'my-name'; const unnamed = { once() {}, leave() {}, enter() {}, }; const named = { ...unnamed, name, }; const data = { current: { container }, next: { container }, }; barba.use(css); barba.init({ transitions: [named, unnamed], }); it('prefixes with transition name', async () => { await barba.hooks.do('before', data, named); expect(css.prefix).toBe(name); css.prefix = null; await barba.hooks.do('beforeOnce', data, named); expect(css.prefix).toBe(name); }); it('prefixes with default', async () => { await barba.hooks.do('before', data, unnamed); expect(css.prefix).toBe('barba'); css.prefix = null; await barba.hooks.do('beforeOnce', data, unnamed); expect(css.prefix).toBe('barba'); }); // DEV // it('adds and removes default CSS classes', async () => { // barba.hooks.do('beforeOnce', {}, unnamed); // await checkHooks(); // }); // it('adds and removes named CSS classes', async () => { // barba.hooks.do('beforeOnce', {}, named); // await checkHooks(name); // }); ================================================ FILE: packages/css/__tests__/css.states.test.ts ================================================ /* tslint:disable:no-string-literal */ import barba from '@barba/core/src'; import css from '../src'; // Dom const container = document.createElement('div'); const kind = 'test'; css.install(barba); css.add = jest.fn(); css.remove = jest.fn(); container.addEventListener = jest.fn(); container.removeEventListener = jest.fn(); it('do start', async () => { await css.start(container, kind); expect(css.add).toHaveBeenNthCalledWith(1, container, kind); expect(css.add).toHaveBeenNthCalledWith(2, container, `${kind}-active`); }); it('do next', async () => { css['_checkTransition'] = jest.fn().mockReturnValue(true); css.next(container, kind); expect.assertions(4); expect(css.callbacks[kind]).toBeDefined(); expect(container.addEventListener).toHaveBeenCalledTimes(1); await barba.helpers.nextTick(); expect(css.remove).toHaveBeenNthCalledWith(1, container, kind); expect(css.add).toHaveBeenCalledTimes(1); // DEV not working?!?? // expect(css.add).toHaveBeenNthCalledWith(2, container, `${kind}-to`); }); it('do end', async () => { await css.end(container, kind); expect(css.remove).toHaveBeenNthCalledWith(1, container, `${kind}-to`); expect(css.remove).toHaveBeenNthCalledWith(2, container, `${kind}-active`); expect(container.removeEventListener).toHaveBeenCalledTimes(1); }); it('do next with no CSS transition', async () => { css['_checkTransition'] = jest.fn().mockReturnValue(false); await css.next(container, kind); expect(css.remove).toHaveBeenNthCalledWith(1, container, kind); expect(css.add).toHaveBeenNthCalledWith(1, container, `${kind}-to`); expect(container.removeEventListener).toHaveBeenCalledTimes(0); }); ================================================ FILE: packages/css/__web__/index.html ================================================ home

    home

    Go to page
    ================================================ FILE: packages/css/__web__/named.html ================================================ named

    named

    Go to page
    ================================================ FILE: packages/css/__web__/once.html ================================================ once

    once

    Go to page
    ================================================ FILE: packages/css/__web__/page.html ================================================ page

    page

    Go to home
    ================================================ FILE: packages/css/__web__/scripts/default.js ================================================ console.info('🚀 Barba e2e'); const { barba, barbaCss: css } = window; barba.use(css); barba.init({ debug: true }); ================================================ FILE: packages/css/__web__/scripts/named.js ================================================ console.info('🚀 Barba e2e'); const { barba, barbaCss: css } = window; barba.use(css); barba.init({ transitions: [{ name: 'named' }], }); ================================================ FILE: packages/css/__web__/styles/default.css ================================================ [data-barba='container'] { opacity: 1; } .barba-enter-active, .barba-leave-active { transition: opacity 2s; } .barba-leave, .barba-enter-to { opacity: 1; } .barba-enter, .barba-leave-to { opacity: 0; } ================================================ FILE: packages/css/__web__/styles/named.css ================================================ [data-barba='container'] { opacity: 1; } .named-enter-active, .named-leave-active { transition: opacity 2s; } .named-leave, .named-enter-to { opacity: 1; } .named-enter, .named-leave-to { opacity: 0; } ================================================ FILE: packages/css/__web__/styles/once.css ================================================ [data-barba='container'] { opacity: 1; } .barba-once { opacity: 0; } .barba-once-active { transition: opacity 5s; } .barba-once-to { opacity: 1; } ================================================ FILE: packages/css/jest.config.js ================================================ const jestBase = require('../../jest.config.js'); module.exports = { ...jestBase, }; ================================================ FILE: packages/css/package.json ================================================ { "name": "@barba/css", "version": "2.1.16", "description": "Style helper that manage you CSS classes during transitions", "publishConfig": { "access": "public" }, "main": "dist/barba-css.js", "umd:main": "dist/barba-css.umd.js", "browser": "dist/barba-css.umd.js", "unpkg": "dist/barba-css.umd.js", "module": "dist/barba-css.mjs", "source": "src/index.ts", "types": "dist/css/src/typings", "mangle": { "regex": "^_" }, "files": [ "dist" ], "keywords": [ "page", "transition", "animation", "css", "router", "prefetch" ], "homepage": "https://github.com/barbajs/barba#readme", "bugs": { "url": "https://github.com/barbajs/barba/issues" }, "license": "MIT", "repository": { "type": "git", "url": "git+ssh://git@github.com/barbajs/barba.git" }, "scripts": { "build": "microbundle --name barbaCss", "build:watch": "microbundle watch --name barbaCss", "clear": "rimraf dist", "lint": "tslint src/**", "precommit": "lint-staged", "size": "echo '💄 css' && gzip-size ./dist/barba-css.umd.js", "tag:latest": "npm dist-tag add @barba/css@$npm_package_version latest", "tag:next": "npm dist-tag add @barba/css@$npm_package_version next" }, "gitHead": "33c213bc36a0996f6333185dfc695fcd0d37c9d9" } ================================================ FILE: packages/css/src/css.ts ================================================ /** * @barba/css *

    * ## Barba CSS. * * - Add CSS classes * - Manage CSS transitions * * @module css * @preferred */ /***/ // Definitions import { IBarbaPlugin, ITransitionData, ITransitionPage, } from '@barba/core/src/defs'; import { Core } from '@barba/core/src/core'; import { Logger } from '@barba/core/src/modules/Logger'; import { ICssCallbacks } from './defs'; import { version } from '../package.json'; export class Css implements IBarbaPlugin<{}> { public name = '@barba/css'; public version = version; public barba: Core; public logger: Logger; public prefix: string = 'barba'; public callbacks: ICssCallbacks = {}; public cb: any; // Check if transition property applied private _hasTransition: boolean = false; /** * Plugin installation. */ public install(barba: Core) { this.logger = new barba.Logger(this.name); this.logger.info(this.version); this.barba = barba; this._once = this._once.bind(this); this._leave = this._leave.bind(this); this._enter = this._enter.bind(this); } /** * Plugin installation. */ public init() { // Register hooks to get prefix this.barba.hooks.before(this._getPrefix, this); this.barba.hooks.beforeOnce(this._getPrefix, this); // Register hook for CSS classes this.barba.hooks.beforeOnce(this._beforeOnce, this); this.barba.hooks.afterOnce(this._afterOnce, this); this.barba.hooks.beforeLeave(this._beforeLeave, this); this.barba.hooks.afterLeave(this._afterLeave, this); this.barba.hooks.beforeEnter(this._beforeEnter, this); this.barba.hooks.afterEnter(this._afterEnter, this); // Override main transitions this.barba.transitions.once = this._once; this.barba.transitions.leave = this._leave; this.barba.transitions.enter = this._enter; // Add empty default transition (force prepend) /* istanbul ignore next */ this.barba.transitions.store.all.unshift({ name: 'barba', once() {}, // tslint:disable-line:no-empty leave() {}, // tslint:disable-line:no-empty enter() {}, // tslint:disable-line:no-empty }); this.barba.transitions.store.update(); } /** * Initial state. */ public async start(container: HTMLElement, kind: string): Promise { // Set initial CSS values this.add(container, kind); // CSS: add kind await this.barba.helpers.nextTick(); // Apply CSS transition this.add(container, `${kind}-active`); // CSS: add kind-active await this.barba.helpers.nextTick(); } /** * Next frame state. */ public async next(container: HTMLElement, kind: string): Promise { this._hasTransition = this._checkTransition(container); if (this._hasTransition) { // We need to listen the end of the animation return new Promise(async resolve => { this.cb = resolve; this.callbacks[kind] = resolve; container.addEventListener('transitionend', resolve, false); this.remove(container, kind); // CSS: remove kind await this.barba.helpers.nextTick(); this.add(container, `${kind}-to`); // CSS: add kind-to await this.barba.helpers.nextTick(); }); } else { this.remove(container, kind); // CSS: remove kind await this.barba.helpers.nextTick(); this.add(container, `${kind}-to`); // CSS: add kind-to await this.barba.helpers.nextTick(); } } /** * Final state. */ public async end(container: HTMLElement, kind: string): Promise { this.remove(container, `${kind}-to`); // CSS: remove kind-to this.remove(container, `${kind}-active`); // CSS: remove kind-active container.removeEventListener('transitionend', this.callbacks[kind]); this._hasTransition = false; } /** * Add CSS classes. */ public add(el: HTMLElement, step: string): void { el.classList.add(`${this.prefix}-${step}`); } /** * Remove CSS classes. */ public remove(el: HTMLElement, step: string): void { el.classList.remove(`${this.prefix}-${step}`); } /** * Get CSS prefix from transition `name` property. */ private _getPrefix(data: ITransitionData, t: ITransitionPage): void { this.prefix = t.name || 'barba'; } /** * Check if CSS transition is applied */ private _checkTransition(container: HTMLElement) { // DEV: check for CSS animation property? return getComputedStyle(container).transitionDuration !== '0s'; } /** * `beforeOnce` hook. */ private _beforeOnce(data: ITransitionData): Promise { return this.start(data.next.container, 'once'); } /** * `once` hook. */ private async _once( data: ITransitionData, t: ITransitionPage ): Promise { await this.barba.hooks.do('once', data, t); return this.next(data.next.container, 'once'); } /** * `afterOnce` hook. */ private _afterOnce(data: ITransitionData): Promise { return this.end(data.next.container, 'once'); } /** * `beforeLeave` hook. */ private _beforeLeave(data: ITransitionData): Promise { return this.start(data.current.container, 'leave'); } /** * `leave` hook. */ private async _leave( data: ITransitionData, t: ITransitionPage ): Promise { await this.barba.hooks.do('leave', data, t); return this.next(data.current.container, 'leave'); } /** * `afterLeave` hook. */ private _afterLeave(data: ITransitionData): Promise { this.end(data.current.container, 'leave'); // For CSS transitions, we need to remove current container // directly after the leave transition this.barba.transitions.remove(data); return Promise.resolve(); } /** * `beforeEnter` hook. */ private _beforeEnter(data: ITransitionData): Promise { return this.start(data.next.container, 'enter'); } /** * `enter` hook. */ private async _enter( data: ITransitionData, t: ITransitionPage ): Promise { await this.barba.hooks.do('enter', data, t); return this.next(data.next.container, 'enter'); } /** * `afterEnter` hook. */ private _afterEnter(data: ITransitionData): Promise { return this.end(data.next.container, 'enter'); } } const css = new Css(); export default css; ================================================ FILE: packages/css/src/defs/index.ts ================================================ /** * @module typings/css */ type CssKinds = 'once' | 'leave' | 'enter'; export interface ICssCallbacks { [key: string]: EventListenerOrEventListenerObject; } ================================================ FILE: packages/css/src/index.ts ================================================ import * as t from './typings'; export { default } from './css'; ================================================ FILE: packages/css/src/typings.ts ================================================ export * from './defs'; export { default } from './css'; ================================================ FILE: packages/prefetch/.npmignore ================================================ ### Custom ### /__e2e__ /__mocks__ /__tests__ /__web__ /jest.config.js /mangle.json /*.md /.rts2* !/README.md !/CHANGELOG.md ================================================ FILE: packages/prefetch/AUTHORS ================================================ Luigi De Rosa (https://luruke.com/) Thierry Michel (https://www.epic.net/) Xavier Foucrier (https://xavierfoucrier.dev/) ================================================ FILE: packages/prefetch/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. # [2.2.0](https://github.com/barbajs/barba/compare/@barba/prefetch@2.1.10...@barba/prefetch@2.2.0) (2024-05-10) ### Bug Fixes - **prefetch:** :bug: fix missing request headers argument ([56aae92](https://github.com/barbajs/barba/commit/56aae920b1b5d08c221ff136d905aee03bf0b249)) - **prefetch:** :bug: prevent prefetch plugin to cache relative URLs ([4f7ba02](https://github.com/barbajs/barba/commit/4f7ba028be7024dde39be96c1888e561265d9187)) ### Features - **prefetch:** :sparkles: add `limit` option to `IPrefetchOptions` interface ([28460cb](https://github.com/barbajs/barba/commit/28460cb37a028d480e7949a810eae85c37a3da7d)) - **prefetch:** :sparkles: limit links to prefetch ([1288029](https://github.com/barbajs/barba/commit/1288029116089d88d7041c82673f36d226640c33)) ## [2.1.10](https://github.com/barbajs/barba/compare/@barba/prefetch@2.1.9...@barba/prefetch@2.1.10) (2019-11-25) ### Bug Fixes - **root:** :art: improve typings for TS ([48f0637](https://github.com/barbajs/barba/commit/48f0637)) ## [2.1.9](https://github.com/barbajs/barba/compare/@barba/prefetch@2.1.8...@barba/prefetch@2.1.9) (2019-11-25) **Note:** Version bump only for package @barba/prefetch ## [2.1.8](https://github.com/barbajs/barba/compare/@barba/prefetch@2.1.7...@barba/prefetch@2.1.8) (2019-11-05) **Note:** Version bump only for package @barba/prefetch ## [2.1.7](https://github.com/barbajs/barba/compare/@barba/prefetch@2.1.6...@barba/prefetch@2.1.7) (2019-10-22) ### Bug Fixes - **core:** :ambulance: fix URL with query/hash ([f5e639c](https://github.com/barbajs/barba/commit/f5e639c)), closes [#445](https://github.com/barbajs/barba/issues/445) ## [2.1.6](https://github.com/barbajs/barba/compare/@barba/prefetch@2.1.5...@barba/prefetch@2.1.6) (2019-07-16) ### Bug Fixes - **root:** :mute: remove print version ([be5aa73](https://github.com/barbajs/barba/commit/be5aa73)), closes [#415](https://github.com/barbajs/barba/issues/415) ## [2.1.5](https://github.com/barbajs/barba/compare/@barba/prefetch@2.1.4...@barba/prefetch@2.1.5) (2019-06-26) ### Bug Fixes - **core:** :ok_hand: improve support of SVG links ([19e7e5d](https://github.com/barbajs/barba/commit/19e7e5d)) ## [2.1.4](https://github.com/barbajs/barba/compare/@barba/prefetch@2.1.3...@barba/prefetch@2.1.4) (2019-06-11) **Note:** Version bump only for package @barba/prefetch ## [2.1.3](https://github.com/barbajs/barba/compare/@barba/prefetch@2.1.2...@barba/prefetch@2.1.3) (2019-04-29) **Note:** Version bump only for package @barba/prefetch ## [2.1.2](https://github.com/barbajs/barba/compare/@barba/prefetch@2.1.1...@barba/prefetch@2.1.2) (2019-04-13) ### Bug Fixes - **prefetch:** :loud_sound: print version ([a5beae3](https://github.com/barbajs/barba/commit/a5beae3)) ### Reverts - **root:** :bug: revert failed release ([2b8a1ef](https://github.com/barbajs/barba/commit/2b8a1ef)) ## [2.1.2](https://github.com/barbajs/barba/compare/@barba/prefetch@2.1.1...@barba/prefetch@2.1.2) (2019-04-13) ### Bug Fixes - **prefetch:** :loud_sound: print version ([a5beae3](https://github.com/barbajs/barba/commit/a5beae3)) ## [2.1.1](https://github.com/barbajs/barba/compare/@barba/prefetch@2.1.0...@barba/prefetch@2.1.1) (2019-04-13) ### Bug Fixes - **core:** :bug: fix sameUrl + anchors ([039f5d9](https://github.com/barbajs/barba/commit/039f5d9)), closes [#359](https://github.com/barbajs/barba/issues/359) - **core:** :bug: fix timeout error ([70b7805](https://github.com/barbajs/barba/commit/70b7805)), closes [#373](https://github.com/barbajs/barba/issues/373) - **prefetch:** :bug: fix wrong/missing context + catch error ([7220126](https://github.com/barbajs/barba/commit/7220126)) # 2.1.0 (2019-03-17) ### Bug Fixes - **prefetch:** :bug: fix check error ([f12a441](https://github.com/barbajs/barba/commit/f12a441)) ### Features - **prefetch:** :sparkles: add prefetch + refactor e2e scripts ([c90b85b](https://github.com/barbajs/barba/commit/c90b85b)) - **root:** :sparkles: add logger + fixes ([6db3875](https://github.com/barbajs/barba/commit/6db3875)) ================================================ FILE: packages/prefetch/LICENSE ================================================ MIT License Copyright (c) 2024 Luigi De Rosa, Thierry Michel, Xavier Foucrier 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: packages/prefetch/README.md ================================================ # @barba/prefetch [![NPM version](https://img.shields.io/npm/v/@barba/prefetch?style=flat-square)](https://www.npmjs.com/package/@barba/prefetch) [![Dependencies](https://img.shields.io/librariesio/release/npm/@barba/prefetch?style=flat-square)](https://github.com/barbajs/barba/network/dependencies) > TBD ([GitHub repo](https://github.com/barbajs/barba.js)) ## Install Using npm: ```sh npm install --save-dev @barba/prefetch ``` or using yarn: ```sh yarn add @barba/prefetch --dev ``` ================================================ FILE: packages/prefetch/__e2e__/prefetch.spec.js ================================================ describe('page', () => { it('Page is visible', () => { cy.visit('/'); expect(true).to.equal(true); }); }); ================================================ FILE: packages/prefetch/__tests__/prefetch.init.test.ts ================================================ /* tslint:disable:no-empty */ import barba from '@barba/core/src'; import { version } from '../package.json'; import prefetch from '../src'; const wrapper = document.createElement('div'); const container = document.createElement('div'); wrapper.dataset.barba = 'wrapper'; container.dataset.barba = 'container'; document.body.appendChild(wrapper); document.body.appendChild(container); // https://stackoverflow.com/questions/40743131/how-to-prevent-property-does-not-exist-on-type-global-with-jsdom-and-t (global as any).IntersectionObserver = class { public observe() {} public unobserve() {} }; (global as any).window.requestIdleCallback = jest.fn(); it('has defaults', () => { expect(prefetch.name).toBe('@barba/prefetch'); expect(prefetch.version).toBe(version); }); it('init with defaults', () => { prefetch.observe = jest.fn(); barba.use(prefetch); barba.init(); expect(prefetch.root).toBe(document.body); expect(prefetch.timeout).toBe(2e3); expect(prefetch.limit).toBe(0); expect(prefetch.observe).toHaveBeenCalled(); }); it('init with options', () => { prefetch.observe = jest.fn(); barba.destroy(); barba.use(prefetch, { limit: 0, root: wrapper, timeout: 0, }); barba.init(); expect(prefetch.root).toBe(wrapper); expect(prefetch.timeout).toBe(0); expect(prefetch.limit).toBe(0); }); it('registers hooks', () => { expect(barba.hooks.registered.has('after')).toBeTruthy(); }); it('warns with cache/prefetch disabled', () => { global.console.warn = jest.fn(); barba.use(prefetch); barba.init({ cacheIgnore: false, debug: true, prefetchIgnore: true, }); expect(global.console.warn).toHaveBeenCalledWith( '[@barba/prefetch] ', 'barba.prefetchIgnore is enabled' ); barba.init({ cacheIgnore: true, debug: true, prefetchIgnore: false, }); expect(global.console.warn).toHaveBeenCalledWith( '[@barba/prefetch] ', 'barba.cacheIgnore is enabled' ); }); ================================================ FILE: packages/prefetch/__web__/index.html ================================================ Test

    Hello prefetch

    ================================================ FILE: packages/prefetch/jest.config.js ================================================ const jestBase = require('../../jest.config.js'); module.exports = { ...jestBase, }; ================================================ FILE: packages/prefetch/package.json ================================================ { "name": "@barba/prefetch", "version": "2.2.0", "description": "Automatically fetch and cache your pages based on the viewport", "publishConfig": { "access": "public" }, "main": "dist/barba-prefetch.js", "umd:main": "dist/barba-prefetch.umd.js", "browser": "dist/barba-prefetch.umd.js", "unpkg": "dist/barba-prefetch.umd.js", "module": "dist/barba-prefetch.mjs", "source": "src/index.ts", "types": "dist/prefetch/src/typings", "mangle": { "regex": "^_" }, "files": [ "dist" ], "keywords": [ "page", "transition", "animation", "css", "router", "prefetch" ], "homepage": "https://github.com/barbajs/barba#readme", "bugs": { "url": "https://github.com/barbajs/barba/issues" }, "license": "MIT", "repository": { "type": "git", "url": "git+ssh://git@github.com/barbajs/barba.git" }, "scripts": { "build": "microbundle --name barbaPrefetch", "build:watch": "microbundle watch --name barbaPrefetch", "clear": "rimraf dist", "lint": "tslint src/**", "precommit": "lint-staged", "report": "source-map-explorer --html ./dist/barba-prefetch.umd.js > report.html", "size": "echo '🚀 prefetch' && gzip-size ./dist/barba-prefetch.umd.js", "tag:latest": "npm dist-tag add @barba/prefetch@$npm_package_version latest", "tag:next": "npm dist-tag add @barba/prefetch@$npm_package_version next" }, "gitHead": "33c213bc36a0996f6333185dfc695fcd0d37c9d9" } ================================================ FILE: packages/prefetch/src/defs/index.ts ================================================ /** * @module typings/prefetch */ export interface IPrefetchOptions { root?: HTMLElement | HTMLDocument; timeout?: number; limit?: number; } ================================================ FILE: packages/prefetch/src/index.ts ================================================ import * as t from './typings'; export { default } from './prefetch'; ================================================ FILE: packages/prefetch/src/polyfills/index.ts ================================================ // Browser support + polyfills // if (window.NodeList && !NodeList.prototype.forEach) { // NodeList.prototype.forEach = Array.prototype.forEach; // } export * from './requestIdleCallback'; ================================================ FILE: packages/prefetch/src/polyfills/requestIdleCallback.ts ================================================ /** * @module prefetch/polyfills */ /** * Copyright 2018 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // [source](https://github.com/GoogleChromeLabs/quicklink/blob/master/src/request-idle-callback.mjs) // RIC and shim for browsers setTimeout() without it export const requestIdleCallback = // @ts-ignore window.requestIdleCallback || function ric(cb: any) { const start = Date.now(); return setTimeout(() => { cb({ didTimeout: false, timeRemaining: function timeRemaining() { return Math.max(0, 50 - (Date.now() - start)); }, }); }, 1); }; ================================================ FILE: packages/prefetch/src/prefetch.ts ================================================ /** * @barba/prefetch *

    * ## Barba prefetch. * * @module prefetch * @preferred */ import { IPrefetchOptions } from './defs'; /***/ // Definitions import { Core } from '@barba/core/src/core'; import { IBarbaPlugin, Link } from '@barba/core/src/defs'; import { Logger } from '@barba/core/src/modules/Logger'; import { version } from '../package.json'; import { requestIdleCallback } from './polyfills'; class Prefetch implements IBarbaPlugin { public name = '@barba/prefetch'; public version = version; public barba: Core; public logger: Logger; public observer: IntersectionObserver; public root: HTMLElement | HTMLDocument; public timeout: number; public limit: number; public toPrefetch: Set = new Set(); /** * Plugin installation. */ public install( barba: Core, { root = document.body, timeout = 2e3, limit = 0 }: IPrefetchOptions = {} ) { this.logger = new barba.Logger(this.name); this.logger.info(this.version); this.barba = barba; this.root = root; this.timeout = timeout; this.limit = limit; } /** * Plugin initialisation. */ public init() { if (this.barba.prefetchIgnore) { this.logger.warn('barba.prefetchIgnore is enabled'); return; } if (this.barba.cacheIgnore) { this.logger.warn('barba.cacheIgnore is enabled'); return; } /** * Init intersection observer * when intersecting, it will check if URL should be prefetched * then unobserve the element * and, if no cache data, fetch the page */ /* istanbul ignore next */ this.observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (!entry.isIntersecting) { return; } const link = entry.target as Link; const href = this.barba.url.getAbsoluteHref(this.barba.dom.getHref(link)); if (!this.toPrefetch.has(href)) { return; } this.observer.unobserve(link); // Prefetch and cache if (!this.barba.cache.has(href)) { this.barba.cache.set( href, this.barba .request( href, this.barba.timeout, this.barba['onRequestError'].bind(this.barba, 'barba'), // tslint:disable-line:no-string-literal this.barba.cache, this.barba.headers ) .catch(error => { this.logger.error(error); }), 'prefetch', 'pending' ); } else { this.barba.cache.update(href, { action: 'prefetch' }); } }); }); this.observe(); // Register hooks this.barba.hooks.after(this.observe, this); } /* istanbul ignore next */ public observe(): void { const timeout = this.timeout; requestIdleCallback( () => { let links = Array.from(this.root.querySelectorAll('a')); if (this.limit > 0) { links = links.slice(0, this.limit); } // If not, find all links and use IntersectionObserver. links.forEach(el => { const link = (el as unknown) as Link; const href = this.barba.dom.getHref(link); if ( !this.barba.cache.has(href) && !this.barba.prevent.checkHref(href) && !this.barba.prevent.checkLink(link, {} as Event, href) ) { this.observer.observe(el); this.toPrefetch.add(href); } }); }, { timeout } ); } } const prefetch = new Prefetch(); export default prefetch; ================================================ FILE: packages/prefetch/src/typings.ts ================================================ export * from './defs'; export { default } from './prefetch'; ================================================ FILE: packages/router/.npmignore ================================================ ### Custom ### /__e2e__ /__mocks__ /__tests__ /__web__ /jest.config.js /mangle.json /*.md /.rts2* !/README.md !/CHANGELOG.md ================================================ FILE: packages/router/AUTHORS ================================================ Luigi De Rosa (https://luruke.com/) Thierry Michel (https://www.epic.net/) Xavier Foucrier (https://xavierfoucrier.dev/) ================================================ FILE: packages/router/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [2.1.11](https://github.com/barbajs/barba/compare/@barba/router@2.1.10...@barba/router@2.1.11) (2024-05-10) **Note:** Version bump only for package @barba/router ## [2.1.10](https://github.com/barbajs/barba/compare/@barba/router@2.1.9...@barba/router@2.1.10) (2019-12-12) **Note:** Version bump only for package @barba/router ## [2.1.9](https://github.com/barbajs/barba/compare/@barba/router@2.1.8...@barba/router@2.1.9) (2019-11-25) ### Bug Fixes - **root:** :art: improve typings for TS ([48f0637](https://github.com/barbajs/barba/commit/48f0637)) ## [2.1.8](https://github.com/barbajs/barba/compare/@barba/router@2.1.7...@barba/router@2.1.8) (2019-11-25) **Note:** Version bump only for package @barba/router ## [2.1.7](https://github.com/barbajs/barba/compare/@barba/router@2.1.6...@barba/router@2.1.7) (2019-11-05) **Note:** Version bump only for package @barba/router ## [2.1.6](https://github.com/barbajs/barba/compare/@barba/router@2.1.5...@barba/router@2.1.6) (2019-10-22) **Note:** Version bump only for package @barba/router ## [2.1.5](https://github.com/barbajs/barba/compare/@barba/router@2.1.4...@barba/router@2.1.5) (2019-07-16) ### Bug Fixes - **root:** :mute: remove print version ([be5aa73](https://github.com/barbajs/barba/commit/be5aa73)), closes [#415](https://github.com/barbajs/barba/issues/415) ## [2.1.4](https://github.com/barbajs/barba/compare/@barba/router@2.1.3...@barba/router@2.1.4) (2019-06-11) **Note:** Version bump only for package @barba/router ## [2.1.3](https://github.com/barbajs/barba/compare/@barba/router@2.1.2...@barba/router@2.1.3) (2019-04-29) **Note:** Version bump only for package @barba/router ## [2.1.2](https://github.com/barbajs/barba/compare/@barba/router@2.1.1...@barba/router@2.1.2) (2019-04-13) ### Bug Fixes - **router:** :loud_sound: print version ([70e9857](https://github.com/barbajs/barba/commit/70e9857)) ### Reverts - **root:** :bug: revert failed release ([2b8a1ef](https://github.com/barbajs/barba/commit/2b8a1ef)) ## [2.1.2](https://github.com/barbajs/barba/compare/@barba/router@2.1.1...@barba/router@2.1.2) (2019-04-13) ### Bug Fixes - **router:** :loud_sound: print version ([70e9857](https://github.com/barbajs/barba/commit/70e9857)) ## [2.1.1](https://github.com/barbajs/barba/compare/@barba/router@2.1.0...@barba/router@2.1.1) (2019-04-13) ### Bug Fixes - **core:** :bug: fix sameUrl + anchors ([039f5d9](https://github.com/barbajs/barba/commit/039f5d9)), closes [#359](https://github.com/barbajs/barba/issues/359) # 2.1.0 (2019-03-17) ### Bug Fixes - **root:** :bug: force publish ([ddb8798](https://github.com/barbajs/barba/commit/ddb8798)) ### Features - **core:** :sparkles: add prevent custom + update README ([2fb4ec6](https://github.com/barbajs/barba/commit/2fb4ec6)) - **router:** :sparkles: add multiple properties to `route` ([4e92c83](https://github.com/barbajs/barba/commit/4e92c83)) ================================================ FILE: packages/router/LICENSE ================================================ MIT License Copyright (c) 2024 Luigi De Rosa, Thierry Michel, Xavier Foucrier 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: packages/router/README.md ================================================ # @barba/router [![NPM version](https://img.shields.io/npm/v/@barba/router?style=flat-square)](https://www.npmjs.com/package/@barba/router) [![Dependencies](https://img.shields.io/librariesio/release/npm/@barba/router?style=flat-square)](https://github.com/barbajs/barba/network/dependencies) > TBD ([GitHub repo](https://github.com/barbajs/barba.js)) ## Install Using npm: ```sh npm install --save-dev @barba/router ``` or using yarn: ```sh yarn add @barba/router --dev ``` ================================================ FILE: packages/router/__e2e__/default.spec.js ================================================ /* eslint-disable cypress/no-unnecessary-waiting */ const logs = [ 'leave-from', 'enter-from', 'leave-default', 'enter-default', 'leave-to', 'enter-to', ]; describe('Route transition', () => { it('works', () => { cy.prepare('/index.html', 'home', 'home'); cy.get('[data-test="logs-list"]').as('logs'); // Alias to @hooks cy.wait(1000); // Wait for once complete // Click link cy.get('[data-test=link]').click(); // Check route "from" logs.forEach((name, i) => { if (i < 2) { cy.get('@logs') .find(`:nth-child(${i + 1})`) .should('contain', name); } }); // Go to default cy.get('[data-test=link2]').click(); // Check no route logs.forEach((name, i) => { if (i < 4) { cy.get('@logs') .find(`:nth-child(${i + 1})`) .should('contain', name); } }); // Back to home cy.get('[data-test=link]').click(); // Check route "to" logs.forEach((name, i) => { cy.get('@logs') .find(`:nth-child(${i + 1})`) .should('contain', name); }); // Go to page again, for final check cy.get('[data-test=link]').click(); cy.final('/page.html', 'page', 'page'); }); }); ================================================ FILE: packages/router/__tests__/router.test.ts ================================================ import barba from '@barba/core/src'; // Definitions import { ITransitionData } from '@barba/core/src/defs'; import { version } from '../package.json'; import router from '../src'; const wrapper = document.createElement('div'); const container = document.createElement('div'); wrapper.dataset.barba = 'wrapper'; container.dataset.barba = 'container'; document.body.appendChild(wrapper); document.body.appendChild(container); const routes = [ { name: 'home', path: '(/|/index.html)', }, { name: 'foo', path: '/foo/:bar', }, ]; it('has defaults', () => { expect(router.name).toBe('@barba/router'); expect(router.version).toBe(version); expect(router.routeNames).toHaveLength(0); router.install(barba); expect(router.routeNames).toHaveLength(0); }); it('has routes', () => { router.install(barba, { routes, }); expect([...router.routeNames]).toEqual(['home', 'foo']); expect(router.routesByName.foo).toEqual({ keys: [ { modifier: '', name: 'bar', pattern: '[^\\/#\\?]+?', prefix: '/', suffix: '', }, ], path: '/foo/:bar', regex: /^\/foo(?:\/([^\/#\?]+?))[\/#\?]?$/i, }); }); it('add rule', () => { barba.init(); barba.transitions.store.add = jest.fn(); router.init(); expect(barba.transitions.store.add).toHaveBeenCalledTimes(1); }); it('has duplicate routes', () => { console.warn = jest.fn(); router.install(barba, { routes, }); expect(console.warn).toHaveBeenCalledTimes(2); }); it('resolves url', () => { const result = router.resolveUrl('http://localhost/foo/something'); expect(result.name).toBe('foo'); expect(result.params.bar).toBe('something'); }); it('resolves unknown url', () => { const result = router.resolveUrl('http://localhost/bar/something'); expect(result).toBeNull(); }); it('resolves data urls (home)', () => { const data = { current: { url: { href: 'http://localhost/' } }, next: { url: { href: 'http://localhost/' } }, } as ITransitionData; router.resolveRoutes(data); expect(data.current.route.name).toBe('home'); expect(data.next.route.name).toBe('home'); }); it('resolves data urls (foo)', () => { const data = { current: { url: { href: 'http://localhost/foo/current' } }, next: { url: { href: 'http://localhost/foo/next' } }, } as ITransitionData; router.resolveRoutes(data); expect(data.current.route.name).toBe('foo'); expect(data.next.route.name).toBe('foo'); }); it('resolves unknown data urls', () => { const data = { current: { url: {} }, next: { url: {} }, } as ITransitionData; router.resolveRoutes(data); expect(data.current.route).toBeUndefined(); expect(data.next.route).toBeUndefined(); }); ================================================ FILE: packages/router/__web__/default.html ================================================ default

    default

    Go to home
    ================================================ FILE: packages/router/__web__/index.html ================================================ home ================================================ FILE: packages/router/__web__/page.html ================================================ page ================================================ FILE: packages/router/__web__/scripts/default.js ================================================ console.info('🚀 Barba e2e'); const { barba, barbaRouter: router } = window; const list = document.querySelector('[data-test="logs-list"]'); const routes = [ { name: 'home', path: '(/packages/router/__web__/|/packages/router/__web__/index.html)', }, { name: 'page', path: '/packages/router/__web__/page.html' }, ]; /** * Append list item * * @param {*} str List item content * @param {string} prefix Prefix for global * @returns {void} */ function append(str, prefix = '') { const item = document.createElement('li'); item.textContent = prefix + str; list.appendChild(item); } barba.use(router, { routes, }); barba.init({ debug: true, transitions: [ { name: 'default', leave: data => { console.info('leave-default', data); append('leave-default'); return Promise.resolve(); }, enter: data => { console.info('enter-default', data); append('enter-default'); return Promise.resolve(); }, }, { name: 'from', from: { route: 'home' }, leave: data => { console.info('leave-from', data); append('leave-from'); return Promise.resolve(); }, enter: data => { console.info('enter-from', data); append('enter-from'); return Promise.resolve(); }, }, { name: 'to', to: { route: 'home' }, leave: data => { console.info('leave-to', data); append('leave-to'); return Promise.resolve(); }, enter: data => { console.info('enter-to', data); append('enter-to'); return Promise.resolve(); }, }, ], }); ================================================ FILE: packages/router/jest.config.js ================================================ const jestBase = require('../../jest.config.js'); module.exports = { ...jestBase, }; ================================================ FILE: packages/router/package.json ================================================ { "name": "@barba/router", "version": "2.1.11", "description": "Use custom routes for your page transitions", "publishConfig": { "access": "public" }, "main": "dist/barba-router.js", "umd:main": "dist/barba-router.umd.js", "browser": "dist/barba-router.umd.js", "unpkg": "dist/barba-router.umd.js", "module": "dist/barba-router.mjs", "source": "src/index.ts", "types": "dist/router/src/typings", "mangle": { "regex": "^_" }, "files": [ "dist" ], "keywords": [ "page", "transition", "animation", "css", "router", "prefetch" ], "homepage": "https://github.com/barbajs/barba#readme", "bugs": { "url": "https://github.com/barbajs/barba/issues" }, "license": "MIT", "repository": { "type": "git", "url": "git+ssh://git@github.com/barbajs/barba.git" }, "scripts": { "build": "microbundle --name barbaRouter --external none", "build:watch": "microbundle watch --name barbaRouter --external none", "clear": "rimraf dist", "lint": "tslint src/**", "precommit": "lint-staged", "size": "echo '🚦 router' && gzip-size ./dist/barba-router.umd.js", "tag:latest": "npm dist-tag add @barba/router@$npm_package_version latest", "tag:next": "npm dist-tag add @barba/router@$npm_package_version next" }, "gitHead": "33c213bc36a0996f6333185dfc695fcd0d37c9d9" } ================================================ FILE: packages/router/src/defs/index.ts ================================================ /** * @module typings/router */ export interface IRoute { name: string; path: string; } export interface IRouteParsed { path: string; regex: RegExp; keys: any[]; } export interface IRouteResolved { name: string; params: any; } export interface IRouteByName { [key: string]: IRouteParsed; } export interface IRouterOptions { routes?: IRoute[]; } ================================================ FILE: packages/router/src/index.ts ================================================ import * as t from './typings'; export { default } from './router'; ================================================ FILE: packages/router/src/router.ts ================================================ /** * @barba/router *

    * ## Barba Router. * * - Add `route` to Barba transitions resolution * * @module router * @preferred */ /***/ // Definitions import { IBarbaPlugin, ITransitionData } from '@barba/core/src/defs'; // Barba/core import { Core } from '@barba/core/src/core'; import { Logger } from '@barba/core/src/modules/Logger'; // Local import { version } from '../package.json'; import { IRouteByName, IRouteResolved, IRouterOptions } from './defs'; class Router implements IBarbaPlugin { public name = '@barba/router'; public version = version; public barba: Core; public logger: Logger; public routeNames: string[] = []; public routesByName: IRouteByName = {}; /** * Plugin installation. */ public install(barba: Core, { routes = [] }: IRouterOptions = {}) { this.logger = new barba.Logger(this.name); this.logger.info(this.version); this.barba = barba; routes.forEach(route => { const { name, path } = route; const keys: any[] = []; const regex = this.barba.helpers.pathToRegexp(path, keys); if (this.routeNames.indexOf(name) > -1) { console.warn(`[@barba/router] Duplicated route name (${name})`); } else { this.routeNames.push(name); this.routesByName[name] = { keys, path, regex, }; } }); // Add property to "pageSchema" (current, next) barba.schemaPage.route = undefined; } /** * Plugin initialisation. */ public init() { // Wait for store initialization then add new rule for routes this.barba.transitions.store.add('rule', { position: 1, value: { name: 'route', type: 'object', }, }); // Register hooks this.barba.hooks.page(this.resolveRoutes, this); this.barba.hooks.reset(this.resolveRoutes, this); } /** * Resolve URL to route name. */ public resolveUrl(url: string): IRouteResolved | null { const { path } = this.barba.url.parse(url); const output: IRouteResolved = { name: '', params: {}, }; /* tslint:disable:no-shadowed-variable */ for (let i = 0, l = this.routeNames.length; i < l; i++) { const name = this.routeNames[i]; const { regex, keys } = this.routesByName[name]; const res = regex.exec(path); if (res !== null) { output.name = name; keys.forEach((key, i) => { output.params[key.name] = res[i + 1]; }); return output; } } return null; } /** * Hooks: do, reset. * * - Update `current` and `next` data */ public resolveRoutes(data: ITransitionData): void { const { current, next } = data; current.route = current.url.href ? this.resolveUrl(current.url.href) : undefined; next.route = next.url.href ? this.resolveUrl(next.url.href) : undefined; } } const router = new Router(); export default router; ================================================ FILE: packages/router/src/typings.ts ================================================ export * from './defs'; export { default } from './router'; ================================================ FILE: prettier.config.js ================================================ module.exports = { printWidth: 80, tabWidth: 2, useTabs: false, semi: true, singleQuote: true, trailingComma: 'es5', bracketSpacing: true, arrowParens: 'avoid', overrides: [ { files: '*.md', options: { parser: 'markdown', }, }, ], }; ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "resolveJsonModule": true, "strict": true, "strictNullChecks": false, "useUnknownInCatchVariables": false, "esModuleInterop": true, "target": "es5", "plugins": [ { "name": "typescript-tslint-plugin", "alwaysShowRuleFailuresAsWarnings": false, "ignoreDefinitionFiles": true, "configFile": "./tslint.json", "suppressWhileTypeErrorsPresent": false, "mockTypeScriptVersion": false } ] }, "exclude": [ "node_modules", "**/__*__/**/*", "**/polyfills/index.d.ts", "./cypress.config.ts" ] } ================================================ FILE: tslint.json ================================================ { "defaultSeverity": "error", "extends": ["tslint:recommended"], "jsRules": {}, "rules": { "arrow-parens": [true, "ban-single-arg-parens"], "max-line-length": [true, 120], "no-console": [false], "no-unused-expression": [true, "allow-fast-null-checks"], "quotemark": [true, "single"], "variable-name": [ true, "ban-keywords", "check-format", "allow-leading-underscore" ], "trailing-comma": [false] }, "rulesDirectory": [] } ================================================ FILE: typedoc.json ================================================ { "extends": "./tsconfig", "compilerOptions": { "module": "esnext", "moduleResolution": "node", "skipLibCheck": true }, "typedocOptions": { "exclude": "**/index.ts", "excludeExternals": true, "ignoreCompilerErrors": true, "name": "barba.js", "readme": "README.md", "out": "documentation/api", "theme": "documentation/theme" } }