UNPKG

@nativescript/core

Version:

A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.

652 lines • 23.3 kB
var FrameBase_1; import { NavigationType } from './frame-interfaces'; import { Page } from '../page'; import { CustomLayoutView, CSSType } from '../core/view'; import { Property } from '../core/properties'; import { Trace } from '../../trace'; import { frameStack, topmost as frameStackTopmost, _pushInFrameStack, _popFromFrameStack, _removeFromFrameStack } from './frame-stack'; import { viewMatchesModuleContext } from '../core/view/view-common'; import { getAncestor } from '../core/view-base'; import { Builder } from '../builder'; import { sanitizeModuleName } from '../../utils/common'; import { profile } from '../../profiling'; import { FRAME_SYMBOL } from './frame-helpers'; import { SharedTransition } from '../transition/shared-transition'; export { NavigationType } from './frame-interfaces'; function buildEntryFromArgs(arg) { let entry; if (typeof arg === 'string') { entry = { moduleName: arg, }; } else if (typeof arg === 'function') { entry = { create: arg, }; } else { entry = arg; } return entry; } let FrameBase = FrameBase_1 = class FrameBase extends CustomLayoutView { constructor() { super(...arguments); this._backStack = new Array(); this._navigationQueue = new Array(); this._animationInProgress = false; this._isInFrameStack = false; } static getFrameById(id) { return frameStack.find((frame) => frame.id && frame.id === id); } static topmost() { return frameStackTopmost(); } static goBack() { const top = FrameBase_1.topmost(); if (top && top.canGoBack()) { top.goBack(); return true; } else if (top) { let parentFrameCanGoBack = false; let parentFrame = getAncestor(top, 'Frame'); while (parentFrame && !parentFrameCanGoBack) { if (parentFrame && parentFrame.canGoBack()) { parentFrameCanGoBack = true; } else { parentFrame = getAncestor(parentFrame, 'Frame'); } } if (parentFrame && parentFrameCanGoBack) { parentFrame.goBack(); return true; } } if (frameStack.length > 1) { top._popFromFrameStack(); } return false; } /** * @private */ static reloadPage() { // Implemented in plat-specific file - only for android. } /** * @private */ static _stack() { return frameStack; } // TODO: Currently our navigation will not be synchronized in case users directly call native navigation methods like Activity.startActivity. _addChildFromBuilder(name, value) { throw new Error(`Frame should not have a view. Use 'defaultPage' property instead.`); } onLoaded() { super.onLoaded(); this._processNextNavigationEntry(); } canGoBack() { let backstack = this._backStack.length; let previousForwardNotInBackstack = false; this._navigationQueue.forEach((item) => { const entry = item.entry; const isBackNavigation = item.navigationType === NavigationType.back; if (isBackNavigation) { previousForwardNotInBackstack = false; if (!entry) { backstack--; } else { const backstackIndex = this._backStack.indexOf(entry); if (backstackIndex !== -1) { backstack = backstackIndex; } else { // NOTE: We don't search for entries in navigationQueue because there is no way for // developer to get reference to BackstackEntry unless transition is completed. // At that point the entry is put in the backstack array. // If we start to return Backstack entry from navigate method then // here we should check also navigationQueue as well. backstack--; } } } else if (entry.entry.clearHistory) { previousForwardNotInBackstack = false; backstack = 0; } else { backstack++; if (previousForwardNotInBackstack) { backstack--; } previousForwardNotInBackstack = entry.entry.backstackVisible === false; } }); // this is our first navigation which is not completed yet. if (this._navigationQueue.length > 0 && !this._currentEntry) { backstack--; } return backstack > 0; } /** * Navigates to the previous entry (if any) in the back stack. * @param to The backstack entry to navigate back to. */ goBack(backstackEntry) { if (Trace.isEnabled()) { Trace.write(`GO BACK`, Trace.categories.Navigation); } if (!this.canGoBack()) { return; } if (backstackEntry) { const index = this._backStack.indexOf(backstackEntry); if (index < 0) { return; } } const navigationContext = { entry: backstackEntry, isBackNavigation: true, navigationType: NavigationType.back, }; this._navigationQueue.push(navigationContext); this._processNextNavigationEntry(); } _removeEntry(removed) { const page = removed.resolvedPage; const frame = page.frame; if (frame) { frame._removeView(page); } else { page._tearDownUI(true); } removed.resolvedPage = null; } navigate(param) { if (Trace.isEnabled()) { Trace.write(`NAVIGATE`, Trace.categories.Navigation); } this._pushInFrameStack(); const entry = buildEntryFromArgs(param); const page = Builder.createViewFromEntry(entry); const backstackEntry = { entry: entry, resolvedPage: page, navDepth: undefined, fragmentTag: undefined, }; const navigationContext = { entry: backstackEntry, isBackNavigation: false, navigationType: NavigationType.forward, }; this._navigationQueue.push(navigationContext); this._processNextNavigationEntry(); } isCurrent(entry) { return this._currentEntry === entry; } setCurrent(entry, navigationType) { const newPage = entry.resolvedPage; // In case we navigated forward to a page that was in the backstack // with clearHistory: true if (!newPage.frame) { this._resolvedPage = newPage; this._addView(newPage); } this._currentEntry = entry; const isBack = navigationType === NavigationType.back; if (isBack) { this._pushInFrameStack(); } newPage.onNavigatedTo(isBack); this.notify({ eventName: Page.navigatedToEvent, object: this, isBack, entry, }); // Reset executing context after NavigatedTo is raised; // we do not want to execute two navigations in parallel in case // additional navigation is triggered from the NavigatedTo handler. this._executingContext = null; } _updateBackstack(entry, navigationType) { const isBack = navigationType === NavigationType.back; const isReplace = navigationType === NavigationType.replace; this.raiseCurrentPageNavigatedEvents(isBack); const current = this._currentEntry; // Do nothing for Hot Module Replacement if (isBack) { const index = this._backStack.indexOf(entry); this._backStack.splice(index + 1).forEach((e) => this._removeEntry(e)); this._backStack.pop(); } else if (!isReplace) { if (entry.entry.clearHistory) { this._backStack.forEach((e) => this._removeEntry(e)); this._backStack.length = 0; } else if (FrameBase_1._isEntryBackstackVisible(current)) { this._backStack.push(current); } } if (current && this._backStack.indexOf(current) < 0) { this._removeEntry(current); } } isNestedWithin(parentFrameCandidate) { let frameAncestor = this; while (frameAncestor) { frameAncestor = getAncestor(frameAncestor, FrameBase_1); if (frameAncestor === parentFrameCandidate) { return true; } } return false; } raiseCurrentPageNavigatedEvents(isBack) { const page = this.currentPage; if (page) { if (page.isLoaded) { // Forward navigation does not remove page from frame so we raise unloaded manually. page.callUnloaded(); } page.onNavigatedFrom(isBack); } } _processNavigationQueue(page) { if (this._navigationQueue.length === 0) { // This could happen when showing recreated page after activity has been destroyed. return; } const entry = this._navigationQueue[0].entry; const currentNavigationPage = entry.resolvedPage; if (page !== currentNavigationPage) { // If the page is not the one that requested navigation - skip it. return; } // remove completed operation. this._navigationQueue.shift(); this._processNextNavigationEntry(); this._updateActionBar(); } _findEntryForTag(fragmentTag) { let entry; if (this._currentEntry && this._currentEntry.fragmentTag === fragmentTag) { entry = this._currentEntry; } else { entry = this._backStack.find((value) => value.fragmentTag === fragmentTag); // on API 26 fragments are recreated lazily after activity is destroyed. if (!entry) { const navigationItem = this._navigationQueue.find((value) => value.entry.fragmentTag === fragmentTag); entry = navigationItem ? navigationItem.entry : undefined; } } return entry; } navigationQueueIsEmpty() { return this._navigationQueue.length === 0; } static _isEntryBackstackVisible(entry) { if (!entry) { return false; } const backstackVisibleValue = entry.entry.backstackVisible; const backstackHidden = backstackVisibleValue !== undefined && !backstackVisibleValue; return !backstackHidden; } _updateActionBar(page, disableNavBarAnimation) { //Trace.write("calling _updateActionBar on Frame", Trace.categories.Navigation); } _processNextNavigationEntry() { if (!this.isLoaded || this._executingContext) { return; } if (this._navigationQueue.length > 0) { const navigationContext = this._navigationQueue[0]; const isBackNavigation = navigationContext.navigationType === NavigationType.back; if (isBackNavigation) { this.performGoBack(navigationContext); } else { this.performNavigation(navigationContext); } } } performNavigation(navigationContext) { this._executingContext = navigationContext; const backstackEntry = navigationContext.entry; const isBackNavigation = navigationContext.navigationType === NavigationType.back; this._onNavigatingTo(backstackEntry, isBackNavigation); const navigationTransition = this._getNavigationTransition(backstackEntry.entry); if (navigationTransition?.instance) { const state = SharedTransition.getState(navigationTransition?.instance.id); SharedTransition.updateState(navigationTransition?.instance.id, { // Allow setting custom page context to override default (from) page // helpful for deeply nested frame navigation setups (eg: Nested Tab Navigation) // when sharing elements in this condition, the (from) page would // get overridden on each frame preventing shared element matching page: state?.page || this.currentPage, toPage: this, }); } this._navigateCore(backstackEntry); } performGoBack(navigationContext) { let backstackEntry = navigationContext.entry; const backstack = this._backStack; if (!backstackEntry) { backstackEntry = backstack[backstack.length - 1]; navigationContext.entry = backstackEntry; } this._executingContext = navigationContext; this._onNavigatingTo(backstackEntry, true); this._goBackCore(backstackEntry); } _goBackCore(backstackEntry) { if (Trace.isEnabled()) { Trace.write(`GO BACK CORE(${this._backstackEntryTrace(backstackEntry)}); currentPage: ${this.currentPage}`, Trace.categories.Navigation); } } _navigateCore(backstackEntry) { if (Trace.isEnabled()) { Trace.write(`NAVIGATE CORE(${this._backstackEntryTrace(backstackEntry)}); currentPage: ${this.currentPage}`, Trace.categories.Navigation); } } _onNavigatingTo(backstackEntry, isBack) { if (this.currentPage) { this.currentPage.onNavigatingFrom(isBack); } backstackEntry.resolvedPage.onNavigatingTo(backstackEntry.entry.context, isBack, backstackEntry.entry.bindingContext); this.notify({ eventName: Page.navigatingToEvent, object: this, isBack, entry: backstackEntry, fromEntry: this._currentEntry, }); } get animated() { return this._animated; } set animated(value) { this._animated = value; } get transition() { return this._transition; } set transition(value) { this._transition = value; } get backStack() { return this._backStack.slice(); } get currentPage() { if (this._currentEntry) { return this._currentEntry.resolvedPage; } return null; } get currentEntry() { if (this._currentEntry) { return this._currentEntry.entry; } return null; } _pushInFrameStackRecursive() { this._pushInFrameStack(); // make sure nested frames order is kept intact i.e. the nested one should always be on top; // see https://github.com/NativeScript/nativescript-angular/issues/1596 for more information const framesToPush = []; for (const frame of frameStack) { if (frame.isNestedWithin(this)) { framesToPush.push(frame); } } for (const frame of framesToPush) { frame._pushInFrameStack(); } } _pushInFrameStack() { _pushInFrameStack(this); } _popFromFrameStack() { _popFromFrameStack(this); } _removeFromFrameStack() { _removeFromFrameStack(this); } _dialogClosed() { // No super call as we do not support nested frames to clean up this._removeFromFrameStack(); } _onRootViewReset() { super._onRootViewReset(); this._removeFromFrameStack(); } get _childrenCount() { if (this._resolvedPage) { return 1; } return 0; } eachChildView(callback) { const page = this._resolvedPage; if (page) { callback(page); } } _getIsAnimatedNavigation(entry) { if (entry && entry.animated !== undefined) { return entry.animated; } if (this.animated !== undefined) { return this.animated; } return FrameBase_1.defaultAnimatedNavigation; } _getNavigationTransition(entry) { if (entry) { if (global.isIOS && entry.transitioniOS !== undefined) { return entry.transitioniOS; } if (global.isAndroid && entry.transitionAndroid !== undefined) { return entry.transitionAndroid; } if (entry.transition !== undefined) { return entry.transition; } } if (this.transition !== undefined) { return this.transition; } return FrameBase_1.defaultTransition; } get navigationBarHeight() { return 0; } _getNavBarVisible(page) { throw new Error(); } // We don't need to put Page as visual child. Don't call super. _addViewToNativeVisualTree(child) { return true; } // We don't need to put Page as visual child. Don't call super. _removeViewFromNativeVisualTree(child) { child._isAddedToNativeVisualTree = false; } _printFrameBackStack() { const length = this.backStack.length; let i = length - 1; console.log(`Frame Back Stack: `); while (i >= 0) { const backstackEntry = this.backStack[i--]; console.log(`\t${backstackEntry.resolvedPage}`); } } _backstackEntryTrace(b) { let result = `${b.resolvedPage}`; const backstackVisible = FrameBase_1._isEntryBackstackVisible(b); if (!backstackVisible) { result += ` | INVISIBLE`; } if (b.entry.clearHistory) { result += ` | CLEAR HISTORY`; } const animated = this._getIsAnimatedNavigation(b.entry); if (!animated) { result += ` | NOT ANIMATED`; } const t = this._getNavigationTransition(b.entry); if (t) { result += ` | Transition[${JSON.stringify(t)}]`; } return result; } _onLivesync(context) { if (super._onLivesync(context)) { return true; } // Fallback if (!context) { return this._onLivesyncWithoutContext(); } return false; } _handleLivesync(context) { if (super._handleLivesync(context)) { return true; } // Handle markup/script changes in currentPage if (this.currentPage && viewMatchesModuleContext(this.currentPage, context, ['markup', 'script'])) { Trace.write(`Change Handled: Replacing page ${context.path}`, Trace.categories.Livesync); // Replace current page with a default fade transition this.replacePage({ moduleName: context.path, transition: { name: 'fade', duration: 100, }, }); return true; } return false; } _onLivesyncWithoutContext() { // Reset activity/window content when: // + Changes are not handled on View // + There is no ModuleContext if (Trace.isEnabled()) { Trace.write(`${this}._onLivesyncWithoutContext()`, Trace.categories.Livesync); } if (!this._currentEntry || !this._currentEntry.entry) { return false; } const currentEntry = this._currentEntry.entry; // If create returns the same page instance we can't recreate it. // Instead of navigation set activity content. // This could happen if current page was set in XML as a Page instance. if (currentEntry.create) { const page = currentEntry.create(); if (page === this.currentPage) { return false; } } // Replace current page with a default fade transition this.replacePage({ moduleName: currentEntry.moduleName, create: currentEntry.create, transition: { name: 'fade', duration: 100, }, }); return true; } replacePage(entry) { const currentBackstackEntry = this._currentEntry; if (typeof entry === 'string') { const contextModuleName = sanitizeModuleName(entry); entry = { moduleName: contextModuleName }; } const newPage = Builder.createViewFromEntry(entry); const newBackstackEntry = { entry: Object.assign({}, currentBackstackEntry.entry, entry), resolvedPage: newPage, navDepth: currentBackstackEntry.navDepth, fragmentTag: currentBackstackEntry.fragmentTag, frameId: currentBackstackEntry.frameId, }; const navigationContext = { entry: newBackstackEntry, isBackNavigation: false, navigationType: NavigationType.replace, }; this._navigationQueue.push(navigationContext); this._processNextNavigationEntry(); } }; FrameBase.androidOptionSelectedEvent = 'optionSelected'; FrameBase.defaultAnimatedNavigation = true; __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], FrameBase.prototype, "onLoaded", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", void 0) ], FrameBase.prototype, "performNavigation", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", void 0) ], FrameBase.prototype, "performGoBack", null); FrameBase = FrameBase_1 = __decorate([ CSSType('Frame') ], FrameBase); export { FrameBase }; // Mark as a Frame with an unique Symbol FrameBase.prototype[FRAME_SYMBOL] = true; export function getFrameById(id) { console.log('getFrameById() is deprecated. Use Frame.getFrameById() instead.'); return FrameBase.getFrameById(id); } export function topmost() { console.log('topmost() is deprecated. Use Frame.topmost() instead.'); return FrameBase.topmost(); } export function goBack() { console.log('goBack() is deprecated. Use Frame.goBack() instead.'); return FrameBase.goBack(); } export function _stack() { console.log('_stack() is deprecated. Use Frame._stack() instead.'); return FrameBase._stack(); } export const defaultPage = new Property({ name: 'defaultPage', valueChanged: (frame, oldValue, newValue) => { frame.navigate({ moduleName: newValue }); }, }); defaultPage.register(FrameBase); export const actionBarVisibilityProperty = new Property({ name: 'actionBarVisibility', defaultValue: 'auto', affectsLayout: global.isIOS }); actionBarVisibilityProperty.register(FrameBase); //# sourceMappingURL=frame-common.js.map