better-react-web-component
Version:
Wrapper for React Component to CustomElement
8 lines (7 loc) • 12.5 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../src/types.ts", "../src/custom-element-template.ts", "../src/custom-html-model.ts", "../src/create-custom-element.ts"],
"sourcesContent": ["// Since typescript cannot calculate exact values of\n// binary number calculations, we calculate them\n// ahead of time in:\n// https://dune.land/dune/d8629da4-6f1d-472e-890a-36e0cf399c23\n\n// Optional types\nexport const optional = {\n\tstring: 0,\n\tnumber: 1,\n\tboolean: 2,\n\tevent: 3,\n\tjson: 4,\n} as const;\n\n// Required types\nexport const required = {\n\tstring: 20,\n\tnumber: 21,\n\tboolean: 22,\n\tevent: 23,\n\tjson: 24,\n} as const;\n\ntype InferType<T> = T extends typeof optional.string\n\t? string | undefined\n\t: T extends typeof optional.json\n\t? Record<string, unknown> | undefined\n\t: T extends typeof optional.number\n\t? number | undefined\n\t: T extends typeof optional.boolean\n\t? boolean | undefined\n\t: T extends typeof optional.event\n\t? ((e: { detail: any }) => void) | undefined\n\t: T extends typeof required.string\n\t? string\n\t: T extends typeof required.json\n\t? Record<string, unknown>\n\t: T extends typeof required.number\n\t? number\n\t: T extends typeof required.boolean\n\t? boolean\n\t: T extends typeof required.event\n\t? (e: { detail: any }) => void\n\t: never;\n\ntype Required<T> = {\n\t[K in keyof T as undefined extends T[K] ? K : never]?: T[K];\n};\n\ntype Optional<T> = {\n\t[K in keyof T as undefined extends T[K] ? never : K]: T[K];\n};\n\ntype MakeUndefinedOptional<T> = Required<T> & Optional<T>;\n\nexport type InferProps<\n\tProps,\n\tOverwrite extends Record<string, any> = {},\n> = Omit<\n\tMakeUndefinedOptional<{\n\t\t[K in keyof Props]-?: InferType<Props[K]>;\n\t}>,\n\tkeyof Overwrite\n> &\n\tOverwrite;\n", "import React from \"react\";\nimport { type Root, createRoot } from \"react-dom/client\";\n\nimport type { CustomHTMLModel } from \"./custom-html-model.ts\";\n\nexport type ReactComponent = ((props: any) => JSX.Element) & {\n\ttypes?: Record<string, number>;\n};\n\nexport class CustomElementTemplate extends HTMLElement {\n\tpublic rootDiv?: HTMLDivElement;\n\tpublic rootElement?: HTMLElement | ShadowRoot;\n\tpublic __reactRoot?: Root;\n\n\tpublic static domModel: CustomHTMLModel;\n\tpublic static ReactComponent: ReactComponent;\n\tpublic static renderRoot:\n\t\t| \"shadowRoot\"\n\t\t| \"shadowRootClosed\"\n\t\t| \"container\"\n\t\t| \"element\";\n\n\tconnectedCallback() {\n\t\tthis.rootElement = this;\n\t\tswitch ((this.constructor as typeof CustomElementTemplate).renderRoot) {\n\t\t\tcase \"container\":\n\t\t\t\tthis.rootElement = this.rootDiv = document.createElement(\"div\");\n\t\t\t\tthis.appendChild(this.rootElement);\n\t\t\t\tbreak;\n\t\t\tcase \"shadowRoot\":\n\t\t\t\tthis.rootElement = this.attachShadow({ mode: \"open\" });\n\t\t\t\tbreak;\n\t\t\tcase \"shadowRootClosed\":\n\t\t\t\tthis.rootElement = this.attachShadow({ mode: \"closed\" });\n\t\t\t\tbreak;\n\t\t}\n\n\t\tthis.renderCustomElement();\n\t}\n\n\trenderCustomElement() {\n\t\tconst ReactComponent = (this.constructor as typeof CustomElementTemplate)\n\t\t\t.ReactComponent;\n\t\tconst model = (this.constructor as typeof CustomElementTemplate).domModel;\n\t\tconst properties = model.getProperties(this);\n\t\tconst events = model.getEvents(this);\n\t\tconst reactElem = React.createElement(\n\t\t\tReactComponent,\n\t\t\tObject.assign(properties, events),\n\t\t);\n\n\t\tconst reactRoot = (this.__reactRoot ??= createRoot(this.rootElement!));\n\t\treactRoot.render(reactElem);\n\t}\n\n\tdisconnectedCallback() {\n\t\tthis.__reactRoot?.unmount();\n\n\t\tif (this.rootDiv) {\n\t\t\tthis.removeChild(this.rootDiv);\n\t\t}\n\n\t\tthis.__reactRoot = undefined;\n\t\tthis.rootDiv = undefined;\n\t\tthis.rootElement = undefined;\n\t}\n\n\tattributeChangedCallback(_name: string, oldValue: any, newValue: any) {\n\t\tif (oldValue === newValue) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.rootElement) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.renderCustomElement();\n\t}\n}\n", "import type { CustomElementTemplate } from \"./custom-element-template.ts\";\nimport { optional, required } from \"./types.ts\";\n\nexport class CustomHTMLModel {\n\tprivate _properties: [\n\t\tstring,\n\t\tstring,\n\t\t(element: CustomElementTemplate) => any,\n\t][] = [];\n\tprivate _events: [string, string][] = [];\n\n\tpublic getProperties(element: CustomElementTemplate) {\n\t\tconst out: Record<string, any> = {};\n\n\t\tfor (const [keyIn, keyOut, evaluator] of this._properties) {\n\t\t\tout[keyOut] = evaluator(element);\n\t\t}\n\n\t\treturn out;\n\t}\n\n\tpublic getEvents(element: CustomElementTemplate) {\n\t\tconst eventsMap: Record<string, (event: CustomEvent) => void> = {};\n\n\t\tconst events = this._events;\n\t\tevents.forEach(([name, key]) => {\n\t\t\teventsMap[key] = function (event) {\n\t\t\t\telement.dispatchEvent(\n\t\t\t\t\tnew CustomEvent(name, { detail: event.detail, bubbles: true }),\n\t\t\t\t);\n\t\t\t};\n\t\t});\n\n\t\treturn eventsMap;\n\t}\n\n\tpublic get observedAttributes() {\n\t\treturn this._properties.map(([keyIn]) => keyIn);\n\t}\n\n\tconstructor(types: Record<string, number>) {\n\t\tconst properties = Object.keys(types || {});\n\t\tfor (const originalName of properties) {\n\t\t\tconst lowerName = originalName.toLowerCase();\n\t\t\tconst type = types[originalName];\n\n\t\t\tif (\n\t\t\t\tlowerName.indexOf(\"on\") === 0 &&\n\t\t\t\t(type === optional.event || type === required.event)\n\t\t\t) {\n\t\t\t\tthis.addEvent(lowerName.slice(2), originalName);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (type === optional.string || type === required.string) {\n\t\t\t\tthis.addAttribute(lowerName, originalName, \"string\");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (type === optional.number || type === required.number) {\n\t\t\t\tthis.addAttribute(lowerName, originalName, \"number\");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (type === optional.json || type === required.json) {\n\t\t\t\tthis.addAttribute(lowerName, originalName, \"json\");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (type === optional.boolean || type === required.boolean) {\n\t\t\t\tthis.addAttribute(lowerName, originalName, \"boolean\", false);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate addAttribute(\n\t\tname: string,\n\t\toriginalName: string,\n\t\ttype: \"string\" | \"number\" | \"json\" | \"boolean\",\n\t\tdefaultValue?: any,\n\t) {\n\t\tif (type === \"string\") {\n\t\t\tthis._properties.push([\n\t\t\t\tname,\n\t\t\t\toriginalName,\n\t\t\t\t(el) =>\n\t\t\t\t\tel && (el.hasAttribute(name) ? el.getAttribute(name) : defaultValue),\n\t\t\t]);\n\t\t\treturn;\n\t\t}\n\n\t\tif (type === \"number\") {\n\t\t\tthis._properties.push([\n\t\t\t\tname,\n\t\t\t\toriginalName,\n\t\t\t\t(el) =>\n\t\t\t\t\tel &&\n\t\t\t\t\t(el.hasAttribute(name)\n\t\t\t\t\t\t? Number(el.getAttribute(name))\n\t\t\t\t\t\t: defaultValue),\n\t\t\t]);\n\t\t\treturn;\n\t\t}\n\n\t\tif (type === \"json\") {\n\t\t\tthis._properties.push([\n\t\t\t\tname,\n\t\t\t\toriginalName,\n\t\t\t\t(el) =>\n\t\t\t\t\tel &&\n\t\t\t\t\t(el.hasAttribute(name)\n\t\t\t\t\t\t? JSON.parse(el.getAttribute(name) || \"null\")\n\t\t\t\t\t\t: defaultValue),\n\t\t\t]);\n\t\t\treturn;\n\t\t}\n\n\t\tif (type === \"boolean\") {\n\t\t\tthis._properties.push([\n\t\t\t\tname,\n\t\t\t\toriginalName,\n\t\t\t\t(el) => el?.hasAttribute(name),\n\t\t\t]);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate addEvent(name: string, key: string) {\n\t\tthis._events.push([name, key]);\n\t}\n}\n", "import {\n\tCustomElementTemplate,\n\ttype ReactComponent,\n} from \"./custom-element-template.ts\";\nimport { CustomHTMLModel } from \"./custom-html-model.ts\";\n\n/**\n * Creates a CustomElement\n * @param ReactComponent ReactComponent with propTypes\n * @param renderRoot\n * - \"container\" appends to generated <div> element\n * - \"shadowRoot\" renders to shadow root\n * - \"shadowRootClosed\" renders to closed shadow root\n * - \"element\" renders directly\n * @param bindValue\n */\nexport function createCustomElement(\n\tReactComponent: ReactComponent,\n\trenderRoot:\n\t\t| \"shadowRoot\"\n\t\t| \"shadowRootClosed\"\n\t\t| \"container\"\n\t\t| \"element\" = \"element\",\n\tbindValue?: Record<string, any>,\n) {\n\tconst BoundReactComponent = bindValue\n\t\t? ReactComponent.bind(bindValue)\n\t\t: ReactComponent;\n\tBoundReactComponent.types = ReactComponent.types;\n\n\tclass FinalCustomElement extends CustomElementTemplate {\n\t\tstatic override domModel = new CustomHTMLModel(\n\t\t\tBoundReactComponent.types || {},\n\t\t);\n\t\tstatic override ReactComponent = BoundReactComponent;\n\t\tstatic override renderRoot = renderRoot;\n\n\t\tstatic observedAttributes = FinalCustomElement.domModel.observedAttributes;\n\t}\n\n\treturn FinalCustomElement;\n}\n"],
"mappings": "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",
"names": ["optional", "required", "React", "createRoot", "CustomElementTemplate", "_a", "_b", "ReactComponent", "model", "properties", "events", "reactElem", "__reactRoot", "_name", "oldValue", "newValue", "CustomHTMLModel", "types", "_properties", "_events", "properties", "originalName", "lowerName", "type", "optional", "required", "element", "out", "keyIn", "keyOut", "evaluator", "eventsMap", "name", "key", "event", "defaultValue", "el", "createCustomElement", "ReactComponent", "renderRoot", "bindValue", "BoundReactComponent", "_FinalCustomElement", "CustomElementTemplate", "CustomHTMLModel", "FinalCustomElement"]
}