diff --git a/angular/src/directives/virtual-scroll/virtual-scroll.ts b/angular/src/directives/virtual-scroll/virtual-scroll.ts
index 059f85a465..22bf8a2026 100644
--- a/angular/src/directives/virtual-scroll/virtual-scroll.ts
+++ b/angular/src/directives/virtual-scroll/virtual-scroll.ts
@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Component, ContentChild, ElementRef, EmbeddedViewRef, IterableDiffer, IterableDiffers, NgZone, SimpleChanges, TrackByFunction } from '@angular/core';
-import { Cell, CellType, HeaderFn, ItemHeightFn } from '@ionic/core';
+import { Cell, CellType, FooterHeightFn, HeaderFn, HeaderHeightFn, ItemHeightFn } from '@ionic/core';
import { proxyInputs, proxyMethods } from '../proxies-utils';
@@ -75,7 +75,7 @@ export declare interface IonVirtualScroll {
/**
* An optional function that maps each item within their height.
- * When this function is provides, heavy optimizations and fast path can be taked by
+ * When this function is provided, heavy optimizations and fast path can be taked by
* `ion-virtual-scroll` leading to massive performance improvements.
*
* This function allows to skip all DOM reads, which can be Doing so leads
@@ -83,6 +83,16 @@ export declare interface IonVirtualScroll {
*/
itemHeight?: ItemHeightFn;
+ /**
+ * An optional function that maps each item header within their height.
+ */
+ headerHeight?: HeaderHeightFn;
+
+ /**
+ * An optional function that maps each item footer within their height.
+ */
+ footerHeight?: FooterHeightFn;
+
/**
* Same as `ngForTrackBy` which can be used on `ngFor`.
*/
@@ -114,6 +124,8 @@ export declare interface IonVirtualScroll {
'footerFn',
'items',
'itemHeight',
+ 'headerHeight',
+ 'footerHeight',
'trackBy'
]
})
@@ -211,7 +223,9 @@ proxyInputs(IonVirtualScroll, [
'headerFn',
'footerFn',
'items',
- 'itemHeight'
+ 'itemHeight',
+ 'headerHeight',
+ 'footerHeight'
]);
proxyMethods(IonVirtualScroll, [
diff --git a/core/api.txt b/core/api.txt
index 14cb78051c..c056c9383c 100644
--- a/core/api.txt
+++ b/core/api.txt
@@ -1280,7 +1280,9 @@ ion-virtual-scroll,prop,approxFooterHeight,number,30,false,false
ion-virtual-scroll,prop,approxHeaderHeight,number,30,false,false
ion-virtual-scroll,prop,approxItemHeight,number,45,false,false
ion-virtual-scroll,prop,footerFn,((item: any, index: number, items: any[]) => string | null | undefined) | undefined,undefined,false,false
+ion-virtual-scroll,prop,footerHeight,((item: any, index: number) => number) | undefined,undefined,false,false
ion-virtual-scroll,prop,headerFn,((item: any, index: number, items: any[]) => string | null | undefined) | undefined,undefined,false,false
+ion-virtual-scroll,prop,headerHeight,((item: any, index: number) => number) | undefined,undefined,false,false
ion-virtual-scroll,prop,itemHeight,((item: any, index: number) => number) | undefined,undefined,false,false
ion-virtual-scroll,prop,items,any[] | undefined,undefined,false,false
ion-virtual-scroll,prop,nodeRender,((el: HTMLElement | null, cell: Cell, domIndex: number) => HTMLElement) | undefined,undefined,false,false
diff --git a/core/src/components.d.ts b/core/src/components.d.ts
index ecedbc9892..fbd8db1c06 100644
--- a/core/src/components.d.ts
+++ b/core/src/components.d.ts
@@ -20,8 +20,10 @@ import {
DatetimeChangeEventDetail,
DatetimeOptions,
DomRenderFn,
+ FooterHeightFn,
FrameworkDelegate,
HeaderFn,
+ HeaderHeightFn,
InputChangeEventDetail,
ItemHeightFn,
ItemRenderFn,
@@ -2835,10 +2837,18 @@ export namespace Components {
*/
'footerFn'?: HeaderFn;
/**
+ * An optional function that maps each item footer within their height.
+ */
+ 'footerHeight'?: FooterHeightFn;
+ /**
* Section headers and the data used within its given template can be dynamically created by passing a function to `headerFn`. For example, a large list of contacts usually has dividers between each letter in the alphabet. App's can provide their own custom `headerFn` which is called with each record within the dataset. The logic within the header function can decide if the header template should be used, and what data to give to the header template. The function must return `null` if a header cell shouldn't be created.
*/
'headerFn'?: HeaderFn;
/**
+ * An optional function that maps each item header within their height.
+ */
+ 'headerHeight'?: HeaderHeightFn;
+ /**
* An optional function that maps each item within their height. When this function is provides, heavy optimizations and fast path can be taked by `ion-virtual-scroll` leading to massive performance improvements. This function allows to skip all DOM reads, which can be Doing so leads to massive performance
*/
'itemHeight'?: ItemHeightFn;
@@ -6101,10 +6111,18 @@ declare namespace LocalJSX {
*/
'footerFn'?: HeaderFn;
/**
+ * An optional function that maps each item footer within their height.
+ */
+ 'footerHeight'?: FooterHeightFn;
+ /**
* Section headers and the data used within its given template can be dynamically created by passing a function to `headerFn`. For example, a large list of contacts usually has dividers between each letter in the alphabet. App's can provide their own custom `headerFn` which is called with each record within the dataset. The logic within the header function can decide if the header template should be used, and what data to give to the header template. The function must return `null` if a header cell shouldn't be created.
*/
'headerFn'?: HeaderFn;
/**
+ * An optional function that maps each item header within their height.
+ */
+ 'headerHeight'?: HeaderHeightFn;
+ /**
* An optional function that maps each item within their height. When this function is provides, heavy optimizations and fast path can be taked by `ion-virtual-scroll` leading to massive performance improvements. This function allows to skip all DOM reads, which can be Doing so leads to massive performance
*/
'itemHeight'?: ItemHeightFn;
diff --git a/core/src/components/virtual-scroll/readme.md b/core/src/components/virtual-scroll/readme.md
index 87bcfcb4e2..9d4fd7893f 100644
--- a/core/src/components/virtual-scroll/readme.md
+++ b/core/src/components/virtual-scroll/readme.md
@@ -252,7 +252,9 @@ within a `
` is a safe way to make sure dimensions are measured correctly.
| `approxHeaderHeight` | `approx-header-height` | The approximate height of each header template's cell. This dimension is used to help determine how many cells should be created when initialized, and to help calculate the height of the scrollable area. This height value can only use `px` units. Note that the actual rendered size of each cell comes from the app's CSS, whereas this approximation is used to help calculate initial dimensions before the item has been rendered. | `number` | `30` |
| `approxItemHeight` | `approx-item-height` | It is important to provide this if virtual item height will be significantly larger than the default The approximate height of each virtual item template's cell. This dimension is used to help determine how many cells should be created when initialized, and to help calculate the height of the scrollable area. This height value can only use `px` units. Note that the actual rendered size of each cell comes from the app's CSS, whereas this approximation is used to help calculate initial dimensions before the item has been rendered. | `number` | `45` |
| `footerFn` | -- | Section footers and the data used within its given template can be dynamically created by passing a function to `footerFn`. The logic within the footer function can decide if the footer template should be used, and what data to give to the footer template. The function must return `null` if a footer cell shouldn't be created. | `((item: any, index: number, items: any[]) => string \| null \| undefined) \| undefined` | `undefined` |
+| `footerHeight` | -- | An optional function that maps each item footer within their height. | `((item: any, index: number) => number) \| undefined` | `undefined` |
| `headerFn` | -- | Section headers and the data used within its given template can be dynamically created by passing a function to `headerFn`. For example, a large list of contacts usually has dividers between each letter in the alphabet. App's can provide their own custom `headerFn` which is called with each record within the dataset. The logic within the header function can decide if the header template should be used, and what data to give to the header template. The function must return `null` if a header cell shouldn't be created. | `((item: any, index: number, items: any[]) => string \| null \| undefined) \| undefined` | `undefined` |
+| `headerHeight` | -- | An optional function that maps each item header within their height. | `((item: any, index: number) => number) \| undefined` | `undefined` |
| `itemHeight` | -- | An optional function that maps each item within their height. When this function is provides, heavy optimizations and fast path can be taked by `ion-virtual-scroll` leading to massive performance improvements. This function allows to skip all DOM reads, which can be Doing so leads to massive performance | `((item: any, index: number) => number) \| undefined` | `undefined` |
| `items` | -- | The data that builds the templates within the virtual scroll. It's important to note that when this data has changed, then the entire virtual scroll is reset, which is an expensive operation and should be avoided if possible. | `any[] \| undefined` | `undefined` |
| `nodeRender` | -- | NOTE: only Vanilla JS API. | `((el: HTMLElement \| null, cell: Cell, domIndex: number) => HTMLElement) \| undefined` | `undefined` |
diff --git a/core/src/components/virtual-scroll/test/virtual-scroll-utils.spec.ts b/core/src/components/virtual-scroll/test/virtual-scroll-utils.spec.ts
index 03d08fb1d2..bed4ba14a8 100644
--- a/core/src/components/virtual-scroll/test/virtual-scroll-utils.spec.ts
+++ b/core/src/components/virtual-scroll/test/virtual-scroll-utils.spec.ts
@@ -146,7 +146,7 @@ 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, 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,
@@ -195,7 +195,7 @@ describe('calcCells', () => {
called++;
return index * 20 + 20;
};
- const cells = calcCells(items, itemHeight, 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([
@@ -251,7 +251,7 @@ describe('calcCells', () => {
called++;
return index * 20 + 20;
};
- const cells = calcCells(items, itemHeight, 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);
@@ -315,7 +315,7 @@ describe('calcHeightIndex', () => {
const footerFn: HeaderFn = (_, index) => {
return (index === 2) ? 'my footer' : null;
};
- const cells = calcCells(items, undefined, headerFn, footerFn, 10, 20, 50, 0, 0, items.length);
+ const cells = calcCells(items, undefined, undefined, undefined, headerFn, footerFn, 10, 20, 50, 0, 0, items.length);
const buf = resizeBuffer(undefined, cells.length);
const totalHeight = calcHeightIndex(buf, cells, 0);
expect(buf.length).toEqual(7);
@@ -508,7 +508,7 @@ function mockVirtualScroll(
headerFn?: HeaderFn,
footerFn?: HeaderFn
) {
- const cells = calcCells(items, itemHeight, headerFn, footerFn, 10, 10, 30, 0, 0, items.length);
+ 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);
return { items, heightIndex, cells };
diff --git a/core/src/components/virtual-scroll/virtual-scroll-interface.ts b/core/src/components/virtual-scroll/virtual-scroll-interface.ts
index 0ee68d3ae7..daa8bc04e7 100644
--- a/core/src/components/virtual-scroll/virtual-scroll-interface.ts
+++ b/core/src/components/virtual-scroll/virtual-scroll-interface.ts
@@ -21,5 +21,7 @@ export type CellType = 'item' | 'header' | 'footer';
export type NodeChange = number;
export type HeaderFn = (item: any, index: number, items: any[]) => string | null | undefined;
export type ItemHeightFn = (item: any, index: number) => number;
+export type HeaderHeightFn = (item: any, index: number) => number;
+export type FooterHeightFn = (item: any, index: number) => number;
export type ItemRenderFn = (el: HTMLElement | null, cell: Cell, domIndex: number) => HTMLElement;
export type DomRenderFn = (dom: VirtualNode[]) => void;
diff --git a/core/src/components/virtual-scroll/virtual-scroll-utils.ts b/core/src/components/virtual-scroll/virtual-scroll-utils.ts
index a11d9df119..b0df3594ab 100644
--- a/core/src/components/virtual-scroll/virtual-scroll-utils.ts
+++ b/core/src/components/virtual-scroll/virtual-scroll-utils.ts
@@ -1,7 +1,7 @@
import { 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 } from './virtual-scroll-interface';
+import { CellType, FooterHeightFn, HeaderHeightFn } from './virtual-scroll-interface';
export interface Viewport {
top: number;
@@ -205,6 +205,8 @@ export const calcCells = (
items: any[],
itemHeight: ItemHeightFn | undefined,
+ headerHeight: HeaderHeightFn | undefined,
+ footerHeight: FooterHeightFn | undefined,
headerFn: HeaderFn | undefined,
footerFn: HeaderFn | undefined,
@@ -228,9 +230,9 @@ export const calcCells = (
type: CELL_TYPE_HEADER,
value,
index: i,
- height: approxHeaderHeight,
- reads: MIN_READS,
- visible: false,
+ height: headerHeight ? headerHeight(value, i) : approxHeaderHeight,
+ reads: headerHeight ? 0 : MIN_READS,
+ visible: !!headerHeight,
});
}
}
@@ -253,9 +255,9 @@ export const calcCells = (
type: CELL_TYPE_FOOTER,
value,
index: i,
- height: approxFooterHeight,
- reads: 2,
- visible: false,
+ height: footerHeight ? footerHeight(value, i) : approxFooterHeight,
+ reads: footerHeight ? 0 : MIN_READS,
+ visible: !!footerHeight,
});
}
}
diff --git a/core/src/components/virtual-scroll/virtual-scroll.tsx b/core/src/components/virtual-scroll/virtual-scroll.tsx
index 50fbd738b2..bd7883d080 100644
--- a/core/src/components/virtual-scroll/virtual-scroll.tsx
+++ b/core/src/components/virtual-scroll/virtual-scroll.tsx
@@ -1,6 +1,6 @@
import { Component, ComponentInterface, Element, FunctionalComponent, Listen, Method, Prop, State, Watch, h, readTask, writeTask } from '@stencil/core';
-import { Cell, DomRenderFn, HeaderFn, ItemHeightFn, ItemRenderFn, VirtualNode } from '../../interface';
+import { Cell, DomRenderFn, FooterHeightFn, HeaderFn, HeaderHeightFn, ItemHeightFn, ItemRenderFn, VirtualNode } from '../../interface';
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';
@@ -104,6 +104,16 @@ export class VirtualScroll implements ComponentInterface {
*/
@Prop() itemHeight?: ItemHeightFn;
+ /**
+ * An optional function that maps each item header within their height.
+ */
+ @Prop() headerHeight?: HeaderHeightFn;
+
+ /**
+ * An optional function that maps each item footer within their height.
+ */
+ @Prop() footerHeight?: FooterHeightFn;
+
/**
* NOTE: only JSX API for stencil.
*
@@ -134,6 +144,8 @@ export class VirtualScroll implements ComponentInterface {
@Prop() domRender?: DomRenderFn;
@Watch('itemHeight')
+ @Watch('headerHeight')
+ @Watch('footerHeight')
@Watch('items')
itemsChanged() {
this.calcCells();
@@ -197,6 +209,8 @@ export class VirtualScroll implements ComponentInterface {
const cells = calcCells(
this.items,
this.itemHeight,
+ this.headerHeight,
+ this.footerHeight,
this.headerFn,
this.footerFn,
this.approxHeaderHeight,
@@ -357,6 +371,8 @@ export class VirtualScroll implements ComponentInterface {
this.cells = calcCells(
this.items,
this.itemHeight,
+ this.headerHeight,
+ this.footerHeight,
this.headerFn,
this.footerFn,
this.approxHeaderHeight,