From 997cda06674cef269b338cc4137618619ccdf4f4 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Fri, 10 Aug 2018 00:00:18 +0200 Subject: [PATCH] refactor(scroll): merge into ion-content --- angular/BREAKING.md | 18 ++ core/src/components.d.ts | 131 +++----- .../content-interface.ts} | 0 core/src/components/content/content.scss | 66 +++- core/src/components/content/content.tsx | 285 +++++++++++++++-- core/src/components/content/readme.md | 82 ++++- .../components/content/test/basic/index.html | 6 +- .../infinite-scroll/infinite-scroll.tsx | 86 +++--- .../infinite-scroll/test/basic/index.html | 35 +-- core/src/components/popover/popover.scss | 9 +- core/src/components/refresher/refresher.scss | 1 - core/src/components/refresher/refresher.tsx | 4 +- .../reorder-group/reorder-group.scss | 4 + .../reorder-group/reorder-group.tsx | 2 +- core/src/components/scroll/readme.md | 102 ------- core/src/components/scroll/scroll.scss | 35 --- core/src/components/scroll/scroll.tsx | 286 ------------------ core/src/components/tabbar/readme.md | 14 - core/src/components/tabbar/tabbar.md.scss | 11 - core/src/components/tabbar/tabbar.scss | 19 -- core/src/components/tabbar/tabbar.tsx | 158 ++-------- core/src/components/tabs/readme.md | 14 - core/src/components/tabs/tabs.tsx | 22 +- .../components/tabs/test/scroll/index.html | 37 --- .../virtual-scroll/virtual-scroll.tsx | 16 +- core/src/interface.d.ts | 2 +- .../src/utils/input-shims/hacks/hide-caret.ts | 2 +- .../utils/input-shims/hacks/scroll-assist.ts | 2 +- core/src/utils/input-shims/input-shims.ts | 7 +- core/src/utils/status-tap.ts | 2 +- core/stencil.config.js | 2 +- 31 files changed, 548 insertions(+), 912 deletions(-) rename core/src/components/{scroll/scroll-interface.ts => content/content-interface.ts} (100%) delete mode 100644 core/src/components/scroll/readme.md delete mode 100644 core/src/components/scroll/scroll.scss delete mode 100644 core/src/components/scroll/scroll.tsx delete mode 100644 core/src/components/tabs/test/scroll/index.html diff --git a/angular/BREAKING.md b/angular/BREAKING.md index 91de097462..b1e3609b81 100644 --- a/angular/BREAKING.md +++ b/angular/BREAKING.md @@ -44,6 +44,7 @@ A list of the breaking changes introduced to each component in Ionic Angular v4. - [Radio](#radio) - [Range](#range) - [Refresher](#refresher) +- [Scroll](#scroll) - [Segment](#segment) - [Select](#select) - [Spinner](#spinner) @@ -1113,6 +1114,23 @@ The `enabled` property (with a default value of `true`) has been renamed to `dis ``` +## Scroll + +`ion-scroll` has been removed, fortunatelly `ion-content` can work as a drop-in replacement: + +```diff +- ++ +``` + +Another very good option is to style a `div` to become scrollable using CSS: + +```css +div.scrollable { + overflow: scroll +} +``` + ## Segment diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 71280a9a46..7a531851bb 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -619,15 +619,35 @@ declare global { * If true, the content will scroll behind the headers and footers. This effect can easily be seen by setting the toolbar to transparent. */ 'fullscreen': boolean; - 'getScrollElement': () => HTMLIonScrollElement; + 'getScrollElement': () => HTMLElement; /** - * By default `ion-content` uses an `ion-scroll` under the hood to implement scrolling, if you want to disable the content scrolling for a given page, set this property to `false`. + * Scroll by a specified X/Y distance in the component */ - 'scrollEnabled': boolean; + 'scrollByPoint': (x: number, y: number, duration: number) => Promise; /** * Because of performance reasons, ionScroll events are disabled by default, in order to enable them and start listening from (ionScroll), set this property to `true`. */ 'scrollEvents': boolean; + /** + * Scroll to the bottom of the component + */ + 'scrollToBottom': (duration?: number) => Promise; + /** + * Scroll to a specified X/Y location in the component + */ + 'scrollToPoint': (x: number | undefined, y: number | undefined, duration?: number) => Promise; + /** + * Scroll to the top of the component + */ + 'scrollToTop': (duration?: number) => Promise; + /** + * If you want to enable the content scrolling in the X axis, set this property to `true`. + */ + 'scrollX': boolean; + /** + * If you want to disable the content scrolling in the Y axis, set this property to `false`. + */ + 'scrollY': boolean; } interface IonDatetime { @@ -1946,37 +1966,6 @@ declare global { } - interface IonScroll { - /** - * If true and the content does not cause an overflow scroll, the scroll interaction will cause a bounce. If the content exceeds the bounds of ionScroll, nothing will change. Note, the does not disable the system bounce on iOS. That is an OS level setting. - */ - 'forceOverscroll': boolean; - /** - * The mode for component. - */ - 'mode': Mode; - /** - * Scroll by a specified X/Y distance in the component - */ - 'scrollByPoint': (x: number, y: number, duration: number) => Promise; - /** - * If true, the component will emit scroll events. - */ - 'scrollEvents': boolean; - /** - * Scroll to the bottom of the component - */ - 'scrollToBottom': (duration: number) => Promise; - /** - * Scroll to a specified X/Y location in the component - */ - 'scrollToPoint': (x: number, y: number, duration: number) => Promise; - /** - * Scroll to the top of the component - */ - 'scrollToTop': (duration: number) => Promise; - } - interface IonSearchbar { /** * If true, enable searchbar animation. Default `false`. @@ -2398,10 +2387,6 @@ declare global { * Set the position of the tabbar, relative to the content. Available options: `"top"`, `"bottom"`. */ 'placement': TabbarPlacement; - /** - * If true, the tabs will be scrollable when there are enough tabs to overflow the width of the screen. - */ - 'scrollable': boolean; /** * The selected tab component */ @@ -2434,10 +2419,6 @@ declare global { * A unique name for the tabs. */ 'name': string; - /** - * If true, the tabs will be scrollable when there are enough tabs to overflow the width of the screen. - */ - 'scrollable': boolean; /** * Index or the Tab instance, of the tab to select. */ @@ -3342,14 +3323,6 @@ declare global { }; - interface HTMLIonScrollElement extends StencilComponents.IonScroll, HTMLStencilElement {} - - var HTMLIonScrollElement: { - prototype: HTMLIonScrollElement; - new (): HTMLIonScrollElement; - }; - - interface HTMLIonSearchbarElement extends StencilComponents.IonSearchbar, HTMLStencilElement {} var HTMLIonSearchbarElement: { @@ -3630,7 +3603,6 @@ declare global { 'ion-router-outlet': JSXElements.IonRouterOutletAttributes; 'ion-router': JSXElements.IonRouterAttributes; 'ion-row': JSXElements.IonRowAttributes; - 'ion-scroll': JSXElements.IonScrollAttributes; 'ion-searchbar': JSXElements.IonSearchbarAttributes; 'ion-segment-button': JSXElements.IonSegmentButtonAttributes; 'ion-segment': JSXElements.IonSegmentAttributes; @@ -4218,13 +4190,29 @@ declare global { */ 'fullscreen'?: boolean; /** - * By default `ion-content` uses an `ion-scroll` under the hood to implement scrolling, if you want to disable the content scrolling for a given page, set this property to `false`. + * Emitted while scrolling. This event is disabled by default. Look at the property: `scrollEvents` */ - 'scrollEnabled'?: boolean; + 'onIonScroll'?: (event: CustomEvent) => void; + /** + * Emitted when the scroll has ended. + */ + 'onIonScrollEnd'?: (event: CustomEvent) => void; + /** + * Emitted when the scroll has started. + */ + 'onIonScrollStart'?: (event: CustomEvent) => void; /** * Because of performance reasons, ionScroll events are disabled by default, in order to enable them and start listening from (ionScroll), set this property to `true`. */ 'scrollEvents'?: boolean; + /** + * If you want to enable the content scrolling in the X axis, set this property to `true`. + */ + 'scrollX'?: boolean; + /** + * If you want to disable the content scrolling in the Y axis, set this property to `false`. + */ + 'scrollY'?: boolean; } export interface IonDatetimeAttributes extends HTMLAttributes { @@ -5510,33 +5498,6 @@ declare global { } - export interface IonScrollAttributes extends HTMLAttributes { - /** - * If true and the content does not cause an overflow scroll, the scroll interaction will cause a bounce. If the content exceeds the bounds of ionScroll, nothing will change. Note, the does not disable the system bounce on iOS. That is an OS level setting. - */ - 'forceOverscroll'?: boolean; - /** - * The mode for component. - */ - 'mode'?: Mode; - /** - * Emitted while scrolling. This event is disabled by default. Look at the property: `scrollEvents` - */ - 'onIonScroll'?: (event: CustomEvent) => void; - /** - * Emitted when the scroll has ended. - */ - 'onIonScrollEnd'?: (event: CustomEvent) => void; - /** - * Emitted when the scroll has started. - */ - 'onIonScrollStart'?: (event: CustomEvent) => void; - /** - * If true, the component will emit scroll events. - */ - 'scrollEvents'?: boolean; - } - export interface IonSearchbarAttributes extends HTMLAttributes { /** * If true, enable searchbar animation. Default `false`. @@ -6033,10 +5994,6 @@ declare global { * Set the position of the tabbar, relative to the content. Available options: `"top"`, `"bottom"`. */ 'placement'?: TabbarPlacement; - /** - * If true, the tabs will be scrollable when there are enough tabs to overflow the width of the screen. - */ - 'scrollable'?: boolean; /** * The selected tab component */ @@ -6076,10 +6033,6 @@ declare global { * Emitted when the navigation will load a component. */ 'onIonNavWillLoad'?: (event: CustomEvent) => void; - /** - * If true, the tabs will be scrollable when there are enough tabs to overflow the width of the screen. - */ - 'scrollable'?: boolean; /** * If true, the tabbar will be hidden. Defaults to `false`. */ @@ -6470,7 +6423,6 @@ declare global { 'ion-router-outlet': HTMLIonRouterOutletElement 'ion-router': HTMLIonRouterElement 'ion-row': HTMLIonRowElement - 'ion-scroll': HTMLIonScrollElement 'ion-searchbar': HTMLIonSearchbarElement 'ion-segment-button': HTMLIonSegmentButtonElement 'ion-segment': HTMLIonSegmentElement @@ -6576,7 +6528,6 @@ declare global { 'ion-router-outlet': HTMLIonRouterOutletElement; 'ion-router': HTMLIonRouterElement; 'ion-row': HTMLIonRowElement; - 'ion-scroll': HTMLIonScrollElement; 'ion-searchbar': HTMLIonSearchbarElement; 'ion-segment-button': HTMLIonSegmentButtonElement; 'ion-segment': HTMLIonSegmentElement; diff --git a/core/src/components/scroll/scroll-interface.ts b/core/src/components/content/content-interface.ts similarity index 100% rename from core/src/components/scroll/scroll-interface.ts rename to core/src/components/content/content-interface.ts diff --git a/core/src/components/content/content.scss b/core/src/components/content/content.scss index 2d8f5042aa..26fdc1240c 100644 --- a/core/src/components/content/content.scss +++ b/core/src/components/content/content.scss @@ -19,14 +19,9 @@ flex: 1; - width: 100%; - height: 100%; - /* stylelint-disable */ - /* TODO: find a better solution in padding.css, that does not require !important, */ - margin: 0 !important; - padding: 0 !important; + margin: 0 !important; /* stylelint-enable */ background-color: #{current-color(base)}; @@ -35,23 +30,64 @@ contain: layout size style; } -:host(.scroll-disabled), -ion-scroll { +:host(.ion-color-outer), +:host(.outer-content) { + --ion-color-base: #{$background-color-step-50}; +} + +.inner-scroll { + @include position( + calc(var(--offset-top) * -1), 0, + calc(var(--offset-bottom) * -1), 0 + ); @include padding( calc(var(--padding-top) + var(--offset-top)), var(--padding-end), calc(var(--padding-bottom) + var(--keyboard-offset) + var(--offset-bottom)), var(--padding-start) ); + + position: absolute; + + box-sizing: border-box; + + overflow: hidden; } -:host(.ion-color-outer), -:host(.outer-content) { - --ion-color-base: #{$background-color-step-50}; +.scroll-x.scroll.y { + -webkit-overflow-scrolling: touch; + will-change: scroll-position; } -:host(.content-size) ion-scroll { - position: relative; - - contain: none; +.scroll-y { + overflow-y: auto; } + +.scroll-x { + overflow-x: auto; +} + +.scroll-enabled { + overflow-x: hidden; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + will-change: scroll-position; +} + +.overscroll::before, +.overscroll::after { + position: absolute; + + width: 1px; + height: 1px; + + content: ""; +} + +.overscroll::before { + bottom: -1px; +} + +.overscroll::after { + top: -1px; +} \ No newline at end of file diff --git a/core/src/components/content/content.tsx b/core/src/components/content/content.tsx index e7486f1161..7c889efce4 100644 --- a/core/src/components/content/content.tsx +++ b/core/src/components/content/content.tsx @@ -1,7 +1,7 @@ -import { Component, Element, Listen, Method, Prop, QueueApi } from '@stencil/core'; +import { Component, Element, Event, EventEmitter, Listen, Method, Prop, QueueApi } from '@stencil/core'; -import { Color, Config, Mode } from '../../interface'; -import { createColorClasses, hostContext } from '../../utils/theme'; +import { Color, Config, Mode, ScrollBaseDetail, ScrollDetail } from '../../interface'; +import { createColorClasses } from '../../utils/theme'; @Component({ tag: 'ion-content', @@ -13,9 +13,35 @@ import { createColorClasses, hostContext } from '../../utils/theme'; }) export class Content { + private watchDog: any; + private isScrolling = false; + private lastScroll = 0; + private queued = false; private cTop = -1; private cBottom = -1; - private scrollEl?: HTMLIonScrollElement; + private scrollEl!: HTMLElement; + + // Detail is used in a hot loop in the scroll event, by allocating it here + // V8 will be able to inline any read/write to it since it's a monomorphic class. + // https://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html + private detail: ScrollDetail = { + scrollTop: 0, + scrollLeft: 0, + type: 'scroll', + event: undefined!, + startX: 0, + startY: 0, + startTimeStamp: 0, + currentX: 0, + currentY: 0, + velocityX: 0, + velocityY: 0, + deltaX: 0, + deltaY: 0, + timeStamp: 0, + data: undefined, + isScrolling: true, + }; mode!: Mode; @Prop() color?: Color; @@ -24,6 +50,7 @@ export class Content { @Prop({ context: 'config' }) config!: Config; @Prop({ context: 'queue' }) queue!: QueueApi; + @Prop({ context: 'window' }) win!: Window; /** * If true, the content will scroll behind the headers @@ -37,13 +64,17 @@ export class Content { * If the content exceeds the bounds of ionContent, nothing will change. * Note, the does not disable the system bounce on iOS. That is an OS level setting. */ - @Prop() forceOverscroll?: boolean; + @Prop({ mutable: true }) forceOverscroll?: boolean; /** - * By default `ion-content` uses an `ion-scroll` under the hood to implement scrolling, - * if you want to disable the content scrolling for a given page, set this property to `false`. + * If you want to enable the content scrolling in the X axis, set this property to `true`. */ - @Prop() scrollEnabled = true; + @Prop() scrollX = false; + + /** + * If you want to disable the content scrolling in the Y axis, set this property to `false`. + */ + @Prop() scrollY = true; /** * Because of performance reasons, ionScroll events are disabled by default, in order to enable them @@ -51,24 +82,44 @@ export class Content { */ @Prop() scrollEvents = false; + /** + * Emitted when the scroll has started. + */ + @Event() ionScrollStart!: EventEmitter; + + /** + * Emitted while scrolling. This event is disabled by default. + * Look at the property: `scrollEvents` + */ + @Event() ionScroll!: EventEmitter; + + /** + * Emitted when the scroll has ended. + */ + @Event() ionScrollEnd!: EventEmitter; + @Listen('body:ionNavDidChange') onNavChanged() { this.resize(); } + componentWillLoad() { + if (this.forceOverscroll === undefined) { + this.forceOverscroll = this.mode === 'ios' && ('ontouchstart' in this.win); + } + } + componentDidLoad() { this.resize(); } - @Method() - getScrollElement(): HTMLIonScrollElement { - return this.scrollEl!; + componentDidUnload() { + if (this.watchDog) { + clearInterval(this.watchDog); + } } private resize() { - if (!this.scrollEl) { - return; - } if (this.fullscreen) { this.queue.read(this.readDimensions.bind(this)); } else if (this.cTop !== 0 || this.cBottom !== 0) { @@ -89,35 +140,173 @@ export class Content { } } + private onScroll(ev: UIEvent) { + const timeStamp = Date.now(); + const didStart = !this.isScrolling; + this.lastScroll = timeStamp; + if (didStart) { + this.onScrollStart(); + } + if (!this.queued && this.scrollEvents) { + this.queued = true; + this.queue.read(ts => { + this.queued = false; + this.detail.event = ev; + updateScrollDetail(this.detail, this.el, ts, didStart); + this.ionScroll.emit(this.detail); + }); + } + } + + @Method() + getScrollElement(): HTMLElement { + return this.scrollEl; + } + + /** + * Scroll to the top of the component + */ + @Method() + scrollToTop(duration = 100): Promise { + return this.scrollToPoint(0, undefined, duration); + } + + /** + * Scroll to the bottom of the component + */ + @Method() + scrollToBottom(duration = 100): Promise { + const y = this.scrollEl.scrollHeight - this.scrollEl.clientHeight; + return this.scrollToPoint(undefined, y, duration); + } + + /** + * Scroll by a specified X/Y distance in the component + */ + @Method() + scrollByPoint(x: number, y: number, duration: number): Promise { + return this.scrollToPoint(x + this.scrollEl.scrollLeft, y + this.scrollEl.scrollTop, duration); + } + + /** + * Scroll to a specified X/Y location in the component + */ + @Method() + async scrollToPoint(x: number | undefined, y: number | undefined, duration = 0): Promise { + const el = this.scrollEl; + if (duration < 32) { + if (y != null) { + el.scrollTop = y; + } + if (x != null) { + el.scrollLeft = x; + } + return; + } + + let resolve!: () => void; + let startTime: number; + const promise = new Promise(r => resolve = r); + const fromY = el.scrollTop; + const fromX = el.scrollLeft; + + const deltaY = y != null ? y - fromY : 0; + const deltaX = x != null ? x - fromX : 0; + + // scroll loop + const step = (timeStamp: number) => { + let linearTime = Math.min(1, ((timeStamp - startTime) / duration)); + const easedT = (--linearTime) * linearTime * linearTime + 1; + + if (deltaY !== 0) { + el.scrollTop = (easedT * deltaY) + fromY; + } + if (deltaX !== 0) { + el.scrollLeft = Math.floor((easedT * deltaX) + fromX); + } + + if (easedT < 1) { + // do not use DomController here + // must use nativeRaf in order to fire in the next frame + // TODO: remove as any + requestAnimationFrame(step); + + } else { + this.isScrolling = false; + resolve(); + } + }; + + // start scroll loop + this.isScrolling = true; + + // chill out for a frame first + requestAnimationFrame(ts => { + startTime = ts; + step(ts); + }); + return promise; + } + + private onScrollStart() { + this.isScrolling = true; + this.ionScrollStart.emit({ + isScrolling: true + }); + + if (this.watchDog) { + clearInterval(this.watchDog); + } + // watchdog + this.watchDog = setInterval(() => { + if (this.lastScroll < Date.now() - 120) { + this.onScrollEnd(); + } + }, 100); + } + + private onScrollEnd() { + + clearInterval(this.watchDog); + this.watchDog = null; + this.isScrolling = false; + this.ionScrollEnd.emit({ + isScrolling: false + }); + } + hostData() { return { class: { ...createColorClasses(this.color), - 'content-size': hostContext('ion-popover', this.el), - 'scroll-disabled': !this.scrollEnabled, + 'overscroll': this.forceOverscroll, + }, + style: { + '--offset-top': `${this.cTop}px`, + '--offset-bottom': `${this.cBottom}px`, } }; } render() { + const { scrollX, scrollY, forceOverscroll } = this; + const scrollEnabled = scrollX || scrollY; + this.resize(); return [ - this.scrollEnabled ? ( - this.scrollEl = el as any} - mode={this.mode} - scrollEvents={this.scrollEvents} - forceOverscroll={this.forceOverscroll} - style={{ - 'top': `${-this.cTop}px`, - 'bottom': `${-this.cBottom}px`, - '--offset-top': `${this.cTop}px`, - '--offset-bottom': `${this.cBottom}px`, - }}> - - - ) : , +
this.scrollEl = el!} + onScroll={ev => this.onScroll(ev)}> + +
, ]; } @@ -146,3 +335,37 @@ function getPageElement(el: HTMLElement) { } return getParentElement(el); } + +// ******** DOM READ **************** +function updateScrollDetail( + detail: ScrollDetail, + el: HTMLElement, + timestamp: number, + didStart: boolean +) { + const prevX = detail.currentX; + const prevY = detail.currentY; + const prevT = detail.timeStamp; + const currentX = el.scrollLeft; + const currentY = el.scrollTop; + if (didStart) { + // remember the start positions + detail.startTimeStamp = timestamp; + detail.startX = currentX; + detail.startY = currentY; + detail.velocityX = detail.velocityY = 0; + } + detail.timeStamp = timestamp; + detail.currentX = detail.scrollLeft = currentX; + detail.currentY = detail.scrollTop = currentY; + detail.deltaX = currentX - detail.startX; + detail.deltaY = currentY - detail.startY; + + const timeDelta = timestamp - prevT; + if (timeDelta > 0 && timeDelta < 100) { + const velocityX = (currentX - prevX) / timeDelta; + const velocityY = (currentY - prevY) / timeDelta; + detail.velocityX = velocityX * 0.7 + detail.velocityX * 0.3; + detail.velocityY = velocityY * 0.7 + detail.velocityY * 0.3; + } +} diff --git a/core/src/components/content/readme.md b/core/src/components/content/readme.md index 77853d2464..14cd139292 100644 --- a/core/src/components/content/readme.md +++ b/core/src/components/content/readme.md @@ -32,14 +32,6 @@ and footers. This effect can easily be seen by setting the toolbar to transparent. -#### scrollEnabled - -boolean - -By default `ion-content` uses an `ion-scroll` under the hood to implement scrolling, -if you want to disable the content scrolling for a given page, set this property to `false`. - - #### scrollEvents boolean @@ -48,6 +40,20 @@ Because of performance reasons, ionScroll events are disabled by default, in ord and start listening from (ionScroll), set this property to `true`. +#### scrollX + +boolean + +If you want to enable the content scrolling in the X axis, set this property to `true`. + + +#### scrollY + +boolean + +If you want to disable the content scrolling in the Y axis, set this property to `false`. + + ## Attributes #### color @@ -73,14 +79,6 @@ and footers. This effect can easily be seen by setting the toolbar to transparent. -#### scroll-enabled - -boolean - -By default `ion-content` uses an `ion-scroll` under the hood to implement scrolling, -if you want to disable the content scrolling for a given page, set this property to `false`. - - #### scroll-events boolean @@ -89,11 +87,63 @@ Because of performance reasons, ionScroll events are disabled by default, in ord and start listening from (ionScroll), set this property to `true`. +#### scroll-x + +boolean + +If you want to enable the content scrolling in the X axis, set this property to `true`. + + +#### scroll-y + +boolean + +If you want to disable the content scrolling in the Y axis, set this property to `false`. + + +## Events + +#### ionScroll + +Emitted while scrolling. This event is disabled by default. +Look at the property: `scrollEvents` + + +#### ionScrollEnd + +Emitted when the scroll has ended. + + +#### ionScrollStart + +Emitted when the scroll has started. + + ## Methods #### getScrollElement() +#### scrollByPoint() + +Scroll by a specified X/Y distance in the component + + +#### scrollToBottom() + +Scroll to the bottom of the component + + +#### scrollToPoint() + +Scroll to a specified X/Y location in the component + + +#### scrollToTop() + +Scroll to the top of the component + + ---------------------------------------------- diff --git a/core/src/components/content/test/basic/index.html b/core/src/components/content/test/basic/index.html index 5a56b7ec44..0030f5fbef 100644 --- a/core/src/components/content/test/basic/index.html +++ b/core/src/components/content/test/basic/index.html @@ -23,7 +23,7 @@
- + Toggle content.fullscreen

