UNPKG

@ionic/core

Version:
458 lines (456 loc) • 17.6 kB
/*! * (C) Ionic http://ionicframework.com - MIT License */ import { Host, h } from "@stencil/core"; import { printIonWarning } from "../../utils/logging/index"; import { getIonMode } from "../../global/ionic-global"; /** * @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use. */ export class AccordionGroup { constructor() { this.animated = true; this.multiple = undefined; this.value = undefined; this.disabled = false; this.readonly = false; this.expand = 'compact'; } valueChanged() { const { value, multiple } = this; if (!multiple && Array.isArray(value)) { /** * We do some processing on the `value` array so * that it looks more like an array when logged to * the console. * Example given ['a', 'b'] * Default toString() behavior: a,b * Custom behavior: ['a', 'b'] */ printIonWarning(`ion-accordion-group was passed an array of values, but multiple="false". This is incorrect usage and may result in unexpected behaviors. To dismiss this warning, pass a string to the "value" property when multiple="false". Value Passed: [${value.map((v) => `'${v}'`).join(', ')}] `, this.el); } /** * Do not use `value` here as that will be * not account for the adjustment we make above. */ this.ionValueChange.emit({ value: this.value }); } async disabledChanged() { const { disabled } = this; const accordions = await this.getAccordions(); for (const accordion of accordions) { accordion.disabled = disabled; } } async readonlyChanged() { const { readonly } = this; const accordions = await this.getAccordions(); for (const accordion of accordions) { accordion.readonly = readonly; } } async onKeydown(ev) { const activeElement = document.activeElement; if (!activeElement) { return; } /** * Make sure focus is in the header, not the body, of the accordion. This ensures * that if there are any interactable elements in the body, their keyboard * interaction doesn't get stolen by the accordion. Example: using up/down keys * in ion-textarea. */ const activeAccordionHeader = activeElement.closest('ion-accordion [slot="header"]'); if (!activeAccordionHeader) { return; } const accordionEl = activeElement.tagName === 'ION-ACCORDION' ? activeElement : activeElement.closest('ion-accordion'); if (!accordionEl) { return; } const closestGroup = accordionEl.closest('ion-accordion-group'); if (closestGroup !== this.el) { return; } // If the active accordion is not in the current array of accordions, do not do anything const accordions = await this.getAccordions(); const startingIndex = accordions.findIndex((a) => a === accordionEl); if (startingIndex === -1) { return; } let accordion; if (ev.key === 'ArrowDown') { accordion = this.findNextAccordion(accordions, startingIndex); } else if (ev.key === 'ArrowUp') { accordion = this.findPreviousAccordion(accordions, startingIndex); } else if (ev.key === 'Home') { accordion = accordions[0]; } else if (ev.key === 'End') { accordion = accordions[accordions.length - 1]; } if (accordion !== undefined && accordion !== activeElement) { accordion.focus(); } } async componentDidLoad() { if (this.disabled) { this.disabledChanged(); } if (this.readonly) { this.readonlyChanged(); } /** * When binding values in frameworks such as Angular * it is possible for the value to be set after the Web Component * initializes but before the value watcher is set up in Stencil. * As a result, the watcher callback may not be fired. * We work around this by manually calling the watcher * callback when the component has loaded and the watcher * is configured. */ this.valueChanged(); } /** * Sets the value property and emits ionChange. * This should only be called when the user interacts * with the accordion and not for any update * to the value property. The exception is when * the app sets the value of a single-select * accordion group to an array. */ setValue(accordionValue) { const value = (this.value = accordionValue); this.ionChange.emit({ value }); } /** * This method is used to ensure that the value * of ion-accordion-group is being set in a valid * way. This method should only be called in * response to a user generated action. * @internal */ async requestAccordionToggle(accordionValue, accordionExpand) { const { multiple, value, readonly, disabled } = this; if (readonly || disabled) { return; } if (accordionExpand) { /** * If group accepts multiple values * check to see if value is already in * in values array. If not, add it * to the array. */ if (multiple) { const groupValue = value !== null && value !== void 0 ? value : []; const processedValue = Array.isArray(groupValue) ? groupValue : [groupValue]; const valueExists = processedValue.find((v) => v === accordionValue); if (valueExists === undefined && accordionValue !== undefined) { this.setValue([...processedValue, accordionValue]); } } else { this.setValue(accordionValue); } } else { /** * If collapsing accordion, either filter the value * out of the values array or unset the value. */ if (multiple) { const groupValue = value !== null && value !== void 0 ? value : []; const processedValue = Array.isArray(groupValue) ? groupValue : [groupValue]; this.setValue(processedValue.filter((v) => v !== accordionValue)); } else { this.setValue(undefined); } } } findNextAccordion(accordions, startingIndex) { const nextAccordion = accordions[startingIndex + 1]; if (nextAccordion === undefined) { return accordions[0]; } return nextAccordion; } findPreviousAccordion(accordions, startingIndex) { const prevAccordion = accordions[startingIndex - 1]; if (prevAccordion === undefined) { return accordions[accordions.length - 1]; } return prevAccordion; } /** * @internal */ async getAccordions() { return Array.from(this.el.querySelectorAll(':scope > ion-accordion')); } render() { const { disabled, readonly, expand } = this; const mode = getIonMode(this); return (h(Host, { key: '82f3e77066fabb4736638ee4c487ad56efd39c63', class: { [mode]: true, 'accordion-group-disabled': disabled, 'accordion-group-readonly': readonly, [`accordion-group-expand-${expand}`]: true, }, role: "presentation" }, h("slot", { key: 'a3c791ea887fc640b512f81d429be465ae902b3d' }))); } static get is() { return "ion-accordion-group"; } static get encapsulation() { return "shadow"; } static get originalStyleUrls() { return { "ios": ["accordion-group.ios.scss"], "md": ["accordion-group.md.scss"] }; } static get styleUrls() { return { "ios": ["accordion-group.ios.css"], "md": ["accordion-group.md.css"] }; } static get properties() { return { "animated": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "If `true`, all accordions inside of the\naccordion group will animate when expanding\nor collapsing." }, "attribute": "animated", "reflect": false, "defaultValue": "true" }, "multiple": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "If `true`, the accordion group can have multiple\naccordion components expanded at the same time." }, "attribute": "multiple", "reflect": false }, "value": { "type": "string", "mutable": true, "complexType": { "original": "string | string[] | null", "resolved": "null | string | string[] | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "The value of the accordion group. This controls which\naccordions are expanded.\nThis should be an array of strings only when `multiple=\"true\"`" }, "attribute": "value", "reflect": false }, "disabled": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "If `true`, the accordion group cannot be interacted with." }, "attribute": "disabled", "reflect": false, "defaultValue": "false" }, "readonly": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "If `true`, the accordion group cannot be interacted with,\nbut does not alter the opacity." }, "attribute": "readonly", "reflect": false, "defaultValue": "false" }, "expand": { "type": "string", "mutable": false, "complexType": { "original": "'compact' | 'inset'", "resolved": "\"compact\" | \"inset\"", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Describes the expansion behavior for each accordion.\nPossible values are `\"compact\"` and `\"inset\"`.\nDefaults to `\"compact\"`." }, "attribute": "expand", "reflect": false, "defaultValue": "'compact'" } }; } static get events() { return [{ "method": "ionChange", "name": "ionChange", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted when the value property has changed as a result of a user action such as a click.\n\nThis event will not emit when programmatically setting the `value` property." }, "complexType": { "original": "AccordionGroupChangeEventDetail", "resolved": "AccordionGroupChangeEventDetail<any>", "references": { "AccordionGroupChangeEventDetail": { "location": "import", "path": "./accordion-group-interface", "id": "src/components/accordion-group/accordion-group-interface.ts::AccordionGroupChangeEventDetail" } } } }, { "method": "ionValueChange", "name": "ionValueChange", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [{ "name": "internal", "text": undefined }], "text": "Emitted when the value property has changed.\nThis is used to ensure that ion-accordion can respond\nto any value property changes." }, "complexType": { "original": "AccordionGroupChangeEventDetail", "resolved": "AccordionGroupChangeEventDetail<any>", "references": { "AccordionGroupChangeEventDetail": { "location": "import", "path": "./accordion-group-interface", "id": "src/components/accordion-group/accordion-group-interface.ts::AccordionGroupChangeEventDetail" } } } }]; } static get methods() { return { "requestAccordionToggle": { "complexType": { "signature": "(accordionValue: string | undefined, accordionExpand: boolean) => Promise<void>", "parameters": [{ "name": "accordionValue", "type": "string | undefined", "docs": "" }, { "name": "accordionExpand", "type": "boolean", "docs": "" }], "references": { "Promise": { "location": "global", "id": "global::Promise" } }, "return": "Promise<void>" }, "docs": { "text": "This method is used to ensure that the value\nof ion-accordion-group is being set in a valid\nway. This method should only be called in\nresponse to a user generated action.", "tags": [{ "name": "internal", "text": undefined }] } }, "getAccordions": { "complexType": { "signature": "() => Promise<HTMLIonAccordionElement[]>", "parameters": [], "references": { "Promise": { "location": "global", "id": "global::Promise" }, "HTMLIonAccordionElement": { "location": "global", "id": "global::HTMLIonAccordionElement" } }, "return": "Promise<HTMLIonAccordionElement[]>" }, "docs": { "text": "", "tags": [{ "name": "internal", "text": undefined }] } } }; } static get elementRef() { return "el"; } static get watchers() { return [{ "propName": "value", "methodName": "valueChanged" }, { "propName": "disabled", "methodName": "disabledChanged" }, { "propName": "readonly", "methodName": "readonlyChanged" }]; } static get listeners() { return [{ "name": "keydown", "method": "onKeydown", "target": undefined, "capture": false, "passive": false }]; } }