@ribajs/bs5
Version:
Bootstrap 5 module for Riba.js
307 lines (268 loc) • 8.12 kB
text/typescript
import {
BasicComponent,
TemplateFunction,
HttpService,
HttpServiceResponse,
} from "@ribajs/core";
import { getLocation } from "@ribajs/utils";
import { BaseCache } from "@ribajs/cache";
import { JsxBs5IconProps } from "../../types/jsx/jsx-icon-props.js";
export class Bs5IconComponent extends BasicComponent {
public static tagName = "bs5-icon";
public static cache = new BaseCache<Promise<HttpServiceResponse<string>>>();
static get observedAttributes(): (keyof JsxBs5IconProps)[] {
return ["size", "width", "height", "src", "color", "direction", "alt"];
}
public scope: any = {};
constructor() {
super();
}
protected getSvg() {
return this.querySelector("svg");
}
protected async fetchCached(url: string) {
let response = Bs5IconComponent.cache.get(url);
if (response) {
return response;
}
response = HttpService.get(url);
if (response) {
Bs5IconComponent.cache.set(url, response);
}
return response;
}
protected async fetchIcon(src: string) {
let response: HttpServiceResponse<string>;
// Append hostname on ssr
if (
window?.ssr &&
!src.startsWith("http") &&
!src.startsWith("ftp") &&
!src.startsWith("sftp")
) {
let url: URL;
if (window.ssr.env.NEST_INTERN_URL) {
url = new URL(src, window.ssr.env.NEST_INTERN_URL);
} else if (window.ssr.ctx) {
url = new URL(
src,
window.ssr.ctx.protocol + "://" + window.ssr.ctx.hostname,
);
} else {
throw new Error("Host for SSR not found!");
}
response = await this.fetchCached(url.href);
} else {
response = await this.fetchCached(src);
}
if (response.status !== 200) {
console.error(response.status);
return "";
}
if (response.headers.get("content-type")?.includes("image/svg+xml")) {
return response.body;
}
console.error(
"[bs5-icon] Only SVG's are supported! But content-type is " +
response.headers.get("content-type"),
);
return "";
}
protected getBasename(src: string) {
const pathnames = getLocation(src).pathname.split("/");
return pathnames?.[pathnames.length - 1];
}
protected getAlternativeText(src: string) {
const basename = this.getBasename(src);
return basename.split(".")[0]?.replaceAll("_", " ");
}
protected async onSrcChanged() {
let icon = "";
if (!this.scope.src) {
this.innerHTML = "";
return;
}
const alt = this.getAlternativeText(this.scope.src);
if (alt) this.setAttribute("alt", alt);
const currentSvg = this.getSvg();
const oldSrc = currentSvg ? currentSvg.getAttribute("src") : "";
// Icon already set (maybe on SSR)
if (oldSrc === this.scope.src) {
return;
}
try {
icon = await this.fetchIcon(this.scope.src);
} catch (error) {
console.warn(
`Error on fetch icon "${this.scope.src}"! Try to switch the protocol...`,
error,
);
// Try to switch protocol on error
if (this.scope.src.startsWith("//")) {
this.scope.src = location.protocol + this.scope.src;
}
const url = new URL(this.scope.src);
if (url.protocol === "http:") {
url.protocol = "https:";
} else {
url.protocol = "http:";
}
try {
icon = await this.fetchIcon(url.href);
} catch (error2) {
console.error(
`Error on fetch icon "${this.scope.src}"!`,
error,
error2,
);
return;
}
}
if (!icon) {
console.error(`Error on fetch icon "${this.scope.src}"!`);
return;
}
this.innerHTML = icon;
const newSvg = this.getSvg();
if (newSvg) {
newSvg.setAttribute("src", this.scope.src);
}
}
protected removeColor() {
this.className = this.className.replace(/(^|\s)color-\S+/g, "");
this.style.color = "";
}
protected setColor(color?: string) {
if (!color) {
return this.removeColor();
}
if (color.includes(",")) {
const colorArr = color.split(",");
if (colorArr.length > 0) {
this.className = this.className.replace(/(^|\s)color-\S+/g, "");
for (let i = 0; i < colorArr.length; i++) {
const newColor: string = colorArr[i];
if (newColor.startsWith("#") || newColor.startsWith("rgb")) {
this.style.color = newColor;
}
this.classList.add(`color-${newColor}`);
}
}
} else {
this.style.color = color;
this.className = this.className.replace(/(^|\s)color-\S+/g, "");
this.classList.add(`color-${color}`);
}
}
protected setSize(size: number) {
this.style.height = size + "px";
this.style.width = size + "px";
this.className = this.className.replace(/(^|\s)size-\S+/g, "");
this.classList.add(`size-${size}`);
}
protected setWidth(width: number) {
this.style.width = width + "px";
this.className = this.className.replace(/(^|\s)width-\S+/g, "");
this.classList.add(`width-${width}`);
}
protected setHeight(height: number) {
this.style.height = height + "px";
this.className = this.className.replace(/(^|\s)height-\S+/g, "");
this.classList.add(`height-${height}`);
}
protected setDirection(direction: string) {
let classString = `direction-${direction}`;
if (direction === "left") {
classString += " rotate-270";
} else if (
direction === "left-top" ||
direction === "left-up" ||
direction === "top-left" ||
direction === "up-left"
) {
classString += " rotate-315";
} else if (direction === "top" || direction === "up") {
classString += " rotate-0";
} else if (
direction === "top-right" ||
direction === "up-right" ||
direction === "right-top" ||
direction === "right-up"
) {
classString += " rotate-45";
} else if (direction === "right") {
classString += " rotate-90";
} else if (
direction === "right-bottom" ||
direction === "right-down" ||
direction === "bottom-right" ||
direction === "down-right"
) {
classString += " rotate-135";
} else if (direction === "bottom" || direction === "down") {
classString += " rotate-180";
} else if (
direction === "left-bottom" ||
direction === "left-down" ||
direction === "bottom-left" ||
direction === "down-left"
) {
classString += " rotate-225";
}
this.className = this.className.replace(/(^|\s)direction-\S+/g, "");
this.className = this.className.replace(/(^|\s)rotate-\S+/g, "");
this.className += " " + classString;
}
protected async attributeChangedCallback(
name: string,
oldValue: any,
newValue: any,
namespace: string | null,
) {
// injects the changed attributes to scope
super.attributeChangedCallback(name, oldValue, newValue, namespace);
if (name === "src") {
// if (!newValue) {
// console.warn("The src attribute must have a value!", this.scope);
// return "";
// }
this.onSrcChanged();
}
if (name === "color") {
this.setColor(newValue);
}
if (name === "size") {
this.setSize(newValue);
}
if (name === "width") {
this.setWidth(newValue);
}
if (name === "height") {
this.setHeight(newValue);
}
if (name === "direction") {
this.setDirection(newValue);
}
}
protected connectedCallback() {
super.connectedCallback();
this.setAttribute("aria-hidden", "true");
this.setAttribute("role", "icon");
this.setAttribute("alt", "icon");
this.classList.add("iconset");
this.init(Bs5IconComponent.observedAttributes);
// set default values
if (!this.scope.direction) {
this.scope.direction = "up";
this.attributeChangedCallback(
"direction",
null,
this.scope.direction,
null,
);
}
}
protected template(): ReturnType<TemplateFunction> {
return null;
}
}