@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
648 lines (647 loc) • 19.7 kB
JavaScript
/*!
* 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
}];
}
}