mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 19:21:34 +08:00
chore(): sync feature-6.1 with main
This commit is contained in:
@ -2,7 +2,7 @@ import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('virtual-scroll: basic', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/virtual-scroll/test/basic?ionic:_testing=true'
|
||||
url: '/src/components/virtual-scroll/test/basic?ionic:_testing=true',
|
||||
});
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
|
@ -1,92 +1,90 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Ionic Item Sliding</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||
<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>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Ionic Item Sliding</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<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>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Ionic CDN demo</ion-title>
|
||||
<ion-buttons slot="primary">
|
||||
<ion-button onclick="addItems()">Add Items</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar></ion-toolbar>
|
||||
<ion-toolbar></ion-toolbar>
|
||||
<ion-toolbar></ion-toolbar>
|
||||
<ion-toolbar></ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Ionic CDN demo</ion-title>
|
||||
<ion-buttons slot="primary">
|
||||
<ion-content>
|
||||
<p>
|
||||
<ion-button onclick="addItems()">Add Items</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar></ion-toolbar>
|
||||
<ion-toolbar></ion-toolbar>
|
||||
<ion-toolbar></ion-toolbar>
|
||||
<ion-toolbar></ion-toolbar>
|
||||
</ion-header>
|
||||
</p>
|
||||
|
||||
<ion-content>
|
||||
<p>
|
||||
<ion-button onclick="addItems()">Add Items</ion-button>
|
||||
</p>
|
||||
<ion-virtual-scroll id="virtual">
|
||||
<template>
|
||||
<ion-item>
|
||||
<ion-label></ion-label>
|
||||
</ion-item>
|
||||
</template>
|
||||
</ion-virtual-scroll>
|
||||
|
||||
<ion-virtual-scroll id="virtual">
|
||||
<template>
|
||||
<ion-item>
|
||||
<ion-label></ion-label>
|
||||
</ion-item>
|
||||
</template>
|
||||
</ion-virtual-scroll>
|
||||
<ion-infinite-scroll threshold="100px" id="infinite-scroll">
|
||||
<ion-infinite-scroll-content loading-spinner="bubbles" loading-text="Loading more data...">
|
||||
</ion-infinite-scroll-content>
|
||||
</ion-infinite-scroll>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
<ion-infinite-scroll threshold="100px" id="infinite-scroll">
|
||||
<ion-infinite-scroll-content loading-spinner="bubbles" loading-text="Loading more data...">
|
||||
</ion-infinite-scroll-content>
|
||||
</ion-infinite-scroll>
|
||||
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
const items = Array.from({ length: 100 }, (x, i) => i);
|
||||
const virtual = document.getElementById('virtual');
|
||||
virtual.items = items;
|
||||
virtual.nodeRender = (el, cell) => {
|
||||
if (cell.type === 'item') {
|
||||
renderItem(el, cell.value);
|
||||
<script>
|
||||
const items = Array.from({ length: 100 }, (x, i) => i);
|
||||
const virtual = document.getElementById('virtual');
|
||||
virtual.items = items;
|
||||
virtual.nodeRender = (el, cell) => {
|
||||
if (cell.type === 'item') {
|
||||
renderItem(el, cell.value);
|
||||
}
|
||||
};
|
||||
function renderItem(el, item) {
|
||||
el.querySelector('ion-label').textContent = item;
|
||||
}
|
||||
};
|
||||
function renderItem(el, item) {
|
||||
el.querySelector('ion-label').textContent = item;
|
||||
}
|
||||
|
||||
|
||||
const infiniteScroll = document.getElementById('infinite-scroll');
|
||||
infiniteScroll.addEventListener('ionInfinite', async function () {
|
||||
const data = await getAsyncData();
|
||||
infiniteScroll.complete();
|
||||
addItems(data);
|
||||
});
|
||||
|
||||
function addItems(append) {
|
||||
if (!append) {
|
||||
append = Array.from({ length: 10 }, (x, i) => "append" + i);
|
||||
}
|
||||
items.push(...append);
|
||||
virtual.checkEnd(append.length);
|
||||
}
|
||||
|
||||
function getAsyncData() {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
const data = Array.from({ length: 10 }, (x, i) => "append" + i);
|
||||
resolve(data);
|
||||
}, 500);
|
||||
const infiniteScroll = document.getElementById('infinite-scroll');
|
||||
infiniteScroll.addEventListener('ionInfinite', async function () {
|
||||
const data = await getAsyncData();
|
||||
infiniteScroll.complete();
|
||||
addItems(data);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
function addItems(append) {
|
||||
if (!append) {
|
||||
append = Array.from({ length: 10 }, (x, i) => 'append' + i);
|
||||
}
|
||||
items.push(...append);
|
||||
virtual.checkEnd(append.length);
|
||||
}
|
||||
|
||||
function getAsyncData() {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const data = Array.from({ length: 10 }, (x, i) => 'append' + i);
|
||||
resolve(data);
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -2,7 +2,7 @@ import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('virtual-scroll: cards', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/virtual-scroll/test/cards?ionic:_testing=true'
|
||||
url: '/src/components/virtual-scroll/test/cards?ionic:_testing=true',
|
||||
});
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
|
@ -1,80 +1,77 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Ionic Item Sliding</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||
<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>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Ionic Item Sliding</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<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>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Ionic CDN demo</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-content>
|
||||
<ion-virtual-scroll id="virtual">
|
||||
<template>
|
||||
<ion-card>
|
||||
<ion-card-header>
|
||||
<ion-card-title>Card Header</ion-card-title>
|
||||
</ion-card-header>
|
||||
<ion-card-content class="content"></ion-card-content>
|
||||
</ion-card>
|
||||
</template>
|
||||
</ion-virtual-scroll>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
const virtual = document.getElementById('virtual');
|
||||
virtual.approxItemHeight = 200;
|
||||
virtual.headerFn = (item, index) => {
|
||||
if (index % 20 === 0) {
|
||||
return 'Header ' + index;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Ionic CDN demo</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
|
||||
<ion-virtual-scroll id="virtual">
|
||||
<template>
|
||||
<ion-card>
|
||||
<ion-card-header>
|
||||
<ion-card-title>Card Header</ion-card-title>
|
||||
</ion-card-header>
|
||||
<ion-card-content class="content"></ion-card-content>
|
||||
</ion-card>
|
||||
</template>
|
||||
</ion-virtual-scroll>
|
||||
|
||||
</ion-content>
|
||||
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
const virtual = document.getElementById('virtual');
|
||||
virtual.approxItemHeight = 200;
|
||||
virtual.headerFn = (item, index) => {
|
||||
if (index % 20 === 0) {
|
||||
return 'Header ' + index;
|
||||
function renderItem(el, item) {
|
||||
el.querySelector('.content').textContent = item.content;
|
||||
return el;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderItem(el, item) {
|
||||
el.querySelector('.content').textContent = item.content;
|
||||
return el;
|
||||
}
|
||||
|
||||
function renderHeader(el, item) {
|
||||
if (!el) {
|
||||
el = document.createElement('ion-item-divider');
|
||||
function renderHeader(el, item) {
|
||||
if (!el) {
|
||||
el = document.createElement('ion-item-divider');
|
||||
}
|
||||
el.textContent = item;
|
||||
return el;
|
||||
}
|
||||
el.textContent = item;
|
||||
return el;
|
||||
}
|
||||
|
||||
virtual.nodeRender = (el, cell) => {
|
||||
if (cell.type === 'item') return renderItem(el, cell.value);
|
||||
return renderHeader(el, cell.value);
|
||||
};
|
||||
const lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
|
||||
const items = [];
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
items.push({
|
||||
content: lorem.substring(0, lorem.length * ((i % 4)+1) / 5)
|
||||
});
|
||||
}
|
||||
virtual.items = items;
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
virtual.nodeRender = (el, cell) => {
|
||||
if (cell.type === 'item') return renderItem(el, cell.value);
|
||||
return renderHeader(el, cell.value);
|
||||
};
|
||||
const lorem =
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
|
||||
const items = [];
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
items.push({
|
||||
content: lorem.substring(0, (lorem.length * ((i % 4) + 1)) / 5),
|
||||
});
|
||||
}
|
||||
virtual.items = items;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,6 +1,16 @@
|
||||
import { HeaderFn, ItemHeightFn, VirtualNode } from '../../../interface';
|
||||
import { Range, calcCells, calcHeightIndex, getRange, getShouldUpdate, getViewport, positionForIndex, resizeBuffer, updateVDom } from '../virtual-scroll-utils';
|
||||
import type { HeaderFn, ItemHeightFn, VirtualNode } from '../../../interface';
|
||||
import { CELL_TYPE_ITEM, CELL_TYPE_HEADER, CELL_TYPE_FOOTER } from '../constants';
|
||||
import type { Range } from '../virtual-scroll-utils';
|
||||
import {
|
||||
calcCells,
|
||||
calcHeightIndex,
|
||||
getRange,
|
||||
getShouldUpdate,
|
||||
getViewport,
|
||||
positionForIndex,
|
||||
resizeBuffer,
|
||||
updateVDom,
|
||||
} from '../virtual-scroll-utils';
|
||||
|
||||
describe('getViewport', () => {
|
||||
it('should return viewport without margin', () => {
|
||||
@ -146,7 +156,20 @@ describe('resizeBuffer', () => {
|
||||
describe('calcCells', () => {
|
||||
it('should calculate cells without headers and itemHeight', () => {
|
||||
const items = ['0', 2, 'hola', { data: 'hello' }];
|
||||
const cells = calcCells(items, undefined, undefined, undefined, undefined, undefined, 10, 20, 30, 0, 0, items.length);
|
||||
const cells = calcCells(
|
||||
items,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
10,
|
||||
20,
|
||||
30,
|
||||
0,
|
||||
0,
|
||||
items.length
|
||||
);
|
||||
expect(cells).toEqual([
|
||||
{
|
||||
type: CELL_TYPE_ITEM,
|
||||
@ -183,7 +206,7 @@ describe('calcCells', () => {
|
||||
height: 30,
|
||||
reads: 2,
|
||||
visible: false,
|
||||
}
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@ -195,7 +218,20 @@ describe('calcCells', () => {
|
||||
called++;
|
||||
return index * 20 + 20;
|
||||
};
|
||||
const cells = calcCells(items, itemHeight, undefined, undefined, undefined, undefined, 10, 20, 30, 0, 0, items.length);
|
||||
const cells = calcCells(
|
||||
items,
|
||||
itemHeight,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
10,
|
||||
20,
|
||||
30,
|
||||
0,
|
||||
0,
|
||||
items.length
|
||||
);
|
||||
|
||||
expect(called).toEqual(3);
|
||||
expect(cells).toEqual([
|
||||
@ -225,7 +261,7 @@ describe('calcCells', () => {
|
||||
height: 60,
|
||||
reads: 0,
|
||||
visible: true,
|
||||
}
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@ -238,20 +274,33 @@ describe('calcCells', () => {
|
||||
expect(item).toEqual(items[index]);
|
||||
expect(items).toBe(allItems);
|
||||
headerCalled++;
|
||||
return (index === 0) ? 'my header' : null;
|
||||
return index === 0 ? 'my header' : null;
|
||||
};
|
||||
const footerFn: HeaderFn = (item, index, allItems) => {
|
||||
expect(item).toEqual(items[index]);
|
||||
expect(items).toBe(allItems);
|
||||
footerCalled++;
|
||||
return (index === 2) ? 'my footer' : null;
|
||||
return index === 2 ? 'my footer' : null;
|
||||
};
|
||||
const itemHeight: ItemHeightFn = (item: any, index: number) => {
|
||||
expect(item).toEqual(items[index]);
|
||||
called++;
|
||||
return index * 20 + 20;
|
||||
};
|
||||
const cells = calcCells(items, itemHeight, undefined, undefined, headerFn, footerFn, 10, 20, 30, 0, 0, items.length);
|
||||
const cells = calcCells(
|
||||
items,
|
||||
itemHeight,
|
||||
undefined,
|
||||
undefined,
|
||||
headerFn,
|
||||
footerFn,
|
||||
10,
|
||||
20,
|
||||
30,
|
||||
0,
|
||||
0,
|
||||
items.length
|
||||
);
|
||||
expect(cells).toHaveLength(5);
|
||||
expect(called).toEqual(3);
|
||||
expect(headerCalled).toEqual(3);
|
||||
@ -301,7 +350,7 @@ describe('calcCells', () => {
|
||||
height: 20,
|
||||
reads: 2,
|
||||
visible: false,
|
||||
}
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -310,10 +359,10 @@ describe('calcHeightIndex', () => {
|
||||
it('should generate height index', () => {
|
||||
const items = [1, 2, 3, 4, 5];
|
||||
const headerFn: HeaderFn = (_, index) => {
|
||||
return (index === 0) ? 'my header' : null;
|
||||
return index === 0 ? 'my header' : null;
|
||||
};
|
||||
const footerFn: HeaderFn = (_, index) => {
|
||||
return (index === 2) ? 'my footer' : null;
|
||||
return index === 2 ? 'my footer' : null;
|
||||
};
|
||||
const cells = calcCells(items, undefined, undefined, undefined, headerFn, footerFn, 10, 20, 50, 0, 0, items.length);
|
||||
const buf = resizeBuffer(undefined, cells.length);
|
||||
@ -352,10 +401,11 @@ describe('getShouldUpdate', () => {
|
||||
describe('positionForIndex', () => {
|
||||
it('should return the correct position', () => {
|
||||
const items = [1, 2, 3, 4];
|
||||
const { cells, heightIndex } = mockVirtualScroll(items,
|
||||
const { cells, heightIndex } = mockVirtualScroll(
|
||||
items,
|
||||
() => 40,
|
||||
(_, i) => i === 1 ? 'hola' : null,
|
||||
(_, i) => i === 2 ? 'hola' : null
|
||||
(_, i) => (i === 1 ? 'hola' : null),
|
||||
(_, i) => (i === 2 ? 'hola' : null)
|
||||
);
|
||||
expect(positionForIndex(0, cells, heightIndex)).toEqual(0);
|
||||
expect(positionForIndex(1, cells, heightIndex)).toEqual(50);
|
||||
@ -368,9 +418,11 @@ describe('updateVDom', () => {
|
||||
it('should initialize empty VDOM', () => {
|
||||
const vdom: VirtualNode[] = [];
|
||||
const items = [1, 2, 3, 4, 5];
|
||||
const { heightIndex, cells } = mockVirtualScroll(items, () => 20,
|
||||
(_, i) => i === 1 ? 'hola' : null,
|
||||
(_, i) => i === 2 ? 'hola' : null
|
||||
const { heightIndex, cells } = mockVirtualScroll(
|
||||
items,
|
||||
() => 20,
|
||||
(_, i) => (i === 1 ? 'hola' : null),
|
||||
(_, i) => (i === 2 ? 'hola' : null)
|
||||
);
|
||||
const range: Range = { offset: 1, length: 6 };
|
||||
|
||||
@ -381,7 +433,7 @@ describe('updateVDom', () => {
|
||||
{ cell: cells[3], change: 2, d: false, top: 50, visible: true },
|
||||
{ cell: cells[4], change: 2, d: false, top: 70, visible: true },
|
||||
{ cell: cells[5], change: 2, d: false, top: 80, visible: true },
|
||||
{ cell: cells[6], change: 2, d: false, top: 100, visible: true }
|
||||
{ cell: cells[6], change: 2, d: false, top: 100, visible: true },
|
||||
]);
|
||||
});
|
||||
|
||||
@ -395,7 +447,7 @@ describe('updateVDom', () => {
|
||||
{ cell: cells[0], change: 0, d: false, top: 0, visible: true },
|
||||
{ cell: cells[1], change: 0, d: false, top: 20, visible: true },
|
||||
{ cell: cells[2], change: 0, d: false, top: 40, visible: true },
|
||||
{ cell: cells[3], change: 0, d: false, top: 60, visible: true }
|
||||
{ cell: cells[3], change: 0, d: false, top: 60, visible: true },
|
||||
]);
|
||||
|
||||
updateVDom(vdom, heightIndex, cells, { offset: 0, length: 5 });
|
||||
@ -404,7 +456,7 @@ describe('updateVDom', () => {
|
||||
{ cell: cells[1], change: 0, d: false, top: 20, visible: true },
|
||||
{ cell: cells[2], change: 0, d: false, top: 40, visible: true },
|
||||
{ cell: cells[3], change: 0, d: false, top: 60, visible: true },
|
||||
{ cell: cells[4], change: 2, d: false, top: 80, visible: true }
|
||||
{ cell: cells[4], change: 2, d: false, top: 80, visible: true },
|
||||
]);
|
||||
|
||||
updateVDom(vdom, heightIndex, cells, { offset: 1, length: 4 });
|
||||
@ -413,7 +465,7 @@ describe('updateVDom', () => {
|
||||
{ cell: cells[1], change: 0, d: false, top: 20, visible: true },
|
||||
{ cell: cells[2], change: 0, d: false, top: 40, visible: true },
|
||||
{ cell: cells[3], change: 0, d: false, top: 60, visible: true },
|
||||
{ cell: cells[4], change: 0, d: false, top: 80, visible: true }
|
||||
{ cell: cells[4], change: 0, d: false, top: 80, visible: true },
|
||||
]);
|
||||
|
||||
updateVDom(vdom, heightIndex, cells, { offset: 1, length: 5 });
|
||||
@ -422,7 +474,7 @@ describe('updateVDom', () => {
|
||||
{ cell: cells[1], change: 0, d: false, top: 20, visible: true },
|
||||
{ cell: cells[2], change: 0, d: false, top: 40, visible: true },
|
||||
{ cell: cells[3], change: 0, d: false, top: 60, visible: true },
|
||||
{ cell: cells[4], change: 0, d: false, top: 80, visible: true }
|
||||
{ cell: cells[4], change: 0, d: false, top: 80, visible: true },
|
||||
]);
|
||||
|
||||
updateVDom(vdom, heightIndex, cells, { offset: 2, length: 5 });
|
||||
@ -431,7 +483,7 @@ describe('updateVDom', () => {
|
||||
{ cell: cells[6], change: 2, d: false, top: 120, visible: true },
|
||||
{ cell: cells[2], change: 0, d: false, top: 40, visible: true },
|
||||
{ cell: cells[3], change: 0, d: false, top: 60, visible: true },
|
||||
{ cell: cells[4], change: 0, d: false, top: 80, visible: true }
|
||||
{ cell: cells[4], change: 0, d: false, top: 80, visible: true },
|
||||
]);
|
||||
|
||||
updateVDom(vdom, heightIndex, cells, { offset: 10, length: 6 });
|
||||
@ -441,7 +493,7 @@ describe('updateVDom', () => {
|
||||
{ cell: cells[12], change: 2, d: false, top: 240, visible: true },
|
||||
{ cell: cells[13], change: 2, d: false, top: 260, visible: true },
|
||||
{ cell: cells[14], change: 2, d: false, top: 280, visible: true },
|
||||
{ cell: cells[15], change: 2, d: false, top: 300, visible: true }
|
||||
{ cell: cells[15], change: 2, d: false, top: 300, visible: true },
|
||||
]);
|
||||
|
||||
updateVDom(vdom, heightIndex, cells, { offset: 13, length: 10 });
|
||||
@ -502,12 +554,7 @@ describe('updateVDom', () => {
|
||||
});
|
||||
});
|
||||
|
||||
function mockVirtualScroll(
|
||||
items: any[],
|
||||
itemHeight?: ItemHeightFn,
|
||||
headerFn?: HeaderFn,
|
||||
footerFn?: HeaderFn
|
||||
) {
|
||||
function mockVirtualScroll(items: any[], itemHeight?: ItemHeightFn, headerFn?: HeaderFn, footerFn?: HeaderFn) {
|
||||
const cells = calcCells(items, itemHeight, undefined, undefined, headerFn, footerFn, 10, 10, 30, 0, 0, items.length);
|
||||
const heightIndex = resizeBuffer(undefined, cells.length);
|
||||
calcHeightIndex(heightIndex, cells, 0);
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
export interface Cell {
|
||||
i: number;
|
||||
index: number;
|
||||
|
@ -1,7 +1,14 @@
|
||||
import { Cell, HeaderFn, ItemHeightFn, ItemRenderFn, VirtualNode } from '../../interface';
|
||||
import type { Cell, HeaderFn, ItemHeightFn, ItemRenderFn, VirtualNode } from '../../interface';
|
||||
|
||||
import { CELL_TYPE_FOOTER, CELL_TYPE_HEADER, CELL_TYPE_ITEM, NODE_CHANGE_CELL, NODE_CHANGE_NONE, NODE_CHANGE_POSITION } from './constants';
|
||||
import { CellType, FooterHeightFn, HeaderHeightFn } from './virtual-scroll-interface';
|
||||
import {
|
||||
CELL_TYPE_FOOTER,
|
||||
CELL_TYPE_HEADER,
|
||||
CELL_TYPE_ITEM,
|
||||
NODE_CHANGE_CELL,
|
||||
NODE_CHANGE_NONE,
|
||||
NODE_CHANGE_POSITION,
|
||||
} from './constants';
|
||||
import type { CellType, FooterHeightFn, HeaderHeightFn } from './virtual-scroll-interface';
|
||||
|
||||
export interface Viewport {
|
||||
top: number;
|
||||
@ -28,7 +35,7 @@ export const updateVDom = (dom: VirtualNode[], heightIndex: Uint32Array, cells:
|
||||
|
||||
for (let i = range.offset; i < end; i++) {
|
||||
const cell = cells[i];
|
||||
const node = dom.find(n => n.d && n.cell === cell);
|
||||
const node = dom.find((n) => n.d && n.cell === cell);
|
||||
if (node) {
|
||||
const top = heightIndex[i];
|
||||
if (top !== node.top) {
|
||||
@ -42,10 +49,10 @@ export const updateVDom = (dom: VirtualNode[], heightIndex: Uint32Array, cells:
|
||||
}
|
||||
|
||||
// needs to append
|
||||
const pool = dom.filter(n => n.d);
|
||||
const pool = dom.filter((n) => n.d);
|
||||
|
||||
for (const cell of toMutate) {
|
||||
const node = pool.find(n => n.d && n.cell.type === cell.type);
|
||||
const node = pool.find((n) => n.d && n.cell.type === cell.type);
|
||||
const index = cell.i;
|
||||
if (node) {
|
||||
node.d = false;
|
||||
@ -63,8 +70,8 @@ export const updateVDom = (dom: VirtualNode[], heightIndex: Uint32Array, cells:
|
||||
}
|
||||
}
|
||||
dom
|
||||
.filter(n => n.d && n.top !== -9999)
|
||||
.forEach(n => {
|
||||
.filter((n) => n.d && n.top !== -9999)
|
||||
.forEach((n) => {
|
||||
n.change = NODE_CHANGE_POSITION;
|
||||
n.top = -9999;
|
||||
});
|
||||
@ -76,7 +83,7 @@ export const doRender = (
|
||||
dom: VirtualNode[],
|
||||
updateCellHeight: (cell: Cell, node: HTMLElement) => void
|
||||
) => {
|
||||
const children = Array.from(el.children).filter(n => n.tagName !== 'TEMPLATE');
|
||||
const children = Array.from(el.children).filter((n) => n.tagName !== 'TEMPLATE');
|
||||
const childrenNu = children.length;
|
||||
let child: HTMLElement;
|
||||
for (let i = 0; i < dom.length; i++) {
|
||||
@ -133,16 +140,19 @@ const createNode = (el: HTMLElement, type: CellType): HTMLElement | null => {
|
||||
|
||||
const getTemplate = (el: HTMLElement, type: CellType): HTMLTemplateElement | null => {
|
||||
switch (type) {
|
||||
case CELL_TYPE_ITEM: return el.querySelector('template:not([name])');
|
||||
case CELL_TYPE_HEADER: return el.querySelector('template[name=header]');
|
||||
case CELL_TYPE_FOOTER: return el.querySelector('template[name=footer]');
|
||||
case CELL_TYPE_ITEM:
|
||||
return el.querySelector('template:not([name])');
|
||||
case CELL_TYPE_HEADER:
|
||||
return el.querySelector('template[name=header]');
|
||||
case CELL_TYPE_FOOTER:
|
||||
return el.querySelector('template[name=footer]');
|
||||
}
|
||||
};
|
||||
|
||||
export const getViewport = (scrollTop: number, vierportHeight: number, margin: number): Viewport => {
|
||||
return {
|
||||
top: Math.max(scrollTop - margin, 0),
|
||||
bottom: scrollTop + vierportHeight + margin
|
||||
bottom: scrollTop + vierportHeight + margin,
|
||||
};
|
||||
};
|
||||
|
||||
@ -173,11 +183,7 @@ export const getRange = (heightIndex: Uint32Array, viewport: Viewport, buffer: n
|
||||
|
||||
export const getShouldUpdate = (dirtyIndex: number, currentRange: Range, range: Range) => {
|
||||
const end = range.offset + range.length;
|
||||
return (
|
||||
dirtyIndex <= end ||
|
||||
currentRange.offset !== range.offset ||
|
||||
currentRange.length !== range.length
|
||||
);
|
||||
return dirtyIndex <= end || currentRange.offset !== range.offset || currentRange.length !== range.length;
|
||||
};
|
||||
|
||||
export const findCellIndex = (cells: Cell[], index: number): number => {
|
||||
@ -187,7 +193,7 @@ export const findCellIndex = (cells: Cell[], index: number): number => {
|
||||
} else if (index === max + 1) {
|
||||
return cells.length;
|
||||
} else {
|
||||
return cells.findIndex(c => c.index === index);
|
||||
return cells.findIndex((c) => c.index === index);
|
||||
}
|
||||
};
|
||||
|
||||
@ -290,7 +296,7 @@ export const resizeBuffer = (buf: Uint32Array | undefined, len: number) => {
|
||||
};
|
||||
|
||||
export const positionForIndex = (index: number, cells: Cell[], heightIndex: Uint32Array): number => {
|
||||
const cell = cells.find(c => c.type === CELL_TYPE_ITEM && c.index === index);
|
||||
const cell = cells.find((c) => c.type === CELL_TYPE_ITEM && c.index === index);
|
||||
if (cell) {
|
||||
return heightIndex[cell.i];
|
||||
}
|
||||
|
@ -1,17 +1,52 @@
|
||||
import { Component, ComponentInterface, Element, FunctionalComponent, Host, Listen, Method, Prop, State, Watch, forceUpdate, h, readTask, writeTask } from '@stencil/core';
|
||||
import type { ComponentInterface, FunctionalComponent } from '@stencil/core';
|
||||
import {
|
||||
Component,
|
||||
Element,
|
||||
Host,
|
||||
Listen,
|
||||
Method,
|
||||
Prop,
|
||||
State,
|
||||
Watch,
|
||||
forceUpdate,
|
||||
h,
|
||||
readTask,
|
||||
writeTask,
|
||||
} from '@stencil/core';
|
||||
|
||||
import { Cell, DomRenderFn, FooterHeightFn, HeaderFn, HeaderHeightFn, ItemHeightFn, ItemRenderFn, VirtualNode } from '../../interface';
|
||||
import type {
|
||||
Cell,
|
||||
DomRenderFn,
|
||||
FooterHeightFn,
|
||||
HeaderFn,
|
||||
HeaderHeightFn,
|
||||
ItemHeightFn,
|
||||
ItemRenderFn,
|
||||
VirtualNode,
|
||||
} from '../../interface';
|
||||
import { componentOnReady } from '../../utils/helpers';
|
||||
|
||||
import { CELL_TYPE_FOOTER, CELL_TYPE_HEADER, CELL_TYPE_ITEM } from './constants';
|
||||
import { Range, calcCells, calcHeightIndex, doRender, findCellIndex, getRange, getShouldUpdate, getViewport, inplaceUpdate, positionForIndex, resizeBuffer, updateVDom } from './virtual-scroll-utils';
|
||||
import type { Range } from './virtual-scroll-utils';
|
||||
import {
|
||||
calcCells,
|
||||
calcHeightIndex,
|
||||
doRender,
|
||||
findCellIndex,
|
||||
getRange,
|
||||
getShouldUpdate,
|
||||
getViewport,
|
||||
inplaceUpdate,
|
||||
positionForIndex,
|
||||
resizeBuffer,
|
||||
updateVDom,
|
||||
} from './virtual-scroll-utils';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-virtual-scroll',
|
||||
styleUrl: 'virtual-scroll.scss'
|
||||
styleUrl: 'virtual-scroll.scss',
|
||||
})
|
||||
export class VirtualScroll implements ComponentInterface {
|
||||
|
||||
private contentEl?: HTMLElement;
|
||||
private scrollEl?: HTMLElement;
|
||||
private range: Range = { offset: 0, length: 0 };
|
||||
@ -154,7 +189,9 @@ export class VirtualScroll implements ComponentInterface {
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
console.warn(`[Deprecation Warning]: ion-virtual-scroll has been deprecated and will be removed in Ionic Framework v7.0. See https://ionicframework.com/docs/angular/virtual-scroll for migration steps.`);
|
||||
console.warn(
|
||||
`[Deprecation Warning]: ion-virtual-scroll has been deprecated and will be removed in Ionic Framework v7.0. See https://ionicframework.com/docs/angular/virtual-scroll for migration steps.`
|
||||
);
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
@ -204,9 +241,7 @@ export class VirtualScroll implements ComponentInterface {
|
||||
if (!this.items) {
|
||||
return;
|
||||
}
|
||||
const length = (len === -1)
|
||||
? this.items.length - offset
|
||||
: len;
|
||||
const length = len === -1 ? this.items.length - offset : len;
|
||||
|
||||
const cellIndex = findCellIndex(this.cells, offset);
|
||||
const cells = calcCells(
|
||||
@ -219,7 +254,9 @@ export class VirtualScroll implements ComponentInterface {
|
||||
this.approxHeaderHeight,
|
||||
this.approxFooterHeight,
|
||||
this.approxItemHeight,
|
||||
cellIndex, offset, length
|
||||
cellIndex,
|
||||
offset,
|
||||
length
|
||||
);
|
||||
this.cells = inplaceUpdate(this.cells, cells, cellIndex);
|
||||
this.lastItemLen = this.items.length;
|
||||
@ -246,7 +283,7 @@ export class VirtualScroll implements ComponentInterface {
|
||||
|
||||
private onScroll = () => {
|
||||
this.updateVirtualScroll();
|
||||
}
|
||||
};
|
||||
|
||||
private updateVirtualScroll() {
|
||||
// do nothing if virtual-scroll is disabled
|
||||
@ -301,12 +338,7 @@ export class VirtualScroll implements ComponentInterface {
|
||||
this.range = range;
|
||||
|
||||
// in place mutation of the virtual DOM
|
||||
updateVDom(
|
||||
this.virtualDom,
|
||||
heightIndex,
|
||||
this.cells,
|
||||
range
|
||||
);
|
||||
updateVDom(this.virtualDom, heightIndex, this.cells, range);
|
||||
|
||||
// Write DOM
|
||||
// Different code paths taken depending of the render API used
|
||||
@ -354,10 +386,7 @@ export class VirtualScroll implements ComponentInterface {
|
||||
}
|
||||
|
||||
private updateState() {
|
||||
const shouldEnable = !!(
|
||||
this.scrollEl &&
|
||||
this.cells
|
||||
);
|
||||
const shouldEnable = !!(this.scrollEl && this.cells);
|
||||
if (shouldEnable !== this.isEnabled) {
|
||||
this.enableScrollEvents(shouldEnable);
|
||||
if (shouldEnable) {
|
||||
@ -381,7 +410,9 @@ export class VirtualScroll implements ComponentInterface {
|
||||
this.approxHeaderHeight,
|
||||
this.approxFooterHeight,
|
||||
this.approxItemHeight,
|
||||
0, 0, this.lastItemLen
|
||||
0,
|
||||
0,
|
||||
this.lastItemLen
|
||||
);
|
||||
this.indexDirty = 0;
|
||||
}
|
||||
@ -420,9 +451,12 @@ export class VirtualScroll implements ComponentInterface {
|
||||
private renderVirtualNode(node: VirtualNode) {
|
||||
const { type, value, index } = node.cell;
|
||||
switch (type) {
|
||||
case CELL_TYPE_ITEM: return this.renderItem!(value, index);
|
||||
case CELL_TYPE_HEADER: return this.renderHeader!(value, index);
|
||||
case CELL_TYPE_FOOTER: return this.renderFooter!(value, index);
|
||||
case CELL_TYPE_ITEM:
|
||||
return this.renderItem!(value, index);
|
||||
case CELL_TYPE_HEADER:
|
||||
return this.renderHeader!(value, index);
|
||||
case CELL_TYPE_FOOTER:
|
||||
return this.renderFooter!(value, index);
|
||||
}
|
||||
}
|
||||
|
||||
@ -430,12 +464,12 @@ export class VirtualScroll implements ComponentInterface {
|
||||
return (
|
||||
<Host
|
||||
style={{
|
||||
height: `${this.totalHeight}px`
|
||||
height: `${this.totalHeight}px`,
|
||||
}}
|
||||
>
|
||||
{this.renderItem && (
|
||||
<VirtualProxy dom={this.virtualDom}>
|
||||
{this.virtualDom.map(node => this.renderVirtualNode(node))}
|
||||
{this.virtualDom.map((node) => this.renderVirtualNode(node))}
|
||||
</VirtualProxy>
|
||||
)}
|
||||
</Host>
|
||||
@ -459,9 +493,9 @@ const VirtualProxy: FunctionalComponent<{ dom: VirtualNode[] }> = ({ dom }, chil
|
||||
class: classes,
|
||||
style: {
|
||||
...vattrs.style,
|
||||
transform: `translate3d(0,${node.top}px,0)`
|
||||
}
|
||||
}
|
||||
transform: `translate3d(0,${node.top}px,0)`,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
Reference in New Issue
Block a user