UNPKG

react-junctions

Version:

Junction-based routing for React.

1,381 lines (1,211 loc) 124 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('react-dom')) : typeof define === 'function' && define.amd ? define(['exports', 'react', 'react-dom'], factory) : (factory((global.ReactJunctions = {}),global.React,global.ReactDOM)); }(this, (function (exports,React,ReactDOM) { 'use strict'; /** * Copyright 2014-2015, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ 'use strict'; /** * Similar to invariant but only logs a warning if the condition is not met. * This can be used to log issues in development environments in critical * paths. Removing the logging code for production environments will keep the * same logic and follow the same code paths. */ var __DEV__ = "development" !== 'production'; var warning = function() {}; if (__DEV__) { warning = function(condition, format, args) { var len = arguments.length; args = new Array(len > 2 ? len - 2 : 0); for (var key = 2; key < len; key++) { args[key - 2] = arguments[key]; } if (format === undefined) { throw new Error( '`warning(condition, format, ...args)` requires a warning ' + 'message argument' ); } if (format.length < 10 || (/^[s\W]*$/).test(format)) { throw new Error( 'The warning format should be able to uniquely identify this ' + 'warning. Please, use a more descriptive format than: ' + format ); } if (!condition) { var argIndex = 0; var message = 'Warning: ' + format.replace(/%s/g, function() { return args[argIndex++]; }); if (typeof console !== 'undefined') { console.error(message); } try { // This error was thrown as a convenience so that you can use this stack // to find the callsite that caused this warning to fire. throw new Error(message); } catch(x) {} } }; } var warning_1 = warning; /** * Copyright 2013-2015, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ 'use strict'; /** * Use invariant() to assert state which your program assumes to be true. * * Provide sprintf-style format (only %s is supported) and arguments * to provide information about what broke and what you were * expecting. * * The invariant message will be stripped in production, but the invariant * will remain to ensure logic does not differ in production. */ var NODE_ENV = "development"; var invariant = function(condition, format, a, b, c, d, e, f) { if (NODE_ENV !== 'production') { if (format === undefined) { throw new Error('invariant requires an error message argument'); } } if (!condition) { var error; if (format === undefined) { error = new Error( 'Minified exception occurred; use the non-minified dev environment ' + 'for the full error message and additional helpful warnings.' ); } else { var args = [a, b, c, d, e, f]; var argIndex = 0; error = new Error( format.replace(/%s/g, function() { return args[argIndex++]; }) ); error.name = 'Invariant Violation'; } error.framesToPop = 1; // we don't care about invariant's own frame throw error; } }; var invariant_1 = invariant; function isAbsolute(pathname) { return pathname.charAt(0) === '/'; } // About 1.5x faster than the two-arg version of Array#splice() function spliceOne(list, index) { for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) { list[i] = list[k]; } list.pop(); } // This implementation is based heavily on node's url.parse function resolvePathname(to) { var from = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; var toParts = to && to.split('/') || []; var fromParts = from && from.split('/') || []; var isToAbs = to && isAbsolute(to); var isFromAbs = from && isAbsolute(from); var mustEndAbs = isToAbs || isFromAbs; if (to && isAbsolute(to)) { // to is absolute fromParts = toParts; } else if (toParts.length) { // to is relative, drop the filename fromParts.pop(); fromParts = fromParts.concat(toParts); } if (!fromParts.length) return '/'; var hasTrailingSlash = void 0; if (fromParts.length) { var last = fromParts[fromParts.length - 1]; hasTrailingSlash = last === '.' || last === '..' || last === ''; } else { hasTrailingSlash = false; } var up = 0; for (var i = fromParts.length; i >= 0; i--) { var part = fromParts[i]; if (part === '.') { spliceOne(fromParts, i); } else if (part === '..') { spliceOne(fromParts, i); up++; } else if (up) { spliceOne(fromParts, i); up--; } } if (!mustEndAbs) for (; up--; up) { fromParts.unshift('..'); }if (mustEndAbs && fromParts[0] !== '' && (!fromParts[0] || !isAbsolute(fromParts[0]))) fromParts.unshift(''); var result = fromParts.join('/'); if (hasTrailingSlash && result.substr(-1) !== '/') result += '/'; return result; } var addLeadingSlash = function addLeadingSlash(path) { return path.charAt(0) === '/' ? path : '/' + path; }; var hasBasename = function hasBasename(path, prefix) { return new RegExp('^' + prefix + '(\\/|\\?|#|$)', 'i').test(path); }; var stripBasename = function stripBasename(path, prefix) { return hasBasename(path, prefix) ? path.substr(prefix.length) : path; }; var stripTrailingSlash = function stripTrailingSlash(path) { return path.charAt(path.length - 1) === '/' ? path.slice(0, -1) : path; }; var parsePath = function parsePath(path) { var pathname = path || '/'; var search = ''; var hash = ''; var hashIndex = pathname.indexOf('#'); if (hashIndex !== -1) { hash = pathname.substr(hashIndex); pathname = pathname.substr(0, hashIndex); } var searchIndex = pathname.indexOf('?'); if (searchIndex !== -1) { search = pathname.substr(searchIndex); pathname = pathname.substr(0, searchIndex); } return { pathname: pathname, search: search === '?' ? '' : search, hash: hash === '#' ? '' : hash }; }; var createPath = function createPath(location) { var pathname = location.pathname, search = location.search, hash = location.hash; var path = pathname || '/'; if (search && search !== '?') path += search.charAt(0) === '?' ? search : '?' + search; if (hash && hash !== '#') path += hash.charAt(0) === '#' ? hash : '#' + hash; return path; }; var _extends$1 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var createLocation = function createLocation(path, state, key, currentLocation) { var location = void 0; if (typeof path === 'string') { // Two-arg form: push(path, state) location = parsePath(path); location.state = state; } else { // One-arg form: push(location) location = _extends$1({}, path); if (location.pathname === undefined) location.pathname = ''; if (location.search) { if (location.search.charAt(0) !== '?') location.search = '?' + location.search; } else { location.search = ''; } if (location.hash) { if (location.hash.charAt(0) !== '#') location.hash = '#' + location.hash; } else { location.hash = ''; } if (state !== undefined && location.state === undefined) location.state = state; } try { location.pathname = decodeURI(location.pathname); } catch (e) { if (e instanceof URIError) { throw new URIError('Pathname "' + location.pathname + '" could not be decoded. ' + 'This is likely caused by an invalid percent-encoding.'); } else { throw e; } } if (key) location.key = key; if (currentLocation) { // Resolve incomplete/relative pathname relative to current location. if (!location.pathname) { location.pathname = currentLocation.pathname; } else if (location.pathname.charAt(0) !== '/') { location.pathname = resolvePathname(location.pathname, currentLocation.pathname); } } else { // When there is no prior location and pathname is empty, set it to / if (!location.pathname) { location.pathname = '/'; } } return location; }; var createTransitionManager = function createTransitionManager() { var prompt = null; var setPrompt = function setPrompt(nextPrompt) { warning_1(prompt == null, 'A history supports only one prompt at a time'); prompt = nextPrompt; return function () { if (prompt === nextPrompt) prompt = null; }; }; var confirmTransitionTo = function confirmTransitionTo(location, action, getUserConfirmation, callback) { // TODO: If another transition starts while we're still confirming // the previous one, we may end up in a weird state. Figure out the // best way to handle this. if (prompt != null) { var result = typeof prompt === 'function' ? prompt(location, action) : prompt; if (typeof result === 'string') { if (typeof getUserConfirmation === 'function') { getUserConfirmation(result, callback); } else { warning_1(false, 'A history needs a getUserConfirmation function in order to use a prompt message'); callback(true); } } else { // Return false from a transition hook to cancel the transition. callback(result !== false); } } else { callback(true); } }; var listeners = []; var appendListener = function appendListener(fn) { var isActive = true; var listener = function listener() { if (isActive) fn.apply(undefined, arguments); }; listeners.push(listener); return function () { isActive = false; listeners = listeners.filter(function (item) { return item !== listener; }); }; }; var notifyListeners = function notifyListeners() { for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } listeners.forEach(function (listener) { return listener.apply(undefined, args); }); }; return { setPrompt: setPrompt, confirmTransitionTo: confirmTransitionTo, appendListener: appendListener, notifyListeners: notifyListeners }; }; var canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement); var addEventListener = function addEventListener(node, event, listener) { return node.addEventListener ? node.addEventListener(event, listener, false) : node.attachEvent('on' + event, listener); }; var removeEventListener = function removeEventListener(node, event, listener) { return node.removeEventListener ? node.removeEventListener(event, listener, false) : node.detachEvent('on' + event, listener); }; var getConfirmation = function getConfirmation(message, callback) { return callback(window.confirm(message)); }; // eslint-disable-line no-alert /** * Returns true if the HTML5 history API is supported. Taken from Modernizr. * * https://github.com/Modernizr/Modernizr/blob/master/LICENSE * https://github.com/Modernizr/Modernizr/blob/master/feature-detects/history.js * changed to avoid false negatives for Windows Phones: https://github.com/reactjs/react-router/issues/586 */ var supportsHistory = function supportsHistory() { var ua = window.navigator.userAgent; if ((ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) && ua.indexOf('Mobile Safari') !== -1 && ua.indexOf('Chrome') === -1 && ua.indexOf('Windows Phone') === -1) return false; return window.history && 'pushState' in window.history; }; /** * Returns true if browser fires popstate on hash change. * IE10 and IE11 do not. */ var supportsPopStateOnHashChange = function supportsPopStateOnHashChange() { return window.navigator.userAgent.indexOf('Trident') === -1; }; /** * Returns false if using go(n) with hash history causes a full page reload. */ /** * Returns true if a given popstate event is an extraneous WebKit event. * Accounts for the fact that Chrome on iOS fires real popstate events * containing undefined state when pressing the back button. */ var isExtraneousPopstateEvent = function isExtraneousPopstateEvent(event) { return event.state === undefined && navigator.userAgent.indexOf('CriOS') === -1; }; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var PopStateEvent = 'popstate'; var HashChangeEvent = 'hashchange'; var getHistoryState = function getHistoryState() { try { return window.history.state || {}; } catch (e) { // IE 11 sometimes throws when accessing window.history.state // See https://github.com/ReactTraining/history/pull/289 return {}; } }; /** * Creates a history object that uses the HTML5 history API including * pushState, replaceState, and the popstate event. */ var createBrowserHistory = function createBrowserHistory() { var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; invariant_1(canUseDOM, 'Browser history needs a DOM'); var globalHistory = window.history; var canUseHistory = supportsHistory(); var needsHashChangeListener = !supportsPopStateOnHashChange(); var _props$forceRefresh = props.forceRefresh, forceRefresh = _props$forceRefresh === undefined ? false : _props$forceRefresh, _props$getUserConfirm = props.getUserConfirmation, getUserConfirmation = _props$getUserConfirm === undefined ? getConfirmation : _props$getUserConfirm, _props$keyLength = props.keyLength, keyLength = _props$keyLength === undefined ? 6 : _props$keyLength; var basename = props.basename ? stripTrailingSlash(addLeadingSlash(props.basename)) : ''; var getDOMLocation = function getDOMLocation(historyState) { var _ref = historyState || {}, key = _ref.key, state = _ref.state; var _window$location = window.location, pathname = _window$location.pathname, search = _window$location.search, hash = _window$location.hash; var path = pathname + search + hash; warning_1(!basename || hasBasename(path, basename), 'You are attempting to use a basename on a page whose URL path does not begin ' + 'with the basename. Expected path "' + path + '" to begin with "' + basename + '".'); if (basename) path = stripBasename(path, basename); return createLocation(path, state, key); }; var createKey = function createKey() { return Math.random().toString(36).substr(2, keyLength); }; var transitionManager = createTransitionManager(); var setState = function setState(nextState) { _extends(history, nextState); history.length = globalHistory.length; transitionManager.notifyListeners(history.location, history.action); }; var handlePopState = function handlePopState(event) { // Ignore extraneous popstate events in WebKit. if (isExtraneousPopstateEvent(event)) return; handlePop(getDOMLocation(event.state)); }; var handleHashChange = function handleHashChange() { handlePop(getDOMLocation(getHistoryState())); }; var forceNextPop = false; var handlePop = function handlePop(location) { if (forceNextPop) { forceNextPop = false; setState(); } else { var action = 'POP'; transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) { if (ok) { setState({ action: action, location: location }); } else { revertPop(location); } }); } }; var revertPop = function revertPop(fromLocation) { var toLocation = history.location; // TODO: We could probably make this more reliable by // keeping a list of keys we've seen in sessionStorage. // Instead, we just default to 0 for keys we don't know. var toIndex = allKeys.indexOf(toLocation.key); if (toIndex === -1) toIndex = 0; var fromIndex = allKeys.indexOf(fromLocation.key); if (fromIndex === -1) fromIndex = 0; var delta = toIndex - fromIndex; if (delta) { forceNextPop = true; go(delta); } }; var initialLocation = getDOMLocation(getHistoryState()); var allKeys = [initialLocation.key]; // Public interface var createHref = function createHref(location) { return basename + createPath(location); }; var push = function push(path, state) { warning_1(!((typeof path === 'undefined' ? 'undefined' : _typeof(path)) === 'object' && path.state !== undefined && state !== undefined), 'You should avoid providing a 2nd state argument to push when the 1st ' + 'argument is a location-like object that already has state; it is ignored'); var action = 'PUSH'; var location = createLocation(path, state, createKey(), history.location); transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) { if (!ok) return; var href = createHref(location); var key = location.key, state = location.state; if (canUseHistory) { globalHistory.pushState({ key: key, state: state }, null, href); if (forceRefresh) { window.location.href = href; } else { var prevIndex = allKeys.indexOf(history.location.key); var nextKeys = allKeys.slice(0, prevIndex === -1 ? 0 : prevIndex + 1); nextKeys.push(location.key); allKeys = nextKeys; setState({ action: action, location: location }); } } else { warning_1(state === undefined, 'Browser history cannot push state in browsers that do not support HTML5 history'); window.location.href = href; } }); }; var replace = function replace(path, state) { warning_1(!((typeof path === 'undefined' ? 'undefined' : _typeof(path)) === 'object' && path.state !== undefined && state !== undefined), 'You should avoid providing a 2nd state argument to replace when the 1st ' + 'argument is a location-like object that already has state; it is ignored'); var action = 'REPLACE'; var location = createLocation(path, state, createKey(), history.location); transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) { if (!ok) return; var href = createHref(location); var key = location.key, state = location.state; if (canUseHistory) { globalHistory.replaceState({ key: key, state: state }, null, href); if (forceRefresh) { window.location.replace(href); } else { var prevIndex = allKeys.indexOf(history.location.key); if (prevIndex !== -1) allKeys[prevIndex] = location.key; setState({ action: action, location: location }); } } else { warning_1(state === undefined, 'Browser history cannot replace state in browsers that do not support HTML5 history'); window.location.replace(href); } }); }; var go = function go(n) { globalHistory.go(n); }; var goBack = function goBack() { return go(-1); }; var goForward = function goForward() { return go(1); }; var listenerCount = 0; var checkDOMListeners = function checkDOMListeners(delta) { listenerCount += delta; if (listenerCount === 1) { addEventListener(window, PopStateEvent, handlePopState); if (needsHashChangeListener) addEventListener(window, HashChangeEvent, handleHashChange); } else if (listenerCount === 0) { removeEventListener(window, PopStateEvent, handlePopState); if (needsHashChangeListener) removeEventListener(window, HashChangeEvent, handleHashChange); } }; var isBlocked = false; var block = function block() { var prompt = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; var unblock = transitionManager.setPrompt(prompt); if (!isBlocked) { checkDOMListeners(1); isBlocked = true; } return function () { if (isBlocked) { isBlocked = false; checkDOMListeners(-1); } return unblock(); }; }; var listen = function listen(listener) { var unlisten = transitionManager.appendListener(listener); checkDOMListeners(1); return function () { checkDOMListeners(-1); unlisten(); }; }; var history = { length: globalHistory.length, action: 'POP', location: initialLocation, createHref: createHref, push: push, replace: replace, go: go, goBack: goBack, goForward: goForward, block: block, listen: listen }; return history; }; var __assign = (undefined && undefined.__assign) || Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; function concatLocations(firstLocation) { var locations = []; for (var _i = 1; _i < arguments.length; _i++) { locations[_i - 1] = arguments[_i]; } var result = typeof firstLocation === 'string' ? { pathname: firstLocation } : firstLocation; for (var i = 0; i < locations.length; i++) { var location_1 = locations[i]; if (typeof location_1 === 'string') { location_1 = parseLocationString(location_1); } result = __assign({}, result, { pathname: joinPaths(result.pathname, location_1.pathname), search: joinQueryStrings(result.search, location_1.search, '?'), hash: joinQueryStrings(result.hash, location_1.hash, '#'), state: result.state || location_1.state ? Object.assign({}, result.state, location_1.state) : undefined }); } return result; } var parsePattern = /^((((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/; function parseLocationString(locationString) { var matches = parsePattern.exec(locationString); if (!matches) { throw new Error("Tried to parse a non-URI object."); } return { pathname: matches[2], search: matches[6], hash: matches[7], }; } function joinPaths(a, b) { if (!b) { return a; } if (a[a.length - 1] === '/') { a = a.substr(0, a.length - 1); } if (b[0] === '/') { b = b.substr(1); } return a + '/' + b; } function joinQueryStrings(left, right, leadingCharacter) { if (leadingCharacter === void 0) { leadingCharacter = '?'; } if (!left || left[0] !== leadingCharacter) { return right; } if (!right || right[0] !== leadingCharacter) { return left; } return leadingCharacter + left.slice(1) + '&' + right.slice(1); } function createHref(location) { return location.pathname + (location.search || '') + (location.hash || ''); } function parseQuery(queryString, leadingCharacter) { if (leadingCharacter === void 0) { leadingCharacter = '?'; } if (!queryString || queryString[0] != leadingCharacter) { return {}; } var query = {}; var queryParts = queryString.slice(1).split('&'); for (var i = 0, len = queryParts.length; i < len; i++) { var x = queryParts[i].split('='); query[x[0]] = x[1] ? decodeURIComponent(x[1]) : ''; } return query; } function stringifyQuery(query, leadingCharacter) { if (leadingCharacter === void 0) { leadingCharacter = '?'; } var keys = Object.keys(query); if (keys.length === 0) { return ''; } var parts = []; for (var i = 0, len = keys.length; i < len; i++) { var key = keys[i]; var value = String(query[key]); parts.push(value === '' ? key : key + '=' + encodeURIComponent(value)); } return leadingCharacter + parts.join('&'); } var __assign$1 = (undefined && undefined.__assign) || Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; var KEY_WILDCARD = '\0'; function createRootMountedPattern(template, relativePath) { var rootPattern = relativePath ? compilePattern(relativePath, template) : { relativePattern: relativePath || '', relativeKey: '', relativeRegExp: new RegExp(''), template: template }; if (rootPattern.relativePathParams && rootPattern.relativePathParams.length > 0) { throw new Error("Your root path may not contain parameters"); } return __assign$1({}, rootPattern, { params: [] }); } function compilePattern(pattern, template) { var processedPattern = pattern; if (processedPattern.length > 1 && processedPattern.substr(-1) === '/') { { console.warn("The pattern \"" + pattern + "\" ends with the character '/', so it has been automatically removed. To avoid this warning, don't add a final \"/\" to Junction patterns."); } processedPattern = processedPattern.substr(0, processedPattern.length - 1); } if (processedPattern[0] !== '/') { { console.warn("The pattern \"" + pattern + "\" does not start with the character '/', so it has been automatically added. To avoid this warning, make sure to add the leading \"/\" to all Junction patterns."); } processedPattern = '/' + processedPattern; } if (/\/{2,}/.test(processedPattern)) { { console.warn("The pattern \"" + pattern + " has adjacent '/' characters, which have been combined into single '/' characters. To avoid this warning, don't use adjacent '/' characters within patterns."); } processedPattern = processedPattern.replace(/\/{2,}/g, '/'); } if (!/^([A-Za-z0-9\$\-_\.+!*'\(\),\/]|\/:)+$/.test(processedPattern)) { { console.warn("The pattern \"" + pattern + "\" uses non-URL safe characters. The URL-safe characters are: A-Z a-z 0-9 $ - _ . + ! * ' ( ) ,"); } } if (processedPattern.length === 0) { throw new Error("You cannot use an empty string \"\" as a Junction pattern!"); } var parts = processedPattern.split('/').slice(1); var pathParams = []; var keyParts = []; var regExpParts = ['^']; for (var i = 0; i < parts.length; i++) { var part = parts[i]; if (part.length > 1 && part[0] === ':') { pathParams.push(part.slice(1)); keyParts.push(KEY_WILDCARD); regExpParts.push('([^/]+)'); } else { keyParts.push(part); regExpParts.push(escapeRegExp(part)); } } return { relativePattern: processedPattern, relativeKey: keyParts.join('/'), relativePathParams: pathParams.length ? pathParams : undefined, relativeRegExp: new RegExp(regExpParts.join('/')), template: template, }; } function createChildMountedPattern(parentPattern, compiledPattern) { { if (compiledPattern.relativePathParams) { var doubleParams = compiledPattern.relativePathParams.filter(function (param) { return parentPattern.params.indexOf(param) !== -1; }); if (doubleParams.length) { console.error("The pattern \"" + compiledPattern.relativePattern + "\" uses the param names " + doubleParams.map(function (x) { return "\"" + x + "\""; }).join(', ') + ", which have already been used by a parent junction."); } } } return __assign$1({ params: parentPattern.params.concat(compiledPattern.relativePathParams || []) }, compiledPattern); } function addParamsToMountedPattern(pattern, params) { var relativeSearchParams = params || []; // Ensure that any params in the mount's path are also specified by the // mounted junction's "params" config. if (pattern.relativePathParams) { for (var i = pattern.relativePathParams.length - 1; i >= 0; i--) { var pathParam = pattern.relativePathParams[i]; var index = relativeSearchParams.indexOf(pathParam); if (index === -1) { { console.warn("The path parameter \":" + pathParam + "\" was not specified in its associated junctions' \"params\" configuration option. To avoid this warning, always specify your junctions' \"params\" object."); } } else { relativeSearchParams.splice(index, 1); } } } // If there are no search params, the mount won't chang if (relativeSearchParams.length === 0) { return pattern; } // Ensure that none of our search param names are already used by parent // junctions. { var doubleParams = relativeSearchParams.filter(function (param) { return pattern.params.indexOf(param) !== -1; }); if (doubleParams.length) { console.error("The junction mounted at \"" + pattern.relativePattern + "\" uses the param names " + doubleParams.map(function (x) { return "\"" + x + "\""; }).join(', ') + ", which have already been used by a parent junction."); } } return __assign$1({}, pattern, { relativeSearchParams: relativeSearchParams.length ? relativeSearchParams : undefined }); } function matchMountedPatternAgainstLocation(pattern, location) { var match = pattern.relativeRegExp.exec(location.pathname); var params = {}; if (!match) { return; } // Set path params using RegExp match if (pattern.relativePathParams) { for (var i = 0; i < pattern.relativePathParams.length; i++) { var paramName = pattern.relativePathParams[i]; params[paramName] = match[i + 1]; } } var matchedQueryParts = {}; var remainingQueryParts = parseQuery(location.search); if (pattern.relativeSearchParams) { for (var i = 0; i < pattern.relativeSearchParams.length; i++) { var paramName = pattern.relativeSearchParams[i]; if (remainingQueryParts[paramName] !== undefined) { params[paramName] = remainingQueryParts[paramName]; matchedQueryParts[paramName] = remainingQueryParts[paramName]; delete remainingQueryParts[paramName]; } } } var matchedLocation = { pathname: match[0], search: stringifyQuery(matchedQueryParts), }; var remainingLocation = { pathname: location.pathname.slice(match[0].length), search: stringifyQuery(remainingQueryParts), hash: location.hash, state: location.state, }; return { params: params, matchedLocation: matchedLocation, remainingLocation: remainingLocation.pathname !== '' || remainingLocation.search !== '' ? remainingLocation : undefined }; } // From http://stackoverflow.com/a/5306111/106302 // Originally from http://simonwillison.net/2006/Jan/20/escape/ (dead link) function escapeRegExp(value) { return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); } var Router = /** @class */ (function () { function Router(config) { var _this = this; this.listeners = []; this.notifyListeners = function () { for (var i = 0; i < _this.listeners.length; i++) { _this.listeners[i](); } }; this.listeners = []; this.config = config; } Router.prototype.getRoute = function () { return this.rootMatcher && this.rootMatcher.getRoute(); }; /** * If you're using code splitting, you'll need to subscribe to changes to * Navigation state, as the state may change as new code chunks are * received. */ Router.prototype.subscribe = function (onRouteChange) { var _this = this; this.listeners.push(onRouteChange); return function () { var index = _this.listeners.indexOf(onRouteChange); if (index !== -1) { _this.listeners.splice(index, 1); } }; }; Router.prototype.isBusy = function () { if (this.rootMatcher) { return this.rootMatcher.isBusy(); } return false; }; Router.prototype.setLocation = function (location) { var locationExistenceHasChanged = (location && !this.location) || (!location && this.location); var pathHasChanged, searchHasChanged; if (location && this.location) { pathHasChanged = location.pathname !== this.location.pathname; searchHasChanged = location.search !== this.location.search; } // The router only looks at path and search, so if they haven't // changed, nothing else will change either. if (!(pathHasChanged || searchHasChanged || locationExistenceHasChanged)) { this.notifyListeners(); return; } this.location = location; var match = location && matchMountedPatternAgainstLocation(this.config.rootMountedPattern, location); if (!match && this.rootMatcher) { this.rootMatcher.willUnmount(); this.rootMatcher = undefined; this.notifyListeners(); return; } else if (location && match) { if (this.rootMatcher) { this.rootMatcher.willUnmount(); } this.rootMatcher = new this.config.rootJunctionTemplate({ routerConfig: this.config, parentLocationPart: { pathname: '' }, matchableLocationPart: location, mountedPattern: this.config.rootMountedPattern, onChange: this.notifyListeners, shouldFetchContent: true, }); this.notifyListeners(); } }; return Router; }()); function createRouterConfig(options) { return { rootJunctionTemplate: options.rootJunctionTemplate, rootMountedPattern: createRootMountedPattern(options.rootJunctionTemplate, options.rootPath), onEvent: options.onEvent || (function () { }) }; } var Deferred = /** @class */ (function () { function Deferred() { this.promise = new Promise(function (resolve, reject) { this.resolve = resolve; this.reject = reject; }.bind(this)); Object.freeze(this); } return Deferred; }()); var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (undefined && undefined.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [0, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var defaultPredicate = function (segment) { return true; }; function createContentHelpers(routerConfig) { function getPages(pathnames) { if (typeof pathnames === 'string') { return getPageNode({ pathname: pathnames }).then(function (page) { return page ? Promise.resolve(page) : Promise.reject(undefined); }); } else { var keys_1 = Object.keys(pathnames); var promises = []; for (var i = 0; i < keys_1.length; i++) { var key = keys_1[i]; promises.push(getPageNode({ pathname: pathnames[key] })); } return Promise.all(promises).then(function (values) { var result = {}; for (var i = 0; i < keys_1.length; i++) { var value = values[i]; if (value === undefined) { return Promise.reject(undefined); } result[keys_1[i]] = value; } return Promise.resolve(result); }); } } /** * Return a promise to a RouteSemgent that corresponds to the given * pathname, or reject the promise if a corresponding segment can't be * found. */ function getRouteSegment(pathname) { var deferred = new Deferred(); var searchLocation = { // Add a trailing slash, in case the pathname corresponds directly // to a junction with no '/' child -- otherwise no match will be // found. pathname: pathname.substr(-1) === '/' ? pathname : pathname + '/' }; processFinalNodeWithoutContent(searchLocation, function (route) { if (route) { var lastSegment = route[route.length - 1]; if (lastSegment.location.pathname === pathname) { deferred.resolve(lastSegment); } else if (lastSegment.location.pathname === pathname + '/') { // If the user requested a segment without a trailing // slash, but we found one *with* a trailing slash, then // we've found a child of a requested junction. var lastJunction = route[route.length - 2]; if (lastJunction) { deferred.resolve(lastJunction); } } } deferred.reject(undefined); }); return deferred.promise; } function getPageMap(junction, predicate) { if (predicate === void 0) { predicate = defaultPredicate; } return __awaiter(this, void 0, void 0, function () { var map, template, children, possiblePromises, queue, junction_1, _i, children_1, _a, pattern, template_1, path, segment; return __generator(this, function (_b) { switch (_b.label) { case 0: map = {}; template = junction.template; children = Object.entries(template.children); queue = [junction]; _b.label = 1; case 1: if (!queue.length) return [3 /*break*/, 6]; junction_1 = queue.shift(); _i = 0, children_1 = children; _b.label = 2; case 2: if (!(_i < children_1.length)) return [3 /*break*/, 5]; _a = children_1[_i], pattern = _a[0], template_1 = _a[1]; path = junction_1.location.pathname + pattern; return [4 /*yield*/, getRouteSegment(path)]; case 3: segment = _b.sent(); if (segment.type !== 'redirect' && predicate(segment)) { if (segment.type === 'junction') { queue.push(junction_1); } else { map[path] = segment; } } _b.label = 4; case 4: _i++; return [3 /*break*/, 2]; case 5: return [3 /*break*/, 1]; case 6: return [2 /*return*/, map]; } }); }); } function getPageNode(location) { var deferred = new Deferred(); processFinalNodeWithoutContent(location, function (route) { var lastSegment = route && route[route.length - 1]; if (!lastSegment || lastSegment.type === 'junction') { deferred.resolve(undefined); } else if (lastSegment.type === 'page') { return deferred.resolve(lastSegment); } else { getPageNode(lastSegment.to).then(deferred.resolve); } }); return deferred.promise; } function processFinalNodeWithoutContent(location, callback) { var match = matchMountedPatternAgainstLocation(routerConfig.rootMountedPattern, location); if (!match) { callback(); } else { var deferred = new Deferred(); var handleRouteChange = function () { if (!rootMatcher_1.isBusy()) { callback(rootMatcher_1.getRoute()); rootMatcher_1.willUnmount(); } }; var rootMatcher_1 = new routerConfig.rootJunctionTemplate({ parentLocationPart: { pathname: '' }, matchableLocationPart: location, mountedPattern: routerConfig.rootMountedPattern, onChange: handleRouteChange, routerConfig: routerConfig, shouldFetchContent: false, }); handleRouteChange(); } } return { getPages: getPages, getRouteSegment: getRouteSegment, getPageMap: getPageMap, }; } var BrowserNavigation = /** @class */ (function () { function BrowserNavigation(options) { var _this = this; this.handleHistoryChange = function (location) { if (_this.location && !!_this.disableScrollHandling && location.pathname === _this.location.pathname && location.hash === _this.location.hash && location.search === _this.location.search) { _this.scrollToHash(location.hash); } else { _this.router.setLocation(location); } }; this.handleLocationChange = function (previousLocation, nextLocation) { if (previousLocation === nextLocation) { return; } if (!_this.disableScrollHandling && (!previousLocation || previousLocation.hash !== nextLocation.hash || (!nextLocation.hash && previousLocation.pathname !== nextLocation.pathname))) { _this.location = nextLocation; if (previousLocation) { _this.scrollToHash(nextLocation.hash); } } }; this.handleRouteChange = function () { var isBusy = _this.router.isBusy(); var route = _this.router.getRoute(); var lastSegment = route && route[route.length - 1]; var redirectTo; var title; if (!isBusy && lastSegment) { if (lastSegment.type === "page" && _this.history.location.pathname.substr(-1) !== '/') { redirectTo = concatLocations(_this.history.location, { pathname: '/' }); } title = null; if (lastSegment.type === "redirect") { redirectTo = lastSegment.to; } else if (lastSegment.type === "page") { if (lastSegment.contentStatus !== "busy") { _this.waitingForInitialContent = false; } title = lastSegment.title; } else if (lastSegment.status !== "busy") { _this.waitingForInitialContent = false; } } if (_this.followRedirects && redirectTo) { _this.replaceLocation(redirectTo); return; } if (title !== undefined) { if (_this.announceTitle) { announce(_this.announceTitle(title)); } if (_this.setDocumentTitle) { document.title = _this.setDocumentTitle(title); } } // Wait until all subscribers have finished handling the changes // before emitting `handleLocationChange`. var waitCount = 0; var decreaseWaitCount = function () { if (--waitCount <= 0) { if (!isBusy) { _this.handleLocationChange(_this.location, _this.history.location); } } }; for (var _i = 0, _a = _this.subscribers; _i < _a.length; _i++) { var subscriber = _a[_i]; if (!isBusy || !subscriber.waitForInitialContent || !_this.waitingForInitialContent) { if (subscriber.callback.length > 0) { waitCount++; subscriber.callback(decreaseWaitCount); } else { subscriber.callback(); } } } if (waitCount === 0) { decreaseWaitCount(); } }; if (options.announceTitle !== false) { this.announceTitle = typeof options.announceTitle === 'function' ? options.announceTitle : (function (x) { return x || 'Untitled Page'; }); // Add an announcer div to the DOM, if we haven't already created one createAnnouncerDiv(); } if (options.setDocumentTitle !== false) { this.setDocumentTitle = typeof options.setDocumentTitle === 'function' ? options.setDocumentTitle : (function (x) { return x || 'Untitled Page'; }); } this.autoscroll = options.autoscroll !== undefined ? options.autoscroll : true; this.followRedirects = options.followRedirects !== undefined ? options.followRedirects : true; this.subscribers = []; this.waitingForInitialContent = true; this.disableScrollHandling = !!options.disableScrollHandling; this.history = options.history || createBrowserHistory(); this.history.listen(this.handleHistoryChange); var routerConfig = createRouterConfig({ rootJunctionTemplate: options.rootJunctionTemplate }); this.router = new Router(routerConfig); this.router.subscribe(this.handleRouteChange); Object.assign(this, createContentHelpers(routerConfig)); this.router.setLocation(this.history.location); } /** * Subscribe to new states from the Navigation object * @callback onChange - called when state changes * @argument waitForInitialContent - if try, will not be called until the initial location's content has loaded */ BrowserNavigation.prototype.subscribe = function (onChange, options) { var _this = this; if (options === void 0) { options = {}; } var subscriber = { callback: onChange, waitForInitialContent: !!options.waitForInitialContent, }; this.subscribers.push(subscriber); return function () { var index = _this.subscribers.indexOf(subscriber);