UNPKG

@stencil/router

Version:
660 lines (659 loc) 26.5 kB
import { r as registerInstance, c as getContext, h, g as getElement } from './stencilrouter-1307249c.js'; import { A as ActiveRouter } from './chunk-cfc6485e.js'; import { s as stripTrailingSlash, c as createLocation, a as addLeadingSlash, b as stripBasename, d as createKey, e as createPath, h as hasBasename, f as stripLeadingSlash, l as locationsAreEqual } from './chunk-d2e78d53.js'; import { s as storageAvailable, a as supportsHistory, b as supportsPopStateOnHashChange, g as getConfirmation, c as isExtraneousPopstateEvent, d as supportsGoWithoutReloadUsingHash } from './chunk-4eecdc1a.js'; var warning = function (value) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } if (!value) { console.warn.apply(console, args); } }; // Adapted from the https://github.com/ReactTraining/history and converted to TypeScript var createTransitionManager = function () { var prompt; var listeners = []; var setPrompt = function (nextPrompt) { warning(prompt == null, 'A history supports only one prompt at a time'); prompt = nextPrompt; return function () { if (prompt === nextPrompt) { prompt = null; } }; }; var confirmTransitionTo = function (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(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 appendListener = function (fn) { var isActive = true; var listener = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } if (isActive) { fn.apply(void 0, args); } }; listeners.push(listener); return function () { isActive = false; listeners = listeners.filter(function (item) { return item !== listener; }); }; }; var notifyListeners = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } listeners.forEach(function (listener) { return listener.apply(void 0, args); }); }; return { setPrompt: setPrompt, confirmTransitionTo: confirmTransitionTo, appendListener: appendListener, notifyListeners: notifyListeners }; }; var createScrollHistory = function (win, applicationScrollKey) { if (applicationScrollKey === void 0) { applicationScrollKey = 'scrollPositions'; } var scrollPositions = new Map(); var set = function (key, value) { scrollPositions.set(key, value); if (storageAvailable(win, 'sessionStorage')) { var arrayData_1 = []; scrollPositions.forEach(function (value, key) { arrayData_1.push([key, value]); }); win.sessionStorage.setItem('scrollPositions', JSON.stringify(arrayData_1)); } }; var get = function (key) { return scrollPositions.get(key); }; var has = function (key) { return scrollPositions.has(key); }; var capture = function (key) { set(key, [win.scrollX, win.scrollY]); }; if (storageAvailable(win, 'sessionStorage')) { var scrollData = win.sessionStorage.getItem(applicationScrollKey); scrollPositions = scrollData ? new Map(JSON.parse(scrollData)) : scrollPositions; } if ('scrollRestoration' in win.history) { history.scrollRestoration = 'manual'; } return { set: set, get: get, has: has, capture: capture }; }; // Adapted from the https://github.com/ReactTraining/history and converted to TypeScript var PopStateEvent = 'popstate'; var HashChangeEvent = 'hashchange'; /** * Creates a history object that uses the HTML5 history API including * pushState, replaceState, and the popstate event. */ var createBrowserHistory = function (win, props) { if (props === void 0) { props = {}; } var forceNextPop = false; var globalHistory = win.history; var globalLocation = win.location; var globalNavigator = win.navigator; var canUseHistory = supportsHistory(win); var needsHashChangeListener = !supportsPopStateOnHashChange(globalNavigator); var scrollHistory = createScrollHistory(win); var forceRefresh = (props.forceRefresh != null) ? props.forceRefresh : false; var getUserConfirmation = (props.getUserConfirmation != null) ? props.getUserConfirmation : getConfirmation; var keyLength = (props.keyLength != null) ? props.keyLength : 6; var basename = props.basename ? stripTrailingSlash(addLeadingSlash(props.basename)) : ''; var getHistoryState = function () { try { return win.history.state || {}; } catch (e) { // IE 11 sometimes throws when accessing window.history.state // See https://github.com/ReactTraining/history/pull/289 return {}; } }; var getDOMLocation = function (historyState) { historyState = historyState || {}; var key = historyState.key, state = historyState.state; var pathname = globalLocation.pathname, search = globalLocation.search, hash = globalLocation.hash; var path = pathname + search + hash; warning((!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 || createKey(keyLength)); }; var transitionManager = createTransitionManager(); var setState = function (nextState) { // Capture location for the view before changing history. scrollHistory.capture(history.location.key); Object.assign(history, nextState); // Set scroll position based on its previous storage value history.location.scrollPosition = scrollHistory.get(history.location.key); history.length = globalHistory.length; transitionManager.notifyListeners(history.location, history.action); }; var handlePopState = function (event) { // Ignore extraneous popstate events in WebKit. if (!isExtraneousPopstateEvent(globalNavigator, event)) { handlePop(getDOMLocation(event.state)); } }; var handleHashChange = function () { handlePop(getDOMLocation(getHistoryState())); }; var handlePop = function (location) { if (forceNextPop) { forceNextPop = false; setState(); } else { var action_1 = 'POP'; transitionManager.confirmTransitionTo(location, action_1, getUserConfirmation, function (ok) { if (ok) { setState({ action: action_1, location: location }); } else { revertPop(location); } }); } }; var revertPop = function (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); var fromIndex = allKeys.indexOf(fromLocation.key); if (toIndex === -1) { toIndex = 0; } if (fromIndex === -1) { fromIndex = 0; } var delta = toIndex - fromIndex; if (delta) { forceNextPop = true; go(delta); } }; var initialLocation = getDOMLocation(getHistoryState()); var allKeys = [initialLocation.key]; var listenerCount = 0; var isBlocked = false; // Public interface var createHref = function (location) { return basename + createPath(location); }; var push = function (path, state) { warning(!(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(keyLength), 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 }, '', href); if (forceRefresh) { globalLocation.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(state === undefined, 'Browser history cannot push state in browsers that do not support HTML5 history'); globalLocation.href = href; } }); }; var replace = function (path, state) { warning(!(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(keyLength), 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 }, '', href); if (forceRefresh) { globalLocation.replace(href); } else { var prevIndex = allKeys.indexOf(history.location.key); if (prevIndex !== -1) { allKeys[prevIndex] = location.key; } setState({ action: action, location: location }); } } else { warning(state === undefined, 'Browser history cannot replace state in browsers that do not support HTML5 history'); globalLocation.replace(href); } }); }; var go = function (n) { globalHistory.go(n); }; var goBack = function () { return go(-1); }; var goForward = function () { return go(1); }; var checkDOMListeners = function (delta) { listenerCount += delta; if (listenerCount === 1) { win.addEventListener(PopStateEvent, handlePopState); if (needsHashChangeListener) { win.addEventListener(HashChangeEvent, handleHashChange); } } else if (listenerCount === 0) { win.removeEventListener(PopStateEvent, handlePopState); if (needsHashChangeListener) { win.removeEventListener(HashChangeEvent, handleHashChange); } } }; var block = function (prompt) { if (prompt === void 0) { prompt = ''; } var unblock = transitionManager.setPrompt(prompt); if (!isBlocked) { checkDOMListeners(1); isBlocked = true; } return function () { if (isBlocked) { isBlocked = false; checkDOMListeners(-1); } return unblock(); }; }; var listen = function (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, win: win }; return history; }; // Adapted from the https://github.com/ReactTraining/history and converted to TypeScript var HashChangeEvent$1 = 'hashchange'; var HashPathCoders = { hashbang: { encodePath: function (path) { return path.charAt(0) === '!' ? path : '!/' + stripLeadingSlash(path); }, decodePath: function (path) { return path.charAt(0) === '!' ? path.substr(1) : path; } }, noslash: { encodePath: stripLeadingSlash, decodePath: addLeadingSlash }, slash: { encodePath: addLeadingSlash, decodePath: addLeadingSlash } }; var createHashHistory = function (win, props) { if (props === void 0) { props = {}; } var forceNextPop = false; var ignorePath = null; var listenerCount = 0; var isBlocked = false; var globalLocation = win.location; var globalHistory = win.history; var canGoWithoutReload = supportsGoWithoutReloadUsingHash(win.navigator); var keyLength = (props.keyLength != null) ? props.keyLength : 6; var _a = props.getUserConfirmation, getUserConfirmation = _a === void 0 ? getConfirmation : _a, _b = props.hashType, hashType = _b === void 0 ? 'slash' : _b; var basename = props.basename ? stripTrailingSlash(addLeadingSlash(props.basename)) : ''; var _c = HashPathCoders[hashType], encodePath = _c.encodePath, decodePath = _c.decodePath; var getHashPath = function () { // We can't use window.location.hash here because it's not // consistent across browsers - Firefox will pre-decode it! var href = globalLocation.href; var hashIndex = href.indexOf('#'); return hashIndex === -1 ? '' : href.substring(hashIndex + 1); }; var pushHashPath = function (path) { return (globalLocation.hash = path); }; var replaceHashPath = function (path) { var hashIndex = globalLocation.href.indexOf('#'); globalLocation.replace(globalLocation.href.slice(0, hashIndex >= 0 ? hashIndex : 0) + '#' + path); }; var getDOMLocation = function () { var path = decodePath(getHashPath()); warning((!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, undefined, createKey(keyLength)); }; var transitionManager = createTransitionManager(); var setState = function (nextState) { Object.assign(history, nextState); history.length = globalHistory.length; transitionManager.notifyListeners(history.location, history.action); }; var handleHashChange = function () { var path = getHashPath(); var encodedPath = encodePath(path); if (path !== encodedPath) { // Ensure we always have a properly-encoded hash. replaceHashPath(encodedPath); } else { var location = getDOMLocation(); var prevLocation = history.location; if (!forceNextPop && locationsAreEqual(prevLocation, location)) { return; // A hashchange doesn't always == location change. } if (ignorePath === createPath(location)) { return; // Ignore this change; we already setState in push/replace. } ignorePath = null; handlePop(location); } }; var handlePop = function (location) { if (forceNextPop) { forceNextPop = false; setState(); } else { var action_2 = 'POP'; transitionManager.confirmTransitionTo(location, action_2, getUserConfirmation, function (ok) { if (ok) { setState({ action: action_2, location: location }); } else { revertPop(location); } }); } }; var revertPop = function (fromLocation) { var toLocation = history.location; // TODO: We could probably make this more reliable by // keeping a list of paths we've seen in sessionStorage. // Instead, we just default to 0 for paths we don't know. var toIndex = allPaths.lastIndexOf(createPath(toLocation)); var fromIndex = allPaths.lastIndexOf(createPath(fromLocation)); if (toIndex === -1) { toIndex = 0; } if (fromIndex === -1) { fromIndex = 0; } var delta = toIndex - fromIndex; if (delta) { forceNextPop = true; go(delta); } }; // Ensure the hash is encoded properly before doing anything else. var path = getHashPath(); var encodedPath = encodePath(path); if (path !== encodedPath) { replaceHashPath(encodedPath); } var initialLocation = getDOMLocation(); var allPaths = [createPath(initialLocation)]; // Public interface var createHref = function (location) { return ('#' + encodePath(basename + createPath(location))); }; var push = function (path, state) { warning(state === undefined, 'Hash history cannot push state; it is ignored'); var action = 'PUSH'; var location = createLocation(path, undefined, createKey(keyLength), history.location); transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) { if (!ok) { return; } var path = createPath(location); var encodedPath = encodePath(basename + path); var hashChanged = getHashPath() !== encodedPath; if (hashChanged) { // We cannot tell if a hashchange was caused by a PUSH, so we'd // rather setState here and ignore the hashchange. The caveat here // is that other hash histories in the page will consider it a POP. ignorePath = path; pushHashPath(encodedPath); var prevIndex = allPaths.lastIndexOf(createPath(history.location)); var nextPaths = allPaths.slice(0, prevIndex === -1 ? 0 : prevIndex + 1); nextPaths.push(path); allPaths = nextPaths; setState({ action: action, location: location }); } else { warning(false, 'Hash history cannot PUSH the same path; a new entry will not be added to the history stack'); setState(); } }); }; var replace = function (path, state) { warning(state === undefined, 'Hash history cannot replace state; it is ignored'); var action = 'REPLACE'; var location = createLocation(path, undefined, createKey(keyLength), history.location); transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) { if (!ok) { return; } var path = createPath(location); var encodedPath = encodePath(basename + path); var hashChanged = getHashPath() !== encodedPath; if (hashChanged) { // We cannot tell if a hashchange was caused by a REPLACE, so we'd // rather setState here and ignore the hashchange. The caveat here // is that other hash histories in the page will consider it a POP. ignorePath = path; replaceHashPath(encodedPath); } var prevIndex = allPaths.indexOf(createPath(history.location)); if (prevIndex !== -1) { allPaths[prevIndex] = path; } setState({ action: action, location: location }); }); }; var go = function (n) { warning(canGoWithoutReload, 'Hash history go(n) causes a full page reload in this browser'); globalHistory.go(n); }; var goBack = function () { return go(-1); }; var goForward = function () { return go(1); }; var checkDOMListeners = function (win, delta) { listenerCount += delta; if (listenerCount === 1) { win.addEventListener(HashChangeEvent$1, handleHashChange); } else if (listenerCount === 0) { win.removeEventListener(HashChangeEvent$1, handleHashChange); } }; var block = function (prompt) { if (prompt === void 0) { prompt = ''; } var unblock = transitionManager.setPrompt(prompt); if (!isBlocked) { checkDOMListeners(win, 1); isBlocked = true; } return function () { if (isBlocked) { isBlocked = false; checkDOMListeners(win, -1); } return unblock(); }; }; var listen = function (listener) { var unlisten = transitionManager.appendListener(listener); checkDOMListeners(win, 1); return function () { checkDOMListeners(win, -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, win: win }; return history; }; var getLocation = function (location, root) { // Remove the root URL if found at beginning of string var pathname = location.pathname.indexOf(root) == 0 ? '/' + location.pathname.slice(root.length) : location.pathname; return Object.assign({}, location, { pathname: pathname }); }; var HISTORIES = { 'browser': createBrowserHistory, 'hash': createHashHistory }; /** * @name Router * @module ionic * @description */ var Router = /** @class */ (function () { function Router(hostRef) { var _this = this; registerInstance(this, hostRef); this.root = '/'; this.historyType = 'browser'; // A suffix to append to the page title whenever // it's updated through RouteTitle this.titleSuffix = ''; this.routeViewsUpdated = function (options) { if (options === void 0) { options = {}; } if (_this.history && options.scrollToId && _this.historyType === 'browser') { var elm = _this.history.win.document.getElementById(options.scrollToId); if (elm) { return elm.scrollIntoView(); } } _this.scrollTo(options.scrollTopOffset || _this.scrollTopOffset); }; this.isServer = getContext(this, "isServer"); this.queue = getContext(this, "queue"); } Router.prototype.componentWillLoad = function () { var _this = this; this.history = HISTORIES[this.historyType](this.el.ownerDocument.defaultView); this.history.listen(function (location) { location = getLocation(location, _this.root); _this.location = location; }); this.location = getLocation(this.history.location, this.root); }; Router.prototype.scrollTo = function (scrollToLocation) { var history = this.history; if (scrollToLocation == null || this.isServer || !history) { return; } if (history.action === 'POP' && Array.isArray(history.location.scrollPosition)) { return this.queue.write(function () { if (history && history.location && Array.isArray(history.location.scrollPosition)) { history.win.scrollTo(history.location.scrollPosition[0], history.location.scrollPosition[1]); } }); } // okay, the frame has passed. Go ahead and render now return this.queue.write(function () { history.win.scrollTo(0, scrollToLocation); }); }; Router.prototype.render = function () { if (!this.location || !this.history) { return; } var state = { historyType: this.historyType, location: this.location, titleSuffix: this.titleSuffix, root: this.root, history: this.history, routeViewsUpdated: this.routeViewsUpdated }; return (h(ActiveRouter.Provider, { state: state }, h("slot", null))); }; Object.defineProperty(Router.prototype, "el", { get: function () { return getElement(this); }, enumerable: true, configurable: true }); return Router; }()); export { Router as stencil_router };