| import { $el, getActionEls } from "../../common/utils_dom.js"; | |
| import { bind } from "../utils_templates.js"; | |
| const CSS_STYLE_SHEETS = new Map(); | |
| const CSS_STYLE_SHEETS_ADDED = new Map(); | |
| const HTML_TEMPLATE_FILES = new Map(); | |
| function getCommonPath(name, extension) { | |
| return `rgthree/common/components/${name.replace("rgthree-", "").replace(/\-/g, "_")}.${extension}`; | |
| } | |
| async function getStyleSheet(name, markupOrPath) { | |
| if (markupOrPath.includes("{")) { | |
| return markupOrPath; | |
| } | |
| if (!CSS_STYLE_SHEETS.has(name)) { | |
| try { | |
| const path = markupOrPath || getCommonPath(name, "css"); | |
| const text = await (await fetch(path)).text(); | |
| CSS_STYLE_SHEETS.set(name, text); | |
| } | |
| catch (e) { | |
| } | |
| } | |
| return CSS_STYLE_SHEETS.get(name); | |
| } | |
| async function addStyleSheet(name, markupOrPath) { | |
| if (markupOrPath.includes("{")) { | |
| throw new Error("Page-level stylesheets should be passed a path."); | |
| } | |
| if (!CSS_STYLE_SHEETS_ADDED.has(name)) { | |
| const link = document.createElement("link"); | |
| link.rel = "stylesheet"; | |
| link.href = markupOrPath; | |
| document.head.appendChild(link); | |
| CSS_STYLE_SHEETS_ADDED.set(name, link); | |
| } | |
| return CSS_STYLE_SHEETS_ADDED.get(name); | |
| } | |
| async function getTemplateMarkup(name, markupOrPath) { | |
| if (markupOrPath.includes("<template")) { | |
| return markupOrPath; | |
| } | |
| if (!HTML_TEMPLATE_FILES.has(name)) { | |
| try { | |
| const path = markupOrPath || getCommonPath(name, "html"); | |
| const text = await (await fetch(path)).text(); | |
| HTML_TEMPLATE_FILES.set(name, text); | |
| } | |
| catch (e) { | |
| } | |
| } | |
| return HTML_TEMPLATE_FILES.get(name); | |
| } | |
| export class RgthreeCustomElement extends HTMLElement { | |
| constructor() { | |
| super(...arguments); | |
| this.ctor = this.constructor; | |
| this.hasBeenConnected = false; | |
| this.connected = false; | |
| this.templates = new Map(); | |
| this.firstConnectedPromise = new Promise((resolve) => (this.firstConnectedPromiseResolver = resolve)); | |
| this.eventElements = new Map(); | |
| } | |
| static create() { | |
| if (this.NAME === "rgthree-override") { | |
| throw new Error("Must override component NAME"); | |
| } | |
| if (!window.customElements.get(this.NAME)) { | |
| window.customElements.define(this.NAME, this); | |
| } | |
| return document.createElement(this.NAME); | |
| } | |
| onFirstConnected() { | |
| } | |
| onReconnected() { | |
| } | |
| onConnected() { | |
| } | |
| onDisconnected() { | |
| } | |
| onAction(action, e) { | |
| console.log("onAction", action, e); | |
| } | |
| getElement(query) { | |
| const el = this.querySelector(query); | |
| if (!el) { | |
| throw new Error("No element found for query: " + query); | |
| } | |
| return el; | |
| } | |
| onActionInternal(action, e) { | |
| if (typeof this[action] === "function") { | |
| this[action](e); | |
| } | |
| else { | |
| this.onAction(action, e); | |
| } | |
| } | |
| onConnectedInternal() { | |
| this.connectActionElements(); | |
| this.onConnected(); | |
| } | |
| onDisconnectedInternal() { | |
| this.disconnectActionElements(); | |
| this.onDisconnected(); | |
| } | |
| async connectedCallback() { | |
| const elementName = this.ctor.NAME; | |
| const wasConnected = this.connected; | |
| if (!wasConnected) { | |
| this.connected = true; | |
| } | |
| if (!this.hasBeenConnected) { | |
| const [stylesheet, markup] = await Promise.all([ | |
| this.ctor.USE_SHADOW | |
| ? getStyleSheet(elementName, this.ctor.CSS) | |
| : addStyleSheet(elementName, this.ctor.CSS), | |
| getTemplateMarkup(elementName, this.ctor.TEMPLATES), | |
| ]); | |
| if (markup) { | |
| const temp = $el("div"); | |
| const templatesMarkup = markup.match(/<template[^]*?<\/template>/gm) || []; | |
| for (const markup of templatesMarkup) { | |
| temp.innerHTML = markup; | |
| const template = temp.children[0]; | |
| if (!(template instanceof HTMLTemplateElement)) { | |
| throw new Error("Not a template element."); | |
| } | |
| let id = template.getAttribute("id"); | |
| if (!id) { | |
| id = this.ctor.NAME; | |
| } | |
| this.templates.set(id, template); | |
| } | |
| } | |
| if (this.ctor.USE_SHADOW) { | |
| this.root = this.attachShadow({ mode: "open" }); | |
| if (typeof stylesheet === "string") { | |
| const sheet = new CSSStyleSheet(); | |
| sheet.replaceSync(stylesheet); | |
| this.root.adoptedStyleSheets = [sheet]; | |
| } | |
| } | |
| else { | |
| this.root = this; | |
| } | |
| let template; | |
| if (this.templates.has(elementName)) { | |
| template = this.templates.get(elementName); | |
| } | |
| else if (this.templates.has(elementName.replace("rgthree-", ""))) { | |
| template = this.templates.get(elementName.replace("rgthree-", "")); | |
| } | |
| if (template) { | |
| this.root.appendChild(template.content.cloneNode(true)); | |
| for (const name of template.getAttributeNames()) { | |
| if (name != "id" && template.getAttribute(name)) { | |
| this.setAttribute(name, template.getAttribute(name)); | |
| } | |
| } | |
| } | |
| this.onFirstConnected(); | |
| this.hasBeenConnected = true; | |
| this.firstConnectedPromiseResolver(); | |
| } | |
| else { | |
| this.onReconnected(); | |
| } | |
| this.onConnectedInternal(); | |
| } | |
| disconnectedCallback() { | |
| this.connected = false; | |
| this.onDisconnected(); | |
| } | |
| connectActionElements() { | |
| const data = getActionEls(this); | |
| for (const dataItem of Object.values(data)) { | |
| const mapItem = this.eventElements.get(dataItem.el) || {}; | |
| for (const [event, action] of Object.entries(dataItem.actions)) { | |
| if (mapItem[event]) { | |
| console.warn(`Element already has an event for ${event}`); | |
| continue; | |
| } | |
| mapItem[event] = (e) => { | |
| this.onActionInternal(action, e); | |
| }; | |
| dataItem.el.addEventListener(event, mapItem[event]); | |
| } | |
| } | |
| } | |
| disconnectActionElements() { | |
| for (const [el, eventData] of this.eventElements.entries()) { | |
| for (const [event, fn] of Object.entries(eventData)) { | |
| el.removeEventListener(event, fn); | |
| } | |
| } | |
| } | |
| async bindWhenConnected(data, el) { | |
| await this.firstConnectedPromise; | |
| this.bind(data, el); | |
| } | |
| bind(data, el) { | |
| bind(el || this.root, data); | |
| } | |
| } | |
| RgthreeCustomElement.NAME = "rgthree-override"; | |
| RgthreeCustomElement.USE_SHADOW = true; | |
| RgthreeCustomElement.TEMPLATES = ""; | |
| RgthreeCustomElement.CSS = ""; | |