@limetech/lime-elements
Version:
507 lines (506 loc) • 19.8 kB
JavaScript
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'"
}
};
}
}