UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

648 lines (647 loc) • 19.7 kB
/*! * All material copyright ESRI, All Rights Reserved, unless otherwise specified. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details. * v1.5.0-next.4 */ import { Build, h, Host } from "@stencil/core"; import { toAriaBoolean, slotChangeHasAssignedElement } from "../../utils/dom"; import { CSS, SLOTS, ICONS } from "./resources"; import { connectConditionalSlotComponent, disconnectConditionalSlotComponent } from "../../utils/conditionalSlot"; import { componentLoaded, setComponentLoaded, setUpLoadableComponent } from "../../utils/loadable"; import { connectMessages, disconnectMessages, setUpMessages, updateMessages } from "../../utils/t9n"; import { connectInteractive, disconnectInteractive, updateHostInteraction } from "../../utils/interactive"; import { connectLocalized, disconnectLocalized } from "../../utils/locale"; import { createObserver } from "../../utils/observers"; import { isActivationKey } from "../../utils/key"; /** * @slot - A slot for adding text. * @slot image - A slot for adding an image. */ export class Chip { constructor() { this.mutationObserver = createObserver("mutation", () => this.updateHasText()); // -------------------------------------------------------------------------- // // Private Methods // // -------------------------------------------------------------------------- this.close = () => { this.calciteChipClose.emit(); this.selected = false; this.closed = true; }; this.closeButtonKeyDownHandler = (event) => { if (isActivationKey(event.key)) { event.preventDefault(); this.close(); } }; this.handleSlotImageChange = (event) => { this.hasImage = slotChangeHasAssignedElement(event); }; this.handleEmittingEvent = () => { if (this.interactive) { this.calciteChipSelect.emit(); } }; this.disabled = false; this.appearance = "solid"; this.kind = "neutral"; this.closable = false; this.icon = undefined; this.iconFlipRtl = false; this.scale = "m"; this.label = undefined; this.value = undefined; this.closed = false; this.selectionMode = "none"; this.selected = false; this.messageOverrides = undefined; this.messages = undefined; this.interactive = false; this.defaultMessages = undefined; this.effectiveLocale = undefined; this.hasText = false; this.hasImage = false; } onMessagesChange() { /* wired up by t9n util */ } effectiveLocaleChange() { updateMessages(this, this.effectiveLocale); } // -------------------------------------------------------------------------- // // Lifecycle // // -------------------------------------------------------------------------- connectedCallback() { connectConditionalSlotComponent(this); connectInteractive(this); connectLocalized(this); connectMessages(this); this.setupTextContentObserver(); } componentDidLoad() { setComponentLoaded(this); } componentDidRender() { updateHostInteraction(this); } disconnectedCallback() { disconnectConditionalSlotComponent(this); disconnectInteractive(this); disconnectLocalized(this); disconnectMessages(this); } async componentWillLoad() { setUpLoadableComponent(this); if (Build.isBrowser) { await setUpMessages(this); this.updateHasText(); } } //-------------------------------------------------------------------------- // // Event Listeners // //-------------------------------------------------------------------------- keyDownHandler(event) { if (event.target === this.el) { switch (event.key) { case " ": case "Enter": this.handleEmittingEvent(); event.preventDefault(); break; case "ArrowRight": case "ArrowLeft": case "Home": case "End": this.calciteInternalChipKeyEvent.emit(event); event.preventDefault(); break; } } } clickHandler() { if (!this.interactive && this.closable) { this.closeButtonEl.focus(); } } //-------------------------------------------------------------------------- // // Public Methods // //-------------------------------------------------------------------------- /** Sets focus on the component. */ async setFocus() { await componentLoaded(this); if (!this.disabled && this.interactive) { this.containerEl?.focus(); } else if (!this.disabled && this.closable) { this.closeButtonEl?.focus(); } } updateHasText() { this.hasText = this.el.textContent.trim().length > 0; } setupTextContentObserver() { this.mutationObserver?.observe(this.el, { childList: true, subtree: true }); } //-------------------------------------------------------------------------- // // Render Methods // //-------------------------------------------------------------------------- renderChipImage() { return (h("div", { class: CSS.imageContainer }, h("slot", { name: SLOTS.image, onSlotchange: this.handleSlotImageChange }))); } renderSelectionIcon() { const icon = this.selectionMode === "multiple" && this.selected ? ICONS.checked : this.selectionMode === "multiple" ? ICONS.unchecked : this.selected ? ICONS.checkedSingle : undefined; return (h("div", { class: { [CSS.selectIcon]: true, [CSS.selectIconActive]: this.selectionMode === "multiple" || this.selected } }, h("calcite-icon", { icon: icon, scale: this.scale === "l" ? "m" : "s" }))); } renderCloseButton() { return (h("button", { "aria-label": this.messages.dismissLabel, class: CSS.close, onClick: this.close, onKeyDown: this.closeButtonKeyDownHandler, tabIndex: this.disabled ? -1 : 0, // eslint-disable-next-line react/jsx-sort-props ref: (el) => (this.closeButtonEl = el) }, h("calcite-icon", { icon: ICONS.close, scale: this.scale === "l" ? "m" : "s" }))); } renderIcon() { return (h("calcite-icon", { class: CSS.chipIcon, flipRtl: this.iconFlipRtl, icon: this.icon, scale: this.scale === "l" ? "m" : "s" })); } render() { const disableInteraction = this.disabled || (!this.disabled && !this.interactive); const role = this.selectionMode === "multiple" && this.interactive ? "checkbox" : this.selectionMode !== "none" && this.interactive ? "radio" : this.interactive ? "button" : undefined; return (h(Host, null, h("div", { "aria-checked": this.selectionMode !== "none" && this.interactive ? toAriaBoolean(this.selected) : undefined, "aria-disabled": disableInteraction ? toAriaBoolean(this.disabled) : undefined, "aria-label": this.label, class: { [CSS.container]: true, [CSS.textSlotted]: this.hasText, [CSS.imageSlotted]: this.hasImage, [CSS.selectable]: this.selectionMode !== "none", [CSS.multiple]: this.selectionMode === "multiple", [CSS.closable]: this.closable, [CSS.nonInteractive]: !this.interactive, [CSS.isCircle]: !this.closable && !this.hasText && (!this.icon || !this.hasImage) && (this.selectionMode === "none" || (!!this.selectionMode && this.selectionMode !== "multiple" && !this.selected)) }, onClick: this.handleEmittingEvent, role: role, tabIndex: disableInteraction ? -1 : 0, // eslint-disable-next-line react/jsx-sort-props ref: (el) => (this.containerEl = el) }, this.selectionMode !== "none" && this.renderSelectionIcon(), this.renderChipImage(), this.icon && this.renderIcon(), h("span", { class: CSS.title }, h("slot", null)), this.closable && this.renderCloseButton()))); } static get is() { return "calcite-chip"; } static get encapsulation() { return "shadow"; } static get originalStyleUrls() { return { "$": ["chip.scss"] }; } static get styleUrls() { return { "$": ["chip.css"] }; } static get assetsDirs() { return ["assets"]; } static get properties() { return { "disabled": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When `true`, interaction is prevented and the component is displayed with lower opacity." }, "attribute": "disabled", "reflect": true, "defaultValue": "false" }, "appearance": { "type": "string", "mutable": false, "complexType": { "original": "Extract<\"outline\" | \"outline-fill\" | \"solid\", Appearance>", "resolved": "\"outline\" | \"outline-fill\" | \"solid\"", "references": { "Extract": { "location": "global" }, "Appearance": { "location": "import", "path": "../interfaces" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies the appearance style of the component." }, "attribute": "appearance", "reflect": true, "defaultValue": "\"solid\"" }, "kind": { "type": "string", "mutable": false, "complexType": { "original": "Extract<\"brand\" | \"inverse\" | \"neutral\", Kind>", "resolved": "\"brand\" | \"inverse\" | \"neutral\"", "references": { "Extract": { "location": "global" }, "Kind": { "location": "import", "path": "../interfaces" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies the kind of the component (will apply to border and background if applicable)." }, "attribute": "kind", "reflect": true, "defaultValue": "\"neutral\"" }, "closable": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When `true`, a close button is added to the component." }, "attribute": "closable", "reflect": true, "defaultValue": "false" }, "icon": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies an icon to display." }, "attribute": "icon", "reflect": true }, "iconFlipRtl": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When `true`, the icon will be flipped when the element direction is right-to-left (`\"rtl\"`)." }, "attribute": "icon-flip-rtl", "reflect": true, "defaultValue": "false" }, "scale": { "type": "string", "mutable": false, "complexType": { "original": "Scale", "resolved": "\"l\" | \"m\" | \"s\"", "references": { "Scale": { "location": "import", "path": "../interfaces" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies the size of the component. When contained in a parent `calcite-chip-group` inherits the parent's `scale` value." }, "attribute": "scale", "reflect": true, "defaultValue": "\"m\"" }, "label": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Accessible name for the component." }, "attribute": "label", "reflect": false }, "value": { "type": "any", "mutable": false, "complexType": { "original": "any", "resolved": "any", "references": {} }, "required": true, "optional": false, "docs": { "tags": [], "text": "The component's value." }, "attribute": "value", "reflect": false }, "closed": { "type": "boolean", "mutable": true, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When `true`, hides the component." }, "attribute": "closed", "reflect": true, "defaultValue": "false" }, "selectionMode": { "type": "string", "mutable": false, "complexType": { "original": "Extract<\"multiple\" | \"single\" | \"single-persist\" | \"none\", SelectionMode>", "resolved": "\"multiple\" | \"none\" | \"single\" | \"single-persist\"", "references": { "Extract": { "location": "global" }, "SelectionMode": { "location": "import", "path": "../interfaces" } } }, "required": false, "optional": false, "docs": { "tags": [{ "name": "internal", "text": undefined }], "text": "This internal property, managed by a containing `calcite-chip-group`, is\nconditionally set based on the `selectionMode` of the parent" }, "attribute": "selection-mode", "reflect": false, "defaultValue": "\"none\"" }, "selected": { "type": "boolean", "mutable": true, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When `true`, the component is selected." }, "attribute": "selected", "reflect": true, "defaultValue": "false" }, "messageOverrides": { "type": "unknown", "mutable": true, "complexType": { "original": "Partial<ChipMessages>", "resolved": "{ dismissLabel?: string; }", "references": { "Partial": { "location": "global" }, "ChipMessages": { "location": "import", "path": "./assets/chip/t9n" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Use this property to override individual strings used by the component." } }, "messages": { "type": "unknown", "mutable": true, "complexType": { "original": "ChipMessages", "resolved": "{ dismissLabel: string; }", "references": { "ChipMessages": { "location": "import", "path": "./assets/chip/t9n" } } }, "required": false, "optional": false, "docs": { "tags": [{ "name": "internal", "text": undefined }], "text": "Made into a prop for testing purposes only" } }, "interactive": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "name": "internal", "text": undefined }], "text": "When true, enables the chip to be focused, and allows the `calciteChipSelect` to emit.\nThis is set to `true` by a parent Chip Group component." }, "attribute": "interactive", "reflect": false, "defaultValue": "false" } }; } static get states() { return { "defaultMessages": {}, "effectiveLocale": {}, "hasText": {}, "hasImage": {} }; } static get events() { return [{ "method": "calciteChipClose", "name": "calciteChipClose", "bubbles": true, "cancelable": false, "composed": true, "docs": { "tags": [], "text": "Fires when the component's close button is selected." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }, { "method": "calciteChipSelect", "name": "calciteChipSelect", "bubbles": true, "cancelable": false, "composed": true, "docs": { "tags": [], "text": "Fires when the selected state of the component changes." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }, { "method": "calciteInternalChipKeyEvent", "name": "calciteInternalChipKeyEvent", "bubbles": true, "cancelable": false, "composed": true, "docs": { "tags": [{ "name": "internal", "text": undefined }], "text": "" }, "complexType": { "original": "KeyboardEvent", "resolved": "KeyboardEvent", "references": { "KeyboardEvent": { "location": "global" } } } }]; } static get methods() { return { "setFocus": { "complexType": { "signature": "() => Promise<void>", "parameters": [], "references": { "Promise": { "location": "global" } }, "return": "Promise<void>" }, "docs": { "text": "Sets focus on the component.", "tags": [] } } }; } static get elementRef() { return "el"; } static get watchers() { return [{ "propName": "messageOverrides", "methodName": "onMessagesChange" }, { "propName": "effectiveLocale", "methodName": "effectiveLocaleChange" }]; } static get listeners() { return [{ "name": "keydown", "method": "keyDownHandler", "target": undefined, "capture": false, "passive": false }, { "name": "click", "method": "clickHandler", "target": undefined, "capture": false, "passive": false }]; } }