UNPKG

25.4 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5const __chunk_1 = require('./stencilrouter-a3d77a87.js');
6const __chunk_2 = require('./chunk-94c92d88.js');
7const __chunk_4 = require('./chunk-e6311a56.js');
8const __chunk_5 = require('./chunk-b9bd6b52.js');
9
10const warning = (value, ...args) => {
11 if (!value) {
12 console.warn(...args);
13 }
14};
15
16// Adapted from the https://github.com/ReactTraining/history and converted to TypeScript
17const createTransitionManager = () => {
18 let prompt;
19 let listeners = [];
20 const setPrompt = (nextPrompt) => {
21 warning(prompt == null, 'A history supports only one prompt at a time');
22 prompt = nextPrompt;
23 return () => {
24 if (prompt === nextPrompt) {
25 prompt = null;
26 }
27 };
28 };
29 const confirmTransitionTo = (location, action, getUserConfirmation, callback) => {
30 // TODO: If another transition starts while we're still confirming
31 // the previous one, we may end up in a weird state. Figure out the
32 // best way to handle this.
33 if (prompt != null) {
34 const result = typeof prompt === 'function' ? prompt(location, action) : prompt;
35 if (typeof result === 'string') {
36 if (typeof getUserConfirmation === 'function') {
37 getUserConfirmation(result, callback);
38 }
39 else {
40 warning(false, 'A history needs a getUserConfirmation function in order to use a prompt message');
41 callback(true);
42 }
43 }
44 else {
45 // Return false from a transition hook to cancel the transition.
46 callback(result !== false);
47 }
48 }
49 else {
50 callback(true);
51 }
52 };
53 const appendListener = (fn) => {
54 let isActive = true;
55 const listener = (...args) => {
56 if (isActive) {
57 fn(...args);
58 }
59 };
60 listeners.push(listener);
61 return () => {
62 isActive = false;
63 listeners = listeners.filter(item => item !== listener);
64 };
65 };
66 const notifyListeners = (...args) => {
67 listeners.forEach(listener => listener(...args));
68 };
69 return {
70 setPrompt,
71 confirmTransitionTo,
72 appendListener,
73 notifyListeners
74 };
75};
76
77const createScrollHistory = (win, applicationScrollKey = 'scrollPositions') => {
78 let scrollPositions = new Map();
79 const set = (key, value) => {
80 scrollPositions.set(key, value);
81 if (__chunk_5.storageAvailable(win, 'sessionStorage')) {
82 const arrayData = [];
83 scrollPositions.forEach((value, key) => {
84 arrayData.push([key, value]);
85 });
86 win.sessionStorage.setItem('scrollPositions', JSON.stringify(arrayData));
87 }
88 };
89 const get = (key) => {
90 return scrollPositions.get(key);
91 };
92 const has = (key) => {
93 return scrollPositions.has(key);
94 };
95 const capture = (key) => {
96 set(key, [win.scrollX, win.scrollY]);
97 };
98 if (__chunk_5.storageAvailable(win, 'sessionStorage')) {
99 const scrollData = win.sessionStorage.getItem(applicationScrollKey);
100 scrollPositions = scrollData ?
101 new Map(JSON.parse(scrollData)) :
102 scrollPositions;
103 }
104 if ('scrollRestoration' in win.history) {
105 history.scrollRestoration = 'manual';
106 }
107 return {
108 set,
109 get,
110 has,
111 capture
112 };
113};
114
115// Adapted from the https://github.com/ReactTraining/history and converted to TypeScript
116const PopStateEvent = 'popstate';
117const HashChangeEvent = 'hashchange';
118/**
119 * Creates a history object that uses the HTML5 history API including
120 * pushState, replaceState, and the popstate event.
121 */
122const createBrowserHistory = (win, props = {}) => {
123 let forceNextPop = false;
124 const globalHistory = win.history;
125 const globalLocation = win.location;
126 const globalNavigator = win.navigator;
127 const canUseHistory = __chunk_5.supportsHistory(win);
128 const needsHashChangeListener = !__chunk_5.supportsPopStateOnHashChange(globalNavigator);
129 const scrollHistory = createScrollHistory(win);
130 const forceRefresh = (props.forceRefresh != null) ? props.forceRefresh : false;
131 const getUserConfirmation = (props.getUserConfirmation != null) ? props.getUserConfirmation : __chunk_5.getConfirmation;
132 const keyLength = (props.keyLength != null) ? props.keyLength : 6;
133 const basename = props.basename ? __chunk_4.stripTrailingSlash(__chunk_4.addLeadingSlash(props.basename)) : '';
134 const getHistoryState = () => {
135 try {
136 return win.history.state || {};
137 }
138 catch (e) {
139 // IE 11 sometimes throws when accessing window.history.state
140 // See https://github.com/ReactTraining/history/pull/289
141 return {};
142 }
143 };
144 const getDOMLocation = (historyState) => {
145 historyState = historyState || {};
146 const { key, state } = historyState;
147 const { pathname, search, hash } = globalLocation;
148 let path = pathname + search + hash;
149 warning((!basename || __chunk_4.hasBasename(path, basename)), 'You are attempting to use a basename on a page whose URL path does not begin ' +
150 'with the basename. Expected path "' + path + '" to begin with "' + basename + '".');
151 if (basename) {
152 path = __chunk_4.stripBasename(path, basename);
153 }
154 return __chunk_4.createLocation(path, state, key || __chunk_4.createKey(keyLength));
155 };
156 const transitionManager = createTransitionManager();
157 const setState = (nextState) => {
158 // Capture location for the view before changing history.
159 scrollHistory.capture(history.location.key);
160 Object.assign(history, nextState);
161 // Set scroll position based on its previous storage value
162 history.location.scrollPosition = scrollHistory.get(history.location.key);
163 history.length = globalHistory.length;
164 transitionManager.notifyListeners(history.location, history.action);
165 };
166 const handlePopState = (event) => {
167 // Ignore extraneous popstate events in WebKit.
168 if (!__chunk_5.isExtraneousPopstateEvent(globalNavigator, event)) {
169 handlePop(getDOMLocation(event.state));
170 }
171 };
172 const handleHashChange = () => {
173 handlePop(getDOMLocation(getHistoryState()));
174 };
175 const handlePop = (location) => {
176 if (forceNextPop) {
177 forceNextPop = false;
178 setState();
179 }
180 else {
181 const action = 'POP';
182 transitionManager.confirmTransitionTo(location, action, getUserConfirmation, (ok) => {
183 if (ok) {
184 setState({ action, location });
185 }
186 else {
187 revertPop(location);
188 }
189 });
190 }
191 };
192 const revertPop = (fromLocation) => {
193 const toLocation = history.location;
194 // TODO: We could probably make this more reliable by
195 // keeping a list of keys we've seen in sessionStorage.
196 // Instead, we just default to 0 for keys we don't know.
197 let toIndex = allKeys.indexOf(toLocation.key);
198 let fromIndex = allKeys.indexOf(fromLocation.key);
199 if (toIndex === -1) {
200 toIndex = 0;
201 }
202 if (fromIndex === -1) {
203 fromIndex = 0;
204 }
205 const delta = toIndex - fromIndex;
206 if (delta) {
207 forceNextPop = true;
208 go(delta);
209 }
210 };
211 const initialLocation = getDOMLocation(getHistoryState());
212 let allKeys = [initialLocation.key];
213 let listenerCount = 0;
214 let isBlocked = false;
215 // Public interface
216 const createHref = (location) => {
217 return basename + __chunk_4.createPath(location);
218 };
219 const push = (path, state) => {
220 warning(!(typeof path === 'object' && path.state !== undefined && state !== undefined), 'You should avoid providing a 2nd state argument to push when the 1st ' +
221 'argument is a location-like object that already has state; it is ignored');
222 const action = 'PUSH';
223 const location = __chunk_4.createLocation(path, state, __chunk_4.createKey(keyLength), history.location);
224 transitionManager.confirmTransitionTo(location, action, getUserConfirmation, (ok) => {
225 if (!ok) {
226 return;
227 }
228 const href = createHref(location);
229 const { key, state } = location;
230 if (canUseHistory) {
231 globalHistory.pushState({ key, state }, '', href);
232 if (forceRefresh) {
233 globalLocation.href = href;
234 }
235 else {
236 const prevIndex = allKeys.indexOf(history.location.key);
237 const nextKeys = allKeys.slice(0, prevIndex === -1 ? 0 : prevIndex + 1);
238 nextKeys.push(location.key);
239 allKeys = nextKeys;
240 setState({ action, location });
241 }
242 }
243 else {
244 warning(state === undefined, 'Browser history cannot push state in browsers that do not support HTML5 history');
245 globalLocation.href = href;
246 }
247 });
248 };
249 const replace = (path, state) => {
250 warning(!(typeof path === 'object' && path.state !== undefined && state !== undefined), 'You should avoid providing a 2nd state argument to replace when the 1st ' +
251 'argument is a location-like object that already has state; it is ignored');
252 const action = 'REPLACE';
253 const location = __chunk_4.createLocation(path, state, __chunk_4.createKey(keyLength), history.location);
254 transitionManager.confirmTransitionTo(location, action, getUserConfirmation, (ok) => {
255 if (!ok) {
256 return;
257 }
258 const href = createHref(location);
259 const { key, state } = location;
260 if (canUseHistory) {
261 globalHistory.replaceState({ key, state }, '', href);
262 if (forceRefresh) {
263 globalLocation.replace(href);
264 }
265 else {
266 const prevIndex = allKeys.indexOf(history.location.key);
267 if (prevIndex !== -1) {
268 allKeys[prevIndex] = location.key;
269 }
270 setState({ action, location });
271 }
272 }
273 else {
274 warning(state === undefined, 'Browser history cannot replace state in browsers that do not support HTML5 history');
275 globalLocation.replace(href);
276 }
277 });
278 };
279 const go = (n) => {
280 globalHistory.go(n);
281 };
282 const goBack = () => go(-1);
283 const goForward = () => go(1);
284 const checkDOMListeners = (delta) => {
285 listenerCount += delta;
286 if (listenerCount === 1) {
287 win.addEventListener(PopStateEvent, handlePopState);
288 if (needsHashChangeListener) {
289 win.addEventListener(HashChangeEvent, handleHashChange);
290 }
291 }
292 else if (listenerCount === 0) {
293 win.removeEventListener(PopStateEvent, handlePopState);
294 if (needsHashChangeListener) {
295 win.removeEventListener(HashChangeEvent, handleHashChange);
296 }
297 }
298 };
299 const block = (prompt = '') => {
300 const unblock = transitionManager.setPrompt(prompt);
301 if (!isBlocked) {
302 checkDOMListeners(1);
303 isBlocked = true;
304 }
305 return () => {
306 if (isBlocked) {
307 isBlocked = false;
308 checkDOMListeners(-1);
309 }
310 return unblock();
311 };
312 };
313 const listen = (listener) => {
314 const unlisten = transitionManager.appendListener(listener);
315 checkDOMListeners(1);
316 return () => {
317 checkDOMListeners(-1);
318 unlisten();
319 };
320 };
321 const history = {
322 length: globalHistory.length,
323 action: 'POP',
324 location: initialLocation,
325 createHref,
326 push,
327 replace,
328 go,
329 goBack,
330 goForward,
331 block,
332 listen,
333 win: win
334 };
335 return history;
336};
337
338// Adapted from the https://github.com/ReactTraining/history and converted to TypeScript
339const HashChangeEvent$1 = 'hashchange';
340const HashPathCoders = {
341 hashbang: {
342 encodePath: (path) => path.charAt(0) === '!' ? path : '!/' + __chunk_4.stripLeadingSlash(path),
343 decodePath: (path) => path.charAt(0) === '!' ? path.substr(1) : path
344 },
345 noslash: {
346 encodePath: __chunk_4.stripLeadingSlash,
347 decodePath: __chunk_4.addLeadingSlash
348 },
349 slash: {
350 encodePath: __chunk_4.addLeadingSlash,
351 decodePath: __chunk_4.addLeadingSlash
352 }
353};
354const createHashHistory = (win, props = {}) => {
355 let forceNextPop = false;
356 let ignorePath = null;
357 let listenerCount = 0;
358 let isBlocked = false;
359 const globalLocation = win.location;
360 const globalHistory = win.history;
361 const canGoWithoutReload = __chunk_5.supportsGoWithoutReloadUsingHash(win.navigator);
362 const keyLength = (props.keyLength != null) ? props.keyLength : 6;
363 const { getUserConfirmation = __chunk_5.getConfirmation, hashType = 'slash' } = props;
364 const basename = props.basename ? __chunk_4.stripTrailingSlash(__chunk_4.addLeadingSlash(props.basename)) : '';
365 const { encodePath, decodePath } = HashPathCoders[hashType];
366 const getHashPath = () => {
367 // We can't use window.location.hash here because it's not
368 // consistent across browsers - Firefox will pre-decode it!
369 const href = globalLocation.href;
370 const hashIndex = href.indexOf('#');
371 return hashIndex === -1 ? '' : href.substring(hashIndex + 1);
372 };
373 const pushHashPath = (path) => (globalLocation.hash = path);
374 const replaceHashPath = (path) => {
375 const hashIndex = globalLocation.href.indexOf('#');
376 globalLocation.replace(globalLocation.href.slice(0, hashIndex >= 0 ? hashIndex : 0) + '#' + path);
377 };
378 const getDOMLocation = () => {
379 let path = decodePath(getHashPath());
380 warning((!basename || __chunk_4.hasBasename(path, basename)), 'You are attempting to use a basename on a page whose URL path does not begin ' +
381 'with the basename. Expected path "' + path + '" to begin with "' + basename + '".');
382 if (basename) {
383 path = __chunk_4.stripBasename(path, basename);
384 }
385 return __chunk_4.createLocation(path, undefined, __chunk_4.createKey(keyLength));
386 };
387 const transitionManager = createTransitionManager();
388 const setState = (nextState) => {
389 Object.assign(history, nextState);
390 history.length = globalHistory.length;
391 transitionManager.notifyListeners(history.location, history.action);
392 };
393 const handleHashChange = () => {
394 const path = getHashPath();
395 const encodedPath = encodePath(path);
396 if (path !== encodedPath) {
397 // Ensure we always have a properly-encoded hash.
398 replaceHashPath(encodedPath);
399 }
400 else {
401 const location = getDOMLocation();
402 const prevLocation = history.location;
403 if (!forceNextPop && __chunk_4.locationsAreEqual(prevLocation, location)) {
404 return; // A hashchange doesn't always == location change.
405 }
406 if (ignorePath === __chunk_4.createPath(location)) {
407 return; // Ignore this change; we already setState in push/replace.
408 }
409 ignorePath = null;
410 handlePop(location);
411 }
412 };
413 const handlePop = (location) => {
414 if (forceNextPop) {
415 forceNextPop = false;
416 setState();
417 }
418 else {
419 const action = 'POP';
420 transitionManager.confirmTransitionTo(location, action, getUserConfirmation, (ok) => {
421 if (ok) {
422 setState({ action, location });
423 }
424 else {
425 revertPop(location);
426 }
427 });
428 }
429 };
430 const revertPop = (fromLocation) => {
431 const toLocation = history.location;
432 // TODO: We could probably make this more reliable by
433 // keeping a list of paths we've seen in sessionStorage.
434 // Instead, we just default to 0 for paths we don't know.
435 let toIndex = allPaths.lastIndexOf(__chunk_4.createPath(toLocation));
436 let fromIndex = allPaths.lastIndexOf(__chunk_4.createPath(fromLocation));
437 if (toIndex === -1) {
438 toIndex = 0;
439 }
440 if (fromIndex === -1) {
441 fromIndex = 0;
442 }
443 const delta = toIndex - fromIndex;
444 if (delta) {
445 forceNextPop = true;
446 go(delta);
447 }
448 };
449 // Ensure the hash is encoded properly before doing anything else.
450 const path = getHashPath();
451 const encodedPath = encodePath(path);
452 if (path !== encodedPath) {
453 replaceHashPath(encodedPath);
454 }
455 const initialLocation = getDOMLocation();
456 let allPaths = [__chunk_4.createPath(initialLocation)];
457 // Public interface
458 const createHref = (location) => ('#' + encodePath(basename + __chunk_4.createPath(location)));
459 const push = (path, state) => {
460 warning(state === undefined, 'Hash history cannot push state; it is ignored');
461 const action = 'PUSH';
462 const location = __chunk_4.createLocation(path, undefined, __chunk_4.createKey(keyLength), history.location);
463 transitionManager.confirmTransitionTo(location, action, getUserConfirmation, (ok) => {
464 if (!ok) {
465 return;
466 }
467 const path = __chunk_4.createPath(location);
468 const encodedPath = encodePath(basename + path);
469 const hashChanged = getHashPath() !== encodedPath;
470 if (hashChanged) {
471 // We cannot tell if a hashchange was caused by a PUSH, so we'd
472 // rather setState here and ignore the hashchange. The caveat here
473 // is that other hash histories in the page will consider it a POP.
474 ignorePath = path;
475 pushHashPath(encodedPath);
476 const prevIndex = allPaths.lastIndexOf(__chunk_4.createPath(history.location));
477 const nextPaths = allPaths.slice(0, prevIndex === -1 ? 0 : prevIndex + 1);
478 nextPaths.push(path);
479 allPaths = nextPaths;
480 setState({ action, location });
481 }
482 else {
483 warning(false, 'Hash history cannot PUSH the same path; a new entry will not be added to the history stack');
484 setState();
485 }
486 });
487 };
488 const replace = (path, state) => {
489 warning(state === undefined, 'Hash history cannot replace state; it is ignored');
490 const action = 'REPLACE';
491 const location = __chunk_4.createLocation(path, undefined, __chunk_4.createKey(keyLength), history.location);
492 transitionManager.confirmTransitionTo(location, action, getUserConfirmation, (ok) => {
493 if (!ok) {
494 return;
495 }
496 const path = __chunk_4.createPath(location);
497 const encodedPath = encodePath(basename + path);
498 const hashChanged = getHashPath() !== encodedPath;
499 if (hashChanged) {
500 // We cannot tell if a hashchange was caused by a REPLACE, so we'd
501 // rather setState here and ignore the hashchange. The caveat here
502 // is that other hash histories in the page will consider it a POP.
503 ignorePath = path;
504 replaceHashPath(encodedPath);
505 }
506 const prevIndex = allPaths.indexOf(__chunk_4.createPath(history.location));
507 if (prevIndex !== -1) {
508 allPaths[prevIndex] = path;
509 }
510 setState({ action, location });
511 });
512 };
513 const go = (n) => {
514 warning(canGoWithoutReload, 'Hash history go(n) causes a full page reload in this browser');
515 globalHistory.go(n);
516 };
517 const goBack = () => go(-1);
518 const goForward = () => go(1);
519 const checkDOMListeners = (win, delta) => {
520 listenerCount += delta;
521 if (listenerCount === 1) {
522 win.addEventListener(HashChangeEvent$1, handleHashChange);
523 }
524 else if (listenerCount === 0) {
525 win.removeEventListener(HashChangeEvent$1, handleHashChange);
526 }
527 };
528 const block = (prompt = '') => {
529 const unblock = transitionManager.setPrompt(prompt);
530 if (!isBlocked) {
531 checkDOMListeners(win, 1);
532 isBlocked = true;
533 }
534 return () => {
535 if (isBlocked) {
536 isBlocked = false;
537 checkDOMListeners(win, -1);
538 }
539 return unblock();
540 };
541 };
542 const listen = (listener) => {
543 const unlisten = transitionManager.appendListener(listener);
544 checkDOMListeners(win, 1);
545 return () => {
546 checkDOMListeners(win, -1);
547 unlisten();
548 };
549 };
550 const history = {
551 length: globalHistory.length,
552 action: 'POP',
553 location: initialLocation,
554 createHref,
555 push,
556 replace,
557 go,
558 goBack,
559 goForward,
560 block,
561 listen,
562 win: win
563 };
564 return history;
565};
566
567const getLocation = (location, root) => {
568 // Remove the root URL if found at beginning of string
569 const pathname = location.pathname.indexOf(root) == 0 ?
570 '/' + location.pathname.slice(root.length) :
571 location.pathname;
572 return Object.assign({}, location, { pathname });
573};
574const HISTORIES = {
575 'browser': createBrowserHistory,
576 'hash': createHashHistory
577};
578/**
579 * @name Router
580 * @module ionic
581 * @description
582 */
583class Router {
584 constructor(hostRef) {
585 __chunk_1.registerInstance(this, hostRef);
586 this.root = '/';
587 this.historyType = 'browser';
588 // A suffix to append to the page title whenever
589 // it's updated through RouteTitle
590 this.titleSuffix = '';
591 this.routeViewsUpdated = (options = {}) => {
592 if (this.history && options.scrollToId && this.historyType === 'browser') {
593 const elm = this.history.win.document.getElementById(options.scrollToId);
594 if (elm) {
595 return elm.scrollIntoView();
596 }
597 }
598 this.scrollTo(options.scrollTopOffset || this.scrollTopOffset);
599 };
600 this.isServer = __chunk_1.getContext(this, "isServer");
601 this.queue = __chunk_1.getContext(this, "queue");
602 }
603 componentWillLoad() {
604 this.history = HISTORIES[this.historyType](this.el.ownerDocument.defaultView);
605 this.history.listen((location) => {
606 location = getLocation(location, this.root);
607 this.location = location;
608 });
609 this.location = getLocation(this.history.location, this.root);
610 }
611 scrollTo(scrollToLocation) {
612 const history = this.history;
613 if (scrollToLocation == null || this.isServer || !history) {
614 return;
615 }
616 if (history.action === 'POP' && Array.isArray(history.location.scrollPosition)) {
617 return this.queue.write(() => {
618 if (history && history.location && Array.isArray(history.location.scrollPosition)) {
619 history.win.scrollTo(history.location.scrollPosition[0], history.location.scrollPosition[1]);
620 }
621 });
622 }
623 // okay, the frame has passed. Go ahead and render now
624 return this.queue.write(() => {
625 history.win.scrollTo(0, scrollToLocation);
626 });
627 }
628 render() {
629 if (!this.location || !this.history) {
630 return;
631 }
632 const state = {
633 historyType: this.historyType,
634 location: this.location,
635 titleSuffix: this.titleSuffix,
636 root: this.root,
637 history: this.history,
638 routeViewsUpdated: this.routeViewsUpdated
639 };
640 return (__chunk_1.h(__chunk_2.ActiveRouter.Provider, { state: state }, __chunk_1.h("slot", null)));
641 }
642 get el() { return __chunk_1.getElement(this); }
643}
644
645exports.stencil_router = Router;