import { render } from './jsx'; import { generateRandomString } from './helpers/random'; import { Signal, effect } from './signals'; import Vanille from './Vanille'; import { ViewMode } from './ViewMode'; export class View

extends HTMLElement { props: P; styleTag?: HTMLStyleElement; refs: ProxyConstructor; $c: Signal[]; $scopedId: string; constructor(viewMode: ViewMode = ViewMode.OPEN) { super(); this.props = {}; this.$c = []; this.attachShadow({ mode: viewMode }); this.refs = new Proxy( {}, { get: (_, prop: string) => { return this.root.querySelector( `[${this.$scopedId}][ref=${prop}]` ); }, } ) as typeof Proxy; } get root(): ShadowRoot | Element { return this.shadowRoot ? this.shadowRoot : this; } styles(): string { return ''; } def(): object { const define = (accum, key) => { const val = this[key]; if (typeof val === 'function') { accum[key] = val.bind(this); } else { accum[key] = val; } return accum; }; const accum = Object.getOwnPropertyNames(this).reduce(define, {}); return Object.getOwnPropertyNames(this.__proto__).reduce(define, accum); } protected connectedCallback() { this.updateRender(); this.connected(); } disconnectedCallback() { this.disconnected(); } adoptedCallback() { this.adopted(); } attributeChangedCallback(name, oldValue, newValue) { this.attributeChanged(name, oldValue, newValue); } public connected() {} public disconnected() {} public adopted() {} public attributeChanged(name, oldValue, newValue) {} protected createStyleTag(): void { const styleTagContent = `${this.styles()}${Vanille.getStyles()}`; if (!styleTagContent) { return; } this.styleTag = document.createElement('style'); effect(() => { this.updateStyles(); }); this.root.appendChild(this.styleTag); } protected updateStyles(): void { const styleTagContent = `${this.styles()} ${Vanille.getStyles()}`; const styles = styleTagContent .trim() .replaceAll(/(\S+)(h*.*\{)/gm, `$1[${this.$scopedId}]$2 `); this.styleTag.textContent = styles; } updateRender() { // temp, optimize this.root.innerHTML = ''; const node = this.render?.(this.props); // for root only if (!this.$scopedId) { this.$scopedId = `v${generateRandomString(8)}`; this.setAttribute(this.$scopedId, ''); } if (node) { this.createStyleTag(); node.$scopedId = this.$scopedId; const content = render(node); this.root.appendChild(content); } } emit(name: string, data?: unknown) { setTimeout(() => { let options: { bubbles: boolean; detail?: unknown } = { bubbles: true }; if (data) { options = { ...options }; if (typeof data === 'object') { options.detail = { ...data }; } else { options.detail = data; } } this.dispatchEvent(new CustomEvent(name, options)); }, 0); } } customElements.define('v-view', View);