chore(): sync feature-6.1 with main

This commit is contained in:
Liam DeBeasi
2022-04-04 15:27:16 -04:00
940 changed files with 79771 additions and 96056 deletions

View File

@ -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;