From fd228dbff50f5eef7da30c2eab0bef8a07db3d9d Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 17 Mar 2023 22:54:33 -0700 Subject: [PATCH] chore: cleanup --- apps/toolbox/src/pages/transitions.ts | 12 +++- packages/core/ui/core/view/index.ios.ts | 43 ++++++++------ packages/core/ui/core/view/view-common.ts | 26 ++++++--- packages/core/ui/frame/index.ios.ts | 58 +++++++++++-------- .../ui/transition/modal-transition.ios.ts | 5 +- .../core/ui/transition/page-transition.ios.ts | 4 +- .../core/ui/transition/shared-transition.ts | 30 ++++++++-- 7 files changed, 115 insertions(+), 63 deletions(-) diff --git a/apps/toolbox/src/pages/transitions.ts b/apps/toolbox/src/pages/transitions.ts index 3b3f5157e..8539b1223 100644 --- a/apps/toolbox/src/pages/transitions.ts +++ b/apps/toolbox/src/pages/transitions.ts @@ -15,7 +15,11 @@ export class TransitionsModel extends Observable { page.frame.navigate({ moduleName: `pages/transitions/transitions-detail`, transition: SharedTransition.custom(new PageTransition(), { - interactiveDismissal: true, + interactive: { + dismiss: { + finishThreshold: 0.5, + }, + }, // toPageStart: { // duration: 400, // }, @@ -29,7 +33,11 @@ export class TransitionsModel extends Observable { openModal() { page.showModal('pages/transitions/transitions-modal', { transition: SharedTransition.custom(new ModalTransition(), { - interactiveDismissal: true, + interactive: { + dismiss: { + finishThreshold: 0.5, + }, + }, toPageStart: { y: 200, // duration: 400, diff --git a/packages/core/ui/core/view/index.ios.ts b/packages/core/ui/core/view/index.ios.ts index 833da7465..0ca6a1103 100644 --- a/packages/core/ui/core/view/index.ios.ts +++ b/packages/core/ui/core/view/index.ios.ts @@ -6,6 +6,7 @@ import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUser import { ShowModalOptions, hiddenProperty } from '../view-base'; import { Trace } from '../../../trace'; import { layout, iOSNativeHelper } from '../../../utils'; +import { isNumber } from '../../../utils/types'; import { IOSHelper } from './view-helper'; import { ios as iosBackground, Background } from '../../styling/background'; import { perspectiveProperty, visibilityProperty, opacityProperty, rotateProperty, rotateXProperty, rotateYProperty, scaleXProperty, scaleYProperty, translateXProperty, translateYProperty, zIndexProperty, backgroundInternalProperty, clipPathProperty } from '../../styling/style-properties'; @@ -32,7 +33,7 @@ const majorVersion = iOSNativeHelper.MajorVersion; export class View extends ViewCommon implements ViewDefinition { nativeViewProtected: UIView; viewController: UIViewController; - transitionIteractiveCtrl: UIPercentDrivenInteractiveTransition; + transitionInteractiveCtrl: UIPercentDrivenInteractiveTransition; private _popoverPresentationDelegate: IOSHelper.UIPopoverPresentationControllerDelegateImp; private _adaptivePresentationDelegate: IOSHelper.UIAdaptivePresentationControllerDelegateImp; private _transitioningDelegate: UIViewControllerTransitioningDelegateImpl; @@ -473,7 +474,7 @@ export class View extends ViewCommon implements ViewDefinition { this._transitioningDelegate = UIViewControllerTransitioningDelegateImpl.initWithOwner(new WeakRef(options.transition.instance), new WeakRef(this)); controller.transitioningDelegate = this._transitioningDelegate; const transitionState = SharedTransition.getState(options.transition.instance.id); - if (transitionState?.interactiveDismissal) { + if (transitionState?.interactive?.dismiss) { // interactive transitions via gestures // TODO - these could be typed as: boolean | (view: View) => void // to allow users to define their own custom gesture dismissals @@ -563,11 +564,13 @@ export class View extends ViewCommon implements ViewDefinition { private _interactiveDismissGestureHandler(args: PanGestureEventData) { if (args?.ios?.view) { - this.interactiveDismissGestureBegan = true; - this.interactiveDismissGestureCancelled = false; - const percent = args.deltaY / (args.ios.view.bounds.size.height / 2); + this._updateInteractiveTransition({ + began: true, + cancelled: false, + }); + const percent = this.interactiveTransition?.options.percentFormula ? this.interactiveTransition?.options.percentFormula(args) : args.deltaY / (args.ios.view.bounds.size.height / 2); if (SharedTransition.DEBUG) { - console.log('interactive dismissal pan:', percent); + console.log('Interactive dismissal percentage:', percent); } switch (args.state) { case GestureStateTypes.began: @@ -576,21 +579,23 @@ export class View extends ViewCommon implements ViewDefinition { } break; case GestureStateTypes.changed: - // TODO: allow customization of threshold if (percent < 1) { - if (this.transitionIteractiveCtrl) { - this.transitionIteractiveCtrl.updateInteractiveTransition(percent); + if (this.transitionInteractiveCtrl) { + this.transitionInteractiveCtrl.updateInteractiveTransition(percent); } } break; case GestureStateTypes.cancelled: case GestureStateTypes.ended: - if (this.transitionIteractiveCtrl) { - if (percent > 0.5) { - this.transitionIteractiveCtrl.finishInteractiveTransition(); + if (this.transitionInteractiveCtrl) { + const finishThreshold = isNumber(this.interactiveTransition?.options?.finishThreshold) ? this.interactiveTransition?.options?.finishThreshold : 0.5; + if (percent > finishThreshold) { + this.transitionInteractiveCtrl.finishInteractiveTransition(); } else { - this.interactiveDismissGestureCancelled = true; - this.transitionIteractiveCtrl.cancelInteractiveTransition(); + this._updateInteractiveTransition({ + cancelled: true, + }); + this.transitionInteractiveCtrl.cancelInteractiveTransition(); } } break; @@ -619,9 +624,9 @@ export class View extends ViewCommon implements ViewDefinition { } parentController.dismissViewControllerAnimatedCompletion(animated, () => { - if (!this.interactiveDismissGestureCancelled) { + if (!this.interactiveTransition?.cancelled) { this._transitioningDelegate = null; - this.transitionIteractiveCtrl = null; + this.transitionInteractiveCtrl = null; this.off('pan', this._interactiveDismissGesture); if (this._modalAnimatedOptions) { this._modalAnimatedOptions.pop(); @@ -1013,9 +1018,9 @@ class UIViewControllerTransitioningDelegateImpl extends NSObject implements UIVi if (owner?.iosInteractionDismiss) { const ownerView = this.ownerView?.deref(); if (ownerView) { - if (ownerView.interactiveDismissGestureBegan) { - ownerView.transitionIteractiveCtrl = owner.iosInteractionDismiss(animator); - return ownerView.transitionIteractiveCtrl; + if (ownerView.interactiveTransition?.began) { + ownerView.transitionInteractiveCtrl = owner.iosInteractionDismiss(animator); + return ownerView.transitionInteractiveCtrl; } } } diff --git a/packages/core/ui/core/view/view-common.ts b/packages/core/ui/core/view/view-common.ts index a8648792a..6c915c87d 100644 --- a/packages/core/ui/core/view/view-common.ts +++ b/packages/core/ui/core/view/view-common.ts @@ -27,7 +27,7 @@ import { AccessibilityEventOptions, AccessibilityLiveRegion, AccessibilityRole, import { accessibilityHintProperty, accessibilityIdentifierProperty, accessibilityLabelProperty, accessibilityValueProperty, accessibilityIgnoresInvertColorsProperty } from '../../../accessibility/accessibility-properties'; import { accessibilityBlurEvent, accessibilityFocusChangedEvent, accessibilityFocusEvent, accessibilityPerformEscapeEvent, getCurrentFontScale } from '../../../accessibility'; import { CSSShadow } from '../../styling/css-shadow'; -import { SharedTransition } from '../../transition/shared-transition'; +import { SharedTransition, SharedTransitionInteractiveOptions } from '../../transition/shared-transition'; // helpers (these are okay re-exported here) export * from './view-helper'; @@ -69,6 +69,8 @@ export function PseudoClassHandler(...pseudoClasses: string[]): MethodDecorator export const _rootModalViews = new Array(); +type InteractiveTransitionState = { began?: boolean; cancelled?: boolean; options?: SharedTransitionInteractiveOptions }; + export abstract class ViewCommon extends ViewBase implements ViewDefinition { public static layoutChangedEvent = 'layoutChanged'; public static shownModallyEvent = 'shownModally'; @@ -94,8 +96,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { public _modalParent: ViewCommon; private _modalContext: any; private _modal: ViewCommon; - interactiveDismissGestureBegan = false; - interactiveDismissGestureCancelled = false; + interactiveTransition: InteractiveTransitionState; private _measuredWidth: number; private _measuredHeight: number; @@ -249,6 +250,13 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { return false; } + _updateInteractiveTransition(state: InteractiveTransitionState) { + this.interactiveTransition = { + ...(this.interactiveTransition || {}), + ...state, + }; + } + _setupAsRootView(context: any): void { super._setupAsRootView(context); if (!this._styleScope) { @@ -415,14 +423,16 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { }; const whenClosedCallback = () => { - if (this.interactiveDismissGestureBegan) { - this.interactiveDismissGestureBegan = false; - if (!this.interactiveDismissGestureCancelled) { + if (this.interactiveTransition?.began) { + this._updateInteractiveTransition({ + began: false, + }); + if (!this.interactiveTransition?.cancelled) { cleanupModalViews(); } } - if (!this.interactiveDismissGestureCancelled) { + if (!this.interactiveTransition?.cancelled) { if (typeof options.closeCallback === 'function') { options.closeCallback.apply(undefined, originalArgs); } @@ -431,7 +441,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { } }; - if (!this.interactiveDismissGestureBegan) { + if (!this.interactiveTransition?.began) { cleanupModalViews(); } diff --git a/packages/core/ui/frame/index.ios.ts b/packages/core/ui/frame/index.ios.ts index 6c851b0ec..7957baeff 100644 --- a/packages/core/ui/frame/index.ios.ts +++ b/packages/core/ui/frame/index.ios.ts @@ -30,7 +30,7 @@ let navDepth = -1; export class Frame extends FrameBase { viewController: UINavigationControllerImpl; _animatedDelegate = UINavigationControllerAnimatedDelegate.initWithOwner(new WeakRef(this)); - transitionIteractiveCtrl: UIPercentDrivenInteractiveTransition; + transitionInteractiveCtrl: UIPercentDrivenInteractiveTransition; public _ios: iOSFrame; private _interactiveDismissGesture: (args: PanGestureEventData) => void; @@ -106,10 +106,12 @@ export class Frame extends FrameBase { this._ios.controller.delegate = this._animatedDelegate; viewController[DELEGATE] = this._animatedDelegate; const transitionState = SharedTransition.getState(navigationTransition.instance.id); - if (transitionState?.interactiveDismissal) { + if (transitionState?.interactive?.dismiss) { + this._updateInteractiveTransition({ + options: transitionState?.interactive?.dismiss, + }); // interactive transitions via gestures - // TODO - these could be typed as: boolean | (view: View) => void - // to allow users to define their own custom gesture dismissals + // TODO - allow users to define their own custom gesture dismissals this._interactiveDismissGesture = this._interactiveDismissGestureHandler.bind(this); this.on('pan', this._interactiveDismissGesture); } @@ -198,35 +200,42 @@ export class Frame extends FrameBase { private _interactiveDismissGestureHandler(args: PanGestureEventData) { if (args?.ios?.view) { - this.interactiveDismissGestureBegan = true; - this.interactiveDismissGestureCancelled = false; - const percent = args.deltaX / (args.ios.view.bounds.size.width / 2); + this._updateInteractiveTransition({ + began: true, + cancelled: false, + }); + const percent = this.interactiveTransition?.options.percentFormula ? this.interactiveTransition?.options.percentFormula(args) : args.deltaX / (args.ios.view.bounds.size.width / 2); if (SharedTransition.DEBUG) { - console.log('interactive dismissal pan:', percent); + console.log('Interactive dismissal percentage:', percent); } switch (args.state) { case GestureStateTypes.began: // const navigationContext = this.parent.page.frame._navigationQueue[0]; // this.performGoBack(navigationContext); - this._ios.controller.popViewControllerAnimated(true); + // this._ios.controller.popViewControllerAnimated(true); + this._goBackCore(this.backStack.slice(-1)[0]); break; case GestureStateTypes.changed: - // TODO: allow customization of threshold if (percent < 1) { - if (this.transitionIteractiveCtrl) { - this.transitionIteractiveCtrl.updateInteractiveTransition(percent); + if (this.transitionInteractiveCtrl) { + this.transitionInteractiveCtrl.updateInteractiveTransition(percent); } } break; case GestureStateTypes.cancelled: case GestureStateTypes.ended: - if (this.transitionIteractiveCtrl) { - if (percent > 0.5) { - this.transitionIteractiveCtrl.finishInteractiveTransition(); + if (this.transitionInteractiveCtrl) { + const finishThreshold = isNumber(this.interactiveTransition?.options?.finishThreshold) ? this.interactiveTransition?.options?.finishThreshold : 0.5; + if (percent > finishThreshold) { + this.callUnloaded(); + this._tearDownUI(true); + this.transitionInteractiveCtrl.finishInteractiveTransition(); } else { - this.interactiveDismissGestureCancelled = true; - this.transitionIteractiveCtrl.cancelInteractiveTransition(); + this._updateInteractiveTransition({ + cancelled: true, + }); + this.transitionInteractiveCtrl.cancelInteractiveTransition(); } } break; @@ -253,7 +262,7 @@ export class Frame extends FrameBase { const controller = backstackEntry.resolvedPage.ios; const animated = this._currentEntry ? this._getIsAnimatedNavigation(this._currentEntry.entry) : false; - if (!this.interactiveDismissGestureCancelled) { + if (!this.interactiveTransition?.cancelled) { this._updateActionBar(backstackEntry.resolvedPage); } if (Trace.isEnabled()) { @@ -475,9 +484,9 @@ class UINavigationControllerAnimatedDelegate extends NSObject implements UINavig navigationControllerInteractionControllerForAnimationController(navigationController: UINavigationController, animationController: UIViewControllerAnimatedTransitioning): UIViewControllerInteractiveTransitioning { const owner = this.owner?.deref(); if (owner) { - if (owner.interactiveDismissGestureBegan) { - owner.transitionIteractiveCtrl = PercentInteractiveController.initWithOwner(new WeakRef(this.transition)); - return owner.transitionIteractiveCtrl; + if (owner.interactiveTransition?.began) { + owner.transitionInteractiveCtrl = PercentInteractiveController.initWithOwner(new WeakRef(this.transition)); + return owner.transitionInteractiveCtrl; } } @@ -500,12 +509,11 @@ class PercentInteractiveController extends UIPercentDrivenInteractiveTransition } startInteractiveTransition(transitionContext: UIViewControllerContextTransitioning) { - console.log('startInteractiveTransition'); + // console.log('startInteractiveTransition'); this.transitionContext = transitionContext; } updateInteractiveTransition(percentComplete: number) { - console.log('percentComplete:', percentComplete); const owner: any = this.owner?.deref(); if (owner) { if (!this.started) { @@ -534,7 +542,7 @@ class PercentInteractiveController extends UIPercentDrivenInteractiveTransition } cancelInteractiveTransition() { - console.log('cancelInteractiveTransition'); + // console.log('cancelInteractiveTransition'); const owner: any = this.owner?.deref(); if (owner && this.started) { const state = SharedTransition.getState(owner.id); @@ -562,7 +570,7 @@ class PercentInteractiveController extends UIPercentDrivenInteractiveTransition } finishInteractiveTransition() { - console.log('finishInteractiveTransition'); + // console.log('finishInteractiveTransition'); const owner: any = this.owner?.deref(); if (owner && this.started) { if (this.backgroundAnimation) { diff --git a/packages/core/ui/transition/modal-transition.ios.ts b/packages/core/ui/transition/modal-transition.ios.ts index d273c4c22..58df067d8 100644 --- a/packages/core/ui/transition/modal-transition.ios.ts +++ b/packages/core/ui/transition/modal-transition.ios.ts @@ -65,7 +65,6 @@ class PercentInteractiveController extends UIPercentDrivenInteractiveTransition } updateInteractiveTransition(percentComplete: number) { - // console.log('percentComplete:', percentComplete); const owner = this.owner?.deref(); if (owner) { if (!this.started) { @@ -185,7 +184,7 @@ class ModalTransitionController extends NSObject implements UIViewControllerAnim const { sharedElements, presented } = SharedTransition.getSharedElements(state.page, state.toPage); if (SharedTransition.DEBUG) { - console.log(' '); + console.log(' ModalTransition: Present'); console.log( `1. Found sharedTransitionTags to animate:`, sharedElements.map((v) => v.sharedTransitionTag) @@ -362,7 +361,7 @@ class ModalTransitionController extends NSObject implements UIViewControllerAnim // console.log('transitionContext.containerView.subviews.count:', transitionContext.containerView.subviews.count); if (SharedTransition.DEBUG) { - console.log(' '); + console.log(' ModalTransition: Dismiss'); console.log( `1. Dismiss sharedTransitionTags to animate:`, owner.sharedElements.presented.map((p) => p.view.sharedTransitionTag) diff --git a/packages/core/ui/transition/page-transition.ios.ts b/packages/core/ui/transition/page-transition.ios.ts index 66b451adb..26b4f2952 100644 --- a/packages/core/ui/transition/page-transition.ios.ts +++ b/packages/core/ui/transition/page-transition.ios.ts @@ -38,7 +38,7 @@ export class PageTransition extends Transition { const { sharedElements, presented } = SharedTransition.getSharedElements(state.page, state.toPage); if (SharedTransition.DEBUG) { - console.log(' '); + console.log(' PageTransition: Push'); console.log( `1. Found sharedTransitionTags to animate:`, sharedElements.map((v) => v.sharedTransitionTag) @@ -216,7 +216,7 @@ export class PageTransition extends Transition { // console.log('transitionContext.containerView.subviews.count:', transitionContext.containerView.subviews.count); if (SharedTransition.DEBUG) { - console.log(' '); + console.log(' PageTransition: Pop'); console.log( `1. Dismiss sharedTransitionTags to animate:`, this.sharedElements.presented.map((p) => p.view.sharedTransitionTag) diff --git a/packages/core/ui/transition/shared-transition.ts b/packages/core/ui/transition/shared-transition.ts index d9f33e241..ace2f4390 100644 --- a/packages/core/ui/transition/shared-transition.ts +++ b/packages/core/ui/transition/shared-transition.ts @@ -1,6 +1,7 @@ import type { Transition } from '.'; import { querySelectorAll, ViewBase } from '../core/view-base'; import type { View } from '../core/view'; +import type { PanGestureEventData } from '../gestures'; export const DEFAULT_DURATION = 0.35; export const DEFAULT_SPRING = { @@ -12,19 +13,40 @@ export enum SharedTransitionAnimationType { present, dismiss, } +export interface SharedTransitionInteractiveOptions { + /** + * When the pan exceeds this percentage and you let go, finish the transition. + * Default 0.5 + */ + finishThreshold?: number; + /** + * You can create your own percent formula used for determing the interactive value. + * By default, we handle this via a formula like this for an interactive page back transition: + * - return eventData.deltaX / (eventData.ios.view.bounds.size.width / 2); + * @param eventData PanGestureEventData + * @returns Should return a percentage value + */ + percentFormula?: (eventData: PanGestureEventData) => number; +} export interface SharedTransitionConfig { /** - * Page which will start the transition + * Page which will start the transition. */ page?: ViewBase; /** - * Preconfigured transition or your own custom configured one + * Preconfigured transition or your own custom configured one. */ instance?: Transition; /** - * Whether you want to allow interactive dismissal + * Interactive transition settings. (iOS only at the moment) */ - interactiveDismissal?: boolean; + interactive?: { + /** + * Whether you want to allow interactive dismissal. + * Defaults to using 'pan' gesture for dismissal however you can customize your own. + */ + dismiss?: SharedTransitionInteractiveOptions; + }; /** * View settings to start your transition. */