import { createAttributePropertyNameMap, handleAttributeChanged, usePropertiesToAttributes, } from './attribute.ts' import type { AnyProps, PropDeclaration, PropsDeclaration } from './define-props.ts' import { HostElement } from './host-element.ts' import type { Signal } from './signal.ts' import { createState, type State } from './store.ts' /** * @internal */ export type HostElementConstructor = new () => HostElement & Props type SetupFunction = (host: HostElement, props: State) => void export function defineCustomElement( setup: SetupFunction, props: PropsDeclaration, ): HostElementConstructor { const attributeNameToPropertyName = createAttributePropertyNameMap(props) const observedAttributes = Array.from(attributeNameToPropertyName.keys()) const hasAttributes = observedAttributes.length > 0 class CustomElement extends HostElement { static observedAttributes = observedAttributes readonly _store: State constructor() { super() this._store = createState(props) setup(this, this._store) if (hasAttributes) { usePropertiesToAttributes(this, this._store, props) } } attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void { if (oldValue === newValue) return handleAttributeChanged(this._store, props, attributeNameToPropertyName, name, newValue) } } defineGetterSetter(CustomElement, props) return CustomElement as HostElementConstructor as HostElementConstructor } function defineGetterSetter( ElementConstructor: new () => { _store: State }, props: Record>, ) { for (const prop of Object.keys(props)) { Object.defineProperty(ElementConstructor.prototype, prop, { enumerable: true, configurable: false, get() { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access return (this._store[prop] as Signal).get() }, set(v: unknown) { { if (v === undefined) { return } } { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access ;(this._store[prop] as Signal).set(v) } }, }) } }