UNPKG

@zoff-tech/zt-bottom-drawer

Version:
1,530 lines (1,522 loc) 80.8 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); const index = require('./index-d7fcb92f.js'); const cubicBezier = require('@utils/animation/cubic-bezier'); const helpers = require('@utils/helpers'); require('@utils/logging'); const transition = require('@utils/transition'); const ionicGlobal = require('./ionic-global-70a62cb2.js'); const frameworkDelegate = require('@utils/framework-delegate'); const index$1 = require('./index-9564fe2f.js'); const helpers$1 = require('./helpers-ad4a8d0f.js'); const index$2 = require('./index-0c0ba05f.js'); /*! * (C) Ionic http://ionicframework.com - MIT License */ const LIFECYCLE_WILL_LEAVE = 'ionViewWillLeave'; const LIFECYCLE_DID_LEAVE = 'ionViewDidLeave'; const LIFECYCLE_WILL_UNLOAD = 'ionViewWillUnload'; /*! * (C) Ionic http://ionicframework.com - MIT License */ const VIEW_STATE_NEW = 1; const VIEW_STATE_ATTACHED = 2; const VIEW_STATE_DESTROYED = 3; // TODO(FW-2832): types class ViewController { constructor(component, params) { this.component = component; this.params = params; this.state = VIEW_STATE_NEW; } async init(container) { this.state = VIEW_STATE_ATTACHED; if (!this.element) { const component = this.component; this.element = await frameworkDelegate.attachComponent(this.delegate, container, component, ['ion-page', 'ion-page-invisible'], this.params); } } /** * DOM WRITE */ _destroy() { helpers.assert(this.state !== VIEW_STATE_DESTROYED, 'view state must be ATTACHED'); const element = this.element; if (element) { if (this.delegate) { this.delegate.removeViewFromDom(element.parentElement, element); } else { element.remove(); } } this.nav = undefined; this.state = VIEW_STATE_DESTROYED; } } const matches = (view, id, params) => { if (!view) { return false; } if (view.component !== id) { return false; } return helpers.shallowEqualStringMap(view.params, params); }; const convertToView = (page, params) => { if (!page) { return null; } if (page instanceof ViewController) { return page; } return new ViewController(page, params); }; const convertToViews = (pages) => { return pages .map((page) => { if (page instanceof ViewController) { return page; } if ('component' in page) { return convertToView(page.component, page.componentProps === null ? undefined : page.componentProps); } return convertToView(page, undefined); }) .filter((v) => v !== null); }; const navCss = ":host{left:0;right:0;top:0;bottom:0;position:absolute;contain:layout size style;overflow:hidden;z-index:0}"; const Nav = class { constructor(hostRef) { index.registerInstance(this, hostRef); this.ionNavWillLoad = index.createEvent(this, "ionNavWillLoad", 7); this.ionNavWillChange = index.createEvent(this, "ionNavWillChange", 3); this.ionNavDidChange = index.createEvent(this, "ionNavDidChange", 3); this.transInstr = []; this.animationEnabled = true; this.useRouter = false; this.isTransitioning = false; this.destroyed = false; this.views = []; this.didLoad = false; this.delegate = undefined; this.swipeGesture = undefined; this.animated = true; this.animation = undefined; this.rootParams = undefined; this.root = undefined; } swipeGestureChanged() { if (this.gesture) { this.gesture.enable(this.swipeGesture === true); } } rootChanged() { if (this.root === undefined) { return; } if (this.didLoad === false) { /** * If the component has not loaded yet, we can skip setting up the root component. * It will be called when `componentDidLoad` fires. */ return; } if (!this.useRouter) { if (this.root !== undefined) { this.setRoot(this.root, this.rootParams); } } } componentWillLoad() { this.useRouter = document.querySelector('ion-router') !== null && this.el.closest('[no-router]') === null; if (this.swipeGesture === undefined) { const mode = ionicGlobal.getIonMode(this); this.swipeGesture = ionicGlobal.config.getBoolean('swipeBackEnabled', mode === 'ios'); } this.ionNavWillLoad.emit(); } async componentDidLoad() { // We want to set this flag before any watch callbacks are manually called this.didLoad = true; this.rootChanged(); this.gesture = (await Promise.resolve().then(function () { return require('./swipe-back-3ac5d499.js'); })).createSwipeBackGesture(this.el, this.canStart.bind(this), this.onStart.bind(this), this.onMove.bind(this), this.onEnd.bind(this)); this.swipeGestureChanged(); } connectedCallback() { this.destroyed = false; } disconnectedCallback() { for (const view of this.views) { transition.lifecycle(view.element, LIFECYCLE_WILL_UNLOAD); view._destroy(); } // Release swipe back gesture and transition. if (this.gesture) { this.gesture.destroy(); this.gesture = undefined; } this.transInstr.length = 0; this.views.length = 0; this.destroyed = true; } /** * Push a new component onto the current navigation stack. Pass any additional * information along as an object. This additional information is accessible * through NavParams. * * @param component The component to push onto the navigation stack. * @param componentProps Any properties of the component. * @param opts The navigation options. * @param done The transition complete function. */ push(component, componentProps, opts, done) { return this.insert(-1, component, componentProps, opts, done); } /** * Inserts a component into the navigation stack at the specified index. * This is useful to add a component at any point in the navigation stack. * * @param insertIndex The index to insert the component at in the stack. * @param component The component to insert into the navigation stack. * @param componentProps Any properties of the component. * @param opts The navigation options. * @param done The transition complete function. */ insert(insertIndex, component, componentProps, opts, done) { return this.insertPages(insertIndex, [{ component, componentProps }], opts, done); } /** * Inserts an array of components into the navigation stack at the specified index. * The last component in the array will become instantiated as a view, and animate * in to become the active view. * * @param insertIndex The index to insert the components at in the stack. * @param insertComponents The components to insert into the navigation stack. * @param opts The navigation options. * @param done The transition complete function. */ insertPages(insertIndex, insertComponents, opts, done) { return this.queueTrns({ insertStart: insertIndex, insertViews: insertComponents, opts, }, done); } /** * Pop a component off of the navigation stack. Navigates back from the current * component. * * @param opts The navigation options. * @param done The transition complete function. */ pop(opts, done) { return this.removeIndex(-1, 1, opts, done); } /** * Pop to a specific index in the navigation stack. * * @param indexOrViewCtrl The index or view controller to pop to. * @param opts The navigation options. * @param done The transition complete function. */ popTo(indexOrViewCtrl, opts, done) { const ti = { removeStart: -1, removeCount: -1, opts, }; if (typeof indexOrViewCtrl === 'object' && indexOrViewCtrl.component) { ti.removeView = indexOrViewCtrl; ti.removeStart = 1; } else if (typeof indexOrViewCtrl === 'number') { ti.removeStart = indexOrViewCtrl + 1; } return this.queueTrns(ti, done); } /** * Navigate back to the root of the stack, no matter how far back that is. * * @param opts The navigation options. * @param done The transition complete function. */ popToRoot(opts, done) { return this.removeIndex(1, -1, opts, done); } /** * Removes a component from the navigation stack at the specified index. * * @param startIndex The number to begin removal at. * @param removeCount The number of components to remove. * @param opts The navigation options. * @param done The transition complete function. */ removeIndex(startIndex, removeCount = 1, opts, done) { return this.queueTrns({ removeStart: startIndex, removeCount, opts, }, done); } /** * Set the root for the current navigation stack to a component. * * @param component The component to set as the root of the navigation stack. * @param componentProps Any properties of the component. * @param opts The navigation options. * @param done The transition complete function. */ setRoot(component, componentProps, opts, done) { return this.setPages([{ component, componentProps }], opts, done); } /** * Set the views of the current navigation stack and navigate to the last view. * By default animations are disabled, but they can be enabled by passing options * to the navigation controller. Navigation parameters can also be passed to the * individual pages in the array. * * @param views The list of views to set as the navigation stack. * @param opts The navigation options. * @param done The transition complete function. */ setPages(views, opts, done) { opts !== null && opts !== void 0 ? opts : (opts = {}); // if animation wasn't set to true then default it to NOT animate if (opts.animated !== true) { opts.animated = false; } return this.queueTrns({ insertStart: 0, insertViews: views, removeStart: 0, removeCount: -1, opts, }, done); } /** * Called by the router to update the view. * * @param id The component tag. * @param params The component params. * @param direction A direction hint. * @param animation an AnimationBuilder. * * @return the status. * @internal */ setRouteId(id, params, direction, animation) { const active = this.getActiveSync(); if (matches(active, id, params)) { return Promise.resolve({ changed: false, element: active.element, }); } let resolve; const promise = new Promise((r) => (resolve = r)); let finish; const commonOpts = { updateURL: false, viewIsReady: (enteringEl) => { let mark; const p = new Promise((r) => (mark = r)); resolve({ changed: true, element: enteringEl, markVisible: async () => { mark(); await finish; }, }); return p; }, }; if (direction === 'root') { finish = this.setRoot(id, params, commonOpts); } else { // Look for a view matching the target in the view stack. const viewController = this.views.find((v) => matches(v, id, params)); if (viewController) { finish = this.popTo(viewController, Object.assign(Object.assign({}, commonOpts), { direction: 'back', animationBuilder: animation })); } else if (direction === 'forward') { finish = this.push(id, params, Object.assign(Object.assign({}, commonOpts), { animationBuilder: animation })); } else if (direction === 'back') { finish = this.setRoot(id, params, Object.assign(Object.assign({}, commonOpts), { direction: 'back', animated: true, animationBuilder: animation })); } } return promise; } /** * Called by <ion-router> to retrieve the current component. * * @internal */ async getRouteId() { const active = this.getActiveSync(); if (active) { return { id: active.element.tagName, params: active.params, element: active.element, }; } return undefined; } /** * Get the active view. */ async getActive() { return this.getActiveSync(); } /** * Get the view at the specified index. * * @param index The index of the view. */ async getByIndex(index) { return this.views[index]; } /** * Returns `true` if the current view can go back. * * @param view The view to check. */ async canGoBack(view) { return this.canGoBackSync(view); } /** * Get the previous view. * * @param view The view to get. */ async getPrevious(view) { return this.getPreviousSync(view); } getLength() { return this.views.length; } getActiveSync() { return this.views[this.views.length - 1]; } canGoBackSync(view = this.getActiveSync()) { return !!(view && this.getPreviousSync(view)); } getPreviousSync(view = this.getActiveSync()) { if (!view) { return undefined; } const views = this.views; const index = views.indexOf(view); return index > 0 ? views[index - 1] : undefined; } /** * Adds a navigation stack change to the queue and schedules it to run. * * @returns Whether the transition succeeds. */ async queueTrns(ti, done) { var _a, _b; if (this.isTransitioning && ((_a = ti.opts) === null || _a === void 0 ? void 0 : _a.skipIfBusy)) { return false; } const promise = new Promise((resolve, reject) => { ti.resolve = resolve; ti.reject = reject; }); ti.done = done; /** * If using router, check to see if navigation hooks * will allow us to perform this transition. This * is required in order for hooks to work with * the ion-back-button or swipe to go back. */ if (ti.opts && ti.opts.updateURL !== false && this.useRouter) { const router = document.querySelector('ion-router'); if (router) { const canTransition = await router.canTransition(); if (canTransition === false) { return false; } if (typeof canTransition === 'string') { router.push(canTransition, ti.opts.direction || 'back'); return false; } } } // Normalize empty if (((_b = ti.insertViews) === null || _b === void 0 ? void 0 : _b.length) === 0) { ti.insertViews = undefined; } // Enqueue transition instruction this.transInstr.push(ti); // if there isn't a transition already happening // then this will kick off this transition this.nextTrns(); return promise; } success(result, ti) { if (this.destroyed) { this.fireError('nav controller was destroyed', ti); return; } if (ti.done) { ti.done(result.hasCompleted, result.requiresTransition, result.enteringView, result.leavingView, result.direction); } ti.resolve(result.hasCompleted); if (ti.opts.updateURL !== false && this.useRouter) { const router = document.querySelector('ion-router'); if (router) { const direction = result.direction === 'back' ? 'back' : 'forward'; router.navChanged(direction); } } } failed(rejectReason, ti) { if (this.destroyed) { this.fireError('nav controller was destroyed', ti); return; } this.transInstr.length = 0; this.fireError(rejectReason, ti); } fireError(rejectReason, ti) { if (ti.done) { ti.done(false, false, rejectReason); } if (ti.reject && !this.destroyed) { ti.reject(rejectReason); } else { ti.resolve(false); } } /** * Consumes the next transition in the queue. * * @returns whether the transition is executed. */ nextTrns() { // this is the framework's bread 'n butta function // only one transition is allowed at any given time if (this.isTransitioning) { return false; } // there is no transition happening right now, executes the next instructions. const ti = this.transInstr.shift(); if (!ti) { return false; } this.runTransition(ti); return true; } /** Executes all the transition instruction from the queue. */ async runTransition(ti) { try { // set that this nav is actively transitioning this.ionNavWillChange.emit(); this.isTransitioning = true; this.prepareTI(ti); const leavingView = this.getActiveSync(); const enteringView = this.getEnteringView(ti, leavingView); if (!leavingView && !enteringView) { throw new Error('no views in the stack to be removed'); } if (enteringView && enteringView.state === VIEW_STATE_NEW) { await enteringView.init(this.el); } this.postViewInit(enteringView, leavingView, ti); // Needs transition? const requiresTransition = (ti.enteringRequiresTransition || ti.leavingRequiresTransition) && enteringView !== leavingView; if (requiresTransition && ti.opts && leavingView) { const isBackDirection = ti.opts.direction === 'back'; /** * If heading back, use the entering page's animation * unless otherwise specified by the developer. */ if (isBackDirection) { ti.opts.animationBuilder = ti.opts.animationBuilder || (enteringView === null || enteringView === void 0 ? void 0 : enteringView.animationBuilder); } leavingView.animationBuilder = ti.opts.animationBuilder; } let result; if (requiresTransition) { result = await this.transition(enteringView, leavingView, ti); } else { // transition is not required, so we are already done! // they're inserting/removing the views somewhere in the middle or // beginning, so visually nothing needs to animate/transition // resolve immediately because there's no animation that's happening result = { hasCompleted: true, requiresTransition: false, }; } this.success(result, ti); this.ionNavDidChange.emit(); } catch (rejectReason) { this.failed(rejectReason, ti); } this.isTransitioning = false; this.nextTrns(); } prepareTI(ti) { var _a, _b; var _c; const viewsLength = this.views.length; (_a = ti.opts) !== null && _a !== void 0 ? _a : (ti.opts = {}); (_b = (_c = ti.opts).delegate) !== null && _b !== void 0 ? _b : (_c.delegate = this.delegate); if (ti.removeView !== undefined) { helpers.assert(ti.removeStart !== undefined, 'removeView needs removeStart'); helpers.assert(ti.removeCount !== undefined, 'removeView needs removeCount'); const index = this.views.indexOf(ti.removeView); if (index < 0) { throw new Error('removeView was not found'); } ti.removeStart += index; } if (ti.removeStart !== undefined) { if (ti.removeStart < 0) { ti.removeStart = viewsLength - 1; } if (ti.removeCount < 0) { ti.removeCount = viewsLength - ti.removeStart; } ti.leavingRequiresTransition = ti.removeCount > 0 && ti.removeStart + ti.removeCount === viewsLength; } if (ti.insertViews) { // allow -1 to be passed in to auto push it on the end // and clean up the index if it's larger then the size of the stack if (ti.insertStart < 0 || ti.insertStart > viewsLength) { ti.insertStart = viewsLength; } ti.enteringRequiresTransition = ti.insertStart === viewsLength; } const insertViews = ti.insertViews; if (!insertViews) { return; } helpers.assert(insertViews.length > 0, 'length can not be zero'); const viewControllers = convertToViews(insertViews); if (viewControllers.length === 0) { throw new Error('invalid views to insert'); } // Check all the inserted view are correct for (const view of viewControllers) { view.delegate = ti.opts.delegate; const nav = view.nav; if (nav && nav !== this) { throw new Error('inserted view was already inserted'); } if (view.state === VIEW_STATE_DESTROYED) { throw new Error('inserted view was already destroyed'); } } ti.insertViews = viewControllers; } /** * Returns the view that will be entered considering the transition instructions. * * @param ti The instructions. * @param leavingView The view being left or undefined if none. * * @returns The view that will be entered, undefined if none. */ getEnteringView(ti, leavingView) { // The last inserted view will be entered when view are inserted. const insertViews = ti.insertViews; if (insertViews !== undefined) { return insertViews[insertViews.length - 1]; } // When views are deleted, we will enter the last view that is not removed and not the view being left. const removeStart = ti.removeStart; if (removeStart !== undefined) { const views = this.views; const removeEnd = removeStart + ti.removeCount; for (let i = views.length - 1; i >= 0; i--) { const view = views[i]; if ((i < removeStart || i >= removeEnd) && view !== leavingView) { return view; } } } return undefined; } /** * Adds and Removes the views from the navigation stack. * * @param enteringView The view being entered. * @param leavingView The view being left. * @param ti The instructions. */ postViewInit(enteringView, leavingView, ti) { var _a, _b, _c; helpers.assert(leavingView || enteringView, 'Both leavingView and enteringView are null'); helpers.assert(ti.resolve, 'resolve must be valid'); helpers.assert(ti.reject, 'reject must be valid'); // Compute the views to remove. const opts = ti.opts; const { insertViews, removeStart, removeCount } = ti; /** Records the view to destroy */ let destroyQueue; // there are views to remove if (removeStart !== undefined && removeCount !== undefined) { helpers.assert(removeStart >= 0, 'removeStart can not be negative'); helpers.assert(removeCount >= 0, 'removeCount can not be negative'); destroyQueue = []; for (let i = removeStart; i < removeStart + removeCount; i++) { const view = this.views[i]; if (view !== undefined && view !== enteringView && view !== leavingView) { destroyQueue.push(view); } } // default the direction to "back" (_a = opts.direction) !== null && _a !== void 0 ? _a : (opts.direction = 'back'); } const finalNumViews = this.views.length + ((_b = insertViews === null || insertViews === void 0 ? void 0 : insertViews.length) !== null && _b !== void 0 ? _b : 0) - (removeCount !== null && removeCount !== void 0 ? removeCount : 0); helpers.assert(finalNumViews >= 0, 'final balance can not be negative'); if (finalNumViews === 0) { console.warn(`You can't remove all the pages in the navigation stack. nav.pop() is probably called too many times.`, this, this.el); throw new Error('navigation stack needs at least one root page'); } // At this point the transition can not be rejected, any throw should be an error // Insert the new views in the stack. if (insertViews) { // add the views to the let insertIndex = ti.insertStart; for (const view of insertViews) { this.insertViewAt(view, insertIndex); insertIndex++; } if (ti.enteringRequiresTransition) { // default to forward if not already set (_c = opts.direction) !== null && _c !== void 0 ? _c : (opts.direction = 'forward'); } } // if the views to be removed are in the beginning or middle // and there is not a view that needs to visually transition out // then just destroy them and don't transition anything // batch all of lifecycles together // let's make sure, callbacks are zoned if (destroyQueue && destroyQueue.length > 0) { for (const view of destroyQueue) { transition.lifecycle(view.element, LIFECYCLE_WILL_LEAVE); transition.lifecycle(view.element, LIFECYCLE_DID_LEAVE); transition.lifecycle(view.element, LIFECYCLE_WILL_UNLOAD); } // once all lifecycle events has been delivered, we can safely detroy the views for (const view of destroyQueue) { this.destroyView(view); } } } async transition(enteringView, leavingView, ti) { // we should animate (duration > 0) if the pushed page is not the first one (startup) // or if it is a portal (modal, actionsheet, etc.) const opts = ti.opts; const progressCallback = opts.progressAnimation ? (ani) => (this.sbAni = ani) : undefined; const mode = ionicGlobal.getIonMode(this); const enteringEl = enteringView.element; const leavingEl = leavingView && leavingView.element; const animationOpts = Object.assign(Object.assign({ mode, showGoBack: this.canGoBackSync(enteringView), baseEl: this.el, progressCallback, animated: this.animated && ionicGlobal.config.getBoolean('animated', true), enteringEl, leavingEl }, opts), { animationBuilder: opts.animationBuilder || this.animation || ionicGlobal.config.get('navAnimation') }); const { hasCompleted } = await transition.transition(animationOpts); return this.transitionFinish(hasCompleted, enteringView, leavingView, opts); } transitionFinish(hasCompleted, enteringView, leavingView, opts) { /** * If the transition did not complete, the leavingView will still be the active * view on the stack. Otherwise unmount all the views after the enteringView. */ const activeView = hasCompleted ? enteringView : leavingView; if (activeView) { this.unmountInactiveViews(activeView); } return { hasCompleted, requiresTransition: true, enteringView, leavingView, direction: opts.direction, }; } /** * Inserts a view at the specified index. * * When the view already is in the stack it will be moved to the new position. * * @param view The view to insert. * @param index The index where to insert the view. */ insertViewAt(view, index) { const views = this.views; const existingIndex = views.indexOf(view); if (existingIndex > -1) { helpers.assert(view.nav === this, 'view is not part of the nav'); // The view already in the stack, removes it. views.splice(existingIndex, 1); // and add it back at the requested index. views.splice(index, 0, view); } else { helpers.assert(!view.nav, 'nav is used'); // this is a new view to add to the stack // create the new entering view view.nav = this; views.splice(index, 0, view); } } /** * Removes a view from the stack. * * @param view The view to remove. */ removeView(view) { helpers.assert(view.state === VIEW_STATE_ATTACHED || view.state === VIEW_STATE_DESTROYED, 'view state should be loaded or destroyed'); const views = this.views; const index = views.indexOf(view); helpers.assert(index > -1, 'view must be part of the stack'); if (index >= 0) { views.splice(index, 1); } } destroyView(view) { view._destroy(); this.removeView(view); } /** * Unmounts all inactive views after the specified active view. * * DOM WRITE * * @param activeView The view that is actively visible in the stack. Used to calculate which views to unmount. */ unmountInactiveViews(activeView) { // ok, cleanup time!! Destroy all of the views that are // INACTIVE and come after the active view // only do this if the views exist, though if (this.destroyed) { return; } const views = this.views; const activeViewIndex = views.indexOf(activeView); for (let i = views.length - 1; i >= 0; i--) { const view = views[i]; /** * When inserting multiple views via insertPages * the last page will be transitioned to, but the * others will not be. As a result, a DOM element * will only be created for the last page inserted. * As a result, it is possible to have views in the * stack that do not have `view.element` yet. */ const element = view.element; if (element) { if (i > activeViewIndex) { // this view comes after the active view // let's unload it transition.lifecycle(element, LIFECYCLE_WILL_UNLOAD); this.destroyView(view); } else if (i < activeViewIndex) { // this view comes before the active view // and it is not a portal then ensure it is hidden transition.setPageHidden(element, true); } } } } canStart() { return (!!this.swipeGesture && !this.isTransitioning && this.transInstr.length === 0 && this.animationEnabled && this.canGoBackSync()); } onStart() { this.pop({ direction: 'back', progressAnimation: true }); } onMove(stepValue) { if (this.sbAni) { this.sbAni.progressStep(stepValue); } } onEnd(shouldComplete, stepValue, dur) { if (this.sbAni) { this.animationEnabled = false; this.sbAni.onFinish(() => { this.animationEnabled = true; }, { oneTimeCallback: true }); // Account for rounding errors in JS let newStepValue = shouldComplete ? -0.001 : 0.001; /** * Animation will be reversed here, so need to * reverse the easing curve as well * * Additionally, we need to account for the time relative * to the new easing curve, as `stepValue` is going to be given * in terms of a linear curve. */ if (!shouldComplete) { this.sbAni.easing('cubic-bezier(1, 0, 0.68, 0.28)'); newStepValue += cubicBezier.getTimeGivenProgression([0, 0], [1, 0], [0.68, 0.28], [1, 1], stepValue)[0]; } else { newStepValue += cubicBezier.getTimeGivenProgression([0, 0], [0.32, 0.72], [0, 1], [1, 1], stepValue)[0]; } this.sbAni.progressEnd(shouldComplete ? 1 : 0, newStepValue, dur); } } render() { return index.h("slot", null); } get el() { return index.getElement(this); } static get watchers() { return { "swipeGesture": ["swipeGestureChanged"], "root": ["rootChanged"] }; } }; Nav.style = navCss; /*! * (C) Ionic http://ionicframework.com - MIT License */ let animationPrefix; /** * Web Animations requires hyphenated CSS properties * to be written in camelCase when animating */ const processKeyframes = (keyframes) => { keyframes.forEach((keyframe) => { for (const key in keyframe) { // eslint-disable-next-line no-prototype-builtins if (keyframe.hasOwnProperty(key)) { const value = keyframe[key]; if (key === 'easing') { const newKey = 'animation-timing-function'; keyframe[newKey] = value; delete keyframe[key]; } else { const newKey = convertCamelCaseToHypen(key); if (newKey !== key) { keyframe[newKey] = value; delete keyframe[key]; } } } } }); return keyframes; }; const convertCamelCaseToHypen = (str) => { return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); }; const getAnimationPrefix = (el) => { if (animationPrefix === undefined) { const supportsUnprefixed = el.style.animationName !== undefined; const supportsWebkitPrefix = el.style.webkitAnimationName !== undefined; animationPrefix = !supportsUnprefixed && supportsWebkitPrefix ? '-webkit-' : ''; } return animationPrefix; }; const setStyleProperty = (element, propertyName, value) => { const prefix = propertyName.startsWith('animation') ? getAnimationPrefix(element) : ''; element.style.setProperty(prefix + propertyName, value); }; const removeStyleProperty = (element, propertyName) => { const prefix = propertyName.startsWith('animation') ? getAnimationPrefix(element) : ''; element.style.removeProperty(prefix + propertyName); }; const animationEnd = (el, callback) => { let unRegTrans; const opts = { passive: true }; const unregister = () => { if (unRegTrans) { unRegTrans(); } }; const onTransitionEnd = (ev) => { if (el === ev.target) { unregister(); callback(ev); } }; if (el) { el.addEventListener('webkitAnimationEnd', onTransitionEnd, opts); el.addEventListener('animationend', onTransitionEnd, opts); unRegTrans = () => { el.removeEventListener('webkitAnimationEnd', onTransitionEnd, opts); el.removeEventListener('animationend', onTransitionEnd, opts); }; } return unregister; }; // TODO(FW-2832): type const generateKeyframeRules = (keyframes = []) => { return keyframes .map((keyframe) => { const offset = keyframe.offset; const frameString = []; for (const property in keyframe) { // eslint-disable-next-line no-prototype-builtins if (keyframe.hasOwnProperty(property) && property !== 'offset') { frameString.push(`${property}: ${keyframe[property]};`); } } return `${offset * 100}% { ${frameString.join(' ')} }`; }) .join(' '); }; const keyframeIds = []; const generateKeyframeName = (keyframeRules) => { let index = keyframeIds.indexOf(keyframeRules); if (index < 0) { index = keyframeIds.push(keyframeRules) - 1; } return `ion-animation-${index}`; }; const getStyleContainer = (element) => { // getRootNode is not always available in SSR environments. // TODO(FW-2832): types const rootNode = element.getRootNode !== undefined ? element.getRootNode() : element; return rootNode.head || rootNode; }; const createKeyframeStylesheet = (keyframeName, keyframeRules, element) => { var _a; const styleContainer = getStyleContainer(element); const keyframePrefix = getAnimationPrefix(element); const existingStylesheet = styleContainer.querySelector('#' + keyframeName); if (existingStylesheet) { return existingStylesheet; } const stylesheet = ((_a = element.ownerDocument) !== null && _a !== void 0 ? _a : document).createElement('style'); stylesheet.id = keyframeName; stylesheet.textContent = `@${keyframePrefix}keyframes ${keyframeName} { ${keyframeRules} } @${keyframePrefix}keyframes ${keyframeName}-alt { ${keyframeRules} }`; styleContainer.appendChild(stylesheet); return stylesheet; }; const addClassToArray = (classes = [], className) => { if (className !== undefined) { const classNameToAppend = Array.isArray(className) ? className : [className]; return [...classes, ...classNameToAppend]; } return classes; }; /*! * (C) Ionic http://ionicframework.com - MIT License */ const createAnimation = (animationId) => { let _delay; let _duration; let _easing; let _iterations; let _fill; let _direction; let _keyframes = []; let beforeAddClasses = []; let beforeRemoveClasses = []; let initialized = false; let parentAnimation; let beforeStylesValue = {}; let afterAddClasses = []; let afterRemoveClasses = []; let afterStylesValue = {}; let numAnimationsRunning = 0; let shouldForceLinearEasing = false; let shouldForceSyncPlayback = false; let cssAnimationsTimerFallback; let forceDirectionValue; let forceDurationValue; let forceDelayValue; let willComplete = true; let finished = false; let shouldCalculateNumAnimations = true; let keyframeName; let ani; let paused = false; const id = animationId; const onFinishCallbacks = []; const onFinishOneTimeCallbacks = []; const elements = []; const childAnimations = []; const stylesheets = []; const _beforeAddReadFunctions = []; const _beforeAddWriteFunctions = []; const _afterAddReadFunctions = []; const _afterAddWriteFunctions = []; const webAnimations = []; const supportsAnimationEffect = typeof AnimationEffect === 'function' || (index$1.win !== undefined && typeof index$1.win.AnimationEffect === 'function'); const supportsWebAnimations = typeof Element === 'function' && typeof Element.prototype.animate === 'function' && supportsAnimationEffect; const ANIMATION_END_FALLBACK_PADDING_MS = 100; const getWebAnimations = () => { return webAnimations; }; const destroy = (clearStyleSheets) => { childAnimations.forEach((childAnimation) => { childAnimation.destroy(clearStyleSheets); }); cleanUp(clearStyleSheets); elements.length = 0; childAnimations.length = 0; _keyframes.length = 0; clearOnFinish(); initialized = false; shouldCalculateNumAnimations = true; return ani; }; /** * Cancels any Web Animations, removes * any animation properties from the * animation's elements, and removes the * animation's stylesheets from the DOM. */ const cleanUp = (clearStyleSheets) => { cleanUpElements(); if (clearStyleSheets) { cleanUpStyleSheets(); } }; const resetFlags = () => { shouldForceLinearEasing = false; shouldForceSyncPlayback = false; shouldCalculateNumAnimations = true; forceDirectionValue = undefined; forceDurationValue = undefined; forceDelayValue = undefined; numAnimationsRunning = 0; finished = false; willComplete = true; paused = false; }; const isRunning = () => { return numAnimationsRunning !== 0 && !paused; }; const onFinish = (callback, opts) => { const callbacks = (opts === null || opts === void 0 ? void 0 : opts.oneTimeCallback) ? onFinishOneTimeCallbacks : onFinishCallbacks; callbacks.push({ c: callback, o: opts }); return ani; }; const clearOnFinish = () => { onFinishCallbacks.length = 0; onFinishOneTimeCallbacks.length = 0; return ani; }; /** * Cancels any Web Animations and removes * any animation properties from the * the animation's elements. */ const cleanUpElements = () => { if (supportsWebAnimations) { webAnimations.forEach((animation) => { animation.cancel(); }); webAnimations.length = 0; } else { const elementsArray = elements.slice(); helpers$1.raf(() => { elementsArray.forEach((element) => { removeStyleProperty(element, 'animation-name'); removeStyleProperty(element, 'animation-duration'); removeStyleProperty(element, 'animation-timing-function'); removeStyleProperty(element, 'animation-iteration-count'); removeStyleProperty(element, 'animation-delay'); removeStyleProperty(element, 'animation-play-state'); removeStyleProperty(element, 'animation-fill-mode'); removeStyleProperty(element, 'animation-direction'); }); }); } }; /** * Removes the animation's stylesheets * from the DOM. */ const cleanUpStyleSheets = () => { stylesheets.forEach((stylesheet) => { /** * When sharing stylesheets, it's possible * for another animation to have already * cleaned up a particular stylesheet */ if (stylesheet === null || stylesheet === void 0 ? void 0 : stylesheet.parentNode) { stylesheet.parentNode.removeChild(stylesheet); } }); stylesheets.length = 0; }; const beforeAddRead = (readFn) => { _beforeAddReadFunctions.push(readFn); return ani; }; const beforeAddWrite = (writeFn) => { _beforeAddWriteFunctions.push(writeFn); return ani; }; const afterAddRead = (readFn) => { _afterAddReadFunctions.push(readFn); return ani; }; const afterAddWrite = (writeFn) => { _afterAddWriteFunctions.push(writeFn); return ani; }; const beforeAddClass = (className) => { beforeAddClasses = addClassToArray(beforeAddClasses, className); return ani; }; const beforeRemoveClass = (className) => { beforeRemoveClasses = addClassToArray(beforeRemoveClasses, className); return ani; }; /** * Set CSS inline styles to the animation's * elements before the animation begins. */ const beforeStyles = (styles = {}) => { beforeStylesValue = styles; return ani; }; /** * Clear CSS inline styles from the animation's * elements before the animation begins. */ const beforeClearStyles = (propertyNames = []) => { for (const property of propertyNames) { beforeStylesValue[property] = ''; } return ani; }; const afterAddClass = (className) => { afterAddClasses = addClassToArray(afterAddClasses, className); return ani; }; const afterRemoveClass = (className) => { afterRemoveClasses = addClassToArray(afterRemoveClasses, className); return ani; }; const afterStyles = (styles = {}) => { afterStylesValue = styles; return ani; }; const afterClearStyles = (propertyNames = []) => { for (const property of propertyNames) { afterStylesValue[property] = ''; } return ani; }; const getFill = () => { if (_fill !== undefined) { return _fill; } if (parentAnimation) { return parentAnimation.getFill(); } return 'both'; }; const getDirection = () => { if (forceDirectionValue !== undefined) { return forceDirectionValue; } if (_direction !== undefined) { return _direction; } if (parentAnimation) { return parentAnimation.getDirection(); } return 'normal'; }; const getEasing = () => { if (shouldForceLinearEasing) { return 'linear'; } if (_easing !== undefined) { return _easing; } if (parentAnimation) { return parentAnimation.getEasing(); } return 'linear'; }; const getDuration = () => { if (shouldForceSyncPlayback) { return 0; } if (forceDurationValue !== undefined) { return forceDurationValue; } if (_duration !== undefined) { return _duration; } if (parentAnimation) { return parentAnimation.getDuration(); } return 0; }; const getIterations = () => { if (_iterations !== undefined) { return _iterations; } if (parentAnimation) { return parentAnimation.getIterations(); } return 1; }; const getDelay = () => { if (forceDelayValue !== undefined) { return forceDelayValue; } if (_delay !== undefined) { return _delay; } if (parentAnimation) { return parentAnimation.getDelay(); } return 0; }; const getKeyframes = () => { return _keyframes; }; const direction = (animationDirection) => { _direction = animationDirection; update(true); return ani; }; const fill = (animationFill) => { _fill = animationFill; update(true); return ani; }; const delay = (animationDelay) => { _delay = animationDelay; update(true); return ani; }; const easing = (animationEasing) => { _easing = animationEasing; update(true); return ani; }; const duration = (animationDuration) => { /** * CSS Animation Durations of 0ms work fine on Chrome * but do not run on Safari, so force it to 1ms to * get it to run on both platforms. */ if (!supportsWebAnimations && animationDuration === 0) { animationDuration = 1; } _duration = animationDuration; update(true); return ani; }; const iterations = (animationIterations) => { _iterations = animationIterations; update(true); return ani; }; const parent = (animation) => { parentAnimation = animation; return ani; }; const addElement = (el) => { if (el != null) { if (el.nodeType === 1) { elements.push(el); } else if (el.length >= 0) { for (let i = 0; i < el.length; i++) { elements.push(el[i]); } } else { console.error('Invalid addElement value'); } } return ani; }; const addAnimation = (animationToAdd) => { if (animationToAdd != null) { if (Array.isArray(animationToAdd)) { for (const animation of animationToAdd) { animation.parent(ani); childAnimations.push(animation); } } else { animationToAdd.parent(ani); childAnimations.push(animationToAdd); } } return ani; }; const keyframes = (keyframeValues) => { const different = _keyframes !== keyframeValues; _keyframes = keyframeValues; if (different) { updateKeyframes(_keyframes); } return ani; }; const updateKeyframes = (keyframeValues) => { if (supportsWebAnimations) { getWebAnimations().forEach((animation) => { if (animation.effect.setKeyframes) { animation.effect.setKeyframes(keyframeValues); } else { const newEffect = new KeyframeEffect(animation.effect.target, keyframeValues, animation.effect.getTiming()); animation.effect = newEffect; } }); } else { initializeCSSAnimation(); } }; /** * Run all "before" animation hooks. */ const beforeAnimation = () => { // Runs all before read callbacks _beforeAddReadFunctions.forEach((callback) => callback()); // Runs all before write callbacks _beforeAddWriteFunctions.forEach((callback) => callback()); // Updates styles and classes before animation runs const addClasses = beforeAddClasses; const removeClasses = beforeRemoveClasses; const styles = beforeStylesValue; elements.forEach((el) => { const elementClassList = el.classList; addClasses.forEach((c) => elementClassList.add(c)); removeClasses.forEach((c) => elementClassList.remove(c)); for (const property in styles) { // eslint-disable-next-line no-prototype-builtins if (styles.hasOwnProperty(property)) { setStyleProperty(el, property, styles[property]); } } }); }; /** * Run all "after" animation hooks. */ const afterAnimation = () => { clearCSSAnimationsTimeout(); // Runs all after read callbacks _afterAddReadFunctions.forEach((callback) => callback()); // Runs all after write callbacks _afterAddWriteFunctions.forEach((callback) => callback()); // Updates styles and classes before animation ends const currentStep = willComplete ? 1 : 0; const addClasses = afterAddClasses; const removeClasses = afterRemoveClasses; const styles = afterStylesValue; elements.forEach((el) => { const elementClassList = el.classList; addClasses.forEach((c) => elementClassList.add(c)); removeClasses.forEach((c) => elementClassList.remove(c)); for (const property in styles) { // eslint-disable-next-line no-prototype-builtins if (styles.hasOwnProperty(property)) { setStyleProperty(el, property, styles[property]); } } }); onFinishCallbacks.forEach((onFinishCallback) => { return onFinishCallback.c(currentStep, ani); }); onFinishOneTimeCallbacks.forEach((onFinishCallback) => { return onFinishCallback.c(currentStep, ani); }); onFinishOneTimeCallbacks.length = 0; shouldCalculateNumAnimations = true; if (willComplete) { finished = true; } willComplete = true; }; const animationFinish = () => { if (numAnimationsRunning === 0) { return; } numAnimationsRunning--; if (numAnimationsRunning === 0) { afterAnimation(); if (parentAnimation) { parentAnimation.animationFinish(); } } }; const initializeCSSAnimation = (toggleAnimationName = true) => { cleanUpStyleSheets(); const processedKeyframes = processKeyframes(_keyframes); elements.forEach((element) => { if (processedKeyframes.length > 0) { const keyframeRules = generateKeyframeRules(processedKeyframes); keyframeName = animationId !== undefined ? animationId : generateKeyframeName(keyframeRules); const stylesheet = createKeyframeStylesheet(keyframeName, keyframeRules, element); stylesheets.push(stylesheet); setStyleProperty(element, 'animation-duration', `${getDuration()}ms`); setStyleProperty(element, 'animation-timing-function', getEasing()); setStyleProperty(element, 'animation-delay', `${getDelay()}ms`); setStyleProperty(element, 'animation-fill-mode', getFill()); setStyleProperty(element, 'animation-direction', getDirection()); const iterationsCount = getIterations() === Infinity ? 'infinite' : getIterations().toString(); setStyleProperty(element, 'animation-iteration-count', iterationsCount); setStyleProperty(element, 'animation-play-state', 'paused'); if (toggleAnimationName) { setStyleProperty(element, 'animation-name', `${stylesheet.id}-alt`); } helpers$1.raf(() => { setStyleProperty(element, 'animation-name', stylesheet.id || null); }); } }); }; const initializeWebAnimation = () => { elements.forEach((element) => { const animation = element.animate(_keyframes, { id, delay: getDelay(), duration: getDuration(), easing: getEasing(), iterations: getIterations(), fill: getFill(), direction: getDirection(), }); animation.pause(); webAnimations.push(animation); }); if (webAnimations.length > 0) { webAnimations[0].onfinish = () => { animationFinish(); }; } }; const initializeAnimation = (toggleAnimationName = true) => { beforeAnimation(); if (_keyframes.length > 0) { if (supportsWebAnimations) { initializeWebAnimation(); } else { initializeCSSAnimation(toggleAnimationName); } } initialized = true; }; const setAnimationStep = (step) => { step = Math.min(Math.max(step, 0), 0.9999); if (supportsWebAnimations) { webAnimations.forEach((animation) => { animation.currentTi