diff --git a/core/api.txt b/core/api.txt index 201c86c28d0..70c32153f68 100644 --- a/core/api.txt +++ b/core/api.txt @@ -1695,6 +1695,7 @@ ion-select,part,supporting-text ion-select,part,text ion-select-modal,scoped +ion-select-modal,prop,closeText,string | undefined,undefined,false,false ion-select-modal,prop,header,string | undefined,undefined,false,false ion-select-modal,prop,multiple,boolean | undefined,undefined,false,false ion-select-modal,prop,options,SelectModalOption[],[],false,false diff --git a/core/src/components.d.ts b/core/src/components.d.ts index c458b851506..6a13ab518ca 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -2862,6 +2862,7 @@ export namespace Components { "value"?: any | null; } interface IonSelectModal { + "cancelText"?: string; "header"?: string; "multiple"?: boolean; "options": SelectModalOption[]; @@ -7742,6 +7743,7 @@ declare namespace LocalJSX { "value"?: any | null; } interface IonSelectModal { + "cancelText"?: string; "header"?: string; "multiple"?: boolean; "options"?: SelectModalOption[]; diff --git a/core/src/components/select-modal/select-modal.tsx b/core/src/components/select-modal/select-modal.tsx index 5925d209640..a5447be1d8d 100644 --- a/core/src/components/select-modal/select-modal.tsx +++ b/core/src/components/select-modal/select-modal.tsx @@ -23,6 +23,8 @@ export class SelectModal implements ComponentInterface { @Prop() header?: string; + @Prop() cancelText?: string; + @Prop() multiple?: boolean; @Prop() options: SelectModalOption[] = []; @@ -149,7 +151,7 @@ export class SelectModal implements ComponentInterface { {this.header !== undefined && {this.header}} - this.closeModal()}>Close + this.closeModal()}>{this.cancelText || 'Close'} diff --git a/core/src/components/select-modal/test/custom-close-text/index.html b/core/src/components/select-modal/test/custom-close-text/index.html new file mode 100644 index 00000000000..ba329f09300 --- /dev/null +++ b/core/src/components/select-modal/test/custom-close-text/index.html @@ -0,0 +1,40 @@ + + + + + Select - Modal + + + + + + + + + + + + + Select Modal - Custom Close Text + + + + + + + + + + + + + diff --git a/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts new file mode 100644 index 00000000000..f78f5eb6a82 --- /dev/null +++ b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts @@ -0,0 +1,101 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +import type { SelectModalOption } from '../../select-modal-interface'; +import { SelectModalPage } from '../fixtures'; + +const options: SelectModalOption[] = [ + { value: 'apple', text: 'Apple', disabled: false, checked: false }, + { value: 'banana', text: 'Banana', disabled: false, checked: false }, +]; + +const checkedOptions: SelectModalOption[] = [ + { value: 'apple', text: 'Apple', disabled: false, checked: true }, + { value: 'banana', text: 'Banana', disabled: false, checked: false }, +]; + +/** + * This behavior does not vary across modes/directions. + */ +configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('select-modal: custom-close-text'), () => { + test.beforeEach(({ browserName }) => { + test.skip(browserName === 'webkit', 'ROU-5437'); + }); + + test.describe('single selection', () => { + let selectModalPage: SelectModalPage; + + test.beforeEach(async ({ page }) => { + selectModalPage = new SelectModalPage(page); + }); + + test('clicking an unselected option should dismiss the modal', async () => { + await selectModalPage.setup(config, options, false); + + await selectModalPage.clickOption('apple'); + await selectModalPage.ionModalDidDismiss.next(); + await expect(selectModalPage.modal).not.toBeVisible(); + }); + + test('clicking a selected option should dismiss the modal', async () => { + await selectModalPage.setup(config, checkedOptions, false); + + await selectModalPage.clickOption('apple'); + await selectModalPage.ionModalDidDismiss.next(); + await expect(selectModalPage.modal).not.toBeVisible(); + }); + + test('pressing Space on an unselected option should dismiss the modal', async () => { + await selectModalPage.setup(config, options, false); + + await selectModalPage.pressSpaceOnOption('apple'); + await selectModalPage.ionModalDidDismiss.next(); + await expect(selectModalPage.modal).not.toBeVisible(); + }); + + test('pressing Space on a selected option should dismiss the modal', async ({ browserName }) => { + test.skip(browserName === 'firefox', 'Same behavior as ROU-5437'); + + await selectModalPage.setup(config, checkedOptions, false); + + await selectModalPage.pressSpaceOnOption('apple'); + await selectModalPage.ionModalDidDismiss.next(); + await expect(selectModalPage.modal).not.toBeVisible(); + }); + + test('clicking the close button should dismiss the modal', async () => { + await selectModalPage.setup(config, options, false); + + const closeButton = selectModalPage.modal.locator('ion-header ion-toolbar ion-button'); + await closeButton.click(); + await selectModalPage.ionModalDidDismiss.next(); + await expect(selectModalPage.modal).not.toBeVisible(); + }); + }); + }); +}); + +/** + * This behavior does not vary across directions. + * The components used inside of `ion-select-modal` + * do have RTL logic, but those are tested in their + * respective component test files. + */ +configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('select-modal: rendering'), () => { + let selectModalPage: SelectModalPage; + + test.beforeEach(async ({ page }) => { + selectModalPage = new SelectModalPage(page); + }); + test('should not have visual regressions with single selection', async () => { + await selectModalPage.setup(config, checkedOptions, false); + await selectModalPage.screenshot(screenshot, 'select-modal-diff'); + }); + test('should not have visual regressions with multiple selection', async () => { + await selectModalPage.setup(config, checkedOptions, true); + await selectModalPage.screenshot(screenshot, 'select-modal-multiple-diff'); + }); + }); +}); diff --git a/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Chrome-darwin.png b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Chrome-darwin.png new file mode 100644 index 00000000000..863cbde10a9 Binary files /dev/null and b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Chrome-darwin.png differ diff --git a/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Firefox-darwin.png b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Firefox-darwin.png new file mode 100644 index 00000000000..42a65eb1fa8 Binary files /dev/null and b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Firefox-darwin.png differ diff --git a/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Safari-darwin.png b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Safari-darwin.png new file mode 100644 index 00000000000..9297c6904dc Binary files /dev/null and b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Safari-darwin.png differ diff --git a/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Chrome-darwin.png b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Chrome-darwin.png new file mode 100644 index 00000000000..f214fbb6579 Binary files /dev/null and b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Chrome-darwin.png differ diff --git a/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Firefox-darwin.png b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Firefox-darwin.png new file mode 100644 index 00000000000..1701959edf2 Binary files /dev/null and b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Firefox-darwin.png differ diff --git a/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Safari-darwin.png b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Safari-darwin.png new file mode 100644 index 00000000000..2ae98a5096c Binary files /dev/null and b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Safari-darwin.png differ diff --git a/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Chrome-darwin.png b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Chrome-darwin.png new file mode 100644 index 00000000000..4ab59e44a7b Binary files /dev/null and b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Chrome-darwin.png differ diff --git a/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Firefox-darwin.png b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Firefox-darwin.png new file mode 100644 index 00000000000..fb230741df1 Binary files /dev/null and b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Firefox-darwin.png differ diff --git a/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Safari-darwin.png b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Safari-darwin.png new file mode 100644 index 00000000000..c7e0b5836e2 Binary files /dev/null and b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Safari-darwin.png differ diff --git a/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Chrome-darwin.png b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Chrome-darwin.png new file mode 100644 index 00000000000..3e58df9f4d8 Binary files /dev/null and b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Chrome-darwin.png differ diff --git a/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Firefox-darwin.png b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Firefox-darwin.png new file mode 100644 index 00000000000..2989c08534f Binary files /dev/null and b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Firefox-darwin.png differ diff --git a/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Safari-darwin.png b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Safari-darwin.png new file mode 100644 index 00000000000..9914a5cdf0f Binary files /dev/null and b/core/src/components/select-modal/test/custom-close-text/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Safari-darwin.png differ diff --git a/core/src/components/select-modal/test/fixtures.ts b/core/src/components/select-modal/test/fixtures.ts index 2058848aa84..ef5c39ecaf6 100644 --- a/core/src/components/select-modal/test/fixtures.ts +++ b/core/src/components/select-modal/test/fixtures.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { E2EPage, E2ELocator, EventSpy, E2EPageOptions, ScreenshotFn } from '@utils/test/playwright'; +import type { E2ELocator, E2EPage, E2EPageOptions, EventSpy, ScreenshotFn } from '@utils/test/playwright'; import type { SelectModalOption } from '../select-modal-interface'; @@ -31,6 +31,7 @@ export class SelectModalPage { const selectModal = document.querySelector('ion-select-modal'); selectModal.options = ${JSON.stringify(options)}; selectModal.multiple = ${multiple}; + selectModal.cancelText = 'Close me'; `, config diff --git a/core/src/components/select/select.tsx b/core/src/components/select/select.tsx index 7fc456092db..f32ddc08de7 100644 --- a/core/src/components/select/select.tsx +++ b/core/src/components/select/select.tsx @@ -1,9 +1,9 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core'; -import { Component, Element, Event, Host, Method, Prop, State, Watch, h, forceUpdate } from '@stencil/core'; +import { Component, Element, Event, Host, Method, Prop, State, Watch, forceUpdate, h } from '@stencil/core'; import type { NotchController } from '@utils/forms'; import { compareOptions, createNotchController, isOptionSelected } from '@utils/forms'; -import { focusVisibleElement, renderHiddenInput, inheritAttributes } from '@utils/helpers'; import type { Attributes } from '@utils/helpers'; +import { focusVisibleElement, inheritAttributes, renderHiddenInput } from '@utils/helpers'; import { printIonWarning } from '@utils/logging'; import { actionSheetController, alertController, popoverController, modalController } from '@utils/overlays'; import type { OverlaySelect } from '@utils/overlays-interface'; @@ -18,15 +18,15 @@ import type { AlertOptions, Color, CssClassMap, + ModalOptions, PopoverOptions, StyleEventDetail, - ModalOptions, } from '../../interface'; import type { ActionSheetButton } from '../action-sheet/action-sheet-interface'; import type { AlertInput } from '../alert/alert-interface'; import type { SelectPopoverOption } from '../select-popover/select-popover-interface'; -import type { SelectChangeEventDetail, SelectInterface, SelectCompareFn } from './select-interface'; +import type { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from './select-interface'; // TODO(FW-2832): types @@ -735,6 +735,7 @@ export class Select implements ComponentInterface { component: 'ion-select-modal', componentProps: { header: interfaceOptions.header, + cancelText: this.cancelText, multiple, value, options: this.createOverlaySelectOptions(this.childOpts, value), diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index cfa828e322c..90c2e0ab7c4 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -2109,14 +2109,14 @@ This event will not emit when programmatically setting the `value` property. @ProxyCmp({ - inputs: ['header', 'multiple', 'options'] + inputs: ['cancelText', 'header', 'multiple', 'options'] }) @Component({ selector: 'ion-select-modal', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['header', 'multiple', 'options'], + inputs: ['cancelText', 'header', 'multiple', 'options'], }) export class IonSelectModal { protected el: HTMLIonSelectModalElement; diff --git a/packages/angular/standalone/src/directives/proxies.ts b/packages/angular/standalone/src/directives/proxies.ts index c7d111f7bef..b71349908d8 100644 --- a/packages/angular/standalone/src/directives/proxies.ts +++ b/packages/angular/standalone/src/directives/proxies.ts @@ -1901,14 +1901,14 @@ export declare interface IonSegmentView extends Components.IonSegmentView { @ProxyCmp({ defineCustomElementFn: defineIonSelectModal, - inputs: ['header', 'multiple', 'options'] + inputs: ['cancelText', 'header', 'multiple', 'options'] }) @Component({ selector: 'ion-select-modal', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['header', 'multiple', 'options'], + inputs: ['cancelText', 'header', 'multiple', 'options'], standalone: true }) export class IonSelectModal { diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts index 97270298bb4..da71535c252 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -910,6 +910,7 @@ export const IonSelect: StencilVueComponent = /*@__PURE__*/ defineContainer('ion-select-modal', defineIonSelectModal, [ 'header', + 'cancelText', 'multiple', 'options' ]);