refactor(content): use css instead of js

This commit is contained in:
Manu Mtz.-Almeida
2017-10-26 22:11:46 +02:00
parent 10c77e7513
commit 094816de01
30 changed files with 510 additions and 575 deletions

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -69,9 +69,9 @@ export class IonApp implements App {
}
protected render() {
return ([
return (
<slot></slot>
]);
);
}
}
@@ -122,9 +122,3 @@ export function handleBackButtonClick(): Promise<any> {
console.log('todo');
});
}

View File

@@ -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
// --------------------------------------------------

View File

@@ -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);
}
}

View File

@@ -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 (
<ion-scroll style={scrollStyle} class={scrollClasses}>
this.resize();
return [
<ion-scroll class={scrollClasses}>
<slot></slot>
</ion-scroll>
);
</ion-scroll>,
<slot name='fixed'></slot>
];
}
}

View File

@@ -0,0 +1,142 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Ionic Chips</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<script src="/dist/ionic.js"></script>
</head>
<body>
<ion-app>
<ion-page>
<ion-header id="header">
<ion-toolbar style="display: none" id="toolbar2">
<ion-title>hidden toolbar</ion-title>
</ion-toolbar>
<ion-toolbar>
<ion-title>Header</ion-title>
</ion-toolbar>
</ion-header>
<div style="display: flex; flex: 1">
<ion-content padding style="text-align: center; flex: 2" id="content">
<f></f>
<ion-button onclick="toggleFullscreen()">Toggle content.fullscreen</ion-button>
<p>
<ion-button onclick="toggleDisplay('header')" color="secondary">Toggle header</ion-button>
<ion-button onclick="toggleDisplay('footer')" color="secondary">Toggle footer</ion-button>
<ion-button onclick="toggleDisplay('toolbar2')" color="secondary">Toggle 2nd toolbar</ion-button>
</p>
<p>
<ion-button onclick="toggleDisplay('content2')" color="danger">Toggle bottom content</ion-button>
<ion-button onclick="toggleDisplay('content3')" color="danger">Toggle right content</ion-button>
</p>
<p>
<ion-button onclick="myanimation()" color="dark">Animate</ion-button>
</p>
<f></f>
<f></f>
<f></f>
<f></f>
<f></f>
<f></f>
<f></f>
<f></f>
</ion-content>
<ion-content padding style="display: none" id="content3">
<f></f>
<f></f>
<f></f>
<f></f>
<f></f>
<f></f>
</ion-content>
</div>
<ion-content padding style="display: none" id="content2">
<f></f>
<f></f>
<f></f>
<f></f>
<f></f>
<f></f>
</ion-content>
<ion-footer id="footer">
<ion-toolbar>
<ion-title>Footer</ion-title>
</ion-toolbar>
</ion-footer>
</ion-page>
<script>
function toggleFullscreen() {
const content = document.getElementById('content');
content.fullscreen = !content.fullscreen;
console.log('content.fullscren =', content.fullscreen);
}
function toggleDisplay(id) {
const el = document.getElementById(id);
el.style.display = (!el.style.display) ? 'none' : null;
}
function myanimation() {
const content = document.getElementById('content');
const header = document.getElementById('header');
const footer = document.getElementById('footer');
content.classList.add('animation');
header.classList.add('animation');
footer.classList.add('animation');
}
</script>
<style>
ion-header.animation,
ion-footer.animation,
ion-content.animation {
animation: ani1 5s infinite;
}
#content2 ion-scroll {
background: black;
}
@keyframes ani1 {
0% {
transform: translateX(0)
}
25% {
transform: translateX(-100%)
}
50% {
transform: translateX(0)
}
75% {
transform: translateX(100%)
}
100% {
transform: translateX(0%);
}
}
ion-content.animation {
animation-delay: 500ms;
}
ion-footer.animation {
animation-delay: 1000ms;
}
.toolbar-background {
background: #f8f8f8b8;
}
f {
display: block;
margin: 15px auto;
max-width: 150px;
height: 150px;
background: blue;
}
f:last-of-type {
background: yellow;
}
</style>
</ion-app>
</body>
</html>

View File

@@ -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);
}

View File

@@ -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 (
<slot></slot>
);
}
}

View File

@@ -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);
}

View File

@@ -10,7 +10,7 @@
<body>
<ion-app>
<ion-page main class="show-page">
<ion-page main>
<ion-header>
<ion-toolbar>

View File

@@ -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

View File

@@ -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> = T &
{ componentOnReady(): Promise<T> } &
{ 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<MenuController>;
@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() {

View File

@@ -11,14 +11,11 @@ export class StencilNavDelegate implements FrameworkDelegate {
@Method()
attachViewToDom(nav: Nav, enteringView: ViewController): Promise<any> {
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();
});
}

View File

@@ -18,9 +18,9 @@ export class PageOne {
protected render() {
return [<ion-header>
<ion-navbar>
<ion-toolbar>
<ion-title>Page One</ion-title>
</ion-navbar>
</ion-toolbar>
</ion-header>,
<ion-content>
Page One Content

View File

@@ -1,6 +0,0 @@
@import "../../themes/ionic.globals.ios";
@import "./page";
// iOS Page
// --------------------------------------------------

View File

@@ -1,6 +0,0 @@
@import "../../themes/ionic.globals.md";
@import "./page";
// Material Design Page
// --------------------------------------------------

View File

@@ -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;
}

View File

@@ -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() {

View File

@@ -1,6 +0,0 @@
@import "../../themes/ionic.globals.wp";
@import "./page";
// Windows Page
// --------------------------------------------------

View File

@@ -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 {

View File

@@ -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<void> {
return this.scrollToPoint(0, 0, duration);
}
@Method()
scrollToBottom(duration: number): Promise<void> {
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<any> {
// 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<void> {
return this.scrollToPoint(0, 0, duration);
}
// Native Scroll *************************
scrollToBottom(duration: number): Promise<void> {
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 <slot></slot>;
return (
// scroll-inner is used to keep custom user padding
<div class='scroll-inner'>
<slot></slot>
</div>
);
}
}
@@ -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;
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -112,6 +112,8 @@ export {
ToastController
}
export interface StencilElement extends HTMLElement {
componentOnReady?: (cb: (elm?: StencilElement) => void) => void;
componentOnReady(): Promise<HTMLElement>;
componentOnReady(done: (cmp?: HTMLElement) => void): void;
}

View File

@@ -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');

View File

@@ -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) {

View File

@@ -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

View File

@@ -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<any> {
return new Promise((resolve) => {
(element as StencilElement).componentOnReady((elm: HTMLElement) => {
resolve(elm);
});
});
return (element as StencilElement).componentOnReady();
}
export function getOrAppendElement(tagName: string): Element {

View File

@@ -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'] },