All files / src h.ts

100% Statements 69/69
100% Branches 38/38
100% Functions 3/3
100% Lines 69/69

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89  1x 1x         30x 30x 9x 9x   30x 5x   5x 12x 12x 5x 5x 5x   16x 16x   36x 36x 36x 36x   36x 46x 1x 46x   18x 29x 29x 4x 4x 4x 29x 1x 1x 29x   1x 24x   12x   11x 11x 7x 10x   4x 4x 12x   1x 1x 23x   11x 1x 11x 10x 10x 11x 29x 45x 27x 27x 46x   36x 4x 4x 36x 36x 36x   1x 1x 36x 36x 1x  
/* eslint-disable @typescript-eslint/no-explicit-any */
import { bindEvent, bindAttr, bindClassList, bindStyle } from './dom/bindings';
import { createEffect } from './primitives/effect';
import type { SignalGetter } from './types';
 
type Child = Node | string | number | SignalGetter<any> | (() => Node);
 
function processChild(child: Child): Node {
  if (child instanceof Node) {
    return child;
  }
  // Check if it's a function (could be a signal or a memo)
  if (typeof child === 'function') {
    const textNode = document.createTextNode('');
    // Use createEffect directly for text binding to avoid circular dependencies if h is used inside a binder
    createEffect(() => {
      const value = (child as SignalGetter<any>)();
      textNode.textContent = value === null || value === undefined ? '' : String(value);
    });
    return textNode;
  }
  // For static strings and numbers
  return document.createTextNode(String(child));
}
 
function hyperscript(tag: string) {
  return (...args: any[]): HTMLElement => {
    const el = document.createElement(tag);
    let ref: ((el: Element) => void) | undefined;
 
    for (const arg of args) {
      if (Array.isArray(arg)) {
        arg.forEach(child => el.appendChild(processChild(child)));
      } else if (typeof arg === 'object' && arg !== null && !(arg instanceof Node)) {
        // This is the attributes object
        for (const key in arg) {
          const value = arg[key];
          if (key === 'ref') {
            ref = value;
            continue; // Don't process ref as a normal attribute
          }
          if (key.startsWith('on')) {
            const eventName = key.substring(2).toLowerCase();
            bindEvent(el, eventName as any, value);
          } else if (key === 'classList') {
            // Special handling for classList
            bindClassList(el, value);
          } else if (key === 'style') {
            // Special handling for style object
            if (typeof value === 'object') {
              // Check if any style properties are signals
              const isReactive = Object.values(value).some(v => typeof v === 'function');
              if (isReactive) {
                bindStyle(el, value);
              } else {
                // It's a static style object
                Object.assign(el.style, value);
              }
            } else {
              // It's a static style string
              el.setAttribute('style', value);
            }
          } else {
            // Handle all other attributes
            if (typeof value === 'function') {
              bindAttr(el, key, value);
            } else {
              el.setAttribute(key, String(value));
            }
          }
        }
      } else {
        el.appendChild(processChild(arg as Child));
      }
    }
 
    if (ref) {
      ref(el);
    }
    return el;
  };
}
 
export const h = new Proxy({}, {
  get(_target, prop: string) {
    return hyperscript(prop);
  }
}) as Record<string, (...args: any[]) => HTMLElement>;