Merge remote-tracking branch 'origin/main' into sync-73-815
6
core/src/components.d.ts
vendored
@@ -1277,9 +1277,6 @@ export namespace Components {
|
||||
* The shape of the input. If "round" it will have an increased border radius.
|
||||
*/
|
||||
"shape"?: 'round';
|
||||
/**
|
||||
* The initial size of the control. This value is in pixels unless the value of the type attribute is `"text"` or `"password"`, in which case it is an integer number of characters. This attribute applies only when the `type` attribute is set to `"text"`, `"search"`, `"tel"`, `"url"`, `"email"`, or `"password"`, otherwise it is ignored.
|
||||
*/
|
||||
"size"?: number;
|
||||
/**
|
||||
* If `true`, the element will have its spelling and grammar checked.
|
||||
@@ -5339,9 +5336,6 @@ declare namespace LocalJSX {
|
||||
* The shape of the input. If "round" it will have an increased border radius.
|
||||
*/
|
||||
"shape"?: 'round';
|
||||
/**
|
||||
* The initial size of the control. This value is in pixels unless the value of the type attribute is `"text"` or `"password"`, in which case it is an integer number of characters. This attribute applies only when the `type` attribute is set to `"text"`, `"search"`, `"tel"`, `"url"`, `"email"`, or `"password"`, otherwise it is ignored.
|
||||
*/
|
||||
"size"?: number;
|
||||
/**
|
||||
* If `true`, the element will have its spelling and grammar checked.
|
||||
|
||||
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
@@ -51,7 +51,7 @@
|
||||
}
|
||||
|
||||
.header-collapse-condense ion-toolbar:first-of-type {
|
||||
padding-top: 7px;
|
||||
padding-top: 1px;
|
||||
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -268,9 +268,7 @@ export class Input implements ComponentInterface {
|
||||
*/
|
||||
@Prop() step?: string;
|
||||
|
||||
/**
|
||||
* The initial size of the control. This value is in pixels unless the value of the type attribute is `"text"` or `"password"`, in which case it is an integer number of characters. This attribute applies only when the `type` attribute is set to `"text"`, `"search"`, `"tel"`, `"url"`, `"email"`, or `"password"`, otherwise it is ignored.
|
||||
*/
|
||||
// FW-4914 Remove this property in Ionic 8
|
||||
@Prop() size?: number;
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,6 @@ import { configs, dragElementBy, test } from '@utils/test/playwright';
|
||||
|
||||
import { testSlidingItem } from '../test.utils';
|
||||
|
||||
// TODO FW-3006
|
||||
/**
|
||||
* item-sliding doesn't have mode-specific styling
|
||||
*/
|
||||
@@ -18,19 +17,12 @@ configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => {
|
||||
});
|
||||
});
|
||||
|
||||
// TODO FW-3006
|
||||
/**
|
||||
* This behavior does not vary across modes/directions
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('item-sliding: basic'), () => {
|
||||
// mouse gesture is flaky on CI, skip for now
|
||||
test.fixme('should open when swiped', async ({ page, skip }) => {
|
||||
skip.browser(
|
||||
(browserName: string) => browserName !== 'chromium',
|
||||
'dragElementBy is flaky outside of Chrome browsers.'
|
||||
);
|
||||
|
||||
test('should open when swiped', async ({ page }) => {
|
||||
await page.goto(`/src/components/item-sliding/test/basic`, config);
|
||||
const item = page.locator('#item2');
|
||||
|
||||
@@ -41,7 +33,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
|
||||
await expect(item).toHaveScreenshot(screenshot(`item-sliding-gesture`));
|
||||
});
|
||||
|
||||
test.skip('should not scroll when the item-sliding is swiped', async ({ page, skip }) => {
|
||||
test('should not scroll when the item-sliding is swiped', async ({ page, skip }) => {
|
||||
skip.browser('webkit', 'mouse.wheel is not available in WebKit');
|
||||
|
||||
await page.goto(`/src/components/item-sliding/test/basic`, config);
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
@@ -2,6 +2,7 @@
|
||||
|
||||
// Item
|
||||
// --------------------------------------------------
|
||||
// TODO: FW-4955
|
||||
|
||||
:host {
|
||||
/**
|
||||
@@ -45,10 +46,10 @@
|
||||
*
|
||||
* @prop --ripple-color: Color of the item ripple effect
|
||||
*
|
||||
* @prop --highlight-height: The height of the highlight on the item
|
||||
* @prop --highlight-color-focused: The color of the highlight on the item when focused
|
||||
* @prop --highlight-color-valid: The color of the highlight on the item when valid
|
||||
* @prop --highlight-color-invalid: The color of the highlight on the item when invalid
|
||||
* @prop --highlight-height: The height of the highlight on the item. Only applies to inputs and textareas using the legacy form syntax. DEPRECATED: Highlights can be styled on `ion-input` or `ion-textarea` when using the modern form syntax.
|
||||
* @prop --highlight-color-focused: The color of the highlight on the item when focused. Only applies to inputs and textareas using the legacy form syntax. DEPRECATED: Highlights can be styled on `ion-input` or `ion-textarea` when using the modern form syntax.
|
||||
* @prop --highlight-color-valid: The color of the highlight on the item when valid. Only applies to inputs and textareas using the legacy form syntax. DEPRECATED: Highlights can be styled on `ion-input` or `ion-textarea` when using the modern form syntax.
|
||||
* @prop --highlight-color-invalid: The color of the highlight on the item when invalid. Only applies to inputs and textareas using the legacy form syntax. DEPRECATED: Highlights can be styled on `ion-input` or `ion-textarea` when using the modern form syntax.
|
||||
*/
|
||||
--border-radius: 0px;
|
||||
--border-width: 0px;
|
||||
|
||||
@@ -73,7 +73,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
const modalHeader = page.locator('#modal-header');
|
||||
await dragElementBy(modalHeader, page, 0, -500);
|
||||
await dragElementBy(modalHeader, page, 0, 30);
|
||||
|
||||
const modal = page.locator('ion-modal');
|
||||
expect(modal).not.toBe(null);
|
||||
|
||||
@@ -9,12 +9,7 @@ import { CardModalPage } from '../fixtures';
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('card modal - nav'), () => {
|
||||
let cardModalPage: CardModalPage;
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
skip.browser(
|
||||
(browserName: string) => browserName !== 'chromium',
|
||||
'dragElementBy is flaky outside of Chrome browsers.'
|
||||
);
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
cardModalPage = new CardModalPage(page);
|
||||
await cardModalPage.navigate('/src/components/modal/test/card-nav?ionic:_testing=false', config);
|
||||
});
|
||||
@@ -33,7 +28,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
const content = page.locator('.page-two-content');
|
||||
|
||||
await dragElementBy(content, page, 1000, 0, 10);
|
||||
await dragElementBy(content, page, 370, 0, 10);
|
||||
|
||||
await ionNavDidChange.next();
|
||||
});
|
||||
@@ -47,7 +42,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
await ionNavDidChange.next();
|
||||
|
||||
await cardModalPage.swipeToCloseModal('ion-modal ion-content.page-two-content');
|
||||
await cardModalPage.swipeToCloseModal('ion-modal ion-content.page-two-content', true, 270);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
const modal = page.locator('ion-modal');
|
||||
const content = (await page.$('ion-modal ion-content'))!;
|
||||
|
||||
await dragElementBy(content, page, 0, 500);
|
||||
await dragElementBy(content, page, 0, 300);
|
||||
|
||||
await content.waitForElementState('stable');
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
await content.evaluate((el: HTMLElement) => (el.scrollTop = 500));
|
||||
|
||||
await dragElementBy(content, page, 0, 500);
|
||||
await dragElementBy(content, page, 0, 300);
|
||||
|
||||
await content.waitForElementState('stable');
|
||||
|
||||
|
||||
@@ -61,12 +61,12 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
test('it should not swipe to close when swiped on the content but the content is scrolled', async ({ page }) => {
|
||||
const modal = await cardModalPage.openModalByTrigger('#card');
|
||||
|
||||
const content = (await page.$('ion-modal ion-content'))!;
|
||||
const content = page.locator('ion-modal ion-content');
|
||||
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
|
||||
|
||||
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false);
|
||||
|
||||
await content.waitForElementState('stable');
|
||||
await content.waitFor();
|
||||
await expect(modal).toBeVisible();
|
||||
});
|
||||
test('it should not swipe to close when swiped on the content but the content is scrolled even when content is replaced', async ({
|
||||
@@ -76,12 +76,12 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
await page.click('ion-button.replace');
|
||||
|
||||
const content = (await page.$('ion-modal ion-content'))!;
|
||||
const content = page.locator('ion-modal ion-content');
|
||||
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
|
||||
|
||||
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false);
|
||||
|
||||
await content.waitForElementState('stable');
|
||||
await content.waitFor();
|
||||
await expect(modal).toBeVisible();
|
||||
});
|
||||
test('content should be scrollable after gesture ends', async ({ page }) => {
|
||||
|
||||
@@ -16,7 +16,7 @@ export class CardModalPage {
|
||||
this.ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
}
|
||||
async openModalByTrigger(selector: string) {
|
||||
await this.page.click(selector);
|
||||
await this.page.locator(selector).click();
|
||||
await this.ionModalDidPresent.next();
|
||||
|
||||
return this.page.locator('ion-modal');
|
||||
|
||||
@@ -129,7 +129,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
const ionBreakpointDidChange = await page.spyOnEvent('ionBreakpointDidChange');
|
||||
const header = page.locator('.modal-sheet ion-header');
|
||||
|
||||
await dragElementBy(header, page, 0, 150);
|
||||
await dragElementBy(header, page, 0, 125);
|
||||
|
||||
await ionBreakpointDidChange.next();
|
||||
|
||||
|
||||
@@ -77,13 +77,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0);
|
||||
|
||||
const box = (await knobEl.boundingBox())!;
|
||||
const centerX = box.x + box.width / 2;
|
||||
const centerY = box.y + box.height / 2;
|
||||
|
||||
await page.mouse.move(centerX, centerY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(centerX + 30, centerY);
|
||||
await dragElementBy(knobEl, page, 30, 0, undefined, undefined, false);
|
||||
|
||||
/**
|
||||
* Do not use scrollToBottom() or other scrolling methods
|
||||
|
||||
@@ -10,15 +10,28 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
/**
|
||||
* The mouse events are flaky on CI
|
||||
*/
|
||||
test.fixme('should emit start/end events', async ({ page }, testInfo) => {
|
||||
await page.setContent(`<ion-range value="20"></ion-range>`, config);
|
||||
test.fixme('should emit start/end events', async ({ page }) => {
|
||||
/**
|
||||
* Requires padding to prevent the knob from being clipped.
|
||||
* If it's clipped, then the value might be one off.
|
||||
* For example, if the knob is clipped on the right, then the value
|
||||
* will be 99 instead of 100.
|
||||
*/
|
||||
await page.setContent(
|
||||
`
|
||||
<div style="padding: 0 20px">
|
||||
<ion-range value="20"></ion-range>
|
||||
</div>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const rangeStart = await page.spyOnEvent('ionKnobMoveStart');
|
||||
const rangeEnd = await page.spyOnEvent('ionKnobMoveEnd');
|
||||
|
||||
const rangeEl = page.locator('ion-range');
|
||||
|
||||
await dragElementBy(rangeEl, page, testInfo.project.metadata.rtl ? -300 : 300, 0);
|
||||
await dragElementBy(rangeEl, page, 300, 0);
|
||||
await page.waitForChanges();
|
||||
|
||||
/**
|
||||
@@ -65,13 +78,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0);
|
||||
|
||||
const box = (await knobEl.boundingBox())!;
|
||||
const centerX = box.x + box.width / 2;
|
||||
const centerY = box.y + box.height / 2;
|
||||
|
||||
await page.mouse.move(centerX, centerY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(centerX + 30, centerY);
|
||||
await dragElementBy(knobEl, page, 30, 0, undefined, undefined, false);
|
||||
|
||||
/**
|
||||
* Do not use scrollToBottom() or other scrolling methods
|
||||
@@ -118,13 +125,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
const rangeHandle = page.locator('ion-range .range-knob-handle');
|
||||
const ionChangeSpy = await page.spyOnEvent('ionChange');
|
||||
|
||||
const boundingBox = await rangeHandle.boundingBox();
|
||||
|
||||
await rangeHandle.hover();
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(boundingBox!.x + 100, boundingBox!.y);
|
||||
|
||||
await page.mouse.up();
|
||||
await dragElementBy(rangeHandle, page, 100, 0);
|
||||
|
||||
await ionChangeSpy.next();
|
||||
|
||||
@@ -169,11 +170,9 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
const rangeHandle = page.locator('ion-range .range-knob-handle');
|
||||
const ionInputSpy = await page.spyOnEvent('ionInput');
|
||||
|
||||
const boundingBox = await rangeHandle.boundingBox();
|
||||
|
||||
await rangeHandle.hover();
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(boundingBox!.x + 100, boundingBox!.y);
|
||||
|
||||
await dragElementBy(rangeHandle, page, 100, 0, undefined, undefined, false);
|
||||
|
||||
await ionInputSpy.next();
|
||||
|
||||
|
||||
@@ -3,13 +3,11 @@ import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
import { pullToRefresh } from '../test.utils';
|
||||
|
||||
// TODO FW-2795: Enable this test when touch events/gestures are better supported in Playwright
|
||||
|
||||
/**
|
||||
* This behavior does not vary across directions.
|
||||
*/
|
||||
configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe.skip(title('refresher: basic'), () => {
|
||||
test.describe(title('refresher: basic'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/src/components/refresher/test/basic', config);
|
||||
});
|
||||
|
||||
@@ -3,12 +3,11 @@ import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
import { pullToRefresh } from '../test.utils';
|
||||
|
||||
// TODO FW-2795: Enable this test when touch events/gestures are better supported in Playwright
|
||||
/**
|
||||
* This behavior does not vary across directions.
|
||||
*/
|
||||
configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe.skip(title('refresher: custom scroll target'), () => {
|
||||
test.describe(title('refresher: custom scroll target'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/src/components/refresher/test/scroll-target', config);
|
||||
});
|
||||
@@ -19,7 +18,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
|
||||
expect(await items.count()).toBe(30);
|
||||
|
||||
await pullToRefresh(page, '#inner-scroll');
|
||||
await pullToRefresh(page);
|
||||
|
||||
expect(await items.count()).toBe(60);
|
||||
});
|
||||
@@ -39,7 +38,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
|
||||
expect(await items.count()).toBe(30);
|
||||
|
||||
await pullToRefresh(page, '#inner-scroll');
|
||||
await pullToRefresh(page);
|
||||
|
||||
expect(await items.count()).toBe(60);
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ const pullToRefresh = async (page: E2EPage, selector = 'body') => {
|
||||
|
||||
const ev = await page.spyOnEvent('ionRefreshComplete');
|
||||
|
||||
await dragElementByYAxis(target, page, 400);
|
||||
await dragElementByYAxis(target, page, 320);
|
||||
await ev.next();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
||||
|
||||
// TODO FW-3079
|
||||
/**
|
||||
* Reorder group does not have per-mode styles
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe.skip(title('reorder group: interactive'), () => {
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
skip.browser(
|
||||
(browserName: string) => browserName !== 'chromium',
|
||||
'dragElementBy is flaky outside of Chrome browsers.'
|
||||
);
|
||||
|
||||
test.describe(title('reorder group: interactive'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`/src/components/reorder-group/test/interactive`, config);
|
||||
});
|
||||
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
||||
|
||||
// TODO FW-3079
|
||||
/**
|
||||
* Reorder group does not have per-mode styles
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe.skip(title('reorder group: nested'), () => {
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
skip.browser(
|
||||
(browserName: string) => browserName !== 'chromium',
|
||||
'dragElementBy is flaky outside of Chrome browsers.'
|
||||
);
|
||||
test.describe(title('reorder group: nested'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`/src/components/reorder-group/test/nested`, config);
|
||||
});
|
||||
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
||||
|
||||
// TODO FW-3079
|
||||
/**
|
||||
* Reorder group does not have per-mode styles
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe.skip(title('reorder group: scroll-target'), () => {
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
skip.browser(
|
||||
(browserName: string) => browserName !== 'chromium',
|
||||
'dragElementBy is flaky outside of Chrome browsers.'
|
||||
);
|
||||
test.describe(title('reorder group: scroll-target'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`/src/components/reorder-group/test/scroll-target`, config);
|
||||
});
|
||||
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {
|
||||
|
||||
@@ -12,3 +12,23 @@ configs().forEach(({ title, screenshot, config }) => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('title: special characters'), () => {
|
||||
test('should not cut off characters', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">ÔÔÔ</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const title = page.locator('ion-title');
|
||||
await expect(title).toHaveScreenshot(screenshot('title-characters'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
@@ -49,8 +49,6 @@
|
||||
|
||||
min-width: 100%;
|
||||
|
||||
padding-bottom: 6px;
|
||||
|
||||
font-size: 34px;
|
||||
font-weight: 700;
|
||||
|
||||
|
||||
@@ -84,6 +84,16 @@
|
||||
// Toolbar: Large Title
|
||||
// --------------------------------------------------
|
||||
|
||||
/**
|
||||
* Ensure there is enough spacing between the large
|
||||
* title and any potential content in subsequent
|
||||
* toolbars. We do not add padding to the title
|
||||
* itself because it is absolutely positioned.
|
||||
*/
|
||||
:host(.toolbar-title-large) {
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
:host(.toolbar-title-large) .toolbar-container {
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
@@ -94,4 +104,4 @@
|
||||
order: map-get($toolbar-order-ios, title-large);
|
||||
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { doc } from '@utils/browser';
|
||||
|
||||
import type { Config } from '../../interface';
|
||||
import { now, pointerCoord } from '../helpers';
|
||||
|
||||
export const startTapClick = (config: Config) => {
|
||||
if (doc === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let lastTouch = -MOUSE_WAIT * 10;
|
||||
let lastActivated = 0;
|
||||
|
||||
@@ -143,7 +149,6 @@ export const startTapClick = (config: Config) => {
|
||||
}
|
||||
};
|
||||
|
||||
const doc = document;
|
||||
doc.addEventListener('ionGestureCaptured', cancelActive);
|
||||
|
||||
doc.addEventListener('touchstart', onTouchStart, true);
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
/**
|
||||
* The drag gesture will not operate as expected when the element is dragged outside of the viewport because the Mouse class does not fire events outside of the viewport.
|
||||
*
|
||||
* For example, if the mouse is moved outside of the viewport, then the `mouseup` event will not fire.
|
||||
*
|
||||
* See https://playwright.dev/docs/api/class-mouse#mouse-move for more information.
|
||||
*/
|
||||
|
||||
import type { ElementHandle, Locator } from '@playwright/test';
|
||||
|
||||
import type { E2EPage } from './';
|
||||
@@ -8,7 +16,8 @@ export const dragElementBy = async (
|
||||
dragByX = 0,
|
||||
dragByY = 0,
|
||||
startXCoord?: number,
|
||||
startYCoord?: number
|
||||
startYCoord?: number,
|
||||
releaseDrag = true
|
||||
) => {
|
||||
const boundingBox = await el.boundingBox();
|
||||
|
||||
@@ -21,14 +30,17 @@ export const dragElementBy = async (
|
||||
const startX = startXCoord === undefined ? boundingBox.x + boundingBox.width / 2 : startXCoord;
|
||||
const startY = startYCoord === undefined ? boundingBox.y + boundingBox.height / 2 : startYCoord;
|
||||
|
||||
const endX = startX + dragByX;
|
||||
const endY = startY + dragByY;
|
||||
|
||||
// Navigate to the start position.
|
||||
await page.mouse.move(startX, startY);
|
||||
|
||||
await page.mouse.down();
|
||||
|
||||
await page.mouse.move(endX, endY, { steps: 10 });
|
||||
await page.mouse.up();
|
||||
// Drag the element.
|
||||
await moveElement(page, startX, startY, dragByX, dragByY);
|
||||
|
||||
if (releaseDrag) {
|
||||
await page.mouse.up();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -55,12 +67,83 @@ export const dragElementByYAxis = async (
|
||||
const startX = boundingBox.x + boundingBox.width / 2;
|
||||
const startY = startYCoord === undefined ? boundingBox.y + boundingBox.height / 2 : startYCoord;
|
||||
|
||||
// Navigate to the start position.
|
||||
await page.mouse.move(startX, startY);
|
||||
await page.mouse.down();
|
||||
|
||||
for (let i = 0; i < dragByY; i += 20) {
|
||||
await page.mouse.move(startX, startY + i);
|
||||
}
|
||||
// Drag the element.
|
||||
await moveElement(page, startX, startY, 0, dragByY);
|
||||
|
||||
await page.mouse.up();
|
||||
};
|
||||
|
||||
const validateDragByX = (startX: number, dragByX: number, viewportWidth: number) => {
|
||||
const endX = startX + dragByX;
|
||||
// The element is being dragged past the right of the viewport.
|
||||
if (endX > viewportWidth) {
|
||||
const recommendedDragByX = viewportWidth - startX - 5;
|
||||
throw new Error(
|
||||
`The element is being dragged past the right of the viewport. Update the dragByX value to prevent going out of bounds. A recommended value is ${recommendedDragByX}.`
|
||||
);
|
||||
}
|
||||
|
||||
// The element is being dragged past the left of the viewport.
|
||||
if (endX < 0) {
|
||||
const recommendedDragByX = startX - 5;
|
||||
throw new Error(
|
||||
`The element is being dragged past the left of the viewport. Update the dragByX value to prevent going out of bounds. A recommended value is ${recommendedDragByX}.`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const validateDragByY = (startY: number, dragByY: number, viewportHeight: number) => {
|
||||
const endY = startY + dragByY;
|
||||
// The element is being dragged past the bottom of the viewport.
|
||||
if (endY > viewportHeight) {
|
||||
const recommendedDragByY = viewportHeight - startY - 5;
|
||||
throw new Error(
|
||||
`The element is being dragged past the bottom of the viewport. Update the dragByY value to prevent going out of bounds. A recommended value is ${recommendedDragByY}.`
|
||||
);
|
||||
}
|
||||
|
||||
// The element is being dragged past the top of the viewport.
|
||||
if (endY < 0) {
|
||||
const recommendedDragByY = startY - 5;
|
||||
throw new Error(
|
||||
`The element is being dragged past the top of the viewport. Update the dragByY value to prevent going out of bounds. A recommended value is ${recommendedDragByY}.`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const moveElement = async (page: E2EPage, startX: number, startY: number, dragByX = 0, dragByY = 0) => {
|
||||
const steps = 10;
|
||||
const browser = page.context().browser()!.browserType().name();
|
||||
|
||||
const viewport = page.viewportSize();
|
||||
if (viewport === null) {
|
||||
throw new Error(
|
||||
'Cannot get viewport size. See https://playwright.dev/docs/api/class-page#page-viewport-size for more information'
|
||||
);
|
||||
}
|
||||
|
||||
validateDragByX(startX, dragByX, viewport.width);
|
||||
validateDragByY(startY, dragByY, viewport.height);
|
||||
|
||||
const endX = startX + dragByX;
|
||||
const endY = startY + dragByY;
|
||||
|
||||
// Drag the element.
|
||||
for (let i = 1; i <= steps; i++) {
|
||||
const middleX = startX + (endX - startX) * (i / steps);
|
||||
const middleY = startY + (endY - startY) * (i / steps);
|
||||
|
||||
await page.mouse.move(middleX, middleY);
|
||||
|
||||
// Safari needs to wait for a repaint to occur before moving the mouse again.
|
||||
if (browser === 'webkit' && i % 2 === 0) {
|
||||
// Repainting every 2 steps is enough to keep the drag gesture smooth.
|
||||
// Anything past 4 steps will cause the drag gesture to be flaky.
|
||||
await page.evaluate(() => new Promise(requestAnimationFrame));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||