import { ShadowlessElement } from '@blockeditor/lit'; import type { LitElement, PropertyValues, TemplateResult } from 'lit'; import { css, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import type { Ref } from 'lit/directives/ref.js'; import { type StyleInfo, styleMap } from 'lit/directives/style-map.js'; export type UniComponentReturn< Props = NonNullable, Expose extends NonNullable = NonNullable, > = { update: (props: Props) => void; unmount: () => void; expose: Expose; }; export type UniComponent< Props = NonNullable, Expose extends NonNullable = NonNullable, > = (ele: HTMLElement, props: Props) => UniComponentReturn; export const renderUniLit = >( uni: UniComponent | undefined, props: Props, options?: { ref?: Ref; style?: Readonly; class?: string; } ): TemplateResult => { return html` `; }; @customElement('uni-lit') export class UniLit< Props, Expose extends NonNullable = NonNullable, > extends ShadowlessElement { static override styles = css` uni-lit { display: contents; } `; @property({ attribute: false }) uni?: UniComponent; @property({ attribute: false }) props!: Props; @property({ attribute: false }) ref?: Ref; uniReturn?: UniComponentReturn; get expose(): Expose | undefined { return this.uniReturn?.expose; } private mount() { this.uniReturn = this.uni?.(this, this.props); if (this.ref) { // @ts-expect-error this.ref.value = this.uniReturn?.expose; } } private unmount() { this.uniReturn?.unmount(); } override connectedCallback() { super.connectedCallback(); this.mount(); } override disconnectedCallback() { super.disconnectedCallback(); this.unmount(); } protected override updated(_changedProperties: PropertyValues) { super.updated(_changedProperties); if (_changedProperties.has('uni')) { this.unmount(); this.mount(); } else if (_changedProperties.has('props')) { this.uniReturn?.update(this.props); } } protected override render(): unknown { return html``; } } export const createUniComponentFromWebComponent = < T, Expose extends NonNullable = NonNullable, >( component: typeof LitElement ): UniComponent => { return (ele, props) => { const ins = new component(); Object.assign(ins, props); ele.appendChild(ins); return { update: props => { Object.assign(ins, props); }, unmount: () => { ins.remove(); }, expose: ins as never as Expose, }; }; }; @customElement('uni-any-render') class UniAnyRender< T, Expose extends NonNullable, > extends ShadowlessElement { @property({ attribute: false }) props!: T; @property({ attribute: false }) expose!: Expose; @property({ attribute: false }) renderTemplate!: (props: T, expose: Expose) => TemplateResult; override render() { return this.renderTemplate(this.props, this.expose); } } export const defineUniComponent = >( renderTemplate: (props: T, expose: Expose) => TemplateResult ): UniComponent => { return (ele, props) => { const ins = new UniAnyRender(); ins.props = props; ins.expose = {} as Expose; ins.renderTemplate = renderTemplate; ele.appendChild(ins); return { update: props => { ins.props = props; }, unmount: () => { ins.remove(); }, expose: ins.expose, }; }; };