Compare commits
14 Commits
sp/react-h
...
sp/default
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1675a9b5de | ||
|
|
5e016a6616 | ||
|
|
0edcb2cd85 | ||
|
|
7b197a3226 | ||
|
|
2e626a909f | ||
|
|
d82414d43f | ||
|
|
5b7e422dc0 | ||
|
|
0104d89927 | ||
|
|
3f06da4cfc | ||
|
|
5ff32b7786 | ||
|
|
b5c736f5ac | ||
|
|
81714d45bd | ||
|
|
4e0b522728 | ||
|
|
94514501a6 |
15
CHANGELOG.md
@@ -3,6 +3,21 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [7.4.1](https://github.com/ionic-team/ionic-framework/compare/v7.4.0...v7.4.1) (2023-09-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **overlays:** correctly re-add root to accessibility tree ([#28183](https://github.com/ionic-team/ionic-framework/issues/28183)) ([81714d4](https://github.com/ionic-team/ionic-framework/commit/81714d45bd97f0ba91729959b60a0dc1d1d06533)), closes [#28180](https://github.com/ionic-team/ionic-framework/issues/28180)
|
||||
* **radio,toggle,checkbox,select:** padded space is clickable in items ([#28136](https://github.com/ionic-team/ionic-framework/issues/28136)) ([5b7e422](https://github.com/ionic-team/ionic-framework/commit/5b7e422dc0bfd4d58fb31f62715af47e62dabb57)), closes [#27169](https://github.com/ionic-team/ionic-framework/issues/27169)
|
||||
* **range:** knob is not cut off in item with modern syntax ([#28199](https://github.com/ionic-team/ionic-framework/issues/28199)) ([0104d89](https://github.com/ionic-team/ionic-framework/commit/0104d899270d73e16f2850a5fd7d2ba25a9e7ef0)), closes [#27199](https://github.com/ionic-team/ionic-framework/issues/27199)
|
||||
* **scroll-assist:** improve input scroll accuracy with native resizing ([#28169](https://github.com/ionic-team/ionic-framework/issues/28169)) ([b5c736f](https://github.com/ionic-team/ionic-framework/commit/b5c736f5ac829efebedf3256ddf77ab3daa7a5f6)), closes [#22940](https://github.com/ionic-team/ionic-framework/issues/22940)
|
||||
* **scroll-assist:** re-run when keyboard changes ([#28174](https://github.com/ionic-team/ionic-framework/issues/28174)) ([3f06da4](https://github.com/ionic-team/ionic-framework/commit/3f06da4cfc0d59c658e17e09ccb1ea28a29339f9)), closes [#22940](https://github.com/ionic-team/ionic-framework/issues/22940)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [7.4.0](https://github.com/ionic-team/ionic-framework/compare/v7.3.4...v7.4.0) (2023-09-14)
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,21 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [7.4.1](https://github.com/ionic-team/ionic-framework/compare/v7.4.0...v7.4.1) (2023-09-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **overlays:** correctly re-add root to accessibility tree ([#28183](https://github.com/ionic-team/ionic-framework/issues/28183)) ([81714d4](https://github.com/ionic-team/ionic-framework/commit/81714d45bd97f0ba91729959b60a0dc1d1d06533)), closes [#28180](https://github.com/ionic-team/ionic-framework/issues/28180)
|
||||
* **radio,toggle,checkbox,select:** padded space is clickable in items ([#28136](https://github.com/ionic-team/ionic-framework/issues/28136)) ([5b7e422](https://github.com/ionic-team/ionic-framework/commit/5b7e422dc0bfd4d58fb31f62715af47e62dabb57)), closes [#27169](https://github.com/ionic-team/ionic-framework/issues/27169)
|
||||
* **range:** knob is not cut off in item with modern syntax ([#28199](https://github.com/ionic-team/ionic-framework/issues/28199)) ([0104d89](https://github.com/ionic-team/ionic-framework/commit/0104d899270d73e16f2850a5fd7d2ba25a9e7ef0)), closes [#27199](https://github.com/ionic-team/ionic-framework/issues/27199)
|
||||
* **scroll-assist:** improve input scroll accuracy with native resizing ([#28169](https://github.com/ionic-team/ionic-framework/issues/28169)) ([b5c736f](https://github.com/ionic-team/ionic-framework/commit/b5c736f5ac829efebedf3256ddf77ab3daa7a5f6)), closes [#22940](https://github.com/ionic-team/ionic-framework/issues/22940)
|
||||
* **scroll-assist:** re-run when keyboard changes ([#28174](https://github.com/ionic-team/ionic-framework/issues/28174)) ([3f06da4](https://github.com/ionic-team/ionic-framework/commit/3f06da4cfc0d59c658e17e09ccb1ea28a29339f9)), closes [#22940](https://github.com/ionic-team/ionic-framework/issues/22940)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [7.4.0](https://github.com/ionic-team/ionic-framework/compare/v7.3.4...v7.4.0) (2023-09-14)
|
||||
|
||||
|
||||
|
||||
@@ -556,6 +556,7 @@ ion-input,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secon
|
||||
ion-input,prop,counter,boolean,false,false,false
|
||||
ion-input,prop,counterFormatter,((inputLength: number, maxLength: number) => string) | undefined,undefined,false,false
|
||||
ion-input,prop,debounce,number | undefined,undefined,false,false
|
||||
ion-input,prop,defaultValue,null | number | string | undefined,this.getValue(),false,false
|
||||
ion-input,prop,disabled,boolean,false,false,false
|
||||
ion-input,prop,enterkeyhint,"done" | "enter" | "go" | "next" | "previous" | "search" | "send" | undefined,undefined,false,false
|
||||
ion-input,prop,errorText,string | undefined,undefined,false,false
|
||||
|
||||
18
core/package-lock.json
generated
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "7.4.0",
|
||||
"version": "7.4.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/core",
|
||||
"version": "7.4.0",
|
||||
"version": "7.4.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^4.2.1",
|
||||
"@stencil/core": "^4.3.0",
|
||||
"ionicons": "7.1.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
@@ -1630,9 +1630,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.2.1.tgz",
|
||||
"integrity": "sha512-alYwqVwxfD0n6HKRVJqJoTzQNnf44n/sddvjNu3JMEn3sfY/Ag7rpmwUntYjtJmRut+or+9gPPgIJviCuKi4yQ==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.3.0.tgz",
|
||||
"integrity": "sha512-WYjftKg5fuqO7mf3nTl1aCRurkeMmfEF38WcBG4VLF6UPQ+MA76/koedGR2LGhATGByx+pbxR4iRxAr2Bspc9A==",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
},
|
||||
@@ -11536,9 +11536,9 @@
|
||||
"requires": {}
|
||||
},
|
||||
"@stencil/core": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.2.1.tgz",
|
||||
"integrity": "sha512-alYwqVwxfD0n6HKRVJqJoTzQNnf44n/sddvjNu3JMEn3sfY/Ag7rpmwUntYjtJmRut+or+9gPPgIJviCuKi4yQ=="
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.3.0.tgz",
|
||||
"integrity": "sha512-WYjftKg5fuqO7mf3nTl1aCRurkeMmfEF38WcBG4VLF6UPQ+MA76/koedGR2LGhATGByx+pbxR4iRxAr2Bspc9A=="
|
||||
},
|
||||
"@stencil/react-output-target": {
|
||||
"version": "0.5.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "7.4.0",
|
||||
"version": "7.4.1",
|
||||
"description": "Base components for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -31,7 +31,7 @@
|
||||
"loader/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@stencil/core": "^4.2.1",
|
||||
"@stencil/core": "^4.3.0",
|
||||
"ionicons": "7.1.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
|
||||
@@ -60,7 +60,8 @@ 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,
|
||||
/* Test retries help catch flaky tests on CI */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
reportSlowTests: null,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
|
||||
10
core/src/components.d.ts
vendored
@@ -17,7 +17,7 @@ import { CheckboxChangeEventDetail } from "./components/checkbox/checkbox-interf
|
||||
import { ScrollBaseDetail, ScrollDetail } from "./components/content/content-interface";
|
||||
import { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimePresentation, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
|
||||
import { SpinnerTypes } from "./components/spinner/spinner-configs";
|
||||
import { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface";
|
||||
import { InputChangeEventDetail, InputInputEventDetail, InputValue } from "./components/input/input-interface";
|
||||
import { CounterFormatter } from "./components/item/item-interface";
|
||||
import { MenuChangeEventDetail, Side } from "./components/menu/menu-interface";
|
||||
import { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface";
|
||||
@@ -53,7 +53,7 @@ export { CheckboxChangeEventDetail } from "./components/checkbox/checkbox-interf
|
||||
export { ScrollBaseDetail, ScrollDetail } from "./components/content/content-interface";
|
||||
export { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimePresentation, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
|
||||
export { SpinnerTypes } from "./components/spinner/spinner-configs";
|
||||
export { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface";
|
||||
export { InputChangeEventDetail, InputInputEventDetail, InputValue } from "./components/input/input-interface";
|
||||
export { CounterFormatter } from "./components/item/item-interface";
|
||||
export { MenuChangeEventDetail, Side } from "./components/menu/menu-interface";
|
||||
export { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface";
|
||||
@@ -1189,6 +1189,7 @@ export namespace Components {
|
||||
* Set the amount of time, in milliseconds, to wait to trigger the `ionInput` event after each keystroke.
|
||||
*/
|
||||
"debounce"?: number;
|
||||
"defaultValue"?: InputValue;
|
||||
/**
|
||||
* If `true`, the user cannot interact with the input.
|
||||
*/
|
||||
@@ -1297,7 +1298,7 @@ export namespace Components {
|
||||
/**
|
||||
* The value of the input.
|
||||
*/
|
||||
"value"?: string | number | null;
|
||||
"value"?: InputValue;
|
||||
}
|
||||
interface IonItem {
|
||||
/**
|
||||
@@ -5248,6 +5249,7 @@ declare namespace LocalJSX {
|
||||
* Set the amount of time, in milliseconds, to wait to trigger the `ionInput` event after each keystroke.
|
||||
*/
|
||||
"debounce"?: number;
|
||||
"defaultValue"?: InputValue;
|
||||
/**
|
||||
* If `true`, the user cannot interact with the input.
|
||||
*/
|
||||
@@ -5368,7 +5370,7 @@ declare namespace LocalJSX {
|
||||
/**
|
||||
* The value of the input.
|
||||
*/
|
||||
"value"?: string | number | null;
|
||||
"value"?: InputValue;
|
||||
}
|
||||
interface IonItem {
|
||||
/**
|
||||
|
||||
@@ -140,9 +140,8 @@ export class Checkbox implements ComponentInterface {
|
||||
*/
|
||||
@Event() ionStyle!: EventEmitter<StyleEventDetail>;
|
||||
|
||||
// TODO(FW-3100): remove this
|
||||
connectedCallback() {
|
||||
this.legacyFormController = createLegacyFormController(this.el);
|
||||
this.legacyFormController = createLegacyFormController(this.el); // TODO(FW-3100): remove this
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
@@ -195,7 +194,7 @@ export class Checkbox implements ComponentInterface {
|
||||
});
|
||||
};
|
||||
|
||||
private toggleChecked = (ev: any) => {
|
||||
private toggleChecked = (ev: Event) => {
|
||||
ev.preventDefault();
|
||||
|
||||
this.setFocus();
|
||||
@@ -211,6 +210,10 @@ export class Checkbox implements ComponentInterface {
|
||||
this.ionBlur.emit();
|
||||
};
|
||||
|
||||
private onClick = (ev: MouseEvent) => {
|
||||
this.toggleChecked(ev);
|
||||
};
|
||||
|
||||
// TODO(FW-3100): run contents of renderCheckbox directly instead
|
||||
render() {
|
||||
const { legacyFormController } = this;
|
||||
@@ -252,6 +255,7 @@ export class Checkbox implements ComponentInterface {
|
||||
[`checkbox-alignment-${alignment}`]: true,
|
||||
[`checkbox-label-placement-${labelPlacement}`]: true,
|
||||
})}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
<label class="checkbox-wrapper">
|
||||
{/*
|
||||
@@ -333,6 +337,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
|
||||
'legacy-checkbox': true,
|
||||
interactive: true,
|
||||
})}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
<svg class="checkbox-icon" viewBox="0 0 24 24" part="container">
|
||||
{path}
|
||||
|
||||
@@ -83,5 +83,28 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
await checkbox.evaluate((el: HTMLIonCheckboxElement) => (el.checked = true));
|
||||
expect(ionChange).not.toHaveReceivedEvent();
|
||||
});
|
||||
|
||||
test('clicking padded space within item should click the checkbox', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-item>
|
||||
<ion-checkbox>Size</ion-checkbox>
|
||||
</ion-item>
|
||||
`,
|
||||
config
|
||||
);
|
||||
const itemNative = page.locator('.item-native');
|
||||
const ionChange = await page.spyOnEvent('ionChange');
|
||||
|
||||
// Clicks the padded space within the item
|
||||
await itemNative.click({
|
||||
position: {
|
||||
x: 5,
|
||||
y: 5,
|
||||
},
|
||||
});
|
||||
|
||||
expect(ionChange).toHaveReceivedEvent();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -67,5 +67,28 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
await checkbox.evaluate((el: HTMLIonCheckboxElement) => (el.checked = true));
|
||||
await expect(ionChange).not.toHaveReceivedEvent();
|
||||
});
|
||||
|
||||
test('clicking padded space within item should click the checkbox', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-item>
|
||||
<ion-checkbox legacy="true" value="my-checkbox"></ion-checkbox>
|
||||
</ion-item>
|
||||
`,
|
||||
config
|
||||
);
|
||||
const itemNative = page.locator('.item-native');
|
||||
const ionChange = await page.spyOnEvent('ionChange');
|
||||
|
||||
// Clicks the padded space within the item
|
||||
await itemNative.click({
|
||||
position: {
|
||||
x: 5,
|
||||
y: 5,
|
||||
},
|
||||
});
|
||||
|
||||
expect(ionChange).toHaveReceivedEvent();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,3 +19,5 @@ export interface InputCustomEvent<T = InputChangeEventDetail> extends CustomEven
|
||||
detail: T;
|
||||
target: HTMLIonInputElement;
|
||||
}
|
||||
|
||||
export type InputValue = string | number | null | undefined;
|
||||
|
||||
@@ -13,7 +13,7 @@ import { closeCircle, closeSharp } from 'ionicons/icons';
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import type { AutocompleteTypes, Color, StyleEventDetail, TextFieldTypes } from '../../interface';
|
||||
|
||||
import type { InputChangeEventDetail, InputInputEventDetail } from './input-interface';
|
||||
import type { InputChangeEventDetail, InputInputEventDetail, InputValue } from './input-interface';
|
||||
import { getCounterText } from './input.utils';
|
||||
|
||||
/**
|
||||
@@ -53,7 +53,7 @@ export class Input implements ComponentInterface {
|
||||
/**
|
||||
* The value of the input when the input is focused.
|
||||
*/
|
||||
private focusedValue?: string | number | null;
|
||||
private focusedValue?: InputValue;
|
||||
|
||||
@State() hasFocus = false;
|
||||
|
||||
@@ -279,7 +279,9 @@ export class Input implements ComponentInterface {
|
||||
/**
|
||||
* The value of the input.
|
||||
*/
|
||||
@Prop({ mutable: true }) value?: string | number | null = '';
|
||||
@Prop({ mutable: true }) value?: InputValue = '';
|
||||
|
||||
@Prop({ mutable: true }) defaultValue?: InputValue = this.getValue();
|
||||
|
||||
/**
|
||||
* The `ionInput` event is fired each time the user modifies the input's value.
|
||||
@@ -352,6 +354,11 @@ export class Input implements ComponentInterface {
|
||||
this.emitStyle();
|
||||
}
|
||||
|
||||
@Watch('defaultValue')
|
||||
protected defaultValueChanged(newValue: InputValue) {
|
||||
this.value = this.defaultValue = this.getValue(newValue);
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
this.inheritedAttributes = {
|
||||
...inheritAriaAttributes(this.el),
|
||||
@@ -383,6 +390,12 @@ export class Input implements ComponentInterface {
|
||||
|
||||
componentDidLoad() {
|
||||
this.originalIonInput = this.ionInput;
|
||||
|
||||
if (this.value === '' || this.value == null) {
|
||||
if (this.defaultValue !== '') {
|
||||
this.value = this.getValue(this.defaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidRender() {
|
||||
@@ -466,8 +479,8 @@ export class Input implements ComponentInterface {
|
||||
return clearOnEdit === undefined ? type === 'password' : clearOnEdit;
|
||||
}
|
||||
|
||||
private getValue(): string {
|
||||
return typeof this.value === 'number' ? this.value.toString() : (this.value || '').toString();
|
||||
private getValue(value = this.value): string {
|
||||
return typeof value === 'number' ? value.toString() : (value || '').toString();
|
||||
}
|
||||
|
||||
private emitStyle() {
|
||||
@@ -685,6 +698,7 @@ export class Input implements ComponentInterface {
|
||||
const { disabled, fill, readonly, shape, inputId, labelPlacement } = this;
|
||||
const mode = getIonMode(this);
|
||||
const value = this.getValue();
|
||||
const defaultValue = this.getValue(this.defaultValue);
|
||||
const inItem = hostContext('ion-item', this.el);
|
||||
const shouldRenderHighlight = mode === 'md' && fill !== 'outline' && !inItem;
|
||||
|
||||
@@ -732,6 +746,7 @@ export class Input implements ComponentInterface {
|
||||
size={this.size}
|
||||
type={this.type}
|
||||
value={value}
|
||||
defaultValue={defaultValue}
|
||||
onInput={this.onInput}
|
||||
onChange={this.onChange}
|
||||
onBlur={this.onBlur}
|
||||
@@ -796,6 +811,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
|
||||
|
||||
const mode = getIonMode(this);
|
||||
const value = this.getValue();
|
||||
const defaultValue = this.getValue(this.defaultValue);
|
||||
const labelId = this.inputId + '-lbl';
|
||||
const label = findItemLabel(this.el);
|
||||
if (label) {
|
||||
@@ -840,6 +856,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
|
||||
size={this.size}
|
||||
type={this.type}
|
||||
value={value}
|
||||
defaultValue={defaultValue}
|
||||
onInput={this.onInput}
|
||||
onChange={this.onChange}
|
||||
onBlur={this.onBlur}
|
||||
|
||||
@@ -1,18 +1,53 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, dragElementBy, test } from '@utils/test/playwright';
|
||||
|
||||
import { testSlidingItem } from '../test.utils';
|
||||
|
||||
/**
|
||||
* item-sliding doesn't have mode-specific styling
|
||||
* item-sliding doesn't have mode-specific styling,
|
||||
* but the child components, item-options and item-option, do.
|
||||
*
|
||||
* It is important to test all modes to ensure that the
|
||||
* child components are being rendered correctly.
|
||||
*/
|
||||
configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => {
|
||||
configs().forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('item-sliding: basic'), () => {
|
||||
test.fixme('should not have visual regressions', async ({ page }) => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`/src/components/item-sliding/test/basic`, config);
|
||||
});
|
||||
test.describe('start options', () => {
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
const item = page.locator('#item2');
|
||||
|
||||
await testSlidingItem(page, 'item2', 'start', screenshot, true);
|
||||
await testSlidingItem(page, 'item2', 'end', screenshot);
|
||||
/**
|
||||
* Negative dragByX value to drag element from the right to the left
|
||||
* to reveal the options on the right side.
|
||||
* Positive dragByX value to drag element from the left to the right
|
||||
* to reveal the options on the left side.
|
||||
*/
|
||||
const dragByX = config.direction === 'rtl' ? -150 : 150;
|
||||
|
||||
await dragElementBy(item, page, dragByX);
|
||||
await page.waitForChanges();
|
||||
|
||||
await expect(item).toHaveScreenshot(screenshot('item-sliding-start'));
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('end options', () => {
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
const item = page.locator('#item2');
|
||||
|
||||
/**
|
||||
* Negative dragByX value to drag element from the right to the left
|
||||
* to reveal the options on the right side.
|
||||
* Positive dragByX value to drag element from the left to the right
|
||||
* to reveal the options on the left side.
|
||||
*/
|
||||
const dragByX = config.direction === 'rtl' ? 150 : -150;
|
||||
|
||||
await dragElementBy(item, page, dragByX);
|
||||
|
||||
await expect(item).toHaveScreenshot(screenshot('item-sliding-end'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
@@ -1,19 +1,36 @@
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
||||
|
||||
import { testSlidingItem } from '../test.utils';
|
||||
|
||||
// TODO FW-3006
|
||||
/**
|
||||
* item-sliding doesn't have mode-specific styling
|
||||
* item-sliding doesn't have mode-specific styling,
|
||||
* but the child components, item-options and item-option, do.
|
||||
*
|
||||
* It is important to test all modes to ensure that the
|
||||
* child components are being rendered correctly.
|
||||
*/
|
||||
configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe.skip(title('item-sliding: icons'), () => {
|
||||
configs().forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('item-sliding: icons'), () => {
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
await page.goto(`/src/components/item-sliding/test/icons`, config);
|
||||
|
||||
const itemIDs = ['iconsOnly', 'iconsStart', 'iconsEnd', 'iconsTop', 'iconsBottom'];
|
||||
for (const itemID of itemIDs) {
|
||||
await testSlidingItem(page, itemID, itemID, screenshot);
|
||||
const item = page.locator(`#${itemID}`);
|
||||
|
||||
/**
|
||||
* Negative dragByX value to drag element from the right to the left
|
||||
* to reveal the options on the right side.
|
||||
* Positive dragByX value to drag element from the left to the right
|
||||
* to reveal the options on the left side.
|
||||
*/
|
||||
const dragByX = config.direction === 'rtl' ? 150 : -150;
|
||||
|
||||
await dragElementBy(item, page, dragByX);
|
||||
await page.waitForChanges();
|
||||
|
||||
// Convert camelCase ids to kebab-case for screenshot file names
|
||||
const itemIDKebab = itemID.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
||||
await expect(item).toHaveScreenshot(screenshot(`item-sliding-${itemIDKebab}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
@@ -1,12 +1,11 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
||||
/**
|
||||
* This behavior does not vary across modes/directions
|
||||
*/
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('item-sliding: scroll-target'), () => {
|
||||
// TODO FW-3006
|
||||
test.skip('should not scroll when the item-sliding is swiped in custom scroll target', async ({ page, skip }) => {
|
||||
test('should not scroll when the item-sliding is swiped in custom scroll target', async ({ page, skip }) => {
|
||||
skip.browser('webkit', 'mouse.wheel is not available in WebKit');
|
||||
|
||||
await page.goto(`/src/components/item-sliding/test/scroll-target`, config);
|
||||
@@ -16,13 +15,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0);
|
||||
|
||||
const box = (await itemSlidingEl.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(itemSlidingEl, page, -150, 0, undefined, undefined, false);
|
||||
|
||||
/**
|
||||
* Do not use scrollToBottom() or other scrolling methods
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import type { E2EPage, ScreenshotFn } from '@utils/test/playwright';
|
||||
|
||||
/**
|
||||
* Warning: This function will fail when in RTL mode.
|
||||
* TODO(FW-3711): Remove the `directions` config when this issue preventing
|
||||
* tests from passing in RTL mode is resolved.
|
||||
*/
|
||||
export const testSlidingItem = async (
|
||||
page: E2EPage,
|
||||
itemID: string,
|
||||
@@ -23,6 +28,9 @@ export const testSlidingItem = async (
|
||||
}
|
||||
|
||||
// opening animation takes longer than waitForChanges accounts for
|
||||
// especially if another item sliding is already open,
|
||||
// so we wait to ensure the opened item is closed before
|
||||
// opening another
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await expect(item).toHaveScreenshot(screenshot(`item-sliding-${screenshotNameSuffix}`));
|
||||
|
||||
@@ -173,6 +173,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Item: Interactive
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.item-has-interactive-control) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
// Item: Disabled
|
||||
// --------------------------------------------------
|
||||
@@ -189,6 +196,7 @@
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Native Item
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ComponentInterface } from '@stencil/core';
|
||||
import { Component, Element, Host, Listen, Prop, State, Watch, forceUpdate, h } from '@stencil/core';
|
||||
import { Build, Component, Element, Host, Listen, Prop, State, Watch, forceUpdate, h } from '@stencil/core';
|
||||
import type { AnchorInterface, ButtonInterface } from '@utils/element-interface';
|
||||
import type { Attributes } from '@utils/helpers';
|
||||
import { inheritAttributes, raf } from '@utils/helpers';
|
||||
@@ -350,6 +350,22 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
|
||||
}
|
||||
}
|
||||
|
||||
private getFirstInteractive() {
|
||||
if (Build.isTesting) {
|
||||
/**
|
||||
* Pseudo selectors can't be tested in unit tests.
|
||||
* It will cause an error when running the tests.
|
||||
*
|
||||
* TODO: FW-5205 - Remove the build conditional when this is fixed in Stencil
|
||||
*/
|
||||
return undefined;
|
||||
}
|
||||
const controls = this.el.querySelectorAll<HTMLElement>(
|
||||
'ion-toggle:not([disabled]), ion-checkbox:not([disabled]), ion-radio:not([disabled]), ion-select:not([disabled])'
|
||||
);
|
||||
return controls[0];
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
counterString,
|
||||
@@ -367,6 +383,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
|
||||
routerAnimation,
|
||||
routerDirection,
|
||||
inheritedAriaAttributes,
|
||||
multipleInputs,
|
||||
} = this;
|
||||
const childStyles = {} as StyleEventDetail;
|
||||
const mode = getIonMode(this);
|
||||
@@ -383,15 +400,41 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
|
||||
rel,
|
||||
target,
|
||||
};
|
||||
|
||||
let clickFn = {};
|
||||
|
||||
const firstInteractive = this.getFirstInteractive();
|
||||
|
||||
// Only set onClick if the item is clickable to prevent screen
|
||||
// readers from reading all items as clickable
|
||||
const clickFn = clickable
|
||||
? {
|
||||
onClick: (ev: Event) => {
|
||||
if (clickable || (firstInteractive !== undefined && !multipleInputs)) {
|
||||
clickFn = {
|
||||
onClick: (ev: MouseEvent) => {
|
||||
if (clickable) {
|
||||
openURL(href, ev, routerDirection, routerAnimation);
|
||||
},
|
||||
}
|
||||
: {};
|
||||
}
|
||||
if (firstInteractive !== undefined && !multipleInputs) {
|
||||
const path = ev.composedPath();
|
||||
const target = path[0] as HTMLElement;
|
||||
if (ev.isTrusted) {
|
||||
/**
|
||||
* Dispatches a click event to the first interactive element,
|
||||
* when it is the result of a user clicking on the item.
|
||||
*
|
||||
* We check if the click target is in the shadow root,
|
||||
* which means the user clicked on the .item-native or
|
||||
* .item-inner padding.
|
||||
*/
|
||||
const clickedWithinShadowRoot = this.el.shadowRoot!.contains(target);
|
||||
if (clickedWithinShadowRoot) {
|
||||
firstInteractive.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const showDetail = detail !== undefined ? detail : mode === 'ios' && clickable;
|
||||
this.itemStyles.forEach((value) => {
|
||||
Object.assign(childStyles, value);
|
||||
@@ -413,6 +456,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
|
||||
[`item-lines-${lines}`]: lines !== undefined,
|
||||
[`item-fill-${fillValue}`]: true,
|
||||
[`item-shape-${shape}`]: shape !== undefined,
|
||||
'item-has-interactive-control': firstInteractive !== undefined,
|
||||
'item-disabled': disabled,
|
||||
'in-list': inList,
|
||||
'item-multiple-inputs': this.multipleInputs,
|
||||
|
||||