Toggle header @@ -104,10 +104,6 @@ animation: ani1 5s infinite; } - #content2 ion-scroll { - background: black; - } - @keyframes ani1 { 0% { transform: translateX(0) diff --git a/core/src/components/infinite-scroll/infinite-scroll.tsx b/core/src/components/infinite-scroll/infinite-scroll.tsx index 99f3b1e5da..3d2c2fa3cf 100644 --- a/core/src/components/infinite-scroll/infinite-scroll.tsx +++ b/core/src/components/infinite-scroll/infinite-scroll.tsx @@ -8,7 +8,7 @@ export class InfiniteScroll { private thrPx = 0; private thrPc = 0; - private scrollEl?: HTMLIonScrollElement; + private scrollEl?: HTMLElement; private didFire = false; private isBusy = false; @@ -81,9 +81,13 @@ export class InfiniteScroll { } this.thresholdChanged(this.threshold); this.enableScrollEvents(!this.disabled); - if (this.position === 'top') { - this.queue.write(() => this.scrollEl && this.scrollEl.scrollToBottom(0)); - } + // if (this.position === 'top') { + // this.queue.write(() => { + // if (this.scrollEl) { + // this.scrollEl.scrollTop = this.scrollEl.scrollHeight - this.scrollEl.clientHeight; + // } + // }); + // } } componentDidUnload() { @@ -143,45 +147,45 @@ export class InfiniteScroll { } this.isLoading = false; - if (this.position === 'top') { - /** - * New content is being added at the top, but the scrollTop position stays the same, - * which causes a scroll jump visually. This algorithm makes sure to prevent this. - * (Frame 1) - * - complete() is called, but the UI hasn't had time to update yet. - * - Save the current content dimensions. - * - Wait for the next frame using _dom.read, so the UI will be updated. - * (Frame 2) - * - Read the new content dimensions. - * - Calculate the height difference and the new scroll position. - * - Delay the scroll position change until other possible dom reads are done using _dom.write to be performant. - * (Still frame 2, if I'm correct) - * - Change the scroll position (= visually maintain the scroll position). - * - Change the state to re-enable the InfiniteScroll. - * - This should be after changing the scroll position, or it could - * cause the InfiniteScroll to be triggered again immediately. - * (Frame 3) - * Done. - */ - this.isBusy = true; - // ******** DOM READ **************** - // Save the current content dimensions before the UI updates - const prev = scrollEl.scrollHeight - scrollEl.scrollTop; + // if (this.position === 'top') { + // /** + // * New content is being added at the top, but the scrollTop position stays the same, + // * which causes a scroll jump visually. This algorithm makes sure to prevent this. + // * (Frame 1) + // * - complete() is called, but the UI hasn't had time to update yet. + // * - Save the current content dimensions. + // * - Wait for the next frame using _dom.read, so the UI will be updated. + // * (Frame 2) + // * - Read the new content dimensions. + // * - Calculate the height difference and the new scroll position. + // * - Delay the scroll position change until other possible dom reads are done using _dom.write to be performant. + // * (Still frame 2, if I'm correct) + // * - Change the scroll position (= visually maintain the scroll position). + // * - Change the state to re-enable the InfiniteScroll. + // * - This should be after changing the scroll position, or it could + // * cause the InfiniteScroll to be triggered again immediately. + // * (Frame 3) + // * Done. + // */ + // this.isBusy = true; + // // ******** DOM READ **************** + // // Save the current content dimensions before the UI updates + // const prev = scrollEl.scrollHeight - scrollEl.scrollTop; - // ******** DOM READ **************** - this.queue.read(() => { - // UI has updated, save the new content dimensions - const scrollHeight = scrollEl.scrollHeight; - // New content was added on top, so the scroll position should be changed immediately to prevent it from jumping around - const newScrollTop = scrollHeight - prev; + // // ******** DOM READ **************** + // this.queue.read(() => { + // // UI has updated, save the new content dimensions + // const scrollHeight = scrollEl.scrollHeight; + // // New content was added on top, so the scroll position should be changed immediately to prevent it from jumping around + // const newScrollTop = scrollHeight - prev; - // ******** DOM WRITE **************** - this.queue.write(() => { - scrollEl.scrollTop = newScrollTop; - this.isBusy = false; - }); - }); - } + // // ******** DOM WRITE **************** + // this.queue.write(() => { + // scrollEl.scrollTop = newScrollTop; + // this.isBusy = false; + // }); + // }); + // } } /** diff --git a/core/src/components/infinite-scroll/test/basic/index.html b/core/src/components/infinite-scroll/test/basic/index.html index d4b6a5a599..b9309f3715 100644 --- a/core/src/components/infinite-scroll/test/basic/index.html +++ b/core/src/components/infinite-scroll/test/basic/index.html @@ -38,10 +38,6 @@ diff --git a/core/src/components/popover/popover.scss b/core/src/components/popover/popover.scss index 2c6bd41d84..9312eee192 100644 --- a/core/src/components/popover/popover.scss +++ b/core/src/components/popover/popover.scss @@ -30,13 +30,10 @@ ion-popover { z-index: $z-index-overlay-wrapper; } -.popover-content ion-content, -.popover-content ion-scroll { - contain: none; -} - -.popover-content ion-scroll { +.popover-content ion-content { position: relative; + + contain: none; } ion-popover-controller { diff --git a/core/src/components/refresher/refresher.scss b/core/src/components/refresher/refresher.scss index 261ebf16d3..b170e53875 100644 --- a/core/src/components/refresher/refresher.scss +++ b/core/src/components/refresher/refresher.scss @@ -6,7 +6,6 @@ ion-refresher { display: none; position: absolute; - top: 0; width: 100%; height: $refresher-height; diff --git a/core/src/components/refresher/refresher.tsx b/core/src/components/refresher/refresher.tsx index 0c6a0456df..c176185fbc 100644 --- a/core/src/components/refresher/refresher.tsx +++ b/core/src/components/refresher/refresher.tsx @@ -15,7 +15,7 @@ export class Refresher { private appliedStyles = false; private didStart = false; private progress = 0; - private scrollEl?: HTMLIonScrollElement; + private scrollEl?: HTMLElement; private gesture?: Gesture; mode!: Mode; @@ -333,7 +333,7 @@ export class Refresher { this.queue.write(() => { if (this.scrollEl) { const style = this.scrollEl.style; - style.transform = ((y > 0) ? 'translateY(' + y + 'px) translateZ(0px)' : 'translateZ(0px)'); + style.transform = ((y > 0) ? `translateY(${y}px) translateZ(0px)` : 'translateZ(0px)'); style.transitionDuration = duration; style.transitionDelay = delay; style.overflow = (overflowVisible ? 'hidden' : ''); diff --git a/core/src/components/reorder-group/reorder-group.scss b/core/src/components/reorder-group/reorder-group.scss index 96a7d39d07..c7238dee7d 100644 --- a/core/src/components/reorder-group/reorder-group.scss +++ b/core/src/components/reorder-group/reorder-group.scss @@ -8,6 +8,10 @@ will-change: transform; } +.reorder-enabled { + user-select: none; +} + .reorder-enabled ion-reorder { display: block; diff --git a/core/src/components/reorder-group/reorder-group.tsx b/core/src/components/reorder-group/reorder-group.tsx index ebe76ce8b8..ee95cc24ec 100644 --- a/core/src/components/reorder-group/reorder-group.tsx +++ b/core/src/components/reorder-group/reorder-group.tsx @@ -14,7 +14,7 @@ export class ReorderGroup { private selectedItemHeight!: number; private lastToIndex!: number; private cachedHeights: number[] = []; - private scrollEl?: HTMLIonScrollElement; + private scrollEl?: HTMLElement; private gesture?: Gesture; private scrollElTop = 0; diff --git a/core/src/components/scroll/readme.md b/core/src/components/scroll/readme.md deleted file mode 100644 index 20cb569348..0000000000 --- a/core/src/components/scroll/readme.md +++ /dev/null @@ -1,102 +0,0 @@ -# ion-scroll - -Scroll is a low-level component for arbitrary scrolling areas. It's used internally by Content. - - - - - -## Properties - -#### forceOverscroll - -boolean - -If true and the content does not cause an overflow scroll, the scroll interaction will cause a bounce. -If the content exceeds the bounds of ionScroll, nothing will change. -Note, the does not disable the system bounce on iOS. That is an OS level setting. - - -#### mode - -string - -The mode for component. - - -#### scrollEvents - -boolean - -If true, the component will emit scroll events. - - -## Attributes - -#### force-overscroll - -boolean - -If true and the content does not cause an overflow scroll, the scroll interaction will cause a bounce. -If the content exceeds the bounds of ionScroll, nothing will change. -Note, the does not disable the system bounce on iOS. That is an OS level setting. - - -#### mode - -string - -The mode for component. - - -#### scroll-events - -boolean - -If true, the component will emit scroll events. - - -## Events - -#### ionScroll - -Emitted while scrolling. This event is disabled by default. -Look at the property: `scrollEvents` - - -#### ionScrollEnd - -Emitted when the scroll has ended. - - -#### ionScrollStart - -Emitted when the scroll has started. - - -## Methods - -#### scrollByPoint() - -Scroll by a specified X/Y distance in the component - - -#### scrollToBottom() - -Scroll to the bottom of the component - - -#### scrollToPoint() - -Scroll to a specified X/Y location in the component - - -#### scrollToTop() - -Scroll to the top of the component - - - ----------------------------------------------- - -*Built with [StencilJS](https://stenciljs.com/)* diff --git a/core/src/components/scroll/scroll.scss b/core/src/components/scroll/scroll.scss deleted file mode 100644 index 81ee772913..0000000000 --- a/core/src/components/scroll/scroll.scss +++ /dev/null @@ -1,35 +0,0 @@ -@import "../../themes/ionic.globals"; - -:host { - @include position(0, 0, 0, 0); - - display: block; - position: absolute; - - contain: size style layout; - z-index: $z-index-scroll-content; - overflow-x: hidden; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - will-change: scroll-position; - - box-sizing: border-box; -} - -:host(.overscroll)::before, -:host(.overscroll)::after { - position: absolute; - - width: 1px; - height: 1px; - - content: ""; -} - -:host(.overscroll)::before { - bottom: -1px; -} - -:host(.overscroll)::after { - top: -1px; -} diff --git a/core/src/components/scroll/scroll.tsx b/core/src/components/scroll/scroll.tsx deleted file mode 100644 index ccfcfedffb..0000000000 --- a/core/src/components/scroll/scroll.tsx +++ /dev/null @@ -1,286 +0,0 @@ -import { Component, Element, Event, EventEmitter, Listen, Method, Prop, QueueApi } from '@stencil/core'; - -import { Config, Mode, ScrollBaseDetail, ScrollDetail } from '../../interface'; -import { createThemedClasses } from '../../utils/theme'; - -@Component({ - tag: 'ion-scroll', - styleUrl: 'scroll.scss', - shadow: true -}) -export class Scroll { - - private watchDog: any; - private isScrolling = false; - private lastScroll = 0; - private queued = false; - - // Detail is used in a hot loop in the scroll event, by allocating it here - // V8 will be able to inline any read/write to it since it's a monomorphic class. - // https://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html - private detail: ScrollDetail = { - scrollTop: 0, - scrollLeft: 0, - type: 'scroll', - event: undefined!, - startX: 0, - startY: 0, - startTimeStamp: 0, - currentX: 0, - currentY: 0, - velocityX: 0, - velocityY: 0, - deltaX: 0, - deltaY: 0, - timeStamp: 0, - data: undefined, - isScrolling: true, - }; - - @Element() el!: HTMLElement; - - @Prop({ context: 'config' }) config!: Config; - @Prop({ context: 'queue' }) queue!: QueueApi; - @Prop({ context: 'window' }) win!: Window; - - /** The mode for component. */ - @Prop() mode!: Mode; - - /** - * If true and the content does not cause an overflow scroll, the scroll interaction will cause a bounce. - * If the content exceeds the bounds of ionScroll, nothing will change. - * Note, the does not disable the system bounce on iOS. That is an OS level setting. - */ - @Prop({ mutable: true }) forceOverscroll?: boolean; - - /** If true, the component will emit scroll events. */ - @Prop() scrollEvents = false; - - /** - * Emitted when the scroll has started. - */ - @Event() ionScrollStart!: EventEmitter; - - /** - * Emitted while scrolling. This event is disabled by default. - * Look at the property: `scrollEvents` - */ - @Event() ionScroll!: EventEmitter; - - /** - * Emitted when the scroll has ended. - */ - @Event() ionScrollEnd!: EventEmitter; - - componentWillLoad() { - if (this.forceOverscroll === undefined) { - this.forceOverscroll = this.mode === 'ios' && ('ontouchstart' in this.win); - } - } - - componentDidUnload() { - if (this.watchDog) { - clearInterval(this.watchDog); - } - } - - @Listen('scroll', { passive: true }) - onScroll(ev: UIEvent) { - const timeStamp = Date.now(); - const didStart = !this.isScrolling; - this.lastScroll = timeStamp; - if (didStart) { - this.onScrollStart(); - } - if (!this.queued && this.scrollEvents) { - this.queued = true; - this.queue.read(ts => { - this.queued = false; - this.detail.event = ev; - updateScrollDetail(this.detail, this.el, ts, didStart); - this.ionScroll.emit(this.detail); - }); - } - } - - /** Scroll to the top of the component */ - @Method() - scrollToTop(duration: number): Promise { - return this.scrollToPoint(0, 0, duration); - } - - /** Scroll to the bottom of the component */ - @Method() - scrollToBottom(duration: number): Promise { - const y = this.el.scrollHeight - this.el.clientHeight; - return this.scrollToPoint(0, y, duration); - } - - /** Scroll by a specified X/Y distance in the component */ - @Method() - scrollByPoint(x: number, y: number, duration: number): Promise { - return this.scrollToPoint(x + this.el.scrollLeft, y + this.el.scrollTop, duration); - } - - /** Scroll to a specified X/Y location in the component */ - @Method() - scrollToPoint(x: number, y: number, duration: number): Promise { - // scroll animation loop w/ easing - // credit https://gist.github.com/dezinezync/5487119 - - let resolve!: () => void; - const promise = new Promise(r => { - resolve = r; - }); - - const self = this; - const el = self.el; - if (!el) { - // invalid element - resolve(); - return promise; - } - - if (duration < 32) { - el.scrollTop = y; - el.scrollLeft = x; - resolve(); - return promise; - } - - const fromY = el.scrollTop; - const fromX = el.scrollLeft; - - const maxAttempts = (duration / 16) + 100; - - let startTime: number; - let attempts = 0; - let stopScroll = false; - - // scroll loop - function step(timeStamp: number) { - attempts++; - - if (!self.el || stopScroll || attempts > maxAttempts) { - self.isScrolling = false; - el.style.transform = el.style.webkitTransform = ''; - resolve(); - return; - } - - let time = Math.min(1, ((timeStamp - startTime) / duration)); - - // where .5 would be 50% of time on a linear scale easedT gives a - // fraction based on the easing method - const easedT = (--time) * time * time + 1; - - if (fromY !== y) { - el.scrollTop = (easedT * (y - fromY)) + fromY; - } - - if (fromX !== x) { - el.scrollLeft = Math.floor((easedT * (x - fromX)) + fromX); - } - - if (easedT < 1) { - // do not use DomController here - // must use nativeRaf in order to fire in the next frame - // TODO: remove as any - self.queue.read(step as any); - - } else { - stopScroll = true; - self.isScrolling = false; - el.style.transform = el.style.webkitTransform = ''; - resolve(); - } - } - - // start scroll loop - self.isScrolling = true; - - // chill out for a frame first - this.queue.write(() => { - this.queue.write(timeStamp => { - startTime = timeStamp; - step(timeStamp); - }); - }); - - return promise; - } - - private onScrollStart() { - this.isScrolling = true; - this.ionScrollStart.emit({ - isScrolling: true - }); - - if (this.watchDog) { - clearInterval(this.watchDog); - } - // watchdog - this.watchDog = setInterval(() => { - if (this.lastScroll < Date.now() - 120) { - this.onScrollEnd(); - } - }, 100); - } - - private onScrollEnd() { - - clearInterval(this.watchDog); - this.watchDog = null; - this.isScrolling = false; - this.ionScrollEnd.emit({ - isScrolling: false - }); - } - - hostData() { - return { - class: { - ...createThemedClasses(this.mode, 'scroll'), - overscroll: this.forceOverscroll - } - }; - } - - render() { - return ; - } -} - -// ******** DOM READ **************** -function updateScrollDetail( - detail: ScrollDetail, - el: HTMLElement, - timestamp: number, - didStart: boolean -) { - const prevX = detail.currentX; - const prevY = detail.currentY; - const prevT = detail.timeStamp; - const currentX = el.scrollLeft; - const currentY = el.scrollTop; - if (didStart) { - // remember the start positions - detail.startTimeStamp = timestamp; - detail.startX = currentX; - detail.startY = currentY; - detail.velocityX = detail.velocityY = 0; - } - detail.timeStamp = timestamp; - detail.currentX = detail.scrollLeft = currentX; - detail.currentY = detail.scrollTop = currentY; - detail.deltaX = currentX - detail.startX; - detail.deltaY = currentY - detail.startY; - - const timeDelta = timestamp - prevT; - if (timeDelta > 0 && timeDelta < 100) { - const velocityX = (currentX - prevX) / timeDelta; - const velocityY = (currentY - prevY) / timeDelta; - detail.velocityX = velocityX * 0.7 + detail.velocityX * 0.3; - detail.velocityY = velocityY * 0.7 + detail.velocityY * 0.3; - } -} diff --git a/core/src/components/tabbar/readme.md b/core/src/components/tabbar/readme.md index d480c037ba..61053434b9 100644 --- a/core/src/components/tabbar/readme.md +++ b/core/src/components/tabbar/readme.md @@ -39,13 +39,6 @@ string Set the position of the tabbar, relative to the content. Available options: `"top"`, `"bottom"`. -#### scrollable - -boolean - -If true, the tabs will be scrollable when there are enough tabs to overflow the width of the screen. - - #### selectedTab HTMLIonTabElement @@ -100,13 +93,6 @@ string Set the position of the tabbar, relative to the content. Available options: `"top"`, `"bottom"`. -#### scrollable - -boolean - -If true, the tabs will be scrollable when there are enough tabs to overflow the width of the screen. - - #### selected-tab diff --git a/core/src/components/tabbar/tabbar.md.scss b/core/src/components/tabbar/tabbar.md.scss index a10cabd3fd..418268dd94 100644 --- a/core/src/components/tabbar/tabbar.md.scss +++ b/core/src/components/tabbar/tabbar.md.scss @@ -13,20 +13,9 @@ contain: strict; } -ion-scroll { - @include margin(0, 8px); - - max-width: 650px; -} // TODO: REVIEW :host(.placement-top) .tab-button.tab-selected .tab-button-icon, :host(.placement-top) .tab-button.tab-selected .tab-button-text { transform: inherit; } - -.scrollable .tab-button { - flex: 1 0 auto; - - overflow: hidden; -} diff --git a/core/src/components/tabbar/tabbar.scss b/core/src/components/tabbar/tabbar.scss index ac8415d513..820b6c93b8 100644 --- a/core/src/components/tabbar/tabbar.scss +++ b/core/src/components/tabbar/tabbar.scss @@ -72,22 +72,3 @@ :host(.placement-bottom) .tabbar-highlight { top: 0; } - - -// Overflow Scrolling -// -------------------------------------------------- - -ion-scroll { - overflow: hidden; -} - -.scroll-inner { - display: flex; - position: relative; - - flex-direction: row; -} - -ion-button.inactive { - visibility: hidden; -} diff --git a/core/src/components/tabbar/tabbar.tsx b/core/src/components/tabbar/tabbar.tsx index 68ee16fb6e..db52baf4f7 100644 --- a/core/src/components/tabbar/tabbar.tsx +++ b/core/src/components/tabbar/tabbar.tsx @@ -13,8 +13,6 @@ import { createColorClasses } from '../../utils/theme'; }) export class Tabbar { - private scrollEl?: HTMLIonScrollElement; - @Prop() mode!: Mode; @Prop() color?: Color; @@ -40,17 +38,11 @@ export class Tabbar { /** The selected tab component */ @Prop() selectedTab?: HTMLIonTabElement; - /** - * If true, the tabs will be scrollable when there are enough tabs to overflow the width of the screen. - */ - @Prop() scrollable = false; - /** The tabs to render */ @Prop() tabs: HTMLIonTabElement[] = []; @Watch('selectedTab') selectedTabChanged() { - this.scrollToSelectedButton(); this.updateHighlight(); } @@ -85,92 +77,14 @@ export class Tabbar { } componentDidLoad() { - this.updateBoundaries(); this.updateHighlight(); } - protected analyzeTabs() { - const tabs: HTMLIonTabButtonElement[] = Array.from(this.doc.querySelectorAll('ion-tab-button')); - const scrollLeft = this.scrollEl!.scrollLeft; - const tabsWidth = this.scrollEl!.clientWidth; - let previous: {tab: HTMLIonTabButtonElement, amount: number} | undefined; - let next: {tab: HTMLIonTabButtonElement, amount: number} | undefined; - - for (const tab of tabs) { - const left = tab.offsetLeft; - const right = left + tab.offsetWidth; - - if (left < scrollLeft) { - previous = { tab, amount: left }; - } - - if (!next && right > (tabsWidth + scrollLeft)) { - const amount = right - tabsWidth; - next = { tab, amount }; - } - } - - return { previous, next }; - } - private getSelectedButton(): HTMLIonTabButtonElement | undefined { return Array.from(this.el.querySelectorAll('ion-tab-button')) .find(btn => btn.selected); } - protected scrollToSelectedButton() { - if (!this.scrollEl || !this.scrollable) { - return; - } - this.queue.read(() => { - const activeTabButton = this.getSelectedButton(); - - if (activeTabButton) { - const scrollLeft = this.scrollEl!.scrollLeft; - const tabsWidth = this.scrollEl!.clientWidth; - const left = activeTabButton.offsetLeft; - const right = left + activeTabButton.offsetWidth; - - let amount = 0; - - if (right > (tabsWidth + scrollLeft)) { - amount = right - tabsWidth; - } else if (left < scrollLeft) { - amount = left; - } - - if (amount !== 0) { - this.queue.write(() => { - this.scrollEl!.scrollToPoint(amount, 0, 250).then(() => { - this.updateBoundaries(); - }); - }); - } - } - }); - } - - private scrollByTab(direction: 'left' | 'right') { - this.queue.read(() => { - const { previous, next } = this.analyzeTabs(); - const info = direction === 'right' ? next : previous; - const amount = info && info.amount; - - if (info && amount) { - this.scrollEl!.scrollToPoint(amount, 0, 250).then(() => { - this.updateBoundaries(); - }); - } - }); - } - - private updateBoundaries() { - if (this.scrollEl && this.scrollable) { - this.canScrollLeft = this.scrollEl.scrollLeft !== 0; - this.canScrollRight = this.scrollEl.scrollLeft < (this.scrollEl.scrollWidth - this.scrollEl.offsetWidth); - } - } - private updateHighlight() { if (!this.highlight) { return; @@ -185,7 +99,7 @@ export class Tabbar { } hostData() { - const { color, translucent, layout, placement, keyboardVisible, scrollable } = this; + const { color, translucent, layout, placement, keyboardVisible } = this; return { role: 'tablist', 'aria-hidden': keyboardVisible ? 'true' : null, @@ -195,56 +109,38 @@ export class Tabbar { [`layout-${layout}`]: true, [`placement-${placement}`]: true, 'tabbar-hidden': keyboardVisible, - 'scrollable': scrollable } }; } render() { const selectedTab = this.selectedTab; - const ionTabbarHighlight = this.highlight ?

