Merge branch 'main' into rebase-next-after-8.4-release

This commit is contained in:
Tanner Reits
2024-11-04 17:33:10 -05:00
89 changed files with 2163 additions and 201 deletions

View File

@ -5,7 +5,7 @@ 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 { actionSheetController, alertController, popoverController } from '@utils/overlays';
import { actionSheetController, alertController, popoverController, modalController } from '@utils/overlays';
import type { OverlaySelect } from '@utils/overlays-interface';
import { isRTL } from '@utils/rtl';
import { createColorClasses, hostContext } from '@utils/theme';
@ -21,6 +21,7 @@ import type {
CssClassMap,
PopoverOptions,
StyleEventDetail,
ModalOptions,
} from '../../interface';
import type { ActionSheetButton } from '../action-sheet/action-sheet-interface';
import type { AlertInput } from '../alert/alert-interface';
@ -102,15 +103,15 @@ export class Select implements ComponentInterface {
@Prop() fill?: 'outline' | 'solid';
/**
* The interface the select should use: `action-sheet`, `popover` or `alert`.
* The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`.
*/
@Prop() interface: SelectInterface = 'alert';
/**
* Any additional options that the `alert`, `action-sheet` or `popover` interface
* can take. See the [ion-alert docs](./alert), the
* [ion-action-sheet docs](./action-sheet) and the
* [ion-popover docs](./popover) for the
* [ion-action-sheet docs](./action-sheet), the
* [ion-popover docs](./popover), and the [ion-modal docs](./modal) for the
* create options for each interface.
*
* Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface.
@ -322,9 +323,9 @@ export class Select implements ComponentInterface {
await overlay.present();
// focus selected option for popovers
if (this.interface === 'popover') {
const indexOfSelected = this.childOpts.map((o) => o.value).indexOf(this.value);
// focus selected option for popovers and modals
if (this.interface === 'popover' || this.interface === 'modal') {
const indexOfSelected = this.childOpts.findIndex((o) => o.value === this.value);
if (indexOfSelected > -1) {
const selectedItem = overlay.querySelector<HTMLElement>(
@ -332,8 +333,6 @@ export class Select implements ComponentInterface {
);
if (selectedItem) {
focusVisibleElement(selectedItem);
/**
* Browsers such as Firefox do not
* correctly delegate focus when manually
@ -345,10 +344,17 @@ export class Select implements ComponentInterface {
* we only need to worry about those two components
* when focusing.
*/
const interactiveEl = selectedItem.querySelector<HTMLElement>('ion-radio, ion-checkbox');
const interactiveEl = selectedItem.querySelector<HTMLElement>('ion-radio, ion-checkbox') as
| HTMLIonRadioElement
| HTMLIonCheckboxElement
| null;
if (interactiveEl) {
interactiveEl.focus();
// Needs to be called before `focusVisibleElement` to prevent issue with focus event bubbling
// and removing `ion-focused` style
interactiveEl.setFocus();
}
focusVisibleElement(selectedItem);
}
} else {
/**
@ -356,14 +362,18 @@ export class Select implements ComponentInterface {
*/
const firstEnabledOption = overlay.querySelector<HTMLElement>(
'ion-radio:not(.radio-disabled), ion-checkbox:not(.checkbox-disabled)'
);
if (firstEnabledOption) {
focusVisibleElement(firstEnabledOption.closest('ion-item')!);
) as HTMLIonRadioElement | HTMLIonCheckboxElement | null;
if (firstEnabledOption) {
/**
* Focus the option for the same reason as we do above.
*
* Needs to be called before `focusVisibleElement` to prevent issue with focus event bubbling
* and removing `ion-focused` style
*/
firstEnabledOption.focus();
firstEnabledOption.setFocus();
focusVisibleElement(firstEnabledOption.closest('ion-item')!);
}
}
}
@ -393,6 +403,9 @@ export class Select implements ComponentInterface {
if (selectInterface === 'popover') {
return this.openPopover(ev!);
}
if (selectInterface === 'modal') {
return this.openModal();
}
return this.openAlert();
}
@ -410,7 +423,13 @@ export class Select implements ComponentInterface {
case 'popover':
const popover = overlay.querySelector('ion-select-popover');
if (popover) {
popover.options = this.createPopoverOptions(childOpts, value);
popover.options = this.createOverlaySelectOptions(childOpts, value);
}
break;
case 'modal':
const modal = overlay.querySelector('ion-select-modal');
if (modal) {
modal.options = this.createOverlaySelectOptions(childOpts, value);
}
break;
case 'alert':
@ -479,7 +498,7 @@ export class Select implements ComponentInterface {
return alertInputs;
}
private createPopoverOptions(data: HTMLIonSelectOptionElement[], selectValue: any): SelectPopoverOption[] {
private createOverlaySelectOptions(data: HTMLIonSelectOptionElement[], selectValue: any): SelectPopoverOption[] {
const popoverOptions = data.map((option) => {
const value = getOptionValue(option);
@ -557,7 +576,7 @@ export class Select implements ComponentInterface {
message: interfaceOptions.message,
multiple,
value,
options: this.createPopoverOptions(this.childOpts, value),
options: this.createOverlaySelectOptions(this.childOpts, value),
},
};
@ -651,6 +670,40 @@ export class Select implements ComponentInterface {
return alertController.create(alertOpts);
}
private openModal() {
const { multiple, value, interfaceOptions } = this;
const theme = getIonTheme(this);
const modalOpts: ModalOptions = {
...interfaceOptions,
mode: theme,
cssClass: ['select-modal', interfaceOptions.cssClass],
component: 'ion-select-modal',
componentProps: {
header: interfaceOptions.header,
multiple,
value,
options: this.createOverlaySelectOptions(this.childOpts, value),
},
};
/**
* Workaround for Stencil to autodefine
* ion-select-modal and ion-modal when
* using Custom Elements build.
*/
// eslint-disable-next-line
if (false) {
// eslint-disable-next-line
// @ts-ignore
document.createElement('ion-select-modal');
document.createElement('ion-modal');
}
return modalController.create(modalOpts);
}
/**
* Close the select interface.
*/