import { AdoptedManager, GlobalManager, managerTypes, ScopedManager } from "./manager.ts"; interface Constructor { new (...args: A): T; } export interface StyledFn { (...args: Args): Return; [metadataSymbol]: StyledMetadata; } export interface StyledMetadata { tag: Tag; extends: { strings: TemplateStringsArray; values: Values }[]; isStatic: boolean; } export type StyledPartValue = string | number | ((this: T, element: T) => string | number); export type NamedElement = K extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[K] : T; type ApplyStyleOptions = | { type?: "inline"; manager?: undefined; } | { type?: "scoped"; manager?: ScopedManager; } | ({ type?: "adopted"; manager?: AdoptedManager; } & Parameters[1]) | ({ type?: "global"; manager?: GlobalManager; } & Parameters[1]); let globalManager: GlobalManager; const styledMap = new WeakMap void>(); const metadataSymbol: unique symbol = Symbol(); const getMetadata = (tag: any): StyledMetadata => { const tagMetadata = isFunction(tag) ? tag[metadataSymbol] || {} : {}; return { isStatic: tagMetadata.isStatic || false, tag: tagMetadata.tag ?? tag, extends: tagMetadata.extends || [], }; }; const isFunction = (value: any): value is Function => typeof value === "function"; const functionIsConstructor = (value: Function): value is Constructor => value.prototype?.constructor === value; const joinParts = (strings: TemplateStringsArray, values: T[], fn: (arg0: T) => string): string => values.length ? strings.reduce((acc, cur, i) => acc + cur + (i < values.length ? fn(values[i]) || "" : ""), "") : strings[0]; export class Powerstyl { createElement( tag: N | Constructor | ((...args: A) => E), args: A, ): NamedElement { if (isFunction(tag)) { if (functionIsConstructor(tag)) { return new tag(...args) as NamedElement; } return tag(...args) as NamedElement; } return document.createElement(tag) as NamedElement; } styled( tag?: N | Constructor | ((...args: A) => E), options: ApplyStyleOptions = {}, ): ( strings: TemplateStringsArray, ...values: StyledPartValue>[] ) => StyledFn, typeof tag, StyledPartValue>[]> { return (strings, ...values) => { const tagMetadata: StyledMetadata>[]> = getMetadata(tag); const finalTag = tagMetadata.tag; const finalExtends = [...tagMetadata.extends, { strings, values }]; const isStatic = tagMetadata.isStatic && !values.some(isFunction); const styledFn: StyledFn, typeof tag, StyledPartValue>[]> = (...args: A) => { const element = this.createElement(finalTag, args); options.type ??= element.shadowRoot ? managerTypes.adopted : managerTypes.scoped; options.manager ??= this.getManager(element, options.type); const partValueToString = isStatic ? String : (value: StyledPartValue>): string => String(isFunction(value) ? value.call(element, element) : value); const templateObjectToString = ({ strings, values }) => joinParts(strings, values, partValueToString); const updateStyle = () => { this.applyStyle(element, finalExtends.map(templateObjectToString).join(""), options); }; updateStyle(); if (!isStatic) { styledMap.set(element, updateStyle); } return element; }; styledFn[metadataSymbol] = { tag: finalTag, extends: finalExtends, isStatic, }; return styledFn; }; } applyStyle(element: HTMLElement, cssText: string, options: ApplyStyleOptions = {}): void { const { manager, type, ...restOptions } = options; if (type === managerTypes.inline) { element.style.cssText = cssText; return; } const { css, applyOptions } = manager.prepare(cssText, element); if (applyOptions) { manager.applyStyle(this.transform(css), { ...applyOptions, ...restOptions }); } } updateStyle(element: HTMLElement): void { styledMap.get(element)?.(); } transform(s: string): string { return s; } getManager(element: HTMLElement, type?: keyof typeof managerTypes): ScopedManager | GlobalManager | AdoptedManager | undefined { switch (type) { case managerTypes.scoped: return new ScopedManager(element); case managerTypes.global: return (globalManager ??= new GlobalManager(document.head)); case managerTypes.adopted: return new AdoptedManager(element); } } } export const mixin = any>(styler0: T, ...stylers: any[]): T => { const result: any = (...a) => styler0(...a); const resultMetadata = getMetadata(styler0); for (const arg of stylers) { const argMetadata = getMetadata(arg); resultMetadata.isStatic ||= argMetadata.isStatic; resultMetadata.extends.push(...argMetadata.extends); } result[metadataSymbol] = resultMetadata; return result; };