as HTMLElement : null; - const tabButtons = this.tabs.map(tab => { - if (!tab.disabled) { - this.ionTabbarClick.emit(tab); - } - ev.stopPropagation(); - ev.preventDefault(); - }} - />); - if (this.scrollable) { - return [ - this.scrollByTab('left')} fill="clear" class={{ inactive: !this.canScrollLeft }}> - - , - - this.scrollEl = scrollEl as HTMLIonScrollElement}> - {tabButtons} - {ionTabbarHighlight} - , - - this.scrollByTab('right')} fill="clear" class={{ inactive: !this.canScrollRight }}> - - - ]; - } else { - return [ - ...tabButtons, - ionTabbarHighlight - ]; - } + return [ + this.tabs.map(tab => ( + { + if (!tab.disabled) { + this.ionTabbarClick.emit(tab); + } + ev.stopPropagation(); + ev.preventDefault(); + }} + /> + )), + this.highlight &&
+ ]; } } diff --git a/core/src/components/tabs/readme.md b/core/src/components/tabs/readme.md index 6af86fb5a4..6087e36261 100644 --- a/core/src/components/tabs/readme.md +++ b/core/src/components/tabs/readme.md @@ -25,13 +25,6 @@ string A unique name for the tabs. -#### scrollable - -boolean - -If true, the tabs will be scrollable when there are enough tabs to overflow the width of the screen. - - #### tabbarHidden boolean @@ -95,13 +88,6 @@ string A unique name for the tabs. -#### scrollable - -boolean - -If true, the tabs will be scrollable when there are enough tabs to overflow the width of the screen. - - #### tabbar-hidden boolean diff --git a/core/src/components/tabs/tabs.tsx b/core/src/components/tabs/tabs.tsx index ac162b5c8f..2284ab059e 100644 --- a/core/src/components/tabs/tabs.tsx +++ b/core/src/components/tabs/tabs.tsx @@ -21,7 +21,6 @@ export class Tabs implements NavOutlet { @State() selectedTab?: HTMLIonTabElement; @Prop({ context: 'config' }) config!: Config; - @Prop({ context: 'document' }) doc!: Document; /** @@ -64,11 +63,6 @@ export class Tabs implements NavOutlet { */ @Prop() translucent = false; - /** - * If true, the tabs will be scrollable when there are enough tabs to overflow the width of the screen. - */ - @Prop() scrollable = false; - /** * If true, the tabs will use the router and `selectedTab` will not do anything. */ @@ -307,14 +301,12 @@ export class Tabs implements NavOutlet { } render() { - const dom = [ + return [
-
- ]; +
, - if (!this.tabbarHidden) { - dom.push( + !this.tabbarHidden && ( + translucent={this.translucent}> - ); - } - return dom; + ) + ]; } } diff --git a/core/src/components/tabs/test/scroll/index.html b/core/src/components/tabs/test/scroll/index.html deleted file mode 100644 index fce85ee31c..0000000000 --- a/core/src/components/tabs/test/scroll/index.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - Tab - Scroll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/core/src/components/virtual-scroll/virtual-scroll.tsx b/core/src/components/virtual-scroll/virtual-scroll.tsx index e1a6e9667f..d3744d7a8d 100644 --- a/core/src/components/virtual-scroll/virtual-scroll.tsx +++ b/core/src/components/virtual-scroll/virtual-scroll.tsx @@ -11,7 +11,7 @@ import { Range, calcCells, calcHeightIndex, doRender, findCellIndex, getRange, g }) export class VirtualScroll { - private scrollEl?: HTMLIonScrollElement; + private scrollEl?: HTMLElement; private range: Range = { offset: 0, length: 0 }; private timerUpdate: any; private heightIndex?: Uint32Array; @@ -113,17 +113,17 @@ export class VirtualScroll { this.calcCells(); } - componentDidLoad() { + async componentDidLoad() { const contentEl = this.el.closest('ion-content'); if (!contentEl) { - console.error('virtual-scroll must be used inside ion-scroll/ion-content'); + console.error('virtual-scroll must be used inside ion-content'); return; } - contentEl.componentOnReady().then(() => { - this.scrollEl = contentEl.getScrollElement(); - this.calcCells(); - this.updateState(); - }); + await contentEl.componentOnReady(); + + this.scrollEl = contentEl.getScrollElement(); + this.calcCells(); + this.updateState(); } componentDidUpdate() { diff --git a/core/src/interface.d.ts b/core/src/interface.d.ts index 6ce2d423b8..328bd74c0b 100644 --- a/core/src/interface.d.ts +++ b/core/src/interface.d.ts @@ -11,7 +11,7 @@ export * from './components/popover/popover-interface'; export * from './components/nav/nav-interface'; export * from './components/router/utils/interface'; export * from './components/range/range-interface'; -export * from './components/scroll/scroll-interface'; +export * from './components/content/content-interface'; export * from './components/select/select-interface'; export * from './components/select-popover/select-popover-interface'; export * from './components/tabbar/tabbar-interface'; diff --git a/core/src/utils/input-shims/hacks/hide-caret.ts b/core/src/utils/input-shims/hacks/hide-caret.ts index 587cd645ca..34d020b441 100644 --- a/core/src/utils/input-shims/hacks/hide-caret.ts +++ b/core/src/utils/input-shims/hacks/hide-caret.ts @@ -1,6 +1,6 @@ import { isFocused, relocateInput } from './common'; -export function enableHideCaretOnScroll(componentEl: HTMLElement, inputEl: HTMLInputElement | undefined, scrollEl: HTMLIonScrollElement | undefined) { +export function enableHideCaretOnScroll(componentEl: HTMLElement, inputEl: HTMLInputElement | undefined, scrollEl: HTMLIonContentElement | undefined) { if (!scrollEl || !inputEl) { return () => { return; }; } diff --git a/core/src/utils/input-shims/hacks/scroll-assist.ts b/core/src/utils/input-shims/hacks/scroll-assist.ts index 4034c7a258..9757359c1f 100644 --- a/core/src/utils/input-shims/hacks/scroll-assist.ts +++ b/core/src/utils/input-shims/hacks/scroll-assist.ts @@ -64,7 +64,7 @@ function jsSetFocus( inputEl.focus(); // scroll the input into place - contentEl.getScrollElement().scrollByPoint(0, scrollData.scrollAmount, scrollData.scrollDuration).then(() => { + contentEl.scrollByPoint(0, scrollData.scrollAmount, scrollData.scrollDuration).then(() => { // the scroll view is in the correct position now // give the native text input focus relocateInput(componentEl, inputEl, false, scrollData.inputSafeY); diff --git a/core/src/utils/input-shims/input-shims.ts b/core/src/utils/input-shims/input-shims.ts index 4d510b0da9..34442a5ecb 100644 --- a/core/src/utils/input-shims/input-shims.ts +++ b/core/src/utils/input-shims/input-shims.ts @@ -26,8 +26,7 @@ export function startInputShims( function registerInput(componentEl: HTMLElement) { const inputEl = (componentEl.shadowRoot || componentEl).querySelector('input'); - const contentEl = componentEl.closest('ion-content'); - const scrollEl = contentEl && contentEl.getScrollElement(); + const scrollEl = componentEl.closest('ion-content'); if (!inputEl) { return; @@ -38,8 +37,8 @@ export function startInputShims( hideCaretMap.set(componentEl, rmFn); } - if (SCROLL_ASSIST && contentEl && scrollAssist && !scrollAssistMap.has(componentEl)) { - const rmFn = enableScrollAssist(componentEl, inputEl, contentEl, keyboardHeight); + if (SCROLL_ASSIST && scrollEl && scrollAssist && !scrollAssistMap.has(componentEl)) { + const rmFn = enableScrollAssist(componentEl, inputEl, scrollEl, keyboardHeight); scrollAssistMap.set(componentEl, rmFn); } } diff --git a/core/src/utils/status-tap.ts b/core/src/utils/status-tap.ts index b34199a945..6d7af60f23 100644 --- a/core/src/utils/status-tap.ts +++ b/core/src/utils/status-tap.ts @@ -12,7 +12,7 @@ export function startStatusTap(win: Window, queue: QueueApi) { if (contentEl) { await contentEl.componentOnReady(); queue.write(() => { - contentEl.getScrollElement().scrollToTop(300); + contentEl.scrollToTop(300); }); } }); diff --git a/core/stencil.config.js b/core/stencil.config.js index 61aa2a0dad..30af984d89 100644 --- a/core/stencil.config.js +++ b/core/stencil.config.js @@ -7,7 +7,7 @@ exports.config = { { components: ['ion-alert', 'ion-alert-controller'] }, { components: ['ion-anchor', 'ion-back-button'] }, { components: ['ion-animation-controller'] }, - { components: ['ion-app', 'ion-buttons', 'ion-content', 'ion-footer', 'ion-header', 'ion-scroll', 'ion-title', 'ion-toolbar'] }, + { components: ['ion-app', 'ion-buttons', 'ion-content', 'ion-footer', 'ion-header', 'ion-title', 'ion-toolbar'] }, { components: ['ion-avatar', 'ion-badge', 'ion-thumbnail'] }, { components: ['ion-backdrop'] }, { components: ['ion-button', 'ion-icon'] },