@oazmi/tsignal
Version:
a topological order respecting signals library inspired by SolidJS
145 lines (144 loc) • 6.36 kB
TypeScript
/** a minimal implementation of JSX runtime element creation. <br>
* to use in `esbuild`'s javascript build API, you will need to do one of the following options (or do both):
*
* 1) option 1 (preferred): <br>
* for JSX to work with your IDE's LSP, and for esbuild to automatically discover the hyperscript functions,
* you will need to include the following two comment lines at the top of your `.tsx` script:
* ```tsx
* /** \@jsx h *\/
* /** \@jsxFrag hf *\/
* ```
*
* 2) option 2 (no LSP support): <br>
* in the esbuild build options (`BuildOptions`), set `jsxFactory = "h"` and `jsxFragment = "hf"`.
* ```ts
* import { build, stop } from "https://deno.land/x/esbuild/mod.js"
* build({
* entryPoints: ["./path/to/your/script.tsx"],
* jsxFactory: "h",
* jsxFragment: "Fragment",
* // other build options
* minify: true,
* })
* stop()
* ```
*
* and now in your `.jsx` script, you should:
* - import `createHyperScript` from this module
* - create a reactive signal `Context`
* - call `createHyperScript` with the signal context `ctx` as the argument
* - the returned tuple will contain 3 elements:
* - the first element should be named `h` (which is the name you declare as `\@jsx h` in **option 1** or `jsxFactory = "h"` in **option 2**)
* - the second element should be named `hf` (which is the name you declare as `\@jsxFrag hf` in **option 1** or `jsxFragment = "hf"` in **option 2**)
* - the third can be named anything
*
* @example
* ```tsx
* // the `\@jsx h` comment comes here, but I can't show multiline comments in this documentation.
* // the `\@jsxFrag hf` comment comes here, but I can't show multiline comments in this documentation.
*
* import { createHyperScript } from "./path/to/tsignal/jsx-runtime/mod.ts"
* import { Context } from "./path/to/tsignal/mod.ts"
*
* const ctx = new Context()
* const [h, hf, namespaceStack] = createHyperScript(ctx)
*
* const my_elem = <div>Hello world</div>
* const my_fragment_elems = <>
* <span>World<span>
* <span>Hello<span>
* </>
* const my_elem2 = <div>...my_fragment_elems</div>
* document.body.appendChild(my_elem)
* document.body.appendChild(my_elem2)
*
* // when creating svgs or xml, you will have to change the DOM namespace, so that the correct kinds of `Node`s are created.
* namespaceStack.push("svg")
* const my_svg = <svg viewBox="0 0 200 200">
* <g transform="translate(100, 50)">
* <text text-anchor="middle">SVG says Hi!</text>
* <text y="25" text-anchor="middle">SVG stands for "SUGOI! Vector Graphics"</text>
* </g>
* </svg>
* namespaceStack.pop()
* ```
*
* @module
*/
import { Context } from "../context.js";
import { Stringifyable } from "../deps.js";
import { Accessor } from "../typedefs.js";
type AttributeKey = string;
interface Attributes {
[key: AttributeKey]: Stringifyable | Accessor<Stringifyable>;
}
type IntrinsicHTMLElements = {
[tagName in keyof HTMLElementTagNameMap]: Attributes;
};
type IntrinsicSVGElements = {
[tagName in keyof SVGElementTagNameMap]: Attributes;
};
/** a minimal implementation of `JSX.IntrinsicElements` to get syntax highlighting in your `.jsx` and `.tsx` files. <br>
* to use this, and banish all the red error lines under your jsx blocks, simply import this file.
*
* ### how to get it working:
*
* - create the file `global.d.ts` in the root of your source code. so for instance: `/src/global.d.ts` is one possibility.
* - add the following lines to it:
* ```ts
* // file: `/src/global.d.ts`
* import { JSX as JSXInternal } from "@jsr:@oazmi/tsignal/jsx-runtime"
* type IntrinsicElements = JSXInternal.IntrinsicElements
* export as namespace JSX
* ```
*
* - and now, with the global declaration done, your IDE should pick up on the `JSX.IntrinsicElements` definitions.
* however, if it doesn't in certain files, add the following line to the top of that file, so that you can explicitly reference your `global.d.ts` file.
* ```tsx
* // file `/src/components/navigation_bar.tsx`
* /// <reference path="../global.d.ts" />
* ```
*
* @example
* ```tsx
* // file `/src/components/navigation_bar.tsx`
* /// <reference path="../global.d.ts" />
*
* const my_div = <div>
* <span>Hello</span>
* <span>World!!</span>
* </div>
* ```
*/
export declare namespace JSX {
/** {@inheritDoc JSX} */
type IntrinsicElements = IntrinsicHTMLElements & IntrinsicSVGElements;
}
/** a reactive text can either be a stringifyable signal `Accessor`, or a regular stringifyable value (which will get upgraded to an accessor). */
export type ReactiveText = Stringifyable | Accessor<Stringifyable>;
/** the `props` of an `HTMLElement` are simply their attributes */
export type ElementAttrs = {
[attribute_key: Attr["name"]]: ReactiveText;
};
type SingleComponentGenerator<P = {}> = (props?: P) => Element;
type FragmentComponentGenerator<P = {}> = (props?: P) => Element[];
/** a function that generates one `HTMLElement` or an array of them. */
export type ComponentGenerator<P = {}> = SingleComponentGenerator<P> | FragmentComponentGenerator<P>;
type ElementChild = ReactiveText | Node;
type ElementChildren = Array<ElementChild | ElementChild[]>;
/** function signature of the fragment element generator (array of elements). */
export type HyperScript_CreateFragment = (props: object, ...elements: Node[]) => (typeof elements);
/** function signature of the hyperscript DOM generator function. */
export interface HyperScript_CreateElement {
(html_tag: string, attrs?: ElementAttrs | null, ...children: ElementChildren): HTMLElement;
<P = {}>(component: SingleComponentGenerator<P>, props?: P | null, ...children: ElementChildren): Element;
<P = {}>(fragment_component: FragmentComponentGenerator<P>, props?: P | null, ...siblings: ElementChildren): Element[];
<P = {}>(component: ComponentGenerator<P>, props?: P | null, ...children: ElementChildren): ReturnType<typeof component>;
}
/** create hyperscript functions to create elements and fragments during runtime after your JSX or TSX have been transformed. */
export declare const createHyperScript: (ctx: Context) => readonly [HyperScript_CreateElement, HyperScript_CreateFragment, {
push: (...args: "svg"[]) => number;
pop: () => "svg" | undefined;
seek: () => "svg" | undefined;
}];
export {};