import { html, PropertyValueMap, unsafeCSS } from "lit";
import { property, state } from "lit/decorators.js";
import { FRoot } from "../../mixins/components/f-root/f-root";
import eleStyle from "./f-div.scss?inline";
import globalStyle from "./f-div-global.scss?inline";
import { unsafeSVG } from "lit-html/directives/unsafe-svg.js";
import loader from "../../mixins/svg/loader";
import getCustomFillColor from "../../utils/get-custom-fill-color";
import { flowElement } from "./../../utils";
import { injectCss } from "@cldcvr/flow-core-config";
export type FDivBorderWidth = "small" | "medium" | "large";
export type FDivBorderStyle = "solid" | "dashed" | "dotted";
export type FDivBorderColor = "default" | "secondary" | "subtle";
export type FDivBorderPosition = "bottom" | "top" | "left" | "right" | "around";
export type FDivPadding = "x-large" | "large" | "medium" | "small" | "x-small" | "none";
export type FDivBorderProp =
| FDivBorderWidth
| `${FDivBorderWidth} ${FDivBorderStyle}`
| `${FDivBorderWidth} ${FDivBorderStyle} ${FDivBorderColor}`
| `${FDivBorderWidth} ${FDivBorderStyle} ${FDivBorderColor} ${FDivBorderPosition}`;
export type FDivPaddingProp =
| FDivPadding
| `${FDivPadding} ${FDivPadding}`
| `${FDivPadding} ${FDivPadding} ${FDivPadding} ${FDivPadding}`;
export type FDivWidthProp =
| "fill-container"
| "hug-content"
| `${number}px`
| `${number}%`
| `${number}vw`;
export type FDivHeightProp =
| "fill-container"
| "hug-content"
| `${number}px`
| `${number}%`
| `${number}vh`;
export type FDivMaxWidthProp = `${number}px` | `${number}%` | `${number}vw`;
export type FDivMaxHeightProp = `${number}px` | `${number}%` | `${number}vh`;
export type FDivStateProp =
| "subtle"
| "default"
| "secondary"
| "tertiary"
| "success"
| "warning"
| "danger"
| "primary"
| "transparent"
| "inherit"
| `custom, ${string}`;
/**
* START : constant values required for `f-div`
*/
const BORDER_WIDTH_VALUES = {
small: "1px",
medium: "2px",
large: "4px"
};
const BORDER_COLOR_VALUES = {
default: "var(--color-border-default)",
subtle: "var(--color-border-subtle)",
secondary: "var(--color-border-secondary)"
};
const PADDING_VALUES = {
"x-large": "24px",
large: "16px",
medium: "12px",
small: "8px",
"x-small": "4px",
none: "0px"
};
const DEFAULT_BORDER = {
borderWidth: "small",
borderStyle: "solid",
borderColor: "default",
borderPosition: "around"
};
const BORDER_POSITION_CSS = {
bottom: "border-bottom",
top: "border-top",
left: "border-left",
right: "border-right",
around: "border"
} as Record;
/**
* END : constant values required for `f-div`
*/
injectCss("f-div", globalStyle);
/**
* @summary F-div is used as a container for HTML elements.
*/
@flowElement("f-div")
export class FDiv extends FRoot {
/**
* css loaded from scss file
*/
static styles = [unsafeCSS(eleStyle), unsafeCSS(globalStyle)];
/**
* @attribute local state for managing custom fill.
*/
@state()
fill = "";
/**
* @attribute Variants are various representations of a f-div.
*/
@property({ type: String, reflect: true })
variant?: "block" | "curved" | "round" = "block";
/**
* @attribute Direction will decide whether these elements needs to be displayed in vertical or horizontal manner.
*/
@property({ type: String, reflect: true })
direction?: "row" | "column" = "row";
/**
* @attribute state property defines the background color of a f-div. It can take only surface colors defined in the library.
*/
@property({ reflect: true, type: String })
state?: FDivStateProp = "transparent";
/**
* @attribute Border property enables a border for f-div. You can combine border properties to achieve a desired result.
*/
@property({
type: String,
reflect: true
})
border?: FDivBorderProp;
/**
* @attribute Gap defines the space between the items of a f-div
*/
@property({ type: String, reflect: true })
gap?: "auto" | "x-large" | "large" | "medium" | "small" | "x-small" | "none" = "none";
/**
* @attribute Padding defines extra space across the elements inside a f-div.
*/
@property({
type: String,
reflect: true
})
padding?: FDivPaddingProp = "none";
/**
* @attribute Align property places the elements of a layout in particular positions with respect to the f-div.
*/
@property({ type: String, reflect: true })
align?:
| "top-left"
| "top-center"
| "top-right"
| "middle-left"
| "middle-center"
| "middle-right"
| "bottom-left"
| "bottom-center"
| "bottom-right" = "top-left";
/**
* @attribute width of `f-div`
*/
@property({ type: String, reflect: true })
width?: FDivWidthProp = "fill-container";
/**
* @attribute height of `f-div`
*/
@property({ type: String, reflect: true })
height?: FDivHeightProp = "fill-container";
/**
* @attribute width of `f-div`
*/
@property({ type: String, reflect: true, attribute: "max-width" })
maxWidth?: FDivWidthProp;
/**
* @attribute height of `f-div`
*/
@property({ type: String, reflect: true, attribute: "max-height" })
maxHeight?: FDivHeightProp;
/**
* @attribute The disabled attribute can be set to keep a user from clicking on the f-icon.
*/
@property({ reflect: true, type: Boolean })
disabled?: boolean = false;
/**
* @attribute display loader
*/
@property({ reflect: true, type: String })
loading?: "skeleton" | "loader";
/**
* @attribute is clickable
*/
@property({ reflect: true, type: Boolean })
clickable?: boolean = false;
/**
* @attribute is highlighted
*/
@property({ reflect: true, type: Boolean })
highlight?: boolean = false;
/**
* @attribute Overflow property defines the behavior of the overflowing elements inside a f-div
*/
@property({ reflect: true, type: String })
overflow?: "wrap" | "scroll" | "hidden" | "visible" = "wrap";
/**
* @attribute Sets the f-div to a selected state. Select between border, background, or notch based on your use case.
*/
@property({ reflect: true, type: String })
selected?: "none" | "background" | "border" | "notch-right" | "notch-left" = "none";
/**
* @attribute Sticky property defines a f-div’s position based on the scroll position of the container
*/
@property({ reflect: true, type: String })
sticky?: "none" | "top" | "bottom" | "left" | "right" = "none";
/**
* Applying border related style, based on value
*/
applyBorder() {
if (this.border) {
const [borderWidth, borderStyle, borderColor, borderPosition] = this.border.split(" ") || [];
const meta = {
width: BORDER_WIDTH_VALUES[(borderWidth || DEFAULT_BORDER.borderWidth) as FDivBorderWidth],
style: borderStyle || DEFAULT_BORDER.borderStyle,
color: BORDER_COLOR_VALUES[(borderColor || DEFAULT_BORDER.borderColor) as FDivBorderColor],
position: borderPosition || DEFAULT_BORDER.borderPosition
};
this.style.setProperty(
BORDER_POSITION_CSS[meta.position],
`${meta.width} ${meta.style} ${meta.color}`
);
}
}
/**
* Applying padding related style, based on value
*/
applyPadding() {
if (this.padding) {
const paddingValues = (this.padding.split(" ") || []) as FDivPadding[];
let paddingCSS = PADDING_VALUES[`none`];
if (paddingValues) {
paddingCSS = paddingValues
.slice(0, 4)
.map(val => {
return PADDING_VALUES[val];
})
.join(" ");
}
this.style.padding = paddingCSS;
}
}
/**
* Applying height,width related style, based on value
*/
applySize() {
const fixedValues = ["fill-container", "hug-content"];
if (this.width && !fixedValues.includes(this.width)) {
this.style.width = this.width;
}
if (this.height && !fixedValues.includes(this.height)) {
this.style.height = this.height;
}
if (this.maxWidth) {
this.classList.add("f-div-custom-width");
this.style.setProperty("--max-width", this.maxWidth);
} else {
this.style.removeProperty("--max-width");
this.classList.remove("f-div-custom-width");
}
if (this.maxHeight) {
this.classList.add("f-div-custom-height");
this.style.setProperty("--max-height", this.maxHeight);
} else {
this.style.removeProperty("--max-height");
this.classList.remove("f-div-custom-height");
}
}
checkHighlight() {
const highlights = document.querySelectorAll("f-div[highlight]");
const overlayEl = document.querySelector(".f-div-highlight-overlay");
if (highlights.length > 0 && !overlayEl) {
const overlay = ``;
document.body?.insertAdjacentHTML("afterbegin", overlay);
}
if (highlights.length === 0) {
overlayEl?.remove();
}
}
disconnectedCallback() {
this.checkHighlight();
super.disconnectedCallback();
}
render() {
/**
* creating local fill variable out of state prop.
*/
this.fill = getCustomFillColor(this.state ?? "");
/**
* START : apply inline styles based on attribute values
*/
if (this.state?.includes("custom") && this.fill) {
this.style.backgroundColor = this.fill;
} else {
this.style.backgroundColor = "";
}
this.applyBorder();
this.applyPadding();
this.applySize();
/**
* END : apply inline styles based on attribute values
*/
/**
* Final html to render
*/
return html` ${this.loading === "loader" ? html`${unsafeSVG(loader)}` : ""}`;
}
protected updated(changedProperties: PropertyValueMap | Map) {
super.updated(changedProperties);
if (
changedProperties.has("highlight") &&
(changedProperties.get("highlight") === true || this.highlight)
) {
this.checkHighlight();
}
void this.updateComplete.then(() => {
if (this.variant === "round") {
this.style.borderRadius = `${this.offsetHeight / 2}px`;
} else if (this.variant === "curved") {
this.style.borderRadius = `4px`;
} else {
this.style.borderRadius = "0px";
}
});
}
}
/**
* Required for typescript
*/
declare global {
export interface HTMLElementTagNameMap {
"f-div": FDiv;
}
}