Compare commits

..

1 Commits

Author SHA1 Message Date
Liam DeBeasi
6c65fc7d2a add experimental infinite scroll fix 2024-02-07 16:27:52 -05:00
12 changed files with 77 additions and 85 deletions

View File

@@ -160,7 +160,7 @@ export namespace Components {
/**
* Dismiss the action sheet overlay after it has been presented.
* @param data Any data to emit in the dismiss events.
* @param role The role of the element that is dismissing the action sheet. This can be useful in a button handler for determining which button was clicked to dismiss the action sheet. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`. This is a no-op if the overlay has not been presented yet. If you want to remove an overlay from the DOM that was never presented, use the [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
* @param role The role of the element that is dismissing the action sheet. This can be useful in a button handler for determining which button was clicked to dismiss the action sheet. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
*/
"dismiss": (data?: any, role?: string) => Promise<boolean>;
/**
@@ -239,7 +239,7 @@ export namespace Components {
/**
* Dismiss the alert overlay after it has been presented.
* @param data Any data to emit in the dismiss events.
* @param role The role of the element that is dismissing the alert. This can be useful in a button handler for determining which button was clicked to dismiss the alert. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`. This is a no-op if the overlay has not been presented yet. If you want to remove an overlay from the DOM that was never presented, use the [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
* @param role The role of the element that is dismissing the alert. This can be useful in a button handler for determining which button was clicked to dismiss the alert. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
*/
"dismiss": (data?: any, role?: string) => Promise<boolean>;
/**
@@ -1290,7 +1290,6 @@ export namespace Components {
* Works with the min and max attributes to limit the increments at which a value can be set. Possible values are: `"any"` or a positive floating point number.
*/
"step"?: string;
"togglePassword": boolean;
/**
* The type of control to display. The default type is text.
*/
@@ -1528,7 +1527,7 @@ export namespace Components {
/**
* Dismiss the loading overlay after it has been presented.
* @param data Any data to emit in the dismiss events.
* @param role The role of the element that is dismissing the loading. This can be useful in a button handler for determining which button was clicked to dismiss the loading. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`. This is a no-op if the overlay has not been presented yet. If you want to remove an overlay from the DOM that was never presented, use the [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
* @param role The role of the element that is dismissing the loading. This can be useful in a button handler for determining which button was clicked to dismiss the loading. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
*/
"dismiss": (data?: any, role?: string) => Promise<boolean>;
/**
@@ -1721,7 +1720,7 @@ export namespace Components {
/**
* Dismiss the modal overlay after it has been presented.
* @param data Any data to emit in the dismiss events.
* @param role The role of the element that is dismissing the modal. For example, 'cancel' or 'backdrop'. This is a no-op if the overlay has not been presented yet. If you want to remove an overlay from the DOM that was never presented, use the [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
* @param role The role of the element that is dismissing the modal. For example, 'cancel' or 'backdrop'.
*/
"dismiss": (data?: any, role?: string) => Promise<boolean>;
/**
@@ -1974,7 +1973,7 @@ export namespace Components {
/**
* Dismiss the picker overlay after it has been presented.
* @param data Any data to emit in the dismiss events.
* @param role The role of the element that is dismissing the picker. This can be useful in a button handler for determining which button was clicked to dismiss the picker. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`. This is a no-op if the overlay has not been presented yet. If you want to remove an overlay from the DOM that was never presented, use the [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
* @param role The role of the element that is dismissing the picker. This can be useful in a button handler for determining which button was clicked to dismiss the picker. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
*/
"dismiss": (data?: any, role?: string) => Promise<boolean>;
/**
@@ -2111,7 +2110,7 @@ export namespace Components {
* Dismiss the popover overlay after it has been presented.
* @param data Any data to emit in the dismiss events.
* @param role The role of the element that is dismissing the popover. For example, 'cancel' or 'backdrop'.
* @param dismissParentPopover If `true`, dismissing this popover will also dismiss a parent popover if this popover is nested. Defaults to `true`. This is a no-op if the overlay has not been presented yet. If you want to remove an overlay from the DOM that was never presented, use the [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
* @param dismissParentPopover If `true`, dismissing this popover will also dismiss a parent popover if this popover is nested. Defaults to `true`.
*/
"dismiss": (data?: any, role?: string, dismissParentPopover?: boolean) => Promise<boolean>;
/**
@@ -3112,7 +3111,7 @@ export namespace Components {
/**
* Dismiss the toast overlay after it has been presented.
* @param data Any data to emit in the dismiss events.
* @param role The role of the element that is dismissing the toast. This can be useful in a button handler for determining which button was clicked to dismiss the toast. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`. This is a no-op if the overlay has not been presented yet. If you want to remove an overlay from the DOM that was never presented, use the [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
* @param role The role of the element that is dismissing the toast. This can be useful in a button handler for determining which button was clicked to dismiss the toast. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
*/
"dismiss": (data?: any, role?: string) => Promise<boolean>;
/**
@@ -6022,7 +6021,6 @@ declare namespace LocalJSX {
* Works with the min and max attributes to limit the increments at which a value can be set. Possible values are: `"any"` or a positive floating point number.
*/
"step"?: string;
"togglePassword"?: boolean;
/**
* The type of control to display. The default type is text.
*/

View File

@@ -216,10 +216,6 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
* This can be useful in a button handler for determining which button was
* clicked to dismiss the action sheet.
* Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
*
* This is a no-op if the overlay has not been presented yet. If you want
* to remove an overlay from the DOM that was never presented, use the
* [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
@Method()
async dismiss(data?: any, role?: string): Promise<boolean> {

View File

@@ -411,10 +411,6 @@ export class Alert implements ComponentInterface, OverlayInterface {
* This can be useful in a button handler for determining which button was
* clicked to dismiss the alert.
* Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
*
* This is a no-op if the overlay has not been presented yet. If you want
* to remove an overlay from the DOM that was never presented, use the
* [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
@Method()
async dismiss(data?: any, role?: string): Promise<boolean> {

View File

@@ -84,6 +84,9 @@ export class InfiniteScroll implements ComponentInterface {
*/
@Event() ionInfinite!: EventEmitter<void>;
private scrollHeight: number = 0;
async connectedCallback() {
const contentEl = findClosestIonContent(this.el);
if (!contentEl) {
@@ -102,6 +105,33 @@ export class InfiniteScroll implements ComponentInterface {
}
}
async componentDidLoad() {
const contentEl = findClosestIonContent(this.el)!;
const scrollEl = await getScrollElement(contentEl);
const mo = new MutationObserver(async () => {
// wait for items to by hydrated so they have a dimension
const item = document.querySelectorAll('ion-item');
const lastItem = item[0];
await lastItem.componentOnReady();
// restore scroll position
const newScrollTop = scrollEl.scrollHeight - this.scrollHeight;
// TODO not sure why we need to set scrollTop twice
// TODO every once in a while the first ionInfinite callback
// still has a flicker
scrollEl.scrollTop = newScrollTop;
requestAnimationFrame(() => {
scrollEl.scrollTop = newScrollTop;
});
this.isBusy = false;
this.didFire = false;
});
mo.observe(findClosestIonContent(this.el)!, { subtree: true, characterData: true, childList: true });
}
disconnectedCallback() {
this.enableScrollEvents(false);
this.scrollEl = undefined;
@@ -132,6 +162,10 @@ export class InfiniteScroll implements ComponentInterface {
if (!this.didFire) {
this.isLoading = true;
this.didFire = true;
// cache the scroll position before the DOM updates
this.scrollHeight = scrollEl.scrollHeight;
this.ionInfinite.emit();
return 3;
}
@@ -179,28 +213,6 @@ export class InfiniteScroll implements ComponentInterface {
* Done.
*/
this.isBusy = true;
// ******** DOM READ ****************
// Save the current content dimensions before the UI updates
const prev = scrollEl.scrollHeight - scrollEl.scrollTop;
// ******** DOM READ ****************
requestAnimationFrame(() => {
readTask(() => {
// 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 ****************
requestAnimationFrame(() => {
writeTask(() => {
scrollEl.scrollTop = newScrollTop;
this.isBusy = false;
this.didFire = false;
});
});
});
});
} else {
this.didFire = false;
}

View File

@@ -12,6 +12,7 @@
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
@@ -19,42 +20,49 @@
<ion-header>
<ion-toolbar>
<ion-title>Infinite Scroll - Basic</ion-title>
<ion-buttons slot="end">
<ion-button onclick="doScroll()">scroll to top</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding" id="content">
<ion-button onclick="toggleInfiniteScroll()" expand="block"> Toggle InfiniteScroll </ion-button>
<ion-list id="list"></ion-list>
<ion-infinite-scroll threshold="100px" id="infinite-scroll">
<ion-infinite-scroll position="top" threshold="100px" id="infinite-scroll">
<ion-infinite-scroll-content loading-spinner="crescent" loading-text="Loading more data...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>
<ion-list id="list"></ion-list>
</ion-content>
</ion-app>
<script>
const list = document.getElementById('list');
const infiniteScroll = document.getElementById('infinite-scroll');
const content = document.querySelector('ion-content');
let count = 0;
function toggleInfiniteScroll() {
infiniteScroll.disabled = !infiniteScroll.disabled;
}
infiniteScroll.addEventListener('ionInfinite', async function () {
await wait(500);
infiniteScroll.complete();
appendItems();
infiniteScroll.complete();
// Custom event consumed in the e2e tests
window.dispatchEvent(new CustomEvent('ionInfiniteComplete'));
});
function appendItems() {
for (var i = 0; i < 30; i++) {
const c = count;
for (var i = count; i < c + 30; i++) {
const el = document.createElement('ion-item');
el.textContent = `${1 + i}`;
list.appendChild(el);
list.prepend(el);
count += 1;
}
}
@@ -67,6 +75,25 @@
}
appendItems();
// this piece is only needed if items are Ionic components instead of divs
// wait for Angular to load items into the DOM
const observer = new MutationObserver(async () => {
const firstItem = document.querySelector('ion-item');
// wait for item component to be hydrated
await firstItem.componentOnReady();
observer.disconnect();
content.scrollToBottom();
});
observer.observe(list, { childList: true });
const doScroll = () => {
content.scrollToTop();
}
</script>
</body>
</html>

View File

@@ -153,8 +153,6 @@ export class Input implements ComponentInterface {
this.emitStyle();
}
@Prop() togglePassword = false;
/**
* A hint to the browser for which enter key to display.
* Possible values: `"enter"`, `"done"`, `"go"`, `"next"`,
@@ -838,9 +836,7 @@ export class Input implements ComponentInterface {
<ion-icon aria-hidden="true" icon={mode === 'ios' ? closeCircle : closeSharp}></ion-icon>
</button>
)}
<slot name="end">
{ this.togglePassword && <button>Hello World</button> }
</slot>
<slot name="end"></slot>
</div>
{shouldRenderHighlight && <div class="input-highlight"></div>}
</label>

View File

@@ -268,10 +268,6 @@ export class Loading implements ComponentInterface, OverlayInterface {
* This can be useful in a button handler for determining which button was
* clicked to dismiss the loading.
* Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
*
* This is a no-op if the overlay has not been presented yet. If you want
* to remove an overlay from the DOM that was never presented, use the
* [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
@Method()
async dismiss(data?: any, role?: string): Promise<boolean> {

View File

@@ -662,10 +662,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
*
* @param data Any data to emit in the dismiss events.
* @param role The role of the element that is dismissing the modal. For example, 'cancel' or 'backdrop'.
*
* This is a no-op if the overlay has not been presented yet. If you want
* to remove an overlay from the DOM that was never presented, use the
* [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
@Method()
async dismiss(data?: any, role?: string): Promise<boolean> {

View File

@@ -248,10 +248,6 @@ export class Picker implements ComponentInterface, OverlayInterface {
* This can be useful in a button handler for determining which button was
* clicked to dismiss the picker.
* Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
*
* This is a no-op if the overlay has not been presented yet. If you want
* to remove an overlay from the DOM that was never presented, use the
* [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
@Method()
async dismiss(data?: any, role?: string): Promise<boolean> {

View File

@@ -525,10 +525,6 @@ export class Popover implements ComponentInterface, PopoverInterface {
* @param role The role of the element that is dismissing the popover. For example, 'cancel' or 'backdrop'.
* @param dismissParentPopover If `true`, dismissing this popover will also dismiss
* a parent popover if this popover is nested. Defaults to `true`.
*
* This is a no-op if the overlay has not been presented yet. If you want
* to remove an overlay from the DOM that was never presented, use the
* [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
@Method()
async dismiss(data?: any, role?: string, dismissParentPopover = true): Promise<boolean> {

View File

@@ -93,19 +93,6 @@ describe('toast: a11y smoke test', () => {
});
describe('toast: duration config', () => {
afterEach(() => {
/**
* Important: Reset the config
* after each test as it is not
* automatically reset.
* Otherwise, toasts in other tests
* will take on any toastDuration value
* set and timeouts will potentially run
* after tests are finished.
*/
config.reset({});
});
it('should have duration set to 0', async () => {
const page = await newSpecPage({
components: [Toast],

View File

@@ -401,10 +401,6 @@ export class Toast implements ComponentInterface, OverlayInterface {
* This can be useful in a button handler for determining which button was
* clicked to dismiss the toast.
* Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
*
* This is a no-op if the overlay has not been presented yet. If you want
* to remove an overlay from the DOM that was never presented, use the
* [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
@Method()
async dismiss(data?: any, role?: string): Promise<boolean> {