summaryrefslogtreecommitdiff
path: root/src/frontend/h.ts
blob: 77ecf857b1be366e3e83999bf39e4f6eb8c9c682 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
export type HProps = Record<string, string> | null | undefined;
export type HChild = string | undefined | (() => string);
export type HMXComponent = (props: HProps, ...children: Array<HChild>) => string;
export type HTag = string | HMXComponent;

const voidTags = new Set(['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']);

function* _h(tag: string, attrs: HProps, children: Array<HChild>) {
  yield `<${tag}`;
  if (attrs) {
    yield ' ';
    for (let [key, value] of Object.entries(attrs)) {
      yield `${key}="${value}"`;
    }
  }
  yield '>';

  // Void tags have no children.
  if (voidTags.has(tag)) {
    return;
  }

  for (let child of children) {
    // Special: If a child is a function, call it automatically.
    // This lets you do {() => ...} without the extra boilerplate
    // of wrapping it in a function and calling it.
    if (typeof child === 'function') {
      yield child();
    } else if (typeof child === 'string') {
      yield child;
    } else if (!child) {
      // do nothing, safe to ignore falsey values.
    } else {
      yield child;
    }
  }

  yield `</${tag}>`;
}

export async function h(tag: HTag, attrs: HProps, ...pChildren: Array<Promise<HChild>>) {
  const children = await Promise.all(pChildren.flat(Infinity));
  if (typeof tag === 'function') {
    // We assume it's an hmx component
    return tag(attrs, ...children);
  }

  return Array.from(_h(tag, attrs, children)).join('');
}

export function Fragment(_: HProps, ...children: Array<string>) {
  return children.join('');
}