From 094816de01aba8d29dbf6a0ff02053aa53198161 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Thu, 26 Oct 2017 22:11:46 +0200 Subject: [PATCH] refactor(content): use css instead of js --- packages/core/src/components.d.ts | 40 +- packages/core/src/components/app/app.scss | 59 +-- packages/core/src/components/app/app.tsx | 10 +- .../src/components/content/content.ios.scss | 17 - .../core/src/components/content/content.scss | 35 +- .../core/src/components/content/content.tsx | 151 ++++--- .../src/components/content/test/basic.html | 142 +++++++ packages/core/src/components/fixed/fixed.scss | 19 - packages/core/src/components/fixed/fixed.tsx | 36 -- .../infinite-scroll/infinite-scroll.tsx | 28 +- .../infinite-scroll/test/basic.html | 2 +- packages/core/src/components/menu/menu.scss | 6 - packages/core/src/components/menu/menu.tsx | 16 +- .../nav-controller/stencil-nav-delegate.tsx | 13 +- packages/core/src/components/nav/page-one.tsx | 4 +- .../core/src/components/page/page.ios.scss | 6 - .../core/src/components/page/page.md.scss | 6 - packages/core/src/components/page/page.scss | 25 -- packages/core/src/components/page/page.tsx | 8 - .../core/src/components/page/page.wp.scss | 6 - .../core/src/components/reorder/reorder.scss | 5 +- .../core/src/components/scroll/scroll.tsx | 395 ++++++++---------- .../src/components/toolbar/toolbar.ios.scss | 2 +- .../core/src/components/toolbar/toolbar.scss | 6 +- packages/core/src/index.d.ts | 4 +- .../navigation/transitions/transition.ios.ts | 2 +- .../navigation/transitions/transition.md.ts | 2 +- packages/core/src/themes/ionic.globals.scss | 5 +- packages/core/src/utils/helpers.ts | 33 +- packages/core/stencil.config.js | 2 +- 30 files changed, 510 insertions(+), 575 deletions(-) create mode 100644 packages/core/src/components/content/test/basic.html delete mode 100644 packages/core/src/components/fixed/fixed.scss delete mode 100644 packages/core/src/components/fixed/fixed.tsx delete mode 100644 packages/core/src/components/page/page.ios.scss delete mode 100644 packages/core/src/components/page/page.md.scss delete mode 100644 packages/core/src/components/page/page.scss delete mode 100644 packages/core/src/components/page/page.wp.scss diff --git a/packages/core/src/components.d.ts b/packages/core/src/components.d.ts index eee92ae352..580a431e8c 100644 --- a/packages/core/src/components.d.ts +++ b/packages/core/src/components.d.ts @@ -596,6 +596,12 @@ declare global { mode?: string, color?: string, + enableJsScroll?: any, + scrollToTop?: any, + scrollToBottom?: any, + ionScrollStart?: any, + ionScroll?: any, + ionScrollEnd?: any, fullscreen?: boolean } } @@ -744,35 +750,6 @@ declare global { } } -import { Fixed as IonFixed } from './components/fixed/fixed'; - -interface HTMLIonFixedElement extends IonFixed, HTMLElement { -} -declare var HTMLIonFixedElement: { - prototype: HTMLIonFixedElement; - new (): HTMLIonFixedElement; -}; -declare global { - interface HTMLElementTagNameMap { - "ion-fixed": HTMLIonFixedElement; - } - interface ElementTagNameMap { - "ion-fixed": HTMLIonFixedElement; - } - namespace JSX { - interface IntrinsicElements { - "ion-fixed": JSXElements.IonFixedAttributes; - } - } - namespace JSXElements { - export interface IonFixedAttributes extends HTMLAttributes { - mode?: string, - color?: string, - - } - } -} - import { Footer as IonFooter } from './components/footer/footer'; interface HTMLIonFooterElement extends IonFooter, HTMLElement { @@ -1053,7 +1030,7 @@ declare global { complete?: any, threshold?: string, enabled?: boolean, - position?: any + position?: string } } } @@ -2416,6 +2393,9 @@ declare global { mode?: string, color?: string, + scrollToTop?: any, + scrollToBottom?: any, + scrollToPoint?: any, enabled?: boolean, jsScroll?: boolean, onionScrollStart?: any, diff --git a/packages/core/src/components/app/app.scss b/packages/core/src/components/app/app.scss index 9bd1e74e42..f41181b64f 100644 --- a/packages/core/src/components/app/app.scss +++ b/packages/core/src/components/app/app.scss @@ -1,4 +1,3 @@ - // Globals // -------------------------------------------------- @import "../../themes/ionic.globals"; @@ -226,7 +225,9 @@ sub { ion-app, ion-nav, ion-tab, -ion-tabs { +ion-tabs, +ion-page, +.ion-page { @include position(0, null, null, 0); position: absolute; @@ -235,6 +236,8 @@ ion-tabs { width: 100%; height: 100%; + + contain: strict; } ion-nav, @@ -251,58 +254,39 @@ ion-tab.show-tab { display: block; } -ion-app, -ion-nav, -ion-tab, -ion-tabs, -ion-page { - contain: strict; -} - // Page Container Structure // -------------------------------------------------- -ion-page { - @include position(0, null, null, 0); +ion-page, +.ion-page, +.page-inner { + display: flex; - position: absolute; - display: block; - - width: 100%; - height: 100%; - - // do not show, but still render so we can get dimensions - // opacity: 0; + flex-direction: column; + justify-content: space-between; } -ion-page.show-page { - // show the page now that it's ready - // opacity: 1; +.hide-page { + opacity: 0; } - // Toolbar Container Structure // -------------------------------------------------- -ion-header { - @include position(0, null, null, 0); - - position: absolute; +ion-header, +ion-footer { + position: relative; z-index: $z-index-toolbar; display: block; + order: 1; + width: 100%; } -ion-footer { - @include position(null, null, 0, 0); - - position: absolute; - z-index: $z-index-toolbar; - display: block; - - width: 100%; +ion-header { + order: -1; } @@ -312,8 +296,7 @@ ion-footer { [app-viewport], [overlay-portal], [nav-viewport], -[tab-portal], -.nav-decor { +[tab-portal] { display: none; } diff --git a/packages/core/src/components/app/app.tsx b/packages/core/src/components/app/app.tsx index 603e3c7a9e..5c8f4e0d2a 100644 --- a/packages/core/src/components/app/app.tsx +++ b/packages/core/src/components/app/app.tsx @@ -69,9 +69,9 @@ export class IonApp implements App { } protected render() { - return ([ + return ( - ]); + ); } } @@ -122,9 +122,3 @@ export function handleBackButtonClick(): Promise { console.log('todo'); }); } - - - - - - diff --git a/packages/core/src/components/content/content.ios.scss b/packages/core/src/components/content/content.ios.scss index 818e0a64c3..01f510b671 100644 --- a/packages/core/src/components/content/content.ios.scss +++ b/packages/core/src/components/content/content.ios.scss @@ -27,23 +27,6 @@ $content-ios-transition-background: #000 !default; background-color: rgba(0, 0, 0, .12); } -.ios .ion-page.show-page ~ .nav-decor { - // when ios pages transition, the leaving page grays out - // this is the black square behind all pages so they gray out - @include position(0, null, null, 0); - - position: absolute; - z-index: 0; - display: block; - - width: 100%; - height: 100%; - - background: $content-ios-transition-background; - - pointer-events: none; -} - // iOS Content Padding // -------------------------------------------------- diff --git a/packages/core/src/components/content/content.scss b/packages/core/src/components/content/content.scss index 7f45cb8079..849cbc0a33 100644 --- a/packages/core/src/components/content/content.scss +++ b/packages/core/src/components/content/content.scss @@ -4,21 +4,16 @@ // -------------------------------------------------- ion-content { - @include position(0, null, null, 0); - position: relative; display: block; + flex: 1; + width: 100%; - height: 100%; contain: layout size style; } -.ion-page > ion-content { - position: absolute; -} - a { color: $link-color; } @@ -53,11 +48,6 @@ ion-content.js-scroll ion-scroll { will-change: initial; } -.disable-scroll .ion-page { - pointer-events: none; - touch-action: none; -} - ion-content.has-refresher ion-scroll { background-color: inherit; } @@ -72,38 +62,31 @@ ion-app [no-padding] ion-scroll { } @mixin content-padding($mode, $content-padding) { - .app-#{$mode} [padding], - .app-#{$mode} [padding] ion-scroll { + .app-#{$mode} [padding] .scroll-inner { @include padding($content-padding); } - .app-#{$mode} [padding-top], - .app-#{$mode} [padding-top] ion-scroll { + .app-#{$mode} [padding-top] .scroll-inner { @include padding($content-padding, null, null, null); } - .app-#{$mode} [padding-left], - .app-#{$mode} [padding-left] ion-scroll { + .app-#{$mode} [padding-left] .scroll-inner { @include padding-horizontal($content-padding, null); } - .app-#{$mode} [padding-right], - .app-#{$mode} [padding-right] ion-scroll { + .app-#{$mode} [padding-right] .scroll-inner { @include padding-horizontal(null, $content-padding); } - .app-#{$mode} [padding-bottom], - .app-#{$mode} [padding-bottom] ion-scroll { + .app-#{$mode} [padding-bottom] .scroll-inner { @include padding(null, null, $content-padding, null); } - .app-#{$mode} [padding-vertical], - .app-#{$mode} [padding-vertical] ion-scroll { + .app-#{$mode} [padding-vertical] .scroll-inner { @include padding($content-padding, null, $content-padding, null); } - .app-#{$mode} [padding-horizontal], - .app-#{$mode} [padding-horizontal] ion-scroll { + .app-#{$mode} [padding-horizontal] .scroll-inner { @include padding-horizontal($content-padding); } } diff --git a/packages/core/src/components/content/content.tsx b/packages/core/src/components/content/content.tsx index bf28feda19..675615d7d7 100644 --- a/packages/core/src/components/content/content.tsx +++ b/packages/core/src/components/content/content.tsx @@ -1,8 +1,7 @@ -import { Component, Element, Prop } from '@stencil/core'; -import { Config, Scroll, ScrollDetail } from '../../index'; +import { Component, Element, Method, Prop } from '@stencil/core'; +import { Config, HTMLIonScrollElement } from '../../index'; import { createThemedClasses, getElementClassObject } from '../../utils/theme'; -import { getParentElement, getToolbarHeight } from '../../utils/helpers'; - +import { getPageElement } from '../../utils/helpers'; @Component({ tag: 'ion-content', @@ -13,47 +12,33 @@ import { getParentElement, getToolbarHeight } from '../../utils/helpers'; } }) export class Content { - private mode: string; - private color: string; + + private cTop = 0; + private cBottom = 0; + private dirty = false; + private scrollEl: HTMLIonScrollElement; + + mode: string; + color: string; + @Element() private el: HTMLElement; + @Prop({ context: 'config' }) config: Config; - $scroll: Scroll; - $scrollDetail: ScrollDetail = {}; - $fixed: HTMLElement; - $siblingHeader: HTMLElement; - $siblingFooter: HTMLElement; - - headerHeight: string; - - - protected ionViewDidUnload() { - this.$fixed = this.$scroll = this.$siblingFooter = this.$siblingHeader = this.$scrollDetail = null; - } - - enableJsScroll() { - this.$scroll.jsScroll = true; - } + /** + * @output {ScrollEvent} Emitted when the scrolling first starts. + */ + @Prop() ionScrollStart: Function; /** - * Scroll to the top of the content component. - * - * @param {number} [duration] Duration of the scroll animation in milliseconds. Defaults to `300`. - * @returns {Promise} Returns a promise which is resolved when the scroll has completed. + * @output {ScrollEvent} Emitted on every scroll event. */ - scrollToTop(duration: number = 300) { - return this.$scroll.scrollToTop(duration); - } + @Prop() ionScroll: Function; /** - * Scroll to the bottom of the content component. - * - * @param {number} [duration] Duration of the scroll animation in milliseconds. Defaults to `300`. - * @returns {Promise} Returns a promise which is resolved when the scroll has completed. + * @output {ScrollEvent} Emitted when scrolling ends. */ - scrollToBottom(duration: number = 300) { - return this.$scroll.scrollToBottom(duration); - } + @Prop() ionScrollEnd: Function; /** * @input {boolean} If true, the content will scroll behind the headers @@ -62,35 +47,91 @@ export class Content { */ @Prop() fullscreen: boolean = false; + protected ionViewDidLoad() { + this.scrollEl = this.el.querySelector('ion-scroll') as HTMLIonScrollElement; + this.resize(); + } + + protected ionViewDidUnload() { + this.scrollEl = null; + } + + @Method() + enableJsScroll() { + this.scrollEl.jsScroll = true; + } + + /** + * Scroll to the top of the content component. + * + * @param {number} [duration] Duration of the scroll animation in milliseconds. Defaults to `300`. + * @returns {Promise} Returns a promise which is resolved when the scroll has completed. + */ + @Method() + scrollToTop(duration: number = 300) { + return this.scrollEl.scrollToTop(duration); + } + + /** + * Scroll to the bottom of the content component. + * + * @param {number} [duration] Duration of the scroll animation in milliseconds. Defaults to `300`. + * @returns {Promise} Returns a promise which is resolved when the scroll has completed. + */ + @Method() + scrollToBottom(duration: number = 300) { + return this.scrollEl.scrollToBottom(duration); + } + + resize() { + if (!this.scrollEl) { + return; + } + if (this.fullscreen) { + Context.dom.read(this.readDimensions.bind(this)); + Context.dom.write(this.writeDimensions.bind(this)); + } else { + this.cTop = this.cBottom = null; + Context.dom.write(() => this.scrollEl.removeAttribute('style')); + } + } + + private readDimensions() { + const page = getPageElement(this.el); + const top = Math.max(this.el.offsetTop, 0); + const bottom = Math.max(page.offsetHeight - top - this.el.offsetHeight, 0); + this.dirty = top !== this.cTop || bottom !== this.cBottom; + this.cTop = top; + this.cBottom = bottom; + } + + private writeDimensions() { + if (this.dirty && this.scrollEl) { + const style = this.scrollEl.style; + style.paddingTop = this.cTop + 'px'; + style.paddingBottom = this.cBottom + 'px'; + style.top = -this.cTop + 'px'; + style.bottom = -this.cBottom + 'px'; + this.dirty = false; + } + } protected render() { - const scrollStyle: any = {}; - - const pageChildren: HTMLElement[] = getParentElement(this.el).children; - const headerHeight = getToolbarHeight('ION-HEADER', pageChildren, this.mode, '44px', '56px'); - const footerHeight = getToolbarHeight('ION-FOOTER', pageChildren, this.mode, '50px', '48px'); - - if (this.fullscreen) { - scrollStyle.paddingTop = headerHeight; - scrollStyle.paddingBottom = footerHeight; - } else { - scrollStyle.marginTop = headerHeight; - scrollStyle.marginBottom = footerHeight; - } - const themedClasses = createThemedClasses(this.mode, this.color, 'content'); const hostClasses = getElementClassObject(this.el.classList); const scrollClasses = { ...themedClasses, ...hostClasses, - 'statusbar-padding': this.config.getBoolean('statusbarPadding') }; - return ( - + this.resize(); + + return [ + - - ); + , + + ]; } } diff --git a/packages/core/src/components/content/test/basic.html b/packages/core/src/components/content/test/basic.html new file mode 100644 index 0000000000..e28085a541 --- /dev/null +++ b/packages/core/src/components/content/test/basic.html @@ -0,0 +1,142 @@ + + + + + Ionic Chips + + + + + + + + + + Header + + + +
+ + + Toggle content.fullscreen +

+ Toggle header + Toggle footer + Toggle 2nd toolbar +

+

+ Toggle bottom content + Toggle right content +

+

+ Animate +

+ + + + + + + + +
+ +
+ + + + + + Footer + + +
+ + + +
+ + diff --git a/packages/core/src/components/fixed/fixed.scss b/packages/core/src/components/fixed/fixed.scss deleted file mode 100644 index 7228aaa379..0000000000 --- a/packages/core/src/components/fixed/fixed.scss +++ /dev/null @@ -1,19 +0,0 @@ -@import "../../themes/ionic.globals"; - -// Fixed Content (ion-fixed) -// -------------------------------------------------- - -ion-fixed { - @include position(0, 0, 0, 0); - - position: absolute; - display: block; -} - -[ion-fixed] { - position: absolute; - - z-index: $z-index-fixed-content; - - transform: translateZ(0); -} diff --git a/packages/core/src/components/fixed/fixed.tsx b/packages/core/src/components/fixed/fixed.tsx deleted file mode 100644 index e0c804bc75..0000000000 --- a/packages/core/src/components/fixed/fixed.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Component, Element, Prop } from '@stencil/core'; -import { getParentElement, getToolbarHeight } from '../../utils/helpers'; -import { Config } from '../../index'; - - -@Component({ - tag: 'ion-fixed', - styleUrl: 'fixed.scss' -}) -export class Fixed { - @Element() private el: HTMLElement; - @Prop({ context: 'config' }) config: Config; - mode: string; - - hostData() { - const pageChildren: HTMLElement[] = getParentElement(this.el).children; - const headerHeight = getToolbarHeight('ION-HEADER', pageChildren, this.mode, '44px', '56px'); - const footerHeight = getToolbarHeight('ION-FOOTER', pageChildren, this.mode, '50px', '48px'); - - return { - class: { - 'statusbar-padding': this.config.getBoolean('statusbarPadding') - }, - style: { - 'margin-top': headerHeight, - 'margin-bottom': footerHeight - } - }; - } - - protected render() { - return ( - - ); - } -} diff --git a/packages/core/src/components/infinite-scroll/infinite-scroll.tsx b/packages/core/src/components/infinite-scroll/infinite-scroll.tsx index f4e152b7f6..b803d3ffac 100644 --- a/packages/core/src/components/infinite-scroll/infinite-scroll.tsx +++ b/packages/core/src/components/infinite-scroll/infinite-scroll.tsx @@ -1,5 +1,5 @@ -import { Component, Element, Event, EventEmitter, HostElement, Listen, Method, Prop, PropDidChange, State } from '@stencil/core'; -import { ScrollDetail } from '../../index'; +import { Component, Element, Event, EventEmitter, Listen, Method, Prop, PropDidChange, State } from '@stencil/core'; +import { HTMLIonScrollElement, ScrollDetail, StencilElement } from '../../index'; const enum Position { Top = 'top', @@ -148,10 +148,10 @@ export class InfiniteScroll { private thrPx: number = 0; private thrPc: number = 0.15; - private init: boolean = false; - private scrollEl: HTMLElement; + private scrollEl: HTMLIonScrollElement; private didFire = false; private isBusy = false; + private init = false; @Element() private el: HTMLElement; @State() isLoading: boolean = false; @@ -204,7 +204,7 @@ export class InfiniteScroll { * The value can be either `top` or `bottom`. * Default is `bottom`. */ - @Prop() position: Position = Position.Bottom; + @Prop() position: string = Position.Bottom; /** * @output {event} Emitted when the scroll reaches @@ -214,16 +214,23 @@ export class InfiniteScroll { */ @Event() private ionInfinite: EventEmitter; + ionViewWillLoad() { + const scrollEl = this.el.closest('ion-scroll') as StencilElement; + return scrollEl.componentOnReady().then((el) => { + this.scrollEl = el as HTMLIonScrollElement; + }); + } + ionViewDidLoad() { - const scrollEl = this.scrollEl = this.el.closest('ion-scroll') as HostElement; - if (!scrollEl) { - console.error('ion-infinite-scroll must be used ion-content'); + if (this.init) { + console.warn('instance was already initialized'); return; } this.init = true; + this.thresholdChanged(this.threshold); this.enableScrollEvents(this.enabled); if (this.position === Position.Top) { - // scrollEl.scrollDownOnLoad = true; + Context.dom.write(() => this.scrollEl.scrollToBottom(0)); } } @@ -351,9 +358,6 @@ export class InfiniteScroll { * @hidden */ private enableScrollEvents(shouldListen: boolean) { - if (!this.init) { - return; - } Context.enableListener(this, 'ionScroll', shouldListen, this.scrollEl); } diff --git a/packages/core/src/components/infinite-scroll/test/basic.html b/packages/core/src/components/infinite-scroll/test/basic.html index 321a290da5..2147d2885d 100644 --- a/packages/core/src/components/infinite-scroll/test/basic.html +++ b/packages/core/src/components/infinite-scroll/test/basic.html @@ -10,7 +10,7 @@ - + diff --git a/packages/core/src/components/menu/menu.scss b/packages/core/src/components/menu/menu.scss index dedc5a8d91..8bbb2906e7 100644 --- a/packages/core/src/components/menu/menu.scss +++ b/packages/core/src/components/menu/menu.scss @@ -40,12 +40,6 @@ ion-menu.show-menu { contain: strict; } -.menu-inner > ion-header, -.menu-inner > ion-content, -.menu-inner > ion-footer { - position: absolute; -} - .menu-side-left > .menu-inner { @include multi-dir() { // scss-lint:disable PropertySpelling diff --git a/packages/core/src/components/menu/menu.tsx b/packages/core/src/components/menu/menu.tsx index 4b811c8aa1..cb23f74f8a 100644 --- a/packages/core/src/components/menu/menu.tsx +++ b/packages/core/src/components/menu/menu.tsx @@ -1,12 +1,7 @@ import { Component, Element, Event, EventEmitter, Listen, Method, Prop, PropDidChange, PropWillChange } from '@stencil/core'; -import { Animation, Config, GestureDetail, HTMLIonMenuElement, SplitPaneAlert } from '../../index'; -import { MenuController } from './menu-controller'; +import { Animation, Config, GestureDetail, HTMLIonMenuControllerElement, HTMLIonMenuElement, SplitPaneAlert, StencilElement } from '../../index'; import { Side, assert, checkEdgeSide, isRightSide } from '../../utils/helpers'; -export type Lazy = T & - { componentOnReady(): Promise } & - { componentOnReady(done: (cmp: T) => void): void }; - @Component({ tag: 'ion-menu', styleUrls: { @@ -35,12 +30,12 @@ export class Menu { backdropEl: HTMLElement; menuInnerEl: HTMLElement; contentEl: HTMLElement; - menuCtrl: MenuController; + menuCtrl: HTMLIonMenuControllerElement; @Element() el: HTMLIonMenuElement; @Prop({ context: 'config' }) config: Config; - @Prop({ connect: 'ion-menu-controller' }) lazyMenuCtrl: Lazy; + @Prop({ connect: 'ion-menu-controller' }) lazyMenuCtrl: StencilElement; /** * @input {string} The content's id the menu should use. @@ -116,8 +111,9 @@ export class Menu { @Event() ionClose: EventEmitter; protected ionViewWillLoad() { - return this.lazyMenuCtrl.componentOnReady() - .then(menu => this.menuCtrl = menu); + return this.lazyMenuCtrl.componentOnReady().then(menu => { + this.menuCtrl = menu as HTMLIonMenuControllerElement; + }); } protected ionViewDidLoad() { diff --git a/packages/core/src/components/nav-controller/stencil-nav-delegate.tsx b/packages/core/src/components/nav-controller/stencil-nav-delegate.tsx index 272bdfce11..0d2eff25fe 100644 --- a/packages/core/src/components/nav-controller/stencil-nav-delegate.tsx +++ b/packages/core/src/components/nav-controller/stencil-nav-delegate.tsx @@ -11,14 +11,11 @@ export class StencilNavDelegate implements FrameworkDelegate { @Method() attachViewToDom(nav: Nav, enteringView: ViewController): Promise { return new Promise((resolve) => { - const usersElement = document.createElement(enteringView.component); - const ionPage = document.createElement('ion-page'); - enteringView.element = ionPage; - ionPage.appendChild(usersElement); - nav.element.appendChild(ionPage); - (ionPage as StencilElement).componentOnReady(() => { - resolve(); - }); + const usersElement = document.createElement(enteringView.component) as HTMLElement; + usersElement.classList.add('ion-page'); + enteringView.element = usersElement; + nav.element.appendChild(usersElement); + resolve(); }); } diff --git a/packages/core/src/components/nav/page-one.tsx b/packages/core/src/components/nav/page-one.tsx index 6962313712..c130b57415 100644 --- a/packages/core/src/components/nav/page-one.tsx +++ b/packages/core/src/components/nav/page-one.tsx @@ -18,9 +18,9 @@ export class PageOne { protected render() { return [ - + Page One - + , Page One Content diff --git a/packages/core/src/components/page/page.ios.scss b/packages/core/src/components/page/page.ios.scss deleted file mode 100644 index b8ea0ebcb0..0000000000 --- a/packages/core/src/components/page/page.ios.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import "../../themes/ionic.globals.ios"; -@import "./page"; - - -// iOS Page -// -------------------------------------------------- diff --git a/packages/core/src/components/page/page.md.scss b/packages/core/src/components/page/page.md.scss deleted file mode 100644 index fb6200feb4..0000000000 --- a/packages/core/src/components/page/page.md.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import "../../themes/ionic.globals.md"; -@import "./page"; - - -// Material Design Page -// -------------------------------------------------- diff --git a/packages/core/src/components/page/page.scss b/packages/core/src/components/page/page.scss deleted file mode 100644 index c337a22d51..0000000000 --- a/packages/core/src/components/page/page.scss +++ /dev/null @@ -1,25 +0,0 @@ -@import "../../themes/ionic.globals"; - - -// Page -// -------------------------------------------------- - -ion-page { - @include position(0, null, null, 0); - - position: absolute; - display: block; - - width: 100%; - height: 100%; - - // do not show, but still render so we can get dimensions - opacity: 0; - - contain: strict; -} - -ion-page.show-page { - // show the page now that it's ready - opacity: 1; -} diff --git a/packages/core/src/components/page/page.tsx b/packages/core/src/components/page/page.tsx index edee2c2553..4dcc573513 100644 --- a/packages/core/src/components/page/page.tsx +++ b/packages/core/src/components/page/page.tsx @@ -3,14 +3,6 @@ import { Component } from '@stencil/core'; @Component({ tag: 'ion-page', - styleUrls: { - ios: 'page.ios.scss', - md: 'page.md.scss', - wp: 'page.wp.scss' - }, - host: { - theme: 'page' - } }) export class Page { protected render() { diff --git a/packages/core/src/components/page/page.wp.scss b/packages/core/src/components/page/page.wp.scss deleted file mode 100644 index 91a9ccfb25..0000000000 --- a/packages/core/src/components/page/page.wp.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import "../../themes/ionic.globals.wp"; -@import "./page"; - - -// Windows Page -// -------------------------------------------------- diff --git a/packages/core/src/components/reorder/reorder.scss b/packages/core/src/components/reorder/reorder.scss index 348beaa74d..41edc77aa9 100644 --- a/packages/core/src/components/reorder/reorder.scss +++ b/packages/core/src/components/reorder/reorder.scss @@ -16,7 +16,7 @@ ion-reorder-group > ion-gesture { .reorder-selected { position: relative; - z-index: 100; + z-index: $z-index-reorder-selected; box-shadow: 0 0 10px rgba(0, 0, 0, .4); opacity: .8; @@ -29,6 +29,7 @@ ion-reorder-group > ion-gesture { ion-reorder.no-hide { display: block; + visibility: normal; } @@ -72,7 +73,7 @@ ion-reorder[slot="start"] { .item-ios .reorder-icon { font-size: 2em; - opacity: 0.3; + opacity: .3; } ion-reorder[slot="start"] .reorder-icon { diff --git a/packages/core/src/components/scroll/scroll.tsx b/packages/core/src/components/scroll/scroll.tsx index 70d5e86c33..c997daf5bf 100644 --- a/packages/core/src/components/scroll/scroll.tsx +++ b/packages/core/src/components/scroll/scroll.tsx @@ -1,4 +1,4 @@ -import { Component, Element, Event, EventEmitter, Listen, Prop } from '@stencil/core'; +import { Component, Element, Event, EventEmitter, Listen, Method, Prop, PropDidChange } from '@stencil/core'; import { Config, GestureDetail } from '../../index'; import { GestureController, GestureDelegate } from '../gesture-controller/gesture-controller'; @@ -7,7 +7,6 @@ import { GestureController, GestureDelegate } from '../gesture-controller/gestur tag: 'ion-scroll' }) export class Scroll { - @Element() private el: HTMLElement; private gesture: GestureDelegate; private positions: number[] = []; @@ -19,9 +18,17 @@ export class Scroll { isScrolling: boolean = false; detail: ScrollDetail = {}; + @Element() private el: HTMLElement; + @Prop({ context: 'config'}) config: Config; @Prop() enabled: boolean = true; @Prop() jsScroll: boolean = false; + @PropDidChange('jsScroll') + jsScrollChanged(js: boolean) { + if (js) { + throw 'jsScroll: TODO!'; + } + } @Prop() onionScrollStart: ScrollCallback; @Prop() onionScroll: ScrollCallback; @@ -32,218 +39,34 @@ export class Scroll { @Event() ionScrollEnd: EventEmitter; protected ionViewDidLoad() { - if (Context.isServer) return; + if (Context.isServer) { + return; + } const gestureCtrl = Context.gesture = Context.gesture || new GestureController; this.gesture = gestureCtrl.createGesture('scroll', 100, false); } - - // Native Scroll ************************* - - @Listen('scroll', { passive: true }) - onNativeScroll() { - const self = this; - - if (!self.queued && self.enabled) { - self.queued = true; - - Context.dom.read(function(timeStamp) { - self.queued = false; - self.onScroll(timeStamp || Date.now()); - }); - } + protected ionViewDidUnload() { + this.gesture && this.gesture.destroy(); + this.gesture = this.detail = this.detail.event = null; } - onScroll(timeStamp: number) { - const self = this; - const detail = self.detail; - const positions = self.positions; - - detail.timeStamp = timeStamp; - - // get the current scrollTop - // ******** DOM READ **************** - detail.scrollTop = self.getTop(); - - // get the current scrollLeft - // ******** DOM READ **************** - detail.scrollLeft = self.getLeft(); - - if (!self.isScrolling) { - // currently not scrolling, so this is a scroll start - self.isScrolling = true; - - // remember the start positions - detail.startY = detail.scrollTop; - detail.startX = detail.scrollLeft; - - // new scroll, so do some resets - detail.velocityY = detail.velocityX = detail.deltaY = detail.deltaX = positions.length = 0; - - // emit only on the first scroll event - if (self.onionScrollStart) { - self.onionScrollStart(detail); - } else { - self.ionScrollStart.emit(detail); - } - } - - // actively scrolling - positions.push(detail.scrollTop, detail.scrollLeft, detail.timeStamp); - - if (positions.length > 3) { - // we've gotten at least 2 scroll events so far - detail.deltaY = (detail.scrollTop - detail.startY); - detail.deltaX = (detail.scrollLeft - detail.startX); - - var endPos = (positions.length - 1); - var startPos = endPos; - var timeRange = (detail.timeStamp - 100); - - // move pointer to position measured 100ms ago - for (var i = endPos; i > 0 && positions[i] > timeRange; i -= 3) { - startPos = i; - } - - if (startPos !== endPos) { - // compute relative movement between these two points - var movedTop = (positions[startPos - 2] - positions[endPos - 2]); - var movedLeft = (positions[startPos - 1] - positions[endPos - 1]); - var factor = 16.67 / (positions[startPos] - positions[endPos]); - - // based on XXms compute the movement to apply for each render step - detail.velocityY = movedTop * factor; - detail.velocityX = movedLeft * factor; - } - } - - clearTimeout(self.tmr); - self.tmr = setTimeout(function() { - - // haven't scrolled in a while, so it's a scrollend - self.isScrolling = false; - - Context.dom.read(function(timeStamp) { - if (!self.isScrolling) { - self.onEnd(timeStamp); - } - }); - }, 80); - - // emit on each scroll event - if (self.onionScroll) { - self.onionScroll(detail); - } else { - self.ionScroll.emit(detail); - } + @Method() + scrollToTop(duration: number): Promise { + return this.scrollToPoint(0, 0, duration); } + @Method() + scrollToBottom(duration: number): Promise { + const y = (this.el) + ? this.el.scrollHeight - this.el.clientHeight + : 0; - onEnd(timeStamp: number) { - const detail = this.detail; - - detail.timeStamp = timeStamp || Date.now(); - - // emit that the scroll has ended - if (this.onionScrollEnd) { - this.onionScrollEnd(detail); - } else { - this.ionScrollEnd.emit(detail); - } - } - - - enableJsScroll(contentTop: number, contentBottom: number) { - this.jsScroll = true; - - Context.enableListener(this, 'scroll', false); - Context.enableListener(this, 'touchstart', true); - - contentTop; contentBottom; - } - - - // Touch Scroll ************************* - - @Listen('touchstart', { passive: true, enabled: false }) - onTouchStart() { - if (!this.enabled) { - return; - } - - Context.enableListener(this, 'touchmove', true); - Context.enableListener(this, 'touchend', true); - - throw 'jsScroll: TODO!'; - } - - @Listen('touchmove', { passive: true, enabled: false }) - onTouchMove() { - if (!this.enabled) { - return; - } - } - - @Listen('touchend', { passive: true, enabled: false }) - onTouchEnd() { - Context.enableListener(this, 'touchmove', false); - Context.enableListener(this, 'touchend', false); - - if (!this.enabled) { - return; - } - } - - - /** - * DOM READ - */ - getTop() { - if (this.jsScroll) { - return this._t; - } - return this._t = this.el.scrollTop; - } - - /** - * DOM READ - */ - getLeft() { - if (this.jsScroll) { - return 0; - } - return this._l = this.el.scrollLeft; - } - - /** - * DOM WRITE - */ - setTop(top: number) { - this._t = top; - - if (this.jsScroll) { - this.el.style.transform = this.el.style.webkitTransform = `translate3d(${this._l * -1}px,${top * -1}px,0px)`; - - } else { - this.el.scrollTop = top; - } - } - - /** - * DOM WRITE - */ - setLeft(left: number) { - this._l = left; - - if (this.jsScroll) { - this.el.style.transform = this.el.style.webkitTransform = `translate3d(${left * -1}px,${this._t * -1}px,0px)`; - - } else { - this.el.scrollLeft = left; - } + return this.scrollToPoint(0, y, duration); } + @Method() scrollToPoint(x: number, y: number, duration: number, done?: Function): Promise { // scroll animation loop w/ easing // credit https://gist.github.com/dezinezync/5487119 @@ -333,26 +156,164 @@ export class Scroll { return promise; } - scrollToTop(duration: number): Promise { - return this.scrollToPoint(0, 0, duration); - } + // Native Scroll ************************* - scrollToBottom(duration: number): Promise { - let y = 0; - if (this.el) { - y = this.el.scrollHeight - this.el.clientHeight; + @Listen('scroll', { passive: true }) + protected onNativeScroll() { + if (!this.queued) { + this.queued = true; + + Context.dom.read((timeStamp) => { + this.queued = false; + this.onScroll(timeStamp); + }); + } + } + + private onScroll(timeStamp: number) { + const detail = this.detail; + const positions = this.positions; + + detail.timeStamp = timeStamp; + + // get the current scrollTop + // ******** DOM READ **************** + detail.scrollTop = this.getTop(); + + // get the current scrollLeft + // ******** DOM READ **************** + detail.scrollLeft = this.getLeft(); + + if (!this.isScrolling) { + // currently not scrolling, so this is a scroll start + this.isScrolling = true; + + // remember the start positions + detail.startY = detail.scrollTop; + detail.startX = detail.scrollLeft; + + // new scroll, so do some resets + detail.velocityY = detail.velocityX = detail.deltaY = detail.deltaX = positions.length = 0; + + // emit only on the first scroll event + if (this.onionScrollStart) { + this.onionScrollStart(detail); + } else { + this.ionScrollStart.emit(detail); + } + } + + // actively scrolling + positions.push(detail.scrollTop, detail.scrollLeft, detail.timeStamp); + + if (positions.length > 3) { + // we've gotten at least 2 scroll events so far + detail.deltaY = (detail.scrollTop - detail.startY); + detail.deltaX = (detail.scrollLeft - detail.startX); + + var endPos = (positions.length - 1); + var startPos = endPos; + var timeRange = (detail.timeStamp - 100); + + // move pointer to position measured 100ms ago + for (var i = endPos; i > 0 && positions[i] > timeRange; i -= 3) { + startPos = i; + } + + if (startPos !== endPos) { + // compute relative movement between these two points + var deltaY = (positions[startPos - 2] - positions[endPos - 2]); + var deltaX = (positions[startPos - 1] - positions[endPos - 1]); + var factor = 1 / (positions[startPos] - positions[endPos]); + + // based on XXms compute the movement to apply for each render step + detail.velocityY = deltaY * factor; + detail.velocityX = deltaX * factor; + } + } + + clearTimeout(this.tmr); + this.tmr = setTimeout(() => { + + // haven't scrolled in a while, so it's a scrollend + this.isScrolling = false; + + Context.dom.read((timeStamp) => { + if (!this.isScrolling) { + this.onEnd(timeStamp); + } + }); + }, 80); + + // emit on each scroll event + if (this.onionScroll) { + this.onionScroll(detail); + } else { + this.ionScroll.emit(detail); } - return this.scrollToPoint(0, y, duration); } - protected ionViewDidUnload() { - this.gesture && this.gesture.destroy(); - this.gesture = this.detail = this.detail.event = null; + private onEnd(timeStamp: number) { + const detail = this.detail; + + detail.timeStamp = timeStamp; + + // emit that the scroll has ended + if (this.onionScrollEnd) { + this.onionScrollEnd(detail); + } else { + this.ionScrollEnd.emit(detail); + } + } + + /** DOM READ */ + private getTop() { + if (this.jsScroll) { + return this._t; + } + return this._t = this.el.scrollTop; + } + + /** DOM READ */ + private getLeft() { + if (this.jsScroll) { + return 0; + } + return this._l = this.el.scrollLeft; + } + + /** DOM WRITE */ + private setTop(top: number) { + this._t = top; + + if (this.jsScroll) { + this.el.style.transform = this.el.style.webkitTransform = `translate3d(${this._l * -1}px,${top * -1}px,0px)`; + + } else { + this.el.scrollTop = top; + } + } + + /** DOM WRITE */ + private setLeft(left: number) { + this._l = left; + + if (this.jsScroll) { + this.el.style.transform = this.el.style.webkitTransform = `translate3d(${left * -1}px,${this._t * -1}px,0px)`; + + } else { + this.el.scrollLeft = left; + } } protected render() { - return ; + return ( + // scroll-inner is used to keep custom user padding +
+ +
+ ); } } @@ -366,14 +327,8 @@ export interface ScrollDetail extends GestureDetail { contentWidth?: number; contentTop?: number; contentBottom?: number; - contentElement?: HTMLElement; - fixedElement?: HTMLElement; - scrollElement?: HTMLElement; - headerElement?: HTMLElement; - footerElement?: HTMLElement; } - export interface ScrollCallback { (detail?: ScrollDetail): boolean|void; } diff --git a/packages/core/src/components/toolbar/toolbar.ios.scss b/packages/core/src/components/toolbar/toolbar.ios.scss index 78a42e65be..89a2531df4 100644 --- a/packages/core/src/components/toolbar/toolbar.ios.scss +++ b/packages/core/src/components/toolbar/toolbar.ios.scss @@ -273,7 +273,7 @@ $navbar-ios-height: $toolbar-ios-height !default; .back-button-ios { @include margin(0); - z-index: 99; + z-index: $z-index-toolbar-buttons; overflow: visible; order: map-get($toolbar-order-ios, back-button); diff --git a/packages/core/src/components/toolbar/toolbar.scss b/packages/core/src/components/toolbar/toolbar.scss index 719b2a0cc7..377a9d4694 100644 --- a/packages/core/src/components/toolbar/toolbar.scss +++ b/packages/core/src/components/toolbar/toolbar.scss @@ -4,12 +4,10 @@ // Toolbar // -------------------------------------------------- -ion-toolbar { +.toolbar { position: relative; z-index: $z-index-toolbar; -} -.toolbar { display: flex; overflow: hidden; @@ -70,7 +68,7 @@ ion-buttons div { // TODO this is a temp hack to fix segment overlapping ion-nav-item ion-buttons, .bar-button-menutoggle { - z-index: 99; + z-index: $z-index-toolbar-buttons; transform: translateZ(0); } diff --git a/packages/core/src/index.d.ts b/packages/core/src/index.d.ts index 5bdf0a4a9c..dcd8691d8b 100644 --- a/packages/core/src/index.d.ts +++ b/packages/core/src/index.d.ts @@ -112,6 +112,8 @@ export { ToastController } + export interface StencilElement extends HTMLElement { - componentOnReady?: (cb: (elm?: StencilElement) => void) => void; + componentOnReady(): Promise; + componentOnReady(done: (cmp?: HTMLElement) => void): void; } \ No newline at end of file diff --git a/packages/core/src/navigation/transitions/transition.ios.ts b/packages/core/src/navigation/transitions/transition.ios.ts index c258a9baf3..fa0b7d563f 100644 --- a/packages/core/src/navigation/transitions/transition.ios.ts +++ b/packages/core/src/navigation/transitions/transition.ios.ts @@ -27,7 +27,7 @@ export function buildIOSTransition(rootTransition: Transition, enteringView: Vie rootTransition.addElement(enteringView.element); - rootTransition.beforeAddClass('show-page'); + rootTransition.beforeRemoveClass('hide-page'); const backDirection = (opts.direction === 'back'); diff --git a/packages/core/src/navigation/transitions/transition.md.ts b/packages/core/src/navigation/transitions/transition.md.ts index 514e6af532..32a87d734d 100644 --- a/packages/core/src/navigation/transitions/transition.md.ts +++ b/packages/core/src/navigation/transitions/transition.md.ts @@ -15,7 +15,7 @@ export function buildMdTransition(rootTransition: Transition, enteringView: View rootTransition.leavingView = leavingView; rootTransition.addElement(enteringView.element); - rootTransition.beforeAddClass('show-page'); + rootTransition.beforeRemoveClass('hide-page'); const backDirection = (opts.direction === 'back'); if (enteringView) { diff --git a/packages/core/src/themes/ionic.globals.scss b/packages/core/src/themes/ionic.globals.scss index 5529d99c9c..fbda7510d7 100644 --- a/packages/core/src/themes/ionic.globals.scss +++ b/packages/core/src/themes/ionic.globals.scss @@ -38,9 +38,12 @@ $z-index-refresher: 0; // scss-lint:disable DefaultRule $z-index-page-container: 0; // scss-lint:disable DefaultRule $z-index-toolbar: 10; // scss-lint:disable DefaultRule $z-index-toolbar-background: -1; // scss-lint:disable DefaultRule +$z-index-toolbar-buttons: 99; // scss-lint:disable DefaultRule $z-index-backdrop: 2; // scss-lint:disable DefaultRule $z-index-overlay-wrapper: 10; // scss-lint:disable DefaultRule $z-index-item-options: 1; // scss-lint:disable DefaultRule -$z-index-item-divider: 100; // scss-lint:disable DefaultRule +$z-index-item-divider: 100; // scss-lint:disable DefaultRule + +$z-index-reorder-selected: 100; // scss-lint:disable DefaultRule diff --git a/packages/core/src/utils/helpers.ts b/packages/core/src/utils/helpers.ts index 16e6784393..c3aa86b56f 100644 --- a/packages/core/src/utils/helpers.ts +++ b/packages/core/src/utils/helpers.ts @@ -138,6 +138,14 @@ export function getParentElement(elm: any) { return null; } +export function getPageElement(el: HTMLElement) { + const page = el.closest('ion-page,.ion-page,page-inner'); + if (page) { + return page; + } + return getParentElement(el); +} + export function applyStyles(elm: HTMLElement, styles: {[styleProp: string]: string|number}) { const styleProps = Object.keys(styles); @@ -148,25 +156,6 @@ export function applyStyles(elm: HTMLElement, styles: {[styleProp: string]: stri } } -export function getToolbarHeight(toolbarTagName: string, pageChildren: HTMLElement[], mode: string, iosHeight: string, defaultHeight: string) { - for (var i = 0; i < pageChildren.length; i++) { - if (pageChildren[i].tagName === toolbarTagName) { - var headerHeight = pageChildren[i].getAttribute(`${mode}-height`); - if (headerHeight) { - return headerHeight; - } - - if (mode === 'ios') { - return iosHeight; - } - - return defaultHeight; - } - } - - return ''; -} - /** @hidden */ export type Side = 'left' | 'right' | 'start' | 'end'; @@ -216,11 +205,7 @@ export function swipeShouldReset(isResetDirection: boolean, isMovingFast: boolea } export function isReady(element: Element): Promise { - return new Promise((resolve) => { - (element as StencilElement).componentOnReady((elm: HTMLElement) => { - resolve(elm); - }); - }); + return (element as StencilElement).componentOnReady(); } export function getOrAppendElement(tagName: string): Element { diff --git a/packages/core/stencil.config.js b/packages/core/stencil.config.js index ef153b992e..56a33b4087 100644 --- a/packages/core/stencil.config.js +++ b/packages/core/stencil.config.js @@ -4,7 +4,7 @@ exports.config = { generateWWW: false, bundles: [ { components: ['ion-animation-controller'] }, - { components: ['ion-app', 'ion-content', 'ion-fixed', 'ion-footer', 'ion-header', 'ion-navbar', 'ion-page', 'ion-title', 'ion-toolbar'] }, + { components: ['ion-app', 'ion-content', 'ion-footer', 'ion-header', 'ion-navbar', 'ion-page', 'ion-title', 'ion-toolbar'] }, { components: ['ion-action-sheet', 'ion-action-sheet-controller'] }, { components: ['ion-alert', 'ion-alert-controller'] }, { components: ['ion-avatar', 'ion-badge', 'ion-thumbnail'] },