UNPKG

@limetech/lime-elements

Version:
507 lines (506 loc) 19.8 kB
import { Host, h } from "@stencil/core"; import { getIconName } from "../icon/get-icon-props"; import { createRandomString } from "../../util/random-string"; import { CheckboxTemplate } from "../checkbox/checkbox.template"; import translate from "../../global/translations"; /** * This components displays the list item. * This centralizes styles and functionality, and helps reduce redundant code * in consumer components such as `limel-list` and `limel-menu-list`. * * :::note * The component has `shadow: false`. There are a few reasons for it: * 1. This is to improve performance, and ensure that its internal elements are * considered as internal parts of the consumer's DOM. * 2. The consumer does not need to implement the interactive styles * (such as `visualize-keyboard-focus` mixin) on their own. Since there is no * shadow DOM, our mixins can be applied directly to the `limel-list-item` elements, * within the component's own styles. * 3. Most importantly, the MDCList checks the light DOM of each list item * to find native inputs to decide the list mode (checkbox/radio). * With `shadow: true`, those inputs would be hidden inside the `limel-list-items`’s * shadow DOM, so MDC wouldn’t detect them and therefore throw errors, when given * an array index (for the items). * With `shadow: false`, the native `<input type="checkbox/radio">` from this template * would be visible to MDC. * ::: * @exampleComponent limel-example-list-item-basic * @exampleComponent limel-example-list-item-icon * @exampleComponent limel-example-list-item-icon-size * @exampleComponent limel-example-list-item-pictures * @exampleComponent limel-example-list-item-multiple-lines * @exampleComponent limel-example-list-item-interactive * @exampleComponent limel-example-list-item-radio * @exampleComponent limel-example-list-item-checkbox * @exampleComponent limel-example-list-item-actions * @exampleComponent limel-example-list-item-primary-component * @exampleComponent limel-example-list-item-command-text * @private */ export class ListItemComponent { constructor() { /** * Defines the language for translations. * Will translate the translatable strings on the components. */ this.language = 'en'; /** * {@inheritdoc ListItem.disabled} */ this.disabled = false; /** * Size of the icon displayed for this item. */ this.iconSize = 'small'; /** * Set to `true` if the list should display larger icons with a background */ this.badgeIcon = false; /** * {@inheritdoc ListItem.selected} */ this.selected = false; /** * The semantic role of the list item. This affects the ARIA role * and the interaction behavior. * * - 'option' → selectable via click/Enter/Space, aria-selected * - 'radio'/'checkbox' → selectable, aria-checked * - 'menuitem'/'listitem' → activation only, no selection toggle */ this.type = 'listitem'; this.renderLabel = () => { return (h("span", { class: "label", id: this.labelId }, this.text)); }; this.renderDescription = () => { if (!this.secondaryText) { return; } return (h("span", { class: "description", id: this.descriptionId }, this.secondaryText)); }; this.renderIcon = () => { const iconName = getIconName(this.icon); if (!iconName) { return; } let iconColor; let iconBackgroundColor; let title; if (typeof this.icon === 'object') { iconColor = this.icon.color; iconBackgroundColor = this.icon.backgroundColor; title = this.icon.title; } const iconProps = { 'aria-label': title, 'aria-hidden': title ? null : 'true', name: iconName, style: { color: iconColor, 'background-color': iconBackgroundColor, }, badge: this.badgeIcon, size: this.iconSize, }; return h("limel-icon", Object.assign({}, iconProps)); }; this.renderPrimaryComponent = () => { const primary = this.primaryComponent; if (!(primary === null || primary === void 0 ? void 0 : primary.name)) { return; } const PrimaryComponent = primary.name; const props = primary.props || {}; return h(PrimaryComponent, Object.assign({}, props)); }; this.renderImage = () => { if (!this.image) { return; } return h("img", { src: this.image.src, alt: this.image.alt, loading: "lazy" }); }; this.renderActionMenu = (actions) => { if (!actions || actions.length === 0) { return; } const stableActions = this.getStableActions(actions); return (h("limel-menu", { class: "mdc-deprecated-list-item__meta", items: stableActions, openDirection: "left-start" }, h("limel-icon-button", { class: "action-menu-trigger", slot: "trigger", icon: "menu_2", label: this.actionMenuLabel() }))); }; this.renderRadioButton = () => { if (this.type !== 'radio') { return; } return (h("limel-radio-button", { id: `radio_${this.labelId}`, checked: this.selected, disabled: this.disabled })); }; this.renderCheckbox = () => { if (this.type !== 'checkbox') { return; } return (h(CheckboxTemplate, { id: `checkbox_${this.labelId}`, checked: this.selected, disabled: this.disabled })); }; this.actionMenuLabel = () => { return translate.get('file-viewer.more-actions', this.language); }; this.labelId = createRandomString(); this.descriptionId = createRandomString(); } render() { var _a; const ariaProps = { 'aria-labelledby': this.labelId, 'aria-describedby': this.secondaryText ? this.descriptionId : undefined, 'aria-disabled': this.disabled ? 'true' : 'false', }; // ARIA state depending on `role`/`type` if (this.type === 'radio' || this.type === 'checkbox') { ariaProps['aria-checked'] = this.selected ? 'true' : 'false'; } else if (this.type === 'option' || this.type === 'menuitem') { // aria-selected for `option` (spec); // also keep for `menuitem` for visual state consistency ariaProps['aria-selected'] = this.selected ? 'true' : 'false'; } return (h(Host, Object.assign({ key: '4f769d4dc9bcf065f0891ae394efb8f2cb4e3cbe', role: this.getHostRole(), class: { 'has-primary-component': !!((_a = this.primaryComponent) === null || _a === void 0 ? void 0 : _a.name), } }, ariaProps), this.renderRadioButton(), this.renderCheckbox(), this.renderIcon(), this.renderImage(), this.renderPrimaryComponent(), h("div", { key: 'f3ae79f70d048a6541af55782a8a1956fd7338c5', class: "text" }, this.renderLabel(), this.renderDescription()), this.renderActionMenu(this.actions))); } /** * Returns a stable reference for the provided actions array to avoid * unnecessary re-renders of the action menu when the reference is unchanged. * * @param actions The actions (and separators) to display in the menu * @returns The same array instance that was previously seen, if unchanged */ getStableActions(actions) { if (this.memoizedActions === actions) { return this.memoizedActions; } this.memoizedActions = actions; return actions; } getHostRole() { switch (this.type) { case 'option': { return 'option'; } case 'radio': { return 'radio'; } case 'checkbox': { return 'checkbox'; } case 'menuitem': { return 'menuitem'; } default: { return 'listitem'; } } } static get is() { return "limel-list-item"; } static get originalStyleUrls() { return { "$": ["list-item.scss"] }; } static get styleUrls() { return { "$": ["list-item.css"] }; } static get properties() { return { "language": { "type": "string", "mutable": false, "complexType": { "original": "Languages", "resolved": "\"da\" | \"de\" | \"en\" | \"fi\" | \"fr\" | \"nb\" | \"nl\" | \"no\" | \"sv\"", "references": { "Languages": { "location": "import", "path": "../date-picker/date.types", "id": "src/components/date-picker/date.types.ts::Languages", "referenceLocation": "Languages" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Defines the language for translations.\nWill translate the translatable strings on the components." }, "getter": false, "setter": false, "reflect": true, "attribute": "language", "defaultValue": "'en'" }, "value": { "type": "any", "mutable": false, "complexType": { "original": "any", "resolved": "any", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "{@inheritdoc ListItem.value}" }, "getter": false, "setter": false, "reflect": false, "attribute": "value" }, "text": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "{@inheritdoc ListItem.text}" }, "getter": false, "setter": false, "reflect": true, "attribute": "text" }, "secondaryText": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "{@inheritdoc ListItem.secondaryText}" }, "getter": false, "setter": false, "reflect": true, "attribute": "secondary-text" }, "disabled": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "{@inheritdoc ListItem.disabled}" }, "getter": false, "setter": false, "reflect": true, "attribute": "disabled", "defaultValue": "false" }, "icon": { "type": "string", "mutable": false, "complexType": { "original": "string | ListItem['icon']", "resolved": "Icon | string", "references": { "ListItem": { "location": "import", "path": "./list-item.types", "id": "src/components/list-item/list-item.types.ts::ListItem", "referenceLocation": "ListItem" } } }, "required": false, "optional": true, "docs": { "tags": [], "text": "{@inheritdoc ListItem.icon}" }, "getter": false, "setter": false, "reflect": false, "attribute": "icon" }, "iconSize": { "type": "string", "mutable": false, "complexType": { "original": "IconSize", "resolved": "\"large\" | \"medium\" | \"small\" | \"x-small\"", "references": { "IconSize": { "location": "import", "path": "../icon/icon.types", "id": "src/components/icon/icon.types.ts::IconSize", "referenceLocation": "IconSize" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Size of the icon displayed for this item." }, "getter": false, "setter": false, "reflect": true, "attribute": "icon-size", "defaultValue": "'small'" }, "badgeIcon": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Set to `true` if the list should display larger icons with a background" }, "getter": false, "setter": false, "reflect": true, "attribute": "badge-icon", "defaultValue": "false" }, "selected": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "{@inheritdoc ListItem.selected}" }, "getter": false, "setter": false, "reflect": true, "attribute": "selected", "defaultValue": "false" }, "actions": { "type": "unknown", "mutable": false, "complexType": { "original": "ListItem['actions']", "resolved": "(ListSeparator | MenuItem<any>)[]", "references": { "ListItem": { "location": "import", "path": "./list-item.types", "id": "src/components/list-item/list-item.types.ts::ListItem", "referenceLocation": "ListItem" } } }, "required": false, "optional": true, "docs": { "tags": [], "text": "{@inheritdoc ListItem.selected}" }, "getter": false, "setter": false }, "primaryComponent": { "type": "unknown", "mutable": false, "complexType": { "original": "ListItem['primaryComponent']", "resolved": "ListComponent", "references": { "ListItem": { "location": "import", "path": "./list-item.types", "id": "src/components/list-item/list-item.types.ts::ListItem", "referenceLocation": "ListItem" } } }, "required": false, "optional": true, "docs": { "tags": [], "text": "{@inheritdoc ListItem.selected}" }, "getter": false, "setter": false }, "image": { "type": "unknown", "mutable": false, "complexType": { "original": "ListItem['image']", "resolved": "Image", "references": { "ListItem": { "location": "import", "path": "./list-item.types", "id": "src/components/list-item/list-item.types.ts::ListItem", "referenceLocation": "ListItem" } } }, "required": false, "optional": true, "docs": { "tags": [], "text": "{@inheritdoc ListItem.image}" }, "getter": false, "setter": false }, "type": { "type": "string", "mutable": false, "complexType": { "original": "'listitem' | 'menuitem' | 'option' | 'radio' | 'checkbox'", "resolved": "\"checkbox\" | \"listitem\" | \"menuitem\" | \"option\" | \"radio\"", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The semantic role of the list item. This affects the ARIA role\nand the interaction behavior.\n\n- 'option' \u2192 selectable via click/Enter/Space, aria-selected\n- 'radio'/'checkbox' \u2192 selectable, aria-checked\n- 'menuitem'/'listitem' \u2192 activation only, no selection toggle" }, "getter": false, "setter": false, "reflect": true, "attribute": "type", "defaultValue": "'listitem'" } }; } }