Compare commits

..

1 Commits

Author SHA1 Message Date
Sean Perkins
5f56f069e5 refactor(react-router): migrate stack manager to hooks 2023-08-01 16:03:17 -04:00
128 changed files with 20260 additions and 7119 deletions

View File

@@ -14,6 +14,6 @@ runs:
with:
name: ${{ inputs.name }}
path: ${{ inputs.path }}
- name: Extract Archive
- name: Exract Archive
run: unzip -q -o ${{ inputs.path }}/${{ inputs.filename }}
shell: bash

View File

@@ -1,8 +1,5 @@
name: 'Test Core Spec'
description: 'Test Core Spec'
inputs:
stencil-version:
description: 'The NPM tag of @stencil/core to install.'
runs:
using: 'composite'
steps:
@@ -10,14 +7,9 @@ runs:
with:
node-version: 16.x
- name: Install Dependencies
run: npm ci
run: npm install
working-directory: ./core
shell: bash
- name: Install Stencil ${{ inputs.stencil-version }}
run: npm install @stencil/core@${{ inputs.stencil-version }}
shell: bash
working-directory: ./core
if: ${{ inputs.stencil-version != '' }}
- uses: ./.github/workflows/actions/download-archive
with:
name: ionic-core

View File

@@ -46,8 +46,6 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/workflows/actions/test-core-spec
with:
stencil-version: nightly
test-core-screenshot:
strategy:

View File

