├── .babelrc ├── .eleventy.js ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── build.yml ├── .gitignore ├── .nojekyll ├── .npmignore ├── LICENSE.md ├── README.md ├── dist ├── taxi.esm.js ├── taxi.esm.js.map ├── taxi.js ├── taxi.js.map ├── taxi.modern.js ├── taxi.modern.js.map ├── taxi.umd.js └── taxi.umd.js.map ├── docs ├── _data │ ├── global.js │ └── nav.json ├── _includes │ ├── layouts │ │ └── base.njk │ └── nav.njk ├── _public │ ├── .nojekyll │ ├── CNAME │ ├── android-chrome-192x192.png │ ├── android-chrome-384x384.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── fonts │ │ ├── Inter-Bold.woff │ │ ├── Inter-Bold.woff2 │ │ ├── Inter-Regular.woff │ │ ├── Inter-Regular.woff2 │ │ ├── NeueMontreal-Regular.woff │ │ └── NeueMontreal-Regular.woff2 │ ├── mstile-150x150.png │ ├── site.webmanifest │ └── social.jpg ├── api-events.md ├── assets │ ├── js │ │ ├── index.js │ │ ├── renderers │ │ │ └── DefaultRenderer.js │ │ └── transitions │ │ │ └── DefaultTransition.js │ └── sass │ │ ├── index.scss │ │ └── prism.scss ├── how-to-use.md ├── index.njk ├── mix-manifest.json ├── navigation-lifecycle.md ├── reloading-css.md ├── reloading-js.md ├── renderers.md ├── routing.md ├── tailwind.config.js └── transitions.md ├── package-lock.json ├── package.json ├── src ├── Core.d.ts ├── Core.js ├── Renderer.d.ts ├── Renderer.js ├── RouteStore.d.ts ├── RouteStore.js ├── Transition.d.ts ├── Transition.js ├── helpers.d.ts ├── helpers.js ├── taxi.d.ts └── taxi.js ├── tsconfig.json └── webpack.mix.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["prismjs", { 4 | "languages": ["javascript", "html"], 5 | "plugins": ["toolbar", "copy-to-clipboard"], 6 | "css": false 7 | }] 8 | ] 9 | } -------------------------------------------------------------------------------- /.eleventy.js: -------------------------------------------------------------------------------- 1 | const htmlmin = require("html-minifier") 2 | const markdownIt = require("markdown-it") 3 | const anchors = require('markdown-it-anchor') 4 | const date = Date.now() 5 | 6 | function getFromManifest(name) { 7 | try { 8 | const assets = require("./_site/mix-manifest.json") 9 | 10 | if (name[0] !== '/') { 11 | name = '/' + name 12 | } 13 | 14 | return assets[name] || name + '?id=' + date 15 | } catch (error) { 16 | return name + '?id=' + date 17 | } 18 | } 19 | 20 | module.exports = function (eleventyConfig) { 21 | eleventyConfig.addPassthroughCopy({ "docs/_public": '/' }) 22 | 23 | /** 24 | * Add the asset shortcode to fetch an asset name from mix-manifest.json 25 | * 26 | * @example {% asset 'css/style.css' %} 27 | */ 28 | eleventyConfig.addShortcode("asset", function (name) { 29 | return getFromManifest(name) 30 | }); 31 | 32 | eleventyConfig.addTransform("htmlmin", function(content, outputPath) { 33 | if (outputPath && outputPath.endsWith(".html")) { 34 | return htmlmin.minify(content, { 35 | useShortDoctype: true, 36 | removeComments: true, 37 | minifyJS: true, 38 | collapseWhitespace: true 39 | }); 40 | } 41 | 42 | return content; 43 | }); 44 | 45 | eleventyConfig.addFilter('stringify', (value) => { 46 | return JSON.stringify(value) 47 | }) 48 | 49 | 50 | let markdownLibrary = markdownIt({ 51 | html: true 52 | }) 53 | .use(anchors, { 54 | tabIndex: false 55 | }); 56 | 57 | eleventyConfig.setLibrary("md", markdownLibrary); 58 | 59 | return { 60 | dir: { input: 'docs', data: '_data' }, 61 | passthroughFileCopy: true, 62 | templateFormats: ['njk', 'md', 'html'], 63 | htmlTemplateEngine: 'njk' 64 | } 65 | }; -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: jakewhiteley 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | **Info** 17 | If applicable, add screenshots to help explain your problem. 18 | 19 | - Device: [e.g. Windows desktop, Macbook, iphone] 20 | - OS: [e.g. iOS] 21 | - Browser [e.g. chrome, safari] 22 | - Version [e.g. 22] 23 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Eleventy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-20.04 11 | 12 | strategy: 13 | matrix: 14 | node-version: [16.x] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | 24 | - name: Install dependencies & build 25 | run: | 26 | npm ci 27 | npm run docs 28 | 29 | - name: Deploy 30 | uses: peaceiris/actions-gh-pages@v3 31 | with: 32 | publish_dir: ./_site 33 | github_token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | _site 4 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftedbygc/taxi/6785cd56f1f843401a12b867ce0b01425af4c0bb/.nojekyll -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | *.mix.js 4 | tsconfig.json 5 | .gitignore 6 | .eleventy.js 7 | docs 8 | _site 9 | tests 10 | .nojekyll 11 | .github 12 | .babelrc -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2025, Jake Whiteley / Unseen Studio Ltd 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

Taxi.js is the spiritual successor to Highway.js.

6 | 7 |

Full Documentation

8 | 9 |

10 | npm i @unseenco/taxi or yarn add @unseenco/taxi 11 |

12 | 13 |

