UNPKG

router-primitives

Version:

A cross-platform application router. Declarative routing by way of layout primitives

1,411 lines (1,277 loc) 89.3 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function() { __assign = Object.assign || function __assign(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; }; return __assign.apply(this, arguments); }; function __spreadArrays() { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; } var show = function (options, oldLocation, router, ctx) { var location = router.siblings.reduce(function (acc, s) { return s.hide(__assign(__assign({}, options), { disableCaching: true }), acc, s, ctx); }, __assign({}, oldLocation)); if (router.isPathRouter) { location.pathname[router.pathLocation] = router.routeKey; location.pathname = location.pathname.slice(0, router.pathLocation + 1); } else { location.search[router.routeKey] = true; } return location; }; var hide = function (_options, oldLocation, router, _ctx) { var location = __assign({}, oldLocation); if (router.isPathRouter) { location.pathname = location.pathname.slice(0, router.pathLocation); } else { location.search[router.routeKey] = undefined; } return location; }; var reducer = function (location, router, _ctx) { var newState = {}; if (router.isPathRouter) { newState['visible'] = location.pathname[router.pathLocation] === router.routeKey; } else { newState['visible'] = location.search[router.routeKey] === 'true'; } return newState; }; var template = { actions: { show: show, hide: hide }, reducer: reducer, config: { canBePathRouter: true, isPathRouter: true, shouldInverselyActivate: true, shouldParentTryToActivateSiblings: false } }; var getRouteKeyOrderings = function (router, location) { var routeKeyOrderObj = router.parent.children[router.type].reduce(function (acc, r) { if (r.state.visible === false || location.search[r.routeKey] === undefined) { return acc; } acc[r.routeKey] = r.state.data; return acc; }, {}); var routerRouteKeys = Object.keys(routeKeyOrderObj); var orderAsKey = routerRouteKeys.reduce(function (acc, key) { var value = routeKeyOrderObj[key]; if (value != null && !Number.isNaN(value)) { acc[routeKeyOrderObj[key]] = key; } return acc; }, {}); var orders = Object.values(routeKeyOrderObj); var filteredOrders = orders.filter(function (n) { return n != null && !Number.isNaN(n); }); var sortedOrders = filteredOrders.sort(function (a, b) { return a - b; }); var sortedKeys = sortedOrders.map(function (order) { return orderAsKey[order]; }); return sortedKeys; }; var show$1 = function (_options, location, router, _ctx) { if (!router.parent) { return location; } var sortedKeys = getRouteKeyOrderings(router, location); var index = sortedKeys.indexOf(router.routeKey); if (index > -1) { sortedKeys.splice(index, 1); } sortedKeys.unshift(router.routeKey); var search = sortedKeys.reduce(function (acc, key, i) { acc[key] = i + 1; return acc; }, {}); location.search = __assign(__assign({}, location.search), search); return location; }; var hide$1 = function (_options, location, router, _ctx) { if (!router.parent) { return location; } var sortedKeys = getRouteKeyOrderings(router, location); var index = sortedKeys.indexOf(router.routeKey); if (index > -1) { sortedKeys.splice(index, 1); } var search = sortedKeys.reduce(function (acc, key, i) { acc[key] = i + 1; return acc; }, {}); var newLocation = __assign({}, location); newLocation.search = __assign(__assign({}, location.search), search); newLocation.search[router.routeKey] = undefined; return newLocation; }; var forward = function (_options, location, router, _ctx) { if (!router.parent) { return location; } var sortedKeys = getRouteKeyOrderings(router, location); var index = sortedKeys.indexOf(router.routeKey); if (index > -1) { sortedKeys.splice(index, 1); } var newIndex = index >= 1 ? index - 1 : 0; sortedKeys.splice(newIndex, 0, router.routeKey); var search = sortedKeys.reduce(function (acc, key, i) { acc[key] = i + 1; return acc; }, {}); location.search = __assign(__assign({}, location.search), search); return location; }; var backward = function (_options, location, router, _ctx) { if (!router.parent) { return location; } var sortedKeys = getRouteKeyOrderings(router, location); var index = sortedKeys.indexOf(router.routeKey); if (index > -1) { sortedKeys.splice(index, 1); } var newIndex = index + 1; sortedKeys.splice(newIndex, 0, router.routeKey); var search = sortedKeys.reduce(function (acc, key, i) { acc[key] = i + 1; return acc; }, {}); location.search = __assign(__assign({}, location.search), search); return location; }; var toFront = function (options, location, router, ctx) { return router.show(options, location, router, ctx); }; var toBack = function (_options, location, router, _ctx) { if (!router.parent) { return location; } var sortedKeys = getRouteKeyOrderings(router, location); var index = sortedKeys.indexOf(router.routeKey); if (index > -1) { sortedKeys.splice(index, 1); } sortedKeys.push(router.routeKey); var search = sortedKeys.reduce(function (acc, key, i) { acc[key] = i + 1; return acc; }, {}); location.search = __assign(__assign({}, location.search), search); return location; }; var reducer$1 = function (location, router, _ctx) { var value = location.search[router.routeKey]; if (value) { return { visible: true, data: +value }; } return { visible: false, data: undefined }; }; var template$1 = { actions: { show: show$1, hide: hide$1, forward: forward, backward: backward, toFront: toFront, toBack: toBack }, reducer: reducer$1, config: { canBePathRouter: false } }; var show$2 = function (_options, oldLocation, router, ctx) { var location = __assign({}, oldLocation); var data = (ctx && ctx.pathData && ctx.pathData[router.name] ? ctx.pathData[router.name] : router.data); if (!data) { throw new Error("Can't show data router " + router.name + " b/c the data field is not set"); } if (router.isPathRouter) { location.pathname[router.pathLocation] = data; location.pathname = location.pathname.slice(0, router.pathLocation + 1); } else { location.search[router.routeKey] = data; } return location; }; var hide$2 = function (_options, oldLocation, router, _ctx) { var location = __assign({}, oldLocation); if (router.isPathRouter) { location.pathname = location.pathname.slice(0, router.pathLocation); } else { location.search[router.routeKey] = undefined; } return location; }; var setData = function (options, location, router, ctx) { return router.show(options, location, router, ctx); }; var reducer$2 = function (location, router, _ctx) { var routerData; if (router.isPathRouter) { routerData = location.pathname[router.pathLocation]; } else { routerData = location.search[router.routeKey]; } return { visible: !!routerData, data: routerData || router.state.data }; }; var template$2 = { actions: { show: show$2, hide: hide$2, setData: setData }, reducer: reducer$2, config: { canBePathRouter: true, isPathRouter: false, isDependentOnExternalData: true } }; var show$3 = function (_options, oldLocation, router, _ctx) { var location = __assign({}, oldLocation); location.search[router.routeKey] = true; return location; }; var hide$3 = function (_options, oldLocation, router, _ctx) { var location = __assign({}, oldLocation); location.search[router.routeKey] = undefined; return location; }; var reducer$3 = function (location, router, _ctx) { var newState = {}; newState['visible'] = location.search[router.routeKey] === 'true'; return newState; }; var template$3 = { actions: { show: show$3, hide: hide$3 }, reducer: reducer$3, config: { canBePathRouter: false, shouldInverselyActivate: true } }; var show$4 = function (_options, location, _router, _ctx) { return location; }; var hide$4 = function (_options, location, _router, _ctx) { return location; }; var reducer$4 = function (location, _router, _ctx) { var hasSearchRouters = Object.keys(location.search).length > 0; var hasPathRouters = location.pathname.length > 0; return { visible: hasSearchRouters || hasPathRouters }; }; var template$4 = { actions: { show: show$4, hide: hide$4 }, reducer: reducer$4, config: { canBePathRouter: true, isPathRouter: true } }; var defaultRouterTemplates = { scene: template, stack: template$1, data: template$2, feature: template$3, root: template$4 }; function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var strictUriEncode = str => encodeURIComponent(str).replace(/[!'()*]/g, x => `%${x.charCodeAt(0).toString(16).toUpperCase()}`); var token = '%[a-f0-9]{2}'; var singleMatcher = new RegExp(token, 'gi'); var multiMatcher = new RegExp('(' + token + ')+', 'gi'); function decodeComponents(components, split) { try { // Try to decode the entire string first return decodeURIComponent(components.join('')); } catch (err) { // Do nothing } if (components.length === 1) { return components; } split = split || 1; // Split the array in 2 parts var left = components.slice(0, split); var right = components.slice(split); return Array.prototype.concat.call([], decodeComponents(left), decodeComponents(right)); } function decode(input) { try { return decodeURIComponent(input); } catch (err) { var tokens = input.match(singleMatcher); for (var i = 1; i < tokens.length; i++) { input = decodeComponents(tokens, i).join(''); tokens = input.match(singleMatcher); } return input; } } function customDecodeURIComponent(input) { // Keep track of all the replacements and prefill the map with the `BOM` var replaceMap = { '%FE%FF': '\uFFFD\uFFFD', '%FF%FE': '\uFFFD\uFFFD' }; var match = multiMatcher.exec(input); while (match) { try { // Decode as big chunks as possible replaceMap[match[0]] = decodeURIComponent(match[0]); } catch (err) { var result = decode(match[0]); if (result !== match[0]) { replaceMap[match[0]] = result; } } match = multiMatcher.exec(input); } // Add `%C2` at the end of the map to make sure it does not replace the combinator before everything else replaceMap['%C2'] = '\uFFFD'; var entries = Object.keys(replaceMap); for (var i = 0; i < entries.length; i++) { // Replace all decoded components var key = entries[i]; input = input.replace(new RegExp(key, 'g'), replaceMap[key]); } return input; } var decodeUriComponent = function (encodedURI) { if (typeof encodedURI !== 'string') { throw new TypeError('Expected `encodedURI` to be of type `string`, got `' + typeof encodedURI + '`'); } try { encodedURI = encodedURI.replace(/\+/g, ' '); // Try the built in decoder first return decodeURIComponent(encodedURI); } catch (err) { // Fallback to a more advanced decoder return customDecodeURIComponent(encodedURI); } }; var splitOnFirst = (string, separator) => { if (!(typeof string === 'string' && typeof separator === 'string')) { throw new TypeError('Expected the arguments to be of type `string`'); } if (separator === '') { return [string]; } const separatorIndex = string.indexOf(separator); if (separatorIndex === -1) { return [string]; } return [ string.slice(0, separatorIndex), string.slice(separatorIndex + separator.length) ]; }; var queryString = createCommonjsModule(function (module, exports) { const isNullOrUndefined = value => value === null || value === undefined; function encoderForArrayFormat(options) { switch (options.arrayFormat) { case 'index': return key => (result, value) => { const index = result.length; if ( value === undefined || (options.skipNull && value === null) || (options.skipEmptyString && value === '') ) { return result; } if (value === null) { return [...result, [encode(key, options), '[', index, ']'].join('')]; } return [ ...result, [encode(key, options), '[', encode(index, options), ']=', encode(value, options)].join('') ]; }; case 'bracket': return key => (result, value) => { if ( value === undefined || (options.skipNull && value === null) || (options.skipEmptyString && value === '') ) { return result; } if (value === null) { return [...result, [encode(key, options), '[]'].join('')]; } return [...result, [encode(key, options), '[]=', encode(value, options)].join('')]; }; case 'comma': case 'separator': return key => (result, value) => { if (value === null || value === undefined || value.length === 0) { return result; } if (result.length === 0) { return [[encode(key, options), '=', encode(value, options)].join('')]; } return [[result, encode(value, options)].join(options.arrayFormatSeparator)]; }; default: return key => (result, value) => { if ( value === undefined || (options.skipNull && value === null) || (options.skipEmptyString && value === '') ) { return result; } if (value === null) { return [...result, encode(key, options)]; } return [...result, [encode(key, options), '=', encode(value, options)].join('')]; }; } } function parserForArrayFormat(options) { let result; switch (options.arrayFormat) { case 'index': return (key, value, accumulator) => { result = /\[(\d*)\]$/.exec(key); key = key.replace(/\[\d*\]$/, ''); if (!result) { accumulator[key] = value; return; } if (accumulator[key] === undefined) { accumulator[key] = {}; } accumulator[key][result[1]] = value; }; case 'bracket': return (key, value, accumulator) => { result = /(\[\])$/.exec(key); key = key.replace(/\[\]$/, ''); if (!result) { accumulator[key] = value; return; } if (accumulator[key] === undefined) { accumulator[key] = [value]; return; } accumulator[key] = [].concat(accumulator[key], value); }; case 'comma': case 'separator': return (key, value, accumulator) => { const isArray = typeof value === 'string' && value.split('').indexOf(options.arrayFormatSeparator) > -1; const newValue = isArray ? value.split(options.arrayFormatSeparator).map(item => decode(item, options)) : value === null ? value : decode(value, options); accumulator[key] = newValue; }; default: return (key, value, accumulator) => { if (accumulator[key] === undefined) { accumulator[key] = value; return; } accumulator[key] = [].concat(accumulator[key], value); }; } } function validateArrayFormatSeparator(value) { if (typeof value !== 'string' || value.length !== 1) { throw new TypeError('arrayFormatSeparator must be single character string'); } } function encode(value, options) { if (options.encode) { return options.strict ? strictUriEncode(value) : encodeURIComponent(value); } return value; } function decode(value, options) { if (options.decode) { return decodeUriComponent(value); } return value; } function keysSorter(input) { if (Array.isArray(input)) { return input.sort(); } if (typeof input === 'object') { return keysSorter(Object.keys(input)) .sort((a, b) => Number(a) - Number(b)) .map(key => input[key]); } return input; } function removeHash(input) { const hashStart = input.indexOf('#'); if (hashStart !== -1) { input = input.slice(0, hashStart); } return input; } function getHash(url) { let hash = ''; const hashStart = url.indexOf('#'); if (hashStart !== -1) { hash = url.slice(hashStart); } return hash; } function extract(input) { input = removeHash(input); const queryStart = input.indexOf('?'); if (queryStart === -1) { return ''; } return input.slice(queryStart + 1); } function parseValue(value, options) { if (options.parseNumbers && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) { value = Number(value); } else if (options.parseBooleans && value !== null && (value.toLowerCase() === 'true' || value.toLowerCase() === 'false')) { value = value.toLowerCase() === 'true'; } return value; } function parse(input, options) { options = Object.assign({ decode: true, sort: true, arrayFormat: 'none', arrayFormatSeparator: ',', parseNumbers: false, parseBooleans: false }, options); validateArrayFormatSeparator(options.arrayFormatSeparator); const formatter = parserForArrayFormat(options); // Create an object with no prototype const ret = Object.create(null); if (typeof input !== 'string') { return ret; } input = input.trim().replace(/^[?#&]/, ''); if (!input) { return ret; } for (const param of input.split('&')) { let [key, value] = splitOnFirst(options.decode ? param.replace(/\+/g, ' ') : param, '='); // Missing `=` should be `null`: // http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters value = value === undefined ? null : ['comma', 'separator'].includes(options.arrayFormat) ? value : decode(value, options); formatter(decode(key, options), value, ret); } for (const key of Object.keys(ret)) { const value = ret[key]; if (typeof value === 'object' && value !== null) { for (const k of Object.keys(value)) { value[k] = parseValue(value[k], options); } } else { ret[key] = parseValue(value, options); } } if (options.sort === false) { return ret; } return (options.sort === true ? Object.keys(ret).sort() : Object.keys(ret).sort(options.sort)).reduce((result, key) => { const value = ret[key]; if (Boolean(value) && typeof value === 'object' && !Array.isArray(value)) { // Sort object keys, not values result[key] = keysSorter(value); } else { result[key] = value; } return result; }, Object.create(null)); } exports.extract = extract; exports.parse = parse; exports.stringify = (object, options) => { if (!object) { return ''; } options = Object.assign({ encode: true, strict: true, arrayFormat: 'none', arrayFormatSeparator: ',' }, options); validateArrayFormatSeparator(options.arrayFormatSeparator); const shouldFilter = key => ( (options.skipNull && isNullOrUndefined(object[key])) || (options.skipEmptyString && object[key] === '') ); const formatter = encoderForArrayFormat(options); const objectCopy = {}; for (const key of Object.keys(object)) { if (!shouldFilter(key)) { objectCopy[key] = object[key]; } } const keys = Object.keys(objectCopy); if (options.sort !== false) { keys.sort(options.sort); } return keys.map(key => { const value = object[key]; if (value === undefined) { return ''; } if (value === null) { return encode(key, options); } if (Array.isArray(value)) { return value .reduce(formatter(key), []) .join('&'); } return encode(key, options) + '=' + encode(value, options); }).filter(x => x.length > 0).join('&'); }; exports.parseUrl = (input, options) => { return { url: removeHash(input).split('?')[0] || '', query: parse(extract(input), options) }; }; exports.stringifyUrl = (input, options) => { const url = removeHash(input.url).split('?')[0] || ''; const queryFromUrl = exports.extract(input.url); const parsedQueryFromUrl = exports.parse(queryFromUrl); const hash = getHash(input.url); const query = Object.assign(parsedQueryFromUrl, input.query); let queryString = exports.stringify(query, options); if (queryString) { queryString = `?${queryString}`; } return `${url}${queryString}${hash}`; }; }); var queryString_1 = queryString.extract; var queryString_2 = queryString.parse; var queryString_3 = queryString.stringify; var queryString_4 = queryString.parseUrl; var queryString_5 = queryString.stringifyUrl; var deserializer = function (serializedLocation) { if (serializedLocation === void 0) { serializedLocation = ''; } var locationStringParts = serializedLocation.split('?'); var search = queryString_2(locationStringParts[1], { decode: true, arrayFormat: 'bracket' }); var pathname = locationStringParts[0].split('/').filter(function (s) { return s !== ''; }); return { search: search, pathname: pathname }; }; var DEFAULT_LOCATION = { pathname: [], search: {}, options: {} }; var serializer = function (newLocation, oldLocation) { if (oldLocation === void 0) { oldLocation = DEFAULT_LOCATION; } var newPathname = newLocation.pathname || []; var newSearchObj = newLocation.search || {}; var oldSearchObj = oldLocation.search || {}; var combinedSearchObj = __assign(__assign({}, oldSearchObj), newSearchObj); Object.keys(combinedSearchObj).forEach(function (key) { return combinedSearchObj[key] == null && delete combinedSearchObj[key]; }); var searchString = queryString_3(combinedSearchObj, { arrayFormat: 'bracket' }); var pathname = newPathname.join('/'); var pathnameString = pathname === '' ? '/' : "/" + pathname; var location; if (searchString === '') { location = pathnameString; } else { location = pathnameString + "?" + searchString; } return { location: location, options: newLocation.options }; }; var NativeStore = (function () { function NativeStore(config) { this.kind = 'memory'; this.observers = []; this.serializer = (config && config.serializer) || serializer; this.deserializer = (config && config.deserializer) || deserializer; this.historySize = (config && config.historySize) || 10; this.history = []; this.currentLocationInHistory = 0; } NativeStore.prototype.setState = function (unserializedLocation, options) { if (options === void 0) { options = {}; } var oldUnserializedLocation = this.getState(); var newState = this.serializer(unserializedLocation, oldUnserializedLocation).location; if (options.updateHistory !== false) { var newHistory = this.history.slice(); if (unserializedLocation.options && unserializedLocation.options.replaceLocation === true) { newHistory.shift(); } newHistory.unshift(newState.slice()); if (newHistory.length > this.historySize) { newHistory = newHistory.slice(0, this.historySize); } this.history = newHistory; } this.notifyObservers(); }; NativeStore.prototype.getState = function () { return this.deserializer(this.history[this.currentLocationInHistory]); }; NativeStore.prototype.subscribeToStateChanges = function (fn) { this.observers.push(fn); var deserializedState = this.getState(); if (deserializedState) { fn(deserializedState); } }; NativeStore.prototype.unsubscribeFromStateChanges = function (fn) { this.observers = this.observers.filter(function (existingFn) { return existingFn !== fn; }); }; NativeStore.prototype.back = function () { this.go(-1); }; NativeStore.prototype.forward = function () { this.go(1); }; NativeStore.prototype.go = function (historyChange) { if (historyChange === 0) { throw new Error('No history size change specified'); } var newLocation = this.currentLocationInHistory - historyChange; if (newLocation + 1 <= this.history.length && newLocation >= 0) { this.currentLocationInHistory = newLocation; } else if (newLocation + 1 <= this.history.length) { this.currentLocationInHistory = 0; } else if (newLocation >= 0) { this.currentLocationInHistory = this.history.length - 1; } var existingLocation = this.getState(); this.setState(__assign({}, existingLocation), { updateHistory: false }); }; NativeStore.prototype.notifyObservers = function () { var deserializedState = this.getState(); this.observers.forEach(function (fn) { return fn(deserializedState); }); }; return NativeStore; }()); var BrowserStore = (function () { function BrowserStore(config) { var _this = this; this.kind = 'browser'; this.cleanUp = function () { global.clearInterval(_this.stateWatcher); _this.stateWatcher = undefined; }; this.observers = []; this.serializer = (config && config.serializer) || serializer; this.deserializer = (config && config.deserializer) || deserializer; this.stateWatcher = global.setInterval(function () { _this._monitorLocation(); }, 100); this._monitorLocation(); } BrowserStore.prototype.setState = function (unserializedLocation) { if (!window || !window.history || !window.history.replaceState || !window.history.pushState) { throw new Error('Could not find window.history.replaceState or window.history.pushState. Consider using the memory store instead of the browser store.'); } var oldUnserializedLocation = this.getState(); var newState = this.serializer(unserializedLocation, oldUnserializedLocation).location; if (unserializedLocation.options && unserializedLocation.options.replaceLocation === true) { window.history.replaceState({ url: newState }, '', newState); } else { window.history.pushState({ url: newState }, '', newState); } }; BrowserStore.prototype.getState = function () { var searchString = window.location.search || ''; var pathnameString = window.location.pathname || ''; return this.deserializer(pathnameString + searchString); }; BrowserStore.prototype.subscribeToStateChanges = function (fn) { this.observers.push(fn); var deserializedState = this.getState(); fn(deserializedState); }; BrowserStore.prototype.unsubscribeFromStateChanges = function (fn) { this.observers = this.observers.filter(function (existingFn) { return existingFn !== fn; }); }; BrowserStore.prototype.back = function () { if (!window || !window.history || !window.history.back) { throw new Error('Could not find window.history.back. Consider using the memory store instead of the browser store.'); } window.history.back(); }; BrowserStore.prototype.forward = function () { if (!window || !window.history || !window.history.forward) { throw new Error('Could not find window.history.forward. Consider using the memory store instead of the browser store.'); } window.history.forward(); }; BrowserStore.prototype.go = function (historyChange) { if (!window || !window.history || !window.history.go) { throw new Error('Could not find window.history.go. Consider using the memory store instead of the browser store.'); } window.history.go(historyChange); }; BrowserStore.prototype._monitorLocation = function () { if (!window) { throw new Error('window object not found. Wrong environment'); } var newLocation = window.location.href; if (this.existingLocation !== newLocation) { this.existingLocation = newLocation; this.notifyObservers(); } }; BrowserStore.prototype.notifyObservers = function () { var deserializedState = this.getState(); this.observers.forEach(function (fn) { return fn(deserializedState); }); }; return BrowserStore; }()); var isMemorySerializedStateStore = function (store) { return store.kind === 'memory'; }; var isBrowserSerializedStateStore = function (store) { return store.kind === 'browser'; }; var objKeys = function (o) { return Object.keys(o); }; var RouterBase = (function () { function RouterBase(init) { var _this = this; this.link = function (actionName, options) { return _this.manager.linkTo(_this.name, actionName, options); }; this.getLocationDataFromLocationObject = function (location) { return _this.isPathRouter ? location.pathname[_this.pathLocation] : location.search[_this.routeKey]; }; this._state = function () { if (!_this.getState) { throw new Error('no getState function specified by the manager'); } var current = _this.getState().current; var newState = current || {}; return __assign(__assign({}, newState), _this.EXPERIMENTAL_internal_state); }; this._history = function () { if (!_this.getState) { throw new Error('no getState function specified by the manager'); } var historical = _this.getState().historical; return historical || []; }; var name = init.name, config = init.config, type = init.type, manager = init.manager, parent = init.parent, children = init.children, root = init.root, getState = init.getState, subscribe = init.subscribe, actions = init.actions; if (!name || !type || !manager || !config) { throw new Error('Missing required kwargs: name, type, config, and/or manager'); } this.name = name; this.config = config; this.type = type; this.manager = manager; this.parent = parent; this.children = children || {}; this.root = root; this.getState = getState; this.subscribe = subscribe; this._EXPERIMENTAL_internal_state = {}; (actions || []).forEach(function (actionName) { if (_this[actionName]) { _this[actionName] = _this[actionName].bind(_this); } }); } Object.defineProperty(RouterBase.prototype, "lastDefinedParentsDisableChildCacheState", { get: function () { if (!this.parent) { return false; } var parentState = this.parent.config.disableCaching; if (parentState !== undefined) { return parentState; } else { return this.parent.lastDefinedParentsDisableChildCacheState; } }, enumerable: true, configurable: true }); Object.defineProperty(RouterBase.prototype, "routeKey", { get: function () { return this.config.routeKey || this.name; }, enumerable: true, configurable: true }); Object.defineProperty(RouterBase.prototype, "data", { get: function () { return this.state.data ? this.state.data : this.manager.routerCache.cache[this.name] ? this.manager.routerCache.cache[this.name].data : undefined; }, enumerable: true, configurable: true }); Object.defineProperty(RouterBase.prototype, "siblings", { get: function () { var _this = this; return this.parent.children[this.type].filter(function (r) { return r.name !== _this.name; }); }, enumerable: true, configurable: true }); RouterBase.prototype.getNeighborsByType = function (type) { if (this.parent && this.parent.children) { return this.parent.children[type] || []; } return []; }; RouterBase.prototype.getNeighbors = function () { var _this = this; if (!this.parent) { return []; } var flattened = function (acc, arr) { return acc.concat.apply(acc, arr); }; return objKeys(this.parent.children) .filter(function (t) { return t !== _this.type; }) .map(function (t) { return _this.parent.children[t]; }) .reduce(flattened, []); }; Object.defineProperty(RouterBase.prototype, "pathLocation", { get: function () { if (!this.parent) { return -1; } return 1 + this.parent.pathLocation; }, enumerable: true, configurable: true }); Object.defineProperty(RouterBase.prototype, "isRootRouter", { get: function () { return !this.parent; }, enumerable: true, configurable: true }); RouterBase.prototype.EXPERIMENTAL_setInternalState = function (internalState) { this._EXPERIMENTAL_internal_state = __assign({}, internalState); }; Object.defineProperty(RouterBase.prototype, "EXPERIMENTAL_internal_state", { get: function () { return this._EXPERIMENTAL_internal_state; }, enumerable: true, configurable: true }); RouterBase.prototype.serialize = function (options) { var _this = this; if (options === void 0) { options = {}; } var serialized = { name: this.name, routeKey: options.alwaysShowRouteKey ? this.routeKey : this.routeKey === this.name ? undefined : this.routeKey, type: options.showType ? this.type : undefined, parentName: options.showParentName && this.parent ? this.parent.name : undefined, isPathRouter: this.config.isPathRouter, disableCaching: options.showDefaults ? this.config.disableCaching : undefined, shouldInverselyActivate: options.showDefaults ? this.config.shouldInverselyActivate : undefined, defaultAction: options.showDefaults ? this.config.defaultAction : undefined }; var childRouterTypes = Object.keys(this.children); var childRouters = childRouterTypes.reduce(function (acc, type) { acc[type] = _this.children[type].map(function (childRouter) { return childRouter.serialize(options); }); return acc; }, {}); if (childRouterTypes.length > 0) { serialized.routers = childRouters; } Object.keys(serialized).forEach(function (key) { return serialized[key] === undefined ? delete serialized[key] : ''; }); return serialized; }; Object.defineProperty(RouterBase.prototype, "isPathRouter", { get: function () { if (!this.parent) { return true; } if (this.config.isPathRouter && this.parent.isPathRouter) { return true; } if (this.config.isPathRouter) { throw new Error(this.type + " router: " + this.name + " is explicitly set to modify the pathname\n \tbut one of its parent routers doesn't have this permission.\n \tMake sure all parents have 'isPathRouter' attribute set to 'true' in the router config OR\n \tMake sure all parents are of router type 'scene' or 'data'.\n \tIf the routers parents have siblings of both 'scene' and 'data' the 'scene' router will always be used for the pathname\n "); } return false; }, enumerable: true, configurable: true }); Object.defineProperty(RouterBase.prototype, "state", { get: function () { return this._state(); }, enumerable: true, configurable: true }); Object.defineProperty(RouterBase.prototype, "history", { get: function () { return this._history(); }, enumerable: true, configurable: true }); return RouterBase; }()); var DefaultRoutersStateStore = (function () { function DefaultRoutersStateStore(store, config) { this.store = store || {}; this.config = __assign({ historySize: 4 }, config); this.observers = {}; } DefaultRoutersStateStore.prototype.setState = function (desiredRouterStates) { var _this = this; var routerNames = objKeys(desiredRouterStates); var hasUpdatedTracker = []; this.store = routerNames.reduce(function (routerStates, routerName) { var _a = routerStates[routerName] || { current: {}, historical: [] }, prevCurrent = _a.current, historical = _a.historical; var newCurrent = desiredRouterStates[routerName]; if (newCurrent.visible === prevCurrent.visible && newCurrent.data === prevCurrent.data) { return routerStates; } var newHistorical = historical.slice(); if (objKeys(prevCurrent).length > 0) { newHistorical.unshift(prevCurrent); } if (newHistorical.length > _this.config.historySize) { newHistorical = newHistorical.slice(0, _this.config.historySize); } routerStates[routerName] = { current: newCurrent, historical: newHistorical }; hasUpdatedTracker.push(routerName); return routerStates; }, __assign({}, this.getState())); hasUpdatedTracker.forEach(function (routerName) { var observers = _this.observers[routerName] || []; if (Array.isArray(observers)) { observers.forEach(function (fn) { return fn(_this.store[routerName]); }); } }); }; DefaultRoutersStateStore.prototype.createRouterStateGetter = function (routerName) { var _this = this; return function () { return _this.store[routerName] || { current: undefined, historical: [] }; }; }; DefaultRoutersStateStore.prototype.createRouterStateSubscriber = function (routerName) { var _this = this; if (!this.observers[routerName]) { this.observers[routerName] = []; } return function (fn) { if (Array.isArray(_this.observers[routerName])) { _this.observers[routerName].push(fn); } else { _this.observers[routerName] = [fn]; } var currentState = _this.store[routerName]; if (currentState) { fn(currentState); } }; }; DefaultRoutersStateStore.prototype.createRouterStateUnsubscriber = function (routerName) { var _this = this; return function (fn) { if (!_this.observers[routerName]) { return; } var observers = _this.observers[routerName]; _this.observers[routerName] = observers.filter(function (presentObservers) { return presentObservers !== fn; }); }; }; DefaultRoutersStateStore.prototype.unsubscribeAllObserversForRouter = function (routerName) { if (!this.observers[routerName]) { return; } delete this.observers[routerName]; }; DefaultRoutersStateStore.prototype.getState = function () { return this.store; }; return DefaultRoutersStateStore; }()); var addRealDisableCacheFlagToContext = function (router, ctx) { var disableCaching = router.config.disableCaching !== undefined ? router.config.disableCaching : ctx.disableCaching || router.lastDefinedParentsDisableChildCacheState || false; var hasChildren = router.children && Object.values(router.children).reduce(function (childrenExist, children) { return (children.length && children.length > 0) || childrenExist; }, false); if (hasChildren) { ctx.tracer && ctx.tracer.logStep("Passing to children the ctx state: 'disableCaching' = " + disableCaching, { disableCaching: disableCaching }); } return __assign(__assign({}, ctx), { disableCaching: disableCaching }); }; var calculateIfShouldUseCache = function (ctx, _options) { return !ctx.disableCaching; }; var attemptToHideChildRouters = function (options, existingLocation, router, ctx) { if (ctx.actionName !== 'hide') { return { location: existingLocation, ctx: ctx }; } ctx.tracer && ctx.tracer.logStep("Calling 'attemptToHideChildRouters'"); var newCtx = addRealDisableCacheFlagToContext(router, ctx); var locationFromChildren = objKeys(router.children).reduce(function (locationFromChildrenAcc, routerType) { return router.children[routerType].reduce(function (locationFromSpecificChildAcc, child) { var childTracer = router.manager.tracerSession.tracerThing(child.name); ctx.tracer.logStep("Looking at child: " + child.name); if (child.state.visible) { childTracer.logStep("Calling actionFn: 'hide'"); return child.hide({}, locationFromSpecificChildAcc, child, newCtx); } else { childTracer.logStep("Not calling 'hide' b/c its hidden already"); return locationFromSpecificChildAcc; } }, locationFromChildrenAcc); }, existingLocation); var shouldCache = calculateIfShouldUseCache(newCtx); if (shouldCache && !router.manager.routerCache.wasVisible(router.name)) { var visible = !!router.getLocationDataFromLocationObject(__assign({}, locationFromChildren)); if (options.dryRun) { ctx.tracer.logStep("Not caching state because 'dryRun' is enabled"); } else { ctx.tracer.logStep("Caching state", { shouldCache: shouldCache }); router.manager.routerCache.setCache(router.name, { visible: visible, data: router.data }); } } else { ctx.tracer.logStep("Not Caching state", { shouldCache: shouldCache }); } return { location: locationFromChildren, ctx: ctx }; }; var attemptToShowChildRouters = function (options, location, router, ctx) { var newLocation = objKeys(router.children).reduce(function (newLocationFromAllRouters, routerType) { if (routerType === ctx.activatedByChildType && !router.config.shouldParentTryToActivateSiblings) { ctx.tracer && ctx.tracer.logStep("Not calling children of type " + routerType + " b/c they are the same type as activation origin"); return newLocationFromAllRouters; } return router.children[routerType].reduce(function (newLocationForSpecificChild, child) { if (ctx.callDirection === 'up' && child.config.shouldInverselyActivate === false) { ctx && ctx.tracer.logStep("Not calling child (" + child.name + ") b/c it is not inversely active"); return newLocationForSpecificChild; } var newContext = __assign(__assign({}, ctx), { addingDefaults: true, activatedByChildType: undefined, callDirection: 'down' }); if (child.manager.routerCache.wasVisible(child.name) === true) { if (options.dryRun) { ctx.tracer.logStep("Not removing cache because 'dryRun' is enabled"); } else { ctx.tracer.logStep("Removing cache"); child.manager.routerCache.removeCache(child.name); } ctx.tracer && ctx.tracer.logStep("Calling show action of child (" + child.name + ") b/c it has a cached previous visibility"); return child.show(__assign(__assign({}, options), { data: child.manager.routerCache.previousData(child.name) }), newLocationForSpecificChild, child, newContext); } else if (child.manager.routerCache.wasVisible(child.name) === false) { ctx.tracer && ctx.tracer.logStep("Skipping show action of child (" + child.name + ") b/c it wasn't previously visible"); return newLocationForSpecificChild; } else if (child.config.defaultAction && child.config.defaultAction.length > 0) { var _a = child.config.defaultAction, action = _a[0], args = _a.slice(1); ctx.tracer && ctx.tracer.logStep("No cached state found for " + child.name + ", but default action found. Applying default action: " + action + " for " + child.name); return child[action](__assign(__assign({}, options), { data: args[0] }), newLocationForSpecificChild, child, newContext); } return newLocationForSpecificChild; }, newLocationFromAllRouters); }, location); return { location: newLocation, ctx: ctx }; }; var attemptToShowChildRoutersMain = function (options, location, routerInstance, ctx) { var hasChildren = routerInstance.children && Object.values(routerInstance.children).reduce(function (childrenExist, children) { return (children.length && children.length > 0) || childrenExist; }, false); if (ctx.actionName === 'show' && hasChildren) { if (ctx.routerIsMissingData) { ctx.tracer && ctx.tracer.logStep("Not calling 'attemptToShowChildRouters' because the current router or a parent is missing data"); return { location: location, ctx: ctx }; } ctx.tracer && ctx.tracer.logStep("Calling 'attemptToShowChildRouters'"); return attemptToShowChildRouters(options, __assign({}, location), routerInstance, ctx); } return { location: location, ctx: ctx }; }; var attemptToShowParentRouter = function (_options, location, routerInstance, ctx) { if (ctx.actionName === 'show' && routerInstance.parent && (routerInstance.parent.state.visible === false || routerInstance.parent.state.visible === undefined) && ctx.callDirection !== 'down' && ctx.callDirection !== 'lateral') { ctx.tracer && ctx.tracer.logStep("Calling 'show' action of router parent: " + routerInstance.parent.name + " "); var locationFromShowingParent = routerInstance.parent.show({}, __assign({}, location), routerInstance.parent, __assign(__assign({}, ctx), { callDirection: 'up', activatedByChildType: routerInstance.type })); return { location: locationFromShowingParent, ctx: ctx }; } return { location: location, ctx: ctx }; }; var endTracerSession = function (_options, location, routerInstance, ctx) { routerInstance.manager.tracerSession.endWithMessage('Action complete'); return { location: location, ctx: ctx }; }; var logTracerSteps = function (options, location, ro