UNPKG

@ionic/core

Version:
1,157 lines • 57.9 kB
/*! * (C) Ionic http://ionicframework.com - MIT License */ import { Host, h, forceUpdate } from "@stencil/core"; import { compareOptions, createNotchController, isOptionSelected } from "../../utils/forms/index"; import { focusVisibleElement, renderHiddenInput, inheritAttributes } from "../../utils/helpers"; import { actionSheetController, alertController, popoverController, modalController } from "../../utils/overlays"; import { isRTL } from "../../utils/rtl/index"; import { createColorClasses, hostContext } from "../../utils/theme"; import { watchForOptions } from "../../utils/watch-options"; import { caretDownSharp, chevronExpand } from "ionicons/icons"; import { getIonMode } from "../../global/ionic-global"; // TODO(FW-2832): types /** * @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use. * * @slot label - The label text to associate with the select. Use the `labelPlacement` property to control where the label is placed relative to the select. Use this if you need to render a label with custom HTML. * @slot start - Content to display at the leading edge of the select. * @slot end - Content to display at the trailing edge of the select. * * @part placeholder - The text displayed in the select when there is no value. * @part text - The displayed value of the select. * @part icon - The select icon container. * @part container - The container for the selected text or placeholder. * @part label - The label text describing the select. * @part supporting-text - Supporting text displayed beneath the select. * @part helper-text - Supporting text displayed beneath the select when the select is valid. * @part error-text - Supporting text displayed beneath the select when the select is invalid and touched. */ export class Select { constructor() { this.inputId = `ion-sel-${selectIds++}`; this.helperTextId = `${this.inputId}-helper-text`; this.errorTextId = `${this.inputId}-error-text`; this.inheritedAttributes = {}; this.onClick = (ev) => { const target = ev.target; const closestSlot = target.closest('[slot="start"], [slot="end"]'); if (target === this.el || closestSlot === null) { this.setFocus(); this.open(ev); } else { /** * Prevent clicks to the start/end slots from opening the select. * We ensure the target isn't this element in case the select is slotted * in, for example, an item. This would prevent the select from ever * being opened since the element itself has slot="start"/"end". * * Clicking a slotted element also causes a click * on the <label> element (since it wraps the slots). * Clicking <label> dispatches another click event on * the native form control that then bubbles up to this * listener. This additional event targets the host * element, so the select overlay is opened. * * When the slotted elements are clicked (and therefore * the ancestor <label> element) we want to prevent the label * from dispatching another click event. * * Do not call stopPropagation() because this will cause * click handlers on the slotted elements to never fire in React. * When developers do onClick in React a native "click" listener * is added on the root element, not the slotted element. When that * native click listener fires, React then dispatches the synthetic * click event on the slotted element. However, if stopPropagation * is called then the native click event will never bubble up * to the root element. */ ev.preventDefault(); } }; this.onFocus = () => { this.ionFocus.emit(); }; this.onBlur = () => { this.ionBlur.emit(); }; this.isExpanded = false; this.cancelText = 'Cancel'; this.color = undefined; this.compareWith = undefined; this.disabled = false; this.fill = undefined; this.errorText = undefined; this.helperText = undefined; this.interface = 'alert'; this.interfaceOptions = {}; this.justify = undefined; this.label = undefined; this.labelPlacement = 'start'; this.multiple = false; this.name = this.inputId; this.okText = 'OK'; this.placeholder = undefined; this.selectedText = undefined; this.toggleIcon = undefined; this.expandedIcon = undefined; this.shape = undefined; this.value = undefined; this.required = false; } styleChanged() { this.emitStyle(); } setValue(value) { this.value = value; this.ionChange.emit({ value }); } async connectedCallback() { const { el } = this; this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot); this.updateOverlayOptions(); this.emitStyle(); this.mutationO = watchForOptions(this.el, 'ion-select-option', async () => { this.updateOverlayOptions(); /** * We need to re-render the component * because one of the new ion-select-option * elements may match the value. In this case, * the rendered selected text should be updated. */ forceUpdate(this); }); } componentWillLoad() { this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']); } componentDidLoad() { /** * If any of the conditions that trigger the styleChanged callback * are met on component load, it is possible the event emitted * prior to a parent web component registering an event listener. * * To ensure the parent web component receives the event, we * emit the style event again after the component has loaded. * * This is often seen in Angular with the `dist` output target. */ this.emitStyle(); } disconnectedCallback() { if (this.mutationO) { this.mutationO.disconnect(); this.mutationO = undefined; } if (this.notchController) { this.notchController.destroy(); this.notchController = undefined; } } /** * Open the select overlay. The overlay is either an alert, action sheet, or popover, * depending on the `interface` property on the `ion-select`. * * @param event The user interface event that called the open. */ async open(event) { if (this.disabled || this.isExpanded) { return undefined; } this.isExpanded = true; const overlay = (this.overlay = await this.createOverlay(event)); // Add logic to scroll selected item into view before presenting const scrollSelectedIntoView = () => { const indexOfSelected = this.childOpts.findIndex((o) => o.value === this.value); if (indexOfSelected > -1) { const selectedItem = overlay.querySelector(`.select-interface-option:nth-child(${indexOfSelected + 1})`); if (selectedItem) { /** * Browsers such as Firefox do not * correctly delegate focus when manually * focusing an element with delegatesFocus. * We work around this by manually focusing * the interactive element. * ion-radio and ion-checkbox are the only * elements that ion-select-popover uses, so * we only need to worry about those two components * when focusing. */ const interactiveEl = selectedItem.querySelector('ion-radio, ion-checkbox'); if (interactiveEl) { selectedItem.scrollIntoView({ block: 'nearest' }); // Needs to be called before `focusVisibleElement` to prevent issue with focus event bubbling // and removing `ion-focused` style interactiveEl.setFocus(); } focusVisibleElement(selectedItem); } } else { /** * If no value is set then focus the first enabled option. */ const firstEnabledOption = overlay.querySelector('ion-radio:not(.radio-disabled), ion-checkbox:not(.checkbox-disabled)'); 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.setFocus(); focusVisibleElement(firstEnabledOption.closest('ion-item')); } } }; // For modals and popovers, we can scroll before they're visible if (this.interface === 'modal') { overlay.addEventListener('ionModalWillPresent', scrollSelectedIntoView, { once: true }); } else if (this.interface === 'popover') { overlay.addEventListener('ionPopoverWillPresent', scrollSelectedIntoView, { once: true }); } else { /** * For alerts and action sheets, we need to wait a frame after willPresent * because these overlays don't have their content in the DOM immediately * when willPresent fires. By waiting a frame, we ensure the content is * rendered and can be properly scrolled into view. */ const scrollAfterRender = () => { requestAnimationFrame(() => { scrollSelectedIntoView(); }); }; if (this.interface === 'alert') { overlay.addEventListener('ionAlertWillPresent', scrollAfterRender, { once: true }); } else if (this.interface === 'action-sheet') { overlay.addEventListener('ionActionSheetWillPresent', scrollAfterRender, { once: true }); } } overlay.onDidDismiss().then(() => { this.overlay = undefined; this.isExpanded = false; this.ionDismiss.emit(); this.setFocus(); }); await overlay.present(); return overlay; } createOverlay(ev) { 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.`); selectInterface = 'alert'; } if (selectInterface === 'popover' && !ev) { console.warn(`Select interface cannot be a "${selectInterface}" without passing an event. Using the "alert" interface instead.`); selectInterface = 'alert'; } if (selectInterface === 'action-sheet') { return this.openActionSheet(); } if (selectInterface === 'popover') { return this.openPopover(ev); } if (selectInterface === 'modal') { return this.openModal(); } return this.openAlert(); } updateOverlayOptions() { const overlay = this.overlay; if (!overlay) { return; } const childOpts = this.childOpts; const value = this.value; switch (this.interface) { case 'action-sheet': overlay.buttons = this.createActionSheetButtons(childOpts, value); break; case 'popover': const popover = overlay.querySelector('ion-select-popover'); if (popover) { 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': const inputType = this.multiple ? 'checkbox' : 'radio'; overlay.inputs = this.createAlertInputs(childOpts, inputType, value); break; } } createActionSheetButtons(data, selectValue) { 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 optClass = `${OPTION_CLASS} ${copyClasses}`; return { role: isOptionSelected(selectValue, value, this.compareWith) ? 'selected' : '', text: option.textContent, cssClass: optClass, handler: () => { this.setValue(value); }, }; }); // Add "cancel" button actionSheetButtons.push({ text: this.cancelText, role: 'cancel', handler: () => { this.ionCancel.emit(); }, }); return actionSheetButtons; } createAlertInputs(data, inputType, selectValue) { 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 optClass = `${OPTION_CLASS} ${copyClasses}`; return { type: inputType, cssClass: optClass, label: option.textContent || '', value, checked: isOptionSelected(selectValue, value, this.compareWith), disabled: option.disabled, }; }); return alertInputs; } createOverlaySelectOptions(data, selectValue) { 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 optClass = `${OPTION_CLASS} ${copyClasses}`; return { text: option.textContent || '', cssClass: optClass, value, checked: isOptionSelected(selectValue, value, this.compareWith), disabled: option.disabled, handler: (selected) => { this.setValue(selected); if (!this.multiple) { this.close(); } }, }; }); return popoverOptions; } async openPopover(ev) { const { fill, labelPlacement } = this; const interfaceOptions = this.interfaceOptions; const mode = getIonMode(this); const showBackdrop = mode === 'md' ? false : true; const multiple = this.multiple; const value = this.value; let event = ev; let size = 'auto'; const hasFloatingOrStackedLabel = labelPlacement === 'floating' || labelPlacement === 'stacked'; /** * The popover should take up the full width * when using a fill in MD mode or if the * label is floating/stacked. */ if (hasFloatingOrStackedLabel || (mode === 'md' && fill !== undefined)) { size = 'cover'; /** * Otherwise the popover * should be positioned relative * to the native element. */ } else { event = Object.assign(Object.assign({}, ev), { detail: { ionShadowTarget: this.nativeWrapperEl, } }); } const popoverOpts = Object.assign(Object.assign({ mode, event, alignment: 'center', size, showBackdrop }, interfaceOptions), { component: 'ion-select-popover', cssClass: ['select-popover', interfaceOptions.cssClass], componentProps: { header: interfaceOptions.header, subHeader: interfaceOptions.subHeader, message: interfaceOptions.message, multiple, value, options: this.createOverlaySelectOptions(this.childOpts, value), } }); /** * Workaround for Stencil to autodefine * ion-select-popover and ion-popover when * using Custom Elements build. */ // eslint-disable-next-line if (false) { // eslint-disable-next-line // @ts-ignore document.createElement('ion-select-popover'); document.createElement('ion-popover'); } return popoverController.create(popoverOpts); } async openActionSheet() { const mode = getIonMode(this); const interfaceOptions = this.interfaceOptions; const actionSheetOpts = Object.assign(Object.assign({ mode }, interfaceOptions), { buttons: this.createActionSheetButtons(this.childOpts, this.value), cssClass: ['select-action-sheet', interfaceOptions.cssClass] }); /** * Workaround for Stencil to autodefine * ion-action-sheet when * using Custom Elements build. */ // eslint-disable-next-line if (false) { // eslint-disable-next-line // @ts-ignore document.createElement('ion-action-sheet'); } return actionSheetController.create(actionSheetOpts); } async openAlert() { const interfaceOptions = this.interfaceOptions; const inputType = this.multiple ? 'checkbox' : 'radio'; const mode = getIonMode(this); const alertOpts = Object.assign(Object.assign({ mode }, interfaceOptions), { header: interfaceOptions.header ? interfaceOptions.header : this.labelText, inputs: this.createAlertInputs(this.childOpts, inputType, this.value), buttons: [ { text: this.cancelText, role: 'cancel', handler: () => { this.ionCancel.emit(); }, }, { text: this.okText, handler: (selectedValues) => { this.setValue(selectedValues); }, }, ], cssClass: [ 'select-alert', interfaceOptions.cssClass, this.multiple ? 'multiple-select-alert' : 'single-select-alert', ] }); /** * Workaround for Stencil to autodefine * ion-alert when * using Custom Elements build. */ // eslint-disable-next-line if (false) { // eslint-disable-next-line // @ts-ignore document.createElement('ion-alert'); } return alertController.create(alertOpts); } openModal() { const { multiple, value, interfaceOptions } = this; const mode = getIonMode(this); const modalOpts = Object.assign(Object.assign({}, interfaceOptions), { mode, 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. */ close() { if (!this.overlay) { return Promise.resolve(false); } return this.overlay.dismiss(); } hasValue() { return this.getText() !== ''; } get childOpts() { return Array.from(this.el.querySelectorAll('ion-select-option')); } /** * Returns any plaintext associated with * the label (either prop or slot). * Note: This will not return any custom * HTML. Use the `hasLabel` getter if you * want to know if any slotted label content * was passed. */ get labelText() { const { label } = this; if (label !== undefined) { return label; } const { labelSlot } = this; if (labelSlot !== null) { return labelSlot.textContent; } return; } getText() { const selectedText = this.selectedText; if (selectedText != null && selectedText !== '') { return selectedText; } return generateText(this.childOpts, this.value, this.compareWith); } setFocus() { if (this.focusEl) { this.focusEl.focus(); } } emitStyle() { const { disabled } = this; const style = { 'interactive-disabled': disabled, }; this.ionStyle.emit(style); } renderLabel() { const { label } = this; return (h("div", { class: { 'label-text-wrapper': true, 'label-text-wrapper-hidden': !this.hasLabel, }, part: "label" }, label === undefined ? h("slot", { name: "label" }) : h("div", { class: "label-text" }, label))); } componentDidRender() { var _a; (_a = this.notchController) === null || _a === void 0 ? void 0 : _a.calculateNotchWidth(); } /** * Gets any content passed into the `label` slot, * not the <slot> definition. */ get labelSlot() { return this.el.querySelector('[slot="label"]'); } /** * Returns `true` if label content is provided * either by a prop or a content. If you want * to get the plaintext value of the label use * the `labelText` getter instead. */ get hasLabel() { return this.label !== undefined || this.labelSlot !== null; } /** * Renders the border container * when fill="outline". */ renderLabelContainer() { const mode = getIonMode(this); const hasOutlineFill = mode === 'md' && this.fill === 'outline'; if (hasOutlineFill) { /** * The outline fill has a special outline * that appears around the select and the label. * Certain stacked and floating label placements cause the * label to translate up and create a "cut out" * inside of that border by using the notch-spacer element. */ return [ h("div", { class: "select-outline-container" }, h("div", { class: "select-outline-start" }), h("div", { class: { 'select-outline-notch': true, 'select-outline-notch-hidden': !this.hasLabel, } }, h("div", { class: "notch-spacer", "aria-hidden": "true", ref: (el) => (this.notchSpacerEl = el) }, this.label)), h("div", { class: "select-outline-end" })), this.renderLabel(), ]; } /** * If not using the outline style, * we can render just the label. */ return this.renderLabel(); } /** * Renders either the placeholder * or the selected values based on * the state of the select. */ renderSelectText() { const { placeholder } = this; const displayValue = this.getText(); let addPlaceholderClass = false; let selectText = displayValue; if (selectText === '' && placeholder !== undefined) { selectText = placeholder; addPlaceholderClass = true; } const selectTextClasses = { 'select-text': true, 'select-placeholder': addPlaceholderClass, }; const textPart = addPlaceholderClass ? 'placeholder' : 'text'; return (h("div", { "aria-hidden": "true", class: selectTextClasses, part: textPart }, selectText)); } /** * Renders the chevron icon * next to the select text. */ renderSelectIcon() { const mode = getIonMode(this); const { isExpanded, toggleIcon, expandedIcon } = this; let icon; if (isExpanded && expandedIcon !== undefined) { icon = expandedIcon; } else { const defaultIcon = mode === 'ios' ? chevronExpand : caretDownSharp; icon = toggleIcon !== null && toggleIcon !== void 0 ? toggleIcon : defaultIcon; } return h("ion-icon", { class: "select-icon", part: "icon", "aria-hidden": "true", icon: icon }); } get ariaLabel() { var _a; const { placeholder, inheritedAttributes } = this; const displayValue = this.getText(); // The aria label should be preferred over visible text if both are specified const definedLabel = (_a = inheritedAttributes['aria-label']) !== null && _a !== void 0 ? _a : this.labelText; /** * If developer has specified a placeholder * and there is nothing selected, the selectText * should have the placeholder value. */ let renderedLabel = displayValue; if (renderedLabel === '' && placeholder !== undefined) { renderedLabel = placeholder; } /** * If there is a developer-defined label, * then we need to concatenate the developer label * string with the current current value. * The label for the control should be read * before the values of the control. */ if (definedLabel !== undefined) { renderedLabel = renderedLabel === '' ? definedLabel : `${definedLabel}, ${renderedLabel}`; } return renderedLabel; } renderListbox() { const { disabled, inputId, isExpanded, required } = this; return (h("button", { disabled: disabled, id: inputId, "aria-label": this.ariaLabel, "aria-haspopup": "dialog", "aria-expanded": `${isExpanded}`, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === this.errorTextId, "aria-required": `${required}`, onFocus: this.onFocus, onBlur: this.onBlur, ref: (focusEl) => (this.focusEl = focusEl) })); } getHintTextID() { const { el, helperText, errorText, helperTextId, errorTextId } = this; if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) { return errorTextId; } if (helperText) { return helperTextId; } return undefined; } /** * Renders the helper text or error text values */ renderHintText() { const { helperText, errorText, helperTextId, errorTextId } = this; return [ h("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text" }, helperText), h("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text" }, errorText), ]; } /** * Responsible for rendering helper text, and error text. This element * should only be rendered if hint text is set. */ renderBottomContent() { const { helperText, errorText } = this; /** * undefined and empty string values should * be treated as not having helper/error text. */ const hasHintText = !!helperText || !!errorText; if (!hasHintText) { return; } return h("div", { class: "select-bottom" }, this.renderHintText()); } render() { const { disabled, el, isExpanded, expandedIcon, labelPlacement, justify, placeholder, fill, shape, name, value } = this; const mode = getIonMode(this); const hasFloatingOrStackedLabel = labelPlacement === 'floating' || labelPlacement === 'stacked'; const justifyEnabled = !hasFloatingOrStackedLabel && justify !== undefined; const rtl = isRTL(el) ? 'rtl' : 'ltr'; const inItem = hostContext('ion-item', this.el); const shouldRenderHighlight = mode === 'md' && fill !== 'outline' && !inItem; const hasValue = this.hasValue(); const hasStartEndSlots = el.querySelector('[slot="start"], [slot="end"]') !== null; renderHiddenInput(true, el, name, parseValue(value), disabled); /** * If the label is stacked, it should always sit above the select. * For floating labels, the label should move above the select if * the select has a value, is open, or has anything in either * the start or end slot. * * If there is content in the start slot, the label would overlap * it if not forced to float. This is also applied to the end slot * because with the default or solid fills, the select is not * vertically centered in the container, but the label is. This * causes the slots and label to appear vertically offset from each * other when the label isn't floating above the input. This doesn't * apply to the outline fill, but this was not accounted for to keep * things consistent. * * TODO(FW-5592): Remove hasStartEndSlots condition */ const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || isExpanded || hasStartEndSlots)); return (h(Host, { key: 'aa7bd7fbb6479c7805486990650a406e5470fd13', onClick: this.onClick, class: createColorClasses(this.color, { [mode]: true, 'in-item': inItem, 'in-item-color': hostContext('ion-item.ion-color', el), 'select-disabled': disabled, 'select-expanded': isExpanded, 'has-expanded-icon': expandedIcon !== undefined, 'has-value': hasValue, 'label-floating': labelShouldFloat, 'has-placeholder': placeholder !== undefined, 'ion-focusable': true, [`select-${rtl}`]: true, [`select-fill-${fill}`]: fill !== undefined, [`select-justify-${justify}`]: justifyEnabled, [`select-shape-${shape}`]: shape !== undefined, [`select-label-placement-${labelPlacement}`]: true, }) }, h("label", { key: 'fde3cdfd0ef7d1a20263e35ff4358ee7f61a789f', class: "select-wrapper", id: "select-label" }, this.renderLabelContainer(), h("div", { key: '6fb8deedc827b6be2f19f9e57a62efefaaba200f', class: "select-wrapper-inner" }, h("slot", { key: 'a57a204ea1cbd9c4bac338f14e196e780dab0a10', name: "start" }), h("div", { key: '78b83e1484a446537e038527d539d997e330cd69', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), h("slot", { key: '9fc660134e5247c4e5243c7d9d71ac6cec08705d', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && h("div", { key: '7f143285efa7fd7756dfdc5517ca33e84c8a027e', class: "select-highlight" })), this.renderBottomContent())); } static get is() { return "ion-select"; } static get encapsulation() { return "shadow"; } static get originalStyleUrls() { return { "ios": ["select.ios.scss"], "md": ["select.md.scss"] }; } static get styleUrls() { return { "ios": ["select.ios.css"], "md": ["select.md.css"] }; } static get properties() { return { "cancelText": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The text to display on the cancel button." }, "attribute": "cancel-text", "reflect": false, "defaultValue": "'Cancel'" }, "color": { "type": "string", "mutable": false, "complexType": { "original": "Color", "resolved": "\"danger\" | \"dark\" | \"light\" | \"medium\" | \"primary\" | \"secondary\" | \"success\" | \"tertiary\" | \"warning\" | string & Record<never, never> | undefined", "references": { "Color": { "location": "import", "path": "../../interface", "id": "src/interface.d.ts::Color" } } }, "required": false, "optional": true, "docs": { "tags": [], "text": "The color to use from your application's color palette.\nDefault options are: `\"primary\"`, `\"secondary\"`, `\"tertiary\"`, `\"success\"`, `\"warning\"`, `\"danger\"`, `\"light\"`, `\"medium\"`, and `\"dark\"`.\nFor more information on colors, see [theming](/docs/theming/basics).\n\nThis property is only available when using the modern select syntax." }, "attribute": "color", "reflect": true }, "compareWith": { "type": "string", "mutable": false, "complexType": { "original": "string | SelectCompareFn | null", "resolved": "((currentValue: any, compareValue: any) => boolean) | null | string | undefined", "references": { "SelectCompareFn": { "location": "import", "path": "./select-interface", "id": "src/components/select/select-interface.ts::SelectCompareFn" } } }, "required": false, "optional": true, "docs": { "tags": [], "text": "This property allows developers to specify a custom function or property\nname for comparing objects when determining the selected option in the\nion-select. When not specified, the default behavior will use strict\nequality (===) for comparison." }, "attribute": "compare-with", "reflect": false }, "disabled": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "If `true`, the user cannot interact with the select." }, "attribute": "disabled", "reflect": false, "defaultValue": "false" }, "fill": { "type": "string", "mutable": false, "complexType": { "original": "'outline' | 'solid'", "resolved": "\"outline\" | \"solid\" | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "The fill for the item. If `\"solid\"` the item will have a background. If\n`\"outline\"` the item will be transparent with a border. Only available in `md` mode." }, "attribute": "fill", "reflect": false }, "errorText": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Text that is placed under the select and displayed when an error is detected." }, "attribute": "error-text", "reflect": false }, "helperText": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Text that is placed under the select and displayed when no error is detected." }, "attribute": "helper-text", "reflect": false }, "interface": { "type": "string", "mutable": false, "complexType": { "original": "SelectInterface", "resolved": "\"action-sheet\" | \"alert\" | \"modal\" | \"popover\"", "references": { "SelectInterface": { "location": "import", "path": "./select-interface", "id": "src/components/select/select-interface.ts::SelectInterface" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`." }, "attribute": "interface", "reflect": false, "defaultValue": "'alert'" }, "interfaceOptions": { "type": "any", "mutable": false, "complexType": { "original": "any", "resolved": "any", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Any additional options that the `alert`, `action-sheet` or `popover` interface\ncan take. See the [ion-alert docs](./alert), the\n[ion-action-sheet docs](./action-sheet), the\n[ion-popover docs](./popover), and the [ion-modal docs](./modal) for the\ncreate options for each interface.\n\nNote: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface." }, "attribute": "interface-options", "reflect": false, "defaultValue": "{}" }, "justify": { "type": "string", "mutable": false, "complexType": { "original": "'start' | 'end' | 'space-between'", "resolved": "\"end\" | \"space-between\" | \"start\" | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "How to pack the label and select within a line.\n`justify` does not apply when the label and select\nare on different lines when `labelPlacement` is set to\n`\"floating\"` or `\"stacked\"`.\n`\"start\"`: The label and select will appear on the left in LTR and\non the right in RTL.\n`\"end\"`: The label and select will appear on the right in LTR and\non the left in RTL.\n`\"space-between\"`: The label and select will appear on opposite\nends of the line with space between the two elements." }, "attribute": "justify", "reflect": false }, "label": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "The visible label associated with the select.\n\nUse this if you need to render a plaintext label.\n\nThe `label` property will take priority over the `label` slot if both are used." }, "attribute": "label", "reflect": false }, "labelPlacement": { "type": "string", "mutable": false, "complexType": { "original": "'start' | 'end' | 'floating' | 'stacked' | 'fixed'", "resolved": "\"end\" | \"fixed\" | \"floating\" | \"stacked\" | \"start\" | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Where to place the label relative to the select.\n`\"start\"`: The label will appear to the left of the select in LTR and to the right in RTL.\n`\"end\"`: The label will appear to the right of the select in LTR and to the left in RTL.\n`\"floating\"`: The label will appear smaller and above the select when the select is focused or it has a value. Otherwise it will appear on top of the select.\n`\"stacked\"`: The label will appear smaller and above the select regardless even when the select is blurred or has no value.\n`\"fixed\"`: The label has the same behavior as `\"start\"` except it also has a fixed width. Long text will be truncated with ellipses (\"...\").\nWhen using `\"floating\"` or `\"stacked\"` we recommend initializing the select with either a `value` or a `placeholder`." }, "attribute": "label-placement", "reflect": false, "defaultValue": "'start'" }, "multiple": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "If `true`, the select can accept multiple values." }, "attribute": "multiple", "reflect": false, "defaultValue": "false" }, "name": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The name of the control, which is submitted with the form data." }, "attribute": "name", "reflect": false, "defaultValue": "this.inputId" }, "okText": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The text to display on the ok button." }, "attribute": "ok-text", "reflect": false, "defaultValue": "'OK'" }, "placeholder": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "The text to display when the select is empty." }, "attribute": "placeholder", "reflect": false }, "selectedText": { "type": "string", "mutable": false, "complexType": { "original": "string | null", "resolved": "null | string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "The text to display instead of the selected option's value." }, "attribute": "selected-text", "reflect": false }, "toggleIcon": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "The toggle icon to use. Defaults to `chevronExpand` for `ios` mode,\nor `caretDownSharp` for `md` mode." }, "attribute": "toggle-icon", "reflect": false }, "expandedIcon": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "The toggle icon to show when the select is open. If defined, the icon\nrotation behavior in `md` mode will be disabled. If undefined, `toggleIcon`\nwill be used for when the select is both open and closed." }, "attribute": "expanded-icon", "reflect": false }, "shape": { "type": "string", "mutable": false, "complexType": { "original": "'round'", "resolved": "\"round\" | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "The shape of the select. If \"round\" it will have an increased border radius." }, "attribute": "shape", "reflect": false }, "value": { "type": "any", "mutable": true, "complexType": { "original": "any | null", "resolved": "any", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "The value of the select." }, "attribute": "value", "reflect": false }, "required": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "reference