@@ -3,51 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.4](https://github.com/ionic-team/ionic-framework/compare/v7.2.3...v7.2.4) (2023-08-16)
### Bug Fixes
* **tap-click:** do not error in document-less environment ([#27972](https://github.com/ionic-team/ionic-framework/issues/27972)) ([28bd4ba](https://github.com/ionic-team/ionic-framework/commit/28bd4ba720bb77d5f5c48cd7a45e0015daddc9dd))
* **title:** large title aligns with ios spec ([#27969](https://github.com/ionic-team/ionic-framework/issues/27969)) ([8fa12fc](https://github.com/ionic-team/ionic-framework/commit/8fa12fc88857df27a1ca11249f0085e100fe1474)), closes [#27966](https://github.com/ionic-team/ionic-framework/issues/27966)
## [7.2.3](https://github.com/ionic-team/ionic-framework/compare/v7.2.2...v7.2.3) (2023-08-09)
### Bug Fixes
* **button:** hidden button is added when form is set async ([#27955](https://github.com/ionic-team/ionic-framework/issues/27955)) ([e9fa300](https://github.com/ionic-team/ionic-framework/commit/e9fa30002bd5dec4f2f56a15c84eec1b3e794942)), closes [#27952](https://github.com/ionic-team/ionic-framework/issues/27952)
* **datetime:** changing months work if partially visible ([#27917](https://github.com/ionic-team/ionic-framework/issues/27917)) ([eb19c28](https://github.com/ionic-team/ionic-framework/commit/eb19c289d6581639f6df7aff002bebdf2b27d31c)), closes [#27913](https://github.com/ionic-team/ionic-framework/issues/27913)
* **item-sliding:** account for options added before watcher ([#27915](https://github.com/ionic-team/ionic-framework/issues/27915)) ([a0b3ef0](https://github.com/ionic-team/ionic-framework/commit/a0b3ef02af718e232246515bb873ad8c090fa55d)), closes [#27910](https://github.com/ionic-team/ionic-framework/issues/27910)
* **many:** overlays present if isOpen is true on load ([#27933](https://github.com/ionic-team/ionic-framework/issues/27933)) ([a0e6ac6](https://github.com/ionic-team/ionic-framework/commit/a0e6ac6013552c5e3acdde33575d4aaf4d4c0bda)), closes [#27928](https://github.com/ionic-team/ionic-framework/issues/27928)
* **modal:** setCurrentBreakpoint respects animated prop ([#27924](https://github.com/ionic-team/ionic-framework/issues/27924)) ([da55ab9](https://github.com/ionic-team/ionic-framework/commit/da55ab949ef1894738da5a6241176089b7a2b6e3)), closes [#27923](https://github.com/ionic-team/ionic-framework/issues/27923)
* **nav:** improve reliability of swipe back gesture when quickly swiping back ([#27904](https://github.com/ionic-team/ionic-framework/issues/27904)) ([9500769](https://github.com/ionic-team/ionic-framework/commit/9500769f114d180613f0340b1a328b5e631b7188)), closes [#27893](https://github.com/ionic-team/ionic-framework/issues/27893)
* **tab-button:** update event type interface on React ([#27950](https://github.com/ionic-team/ionic-framework/issues/27950)) ([1cf1eca](https://github.com/ionic-team/ionic-framework/commit/1cf1eca00239f3e98854466116e42f9a2e7b4590)), closes [#27949](https://github.com/ionic-team/ionic-framework/issues/27949)
## [7.2.2](https://github.com/ionic-team/ionic-framework/compare/v7.2.1...v7.2.2) (2023-08-02)
### Bug Fixes
* **datetime-button:** render correct text when passing partial date values ([#27816](https://github.com/ionic-team/ionic-framework/issues/27816)) ([bd1910b](https://github.com/ionic-team/ionic-framework/commit/bd1910ba69348877ad5f99d9db2b59d06693b91e)), closes [#27797](https://github.com/ionic-team/ionic-framework/issues/27797)
* **input, textarea:** input does not block floating label ([#27870](https://github.com/ionic-team/ionic-framework/issues/27870)) ([f14c440](https://github.com/ionic-team/ionic-framework/commit/f14c440d6321ef9f168b272338e5cd21cab384ef)), closes [#27812](https://github.com/ionic-team/ionic-framework/issues/27812)
* **item-options:** use correct safe area padding ([#27853](https://github.com/ionic-team/ionic-framework/issues/27853)) ([0b8f1bc](https://github.com/ionic-team/ionic-framework/commit/0b8f1bc7dd4170a2a8c9ed3aede173dd489b25ea))
* **radio:** radios can be focused and are announced with group ([#27817](https://github.com/ionic-team/ionic-framework/issues/27817)) ([ba2f49b](https://github.com/ionic-team/ionic-framework/commit/ba2f49b8a460520d20ac198db800ea2d9e5b015f)), closes [#27438](https://github.com/ionic-team/ionic-framework/issues/27438)
* **react, vue:** custom animations are used when going back ([#27895](https://github.com/ionic-team/ionic-framework/issues/27895)) ([824033f](https://github.com/ionic-team/ionic-framework/commit/824033f1d4b4a3e5d4c6a978a39e5bb1f33b5bb4)), closes [#27873](https://github.com/ionic-team/ionic-framework/issues/27873)
* **select:** popover uses modern form syntax ([#27818](https://github.com/ionic-team/ionic-framework/issues/27818)) ([0c117cf](https://github.com/ionic-team/ionic-framework/commit/0c117cfe7f383b7c7837d27de5a6eee12ddd6c2f)), closes [#27071](https://github.com/ionic-team/ionic-framework/issues/27071) [#27786](https://github.com/ionic-team/ionic-framework/issues/27786)
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)

View File

@@ -3,49 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.4](https://github.com/ionic-team/ionic-framework/compare/v7.2.3...v7.2.4) (2023-08-16)
### Bug Fixes
* **tap-click:** do not error in document-less environment ([#27972](https://github.com/ionic-team/ionic-framework/issues/27972)) ([28bd4ba](https://github.com/ionic-team/ionic-framework/commit/28bd4ba720bb77d5f5c48cd7a45e0015daddc9dd))
* **title:** large title aligns with ios spec ([#27969](https://github.com/ionic-team/ionic-framework/issues/27969)) ([8fa12fc](https://github.com/ionic-team/ionic-framework/commit/8fa12fc88857df27a1ca11249f0085e100fe1474)), closes [#27966](https://github.com/ionic-team/ionic-framework/issues/27966)
## [7.2.3](https://github.com/ionic-team/ionic-framework/compare/v7.2.2...v7.2.3) (2023-08-09)
### Bug Fixes
* **button:** hidden button is added when form is set async ([#27955](https://github.com/ionic-team/ionic-framework/issues/27955)) ([e9fa300](https://github.com/ionic-team/ionic-framework/commit/e9fa30002bd5dec4f2f56a15c84eec1b3e794942)), closes [#27952](https://github.com/ionic-team/ionic-framework/issues/27952)
* **datetime:** changing months work if partially visible ([#27917](https://github.com/ionic-team/ionic-framework/issues/27917)) ([eb19c28](https://github.com/ionic-team/ionic-framework/commit/eb19c289d6581639f6df7aff002bebdf2b27d31c)), closes [#27913](https://github.com/ionic-team/ionic-framework/issues/27913)
* **item-sliding:** account for options added before watcher ([#27915](https://github.com/ionic-team/ionic-framework/issues/27915)) ([a0b3ef0](https://github.com/ionic-team/ionic-framework/commit/a0b3ef02af718e232246515bb873ad8c090fa55d)), closes [#27910](https://github.com/ionic-team/ionic-framework/issues/27910)
* **many:** overlays present if isOpen is true on load ([#27933](https://github.com/ionic-team/ionic-framework/issues/27933)) ([a0e6ac6](https://github.com/ionic-team/ionic-framework/commit/a0e6ac6013552c5e3acdde33575d4aaf4d4c0bda)), closes [#27928](https://github.com/ionic-team/ionic-framework/issues/27928)
* **modal:** setCurrentBreakpoint respects animated prop ([#27924](https://github.com/ionic-team/ionic-framework/issues/27924)) ([da55ab9](https://github.com/ionic-team/ionic-framework/commit/da55ab949ef1894738da5a6241176089b7a2b6e3)), closes [#27923](https://github.com/ionic-team/ionic-framework/issues/27923)
* **nav:** improve reliability of swipe back gesture when quickly swiping back ([#27904](https://github.com/ionic-team/ionic-framework/issues/27904)) ([9500769](https://github.com/ionic-team/ionic-framework/commit/9500769f114d180613f0340b1a328b5e631b7188)), closes [#27893](https://github.com/ionic-team/ionic-framework/issues/27893)
## [7.2.2](https://github.com/ionic-team/ionic-framework/compare/v7.2.1...v7.2.2) (2023-08-02)
### Bug Fixes
* **datetime-button:** render correct text when passing partial date values ([#27816](https://github.com/ionic-team/ionic-framework/issues/27816)) ([bd1910b](https://github.com/ionic-team/ionic-framework/commit/bd1910ba69348877ad5f99d9db2b59d06693b91e)), closes [#27797](https://github.com/ionic-team/ionic-framework/issues/27797)
* **input, textarea:** input does not block floating label ([#27870](https://github.com/ionic-team/ionic-framework/issues/27870)) ([f14c440](https://github.com/ionic-team/ionic-framework/commit/f14c440d6321ef9f168b272338e5cd21cab384ef)), closes [#27812](https://github.com/ionic-team/ionic-framework/issues/27812)
* **item-options:** use correct safe area padding ([#27853](https://github.com/ionic-team/ionic-framework/issues/27853)) ([0b8f1bc](https://github.com/ionic-team/ionic-framework/commit/0b8f1bc7dd4170a2a8c9ed3aede173dd489b25ea))
* **radio:** radios can be focused and are announced with group ([#27817](https://github.com/ionic-team/ionic-framework/issues/27817)) ([ba2f49b](https://github.com/ionic-team/ionic-framework/commit/ba2f49b8a460520d20ac198db800ea2d9e5b015f)), closes [#27438](https://github.com/ionic-team/ionic-framework/issues/27438)
* **select:** popover uses modern form syntax ([#27818](https://github.com/ionic-team/ionic-framework/issues/27818)) ([0c117cf](https://github.com/ionic-team/ionic-framework/commit/0c117cfe7f383b7c7837d27de5a6eee12ddd6c2f)), closes [#27071](https://github.com/ionic-team/ionic-framework/issues/27071) [#27786](https://github.com/ionic-team/ionic-framework/issues/27786)
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)

48
core/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/core",
"version": "7.2.4",
"version": "7.2.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/core",
"version": "7.2.4",
"version": "7.2.1",
"license": "MIT",
"dependencies": {
"@stencil/core": "^3.4.0",
@@ -15,14 +15,14 @@
},
"devDependencies": {
"@axe-core/playwright": "^4.7.3",
"@capacitor/core": "^5.2.3",
"@capacitor/core": "^5.2.2",
"@capacitor/haptics": "^5.0.6",
"@capacitor/keyboard": "^5.0.6",
"@capacitor/status-bar": "^5.0.6",
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@jest/core": "^27.5.1",
"@playwright/test": "^1.37.0",
"@playwright/test": "^1.36.2",
"@rollup/plugin-node-resolve": "^8.4.0",
"@rollup/plugin-virtual": "^2.0.3",
"@stencil/angular-output-target": "^0.7.1",
@@ -607,9 +607,9 @@
"dev": true
},
"node_modules/@capacitor/core": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.2.3.tgz",
"integrity": "sha512-Q1zbgt3Mvldy7six2/GX54kTL0ozgnR37jeDUAXL/fOJBF4Iorr/8A0OjGEAnwEjpR1la7uFZUunESMFyMLhEQ==",
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.2.2.tgz",
"integrity": "sha512-3jKECZC5+YD2rljMZm1e/K3AYyoxUmLDZCyofTPbRYPBSI0wJh5ZCkX+XIGzNM0o/Wokl3Voa1JB8xsLC0MPxA==",
"dev": true,
"dependencies": {
"tslib": "^2.1.0"
@@ -1541,13 +1541,13 @@
}
},
"node_modules/@playwright/test": {
"version": "1.37.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.0.tgz",
"integrity": "sha512-181WBLk4SRUyH1Q96VZl7BP6HcK0b7lbdeKisn3N/vnjitk+9HbdlFz/L5fey05vxaAhldIDnzo8KUoy8S3mmQ==",
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.2.tgz",
"integrity": "sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==",
"dev": true,
"dependencies": {
"@types/node": "*",
"playwright-core": "1.37.0"
"playwright-core": "1.36.2"
},
"bin": {
"playwright": "cli.js"
@@ -8194,9 +8194,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.37.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.0.tgz",
"integrity": "sha512-1c46jhTH/myQw6sesrcuHVtLoSNfJv8Pfy9t3rs6subY7kARv0HRw5PpyfPYPpPtQvBOmgbE6K+qgYUpj81LAA==",
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.2.tgz",
"integrity": "sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
@@ -10788,9 +10788,9 @@
"dev": true
},
"@capacitor/core": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.2.3.tgz",
"integrity": "sha512-Q1zbgt3Mvldy7six2/GX54kTL0ozgnR37jeDUAXL/fOJBF4Iorr/8A0OjGEAnwEjpR1la7uFZUunESMFyMLhEQ==",
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.2.2.tgz",
"integrity": "sha512-3jKECZC5+YD2rljMZm1e/K3AYyoxUmLDZCyofTPbRYPBSI0wJh5ZCkX+XIGzNM0o/Wokl3Voa1JB8xsLC0MPxA==",
"dev": true,
"requires": {
"tslib": "^2.1.0"
@@ -11455,14 +11455,14 @@
}
},
"@playwright/test": {
"version": "1.37.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.0.tgz",
"integrity": "sha512-181WBLk4SRUyH1Q96VZl7BP6HcK0b7lbdeKisn3N/vnjitk+9HbdlFz/L5fey05vxaAhldIDnzo8KUoy8S3mmQ==",
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.2.tgz",
"integrity": "sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==",
"dev": true,
"requires": {
"@types/node": "*",
"fsevents": "2.3.2",
"playwright-core": "1.37.0"
"playwright-core": "1.36.2"
}
},
"@rollup/plugin-node-resolve": {
@@ -16332,9 +16332,9 @@
}
},
"playwright-core": {
"version": "1.37.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.0.tgz",
"integrity": "sha512-1c46jhTH/myQw6sesrcuHVtLoSNfJv8Pfy9t3rs6subY7kARv0HRw5PpyfPYPpPtQvBOmgbE6K+qgYUpj81LAA==",
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.2.tgz",
"integrity": "sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==",
"dev": true
},
"postcss": {

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "7.2.4",
"version": "7.2.1",
"description": "Base components for Ionic",
"keywords": [
"ionic",
@@ -37,14 +37,14 @@
},
"devDependencies": {
"@axe-core/playwright": "^4.7.3",
"@capacitor/core": "^5.2.3",
"@capacitor/core": "^5.2.2",
"@capacitor/haptics": "^5.0.6",
"@capacitor/keyboard": "^5.0.6",
"@capacitor/status-bar": "^5.0.6",
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@jest/core": "^27.5.1",
"@playwright/test": "^1.37.0",
"@playwright/test": "^1.36.2",
"@rollup/plugin-node-resolve": "^8.4.0",
"@rollup/plugin-virtual": "^2.0.3",
"@stencil/angular-output-target": "^0.7.1",

View File

@@ -62,16 +62,14 @@ const config: PlaywrightTestConfig = {
},
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
maxFailures: 0,
retries: 2,
reportSlowTests: null,
/* Fail fast on CI */
maxFailures: process.env.CI ? 1 : 0,
/* Flaky test should be either addressed or disabled until we can address them */
retries: 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [
['html'],
['github']
],
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */

View File

@@ -1277,6 +1277,9 @@ 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.
@@ -5336,6 +5339,9 @@ 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.

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -2,7 +2,6 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Watch, Component, Element, Event, Host, Method, Prop, h, readTask } from '@stencil/core';
import type { Gesture } from '@utils/gesture';
import { createButtonActiveGesture } from '@utils/gesture/button-active';
import { raf } from '@utils/helpers';
import {
BACKDROP,
createDelegateController,
@@ -319,32 +318,25 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
componentDidLoad() {
/**
* Only create gesture if:
* 1. A gesture does not already exist
* 2. App is running in iOS mode
* 3. A wrapper ref exists
* 4. A group ref exists
* Do not create gesture if:
* 1. A gesture already exists
* 2. App is running in MD mode
* 3. A wrapper ref does not exist
*/
const { groupEl, wrapperEl } = this;
if (!this.gesture && getIonMode(this) === 'ios' && wrapperEl && groupEl) {
readTask(() => {
const isScrollable = groupEl.scrollHeight > groupEl.clientHeight;
if (!isScrollable) {
this.gesture = createButtonActiveGesture(wrapperEl, (refEl: HTMLElement) =>
refEl.classList.contains('action-sheet-button')
);
this.gesture.enable(true);
}
});
if (this.gesture || getIonMode(this) === 'md' || !wrapperEl || !groupEl) {
return;
}
/**
* If action sheet was rendered with isOpen="true"
* then we should open action sheet immediately.
*/
if (this.isOpen === true) {
raf(() => this.present());
}
readTask(() => {
const isScrollable = groupEl.scrollHeight > groupEl.clientHeight;
if (!isScrollable) {
this.gesture = createButtonActiveGesture(wrapperEl, (refEl: HTMLElement) =>
refEl.classList.contains('action-sheet-button')
);
this.gesture.enable(true);
}
});
}
render() {

View File

@@ -30,10 +30,5 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ config, title }) =>
await expect(actionSheet).toBeHidden();
});
test('should open if isOpen is true on load', async ({ page }) => {
await page.setContent('<ion-action-sheet is-open="true"></ion-action-sheet>', config);
await expect(page.locator('ion-action-sheet')).toBeVisible();
});
});
});

View File

@@ -3,7 +3,6 @@ import { Component, Element, Event, Host, Listen, Method, Prop, Watch, forceUpda
import { ENABLE_HTML_CONTENT_DEFAULT } from '@utils/config';
import type { Gesture } from '@utils/gesture';
import { createButtonActiveGesture } from '@utils/gesture/button-active';
import { raf } from '@utils/helpers';
import {
createDelegateController,
createTriggerController,
@@ -347,25 +346,19 @@ export class Alert implements ComponentInterface, OverlayInterface {
componentDidLoad() {
/**
* Only create gesture if:
* 1. A gesture does not already exist
* 2. App is running in iOS mode
* 3. A wrapper ref exists
* Do not create gesture if:
* 1. A gesture already exists
* 2. App is running in MD mode
* 3. A wrapper ref does not exist
*/
if (!this.gesture && getIonMode(this) === 'ios' && this.wrapperEl) {
this.gesture = createButtonActiveGesture(this.wrapperEl, (refEl: HTMLElement) =>
refEl.classList.contains('alert-button')
);
this.gesture.enable(true);
if (this.gesture || getIonMode(this) === 'md' || !this.wrapperEl) {
return;
}
/**
* If alert was rendered with isOpen="true"
* then we should open alert immediately.
*/
if (this.isOpen === true) {
raf(() => this.present());
}
this.gesture = createButtonActiveGesture(this.wrapperEl, (refEl: HTMLElement) =>
refEl.classList.contains('alert-button')
);
this.gesture.enable(true);
}
/**

View File

@@ -29,10 +29,5 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ config, title }) =>
await ionAlertDidDismiss.next();
await expect(alert).toBeHidden();
});
test('should open if isOpen is true on load', async ({ page }) => {
await page.setContent('<ion-alert is-open="true"></ion-alert>', config);
await expect(page.locator('ion-alert')).toBeVisible();
});
});
});

View File

@@ -153,27 +153,19 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
*/
@Event() ionBlur!: EventEmitter<void>;
private renderHiddenButton() {
const formEl = (this.formEl = this.findForm());
if (formEl) {
const { formButtonEl } = this;
/**
* If the form already has a rendered form button
* then do not append a new one again.
*/
if (formButtonEl !== null && formEl.contains(formButtonEl)) {
return;
connectedCallback(): void {
// Allow form to be submitted through `ion-button`
if (this.type !== 'button' && hasShadowDom(this.el)) {
this.formEl = this.findForm();
if (this.formEl) {
// Create a hidden native button inside of the form
this.formButtonEl = document.createElement('button');
this.formButtonEl.type = this.type;
this.formButtonEl.style.display = 'none';
// Only submit if the button is not disabled.
this.formButtonEl.disabled = this.disabled;
this.formEl.appendChild(this.formButtonEl);
}
// Create a hidden native button inside of the form
const newFormButtonEl = (this.formButtonEl = document.createElement('button'));
newFormButtonEl.type = this.type;
newFormButtonEl.style.display = 'none';
// Only submit if the button is not disabled.
newFormButtonEl.disabled = this.disabled;
formEl.appendChild(newFormButtonEl);
}
}
@@ -322,11 +314,6 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
if (fill == null) {
fill = this.inToolbar || this.inListHeader ? 'clear' : 'solid';
}
{
type !== 'button' && this.renderHiddenButton();
}
return (
<Host
onClick={this.handleClick}

View File

@@ -130,29 +130,6 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
expect(submitEvent).not.toHaveReceivedEvent();
});
test('should submit the form by id when form is set async', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/27952',
});
await page.setContent(
`
<form id="myForm"></form>
<ion-button type="submit">Submit</ion-button>
`,
config
);
const submitEvent = await page.spyOnEvent('submit');
const button = page.locator('ion-button');
await button.evaluate((el: HTMLIonButtonElement) => (el.form = 'myForm'));
await page.click('ion-button');
expect(submitEvent).toHaveReceivedEvent();
});
});
test.describe(title('should throw a warning if the form cannot be found'), () => {

View File

@@ -1,31 +0,0 @@
import { newSpecPage } from '@stencil/core/testing';
import { Button } from '../../button';
describe('Button: Hidden Form Button', () => {
it('should not add multiple buttons to the form', async () => {
const page = await newSpecPage({
components: [Button],
html: `
<form id="my-form"></form>
<ion-button form="my-form" type="submit">Submit</ion-button>
`,
});
const getButtons = () => {
return page.body.querySelectorAll('form button');
};
const form = page.body.querySelectorAll('form');
const button = page.body.querySelector('ion-button');
await page.waitForChanges();
expect(getButtons().length).toEqual(1);
// Re-render the component
button.color = 'danger';
await page.waitForChanges();
expect(getButtons().length).toEqual(1);
});
});

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -122,42 +122,6 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await expect(dateTarget).toContainText('May 10, 2023');
});
test('should set only month and year when only passing month and year', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/27797',
});
await page.setContent(
`
<ion-datetime-button locale="en-US" datetime="datetime"></ion-datetime-button>
<ion-datetime id="datetime" value="2022-01" presentation="month-year"></ion-datetime>
`,
config
);
await page.waitForSelector('.datetime-ready');
await expect(page.locator('#date-button')).toContainText('January 2022');
await expect(page.locator('#time-button')).toBeHidden();
});
test('should set only year when passing only year', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/27797',
});
await page.setContent(
`
<ion-datetime-button locale="en-US" datetime="datetime"></ion-datetime-button>
<ion-datetime id="datetime" value="2022" presentation="year"></ion-datetime>
`,
config
);
await page.waitForSelector('.datetime-ready');
await expect(page.locator('#date-button')).toContainText('2022');
await expect(page.locator('#time-button')).toBeHidden();
});
});
test.describe(title('datetime-button: locale'), () => {

View File

@@ -138,6 +138,7 @@ export class Datetime implements ComponentInterface {
@Element() el!: HTMLIonDatetimeElement;
@State() isPresented = false;
@State() isTimePopoverOpen = false;
/**
@@ -882,18 +883,21 @@ export class Datetime implements ComponentInterface {
const getChangedMonth = (parts: DatetimeParts): DatetimeParts | undefined => {
const box = calendarBodyRef.getBoundingClientRect();
const root = this.el!.shadowRoot!;
/**
* If the current scroll position is all the way to the left
* then we have scrolled to the previous month.
* Otherwise, assume that we have scrolled to the next
* month. We have a tolerance of 2px to account for
* sub pixel rendering.
*
* Check below the next line ensures that we did not
* swipe and abort (i.e. we swiped but we are still on the current month).
* Get the element that is in the center of the calendar body.
* This will be an element inside of the active month.
*/
const month = calendarBodyRef.scrollLeft <= 2 ? startMonth : endMonth;
const elementAtCenter = root.elementFromPoint(box.x + box.width / 2, box.y + box.height / 2);
/**
* If there is no element then the
* component may be re-rendering on a slow device.
*/
if (!elementAtCenter) return;
const month = elementAtCenter.closest('.calendar-month');
if (!month) return;
/**
* The edge of the month must be lined up with
@@ -2360,7 +2364,19 @@ export class Datetime implements ComponentInterface {
}
render() {
const { name, value, disabled, el, color, readonly, showMonthAndYear, preferWheel, presentation, size } = this;
const {
name,
value,
disabled,
el,
color,
isPresented,
readonly,
showMonthAndYear,
preferWheel,
presentation,
size,
} = this;
const mode = getIonMode(this);
const isMonthAndYearPresentation =
presentation === 'year' || presentation === 'month' || presentation === 'month-year';
@@ -2380,6 +2396,7 @@ export class Datetime implements ComponentInterface {
class={{
...createColorClasses(color, {
[mode]: true,
['datetime-presented']: isPresented,
['datetime-readonly']: readonly,
['datetime-disabled']: disabled,
'show-month-and-year': shouldShowMonthAndYear,

View File

@@ -301,20 +301,21 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
<ion-datetime
min="2022-01-15"
value="2022-02-01"
presentation="month-year"
presentation="date"
></ion-datetime>
`,
config
);
const datetime = page.locator('ion-datetime');
const monthYearToggle = page.locator('ion-datetime .calendar-month-year');
const monthColumnItems = page.locator('ion-datetime .month-column .picker-item:not(.picker-item-empty)');
const ionChange = await page.spyOnEvent('ionChange');
await page.waitForSelector('.datetime-ready');
await monthYearToggle.click();
await page.waitForChanges();
await monthColumnItems.nth(0).click(); // switch to January
await ionChange.next();
await page.waitForChanges();
await expect(datetime).toHaveJSProperty('value', '2022-01-15T00:00:00');
});

View File

@@ -34,14 +34,13 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
`,
config
);
await page.waitForSelector('.datetime-ready');
const datetime = page.locator('ion-datetime');
const activeDayButton = page.locator('.calendar-day-active');
const monthYearButton = page.locator('.calendar-month-year');
const monthColumn = page.locator('.month-column');
const ionChange = await page.spyOnEvent('ionChange');
const yearColumn = page.locator('.year-column');
await datetime.evaluate((el: HTMLIonDatetimeElement) => (el.value = '2021-10-05'));
@@ -50,9 +49,11 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await page.waitForChanges();
// Select October 2021
// The year will automatically switch to 2021 when selecting 10
await monthColumn.locator('.picker-item[data-value="10"]').click();
await ionChange.next();
await page.waitForChanges();
await yearColumn.locator('.picker-item[data-value="2021"]').click();
await page.waitForChanges();
// Close month/year picker
await monthYearButton.click();

View File

@@ -113,7 +113,7 @@ export const generateDayAriaLabel = (locale: string, today: boolean, refParts: D
/**
* MM/DD/YYYY will return midnight in the user's timezone.
*/
const date = getNormalizedDate(refParts);
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
const labelString = new Intl.DateTimeFormat(locale, {
weekday: 'long',
@@ -134,7 +134,7 @@ export const generateDayAriaLabel = (locale: string, today: boolean, refParts: D
* Used for the header in MD mode.
*/
export const getMonthAndDay = (locale: string, refParts: DatetimeParts) => {
const date = getNormalizedDate(refParts);
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
return new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric', timeZone: 'UTC' }).format(
date
);
@@ -147,7 +147,7 @@ export const getMonthAndDay = (locale: string, refParts: DatetimeParts) => {
* Example: May 2021
*/
export const getMonthAndYear = (locale: string, refParts: DatetimeParts) => {
const date = getNormalizedDate(refParts);
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric', timeZone: 'UTC' }).format(date);
};
@@ -183,25 +183,11 @@ export const getYear = (locale: string, refParts: DatetimeParts) => {
return getLocalizedDateTime(locale, refParts, { year: 'numeric' });
};
/**
* Given reference parts, return a JS Date object
* with a normalized time.
*/
export const getNormalizedDate = (refParts: DatetimeParts) => {
const getNormalizedDate = (refParts: DatetimeParts) => {
const timeString =
refParts.hour !== undefined && refParts.minute !== undefined ? ` ${refParts.hour}:${refParts.minute}` : '';
/**
* We use / notation here for the date
* so we do not need to do extra work and pad values with zeroes.
* Values such as YYYY-MM are still valid, so
* we add fallback values so we still get
* a valid date otherwise we will pass in a string
* like "//2023". Some browsers, such as Chrome, will
* account for this and still return a valid date. However,
* this is not a consistent behavior across all browsers.
*/
return new Date(`${refParts.month ?? 1}/${refParts.day ?? 1}/${refParts.year ?? 2023}${timeString} GMT+0000`);
return new Date(`${refParts.month}/${refParts.day}/${refParts.year}${timeString} GMT+0000`);
};
/**

View File

@@ -51,7 +51,7 @@
}
.header-collapse-condense ion-toolbar:first-of-type {
padding-top: 1px;
padding-top: 7px;
z-index: 1;
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -82,6 +82,8 @@
:host(.input-fill-outline) .label-text-wrapper,
:host(.input-fill-outline) .label-text-wrapper {
position: relative;
z-index: 1;
}
/**

View File

@@ -581,13 +581,6 @@
@include transform-origin(start, top);
max-width: 100%;
/**
* The 2 ensures the label
* remains on top of any browser
* autofill background too.
*/
z-index: 2;
}
/**

View File

@@ -268,7 +268,9 @@ export class Input implements ComponentInterface {
*/
@Prop() step?: string;
// FW-4914 Remove this property in Ionic 8
/**
* 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.
*/
@Prop() size?: number;
/**

View File

@@ -186,6 +186,9 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
expect(await input.screenshot()).toMatchSnapshot(screenshot(`input-label-slot-truncate`));
});
});
});
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('input: async label'), () => {
test('input should re-render when label slot is added async', async ({ page }) => {
await page.setContent(
@@ -210,27 +213,4 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
expect(await input.screenshot()).toMatchSnapshot(screenshot(`input-async-label`));
});
});
test.describe(title('input: floating/stacked label layering'), () => {
test('label should not be covered by text field', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/27812',
});
await page.setContent(
`
<style>
.custom-input .native-wrapper {
background: pink;
}
</style>
<ion-input class="custom-input" label="My Label" label-placement="stacked"></ion-input>
`,
config
);
const input = page.locator('ion-input');
expect(await input.screenshot()).toMatchSnapshot(screenshot(`input-label-layering`));
});
});
});

View File

@@ -76,19 +76,12 @@ export class ItemSliding implements ComponentInterface {
this.item = el.querySelector('ion-item');
this.contentEl = findClosestIonContent(el);
/**
* The MutationObserver needs to be added before we
* call updateOptions below otherwise we may miss
* ion-item-option elements that are added to the DOM
* while updateOptions is running and before the MutationObserver
* has been initialized.
*/
await this.updateOptions();
this.mutationObserver = watchForOptions<HTMLIonItemOptionElement>(el, 'ion-item-option', async () => {
await this.updateOptions();
});
await this.updateOptions();
this.gesture = (await import('../../utils/gesture')).createGesture({
el,
gestureName: 'item-swipe',

View File

@@ -3,6 +3,7 @@ import { configs, dragElementBy, test } from '@utils/test/playwright';
import { testSlidingItem } from '../test.utils';
// TODO FW-3006
/**
* item-sliding doesn't have mode-specific styling
*/
@@ -17,12 +18,19 @@ 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'), () => {
test('should open when swiped', async ({ page }) => {
// 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.'
);
await page.goto(`/src/components/item-sliding/test/basic`, config);
const item = page.locator('#item2');
@@ -33,7 +41,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
await expect(item).toHaveScreenshot(screenshot(`item-sliding-gesture`));
});
test('should not scroll when the item-sliding is swiped', async ({ page, skip }) => {
test.skip('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);

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -2,7 +2,6 @@
// Item
// --------------------------------------------------
// TODO: FW-4955
:host {
/**
@@ -46,10 +45,10 @@
*
* @prop --ripple-color: Color of the item ripple effect
*
* @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.
* @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
*/
--border-radius: 0px;
--border-width: 0px;

View File

@@ -23,10 +23,5 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await ionLoadingDidDismiss.next();
await expect(loading).toBeHidden();
});
test('should open if isOpen is true on load', async ({ page }) => {
await page.setContent('<ion-loading is-open="true"></ion-loading>', config);
await expect(page.locator('ion-loading')).toBeVisible();
});
});
});

View File

@@ -31,12 +31,6 @@ export interface MoveSheetToBreakpointOptions {
* `true` if the sheet can be transitioned and dismissed off the view.
*/
canDismiss?: boolean;
/**
* If `true`, the sheet will animate to the breakpoint.
* If `false`, the sheet will jump directly to the breakpoint.
*/
animated: boolean;
}
export const createSheetGesture = (
@@ -252,17 +246,11 @@ export const createSheetGesture = (
breakpoint: closest,
breakpointOffset: offset,
canDismiss: canDismissBlocksGesture,
/**
* The swipe is user-driven, so we should
* always animate when the gesture ends.
*/
animated: true,
});
};
const moveSheetToBreakpoint = (options: MoveSheetToBreakpointOptions) => {
const { breakpoint, canDismiss, breakpointOffset, animated } = options;
const { breakpoint, canDismiss, breakpointOffset } = options;
/**
* canDismiss should only prevent snapping
* when users are trying to dismiss. If canDismiss
@@ -372,7 +360,7 @@ export const createSheetGesture = (
},
{ oneTimeCallback: true }
)
.progressEnd(1, 0, animated ? 500 : 0);
.progressEnd(1, 0, 500);
});
};

View File

@@ -761,7 +761,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
return;
}
const { currentBreakpoint, moveSheetToBreakpoint, canDismiss, breakpoints, animated } = this;
const { currentBreakpoint, moveSheetToBreakpoint, canDismiss, breakpoints } = this;
if (currentBreakpoint === breakpoint) {
return;
@@ -772,7 +772,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
breakpoint,
breakpointOffset: 1 - currentBreakpoint!,
canDismiss: canDismiss !== undefined && canDismiss !== true && breakpoints![0] === 0,
animated,
});
await this.sheetTransition;
this.sheetTransition = undefined;

View File

@@ -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, 30);
await dragElementBy(modalHeader, page, 0, -500);
const modal = page.locator('ion-modal');
expect(modal).not.toBe(null);

View File

@@ -9,7 +9,12 @@ 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 }) => {
test.beforeEach(async ({ page, skip }) => {
skip.browser(
(browserName: string) => browserName !== 'chromium',
'dragElementBy is flaky outside of Chrome browsers.'
);
cardModalPage = new CardModalPage(page);
await cardModalPage.navigate('/src/components/modal/test/card-nav?ionic:_testing=false', config);
});
@@ -28,7 +33,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
const content = page.locator('.page-two-content');
await dragElementBy(content, page, 370, 0, 10);
await dragElementBy(content, page, 1000, 0, 10);
await ionNavDidChange.next();
});
@@ -42,7 +47,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await ionNavDidChange.next();
await cardModalPage.swipeToCloseModal('ion-modal ion-content.page-two-content', true, 270);
await cardModalPage.swipeToCloseModal('ion-modal ion-content.page-two-content');
});
});
});

View File

@@ -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, 300);
await dragElementBy(content, page, 0, 500);
await content.waitForElementState('stable');

View File

@@ -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, 300);
await dragElementBy(content, page, 0, 500);
await content.waitForElementState('stable');

View File

@@ -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 = page.locator('ion-modal ion-content');
const content = (await page.$('ion-modal ion-content'))!;
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false);
await content.waitFor();
await content.waitForElementState('stable');
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 = page.locator('ion-modal ion-content');
const content = (await page.$('ion-modal ion-content'))!;
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false);
await content.waitFor();
await content.waitForElementState('stable');
await expect(modal).toBeVisible();
});
test('content should be scrollable after gesture ends', async ({ page }) => {

View File

@@ -16,7 +16,7 @@ export class CardModalPage {
this.ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
}
async openModalByTrigger(selector: string) {
await this.page.locator(selector).click();
await this.page.click(selector);
await this.ionModalDidPresent.next();
return this.page.locator('ion-modal');

View File

@@ -20,10 +20,5 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await ionModalDidDismiss.next();
await expect(modal).toBeHidden();
});
test('should open if isOpen is true on load', async ({ page }) => {
await page.setContent('<ion-modal is-open="true"></ion-modal>', config);
await expect(page.locator('ion-modal')).toBeVisible();
});
});
});

View File

@@ -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, 125);
await dragElementBy(header, page, 0, 150);
await ionBreakpointDidChange.next();

View File

@@ -31,7 +31,7 @@ import { VIEW_STATE_ATTACHED, VIEW_STATE_DESTROYED, VIEW_STATE_NEW, convertToVie
export class Nav implements NavOutlet {
private transInstr: TransitionInstruction[] = [];
private sbAni?: Animation;
private gestureOrAnimationInProgress = false;
private animationEnabled = true;
private useRouter = false;
private isTransitioning = false;
private destroyed = false;
@@ -869,36 +869,7 @@ export class Nav implements NavOutlet {
// or if it is a portal (modal, actionsheet, etc.)
const opts = ti.opts!;
const progressCallback = opts.progressAnimation
? (ani: Animation | undefined) => {
/**
* Because this progress callback is called asynchronously
* it is possible for the gesture to start and end before
* the animation is ever set. In that scenario, we should
* immediately call progressEnd so that the transition promise
* resolves and the gesture does not get locked up.
*/
if (ani !== undefined && !this.gestureOrAnimationInProgress) {
this.gestureOrAnimationInProgress = true;
ani.onFinish(
() => {
this.gestureOrAnimationInProgress = false;
},
{ oneTimeCallback: true }
);
/**
* Playing animation to beginning
* with a duration of 0 prevents
* any flickering when the animation
* is later cleaned up.
*/
ani.progressEnd(0, 0, 0);
} else {
this.sbAni = ani;
}
}
: undefined;
const progressCallback = opts.progressAnimation ? (ani: Animation | undefined) => (this.sbAni = ani) : undefined;
const mode = getIonMode(this);
const enteringEl = enteringView.element!;
const leavingEl = leavingView && leavingView.element!;
@@ -1037,16 +1008,15 @@ export class Nav implements NavOutlet {
private canStart(): boolean {
return (
!this.gestureOrAnimationInProgress &&
!!this.swipeGesture &&
!this.isTransitioning &&
this.transInstr.length === 0 &&
this.animationEnabled &&
this.canGoBackSync()
);
}
private onStart() {
this.gestureOrAnimationInProgress = true;
this.pop({ direction: 'back', progressAnimation: true });
}
@@ -1058,9 +1028,10 @@ export class Nav implements NavOutlet {
private onEnd(shouldComplete: boolean, stepValue: number, dur: number) {
if (this.sbAni) {
this.animationEnabled = false;
this.sbAni.onFinish(
() => {
this.gestureOrAnimationInProgress = false;
this.animationEnabled = true;
},
{ oneTimeCallback: true }
);
@@ -1084,8 +1055,6 @@ export class Nav implements NavOutlet {
}
this.sbAni.progressEnd(shouldComplete ? 1 : 0, newStepValue, dur);
} else {
this.gestureOrAnimationInProgress = false;
}
}

View File

@@ -1,6 +1,5 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Method, Prop, State, Watch, h } from '@stencil/core';
import { raf } from '@utils/helpers';
import {
createDelegateController,
createTriggerController,
@@ -200,16 +199,6 @@ export class Picker implements ComponentInterface, OverlayInterface {
setOverlayId(this.el);
}
componentDidLoad() {
/**
* If picker was rendered with isOpen="true"
* then we should open picker immediately.
*/
if (this.isOpen === true) {
raf(() => this.present());
}
}
/**
* Present the picker overlay after it has been created.
*/

View File

@@ -23,10 +23,5 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await ionPickerDidDismiss.next();
await expect(picker).toBeHidden();
});
test('should open if isOpen is true on load', async ({ page }) => {
await page.setContent('<ion-picker is-open="true"></ion-picker>', config);
await expect(page.locator('ion-picker')).toBeVisible();
});
});
});

View File

@@ -1,11 +0,0 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('popover: isOpen'), () => {
test('should open if isOpen is true on load', async ({ page }) => {
await page.setContent('<ion-popover is-open="true"></ion-popover>', config);
await expect(page.locator('ion-popover')).toBeVisible();
});
});
});

View File

@@ -77,7 +77,13 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0);
await dragElementBy(knobEl, page, 30, 0, undefined, undefined, false);
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);
/**
* Do not use scrollToBottom() or other scrolling methods

View File

@@ -10,28 +10,15 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
/**
* The mouse events are flaky on CI
*/
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
);
test.fixme('should emit start/end events', async ({ page }, testInfo) => {
await page.setContent(`<ion-range value="20"></ion-range>`, config);
const rangeStart = await page.spyOnEvent('ionKnobMoveStart');
const rangeEnd = await page.spyOnEvent('ionKnobMoveEnd');
const rangeEl = page.locator('ion-range');
await dragElementBy(rangeEl, page, 300, 0);
await dragElementBy(rangeEl, page, testInfo.project.metadata.rtl ? -300 : 300, 0);
await page.waitForChanges();
/**
@@ -78,7 +65,13 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0);
await dragElementBy(knobEl, page, 30, 0, undefined, undefined, false);
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);
/**
* Do not use scrollToBottom() or other scrolling methods
@@ -125,7 +118,13 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
const rangeHandle = page.locator('ion-range .range-knob-handle');
const ionChangeSpy = await page.spyOnEvent('ionChange');
await dragElementBy(rangeHandle, page, 100, 0);
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 ionChangeSpy.next();
@@ -170,9 +169,11 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
const rangeHandle = page.locator('ion-range .range-knob-handle');
const ionInputSpy = await page.spyOnEvent('ionInput');
await rangeHandle.hover();
const boundingBox = await rangeHandle.boundingBox();
await dragElementBy(rangeHandle, page, 100, 0, undefined, undefined, false);
await rangeHandle.hover();
await page.mouse.down();
await page.mouse.move(boundingBox!.x + 100, boundingBox!.y);
await ionInputSpy.next();

View File

@@ -3,11 +3,13 @@ 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(title('refresher: basic'), () => {
test.describe.skip(title('refresher: basic'), () => {
test.beforeEach(async ({ page }) => {
await page.goto('/src/components/refresher/test/basic', config);
});

View File

@@ -3,11 +3,12 @@ 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(title('refresher: custom scroll target'), () => {
test.describe.skip(title('refresher: custom scroll target'), () => {
test.beforeEach(async ({ page }) => {
await page.goto('/src/components/refresher/test/scroll-target', config);
});
@@ -18,7 +19,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
expect(await items.count()).toBe(30);
await pullToRefresh(page);
await pullToRefresh(page, '#inner-scroll');
expect(await items.count()).toBe(60);
});
@@ -38,7 +39,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
expect(await items.count()).toBe(30);
await pullToRefresh(page);
await pullToRefresh(page, '#inner-scroll');
expect(await items.count()).toBe(60);
});

View File

@@ -18,7 +18,7 @@ const pullToRefresh = async (page: E2EPage, selector = 'body') => {
const ev = await page.spyOnEvent('ionRefreshComplete');
await dragElementByYAxis(target, page, 320);
await dragElementByYAxis(target, page, 400);
await ev.next();
};

View File

@@ -1,12 +1,18 @@
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(title('reorder group: interactive'), () => {
test.beforeEach(async ({ page }) => {
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.'
);
await page.goto(`/src/components/reorder-group/test/interactive`, config);
});
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {

View File

@@ -1,12 +1,17 @@
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(title('reorder group: nested'), () => {
test.beforeEach(async ({ page }) => {
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.'
);
await page.goto(`/src/components/reorder-group/test/nested`, config);
});
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {

View File

@@ -1,12 +1,17 @@
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(title('reorder group: scroll-target'), () => {
test.beforeEach(async ({ page }) => {
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.'
);
await page.goto(`/src/components/reorder-group/test/scroll-target`, config);
});
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {

View File

@@ -1,7 +1,7 @@
@import "./select-popover";
@import "./select-popover.md.vars";
ion-list ion-radio::part(container) {
ion-list ion-radio {
opacity: 0;
}

View File

@@ -1,5 +1,5 @@
import type { ComponentInterface } from '@stencil/core';
import { Element, Component, Host, Prop, h, forceUpdate } from '@stencil/core';
import { Element, Component, Host, Prop, h } from '@stencil/core';
import { safeCall } from '@utils/overlays';
import { getClassMap } from '@utils/theme';
@@ -116,28 +116,19 @@ export class SelectPopover implements ComponentInterface {
renderCheckboxOptions(options: SelectPopoverOption[]) {
return options.map((option) => (
<ion-item
class={{
// TODO FW-4784
'item-checkbox-checked': option.checked,
...getClassMap(option.cssClass),
}}
>
<ion-item class={getClassMap(option.cssClass)}>
<ion-checkbox
slot="start"
value={option.value}
disabled={option.disabled}
checked={option.checked}
justify="start"
labelPlacement="end"
legacy={true}
onIonChange={(ev) => {
this.setChecked(ev);
this.callOptionHandler(ev);
// TODO FW-4784
forceUpdate(this);
}}
>
{option.text}
</ion-checkbox>
></ion-checkbox>
<ion-label>{option.text}</ion-label>
</ion-item>
));
}
@@ -148,16 +139,12 @@ export class SelectPopover implements ComponentInterface {
return (
<ion-radio-group value={checked} onIonChange={(ev) => this.callOptionHandler(ev)}>
{options.map((option) => (
<ion-item
class={{
// TODO FW-4784
'item-radio-checked': option.value === checked,
...getClassMap(option.cssClass),
}}
>
<ion-item class={getClassMap(option.cssClass)}>
<ion-label>{option.text}</ion-label>
<ion-radio
value={option.value}
disabled={option.disabled}
legacy={true}
onClick={() => this.dismissParentPopover()}
onKeyUp={(ev) => {
if (ev.key === ' ') {
@@ -169,9 +156,7 @@ export class SelectPopover implements ComponentInterface {
this.dismissParentPopover();
}
}}
>
{option.text}
</ion-radio>
></ion-radio>
</ion-item>
))}
</ion-radio-group>

View File

@@ -81,11 +81,11 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
selectPopoverPage = new SelectPopoverPage(page);
});
test('should not have visual regressions with single selection', async () => {
await selectPopoverPage.setup(config, checkedOptions, false);
await selectPopoverPage.setup(config, options, false);
await selectPopoverPage.screenshot(screenshot, 'select-popover-diff');
});
test('should not have visual regressions with multiple selection', async () => {
await selectPopoverPage.setup(config, checkedOptions, true);
await selectPopoverPage.setup(config, options, true);
await selectPopoverPage.screenshot(screenshot, 'select-popover-multiple-diff');
});
});

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -302,27 +302,4 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
await expect(page.locator('.container')).toHaveScreenshot(screenshot(`textarea-multi-line-sizing`));
});
});
test.describe(title('textarea: floating/stacked label layering'), () => {
test('label should not be covered by text field', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/27812',
});
await page.setContent(
`
<style>
.custom-textarea .native-wrapper {
background: pink;
}
</style>
<ion-textarea class="custom-textarea" label="My Label" label-placement="stacked"></ion-textarea>
`,
config
);
const textarea = page.locator('ion-textarea');
expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-label-layering`));
});
});
});

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -82,6 +82,8 @@
:host(.textarea-fill-outline) .label-text-wrapper,
:host(.textarea-fill-outline) .label-text-wrapper {
position: relative;
z-index: 1;
}
/**

View File

@@ -629,13 +629,6 @@
@include padding(0px);
max-width: 100%;
/**
* The 2 ensures the label
* remains on top of any browser
* autofill background too.
*/
z-index: 2;
}
/**

View File

@@ -12,23 +12,3 @@ 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'));
});
});
});

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -49,6 +49,8 @@
min-width: 100%;
padding-bottom: 6px;
font-size: 34px;
font-weight: 700;

View File

@@ -24,10 +24,5 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await ionToastDidDismiss.next();
await expect(toast).toBeHidden();
});
test('should open if isOpen is true on load', async ({ page }) => {
await page.setContent('<ion-toast is-open="true"></ion-toast>', config);
await expect(page.locator('ion-toast')).toBeVisible();
});
});
});

View File

@@ -1,7 +1,6 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { State, Watch, Component, Element, Event, h, Host, Method, Prop } from '@stencil/core';
import { ENABLE_HTML_CONTENT_DEFAULT } from '@utils/config';
import { raf } from '@utils/helpers';
import { printIonWarning } from '@utils/logging';
import {
createDelegateController,
@@ -254,16 +253,6 @@ export class Toast implements ComponentInterface, OverlayInterface {
setOverlayId(this.el);
}
componentDidLoad() {
/**
* If toast was rendered with isOpen="true"
* then we should open toast immediately.
*/
if (this.isOpen === true) {
raf(() => this.present());
}
}
/**
* Present the toast overlay after it has been created.
*/

View File

@@ -84,16 +84,6 @@
// 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;
@@ -104,4 +94,4 @@
order: map-get($toolbar-order-ios, title-large);
min-width: 100%;
}
}

View File

@@ -1,13 +1,7 @@
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;
@@ -149,6 +143,7 @@ export const startTapClick = (config: Config) => {
}
};
const doc = document;
doc.addEventListener('ionGestureCaptured', cancelActive);
doc.addEventListener('touchstart', onTouchStart, true);

View File

@@ -1,11 +1,3 @@
/**
* 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 './';
@@ -16,8 +8,7 @@ export const dragElementBy = async (
dragByX = 0,
dragByY = 0,
startXCoord?: number,
startYCoord?: number,
releaseDrag = true
startYCoord?: number
) => {
const boundingBox = await el.boundingBox();
@@ -30,17 +21,14 @@ export const dragElementBy = async (
const startX = startXCoord === undefined ? boundingBox.x + boundingBox.width / 2 : startXCoord;
const startY = startYCoord === undefined ? boundingBox.y + boundingBox.height / 2 : startYCoord;
// Navigate to the start position.
await page.mouse.move(startX, startY);
const endX = startX + dragByX;
const endY = startY + dragByY;
await page.mouse.move(startX, startY);
await page.mouse.down();
// Drag the element.
await moveElement(page, startX, startY, dragByX, dragByY);
if (releaseDrag) {
await page.mouse.up();
}
await page.mouse.move(endX, endY, { steps: 10 });
await page.mouse.up();
};
/**
@@ -67,83 +55,12 @@ 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();
// Drag the element.
await moveElement(page, startX, startY, 0, dragByY);
for (let i = 0; i < dragByY; i += 20) {
await page.mouse.move(startX, startY + i);
}
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));
}
}
};

View File

@@ -3,30 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.4](https://github.com/ionic-team/ionic-framework/compare/v7.2.3...v7.2.4) (2023-08-16)
**Note:** Version bump only for package @ionic/docs
## [7.2.3](https://github.com/ionic-team/ionic-framework/compare/v7.2.2...v7.2.3) (2023-08-09)
**Note:** Version bump only for package @ionic/docs
## [7.2.2](https://github.com/ionic-team/ionic-framework/compare/v7.2.1...v7.2.2) (2023-08-02)
**Note:** Version bump only for package @ionic/docs
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
**Note:** Version bump only for package @ionic/docs

View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/docs",
"version": "7.2.4",
"version": "7.2.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/docs",
"version": "7.2.4",
"version": "7.2.1",
"license": "MIT"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/docs",
"version": "7.2.4",
"version": "7.2.1",
"description": "Pre-packaged API documentation for the Ionic docs.",
"main": "core.json",
"types": "core.d.ts",

Some files were not shown because too many files have changed in this diff Show More