chore(): sync with main

This commit is contained in:
Liam DeBeasi
2023-05-11 14:57:44 -04:00
1009 changed files with 4248 additions and 4043 deletions

View File

@ -33,6 +33,14 @@ export class PickerColumnCmp implements ComponentInterface {
private rafId?: ReturnType<typeof requestAnimationFrame>;
private tmrId?: ReturnType<typeof setTimeout>;
private noAnimate = true;
// `colDidChange` is a flag that gets set when the column is changed
// dynamically. When this flag is set, the column will refresh
// after the component re-renders to incorporate the new column data.
// This is necessary because `this.refresh` queries for the option elements,
// so it needs to wait for the latest elements to be available in the DOM.
// Ex: column is created with 3 options. User updates the column data
// to have 5 options. The column will still think it only has 3 options.
private colDidChange = false;
@Element() el!: HTMLElement;
@ -46,7 +54,7 @@ export class PickerColumnCmp implements ComponentInterface {
@Prop() col!: PickerColumn;
@Watch('col')
protected colChanged() {
this.refresh();
this.colDidChange = true;
}
async connectedCallback() {
@ -74,21 +82,32 @@ export class PickerColumnCmp implements ComponentInterface {
onEnd: (ev) => this.onEnd(ev),
});
this.gesture.enable();
// Options have not been initialized yet
// Animation must be disabled through the `noAnimate` flag
// Otherwise, the options will render
// at the top of the column and transition down
this.tmrId = setTimeout(() => {
this.noAnimate = false;
// After initialization, `refresh()` will be called
// At this point, animation will be enabled. The options will
// animate as they are being selected.
this.refresh(true);
}, 250);
}
componentDidLoad() {
const colEl = this.optsEl;
if (colEl) {
// DOM READ
// We perfom a DOM read over a rendered item, this needs to happen after the first render
this.optHeight = colEl.firstElementChild ? colEl.firstElementChild.clientHeight : 0;
}
this.onDomChange();
}
this.refresh();
componentDidUpdate() {
// Options may have changed since last update.
if (this.colDidChange) {
// Animation must be disabled through the `onDomChange` parameter.
// Otherwise, the recently added options will render
// at the top of the column and transition down
this.onDomChange(true, false);
this.colDidChange = false;
}
}
disconnectedCallback() {
@ -331,7 +350,7 @@ export class PickerColumnCmp implements ComponentInterface {
}
}
private refresh(forceRefresh?: boolean) {
private refresh(forceRefresh?: boolean, animated?: boolean) {
let min = this.col.options.length - 1;
let max = 0;
const options = this.col.options;
@ -356,11 +375,22 @@ export class PickerColumnCmp implements ComponentInterface {
const selectedIndex = clamp(min, this.col.selectedIndex ?? 0, max);
if (this.col.prevSelected !== selectedIndex || forceRefresh) {
const y = selectedIndex * this.optHeight * -1;
const duration = animated ? TRANSITION_DURATION : 0;
this.velocity = 0;
this.update(y, TRANSITION_DURATION, true);
this.update(y, duration, true);
}
}
private onDomChange(forceRefresh?: boolean, animated?: boolean) {
const colEl = this.optsEl;
if (colEl) {
// DOM READ
// We perfom a DOM read over a rendered item, this needs to happen after the first render or after the the column has changed
this.optHeight = colEl.firstElementChild ? colEl.firstElementChild.clientHeight : 0;
}
this.refresh(forceRefresh, animated);
}
render() {
const col = this.col;
const mode = getIonMode(this);

View File

@ -0,0 +1,32 @@
import { h } from '@stencil/core';
import { newSpecPage } from '@stencil/core/testing';
import { PickerColumnCmp } from '../picker-column';
describe('picker-column: dynamic options', () => {
/**
* Issue: https://github.com/ionic-team/ionic-framework/issues/21763
*/
it('should add an option', async () => {
const defaultOptions = [
{ text: 'Dog', value: 'dog' },
{ text: 'Cat', value: 'cat' },
];
const page = await newSpecPage({
components: [PickerColumnCmp],
template: () => <ion-picker-column col={{ options: defaultOptions }}></ion-picker-column>,
});
const pickerCol = page.body.querySelector('ion-picker-column');
pickerCol.col = {
options: [...defaultOptions, { text: 'Carrot', value: 'carrot' }],
};
await page.waitForChanges();
const pickerOpt = pickerCol.querySelector('.picker-opt:nth(2)');
expect(pickerOpt.getAttribute('style')).toContain('transform');
});
});

View File

@ -1,22 +1,20 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
import { testPickerColumn } from '../test.utils';
configs().forEach(({ title, screenshot, config }) => {
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('picker-column'), () => {
test.beforeEach(async ({ page }) => {
test('should present picker without ion-app', async ({ page }) => {
await page.goto('/src/components/picker-column/test/standalone', config);
});
test.describe('single column', () => {
test('should not have any visual regressions', async ({ page }) => {
await testPickerColumn(page, screenshot, '#single-column-button', 'single');
});
});
test.describe('multiple columns', () => {
test('should not have any visual regressions', async ({ page }) => {
await testPickerColumn(page, screenshot, '#multiple-column-button', 'multiple');
});
const ionPickerDidPresent = await page.spyOnEvent('ionPickerDidPresent');
const picker = page.locator('ion-picker');
await page.click('#single-column-button');
await ionPickerDidPresent.next();
await expect(picker).toBeVisible();
});
});
});