import { html, PropertyValueMap, unsafeCSS } from "lit"; import { property, query, state } from "lit/decorators.js"; import { FRoot } from "../../mixins/components/f-root/f-root"; import eleStyle from "./f-text.scss?inline"; import globalStyle from "./f-text-global.scss?inline"; import getCustomFillColor from "../../utils/get-custom-fill-color"; import { validateHTMLColor } from "validate-color"; import { validateHTMLColorName } from "validate-color"; import { flowElement } from "./../../utils"; import { FIcon } from "../f-icon/f-icon"; import { injectCss } from "@cldcvr/flow-core-config"; import Mark from "mark.js/dist/mark.es6.min"; injectCss("f-text", globalStyle); export type FTextStateProp = | "default" | "secondary" | "subtle" | "primary" | "success" | "danger" | "warning" | "inherit" | `custom, ${string}`; /** * @summary Text component includes Headings, titles, body texts and links. */ @flowElement("f-text") export class FText extends FRoot { /** * css loaded from scss file */ static styles = [unsafeCSS(eleStyle), unsafeCSS(globalStyle), ...FIcon.styles]; /** * @attribute local state for managing custom fill. */ @state() fill = ""; /** * @attribute Variants of text component are use cases such as Heading, paragraph, and code. */ @property({ type: String, reflect: true }) variant?: "heading" | "para" | "code" = "para"; /** * @attribute Each variant has various sizes. By default medium is the default size. */ @property({ type: String, reflect: true }) size?: "x-large" | "large" | "medium" | "small" | "x-small" = "medium"; /** * @attribute Weight property defines the visual weight of the text such as regular, medium and bold. */ @property({ type: String, reflect: true }) weight?: "bold" | "medium" | "regular"; /** * @attribute text to highlight */ @property({ type: String, reflect: true }) highlight?: string; /** * @attribute States on texts are used to communicate purpose and it’s connotation. For example, a red color connotes danger, whereas a green color connotes success and so on. */ @property({ type: String, reflect: true }) state?: FTextStateProp = "default"; /** * @attribute Sets the alignment of the text. Can take 3 values: left, center, and right. */ @property({ type: String, reflect: true }) align?: "left" | "center" | "right" = "left"; /** * @attribute will work as in the form inline span element taking just the width of internal text */ @property({ reflect: true, type: Boolean }) inline?: boolean = false; /** * @attribute f-text converts into an editable section . */ @property({ reflect: true, type: Boolean }) editable?: boolean = false; /** * @attribute Loader icon replaces the content of the button . */ @property({ reflect: true, type: Boolean }) loading?: boolean = false; /** * @attribute The disabled attribute can be set to keep a user from clicking on the button. */ @property({ reflect: true, type: Boolean }) disabled?: boolean = false; /** * @attribute will ellipsis apply on overflow */ @property({ reflect: true, type: Boolean }) ellipsis?: boolean = false; @query("slot") defaultSlot!: HTMLSlotElement; @query(".slot-text") innerTextValue!: HTMLDivElement; @query(".textarea") spanEditable!: HTMLSpanElement; @query(".pseudo-edit-text") editTextIcon!: HTMLDivElement; @state() isTextInput = false; get iconSize() { return this.size === "x-small" ? "x-small" : "small"; } /** * validation for all atrributes */ validateProperties() { if ( this.state?.includes("custom") && this.fill && !validateHTMLColor(this.fill) && !validateHTMLColorName(this.fill) ) { throw new Error("f-text : enter correct color-name or color-code"); } } handleInput(e: InputEvent) { e.stopPropagation(); const event = new CustomEvent("input", { detail: { value: this.spanEditable?.textContent?.trim() }, bubbles: true, composed: true }); this.showEmptyPlaceholder(); this.dispatchEvent(event); } handleEdit() { this.isTextInput = true; } handleSubmit(e: MouseEvent) { e.stopPropagation(); this.isTextInput = false; } editOnHover(display: "flex" | "none") { if (this.editTextIcon) { this.editTextIcon.style.display = display; } } showEmptyPlaceholder() { if (this.isTextInput) { this.spanEditable.dataset.isEmpty = String(this.spanEditable?.textContent?.trim() === ""); } } render() { /** * creating local fill variable out of state prop. */ this.fill = getCustomFillColor(this.state ?? ""); /** * validate */ this.validateProperties(); /** * set default weight according to variant */ if (this.fill && this.fill.trim() !== "") { this.style.color = this.fill; } else { this.style.removeProperty("color"); } if (!this.weight) { if (this.variant === "heading") { this.weight = "bold"; } else { this.weight = "regular"; } } const textareaSnippet = html`