UNPKG

campfire.js

Version:
205 lines (204 loc) 7.88 kB
import { NuBuilder } from "./dom/NuBuilder.ts"; import type { CfHTMLElementInterface } from "./dom/config.ts"; import { Store } from "./stores/mod.ts"; export interface UpdateEvent<ST> { type: "update"; value: ST extends Store<infer T> ? T : never; } export type AppendEvent<ST> = ST extends Store<infer T> ? (T extends Array<infer L> ? { type: "append"; value: L; idx: number; } : { type: "append"; value: T; }) : never; export type ChangeEvent<ST> = ST extends Store<infer T> ? (T extends Record<string, infer M> ? { type: "change"; value: M; key: string; } : (T extends Array<infer L> ? { type: "change"; value: L; idx: number; } : { type: "change"; value: T; })) : never; export type DeletionEvent<ST> = ST extends Store<infer T> ? T extends Record<string, infer M> ? { type: "deletion"; value: M; key: string; } : T extends Array<infer L> ? { type: "deletion"; value: L; idx: number; } : { type: "deletion"; value: T; } : never; export interface ClearEvent { type: "clear"; } export type StoreEvent<ST> = ChangeEvent<ST> | DeletionEvent<ST> | AppendEvent<ST> | UpdateEvent<ST> | ClearEvent; export type EventType = StoreEvent<any>["type"]; export type SubscriberTypeMap<ST> = { [K in EventType]: Extract<StoreEvent<ST>, { type: K; }>; }; /** A signature for a subscriber function. */ export type AnySubscriber<ST> = (event: StoreEvent<ST>) => void; /** A signature for a subscriber of a specific event. */ export type EventSubscriber<K extends EventType, ST> = (event: SubscriberTypeMap<ST>[K]) => void; /** The function signature for a function returned by `template()`. */ export type Template = (e: Record<string, any>) => string; export type UnwrapStore<D> = { [K in keyof D]: D[K] extends Store<infer V> ? V : never; }; export type StoreEventFromObject<D> = { [K in keyof D]: D[K] extends Store<any> ? StoreEvent<D[K]> : never; }[keyof D]; type NuBuilderInstance<Elem extends HTMLElement, Deps extends Record<string, Store<any>>> = NuBuilder<Elem, Deps, string>; export type RenderBuilder<Elem extends HTMLElement, Deps> = Omit<NuBuilderInstance<Elem, Deps extends Record<string, Store<any>> ? Deps : never>, "children" | "done" | "ref" | "on" | "gimme" | "deps" | "render">; /** * Function signature for rendering reactive content. * * @template Elem The type of HTML element being rendered * @template Deps The type of the dependencies object * * @param props The unwrapped values from the store dependencies * @param opts Additional options and utilities for rendering: * - event: The event that triggered this render (if applicable) * - b: A builder instance for fluent element modification * - elt: Reference to the element being rendered * - first: Boolean flag indicating whether this is the first render (true) or a re-render (false) * * @returns A string, builder instance, or void * * @example * ```js * // Using the first parameter to conditionally render initial state * const [element] = cf.nu("div") * .deps({ count: counterStore }) * .render(({ count }, { b, first }) => { * // Do one-time setup on first render * if (first) { * b.attr("data-initialized", "true"); * b.style("transition", "all 0.3s ease"); * } * * return b.content(`Count is: ${count}`); * }) * .done(); * ``` */ export type RenderFunction<Elem extends HTMLElement, Deps extends Record<string, Store<any>>> = (props: UnwrapStore<Deps>, opts: { event?: StoreEventFromObject<Deps> & { triggeredBy: string; }; b: RenderBuilder<Elem, Deps>; elt: Elem; first: boolean; }) => string | RenderBuilder<Elem, Deps> | void; export type StringStyleProps = keyof { [K in keyof CSSStyleDeclaration as CSSStyleDeclaration[K] extends string ? K : never]: true; }; export type DOMEventHandlers = { [K in keyof HTMLElementEventMap]?: (event: HTMLElementEventMap[K]) => void; }; /** * Properties for the HTML element to be created. */ export interface ElementProperties<T extends HTMLElement, D extends Record<string, Store<any>>> { /** * String that will be set as the inner HTML of the created element. By default, * this is escaped using cf.escape() - however, if you supply `raw: true` in * the args object passed as nu's second argument, escaping is disabled. */ contents?: string; /** * Rendering function to use to update the element on redraws. * As of 4.0.0-rc15, render functions receive a 'first' parameter in the opts * object that indicates if this is the first render (true) or a re-render (false). */ render?: RenderFunction<T, D>; /** * Classes for the newly created element. Will be combined with whatever is * passed into nu(). */ classes?: Record<string, boolean>; /** * Whether or not to escape the `contents` string. If `raw` is true, the * string is not escaped. */ raw?: boolean; /** Miscellaneous properties to set on the created element, * for example, `type: "button"` or `checked: true` */ misc?: Record<string, unknown>; /** * Contains styles that will be applied to the new element. Property names * must be the same as those in `CSSStyleDeclaration`. */ style?: Partial<Record<StringStyleProps, string | number>>; /** * An object containing event handlers that will be applied using addEventListener. * For example: `{'click': (e) => console.log(e)}` */ on?: DOMEventHandlers; /** Attributes that will be set on the element using `Element.setAttribute`. */ attrs?: Record<string, string | number | boolean>; /** * A list of elements to query from the element. Will be returned as * subsequent members of the returned Array after the element itself. * Returns null when a selector isn't found to preserve order of returned * elements. */ gimme?: string[]; /** * A Record<string, Store> of the element's dependencies. The element's * render function will be called every time any of the deps change. */ deps?: D; /** * Children of the element to mount. They will be mounted into `cf-slot`s * corresponding to the Record's keys and preserved between re-renders of * the parent. * * As of 4.0.0-rc15, you can provide an array of elements for a slot, and they * will all be mounted to that slot in order. * * Exercise caution when passing nu().done() directly - all children * returned will be mounted to the element. */ children?: Record<string, CfHTMLElementInterface | CfHTMLElementInterface[]>; /** * A user-supplied ID to track the element by its reference. */ track?: string; } /** * An interface to store data parsed from an element descriptor string passed to `nu`. * @internal */ export interface TagStringParseResult { /** The tagName parsed from the info string. */ tag?: string | undefined; /** The id parsed from the info string. */ id?: string | undefined; /** An array of classes parsed from the info string. */ classes?: string[] | undefined; } export type ElementPosition = { before: CfHTMLElementInterface; } | { after: CfHTMLElementInterface; } | { into: CfHTMLElementInterface; at?: "start"; }; type TagName = keyof HTMLElementTagNameMap; export type EltInfoToTag<T extends string> = T extends `${infer Tag extends TagName}#${string}.${string}` ? Tag : T extends `${infer Tag extends TagName}#${string}` ? Tag : T extends `${infer Tag extends TagName}.${string}` ? Tag : T extends `${infer Tag extends TagName}` ? Tag : 'div'; export type InferElementType<T extends string> = T extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[EltInfoToTag<T>] : HTMLElement; export {};