mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-19 19:57:22 +08:00
chore(): sync feature-6.1 with main
This commit is contained in:
@ -1,13 +1,26 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, State, Watch, h } from '@stencil/core';
|
||||
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||
import { Component, Element, Event, Host, Method, Prop, State, Watch, h } from '@stencil/core';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { ActionSheetButton, ActionSheetOptions, AlertInput, AlertOptions, CssClassMap, OverlaySelect, PopoverOptions, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, StyleEventDetail } from '../../interface';
|
||||
import type {
|
||||
ActionSheetButton,
|
||||
ActionSheetOptions,
|
||||
AlertInput,
|
||||
AlertOptions,
|
||||
CssClassMap,
|
||||
OverlaySelect,
|
||||
PopoverOptions,
|
||||
SelectChangeEventDetail,
|
||||
SelectInterface,
|
||||
SelectPopoverOption,
|
||||
StyleEventDetail,
|
||||
} from '../../interface';
|
||||
import { findItemLabel, focusElement, getAriaLabel, renderHiddenInput } from '../../utils/helpers';
|
||||
import { actionSheetController, alertController, popoverController } from '../../utils/overlays';
|
||||
import { hostContext } from '../../utils/theme';
|
||||
import { watchForOptions } from '../../utils/watch-options';
|
||||
|
||||
import { SelectCompareFn } from './select-interface';
|
||||
import type { SelectCompareFn } from './select-interface';
|
||||
|
||||
/**
|
||||
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
||||
@ -20,12 +33,11 @@ import { SelectCompareFn } from './select-interface';
|
||||
tag: 'ion-select',
|
||||
styleUrls: {
|
||||
ios: 'select.ios.scss',
|
||||
md: 'select.md.scss'
|
||||
md: 'select.md.scss',
|
||||
},
|
||||
shadow: true
|
||||
shadow: true,
|
||||
})
|
||||
export class Select implements ComponentInterface {
|
||||
|
||||
private inputId = `ion-sel-${selectIds++}`;
|
||||
private overlay?: OverlaySelect;
|
||||
private didInit = false;
|
||||
@ -78,9 +90,9 @@ export class Select implements ComponentInterface {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* can take. See the [ion-alert docs](./alert), the
|
||||
* [ion-action-sheet docs](./action-sheet) and the
|
||||
* [ion-popover docs](./popover) for the
|
||||
* create options for each interface.
|
||||
*
|
||||
* Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface.
|
||||
@ -176,7 +188,7 @@ export class Select implements ComponentInterface {
|
||||
if (this.disabled || this.isExpanded) {
|
||||
return undefined;
|
||||
}
|
||||
const overlay = this.overlay = await this.createOverlay(event);
|
||||
const overlay = (this.overlay = await this.createOverlay(event));
|
||||
this.isExpanded = true;
|
||||
overlay.onDidDismiss().then(() => {
|
||||
this.overlay = undefined;
|
||||
@ -189,9 +201,11 @@ export class Select implements ComponentInterface {
|
||||
|
||||
// focus selected option for popovers
|
||||
if (this.interface === 'popover') {
|
||||
let indexOfSelected = this.childOpts.map(o => o.value).indexOf(this.value);
|
||||
let indexOfSelected = this.childOpts.map((o) => o.value).indexOf(this.value);
|
||||
indexOfSelected = indexOfSelected > -1 ? indexOfSelected : 0; // default to first option if nothing selected
|
||||
const selectedEl = overlay.querySelector<HTMLElement>(`.select-interface-option:nth-child(${indexOfSelected + 1})`);
|
||||
const selectedEl = overlay.querySelector<HTMLElement>(
|
||||
`.select-interface-option:nth-child(${indexOfSelected + 1})`
|
||||
);
|
||||
if (selectedEl) {
|
||||
focusElement(selectedEl);
|
||||
}
|
||||
@ -203,12 +217,16 @@ export class Select implements ComponentInterface {
|
||||
private createOverlay(ev?: UIEvent): Promise<OverlaySelect> {
|
||||
let selectInterface = this.interface;
|
||||
if (selectInterface === 'action-sheet' && this.multiple) {
|
||||
console.warn(`Select interface cannot be "${selectInterface}" with a multi-value select. Using the "alert" interface instead.`);
|
||||
console.warn(
|
||||
`Select interface cannot be "${selectInterface}" with a multi-value select. Using the "alert" interface instead.`
|
||||
);
|
||||
selectInterface = 'alert';
|
||||
}
|
||||
|
||||
if (selectInterface === 'popover' && !ev) {
|
||||
console.warn(`Select interface cannot be a "${selectInterface}" without passing an event. Using the "alert" interface instead.`);
|
||||
console.warn(
|
||||
`Select interface cannot be a "${selectInterface}" without passing an event. Using the "alert" interface instead.`
|
||||
);
|
||||
selectInterface = 'alert';
|
||||
}
|
||||
|
||||
@ -222,7 +240,7 @@ export class Select implements ComponentInterface {
|
||||
}
|
||||
|
||||
private updateOverlayOptions(): void {
|
||||
const overlay = (this.overlay as any);
|
||||
const overlay = this.overlay as any;
|
||||
if (!overlay) {
|
||||
return;
|
||||
}
|
||||
@ -239,27 +257,29 @@ export class Select implements ComponentInterface {
|
||||
}
|
||||
break;
|
||||
case 'alert':
|
||||
const inputType = (this.multiple ? 'checkbox' : 'radio');
|
||||
const inputType = this.multiple ? 'checkbox' : 'radio';
|
||||
overlay.inputs = this.createAlertInputs(childOpts, inputType, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private createActionSheetButtons(data: HTMLIonSelectOptionElement[], selectValue: any): ActionSheetButton[] {
|
||||
const actionSheetButtons = data.map(option => {
|
||||
const actionSheetButtons = data.map((option) => {
|
||||
const value = getOptionValue(option);
|
||||
|
||||
// Remove hydrated before copying over classes
|
||||
const copyClasses = Array.from(option.classList).filter(cls => cls !== 'hydrated').join(' ');
|
||||
const copyClasses = Array.from(option.classList)
|
||||
.filter((cls) => cls !== 'hydrated')
|
||||
.join(' ');
|
||||
const optClass = `${OPTION_CLASS} ${copyClasses}`;
|
||||
|
||||
return {
|
||||
role: (isOptionSelected(selectValue, value, this.compareWith) ? 'selected' : ''),
|
||||
role: isOptionSelected(selectValue, value, this.compareWith) ? 'selected' : '',
|
||||
text: option.textContent,
|
||||
cssClass: optClass,
|
||||
handler: () => {
|
||||
this.value = value;
|
||||
}
|
||||
},
|
||||
} as ActionSheetButton;
|
||||
});
|
||||
|
||||
@ -269,18 +289,24 @@ export class Select implements ComponentInterface {
|
||||
role: 'cancel',
|
||||
handler: () => {
|
||||
this.ionCancel.emit();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return actionSheetButtons;
|
||||
}
|
||||
|
||||
private createAlertInputs(data: HTMLIonSelectOptionElement[], inputType: 'checkbox' | 'radio', selectValue: any): AlertInput[] {
|
||||
const alertInputs = data.map(option => {
|
||||
private createAlertInputs(
|
||||
data: HTMLIonSelectOptionElement[],
|
||||
inputType: 'checkbox' | 'radio',
|
||||
selectValue: any
|
||||
): AlertInput[] {
|
||||
const alertInputs = data.map((option) => {
|
||||
const value = getOptionValue(option);
|
||||
|
||||
// Remove hydrated before copying over classes
|
||||
const copyClasses = Array.from(option.classList).filter(cls => cls !== 'hydrated').join(' ');
|
||||
const copyClasses = Array.from(option.classList)
|
||||
.filter((cls) => cls !== 'hydrated')
|
||||
.join(' ');
|
||||
const optClass = `${OPTION_CLASS} ${copyClasses}`;
|
||||
|
||||
return {
|
||||
@ -289,7 +315,7 @@ export class Select implements ComponentInterface {
|
||||
label: option.textContent || '',
|
||||
value,
|
||||
checked: isOptionSelected(selectValue, value, this.compareWith),
|
||||
disabled: option.disabled
|
||||
disabled: option.disabled,
|
||||
};
|
||||
});
|
||||
|
||||
@ -297,11 +323,13 @@ export class Select implements ComponentInterface {
|
||||
}
|
||||
|
||||
private createPopoverOptions(data: HTMLIonSelectOptionElement[], selectValue: any): SelectPopoverOption[] {
|
||||
const popoverOptions = data.map(option => {
|
||||
const popoverOptions = data.map((option) => {
|
||||
const value = getOptionValue(option);
|
||||
|
||||
// Remove hydrated before copying over classes
|
||||
const copyClasses = Array.from(option.classList).filter(cls => cls !== 'hydrated').join(' ');
|
||||
const copyClasses = Array.from(option.classList)
|
||||
.filter((cls) => cls !== 'hydrated')
|
||||
.join(' ');
|
||||
const optClass = `${OPTION_CLASS} ${copyClasses}`;
|
||||
|
||||
return {
|
||||
@ -315,7 +343,7 @@ export class Select implements ComponentInterface {
|
||||
if (!this.multiple) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@ -341,9 +369,9 @@ export class Select implements ComponentInterface {
|
||||
event = {
|
||||
...ev,
|
||||
detail: {
|
||||
ionShadowTarget: item
|
||||
}
|
||||
}
|
||||
ionShadowTarget: item,
|
||||
},
|
||||
};
|
||||
size = 'cover';
|
||||
}
|
||||
|
||||
@ -363,8 +391,8 @@ export class Select implements ComponentInterface {
|
||||
message: interfaceOptions.message,
|
||||
multiple,
|
||||
value,
|
||||
options: this.createPopoverOptions(this.childOpts, value)
|
||||
}
|
||||
options: this.createPopoverOptions(this.childOpts, value),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@ -372,8 +400,9 @@ export class Select implements ComponentInterface {
|
||||
* ion-select-popover and ion-popover when
|
||||
* using Custom Elements build.
|
||||
*/
|
||||
// tslint:disable-next-line
|
||||
// eslint-disable-next-line
|
||||
if (false) {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
document.createElement('ion-select-popover');
|
||||
document.createElement('ion-popover');
|
||||
@ -390,7 +419,7 @@ export class Select implements ComponentInterface {
|
||||
...interfaceOptions,
|
||||
|
||||
buttons: this.createActionSheetButtons(this.childOpts, this.value),
|
||||
cssClass: ['select-action-sheet', interfaceOptions.cssClass]
|
||||
cssClass: ['select-action-sheet', interfaceOptions.cssClass],
|
||||
};
|
||||
|
||||
/**
|
||||
@ -398,8 +427,9 @@ export class Select implements ComponentInterface {
|
||||
* ion-action-sheet when
|
||||
* using Custom Elements build.
|
||||
*/
|
||||
// tslint:disable-next-line
|
||||
// eslint-disable-next-line
|
||||
if (false) {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
document.createElement('ion-action-sheet');
|
||||
}
|
||||
@ -409,10 +439,10 @@ export class Select implements ComponentInterface {
|
||||
|
||||
private async openAlert() {
|
||||
const label = this.getLabel();
|
||||
const labelText = (label) ? label.textContent : null;
|
||||
const labelText = label ? label.textContent : null;
|
||||
|
||||
const interfaceOptions = this.interfaceOptions;
|
||||
const inputType = (this.multiple ? 'checkbox' : 'radio');
|
||||
const inputType = this.multiple ? 'checkbox' : 'radio';
|
||||
const mode = getIonMode(this);
|
||||
|
||||
const alertOpts: AlertOptions = {
|
||||
@ -427,17 +457,20 @@ export class Select implements ComponentInterface {
|
||||
role: 'cancel',
|
||||
handler: () => {
|
||||
this.ionCancel.emit();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
text: this.okText,
|
||||
handler: (selectedValues: any) => {
|
||||
this.value = selectedValues;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
cssClass: [
|
||||
'select-alert',
|
||||
interfaceOptions.cssClass,
|
||||
this.multiple ? 'multiple-select-alert' : 'single-select-alert',
|
||||
],
|
||||
cssClass: ['select-alert', interfaceOptions.cssClass,
|
||||
(this.multiple ? 'multiple-select-alert' : 'single-select-alert')]
|
||||
};
|
||||
|
||||
/**
|
||||
@ -445,8 +478,9 @@ export class Select implements ComponentInterface {
|
||||
* ion-alert when
|
||||
* using Custom Elements build.
|
||||
*/
|
||||
// tslint:disable-next-line
|
||||
// eslint-disable-next-line
|
||||
if (false) {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
document.createElement('ion-alert');
|
||||
}
|
||||
@ -493,9 +527,9 @@ export class Select implements ComponentInterface {
|
||||
|
||||
private emitStyle() {
|
||||
this.ionStyle.emit({
|
||||
'interactive': true,
|
||||
interactive: true,
|
||||
'interactive-disabled': this.disabled,
|
||||
'select': true,
|
||||
select: true,
|
||||
'select-disabled': this.disabled,
|
||||
'has-placeholder': this.placeholder !== undefined,
|
||||
'has-value': this.hasValue(),
|
||||
@ -506,15 +540,15 @@ export class Select implements ComponentInterface {
|
||||
private onClick = (ev: UIEvent) => {
|
||||
this.setFocus();
|
||||
this.open(ev);
|
||||
}
|
||||
};
|
||||
|
||||
private onFocus = () => {
|
||||
this.ionFocus.emit();
|
||||
}
|
||||
};
|
||||
|
||||
private onBlur = () => {
|
||||
this.ionBlur.emit();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { disabled, el, inputId, isExpanded, name, placeholder, value } = this;
|
||||
@ -534,7 +568,7 @@ export class Select implements ComponentInterface {
|
||||
|
||||
const selectTextClasses: CssClassMap = {
|
||||
'select-text': true,
|
||||
'select-placeholder': addPlaceholderClass
|
||||
'select-placeholder': addPlaceholderClass,
|
||||
};
|
||||
|
||||
const textPart = addPlaceholderClass ? 'placeholder' : 'text';
|
||||
@ -543,9 +577,8 @@ export class Select implements ComponentInterface {
|
||||
// current value (or placeholder) and a comma so it separates
|
||||
// nicely when the screen reader announces it, otherwise just
|
||||
// announce the value / placeholder
|
||||
const displayLabel = labelText !== undefined
|
||||
? (selectText !== '' ? `${selectText}, ${labelText}` : labelText)
|
||||
: selectText;
|
||||
const displayLabel =
|
||||
labelText !== undefined ? (selectText !== '' ? `${selectText}, ${labelText}` : labelText) : selectText;
|
||||
|
||||
return (
|
||||
<Host
|
||||
@ -558,7 +591,7 @@ export class Select implements ComponentInterface {
|
||||
[mode]: true,
|
||||
'in-item': hostContext('ion-item', el),
|
||||
'select-disabled': disabled,
|
||||
'select-expanded': isExpanded
|
||||
'select-expanded': isExpanded,
|
||||
}}
|
||||
>
|
||||
<div aria-hidden="true" class={selectTextClasses} part={textPart}>
|
||||
@ -567,9 +600,7 @@ export class Select implements ComponentInterface {
|
||||
<div class="select-icon" role="presentation" part="icon">
|
||||
<div class="select-icon-inner"></div>
|
||||
</div>
|
||||
<label id={labelId}>
|
||||
{displayLabel}
|
||||
</label>
|
||||
<label id={labelId}>{displayLabel}</label>
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
@ -579,19 +610,23 @@ export class Select implements ComponentInterface {
|
||||
aria-expanded={`${isExpanded}`}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
ref={(focusEl => this.focusEl = focusEl)}
|
||||
ref={(focusEl) => (this.focusEl = focusEl)}
|
||||
></button>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const isOptionSelected = (currentValue: any[] | any, compareValue: any, compareWith?: string | SelectCompareFn | null) => {
|
||||
const isOptionSelected = (
|
||||
currentValue: any[] | any,
|
||||
compareValue: any,
|
||||
compareWith?: string | SelectCompareFn | null
|
||||
) => {
|
||||
if (currentValue === undefined) {
|
||||
return false;
|
||||
}
|
||||
if (Array.isArray(currentValue)) {
|
||||
return currentValue.some(val => compareOptions(val, compareValue, compareWith));
|
||||
return currentValue.some((val) => compareOptions(val, compareValue, compareWith));
|
||||
} else {
|
||||
return compareOptions(currentValue, compareValue, compareWith);
|
||||
}
|
||||
@ -599,9 +634,7 @@ const isOptionSelected = (currentValue: any[] | any, compareValue: any, compareW
|
||||
|
||||
const getOptionValue = (el: HTMLIonSelectOptionElement) => {
|
||||
const value = el.value;
|
||||
return (value === undefined)
|
||||
? el.textContent || ''
|
||||
: value;
|
||||
return value === undefined ? el.textContent || '' : value;
|
||||
};
|
||||
|
||||
const parseValue = (value: any) => {
|
||||
@ -614,7 +647,11 @@ const parseValue = (value: any) => {
|
||||
return value.toString();
|
||||
};
|
||||
|
||||
const compareOptions = (currentValue: any, compareValue: any, compareWith?: string | SelectCompareFn | null): boolean => {
|
||||
const compareOptions = (
|
||||
currentValue: any,
|
||||
compareValue: any,
|
||||
compareWith?: string | SelectCompareFn | null
|
||||
): boolean => {
|
||||
if (typeof compareWith === 'function') {
|
||||
return compareWith(currentValue, compareValue);
|
||||
} else if (typeof compareWith === 'string') {
|
||||
@ -624,27 +661,33 @@ const compareOptions = (currentValue: any, compareValue: any, compareWith?: stri
|
||||
}
|
||||
};
|
||||
|
||||
const generateText = (opts: HTMLIonSelectOptionElement[], value: any | any[], compareWith?: string | SelectCompareFn | null) => {
|
||||
const generateText = (
|
||||
opts: HTMLIonSelectOptionElement[],
|
||||
value: any | any[],
|
||||
compareWith?: string | SelectCompareFn | null
|
||||
) => {
|
||||
if (value === undefined) {
|
||||
return '';
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
.map(v => textForValue(opts, v, compareWith))
|
||||
.filter(opt => opt !== null)
|
||||
.map((v) => textForValue(opts, v, compareWith))
|
||||
.filter((opt) => opt !== null)
|
||||
.join(', ');
|
||||
} else {
|
||||
return textForValue(opts, value, compareWith) || '';
|
||||
}
|
||||
};
|
||||
|
||||
const textForValue = (opts: HTMLIonSelectOptionElement[], value: any, compareWith?: string | SelectCompareFn | null): string | null => {
|
||||
const selectOpt = opts.find(opt => {
|
||||
const textForValue = (
|
||||
opts: HTMLIonSelectOptionElement[],
|
||||
value: any,
|
||||
compareWith?: string | SelectCompareFn | null
|
||||
): string | null => {
|
||||
const selectOpt = opts.find((opt) => {
|
||||
return compareOptions(getOptionValue(opt), value, compareWith);
|
||||
});
|
||||
return selectOpt
|
||||
? selectOpt.textContent
|
||||
: null;
|
||||
return selectOpt ? selectOpt.textContent : null;
|
||||
};
|
||||
|
||||
let selectIds = 0;
|
||||
|
Reference in New Issue
Block a user