14 | 15 | ---- 16 | 17 | Taxi is a js library for adding AJAX navigation and beautiful transitions to your website. 18 | 19 | It was designed as a drop-in replacement for [Highway.js](https://github.com/Dogstudio/highway) which is sadly no longer maintained. 20 | 21 | ### Enhancements over Highway: 22 | 23 | * URL-based routing 24 | * Better cache management 25 | * Ability to preload URLs 26 | * Blocks navigation during an active transition (can be opted out) 27 | * Auto runs javascript on the new page 28 | * Previous page's content is automatically removed (you can opt out of this if you like) 29 | * Click events on links can be intercepted via `stopPropagation` without hacks 30 | 31 | 32 | ### Differences to Highway 33 | * Different public API 34 | * New methods and functionality 35 | * `data-taxi`, `data-taxi-view`, `data-taxi-ignore` are to be used instead of `data-router-wrapper`, `data-router-view`, `data-router-disabled` respectively. 36 | * `attach` and `detach` are no longer methods - link clicks are listened to via delegation so these are no longer needed. 37 | * `redirect` is now `navigateTo` as "redirect" felt weird as a method name! 38 | * Renderers now have an `initialLoad` method 39 | * The params passed to renderers, transitions, and events are now a little different 40 | * Old content is automatically removed during a transition - so no need to manually call `from.remove()` in your transitions. 41 | 42 | ---- 43 | 44 |

Full Documentation

45 | -------------------------------------------------------------------------------- /dist/taxi.esm.js: -------------------------------------------------------------------------------- 1 | import t from"@unseenco/e";function e(){return e=Object.assign||function(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=new Array(e);r=t.length?{done:!0}:{done:!1,value:t[i++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i=new DOMParser;function o(t){var e=new URL(t,window.location.origin),r=e.hash.length?t.replace(e.hash,""):null;return{hasHash:e.hash.length>0,pathname:e.pathname.replace(/\/+$/,""),host:e.host,search:e.search,raw:t,href:r||e.href}}function a(t,e){t.parentNode.replaceChild(s(t,e),t)}function c(t,e){("HEAD"===t.parentNode.tagName?document.head:document.body).appendChild(s(t,e))}function s(t,e){for(var r=document.createElement(e),n=0;ne.length)&&(t=e.length);for(var r=0,n=new Array(t);r=e.length?{done:!0}:{done:!1,value:e[i++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o=new DOMParser;function a(e){var t=new URL(e,window.location.origin),r=t.hash.length?e.replace(t.hash,""):null;return{hasHash:t.hash.length>0,pathname:t.pathname.replace(/\/+$/,""),host:t.host,search:t.search,raw:e,href:r||t.href}}function c(e,t){e.parentNode.replaceChild(h(e,t),e)}function s(e,t){("HEAD"===e.parentNode.tagName?document.head:document.body).appendChild(h(e,t))}function h(e,t){for(var r=document.createElement(t),n=0;n 0,\n\t\tpathname: details.pathname.replace(/\\/+$/, ''),\n\t\thost: details.host,\n\t\tsearch: details.search,\n\t\traw: url,\n\t\thref: normalized || details.href\n\t}\n}\n\n/**\n * Reloads a provided script/stylesheet by replacing with itself.\n *\n * @param {HTMLElement|HTMLScriptElement|HTMLStyleElement} node\n * @param {string} elementType - 'SCRIPT' or 'STYLE'\n */\nexport function reloadElement(node, elementType) {\n\tnode.parentNode.replaceChild(duplicateElement(node, elementType), node)\n}\n\n/**\n * Loads a provided script/stylesheet by appending a clone to the current document.\n *\n * @param {HTMLElement|HTMLStyleElement} node\n * @param {string} elementType - 'SCRIPT' or 'STYLE'\n */\nexport function appendElement(node, elementType) {\n\tconst target = node.parentNode.tagName === 'HEAD' ? document.head : document.body\n\ttarget.appendChild(duplicateElement(node, elementType))\n}\n\n/**\n * Creates a clone of a given HTMLElement or HTMLStyleElement\n *\n * @param {HTMLElement|HTMLStyleElement} node\n * @param {string} elementType - 'SCRIPT' or 'STYLE'\n * @return {HTMLElement|HTMLStyleElement}\n */\nexport function duplicateElement(node, elementType) {\n\tconst replacement = document.createElement(elementType)\n\n\tfor (let k = 0; k < node.attributes.length; k++) {\n\t\tconst attr = node.attributes[k]\n\t\treplacement.setAttribute(attr.nodeName, attr.nodeValue)\n\t}\n\n\t// Inline Script or Style\n\tif (node.innerHTML) {\n\t\treplacement.innerHTML = node.innerHTML\n\t}\n\n\treturn replacement\n}\n","export default class Transition {\n\t/**\n\t * @param {{wrapper: HTMLElement}} props\n\t */\n\tconstructor({ wrapper }) {\n\t\tthis.wrapper = wrapper\n\t}\n\n\t/**\n\t * @param {{ from: HTMLElement|Element, trigger: string|HTMLElement|false }} props\n\t * @return {Promise}\n\t */\n\tleave(props) {\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.onLeave({ ...props, done: resolve })\n\t\t})\n\t}\n\n\t/**\n\t * @param {{ to: HTMLElement|Element, trigger: string|HTMLElement|false }} props\n\t * @return {Promise}\n\t */\n\tenter(props) {\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.onEnter({ ...props, done: resolve })\n\t\t})\n\t}\n\n\t/**\n\t * Handle the transition leaving the previous page.\n\t * @param {{from: HTMLElement|Element, trigger: string|HTMLElement|false, done: function}} props\n\t */\n\tonLeave({ from, trigger, done }) {\n\t\tdone()\n\t}\n\n\t/**\n\t * Handle the transition entering the next page.\n\t * @param {{to: HTMLElement|Element, trigger: string|HTMLElement|false, done: function}} props\n\t */\n\tonEnter({ to, trigger, done }) {\n\t\tdone()\n\t}\n}\n","import Transition from \"./Transition\"\n\nexport default class Renderer {\n\t/**\n\t * @param {{content: HTMLElement|Element, page: Document|Node, title: string, wrapper: Element}} props\n\t */\n\tconstructor({ content, page, title, wrapper }) {\n\t\tthis._contentString = content.outerHTML\n\t\tthis._DOM = null\n\t\tthis.page = page\n\t\tthis.title = title\n\t\tthis.wrapper = wrapper\n\t\tthis.content = this.wrapper.lastElementChild\n\t}\n\n\tonEnter() {\n\n\t}\n\n\tonEnterCompleted() {\n\n\t}\n\n\tonLeave() {\n\n\t}\n\n\tonLeaveCompleted() {\n\n\t}\n\n\tinitialLoad() {\n\t\tthis.onEnter()\n\t\tthis.onEnterCompleted()\n\t}\n\n\tupdate() {\n\t\tdocument.title = this.title\n\t\tthis.wrapper.appendChild(this._DOM.firstElementChild)\n\t\tthis.content = this.wrapper.lastElementChild\n\t\tthis._DOM = null\n\t}\n\n\tcreateDom() {\n\t\tif (!this._DOM) {\n\t\t\tthis._DOM = document.createElement('div')\n\t\t\tthis._DOM.innerHTML = this._contentString\n\t\t}\n\t}\n\n\tremove() {\n\t\tthis.wrapper.firstElementChild.remove()\n\t}\n\n\t/**\n\t * Called when transitioning into the current page.\n\t * @param {Transition} transition\n\t * @param {string|HTMLElement|false} trigger\n\t * @return {Promise}\n\t */\n\tenter(transition, trigger) {\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.onEnter()\n\n\t\t\ttransition.enter({ trigger, to: this.content })\n\t\t\t\t.then(() => {\n\t\t\t\t\tthis.onEnterCompleted()\n\t\t\t\t\tresolve()\n\t\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * Called when transitioning away from the current page.\n\t * @param {Transition} transition\n\t * @param {string|HTMLElement|false} trigger\n\t * @param {boolean} removeOldContent\n\t * @return {Promise}\n\t */\n\tleave(transition, trigger, removeOldContent) {\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.onLeave()\n\n\t\t\ttransition.leave({ trigger, from: this.content })\n\t\t\t\t.then(() => {\n\t\t\t\t\tif (removeOldContent) {\n\t\t\t\t\t\tthis.remove()\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.onLeaveCompleted()\n\t\t\t\t\tresolve()\n\t\t\t\t})\n\t\t})\n\t}\n}\n","export default class RouteStore {\n\t/**\n\t * @type {Map>}\n\t */\n\tdata = new Map()\n\n\t/**\n\t * @type {Map}\n\t */\n\tregexCache = new Map()\n\n\t/**\n\t *\n\t * @param {string} fromPattern\n\t * @param {string} toPattern\n\t * @param {string} transition\n\t */\n\tadd(fromPattern, toPattern, transition) {\n\t\tif (!this.data.has(fromPattern)) {\n\t\t\tthis.data.set(fromPattern, new Map())\n\t\t\tthis.regexCache.set(fromPattern, new RegExp(`^${fromPattern}$`))\n\t\t}\n\n\t\tthis.data.get(fromPattern).set(toPattern, transition)\n\t\tthis.regexCache.set(toPattern, new RegExp(`^${toPattern}$`))\n\t}\n\n\t/**\n\t *\n\t * @param {{ raw: string, href: string, hasHash: boolean, pathname: string }} currentUrl\n\t * @param {{ raw: string, href: string, hasHash: boolean, pathname: string }} nextUrl\n\t * @return {string|null}\n\t */\n\tfindMatch(currentUrl, nextUrl) {\n\t\t// Loop through all from patterns\n\t\tfor (const [fromPattern, potentialMatches] of this.data) {\n\t\t\t// If we have a match\n\t\t\tif (currentUrl.pathname.match(this.regexCache.get(fromPattern))) {\n\t\t\t\t// loop through all associated to patterns\n\t\t\t\tfor (const [toPattern, transition] of potentialMatches) {\n\t\t\t\t\t// If we find a match, return it\n\t\t\t\t\tif (nextUrl.pathname.match(this.regexCache.get(toPattern))) {\n\t\t\t\t\t\treturn transition\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\treturn null\n\t}\n}\n","import E from '@unseenco/e'\nimport { appendElement, parseDom, processUrl, reloadElement } from './helpers'\nimport Transition from './Transition'\nimport Renderer from './Renderer'\nimport RouteStore from './RouteStore'\n\nconst IN_PROGRESS = 'A transition is currently in progress'\n\n/**\n * @typedef CacheEntry\n * @type {object}\n * @property {typeof Renderer|Renderer} renderer\n * @property {Document|Node} page\n * @property {array} scripts\n * @property {HTMLLinkElement[]} styles\n * @property {string} finalUrl\n * @property {boolean} skipCache\n * @property {string} title\n * @property {HTMLElement|Element} content\n */\n\nexport default class Core {\n\tisTransitioning = false\n\n\t/**\n\t * @type {CacheEntry|null}\n\t */\n\tcurrentCacheEntry = null\n\n\t/**\n\t * @type {Map}\n\t */\n\tcache = new Map()\n\n\t/**\n\t * @private\n\t * @type {Map}\n\t */\n\tactivePromises = new Map()\n\n\t/**\n\t * @param {{\n\t * \t\tlinks?: string,\n\t * \t\tremoveOldContent?: boolean,\n\t * \t\tallowInterruption?: boolean,\n\t * \t\tbypassCache?: boolean,\n\t * \t\tenablePrefetch?: boolean,\n\t * \t\trenderers?: Object.,\n\t * \t\ttransitions?: Object.,\n\t * \t\treloadJsFilter?: boolean|function(HTMLElement): boolean,\n\t * \t\treloadCssFilter?: boolean|function(HTMLLinkElement): boolean\n\t * }} parameters\n\t */\n\tconstructor(parameters = {}) {\n\t\tconst {\n\t\t\tlinks = 'a[href]:not([target]):not([href^=\\\\#]):not([data-taxi-ignore])',\n\t\t\tremoveOldContent = true,\n\t\t\tallowInterruption = false,\n\t\t\tbypassCache = false,\n\t\t\tenablePrefetch = true,\n\t\t\trenderers = {\n\t\t\t\tdefault: Renderer\n\t\t\t},\n\t\t\ttransitions = {\n\t\t\t\tdefault: Transition\n\t\t\t},\n\t\t\treloadJsFilter = (element) => element.dataset.taxiReload !== undefined,\n\t\t\treloadCssFilter = (element) => true //element.dataset.taxiReload !== undefined\n\t\t} = parameters\n\n\t\tthis.renderers = renderers\n\t\tthis.transitions = transitions\n\t\tthis.defaultRenderer = this.renderers.default || Renderer\n\t\tthis.defaultTransition = this.transitions.default || Transition\n\t\tthis.wrapper = document.querySelector('[data-taxi]')\n\t\tthis.reloadJsFilter = reloadJsFilter\n\t\tthis.reloadCssFilter = reloadCssFilter\n\t\tthis.removeOldContent = removeOldContent\n\t\tthis.allowInterruption = allowInterruption\n\t\tthis.bypassCache = bypassCache\n\t\tthis.enablePrefetch = enablePrefetch\n\t\tthis.cache = new Map()\n\t\tthis.isPopping = false\n\n\t\t// Add delegated link events\n\t\tthis.attachEvents(links)\n\n\t\tthis.currentLocation = processUrl(window.location.href)\n\n\t\t// as this is the initial page load, prime this page into the cache\n\t\tthis.cache.set(this.currentLocation.href, this.createCacheEntry(document.cloneNode(true), window.location.href))\n\n\t\t// fire the current Renderer enter methods\n\t\tthis.currentCacheEntry = this.cache.get(this.currentLocation.href)\n\t\tthis.currentCacheEntry.renderer.initialLoad()\n\t}\n\n\t/**\n\t * @param {string} renderer\n\t */\n\tsetDefaultRenderer(renderer) {\n\t\tthis.defaultRenderer = this.renderers[renderer]\n\t}\n\n\t/**\n\t * @param {string} transition\n\t */\n\tsetDefaultTransition(transition) {\n\t\tthis.defaultTransition = this.transitions[transition]\n\t}\n\n\t/**\n\t * Registers a route into the RouteStore\n\t *\n\t * @param {string} fromPattern\n\t * @param {string} toPattern\n\t * @param {string} transition\n\t */\n\taddRoute(fromPattern, toPattern, transition) {\n\t\tif (!this.router) {\n\t\t\tthis.router = new RouteStore()\n\t\t}\n\n\t\tthis.router.add(fromPattern, toPattern, transition)\n\t}\n\n\t/**\n\t * Prime the cache for a given URL\n\t *\n\t * @param {string} url\n\t * @param {boolean} [preloadAssets]\n\t * @return {Promise}\n\t */\n\tpreload(url, preloadAssets = false) {\n\t\t// convert relative URLs to absolute\n\t\turl = processUrl(url).href\n\n\t\tif (!this.cache.has(url)) {\n\t\t\treturn this.fetch(url, false)\n\t\t\t\t.then(async (response) => {\n\t\t\t\t\tthis.cache.set(url, this.createCacheEntry(response.html, response.url))\n\n\t\t\t\t\tif (preloadAssets) {\n\t\t\t\t\t\tthis.cache.get(url).renderer.createDom()\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.catch(err => console.warn(err))\n\t\t}\n\n\t\treturn Promise.resolve()\n\t}\n\n\t/**\n\t * Updates the HTML cache for a given URL.\n\t * If no URL is passed, then cache for the current page is updated.\n\t * Useful when adding/removing content via AJAX such as a search page or infinite loader.\n\t *\n\t * @param {string} [url]\n\t */\n\tupdateCache(url) {\n\t\tconst key = processUrl(url || window.location.href).href\n\n\t\tif (this.cache.has(key)) {\n\t\t\tthis.cache.delete(key)\n\t\t}\n\n\t\tthis.cache.set(key, this.createCacheEntry(document.cloneNode(true), key))\n\t}\n\n\t/**\n\t * Clears the cache for a given URL.\n\t * If no URL is passed, then cache for the current page is cleared.\n\t *\n\t * @param {string} [url]\n\t */\n\tclearCache(url) {\n\t\tconst key = processUrl(url || window.location.href).href\n\n\t\tif (this.cache.has(key)) {\n\t\t\tthis.cache.delete(key)\n\t\t}\n\t}\n\n\t/**\n\t * @param {string} url\n\t * @param {string|false} [transition]\n\t * @param {string|false|HTMLElement} [trigger]\n\t * @return {Promise}\n\t */\n\tnavigateTo(url, transition = false, trigger = false) {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\t// Don't allow multiple navigations to occur at once\n\t\t\tif (!this.allowInterruption && this.isTransitioning) {\n\t\t\t\treject(new Error(IN_PROGRESS))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.isTransitioning = true\n\t\t\tthis.isPopping = true\n\t\t\tthis.targetLocation = processUrl(url)\n\t\t\tthis.popTarget = window.location.href\n\n\t\t\tconst TransitionClass = new (this.chooseTransition(transition))({ wrapper: this.wrapper })\n\n\t\t\tlet navigationPromise\n\n\t\t\tif (this.bypassCache || !this.cache.has(this.targetLocation.href) || this.cache.get(this.targetLocation.href).skipCache) {\n\t\t\t\tconst fetched = this.fetch(this.targetLocation.href)\n\t\t\t\t\t.then((response) => {\n\t\t\t\t\t\tthis.cache.set(this.targetLocation.href, this.createCacheEntry(response.html, response.url))\n\t\t\t\t\t\tthis.cache.get(this.targetLocation.href).renderer.createDom()\n\t\t\t\t\t})\n\t\t\t\t\t.catch(err => {\n\t\t\t\t\t\t// we encountered a 4** or 5** error, redirect to the requested URL\n\t\t\t\t\t\twindow.location.href = url\n\t\t\t\t\t})\n\n\t\t\t\tnavigationPromise = this.beforeFetch(this.targetLocation, TransitionClass, trigger)\n\t\t\t\t\t.then(async () => {\n\t\t\t\t\t\treturn fetched.then(async () => {\n\t\t\t\t\t\t\treturn await this.afterFetch(this.targetLocation, TransitionClass, this.cache.get(this.targetLocation.href), trigger)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tthis.cache.get(this.targetLocation.href).renderer.createDom()\n\n\t\t\t\tnavigationPromise = this.beforeFetch(this.targetLocation, TransitionClass, trigger)\n\t\t\t\t\t.then(async () => {\n\t\t\t\t\t\treturn await this.afterFetch(this.targetLocation, TransitionClass, this.cache.get(this.targetLocation.href), trigger)\n\t\t\t\t\t})\n\t\t\t}\n\n\t\t\tnavigationPromise.then(() => {\n\t\t\t\tresolve()\n\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * Add an event listener.\n\t * @param {string} event\n\t * @param {any} callback\n\t */\n\ton(event, callback) {\n\t\tE.on(event, callback)\n\t}\n\n\t/**\n\t * Remove an event listener.\n\t * @param {string} event\n\t * @param {any} [callback]\n\t */\n\toff(event, callback) {\n\t\tE.off(event, callback)\n\t}\n\n\t/**\n\t * @private\n\t * @param {{ raw: string, href: string, hasHash: boolean, pathname: string }} url\n\t * @param {Transition} TransitionClass\n\t * @param {string|HTMLElement|false} trigger\n\t * @return {Promise}\n\t */\n\tbeforeFetch(url, TransitionClass, trigger) {\n\t\tE.emit('NAVIGATE_OUT', {\n\t\t\tfrom: this.currentCacheEntry,\n\t\t\ttrigger\n\t\t})\n\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.currentCacheEntry.renderer.leave(TransitionClass, trigger, this.removeOldContent)\n\t\t\t\t.then(() => {\n\t\t\t\t\tif (trigger !== 'popstate') {\n\t\t\t\t\t\twindow.history.pushState({}, '', url.raw)\n\t\t\t\t\t}\n\n\t\t\t\t\tresolve()\n\t\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * @private\n\t * @param {{ raw: string, href: string, host: string, hasHash: boolean, pathname: string }} url\n\t * @param {Transition} TransitionClass\n\t * @param {CacheEntry} entry\n\t * @param {string|HTMLElement|false} trigger\n\t * @return {Promise}\n\t */\n\tafterFetch(url, TransitionClass, entry, trigger) {\n\t\tthis.currentLocation = url\n\t\tthis.popTarget = this.currentLocation.href\n\n\t\treturn new Promise((resolve) => {\n\t\t\tentry.renderer.update()\n\n\t\t\tE.emit('NAVIGATE_IN', {\n\t\t\t\tfrom: this.currentCacheEntry,\n\t\t\t\tto: entry,\n\t\t\t\ttrigger\n\t\t\t})\n\n\t\t\tif (this.reloadJsFilter) {\n\t\t\t\tthis.loadScripts(entry.scripts)\n\t\t\t}\n\n\t\t\tif (this.reloadCssFilter) {\n\t\t\t\tthis.loadStyles(entry.styles)\n\t\t\t}\n\n\t\t\t// If the fetched url had a redirect chain, then replace the history to reflect the final resolved URL\n\t\t\tif (trigger !== 'popstate' && url.href !== entry.finalUrl) {\n\t\t\t\twindow.history.replaceState({}, '', entry.finalUrl)\n\t\t\t}\n\n\t\t\tentry.renderer.enter(TransitionClass, trigger)\n\t\t\t\t.then(() => {\n\t\t\t\t\tE.emit('NAVIGATE_END', {\n\t\t\t\t\t\tfrom: this.currentCacheEntry,\n\t\t\t\t\t\tto: entry,\n\t\t\t\t\t\ttrigger\n\t\t\t\t\t})\n\n\t\t\t\t\tthis.currentCacheEntry = entry\n\t\t\t\t\tthis.isTransitioning = false\n\t\t\t\t\tthis.isPopping = false\n\t\t\t\t\tresolve()\n\t\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * Load up scripts from the target page if needed\n\t *\n\t * @param {HTMLElement[]} cachedScripts\n\t */\n\tloadScripts(cachedScripts) {\n\t\tconst newScripts = [...cachedScripts]\n\t\tconst currentScripts = Array.from(document.querySelectorAll('script')).filter(this.reloadJsFilter)\n\n\t\t// loop through all new scripts\n\t\tfor (let i = 0; i < currentScripts.length; i++) {\n\t\t\tfor (let n = 0; n < newScripts.length; n++) {\n\t\t\t\tif (currentScripts[i].outerHTML === newScripts[n].outerHTML) {\n\t\t\t\t\treloadElement(currentScripts[i], 'SCRIPT')\n\t\t\t\t\tnewScripts.splice(n, 1)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (const script of newScripts) {\n\t\t\tappendElement(script, 'SCRIPT')\n\t\t}\n\t}\n\n\t/**\n\t * Load up styles from the target page if needed\n\t *\n\t * @param {Array} cachedStyles\n\t */\n\tloadStyles(cachedStyles) {\n\t\tconst currentStyles = Array.from(document.querySelectorAll('link[rel=\"stylesheet\"]')).filter(this.reloadCssFilter)\n\t\tconst currentInlineStyles = Array.from(document.querySelectorAll('style')).filter(this.reloadCssFilter)\n\n\t\tconst newInlineStyles = cachedStyles.filter(el => {\n\t\t\t// no el.href, assume it's an inline style\n\t\t\tif (!el.href) {\n\t\t\t\treturn true\n\t\t\t} else if (!currentStyles.find((link) => link.href === el.href)) {\n\t\t\t\tdocument.body.append(el)\n\t\t\t\treturn false\n\t\t\t}\n\t\t})\n\n\t\t// loop through all new inline styles\n\t\tfor (let i = 0; i < currentInlineStyles.length; i++) {\n\t\t\tfor (let n = 0; n < newInlineStyles.length; n++) {\n\t\t\t\tif (currentInlineStyles[i].outerHTML === newInlineStyles[n].outerHTML) {\n\t\t\t\t\treloadElement(currentInlineStyles[i], 'STYLE')\n\t\t\t\t\tnewInlineStyles.splice(n, 1)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (const style of newInlineStyles) {\n\t\t\tappendElement(style, 'STYLE')\n\t\t}\n\t}\n\n\t/**\n\t * @private\n\t * @param {string} links\n\t */\n\tattachEvents(links) {\n\t\tE.delegate('click', links, this.onClick)\n\t\tE.on('popstate', window, this.onPopstate)\n\n\t\tif (this.enablePrefetch) {\n\t\t\tE.delegate('mouseenter focus', links, this.onPrefetch)\n\t\t}\n\t}\n\n\t/**\n\t * @private\n\t * @param {MouseEvent} e\n\t */\n\tonClick = (e) => {\n\t\tif (!(e.metaKey || e.ctrlKey)) {\n\t\t\tconst target = processUrl(e.currentTarget.href)\n\t\t\tthis.currentLocation = processUrl(window.location.href)\n\n\t\t\tif (this.currentLocation.host !== target.host) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// the target is a new URL, or is removing the hash from the current URL\n\t\t\tif (this.currentLocation.href !== target.href || (this.currentLocation.hasHash && !target.hasHash)) {\n\t\t\t\te.preventDefault()\n\t\t\t\t// noinspection JSIgnoredPromiseFromCall\n\t\t\t\tthis.navigateTo(target.raw, e.currentTarget.dataset.transition || false, e.currentTarget).catch(err => console.warn(err))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// a click to the current URL was detected\n\t\t\tif (!this.currentLocation.hasHash && !target.hasHash) {\n\t\t\t\te.preventDefault()\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @private\n\t * @return {void|boolean}\n\t */\n\tonPopstate = () => {\n\t\tconst target = processUrl(window.location.href)\n\n\t\t// don't trigger for on-page anchors\n\t\tif (\n\t\t\ttarget.pathname === this.currentLocation.pathname\n\t\t\t&& target.search === this.currentLocation.search\n\t\t\t&& !this.isPopping\n\t\t) {\n\t\t\treturn false\n\t\t}\n\n\t\tif (!this.allowInterruption && (this.isTransitioning || this.isPopping)) {\n\t\t\t// overwrite history state with current page if currently navigating\n\t\t\twindow.history.pushState({}, '', this.popTarget)\n\t\t\tconsole.warn(IN_PROGRESS)\n\t\t\treturn false\n\t\t}\n\n\t\tif (!this.isPopping) {\n\t\t\tthis.popTarget = window.location.href\n\t\t}\n\n\t\tthis.isPopping = true\n\n\t\t// noinspection JSIgnoredPromiseFromCall\n\t\tthis.navigateTo(window.location.href, false, 'popstate')\n\t}\n\n\t/**\n\t * @private\n\t * @param {MouseEvent} e\n\t */\n\tonPrefetch = (e) => {\n\t\tconst target = processUrl(e.currentTarget.href)\n\n\t\tif (this.currentLocation.host !== target.host) {\n\t\t\treturn\n\t\t}\n\n\t\tthis.preload(e.currentTarget.href, false)\n\t}\n\n\t/**\n\t * @private\n\t * @param {string} url\n\t * @param {boolean} [runFallback]\n\t * @return {Promise<{html: Document, url: string}>}\n\t */\n\tfetch(url, runFallback = true) {\n\t\t// If Taxi is currently performing a fetch for the given URL, return that instead of starting a new request\n\t\tif (this.activePromises.has(url)) {\n\t\t\treturn this.activePromises.get(url)\n\t\t}\n\n\t\tconst request = new Promise((resolve, reject) => {\n\t\t\tlet resolvedUrl\n\n\t\t\tfetch(url, {\n\t\t\t\tmode: 'same-origin',\n\t\t\t\tmethod: 'GET',\n\t\t\t\theaders: { 'X-Requested-With': 'Taxi' },\n\t\t\t\tcredentials: 'same-origin'\n\t\t\t})\n\t\t\t\t.then((response) => {\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\treject('Taxi encountered a non 2xx HTTP status code')\n\n\t\t\t\t\t\tif (runFallback) {\n\t\t\t\t\t\t\twindow.location.href = url\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tresolvedUrl = response.url\n\n\t\t\t\t\treturn response.text()\n\t\t\t\t})\n\t\t\t\t.then((htmlString) => {\n\t\t\t\t\tresolve({ html: parseDom(htmlString), url: resolvedUrl })\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\treject(err)\n\n\t\t\t\t\tif (runFallback) {\n\t\t\t\t\t\twindow.location.href = url\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.finally(() => {\n\t\t\t\t\tthis.activePromises.delete(url)\n\t\t\t\t})\n\t\t})\n\n\t\tthis.activePromises.set(url, request)\n\n\t\treturn request\n\t}\n\n\t/**\n\t * @private\n\t * @param {string|false} transition\n\t * @return {Transition|function}\n\t */\n\tchooseTransition(transition) {\n\t\tif (transition) {\n\t\t\treturn this.transitions[transition]\n\t\t}\n\n\t\tconst routeTransition = this.router?.findMatch(this.currentLocation, this.targetLocation)\n\n\t\tif (routeTransition) {\n\t\t\treturn this.transitions[routeTransition]\n\t\t}\n\n\t\treturn this.defaultTransition\n\t}\n\n\t/**\n\t * @private\n\t * @param {Document|Node} page\n\t * @param {string} url\n\t * @return {CacheEntry}\n\t */\n\tcreateCacheEntry(page, url) {\n\t\tconst content = page.querySelector('[data-taxi-view]')\n\t\tconst Renderer = content.dataset.taxiView.length ? this.renderers[content.dataset.taxiView] : this.defaultRenderer\n\n\t\tif (!Renderer) {\n\t\t\tconsole.warn(`The Renderer \"${content.dataset.taxiView}\" was set in the data-taxi-view of the requested page, but not registered in Taxi.`)\n\t\t}\n\n\t\treturn {\n\t\t\tpage,\n\t\t\tcontent,\n\t\t\tfinalUrl: url,\n\t\t\tskipCache: content.hasAttribute('data-taxi-nocache'),\n\t\t\tscripts: this.reloadJsFilter ? Array.from(page.querySelectorAll('script')).filter(this.reloadJsFilter) : [],\n\t\t\tstyles: this.reloadCssFilter ? Array.from(page.querySelectorAll('link[rel=\"stylesheet\"], style')).filter(this.reloadCssFilter) : [],\n\t\t\ttitle: page.title,\n\t\t\trenderer: new Renderer({\n\t\t\t\twrapper: this.wrapper,\n\t\t\t\ttitle: page.title,\n\t\t\t\tcontent,\n\t\t\t\tpage\n\t\t\t})\n\t\t}\n\t}\n}\n"],"names":["parser","DOMParser","processUrl","url","details","URL","window","location","origin","normalized","hash","length","replace","hasHash","pathname","host","search","raw","href","reloadElement","node","elementType","parentNode","replaceChild","duplicateElement","appendElement","tagName","document","head","body","appendChild","replacement","createElement","k","attributes","attr","setAttribute","nodeName","nodeValue","innerHTML","Transition","this","wrapper","leave","props","Promise","resolve","_this","onLeave","done","enter","_this2","onEnter","Renderer","page","title","_contentString","content","outerHTML","_DOM","lastElementChild","onEnterCompleted","onLeaveCompleted","initialLoad","update","firstElementChild","createDom","remove","transition","trigger","to","then","removeOldContent","from","RouteStore","data","Map","regexCache","add","fromPattern","toPattern","has","set","RegExp","get","findMatch","currentUrl","nextUrl","potentialMatches","match","IN_PROGRESS","Core","parameters","isTransitioning","currentCacheEntry","cache","activePromises","onClick","e","metaKey","ctrlKey","target","currentTarget","currentLocation","preventDefault","navigateTo","dataset","err","console","warn","onPopstate","isPopping","allowInterruption","popTarget","history","pushState","onPrefetch","preload","links","bypassCache","enablePrefetch","renderers","transitions","default","reloadJsFilter","element","undefined","taxiReload","reloadCssFilter","defaultRenderer","defaultTransition","querySelector","attachEvents","createCacheEntry","cloneNode","renderer","setDefaultRenderer","setDefaultTransition","addRoute","router","preloadAssets","fetch","response","html","updateCache","key","clearCache","reject","_this3","targetLocation","navigationPromise","TransitionClass","chooseTransition","skipCache","fetched","beforeFetch","afterFetch","Error","on","event","callback","E","off","emit","_this4","entry","_this5","loadScripts","scripts","loadStyles","styles","finalUrl","replaceState","cachedScripts","newScripts","currentScripts","Array","querySelectorAll","filter","i","n","splice","cachedStyles","currentStyles","currentInlineStyles","newInlineStyles","el","find","link","append","delegate","runFallback","request","resolvedUrl","mode","method","headers","credentials","ok","text","htmlString","parseFromString","_this6","routeTransition","_this$router","taxiView","hasAttribute"],"mappings":"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"} -------------------------------------------------------------------------------- /dist/taxi.modern.js: -------------------------------------------------------------------------------- 1 | import t from"@unseenco/e";const e=new DOMParser;function r(t){const e=new URL(t,window.location.origin),r=e.hash.length?t.replace(e.hash,""):null;return{hasHash:e.hash.length>0,pathname:e.pathname.replace(/\/+$/,""),host:e.host,search:e.search,raw:t,href:r||e.href}}function i(t,e){t.parentNode.replaceChild(s(t,e),t)}function n(t,e){("HEAD"===t.parentNode.tagName?document.head:document.body).appendChild(s(t,e))}function s(t,e){const r=document.createElement(e);for(let e=0;e{this.onLeave(a({},t,{done:e}))})}enter(t){return new Promise(e=>{this.onEnter(a({},t,{done:e}))})}onLeave({done:t}){t()}onEnter({done:t}){t()}}class h{constructor({content:t,page:e,title:r,wrapper:i}){this._contentString=t.outerHTML,this._DOM=null,this.page=e,this.title=r,this.wrapper=i,this.content=this.wrapper.lastElementChild}onEnter(){}onEnterCompleted(){}onLeave(){}onLeaveCompleted(){}initialLoad(){this.onEnter(),this.onEnterCompleted()}update(){document.title=this.title,this.wrapper.appendChild(this._DOM.firstElementChild),this.content=this.wrapper.lastElementChild,this._DOM=null}createDom(){this._DOM||(this._DOM=document.createElement("div"),this._DOM.innerHTML=this._contentString)}remove(){this.wrapper.firstElementChild.remove()}enter(t,e){return new Promise(r=>{this.onEnter(),t.enter({trigger:e,to:this.content}).then(()=>{this.onEnterCompleted(),r()})})}leave(t,e,r){return new Promise(i=>{this.onLeave(),t.leave({trigger:e,from:this.content}).then(()=>{r&&this.remove(),this.onLeaveCompleted(),i()})})}}class c{constructor(){this.data=new Map,this.regexCache=new Map}add(t,e,r){this.data.has(t)||(this.data.set(t,new Map),this.regexCache.set(t,new RegExp(`^${t}$`))),this.data.get(t).set(e,r),this.regexCache.set(e,new RegExp(`^${e}$`))}findMatch(t,e){for(const[r,i]of this.data)if(t.pathname.match(this.regexCache.get(r))){for(const[t,r]of i)if(e.pathname.match(this.regexCache.get(t)))return r;break}return null}}const l="A transition is currently in progress";class d{constructor(t={}){this.isTransitioning=!1,this.currentCacheEntry=null,this.cache=new Map,this.activePromises=new Map,this.onClick=t=>{if(!t.metaKey&&!t.ctrlKey){const e=r(t.currentTarget.href);if(this.currentLocation=r(window.location.href),this.currentLocation.host!==e.host)return;if(this.currentLocation.href!==e.href||this.currentLocation.hasHash&&!e.hasHash)return t.preventDefault(),void this.navigateTo(e.raw,t.currentTarget.dataset.transition||!1,t.currentTarget).catch(t=>console.warn(t));this.currentLocation.hasHash||e.hasHash||t.preventDefault()}},this.onPopstate=()=>{const t=r(window.location.href);return!(t.pathname===this.currentLocation.pathname&&t.search===this.currentLocation.search&&!this.isPopping)&&(this.allowInterruption||!this.isTransitioning&&!this.isPopping?(this.isPopping||(this.popTarget=window.location.href),this.isPopping=!0,void this.navigateTo(window.location.href,!1,"popstate")):(window.history.pushState({},"",this.popTarget),console.warn(l),!1))},this.onPrefetch=t=>{const e=r(t.currentTarget.href);this.currentLocation.host===e.host&&this.preload(t.currentTarget.href,!1)};const{links:e="a[href]:not([target]):not([href^=\\#]):not([data-taxi-ignore])",removeOldContent:i=!0,allowInterruption:n=!1,bypassCache:s=!1,enablePrefetch:a=!0,renderers:c={default:h},transitions:d={default:o},reloadJsFilter:u=(t=>void 0!==t.dataset.taxiReload),reloadCssFilter:f=(t=>!0)}=t;this.renderers=c,this.transitions=d,this.defaultRenderer=this.renderers.default||h,this.defaultTransition=this.transitions.default||o,this.wrapper=document.querySelector("[data-taxi]"),this.reloadJsFilter=u,this.reloadCssFilter=f,this.removeOldContent=i,this.allowInterruption=n,this.bypassCache=s,this.enablePrefetch=a,this.cache=new Map,this.isPopping=!1,this.attachEvents(e),this.currentLocation=r(window.location.href),this.cache.set(this.currentLocation.href,this.createCacheEntry(document.cloneNode(!0),window.location.href)),this.currentCacheEntry=this.cache.get(this.currentLocation.href),this.currentCacheEntry.renderer.initialLoad()}setDefaultRenderer(t){this.defaultRenderer=this.renderers[t]}setDefaultTransition(t){this.defaultTransition=this.transitions[t]}addRoute(t,e,r){this.router||(this.router=new c),this.router.add(t,e,r)}preload(t,e=!1){var i=this;return t=r(t).href,this.cache.has(t)?Promise.resolve():this.fetch(t,!1).then(async function(r){i.cache.set(t,i.createCacheEntry(r.html,r.url)),e&&i.cache.get(t).renderer.createDom()}).catch(t=>console.warn(t))}updateCache(t){const e=r(t||window.location.href).href;this.cache.has(e)&&this.cache.delete(e),this.cache.set(e,this.createCacheEntry(document.cloneNode(!0),e))}clearCache(t){const e=r(t||window.location.href).href;this.cache.has(e)&&this.cache.delete(e)}navigateTo(t,e=!1,i=!1){var n=this;return new Promise((s,a)=>{if(!this.allowInterruption&&this.isTransitioning)return void a(new Error(l));this.isTransitioning=!0,this.isPopping=!0,this.targetLocation=r(t),this.popTarget=window.location.href;const o=new(this.chooseTransition(e))({wrapper:this.wrapper});let h;if(this.bypassCache||!this.cache.has(this.targetLocation.href)||this.cache.get(this.targetLocation.href).skipCache){const e=this.fetch(this.targetLocation.href).then(t=>{this.cache.set(this.targetLocation.href,this.createCacheEntry(t.html,t.url)),this.cache.get(this.targetLocation.href).renderer.createDom()}).catch(e=>{window.location.href=t});h=this.beforeFetch(this.targetLocation,o,i).then(async function(){return e.then(async function(){return await n.afterFetch(n.targetLocation,o,n.cache.get(n.targetLocation.href),i)})})}else this.cache.get(this.targetLocation.href).renderer.createDom(),h=this.beforeFetch(this.targetLocation,o,i).then(async function(){return await n.afterFetch(n.targetLocation,o,n.cache.get(n.targetLocation.href),i)});h.then(()=>{s()})})}on(e,r){t.on(e,r)}off(e,r){t.off(e,r)}beforeFetch(e,r,i){return t.emit("NAVIGATE_OUT",{from:this.currentCacheEntry,trigger:i}),new Promise(t=>{this.currentCacheEntry.renderer.leave(r,i,this.removeOldContent).then(()=>{"popstate"!==i&&window.history.pushState({},"",e.raw),t()})})}afterFetch(e,r,i,n){return this.currentLocation=e,this.popTarget=this.currentLocation.href,new Promise(s=>{i.renderer.update(),t.emit("NAVIGATE_IN",{from:this.currentCacheEntry,to:i,trigger:n}),this.reloadJsFilter&&this.loadScripts(i.scripts),this.reloadCssFilter&&this.loadStyles(i.styles),"popstate"!==n&&e.href!==i.finalUrl&&window.history.replaceState({},"",i.finalUrl),i.renderer.enter(r,n).then(()=>{t.emit("NAVIGATE_END",{from:this.currentCacheEntry,to:i,trigger:n}),this.currentCacheEntry=i,this.isTransitioning=!1,this.isPopping=!1,s()})})}loadScripts(t){const e=[...t],r=Array.from(document.querySelectorAll("script")).filter(this.reloadJsFilter);for(let t=0;t!t.href||(e.find(e=>e.href===t.href)?void 0:(document.body.append(t),!1)));for(let t=0;t{let s;fetch(t,{mode:"same-origin",method:"GET",headers:{"X-Requested-With":"Taxi"},credentials:"same-origin"}).then(e=>(e.ok||(n("Taxi encountered a non 2xx HTTP status code"),r&&(window.location.href=t)),s=e.url,e.text())).then(t=>{var r;i({html:(r=t,"string"==typeof r?e.parseFromString(r,"text/html"):r),url:s})}).catch(e=>{n(e),r&&(window.location.href=t)}).finally(()=>{this.activePromises.delete(t)})});return this.activePromises.set(t,i),i}chooseTransition(t){var e;if(t)return this.transitions[t];const r=null==(e=this.router)?void 0:e.findMatch(this.currentLocation,this.targetLocation);return r?this.transitions[r]:this.defaultTransition}createCacheEntry(t,e){const r=t.querySelector("[data-taxi-view]"),i=r.dataset.taxiView.length?this.renderers[r.dataset.taxiView]:this.defaultRenderer;return i||console.warn(`The Renderer "${r.dataset.taxiView}" was set in the data-taxi-view of the requested page, but not registered in Taxi.`),{page:t,content:r,finalUrl:e,skipCache:r.hasAttribute("data-taxi-nocache"),scripts:this.reloadJsFilter?Array.from(t.querySelectorAll("script")).filter(this.reloadJsFilter):[],styles:this.reloadCssFilter?Array.from(t.querySelectorAll('link[rel="stylesheet"], style')).filter(this.reloadCssFilter):[],title:t.title,renderer:new i({wrapper:this.wrapper,title:t.title,content:r,page:t})}}}export{d as Core,h as Renderer,o as Transition}; 2 | //# sourceMappingURL=taxi.modern.js.map 3 | -------------------------------------------------------------------------------- /dist/taxi.modern.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"taxi.modern.js","sources":["../src/helpers.js","../src/Transition.js","../src/Renderer.js","../src/RouteStore.js","../src/Core.js"],"sourcesContent":["const parser = new DOMParser()\n\n/**\n * Parse a HTML string into a proper Document.\n *\n * @param {string|Document} html\n * @return {Document|*}\n */\nexport function parseDom(html) {\n\treturn typeof html === 'string' ? parser.parseFromString(html, 'text/html') : html\n}\n\n/**\n * Extract details from a given URL string. Assumed to be on the current TLD.\n *\n * @param {string} url\n * @return {{raw: string, href: string, host: string, search: string, hasHash: boolean, pathname: string}}\n */\nexport function processUrl(url) {\n\tconst details = new URL(url, window.location.origin)\n\tconst normalized = details.hash.length ? url.replace(details.hash, '') : null\n\n\treturn {\n\t\thasHash: details.hash.length > 0,\n\t\tpathname: details.pathname.replace(/\\/+$/, ''),\n\t\thost: details.host,\n\t\tsearch: details.search,\n\t\traw: url,\n\t\thref: normalized || details.href\n\t}\n}\n\n/**\n * Reloads a provided script/stylesheet by replacing with itself.\n *\n * @param {HTMLElement|HTMLScriptElement|HTMLStyleElement} node\n * @param {string} elementType - 'SCRIPT' or 'STYLE'\n */\nexport function reloadElement(node, elementType) {\n\tnode.parentNode.replaceChild(duplicateElement(node, elementType), node)\n}\n\n/**\n * Loads a provided script/stylesheet by appending a clone to the current document.\n *\n * @param {HTMLElement|HTMLStyleElement} node\n * @param {string} elementType - 'SCRIPT' or 'STYLE'\n */\nexport function appendElement(node, elementType) {\n\tconst target = node.parentNode.tagName === 'HEAD' ? document.head : document.body\n\ttarget.appendChild(duplicateElement(node, elementType))\n}\n\n/**\n * Creates a clone of a given HTMLElement or HTMLStyleElement\n *\n * @param {HTMLElement|HTMLStyleElement} node\n * @param {string} elementType - 'SCRIPT' or 'STYLE'\n * @return {HTMLElement|HTMLStyleElement}\n */\nexport function duplicateElement(node, elementType) {\n\tconst replacement = document.createElement(elementType)\n\n\tfor (let k = 0; k < node.attributes.length; k++) {\n\t\tconst attr = node.attributes[k]\n\t\treplacement.setAttribute(attr.nodeName, attr.nodeValue)\n\t}\n\n\t// Inline Script or Style\n\tif (node.innerHTML) {\n\t\treplacement.innerHTML = node.innerHTML\n\t}\n\n\treturn replacement\n}\n","export default class Transition {\n\t/**\n\t * @param {{wrapper: HTMLElement}} props\n\t */\n\tconstructor({ wrapper }) {\n\t\tthis.wrapper = wrapper\n\t}\n\n\t/**\n\t * @param {{ from: HTMLElement|Element, trigger: string|HTMLElement|false }} props\n\t * @return {Promise}\n\t */\n\tleave(props) {\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.onLeave({ ...props, done: resolve })\n\t\t})\n\t}\n\n\t/**\n\t * @param {{ to: HTMLElement|Element, trigger: string|HTMLElement|false }} props\n\t * @return {Promise}\n\t */\n\tenter(props) {\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.onEnter({ ...props, done: resolve })\n\t\t})\n\t}\n\n\t/**\n\t * Handle the transition leaving the previous page.\n\t * @param {{from: HTMLElement|Element, trigger: string|HTMLElement|false, done: function}} props\n\t */\n\tonLeave({ from, trigger, done }) {\n\t\tdone()\n\t}\n\n\t/**\n\t * Handle the transition entering the next page.\n\t * @param {{to: HTMLElement|Element, trigger: string|HTMLElement|false, done: function}} props\n\t */\n\tonEnter({ to, trigger, done }) {\n\t\tdone()\n\t}\n}\n","import Transition from \"./Transition\"\n\nexport default class Renderer {\n\t/**\n\t * @param {{content: HTMLElement|Element, page: Document|Node, title: string, wrapper: Element}} props\n\t */\n\tconstructor({ content, page, title, wrapper }) {\n\t\tthis._contentString = content.outerHTML\n\t\tthis._DOM = null\n\t\tthis.page = page\n\t\tthis.title = title\n\t\tthis.wrapper = wrapper\n\t\tthis.content = this.wrapper.lastElementChild\n\t}\n\n\tonEnter() {\n\n\t}\n\n\tonEnterCompleted() {\n\n\t}\n\n\tonLeave() {\n\n\t}\n\n\tonLeaveCompleted() {\n\n\t}\n\n\tinitialLoad() {\n\t\tthis.onEnter()\n\t\tthis.onEnterCompleted()\n\t}\n\n\tupdate() {\n\t\tdocument.title = this.title\n\t\tthis.wrapper.appendChild(this._DOM.firstElementChild)\n\t\tthis.content = this.wrapper.lastElementChild\n\t\tthis._DOM = null\n\t}\n\n\tcreateDom() {\n\t\tif (!this._DOM) {\n\t\t\tthis._DOM = document.createElement('div')\n\t\t\tthis._DOM.innerHTML = this._contentString\n\t\t}\n\t}\n\n\tremove() {\n\t\tthis.wrapper.firstElementChild.remove()\n\t}\n\n\t/**\n\t * Called when transitioning into the current page.\n\t * @param {Transition} transition\n\t * @param {string|HTMLElement|false} trigger\n\t * @return {Promise}\n\t */\n\tenter(transition, trigger) {\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.onEnter()\n\n\t\t\ttransition.enter({ trigger, to: this.content })\n\t\t\t\t.then(() => {\n\t\t\t\t\tthis.onEnterCompleted()\n\t\t\t\t\tresolve()\n\t\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * Called when transitioning away from the current page.\n\t * @param {Transition} transition\n\t * @param {string|HTMLElement|false} trigger\n\t * @param {boolean} removeOldContent\n\t * @return {Promise}\n\t */\n\tleave(transition, trigger, removeOldContent) {\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.onLeave()\n\n\t\t\ttransition.leave({ trigger, from: this.content })\n\t\t\t\t.then(() => {\n\t\t\t\t\tif (removeOldContent) {\n\t\t\t\t\t\tthis.remove()\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.onLeaveCompleted()\n\t\t\t\t\tresolve()\n\t\t\t\t})\n\t\t})\n\t}\n}\n","export default class RouteStore {\n\t/**\n\t * @type {Map>}\n\t */\n\tdata = new Map()\n\n\t/**\n\t * @type {Map}\n\t */\n\tregexCache = new Map()\n\n\t/**\n\t *\n\t * @param {string} fromPattern\n\t * @param {string} toPattern\n\t * @param {string} transition\n\t */\n\tadd(fromPattern, toPattern, transition) {\n\t\tif (!this.data.has(fromPattern)) {\n\t\t\tthis.data.set(fromPattern, new Map())\n\t\t\tthis.regexCache.set(fromPattern, new RegExp(`^${fromPattern}$`))\n\t\t}\n\n\t\tthis.data.get(fromPattern).set(toPattern, transition)\n\t\tthis.regexCache.set(toPattern, new RegExp(`^${toPattern}$`))\n\t}\n\n\t/**\n\t *\n\t * @param {{ raw: string, href: string, hasHash: boolean, pathname: string }} currentUrl\n\t * @param {{ raw: string, href: string, hasHash: boolean, pathname: string }} nextUrl\n\t * @return {string|null}\n\t */\n\tfindMatch(currentUrl, nextUrl) {\n\t\t// Loop through all from patterns\n\t\tfor (const [fromPattern, potentialMatches] of this.data) {\n\t\t\t// If we have a match\n\t\t\tif (currentUrl.pathname.match(this.regexCache.get(fromPattern))) {\n\t\t\t\t// loop through all associated to patterns\n\t\t\t\tfor (const [toPattern, transition] of potentialMatches) {\n\t\t\t\t\t// If we find a match, return it\n\t\t\t\t\tif (nextUrl.pathname.match(this.regexCache.get(toPattern))) {\n\t\t\t\t\t\treturn transition\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\treturn null\n\t}\n}\n","import E from '@unseenco/e'\nimport { appendElement, parseDom, processUrl, reloadElement } from './helpers'\nimport Transition from './Transition'\nimport Renderer from './Renderer'\nimport RouteStore from './RouteStore'\n\nconst IN_PROGRESS = 'A transition is currently in progress'\n\n/**\n * @typedef CacheEntry\n * @type {object}\n * @property {typeof Renderer|Renderer} renderer\n * @property {Document|Node} page\n * @property {array} scripts\n * @property {HTMLLinkElement[]} styles\n * @property {string} finalUrl\n * @property {boolean} skipCache\n * @property {string} title\n * @property {HTMLElement|Element} content\n */\n\nexport default class Core {\n\tisTransitioning = false\n\n\t/**\n\t * @type {CacheEntry|null}\n\t */\n\tcurrentCacheEntry = null\n\n\t/**\n\t * @type {Map}\n\t */\n\tcache = new Map()\n\n\t/**\n\t * @private\n\t * @type {Map}\n\t */\n\tactivePromises = new Map()\n\n\t/**\n\t * @param {{\n\t * \t\tlinks?: string,\n\t * \t\tremoveOldContent?: boolean,\n\t * \t\tallowInterruption?: boolean,\n\t * \t\tbypassCache?: boolean,\n\t * \t\tenablePrefetch?: boolean,\n\t * \t\trenderers?: Object.,\n\t * \t\ttransitions?: Object.,\n\t * \t\treloadJsFilter?: boolean|function(HTMLElement): boolean,\n\t * \t\treloadCssFilter?: boolean|function(HTMLLinkElement): boolean\n\t * }} parameters\n\t */\n\tconstructor(parameters = {}) {\n\t\tconst {\n\t\t\tlinks = 'a[href]:not([target]):not([href^=\\\\#]):not([data-taxi-ignore])',\n\t\t\tremoveOldContent = true,\n\t\t\tallowInterruption = false,\n\t\t\tbypassCache = false,\n\t\t\tenablePrefetch = true,\n\t\t\trenderers = {\n\t\t\t\tdefault: Renderer\n\t\t\t},\n\t\t\ttransitions = {\n\t\t\t\tdefault: Transition\n\t\t\t},\n\t\t\treloadJsFilter = (element) => element.dataset.taxiReload !== undefined,\n\t\t\treloadCssFilter = (element) => true //element.dataset.taxiReload !== undefined\n\t\t} = parameters\n\n\t\tthis.renderers = renderers\n\t\tthis.transitions = transitions\n\t\tthis.defaultRenderer = this.renderers.default || Renderer\n\t\tthis.defaultTransition = this.transitions.default || Transition\n\t\tthis.wrapper = document.querySelector('[data-taxi]')\n\t\tthis.reloadJsFilter = reloadJsFilter\n\t\tthis.reloadCssFilter = reloadCssFilter\n\t\tthis.removeOldContent = removeOldContent\n\t\tthis.allowInterruption = allowInterruption\n\t\tthis.bypassCache = bypassCache\n\t\tthis.enablePrefetch = enablePrefetch\n\t\tthis.cache = new Map()\n\t\tthis.isPopping = false\n\n\t\t// Add delegated link events\n\t\tthis.attachEvents(links)\n\n\t\tthis.currentLocation = processUrl(window.location.href)\n\n\t\t// as this is the initial page load, prime this page into the cache\n\t\tthis.cache.set(this.currentLocation.href, this.createCacheEntry(document.cloneNode(true), window.location.href))\n\n\t\t// fire the current Renderer enter methods\n\t\tthis.currentCacheEntry = this.cache.get(this.currentLocation.href)\n\t\tthis.currentCacheEntry.renderer.initialLoad()\n\t}\n\n\t/**\n\t * @param {string} renderer\n\t */\n\tsetDefaultRenderer(renderer) {\n\t\tthis.defaultRenderer = this.renderers[renderer]\n\t}\n\n\t/**\n\t * @param {string} transition\n\t */\n\tsetDefaultTransition(transition) {\n\t\tthis.defaultTransition = this.transitions[transition]\n\t}\n\n\t/**\n\t * Registers a route into the RouteStore\n\t *\n\t * @param {string} fromPattern\n\t * @param {string} toPattern\n\t * @param {string} transition\n\t */\n\taddRoute(fromPattern, toPattern, transition) {\n\t\tif (!this.router) {\n\t\t\tthis.router = new RouteStore()\n\t\t}\n\n\t\tthis.router.add(fromPattern, toPattern, transition)\n\t}\n\n\t/**\n\t * Prime the cache for a given URL\n\t *\n\t * @param {string} url\n\t * @param {boolean} [preloadAssets]\n\t * @return {Promise}\n\t */\n\tpreload(url, preloadAssets = false) {\n\t\t// convert relative URLs to absolute\n\t\turl = processUrl(url).href\n\n\t\tif (!this.cache.has(url)) {\n\t\t\treturn this.fetch(url, false)\n\t\t\t\t.then(async (response) => {\n\t\t\t\t\tthis.cache.set(url, this.createCacheEntry(response.html, response.url))\n\n\t\t\t\t\tif (preloadAssets) {\n\t\t\t\t\t\tthis.cache.get(url).renderer.createDom()\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.catch(err => console.warn(err))\n\t\t}\n\n\t\treturn Promise.resolve()\n\t}\n\n\t/**\n\t * Updates the HTML cache for a given URL.\n\t * If no URL is passed, then cache for the current page is updated.\n\t * Useful when adding/removing content via AJAX such as a search page or infinite loader.\n\t *\n\t * @param {string} [url]\n\t */\n\tupdateCache(url) {\n\t\tconst key = processUrl(url || window.location.href).href\n\n\t\tif (this.cache.has(key)) {\n\t\t\tthis.cache.delete(key)\n\t\t}\n\n\t\tthis.cache.set(key, this.createCacheEntry(document.cloneNode(true), key))\n\t}\n\n\t/**\n\t * Clears the cache for a given URL.\n\t * If no URL is passed, then cache for the current page is cleared.\n\t *\n\t * @param {string} [url]\n\t */\n\tclearCache(url) {\n\t\tconst key = processUrl(url || window.location.href).href\n\n\t\tif (this.cache.has(key)) {\n\t\t\tthis.cache.delete(key)\n\t\t}\n\t}\n\n\t/**\n\t * @param {string} url\n\t * @param {string|false} [transition]\n\t * @param {string|false|HTMLElement} [trigger]\n\t * @return {Promise}\n\t */\n\tnavigateTo(url, transition = false, trigger = false) {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\t// Don't allow multiple navigations to occur at once\n\t\t\tif (!this.allowInterruption && this.isTransitioning) {\n\t\t\t\treject(new Error(IN_PROGRESS))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.isTransitioning = true\n\t\t\tthis.isPopping = true\n\t\t\tthis.targetLocation = processUrl(url)\n\t\t\tthis.popTarget = window.location.href\n\n\t\t\tconst TransitionClass = new (this.chooseTransition(transition))({ wrapper: this.wrapper })\n\n\t\t\tlet navigationPromise\n\n\t\t\tif (this.bypassCache || !this.cache.has(this.targetLocation.href) || this.cache.get(this.targetLocation.href).skipCache) {\n\t\t\t\tconst fetched = this.fetch(this.targetLocation.href)\n\t\t\t\t\t.then((response) => {\n\t\t\t\t\t\tthis.cache.set(this.targetLocation.href, this.createCacheEntry(response.html, response.url))\n\t\t\t\t\t\tthis.cache.get(this.targetLocation.href).renderer.createDom()\n\t\t\t\t\t})\n\t\t\t\t\t.catch(err => {\n\t\t\t\t\t\t// we encountered a 4** or 5** error, redirect to the requested URL\n\t\t\t\t\t\twindow.location.href = url\n\t\t\t\t\t})\n\n\t\t\t\tnavigationPromise = this.beforeFetch(this.targetLocation, TransitionClass, trigger)\n\t\t\t\t\t.then(async () => {\n\t\t\t\t\t\treturn fetched.then(async () => {\n\t\t\t\t\t\t\treturn await this.afterFetch(this.targetLocation, TransitionClass, this.cache.get(this.targetLocation.href), trigger)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tthis.cache.get(this.targetLocation.href).renderer.createDom()\n\n\t\t\t\tnavigationPromise = this.beforeFetch(this.targetLocation, TransitionClass, trigger)\n\t\t\t\t\t.then(async () => {\n\t\t\t\t\t\treturn await this.afterFetch(this.targetLocation, TransitionClass, this.cache.get(this.targetLocation.href), trigger)\n\t\t\t\t\t})\n\t\t\t}\n\n\t\t\tnavigationPromise.then(() => {\n\t\t\t\tresolve()\n\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * Add an event listener.\n\t * @param {string} event\n\t * @param {any} callback\n\t */\n\ton(event, callback) {\n\t\tE.on(event, callback)\n\t}\n\n\t/**\n\t * Remove an event listener.\n\t * @param {string} event\n\t * @param {any} [callback]\n\t */\n\toff(event, callback) {\n\t\tE.off(event, callback)\n\t}\n\n\t/**\n\t * @private\n\t * @param {{ raw: string, href: string, hasHash: boolean, pathname: string }} url\n\t * @param {Transition} TransitionClass\n\t * @param {string|HTMLElement|false} trigger\n\t * @return {Promise}\n\t */\n\tbeforeFetch(url, TransitionClass, trigger) {\n\t\tE.emit('NAVIGATE_OUT', {\n\t\t\tfrom: this.currentCacheEntry,\n\t\t\ttrigger\n\t\t})\n\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.currentCacheEntry.renderer.leave(TransitionClass, trigger, this.removeOldContent)\n\t\t\t\t.then(() => {\n\t\t\t\t\tif (trigger !== 'popstate') {\n\t\t\t\t\t\twindow.history.pushState({}, '', url.raw)\n\t\t\t\t\t}\n\n\t\t\t\t\tresolve()\n\t\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * @private\n\t * @param {{ raw: string, href: string, host: string, hasHash: boolean, pathname: string }} url\n\t * @param {Transition} TransitionClass\n\t * @param {CacheEntry} entry\n\t * @param {string|HTMLElement|false} trigger\n\t * @return {Promise}\n\t */\n\tafterFetch(url, TransitionClass, entry, trigger) {\n\t\tthis.currentLocation = url\n\t\tthis.popTarget = this.currentLocation.href\n\n\t\treturn new Promise((resolve) => {\n\t\t\tentry.renderer.update()\n\n\t\t\tE.emit('NAVIGATE_IN', {\n\t\t\t\tfrom: this.currentCacheEntry,\n\t\t\t\tto: entry,\n\t\t\t\ttrigger\n\t\t\t})\n\n\t\t\tif (this.reloadJsFilter) {\n\t\t\t\tthis.loadScripts(entry.scripts)\n\t\t\t}\n\n\t\t\tif (this.reloadCssFilter) {\n\t\t\t\tthis.loadStyles(entry.styles)\n\t\t\t}\n\n\t\t\t// If the fetched url had a redirect chain, then replace the history to reflect the final resolved URL\n\t\t\tif (trigger !== 'popstate' && url.href !== entry.finalUrl) {\n\t\t\t\twindow.history.replaceState({}, '', entry.finalUrl)\n\t\t\t}\n\n\t\t\tentry.renderer.enter(TransitionClass, trigger)\n\t\t\t\t.then(() => {\n\t\t\t\t\tE.emit('NAVIGATE_END', {\n\t\t\t\t\t\tfrom: this.currentCacheEntry,\n\t\t\t\t\t\tto: entry,\n\t\t\t\t\t\ttrigger\n\t\t\t\t\t})\n\n\t\t\t\t\tthis.currentCacheEntry = entry\n\t\t\t\t\tthis.isTransitioning = false\n\t\t\t\t\tthis.isPopping = false\n\t\t\t\t\tresolve()\n\t\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * Load up scripts from the target page if needed\n\t *\n\t * @param {HTMLElement[]} cachedScripts\n\t */\n\tloadScripts(cachedScripts) {\n\t\tconst newScripts = [...cachedScripts]\n\t\tconst currentScripts = Array.from(document.querySelectorAll('script')).filter(this.reloadJsFilter)\n\n\t\t// loop through all new scripts\n\t\tfor (let i = 0; i < currentScripts.length; i++) {\n\t\t\tfor (let n = 0; n < newScripts.length; n++) {\n\t\t\t\tif (currentScripts[i].outerHTML === newScripts[n].outerHTML) {\n\t\t\t\t\treloadElement(currentScripts[i], 'SCRIPT')\n\t\t\t\t\tnewScripts.splice(n, 1)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (const script of newScripts) {\n\t\t\tappendElement(script, 'SCRIPT')\n\t\t}\n\t}\n\n\t/**\n\t * Load up styles from the target page if needed\n\t *\n\t * @param {Array} cachedStyles\n\t */\n\tloadStyles(cachedStyles) {\n\t\tconst currentStyles = Array.from(document.querySelectorAll('link[rel=\"stylesheet\"]')).filter(this.reloadCssFilter)\n\t\tconst currentInlineStyles = Array.from(document.querySelectorAll('style')).filter(this.reloadCssFilter)\n\n\t\tconst newInlineStyles = cachedStyles.filter(el => {\n\t\t\t// no el.href, assume it's an inline style\n\t\t\tif (!el.href) {\n\t\t\t\treturn true\n\t\t\t} else if (!currentStyles.find((link) => link.href === el.href)) {\n\t\t\t\tdocument.body.append(el)\n\t\t\t\treturn false\n\t\t\t}\n\t\t})\n\n\t\t// loop through all new inline styles\n\t\tfor (let i = 0; i < currentInlineStyles.length; i++) {\n\t\t\tfor (let n = 0; n < newInlineStyles.length; n++) {\n\t\t\t\tif (currentInlineStyles[i].outerHTML === newInlineStyles[n].outerHTML) {\n\t\t\t\t\treloadElement(currentInlineStyles[i], 'STYLE')\n\t\t\t\t\tnewInlineStyles.splice(n, 1)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (const style of newInlineStyles) {\n\t\t\tappendElement(style, 'STYLE')\n\t\t}\n\t}\n\n\t/**\n\t * @private\n\t * @param {string} links\n\t */\n\tattachEvents(links) {\n\t\tE.delegate('click', links, this.onClick)\n\t\tE.on('popstate', window, this.onPopstate)\n\n\t\tif (this.enablePrefetch) {\n\t\t\tE.delegate('mouseenter focus', links, this.onPrefetch)\n\t\t}\n\t}\n\n\t/**\n\t * @private\n\t * @param {MouseEvent} e\n\t */\n\tonClick = (e) => {\n\t\tif (!(e.metaKey || e.ctrlKey)) {\n\t\t\tconst target = processUrl(e.currentTarget.href)\n\t\t\tthis.currentLocation = processUrl(window.location.href)\n\n\t\t\tif (this.currentLocation.host !== target.host) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// the target is a new URL, or is removing the hash from the current URL\n\t\t\tif (this.currentLocation.href !== target.href || (this.currentLocation.hasHash && !target.hasHash)) {\n\t\t\t\te.preventDefault()\n\t\t\t\t// noinspection JSIgnoredPromiseFromCall\n\t\t\t\tthis.navigateTo(target.raw, e.currentTarget.dataset.transition || false, e.currentTarget).catch(err => console.warn(err))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// a click to the current URL was detected\n\t\t\tif (!this.currentLocation.hasHash && !target.hasHash) {\n\t\t\t\te.preventDefault()\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @private\n\t * @return {void|boolean}\n\t */\n\tonPopstate = () => {\n\t\tconst target = processUrl(window.location.href)\n\n\t\t// don't trigger for on-page anchors\n\t\tif (\n\t\t\ttarget.pathname === this.currentLocation.pathname\n\t\t\t&& target.search === this.currentLocation.search\n\t\t\t&& !this.isPopping\n\t\t) {\n\t\t\treturn false\n\t\t}\n\n\t\tif (!this.allowInterruption && (this.isTransitioning || this.isPopping)) {\n\t\t\t// overwrite history state with current page if currently navigating\n\t\t\twindow.history.pushState({}, '', this.popTarget)\n\t\t\tconsole.warn(IN_PROGRESS)\n\t\t\treturn false\n\t\t}\n\n\t\tif (!this.isPopping) {\n\t\t\tthis.popTarget = window.location.href\n\t\t}\n\n\t\tthis.isPopping = true\n\n\t\t// noinspection JSIgnoredPromiseFromCall\n\t\tthis.navigateTo(window.location.href, false, 'popstate')\n\t}\n\n\t/**\n\t * @private\n\t * @param {MouseEvent} e\n\t */\n\tonPrefetch = (e) => {\n\t\tconst target = processUrl(e.currentTarget.href)\n\n\t\tif (this.currentLocation.host !== target.host) {\n\t\t\treturn\n\t\t}\n\n\t\tthis.preload(e.currentTarget.href, false)\n\t}\n\n\t/**\n\t * @private\n\t * @param {string} url\n\t * @param {boolean} [runFallback]\n\t * @return {Promise<{html: Document, url: string}>}\n\t */\n\tfetch(url, runFallback = true) {\n\t\t// If Taxi is currently performing a fetch for the given URL, return that instead of starting a new request\n\t\tif (this.activePromises.has(url)) {\n\t\t\treturn this.activePromises.get(url)\n\t\t}\n\n\t\tconst request = new Promise((resolve, reject) => {\n\t\t\tlet resolvedUrl\n\n\t\t\tfetch(url, {\n\t\t\t\tmode: 'same-origin',\n\t\t\t\tmethod: 'GET',\n\t\t\t\theaders: { 'X-Requested-With': 'Taxi' },\n\t\t\t\tcredentials: 'same-origin'\n\t\t\t})\n\t\t\t\t.then((response) => {\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\treject('Taxi encountered a non 2xx HTTP status code')\n\n\t\t\t\t\t\tif (runFallback) {\n\t\t\t\t\t\t\twindow.location.href = url\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tresolvedUrl = response.url\n\n\t\t\t\t\treturn response.text()\n\t\t\t\t})\n\t\t\t\t.then((htmlString) => {\n\t\t\t\t\tresolve({ html: parseDom(htmlString), url: resolvedUrl })\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\treject(err)\n\n\t\t\t\t\tif (runFallback) {\n\t\t\t\t\t\twindow.location.href = url\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.finally(() => {\n\t\t\t\t\tthis.activePromises.delete(url)\n\t\t\t\t})\n\t\t})\n\n\t\tthis.activePromises.set(url, request)\n\n\t\treturn request\n\t}\n\n\t/**\n\t * @private\n\t * @param {string|false} transition\n\t * @return {Transition|function}\n\t */\n\tchooseTransition(transition) {\n\t\tif (transition) {\n\t\t\treturn this.transitions[transition]\n\t\t}\n\n\t\tconst routeTransition = this.router?.findMatch(this.currentLocation, this.targetLocation)\n\n\t\tif (routeTransition) {\n\t\t\treturn this.transitions[routeTransition]\n\t\t}\n\n\t\treturn this.defaultTransition\n\t}\n\n\t/**\n\t * @private\n\t * @param {Document|Node} page\n\t * @param {string} url\n\t * @return {CacheEntry}\n\t */\n\tcreateCacheEntry(page, url) {\n\t\tconst content = page.querySelector('[data-taxi-view]')\n\t\tconst Renderer = content.dataset.taxiView.length ? this.renderers[content.dataset.taxiView] : this.defaultRenderer\n\n\t\tif (!Renderer) {\n\t\t\tconsole.warn(`The Renderer \"${content.dataset.taxiView}\" was set in the data-taxi-view of the requested page, but not registered in Taxi.`)\n\t\t}\n\n\t\treturn {\n\t\t\tpage,\n\t\t\tcontent,\n\t\t\tfinalUrl: url,\n\t\t\tskipCache: content.hasAttribute('data-taxi-nocache'),\n\t\t\tscripts: this.reloadJsFilter ? Array.from(page.querySelectorAll('script')).filter(this.reloadJsFilter) : [],\n\t\t\tstyles: this.reloadCssFilter ? Array.from(page.querySelectorAll('link[rel=\"stylesheet\"], style')).filter(this.reloadCssFilter) : [],\n\t\t\ttitle: page.title,\n\t\t\trenderer: new Renderer({\n\t\t\t\twrapper: this.wrapper,\n\t\t\t\ttitle: page.title,\n\t\t\t\tcontent,\n\t\t\t\tpage\n\t\t\t})\n\t\t}\n\t}\n}\n"],"names":["parser","DOMParser","processUrl","url","details","URL","window","location","origin","normalized","hash","length","replace","hasHash","pathname","host","search","raw","href","reloadElement","node","elementType","parentNode","replaceChild","duplicateElement","appendElement","tagName","document","head","body","appendChild","replacement","createElement","k","attributes","attr","setAttribute","nodeName","nodeValue","innerHTML","Transition","constructor","wrapper","this","leave","props","Promise","resolve","onLeave","done","enter","onEnter","Renderer","content","page","title","_contentString","outerHTML","_DOM","lastElementChild","onEnterCompleted","onLeaveCompleted","initialLoad","update","firstElementChild","createDom","remove","transition","trigger","to","then","removeOldContent","from","RouteStore","data","Map","regexCache","add","fromPattern","toPattern","has","set","RegExp","get","findMatch","currentUrl","nextUrl","potentialMatches","match","IN_PROGRESS","Core","parameters","isTransitioning","currentCacheEntry","cache","activePromises","onClick","e","metaKey","ctrlKey","target","currentTarget","currentLocation","preventDefault","navigateTo","dataset","catch","err","console","warn","onPopstate","isPopping","allowInterruption","popTarget","history","pushState","onPrefetch","preload","links","bypassCache","enablePrefetch","renderers","default","transitions","reloadJsFilter","element","undefined","taxiReload","reloadCssFilter","defaultRenderer","defaultTransition","querySelector","attachEvents","createCacheEntry","cloneNode","renderer","setDefaultRenderer","setDefaultTransition","addRoute","router","preloadAssets","fetch","async","response","_this","html","updateCache","key","delete","clearCache","reject","Error","targetLocation","TransitionClass","chooseTransition","navigationPromise","skipCache","fetched","beforeFetch","_this2","afterFetch","on","event","callback","E","off","emit","entry","loadScripts","scripts","loadStyles","styles","finalUrl","replaceState","cachedScripts","newScripts","currentScripts","Array","querySelectorAll","filter","i","n","splice","script","cachedStyles","currentStyles","currentInlineStyles","newInlineStyles","el","find","link","append","style","delegate","runFallback","request","resolvedUrl","mode","method","headers","credentials","ok","text","htmlString","parseFromString","finally","routeTransition","_this$router","taxiView","hasAttribute"],"mappings":"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"} -------------------------------------------------------------------------------- /dist/taxi.umd.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@unseenco/e")):"function"==typeof define&&define.amd?define(["exports","@unseenco/e"],t):t((e||self).taxi={},e.E)}(this,function(e,t){function r(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n=/*#__PURE__*/r(t);function i(){return i=Object.assign||function(e){for(var t=1;te.length)&&(t=e.length);for(var r=0,n=new Array(t);r=e.length?{done:!0}:{done:!1,value:e[n++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var c=new DOMParser;function s(e){var t=new URL(e,window.location.origin),r=t.hash.length?e.replace(t.hash,""):null;return{hasHash:t.hash.length>0,pathname:t.pathname.replace(/\/+$/,""),host:t.host,search:t.search,raw:e,href:r||t.href}}function h(e,t){e.parentNode.replaceChild(l(e,t),e)}function u(e,t){("HEAD"===e.parentNode.tagName?document.head:document.body).appendChild(l(e,t))}function l(e,t){for(var r=document.createElement(t),n=0;n 2 | 3 | 4 | 5 | 6 | Taxi.js - {{ title }} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | {% include "nav.njk" %} 68 | 69 |
70 |
71 |
72 |
73 | {% block content %}{% endblock %} 74 | {{ content | safe }} 75 |
76 |
77 | 78 |
79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 |
90 |
91 |
92 | 93 |
94 |
95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /docs/_includes/nav.njk: -------------------------------------------------------------------------------- 1 | 51 | 52 | -------------------------------------------------------------------------------- /docs/_public/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftedbygc/taxi/6785cd56f1f843401a12b867ce0b01425af4c0bb/docs/_public/.nojekyll -------------------------------------------------------------------------------- /docs/_public/CNAME: -------------------------------------------------------------------------------- 1 | taxi.js.org -------------------------------------------------------------------------------- /docs/_public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftedbygc/taxi/6785cd56f1f843401a12b867ce0b01425af4c0bb/docs/_public/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/_public/android-chrome-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftedbygc/taxi/6785cd56f1f843401a12b867ce0b01425af4c0bb/docs/_public/android-chrome-384x384.png -------------------------------------------------------------------------------- /docs/_public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftedbygc/taxi/6785cd56f1f843401a12b867ce0b01425af4c0bb/docs/_public/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/_public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/_public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftedbygc/taxi/6785cd56f1f843401a12b867ce0b01425af4c0bb/docs/_public/favicon-16x16.png -------------------------------------------------------------------------------- /docs/_public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftedbygc/taxi/6785cd56f1f843401a12b867ce0b01425af4c0bb/docs/_public/favicon-32x32.png -------------------------------------------------------------------------------- /docs/_public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftedbygc/taxi/6785cd56f1f843401a12b867ce0b01425af4c0bb/docs/_public/favicon.ico -------------------------------------------------------------------------------- /docs/_public/fonts/Inter-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftedbygc/taxi/6785cd56f1f843401a12b867ce0b01425af4c0bb/docs/_public/fonts/Inter-Bold.woff -------------------------------------------------------------------------------- /docs/_public/fonts/Inter-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftedbygc/taxi/6785cd56f1f843401a12b867ce0b01425af4c0bb/docs/_public/fonts/Inter-Bold.woff2 -------------------------------------------------------------------------------- /docs/_public/fonts/Inter-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftedbygc/taxi/6785cd56f1f843401a12b867ce0b01425af4c0bb/docs/_public/fonts/Inter-Regular.woff -------------------------------------------------------------------------------- /docs/_public/fonts/Inter-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftedbygc/taxi/6785cd56f1f843401a12b867ce0b01425af4c0bb/docs/_public/fonts/Inter-Regular.woff2 -------------------------------------------------------------------------------- /docs/_public/fonts/NeueMontreal-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftedbygc/taxi/6785cd56f1f843401a12b867ce0b01425af4c0bb/docs/_public/fonts/NeueMontreal-Regular.woff -------------------------------------------------------------------------------- /docs/_public/fonts/NeueMontreal-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftedbygc/taxi/6785cd56f1f843401a12b867ce0b01425af4c0bb/docs/_public/fonts/NeueMontreal-Regular.woff2 -------------------------------------------------------------------------------- /docs/_public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftedbygc/taxi/6785cd56f1f843401a12b867ce0b01425af4c0bb/docs/_public/mstile-150x150.png -------------------------------------------------------------------------------- /docs/_public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-384x384.png", 12 | "sizes": "384x384", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /docs/_public/social.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftedbygc/taxi/6785cd56f1f843401a12b867ce0b01425af4c0bb/docs/_public/social.jpg -------------------------------------------------------------------------------- /docs/api-events.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/base.njk 3 | title: API and Events Reference 4 | --- 5 | 6 | # API 7 | 8 | ## addRoute() 9 | Registers a route into the RouteStore. 10 | 11 | ```js 12 | /** 13 | * addRoute(fromPattern: string, toPattern: string, transition: string): void 14 | */ 15 | taxi.addRoute('/blog/.*', '/', 'blogToHome') 16 | ``` 17 | 18 | ## navigateTo() 19 | Perform a manual navigation to the provided URL. 20 | 21 | If a `transition` name is not provided then Taxi will try and find a match in the RouteStore, otherwise the default transition will be used. 22 | 23 | ```js 24 | /** 25 | * navigateTo(url: string, transition?: string = false): Promise 26 | */ 27 | taxi.navigateTo('/contact') 28 | 29 | taxi.navigateTo('/contact', 'explcitTransition').then(() => { ... }) 30 | ``` 31 | 32 | ## preload() 33 | Prefetch the provided URL and add it to the cache ahead of any user navigation. 34 | 35 | ```js 36 | /** 37 | * preload( url: string, preloadAssets?: boolean = false): Promise 38 | */ 39 | taxi.preload('/path/to/preload') 40 | ``` 41 | 42 | You can pass a second argument to indicate you want to preload the assets on the target URL as well (images, media, etc): 43 | ```js 44 | taxi.preload('/path/to/preload', true) 45 | ``` 46 | 47 | 48 | As `preload` returns a promise, you can also run code based on whether the fetch was a success or not: 49 | 50 | ```js 51 | taxi.preload('/path/to/404') 52 | .then(() => console.log('success!')) 53 | .catch(err => console.warn(err)) 54 | ``` 55 | 56 | ## updateCache() 57 | Updates the cached HTML for the provided URL. If no URL is provided, update cache for the current URL. 58 | 59 | Useful when adding/removing content via AJAX such as a search page or infinite scroll. 60 | 61 | ```js 62 | /** 63 | * updateCache(url?: string): void 64 | */ 65 | taxi.updateCache() 66 | ``` 67 | 68 | 69 | ## clearCache() 70 | Remove the cached HTML for the provided URL. If no URL is provided, remove cache for the current URL. 71 | 72 | ```js 73 | /** 74 | * clearCache(url?: string): void 75 | */ 76 | taxi.clearCache('/path/to/delete') 77 | ``` 78 | 79 | ## setDefaultRenderer() 80 | If you don't like "default" as the name of your default renderer, you can change the default renderer to be anything you like here. 81 | 82 | ```js 83 | /** 84 | * setDefaultRenderer(renderer: string): void 85 | */ 86 | taxi.setDefaultRenderer('myRenderer') 87 | ``` 88 | 89 | ## setDefaultTransition() 90 | Same as `setDefaultRenderer`, but for the transitions instead. 91 | ```js 92 | /** 93 | * setDefaultTransition(transition: string): void 94 | */ 95 | taxi.setDefaultTransition('myTransition') 96 | ``` 97 | 98 | 99 | 100 | ## Events 101 | Events are handled by [@unseenco/e](https://www.npmjs.com/package/@unseenco/e). 102 | 103 | ### Adding Listeners 104 | ```js 105 | import { Core } from '@unseenco/taxi' 106 | 107 | const taxi = new Core({ ... }) 108 | 109 | // This event is sent everytime a `data-taxi-view` is added to the DOM 110 | taxi.on('NAVIGATE_IN', ({ to, trigger }) => { 111 | // ... 112 | }) 113 | 114 | // This event is sent before the `onLeave()` method of a transition is run to hide a `data-router-view` 115 | taxi.on('NAVIGATE_OUT', ({ from, trigger }) => { 116 | // ... 117 | }) 118 | 119 | // This event is sent everytime the `done()` method is called in the `onEnter()` method of a transition 120 | taxi.on('NAVIGATE_END', ({ to, from, trigger }) => { 121 | // ... 122 | }) 123 | ``` 124 | 125 | ### Removing Listeners 126 | You can call `taxi.off(event_name)` to remove all listeners for an event, or pass the callback to remove just that listener instead: 127 | 128 | ```js 129 | function foo() { 130 | ... 131 | } 132 | 133 | taxi.on('NAVIGATE_OUT', foo) 134 | 135 | // Remove just the foo listener 136 | taxi.off('NAVIGATE_OUT', foo) 137 | 138 | // Remove all listeners 139 | taxi.off('NAVIGATE_IN') 140 | ``` -------------------------------------------------------------------------------- /docs/assets/js/index.js: -------------------------------------------------------------------------------- 1 | import gsap from 'gsap' 2 | import E from '@unseenco/e' 3 | import { Core } from '../../../src/taxi' 4 | import DefaultRenderer from './renderers/DefaultRenderer' 5 | import DefaultTransition from './transitions/DefaultTransition' 6 | 7 | E.on('DOMContentLoaded', window, function () { 8 | const taxi = new Core({ 9 | renderers: { 10 | default: DefaultRenderer 11 | }, 12 | transitions: { 13 | default: DefaultTransition 14 | } 15 | }) 16 | 17 | const navItems = document.querySelectorAll('.js-nav li') 18 | const menuToggle = document.querySelector('.js-menu-toggle') 19 | const menu = document.getElementById('main-menu') 20 | 21 | taxi.on('NAVIGATE_OUT', () => { 22 | navItems.forEach(el => { 23 | el.classList.remove('active') 24 | }) 25 | 26 | // close menu if open 27 | menuToggle.setAttribute('aria-expanded', 'false') 28 | 29 | gsap.to(menu, { x: () => -menu.clientWidth }) 30 | .then(() => { 31 | menu.classList.add('invisible') 32 | }) 33 | }) 34 | 35 | taxi.on('NAVIGATE_IN', ({ to }) => { 36 | window.scrollTo(0, 0) 37 | 38 | const next = to.page.querySelector('.js-nav li.active a') 39 | 40 | if (next) { 41 | navItems.forEach(el => { 42 | if (el.firstElementChild.href === next.href) { 43 | el.classList.add('active') 44 | } 45 | }) 46 | } 47 | }) 48 | 49 | if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) { 50 | document.documentElement.classList.add('dark') 51 | } else { 52 | document.documentElement.classList.remove('dark') 53 | } 54 | 55 | E.on('click', '.js-theme-toggle', () => { 56 | if (document.documentElement.classList.contains('dark')) { 57 | document.documentElement.classList.remove('dark') 58 | localStorage.setItem('theme', 'light') 59 | } else { 60 | document.documentElement.classList.add('dark') 61 | localStorage.setItem('theme', 'dark') 62 | } 63 | }) 64 | 65 | // Mobile menu 66 | E.on('click', menuToggle, () => { 67 | const current = menuToggle.getAttribute('aria-expanded') 68 | 69 | if (current === 'false') { 70 | menuToggle.setAttribute('aria-expanded', 'true') 71 | menu.classList.remove('invisible') 72 | gsap.to(menu, { x: 0 }) 73 | } else { 74 | menuToggle.setAttribute('aria-expanded', 'false') 75 | 76 | gsap.to(menu, { x: () => -menu.clientWidth }) 77 | .then(() => { 78 | menu.classList.add('invisible') 79 | }) 80 | } 81 | }) 82 | 83 | //star count 84 | const starcount = document.querySelector('.js-stars') 85 | const version = document.querySelector('.js-version') 86 | 87 | fetch('https://api.github.com/repos/craftedbygc/taxi') 88 | .then((response) => response.json()) 89 | .then((data) => { 90 | if (!data.message) { 91 | starcount.innerHTML = convertStars(data.stargazers_count) 92 | } else { 93 | throw new Error('GitHub API hates me') 94 | } 95 | }) 96 | .catch((error) => { 97 | console.error('Error:', error); 98 | starcount.parentElement.remove() 99 | }) 100 | 101 | fetch('https://registry.npmjs.org/@unseenco/taxi/latest') 102 | .then((response) => response.json()) 103 | .then((data) => { 104 | if (data.version) { 105 | version.innerHTML = `v${data.version}` 106 | } 107 | }) 108 | .catch((error) => { 109 | console.error('Error:', error); 110 | }) 111 | }) 112 | 113 | function convertStars(amount) { 114 | return Math.abs(amount) > 999 115 | ? Math.sign(amount) * (Math.abs(amount) / 1000).toFixed(1) + "k" 116 | : Math.sign(amount) * Math.abs(amount); 117 | } 118 | -------------------------------------------------------------------------------- /docs/assets/js/renderers/DefaultRenderer.js: -------------------------------------------------------------------------------- 1 | import { Renderer } from '../../../../src/taxi' 2 | import Prism from 'prismjs'; 3 | 4 | export default class DefaultRenderer extends Renderer { 5 | onEnter() { 6 | Prism.manual = true; 7 | Prism.highlightAll() 8 | } 9 | 10 | onEnterCompleted() { 11 | } 12 | 13 | onLeave() { 14 | } 15 | 16 | onLeaveCompleted() { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/assets/js/transitions/DefaultTransition.js: -------------------------------------------------------------------------------- 1 | import { Transition } from '../../../../src/taxi' 2 | import gsap from 'gsap' 3 | 4 | export default class DefaultTransition extends Transition { 5 | onLeave({ from, trigger, done }) { 6 | const overlay = document.querySelector('.js-overlay') 7 | 8 | gsap.timeline() 9 | .to(this.wrapper, { opacity: 0 }, 0) 10 | .to(overlay, { width: '100%', ease: 'power3.in', duration: .8 }, 0) 11 | .then(() => { 12 | done() 13 | }) 14 | } 15 | 16 | onEnter({ to, trigger, done }) { 17 | const overlay = document.querySelector('.js-overlay') 18 | const tail = document.querySelector('.js-overlay-tail') 19 | 20 | gsap.timeline() 21 | .set(tail, {width: '100%'}) 22 | .to(overlay, { x: '100%', ease: 'power3.out', duration: .85 }, 0) 23 | .to(tail, { x: '100%', ease: 'power3.out', duration: .85 }, .15) 24 | .to(this.wrapper, { opacity: 1 }, .3) 25 | .then(() => { 26 | gsap.set(tail, { width: 0, x: 0 }) 27 | gsap.set(overlay, { width: 0, x: 0 }) 28 | done() 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docs/assets/sass/index.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @import 'prism'; 6 | 7 | @layer base { 8 | code:not(pre code) { 9 | @apply bg-gray-100 px-1 border rounded-sm; 10 | 11 | .dark & { 12 | @apply bg-code border-0; 13 | } 14 | } 15 | 16 | pre:not([class]) { 17 | @apply bg-code border rounded block text-gray-200 text-base; 18 | } 19 | 20 | h1, h2, h3 { 21 | @apply font-neue relative; 22 | scroll-margin-top: 6rem; 23 | } 24 | 25 | .active a { 26 | @apply translate-x-[7px]; 27 | } 28 | 29 | .code-toolbar { 30 | @apply relative; 31 | 32 | .toolbar { 33 | @apply absolute top-2 right-2 text-sm bg-slate-700 text-white opacity-0 transition-opacity py-0.5 px-1 rounded shadow; 34 | } 35 | 36 | &:hover { 37 | .toolbar { 38 | @apply opacity-100; 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /docs/assets/sass/prism.scss: -------------------------------------------------------------------------------- 1 | code[class*="language-"], 2 | pre[class*="language-"] { 3 | text-align: left; 4 | white-space: pre; 5 | word-spacing: normal; 6 | word-break: normal; 7 | word-wrap: normal; 8 | color: #c3cee3; 9 | background: #263238; 10 | font-family: Roboto Mono, monospace; 11 | font-size: .875rem; 12 | line-height: 1.5; 13 | 14 | -moz-tab-size: 4; 15 | -o-tab-size: 4; 16 | tab-size: 4; 17 | 18 | -webkit-hyphens: none; 19 | -moz-hyphens: none; 20 | -ms-hyphens: none; 21 | hyphens: none; 22 | 23 | @screen 2xl { 24 | font-size: 1rem; 25 | } 26 | } 27 | 28 | code[class*="language-"]::-moz-selection, 29 | pre[class*="language-"]::-moz-selection, 30 | code[class*="language-"] ::-moz-selection, 31 | pre[class*="language-"] ::-moz-selection { 32 | background: #363636; 33 | } 34 | 35 | code[class*="language-"]::selection, 36 | pre[class*="language-"]::selection, 37 | code[class*="language-"] ::selection, 38 | pre[class*="language-"] ::selection { 39 | background: #363636; 40 | } 41 | 42 | :not(pre) > code[class*="language-"] { 43 | white-space: normal; 44 | border-radius: 0.2em; 45 | padding: 0.1em; 46 | } 47 | 48 | pre[class*="language-"] { 49 | overflow: auto; 50 | position: relative; 51 | margin: 0.5em 0; 52 | padding: 1.25em 1em; 53 | } 54 | 55 | .language-css > code, 56 | .language-sass > code, 57 | .language-scss > code { 58 | color: #fd9170; 59 | } 60 | 61 | [class*="language-"] .namespace { 62 | opacity: 0.7; 63 | } 64 | 65 | .token.atrule { 66 | color: #c792ea; 67 | } 68 | 69 | .token.attr-name { 70 | color: #ffcb6b; 71 | } 72 | 73 | .token.attr-value { 74 | color: #c3e88d; 75 | } 76 | 77 | .token.attribute { 78 | color: #c3e88d; 79 | } 80 | 81 | .token.boolean { 82 | color: #c792ea; 83 | } 84 | 85 | .token.builtin { 86 | color: #ffcb6b; 87 | } 88 | 89 | .token.cdata { 90 | color: #80cbc4; 91 | } 92 | 93 | .token.char { 94 | color: #80cbc4; 95 | } 96 | 97 | .token.class { 98 | color: #ffcb6b; 99 | } 100 | 101 | .token.class-name { 102 | color: #F99157; 103 | } 104 | 105 | .token.color { 106 | color: #F99157; 107 | } 108 | 109 | .token.comment { 110 | color: #546e7a; 111 | } 112 | 113 | .token.constant { 114 | color: #c792ea; 115 | } 116 | 117 | .token.deleted { 118 | color: #f07178; 119 | } 120 | 121 | .token.doctype { 122 | color: #546e7a; 123 | } 124 | 125 | .token.entity { 126 | color: #f07178; 127 | } 128 | 129 | .token.function { 130 | color: #c792ea; 131 | } 132 | 133 | .token.hexcode { 134 | color: #F99157; 135 | } 136 | 137 | .token.id { 138 | color: #c792ea; 139 | font-weight: bold; 140 | } 141 | 142 | .token.important { 143 | color: #c792ea; 144 | font-weight: bold; 145 | } 146 | 147 | .token.inserted { 148 | color: #80cbc4; 149 | } 150 | 151 | .token.keyword { 152 | color: #c792ea; 153 | font-style: italic; 154 | } 155 | 156 | .token.number { 157 | color: #fd9170; 158 | } 159 | 160 | .token.operator { 161 | color: #89ddff; 162 | } 163 | 164 | .token.prolog { 165 | color: #546e7a; 166 | } 167 | 168 | .token.property { 169 | color: #80cbc4; 170 | } 171 | 172 | .token.pseudo-class { 173 | color: #c3e88d; 174 | } 175 | 176 | .token.pseudo-element { 177 | color: #c3e88d; 178 | } 179 | 180 | .token.punctuation { 181 | color: #89ddff; 182 | } 183 | 184 | .token.regex { 185 | color: #F99157; 186 | } 187 | 188 | .token.selector { 189 | color: #f07178; 190 | } 191 | 192 | .token.string { 193 | color: #c3e88d; 194 | } 195 | 196 | .token.symbol { 197 | color: #c792ea; 198 | } 199 | 200 | .token.tag { 201 | color: #f07178; 202 | } 203 | 204 | .token.unit { 205 | color: #f07178; 206 | } 207 | 208 | .token.url { 209 | color: #fd9170; 210 | } 211 | 212 | .token.variable { 213 | color: #f07178; 214 | } 215 | -------------------------------------------------------------------------------- /docs/how-to-use.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/base.njk 3 | title: How To Use 4 | --- 5 | # How to Use 6 | ## Get the package 7 | 8 | Simply include [the package](https://www.npmjs.com/package/@unseenco/taxi) through your favourite package manager: 9 | 10 | ### npm 11 | ``` 12 | npm i @unseenco/taxi 13 | ``` 14 | 15 | ### yarn 16 | ``` 17 | yarn add @unseenco/taxi 18 | ``` 19 | 20 | ### pnpm 21 | ``` 22 | pnpm add @unseenco/taxi 23 | ``` 24 | 25 | ## Setting up 26 | Next, you need to import `Taxi.Core` into your code and create a new instance: 27 | 28 | ```js 29 | import { Core } from '@unseenco/taxi' 30 | 31 | const taxi = new Core() 32 | 33 | // or if you prefer 34 | 35 | import * as Taxi from '@unseenco/taxi' 36 | 37 | const taxi = new Taxi.Core() 38 | ``` 39 | 40 | Then amend your HTML so that `data-taxi` is added to the parent of the content you want to replace during a transition, and `data-taxi-view` is added to the element you are replacing: 41 | 42 | 43 | ```html 44 |
45 |
46 | ... 47 |
48 |
49 | ``` 50 | 51 | **Please note:** The `data-taxi-view` element **has to be the only child** of `data-taxi`. 52 | 53 | 54 | Now when you navigate in your app, `data-taxi-view` will be replaced with the `data-taxi-view` from the target URL instead of the whole page loading 🥳 55 | 56 | 57 | ## Via CDN 58 | You can use Taxi via a CDN thanks to the kind folks at unpkg.com. Just be sure to note that the main export is `taxi` with a lowercase t: 59 | 60 | ```html 61 | 62 | 63 | 64 |
65 |
66 | ... 67 |
68 |
69 | 70 | 73 | ``` 74 | 75 | ## Which links are handled by Taxi? 76 | Taxi will only transition links to a domain which is the same as the current URL (for obvious reasons). 77 | 78 | By default, Taxi will not transition links which: 79 | 80 | * have `data-taxi-ignore` present on the link element; 81 | * are anchor links for the current page; 82 | * have a `target` attribute present on the link element; 83 | 84 | Of course, you can always change this behaviour using the [links option](#links-string). 85 | 86 | ## Options 87 | When creating a new Taxi instance, you can pass an object of options into the constructor: 88 | 89 | ```js 90 | const taxi = new Core({ ... }) 91 | ``` 92 | 93 | Let's look at these in more detail. 94 | 95 | ### renderers `Object.` 96 | Please see [Renderers]({{ global.url }}/renderers/) for more information. 97 | 98 | 99 | ### transitions `Object.` 100 | Please see [Transitions]({{ global.url }}/transitions/) for more information. 101 | 102 | ### links `string` 103 | Links is a CSS selector which Taxi uses to decide if a clicked link should be transitioned or not. 104 | 105 | Here is the default value: 106 | ```js 107 | const taxi = new Core({ 108 | links: 'a:not([target]):not([href^=\\#]):not([data-taxi-ignore])' 109 | }) 110 | ``` 111 | 112 | As you can see the default value ignored links with a `target` attribute, is an anchor link on the current page, or has `data-taxi-ignore` present. 113 | 114 | You can use this option to extend this behaviour and fine tune which links are considered valid. 115 | 116 | 117 | ### removeOldContent `boolean` 118 | Taxi will remove the previous page's content after the Transition's `onLeave` method has finished. Set this to `false` to disable this behaviour. 119 | 120 | ### allowInterruption `boolean` 121 | Taxi blocks further navigation while a transition is in progress. Set this to `true` to disable this behaviour. 122 | 123 | 124 | ### bypassCache `boolean` 125 | Default behaviour is to cache the contents of a URL after fetching it to make repeated visits faster. Set this to `true` to disable the cache completely. 126 | 127 | If you want default behaviour, but wish to force certain pages to always be fetched (and never loaded from cache), you can add the `data-taxi-nocache` attribute to the `data-taxi-view` element on that page. 128 | 129 | 130 | ### enablePrefetch `boolean` 131 | Default behaviour is to preload links on your website whenever the `mouseenter` or `focus` event is triggered. 132 | 133 | If you want to disable this or want to implement your own preloading strategy, set this to `false`. 134 | 135 | ### reloadJsFilter `bool|function(element: HTMLElement)` 136 | Please see [Reloading JS]({{ global.url }}/reloading-js/) for more information. 137 | 138 | ### reloadCssFilter `bool|function(element: HTMLLinkElement)` 139 | Please see [Reloading CSS]({{ global.url }}/reloading-css/) for more information. 140 | 141 | 142 |
143 |
What's next:
144 |
145 | Renderers 146 |
147 |
-------------------------------------------------------------------------------- /docs/index.njk: -------------------------------------------------------------------------------- 1 | --- 2 | title: Vanilla Javascript Page Transitions Manager 3 | --- 4 | {% extends 'layouts/base.njk' %} 5 | 6 | {% block content %} 7 |

Taxi.js

8 | 9 |

Taxi is a 🤏 small and 🐊 snappy js library for adding slick PJAX navigation and beautiful transitions to 10 | your website.

11 | 12 |

It was designed as a drop-in replacement (with enhancements) for Highway.js, which is sadly no longer maintained.

13 | 14 |

Improvements over Highway:

15 |
    16 |
  • 17 | URL-based routing 18 |
    Perfect for working with WebGL websites, you can now specify unique transitions for every possible navigation.
    19 |
  • 20 |
  • 21 | Page cache management 22 |
    Perfect for archives and search result pages, you can now preserve changes made to the DOM by user interaction.
    23 |
  • 24 |
  • 25 | Ability to preload URLs 26 |
    First-class support for adding pages to the cache ahead of schedule.
    27 |
  • 28 |
  • 29 | Blocks navigation during an active transition 30 |
    Stop users from performing state-breaking navigation by default.
    31 |
  • 32 |
  • 33 | Auto-run javascript on the new page 34 |
    If you are using a CMS or splitting your JS into page-specific modules, we have you covered.
    35 |
  • 36 |
  • 37 | Previous page's content is automatically removed (you can opt out of this if you like) 38 |
    Clean code, clean mind.
    39 |
  • 40 |
  • 41 | Click events on links can be intercepted via stopPropagation without hacks 42 |
    This finally works as you expect.
    43 |
  • 44 |
  • 45 | Handles 301 redirects gracefully 46 |
    The user is served the correct content, and the browser navigation works exactly as you would want.
    47 |
  • 48 |
49 | 50 |

Differences to Highway

51 |
    52 |
  • 53 | Different public API 54 |
    Check it out on the API & events reference.
    55 |
  • 56 |
  • 57 | Different data attributes 58 |
    data-taxi, data-taxi-view, and data-taxi-ignore are to be used instead of data-router-wrapper, data-router-view, and data-router-disabled respectively. See how to use for more.
    59 |
  • 60 |
  • 61 | attach and detach are no longer methods 62 |
    Link clicks are listened to via delegation so these are no longer needed.
    63 |
  • 64 |
  • 65 | redirect is now navigateTo 66 |
    It always felt weird as a method name, and now it doesn't.
    67 |
  • 68 |
  • 69 | Renderers now have an initialLoad method 70 |
    Nice and simple.
    71 |
  • 72 |
  • 73 | Old content is automatically removed during a transition 74 |
    You can opt of this behaviour if you like via the allowInterruption option.
    75 |
  • 76 |
77 | 78 |

Contributors

79 |

Made with ❤️ by Jake Whiteley, and feedback from the Okay Dev community.

80 | 81 |
82 |
What's next:
83 |
84 | How To Use 85 |
86 |
87 | {% endblock %} -------------------------------------------------------------------------------- /docs/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/assets/js/index.js": "/assets/js/index.js?id=9f73e36163448599799c" 3 | } 4 | -------------------------------------------------------------------------------- /docs/navigation-lifecycle.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/base.njk 3 | title: Navigation Lifecycle 4 | --- 5 | 6 | # Navigation Lifecycle 7 | Now that you understand [Renderers]({{ global.url }}/renderers) and [Transitions]({{ global.url }}/transitions/), how does it all fit together? 8 | 9 | Let's use a **real world example** to find out: 10 | 11 | 1. A user clicks a link in your app 12 | 2. Taxi will go and fetch the new page the user has requested, and attach it to the current Document so images etc start downloading 13 | 3. Taxi [checks to see which Transition]({{ global.url }}/transitions/#how-transitions-are-chosen) should be used 14 | 4. The current Renderer's `onLeave` method is called 15 | 5. Then the chosen Transition's `onLeave`.
The old page content is removed here unless you set `removeOldContent: false` when initing Taxi. 16 | 6. Then the Renderer's `onLeaveCompleted` 17 | 7. Next, Taxi will add the new page's content to the container 18 | 8. Taxi will look at the new page content and call the `onEnter` method of the Renderer set via the new page's `data-taxi-view` attribute, or the default if not defined 19 | 9. Then call the Transition's `onEnter` method 20 | 10. Finally, when the transition is all finished, the new Renderer's `onEnterComplete` is called 21 | 22 | 23 |
24 |
What's next:
25 |
26 | Reloading JS 27 |
28 |
-------------------------------------------------------------------------------- /docs/reloading-css.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/base.njk 3 | title: Reloading CSS 4 | --- 5 | 6 | # Running CSS on New Pages 7 | Similarly to [Reloading JS]({{ global.url }}/reloading-js/) Taxi can also reload and run CSS present from the next page after navigation. 8 | 9 | If enabled, this feature will run just after the `NAVIGATE_IN` event, after the new content has been appended to the DOM, but before the `Renderer.onEnter` method is called. 10 | 11 | ## Choosing which stylesheets are reloaded 12 | By default, only stylesheets with the `data-taxi-reload` attribute are reloaded after a navigation. 13 | 14 | ```html 15 | 16 | 17 | 18 | 19 | 20 | ``` 21 | 22 | If using the super cool [astro](https://astro.build/) or some other build tool which outputs per-page/component CSS, `reloadCssFilter` accepts a callback function to filter styles on the new page and decide which to load. 23 | 24 | Your callback is passed the `link` element, and must return a boolean indicating whether the stylesheet should be reloaded or not (you could check the src or id attributes for example). 25 | 26 | Here is the default callback for `reloadCssFilter`: 27 | 28 | ```js 29 | (element) => element.dataset.taxiReload !== undefined 30 | ``` 31 | 32 | and here is a custom example which loads everything: 33 | ```js 34 | import { Core } from '@unseenco/taxi' 35 | 36 | const taxi = new Core({ 37 | reloadCssFilter: (element) => true 38 | }) 39 | ``` 40 | 41 | 42 | ## Disabling this feature 43 | Just set `reloadCssFilter` to false when initing Taxi: 44 | 45 | ```js 46 | import { Core } from '@unseenco/taxi' 47 | 48 | const taxi = new Core({ 49 | reloadCssFilter: false 50 | }) 51 | ``` 52 | 53 |
54 |
What's next:
55 |
56 | API & Events 57 |
58 |
-------------------------------------------------------------------------------- /docs/reloading-js.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/base.njk 3 | title: Reloading JS 4 | --- 5 | 6 | # Running JS on New Pages 7 | Taxi can reload and run js present on a fetched page during the navigation cycle. This is especially useful when working with traditional CMSs such as WordPress or Magento, or if you wanted to split your js if you have a particularly heavy page. 8 | 9 | It will also parse and execute inline js, allowing you to add data to the `window` object for example. 10 | 11 | If enabled, this feature will run just after the `NAVIGATE_IN` event, after the new content has been appended to the DOM, but before the `Renderer.onEnter` method is called. 12 | 13 | ## Choosing which scripts are reloaded 14 | By default, only scripts with the `data-taxi-reload` attribute are reloaded after a navigation. 15 | 16 | ```html 17 | 18 |