= {
- __error: `❌ cannot contain block elements. Only inline elements are allowed in
.`;
- __invalidType: T;
-};
-
-declare const pBrand: unique symbol;
-
-export type P = T extends Div
- ? InvalidPContent
- : T extends Html
- ? InvalidPContent
- : T extends Body
- ? InvalidPContent
- : T extends HTMLElement[]
- ? {
- [pBrand]: 'p';
- children: T;
- }
- : T extends HTMLElement
- ? {
- [pBrand]: 'p';
- children: T;
- }
- : never;
-
-// HTML JSON構造体
-export type HtmlJson = {
- tag: string;
- children: (HtmlJson | string)[];
-};
-
-// HTML レンダリング関数
-export function renderToHtml(input: HtmlJson, indent: number = 0): string {
- const space = ' '.repeat(indent * 2);
- return `${space}<${input.tag}>
-${input.children
- .map((item) => {
- if (typeof item === 'string') {
- return `${space} ${item}`;
- } else {
- return renderToHtml(item, indent + 1);
- }
- })
- .join('\n')}
-${space}${input.tag}>`;
-}
-
-export function renderToStream(
- input: HtmlJson,
- writeStream: { write: (data: string) => void },
- indent: number = 0
-) {
- const space = ' '.repeat(indent * 2);
- writeStream.write(`${space}<${input.tag}>\n`);
- input.children.forEach((item) => {
- if (typeof item === 'string') {
- writeStream.write(`${space} ${item}\n`);
- } else {
- renderToStream(item, writeStream, indent + 1);
- }
- });
- writeStream.write(`${space}${input.tag}>\n`);
-}
diff --git a/src/html/attributes.ts b/src/html/attributes.ts
new file mode 100644
index 0000000..1a11144
--- /dev/null
+++ b/src/html/attributes.ts
@@ -0,0 +1,143 @@
+export interface GlobalAttributes {
+ id?: string;
+ class?: string;
+ style?: string;
+ title?: string;
+ lang?: string;
+ dir?: 'ltr' | 'rtl' | 'auto';
+ tabindex?: number;
+ accesskey?: string;
+ contenteditable?: boolean | 'true' | 'false';
+ draggable?: boolean | 'true' | 'false';
+ hidden?: boolean;
+ spellcheck?: boolean | 'true' | 'false';
+ translate?: 'yes' | 'no';
+ [key: `data-${string}`]: string | number | boolean;
+ role?: string;
+ [key: `aria-${string}`]: string | number | boolean;
+}
+
+export interface EventAttributes {
+ onclick?: string;
+ ondblclick?: string;
+ onmousedown?: string;
+ onmouseup?: string;
+ onmouseover?: string;
+ onmousemove?: string;
+ onmouseout?: string;
+ onkeydown?: string;
+ onkeyup?: string;
+ onkeypress?: string;
+ onfocus?: string;
+ onblur?: string;
+ onchange?: string;
+ onsubmit?: string;
+ onload?: string;
+ onunload?: string;
+ onresize?: string;
+ onscroll?: string;
+}
+
+export interface FormAttributes {
+ name?: string;
+ value?: string | number;
+ type?: string;
+ placeholder?: string;
+ required?: boolean;
+ disabled?: boolean;
+ readonly?: boolean;
+ checked?: boolean;
+ selected?: boolean;
+ maxlength?: number;
+ minlength?: number;
+ max?: number | string;
+ min?: number | string;
+ step?: number | string;
+ pattern?: string;
+ autocomplete?: 'on' | 'off' | string;
+ autofocus?: boolean;
+ multiple?: boolean;
+ size?: number;
+ form?: string;
+ formaction?: string;
+ formenctype?: string;
+ formmethod?: 'get' | 'post';
+ formnovalidate?: boolean;
+ formtarget?: '_blank' | '_self' | '_parent' | '_top' | string;
+}
+
+export interface LinkResourceAttributes {
+ href?: string;
+ src?: string;
+ srcset?: string;
+ sizes?: string;
+ media?: string;
+ rel?: string;
+ type?: string;
+ download?: boolean | string;
+ target?: '_blank' | '_self' | '_parent' | '_top' | string;
+ hreflang?: string;
+ referrerpolicy?:
+ | 'no-referrer'
+ | 'no-referrer-when-downgrade'
+ | 'origin'
+ | 'origin-when-cross-origin'
+ | 'same-origin'
+ | 'strict-origin'
+ | 'strict-origin-when-cross-origin'
+ | 'unsafe-url';
+}
+
+export interface MediaAttributes {
+ alt?: string;
+ width?: number | string;
+ height?: number | string;
+ loading?: 'eager' | 'lazy';
+ decoding?: 'sync' | 'async' | 'auto';
+ crossorigin?: 'anonymous' | 'use-credentials';
+ usemap?: string;
+ ismap?: boolean;
+ autoplay?: boolean;
+ controls?: boolean;
+ loop?: boolean;
+ muted?: boolean;
+ preload?: 'none' | 'metadata' | 'auto';
+ poster?: string;
+}
+
+export interface TableAttributes {
+ colspan?: number;
+ rowspan?: number;
+ headers?: string;
+ scope?: 'row' | 'col' | 'rowgroup' | 'colgroup';
+}
+
+export interface MetadataAttributes {
+ charset?: string;
+ content?: string;
+ 'http-equiv'?: string;
+ property?: string;
+}
+
+export interface ListAttributes {
+ start?: number;
+ reversed?: boolean;
+ type?: '1' | 'a' | 'A' | 'i' | 'I';
+}
+
+export interface EmbeddedAttributes {
+ sandbox?: string;
+ allow?: string;
+ allowfullscreen?: boolean;
+ allowpaymentrequest?: boolean;
+}
+
+export type AllHTMLAttributes = GlobalAttributes &
+ EventAttributes &
+ FormAttributes &
+ LinkResourceAttributes &
+ MediaAttributes &
+ TableAttributes &
+ MetadataAttributes &
+ ListAttributes &
+ EmbeddedAttributes;
diff --git a/src/html/index.ts b/src/html/index.ts
new file mode 100644
index 0000000..343e99f
--- /dev/null
+++ b/src/html/index.ts
@@ -0,0 +1,3 @@
+export * from './attributes';
+export * from './tags';
+export * from './util';
diff --git a/src/html/tags.ts b/src/html/tags.ts
new file mode 100644
index 0000000..fecead5
--- /dev/null
+++ b/src/html/tags.ts
@@ -0,0 +1,304 @@
+import type { AllHTMLAttributes } from './attributes';
+
+export type Text = string;
+
+export type HTMLElement =
+ | Text
+ | {
+ children: (HTMLElement | Text)[] | HTMLElement | Text;
+ attributes?: AllHTMLAttributes;
+ };
+
+declare const htmlBrand: unique symbol;
+export type Html<
+ T extends HTMLElement[] | HTMLElement,
+ A extends AllHTMLAttributes = {},
+> = T extends HTMLElement[]
+ ? {
+ [htmlBrand]: 'html';
+ children: T;
+ attributes: A;
+ }
+ : T extends HTMLElement
+ ? {
+ [htmlBrand]: 'html';
+ children: T;
+ attributes: A;
+ }
+ : T extends Text
+ ? {
+ [htmlBrand]: 'html';
+ children: T;
+ attributes: A;
+ }
+ : never;
+
+declare const bodyBrand: unique symbol;
+export type Body<
+ T extends HTMLElement[] | HTMLElement,
+ A extends AllHTMLAttributes = {},
+> = T extends Html
+ ? never
+ : T extends HTMLElement[]
+ ? {
+ [bodyBrand]: 'body';
+ children: T;
+ attributes: A;
+ }
+ : T extends HTMLElement
+ ? {
+ [bodyBrand]: 'body';
+ children: T;
+ attributes: A;
+ }
+ : never;
+
+type InvalidDivContent = {
+ __error: `❌ cannot contain or elements. Invalid HTML structure.`;
+ __invalidType: T;
+};
+
+declare const divBrand: unique symbol;
+
+export type Div<
+ T extends HTMLElement[] | HTMLElement,
+ A extends AllHTMLAttributes = {},
+> = T extends Html
+ ? InvalidDivContent
+ : T extends Body
+ ? InvalidDivContent
+ : T extends HTMLElement[]
+ ? {
+ [divBrand]: 'div';
+ children: T;
+ attributes: A;
+ }
+ : T extends HTMLElement
+ ? {
+ [divBrand]: 'div';
+ children: T;
+ attributes: A;
+ }
+ : never;
+
+type InvalidPContent = {
+ __error: `❌ cannot contain block elements. Only inline elements are allowed in
.`;
+ __invalidType: T;
+};
+
+declare const pBrand: unique symbol;
+
+export type P<
+ T extends HTMLElement[] | HTMLElement,
+ A extends AllHTMLAttributes = {},
+> = T extends Div
+ ? InvalidPContent
+ : T extends Html
+ ? InvalidPContent
+ : T extends Body
+ ? InvalidPContent
+ : T extends HTMLElement[]
+ ? {
+ [pBrand]: 'p';
+ children: T;
+ attributes: A;
+ }
+ : T extends HTMLElement
+ ? {
+ [pBrand]: 'p';
+ children: T;
+ attributes: A;
+ }
+ : never;
+
+type InvalidH1Content = {
+ __error: `❌