Skip to content

Instantly share code, notes, and snippets.

@guiseek
Created December 10, 2023 06:46
Show Gist options
  • Save guiseek/61dc21fda6a6400ef3cce3780b00e4b4 to your computer and use it in GitHub Desktop.
Save guiseek/61dc21fda6a6400ef3cce3780b00e4b4 to your computer and use it in GitHub Desktop.
TSX
import {ElementType, TagsByType} from '../types'
const ns = {
html: 'http://www.w3.org/1999/xhtml',
svg: 'http://www.w3.org/2000/svg',
mathMl: 'http://www.w3.org/1998/Math/MathML',
}
export function createElement<
Type extends ElementType,
Tag extends TagsByType<Type> & string
>(type: Type, tag: Tag, options?: ElementCreationOptions) {
switch (type) {
case 'html':
return document.createElementNS(ns.html, tag, options)
case 'svg':
return document.createElementNS(ns.svg, tag, options)
case 'mathMl':
return document.createElementNS(ns.mathMl, tag, options)
default:
return document.createElement(tag, options)
}
}
import {Constructor, ElementByTag, ElementFn, Tags} from './types'
import {createElement, determineElementType} from './utilities'
export const create = <K extends Tags>(
tagOrFn: K | ElementFn<K> | Constructor<ElementByTag<K>>,
attrs: Partial<ElementByTag<K>>,
...nodes: Node[]
) => {
let component: Element
if (typeof tagOrFn === 'string') {
const elementType = determineElementType(tagOrFn)
component = createElement(elementType, tagOrFn)
} else {
try {
component = new (tagOrFn as Constructor<ElementByTag<K>>)(attrs)
} catch {
component = (tagOrFn as ElementFn<K>)(attrs)
}
}
component.append(
...nodes.flatMap((node) =>
typeof node === 'string' ? new Text(node) : node
)
)
return Object.assign(component, attrs)
}
import {ElementType} from '../types'
import {mathMl} from './math-ml'
import {html} from './html'
import {svg} from './svg'
export function determineElementType(tag: string): ElementType {
if (Object.keys(html).includes(tag)) return 'html'
if (Object.keys(svg).includes(tag)) return 'svg'
if (Object.keys(mathMl).includes(tag)) return 'mathMl'
return 'custom'
}
export const fragment = () => {
return new DocumentFragment();
};
export const html = {
a: HTMLAnchorElement,
abbr: HTMLElement,
address: HTMLElement,
area: HTMLAreaElement,
article: HTMLElement,
aside: HTMLElement,
audio: HTMLAudioElement,
b: HTMLElement,
base: HTMLBaseElement,
bdi: HTMLElement,
bdo: HTMLElement,
blockquote: HTMLQuoteElement,
body: HTMLBodyElement,
br: HTMLBRElement,
button: HTMLButtonElement,
canvas: HTMLCanvasElement,
caption: HTMLTableCaptionElement,
cite: HTMLElement,
code: HTMLElement,
col: HTMLTableColElement,
colgroup: HTMLTableColElement,
data: HTMLDataElement,
datalist: HTMLDataListElement,
dd: HTMLElement,
del: HTMLModElement,
details: HTMLDetailsElement,
dfn: HTMLElement,
dialog: HTMLDialogElement,
div: HTMLDivElement,
dl: HTMLDListElement,
dt: HTMLElement,
em: HTMLElement,
embed: HTMLEmbedElement,
fieldset: HTMLFieldSetElement,
figcaption: HTMLElement,
figure: HTMLElement,
footer: HTMLElement,
form: HTMLFormElement,
h1: HTMLHeadingElement,
h2: HTMLHeadingElement,
h3: HTMLHeadingElement,
h4: HTMLHeadingElement,
h5: HTMLHeadingElement,
h6: HTMLHeadingElement,
head: HTMLHeadElement,
header: HTMLElement,
hgroup: HTMLElement,
hr: HTMLHRElement,
html: HTMLHtmlElement,
i: HTMLElement,
iframe: HTMLIFrameElement,
img: HTMLImageElement,
input: HTMLInputElement,
ins: HTMLModElement,
kbd: HTMLElement,
label: HTMLLabelElement,
legend: HTMLLegendElement,
li: HTMLLIElement,
link: HTMLLinkElement,
main: HTMLElement,
map: HTMLMapElement,
mark: HTMLElement,
menu: HTMLMenuElement,
meta: HTMLMetaElement,
meter: HTMLMeterElement,
nav: HTMLElement,
noscript: HTMLElement,
object: HTMLObjectElement,
ol: HTMLOListElement,
optgroup: HTMLOptGroupElement,
option: HTMLOptionElement,
output: HTMLOutputElement,
p: HTMLParagraphElement,
picture: HTMLPictureElement,
pre: HTMLPreElement,
progress: HTMLProgressElement,
q: HTMLQuoteElement,
rp: HTMLElement,
rt: HTMLElement,
ruby: HTMLElement,
s: HTMLElement,
samp: HTMLElement,
script: HTMLScriptElement,
search: HTMLElement,
section: HTMLElement,
select: HTMLSelectElement,
slot: HTMLSlotElement,
small: HTMLElement,
source: HTMLSourceElement,
span: HTMLSpanElement,
strong: HTMLElement,
style: HTMLStyleElement,
sub: HTMLElement,
summary: HTMLElement,
sup: HTMLElement,
table: HTMLTableElement,
tbody: HTMLTableSectionElement,
td: HTMLTableCellElement,
template: HTMLTemplateElement,
textarea: HTMLTextAreaElement,
tfoot: HTMLTableSectionElement,
th: HTMLTableCellElement,
thead: HTMLTableSectionElement,
time: HTMLTimeElement,
title: HTMLTitleElement,
tr: HTMLTableRowElement,
track: HTMLTrackElement,
u: HTMLElement,
ul: HTMLUListElement,
var: HTMLElement,
video: HTMLVideoElement,
wbr: HTMLElement,
};
export const mathMl = {
annotation: MathMLElement,
"annotation-xml": MathMLElement,
maction: MathMLElement,
math: MathMLElement,
merror: MathMLElement,
mfrac: MathMLElement,
mi: MathMLElement,
mmultiscripts: MathMLElement,
mn: MathMLElement,
mo: MathMLElement,
mover: MathMLElement,
mpadded: MathMLElement,
mphantom: MathMLElement,
mprescripts: MathMLElement,
mroot: MathMLElement,
mrow: MathMLElement,
ms: MathMLElement,
mspace: MathMLElement,
msqrt: MathMLElement,
mstyle: MathMLElement,
msub: MathMLElement,
msubsup: MathMLElement,
msup: MathMLElement,
mtable: MathMLElement,
mtd: MathMLElement,
mtext: MathMLElement,
mtr: MathMLElement,
munder: MathMLElement,
munderover: MathMLElement,
semantics: MathMLElement,
}
export const svg = {
a: SVGAElement,
animate: SVGAnimateElement,
animateMotion: SVGAnimateMotionElement,
animateTransform: SVGAnimateTransformElement,
circle: SVGCircleElement,
clipPath: SVGClipPathElement,
defs: SVGDefsElement,
desc: SVGDescElement,
ellipse: SVGEllipseElement,
feBlend: SVGFEBlendElement,
feColorMatrix: SVGFEColorMatrixElement,
feComponentTransfer: SVGFEComponentTransferElement,
feComposite: SVGFECompositeElement,
feConvolveMatrix: SVGFEConvolveMatrixElement,
feDiffuseLighting: SVGFEDiffuseLightingElement,
feDisplacementMap: SVGFEDisplacementMapElement,
feDistantLight: SVGFEDistantLightElement,
feDropShadow: SVGFEDropShadowElement,
feFlood: SVGFEFloodElement,
feFuncA: SVGFEFuncAElement,
feFuncB: SVGFEFuncBElement,
feFuncG: SVGFEFuncGElement,
feFuncR: SVGFEFuncRElement,
feGaussianBlur: SVGFEGaussianBlurElement,
feImage: SVGFEImageElement,
feMerge: SVGFEMergeElement,
feMergeNode: SVGFEMergeNodeElement,
feMorphology: SVGFEMorphologyElement,
feOffset: SVGFEOffsetElement,
fePointLight: SVGFEPointLightElement,
feSpecularLighting: SVGFESpecularLightingElement,
feSpotLight: SVGFESpotLightElement,
feTile: SVGFETileElement,
feTurbulence: SVGFETurbulenceElement,
filter: SVGFilterElement,
foreignObject: SVGForeignObjectElement,
g: SVGGElement,
image: SVGImageElement,
line: SVGLineElement,
linearGradient: SVGLinearGradientElement,
marker: SVGMarkerElement,
mask: SVGMaskElement,
metadata: SVGMetadataElement,
mpath: SVGMPathElement,
path: SVGPathElement,
pattern: SVGPatternElement,
polygon: SVGPolygonElement,
polyline: SVGPolylineElement,
radialGradient: SVGRadialGradientElement,
rect: SVGRectElement,
script: SVGScriptElement,
set: SVGSetElement,
stop: SVGStopElement,
style: SVGStyleElement,
svg: SVGSVGElement,
switch: SVGSwitchElement,
symbol: SVGSymbolElement,
text: SVGTextElement,
textPath: SVGTextPathElement,
title: SVGTitleElement,
tspan: SVGTSpanElement,
use: SVGUseElement,
view: SVGViewElement,
};
export type HTMLs = HTMLElementTagNameMap
export type SVGs = SVGElementTagNameMap
export type MathMLs = MathMLElementTagNameMap
export type Tags = keyof (HTMLs | SVGs | MathMLs)
export type HTMLByTag<K extends keyof HTMLs> = HTMLs[K]
export type SVGByTag<K extends keyof SVGs> = SVGs[K]
export type MathMLByTag<K extends keyof MathMLs> = MathMLs[K]
export type ElementByTag<K extends Tags> = K extends keyof HTMLs
? HTMLByTag<K>
: K extends keyof SVGs
? SVGByTag<K>
: K extends keyof MathMLs
? MathMLByTag<K>
: Element
export type ElementFn<K extends Tags> = (props: object) => ElementByTag<K>
export type ElementType = 'html' | 'svg' | 'mathMl' | 'custom'
export type TagsByType<K extends ElementType> = K extends 'html'
? keyof HTMLs
: K extends 'svg'
? keyof SVGs
: K extends keyof MathMLs
? keyof MathMLs
: keyof Tags
export type Constructor<T> = new (...args: any[]) => T
/// <reference types="vite/client" />
type HTMLs = HTMLElementTagNameMap;
type MathMLs = MathMLElementTagNameMap;
type SVGs = SVGElementTagNameMap;
type HTMLByTag<K extends keyof HTMLs> = HTMLs[K];
type MathMLByTag<K extends keyof MathMLs> = MathMLs[K];
type SVGByTag<K extends keyof SVGs> = SVGs[K];
declare namespace JSX {
type Element = ReturnType<<T>(props: T) => Node>;
type IntrinsicElements = {
[K in keyof (HTMLs & SVGs & MathMLs)]: K extends keyof HTMLs
? Partial<HTMLByTag<K>>
: K extends keyof SVGs
? Partial<SVGByTag<K>>
: K extends keyof MathMLs
? Partial<MathMLByTag<K>>
: Partial<HTMLByTag<K> | SVGByTag<K> | MathMLByTag<K>>;
};
}
interface CustomConstructor<K extends keyof HTMLs>
extends CustomElementConstructor {
new (...params: any[]): HTMLByTag<K>;
prototype: HTMLByTag<K>;
}
declare const root: HTMLDivElement;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment