This release is 3 versions behind 0.8.1 — the latest version of @mizu/internal. Jump to latest
Built and signed on GitHub ActionsBuilt and signed on GitHub Actions
Built and signed on GitHub Actions
🦮 Internal components for mizu.js
This package works with Cloudflare Workers, Node.js, Deno, Bun, Browsers
JSR Score
100%
Published
2 months ago (0.7.1)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225// Imports import type { DeepReadonly, Promisable } from "jsr:/@libs/typing@3/types" import type { AttrTypings, Context, InferAttrTypings, Renderer, State } from "./renderer.ts" import { Phase } from "./phase.ts" export { Phase } export type { DeepReadonly, Promisable } /** * A directive implements a custom behaviour for a matching {@link https://developer.mozilla.org/docs/Web/HTML/Attributes | HTML attribute}. * * For more information, see the {@link https://mizu.sh/#concept-directive | mizu.sh documentation}. */ // deno-lint-ignore no-explicit-any export interface Directive<Cache = any, Typings extends AttrTypings = any> { /** * Directive name. * * The {@linkcode Renderer.render()} method uses this value to determine whether {@linkcode Directive.execute()} should be called for the processed node. * * The name should be prefixed to avoid conflicts with regular attribute names and must be unique among other {@linkcode Renderer.directives}. * {@linkcode Renderer.load()} will use this value to check whether the directive is already loaded. * * If the directive name is dynamic, a `RegExp` may be used instead of a `string`. * In this case, {@linkcode Directive.prefix} should be specified. * * ```ts * const foo = { * name: "*foo", * phase: Phase.UNKNOWN, * } as Directive & { name: string } * ``` */ readonly name: string | RegExp /** * Directive prefix. * * It is used as a hint for {@linkcode Renderer.parseAttribute()} to strip prefix from {@linkcode https://developer.mozilla.org/docs/Web/API/Attr/name | Attr.name} when parsing the directive. * * If {@linkcode Directive.name} is a `RegExp`, this property shoud be specified. * * ```ts * const foo = { * name: /^~(?<bar>)/, * prefix: "~", * phase: Phase.UNKNOWN, * } as Directive & { name: RegExp, prefix: string } * ``` */ readonly prefix?: string /** * Directive phase. * * Directives are executed in ascending order based on their {@linkcode Phase}. * * > [!IMPORTANT] * > Directives with {@linkcode Phase.UNKNOWN} and {@linkcode Phase.META} are ignored by {@linkcode Renderer.load()}. * > * > {@linkcode Phase.TESTING} is intended for testing purposes only. * * For more information, see the {@link https://mizu.sh/#concept-renderering-phase | mizu.sh documentation}. * * ```ts * const foo = { * name: "*foo", * phase: Phase.CONTENT, * } as Directive & { name: string } * ``` */ readonly phase: Phase /** * Indicates whether the directive can be specified multiple times on the same node. * * If set to `false`, a warning will be issued to users attempting to apply it more than once. * * ```ts * const foo = { * name: /^\/(?<value>)/, * prefix: "/", * phase: Phase.UNKNOWN, * multiple: true * } as Directive & { name: RegExp; prefix: string } * ``` */ readonly multiple?: boolean /** * Typings for directive parsing. * * For more information, see {@linkcode Renderer.parseAttribute()}. * * ```ts * const typings = { * type: Boolean, * modifiers: { * foo: { type: Boolean, default: false }, * } * } * * const foo = { * name: "*foo", * phase: Phase.UNKNOWN, * typings, * async execute(renderer, element, { attributes: [ attribute ], ...options }) { * console.log(renderer.parseAttribute(attribute, this.typings, { modifiers: true })) * } * } as Directive<null, typeof typings> & { name: string } * ``` */ readonly typings?: Typings /** * Default value. * * This value should be used by directive callbacks when the {@linkcode https://developer.mozilla.org/docs/Web/API/Attr/value | Attr.value} is empty. * * ```ts * const foo = { * name: "*foo", * phase: Phase.UNKNOWN, * default: "bar", * async execute(renderer, element, { attributes: [ attribute ], ...options }) { * console.log(attribute.value || this.default) * } * } as Directive & { name: string; default: string } * ``` */ readonly default?: string /** * Directive initialization callback. * * This callback is executed once during when {@linkcode Renderer.load()} loads the directive. * It should be used to set up dependencies, instantiate directive-specific caches (via {@linkcode Renderer.cache()}), and perform other initialization tasks. * * If a cache is instantiated, it is recommended to use the `Directive<Cache>` generic type to ensure type safety when accessing it in {@linkcode Directive.setup()}, {@linkcode Directive.execute()}, and {@linkcode Directive.cleanup()}. * * ```ts * const foo = { * name: "*foo", * phase: Phase.UNKNOWN, * async init(renderer) { * renderer.cache(this.name, new WeakSet()) * }, * } as Directive<WeakSet<HTMLElement | Comment>> & { name: string } * ``` */ readonly init?: (renderer: Renderer) => Promisable<void> /** * Directive setup callback. * * This callback is executed during {@linkcode Renderer.render()} before any {@linkcode Directive.execute()} calls. * * A partial object can be returned to update the rendering {@linkcode State}, and the eligibility. * * If `false` is returned, the entire rendering process for this node is halted. * * > [!IMPORTANT] * > This method is executed regardless of the directive's presence on the node. * * ```ts * const foo = { * name: "*foo", * phase: Phase.UNKNOWN, * async setup(renderer, element, { cache, context, state }) { * if ((!renderer.isHtmlElement(element)) || (element.hasAttribute("no-render"))) { * return false * } * }, * } as Directive & { name: string } * ``` */ readonly setup?: (renderer: Renderer, element: HTMLElement | Comment, _: { cache: Cache; context: Context; state: DeepReadonly<State> }) => Promisable<void | Partial<{ state: State; execute: boolean } | false>> /** * Directive execution callback. * * This callback is executed during {@linkcode Renderer.render()} if the rendered node has been marked as eligible. * * A node is considered eligible if at least one of the following conditions is met: * - {@linkcode Directive.setup()} returned `{ execute: true }`. * - {@linkcode Directive.setup()} did not return an `execute` value and the element has at least one attribute matching the directive name. * * A partial object can be returned to update the rendering {@linkcode Context}, {@linkcode State}, and the rendered {@linkcode https://developer.mozilla.org/docs/Web/API/HTMLElement | HTMLElement} (or {@linkcode https://developer.mozilla.org/docs/Web/API/Comment | Comment}). * * If `final: true` is returned, the rendering process for this node is stopped (all {@linkcode Directive.cleanup()} will still be called). * * ```ts * const foo = { * name: "*foo", * phase: Phase.UNKNOWN, * async execute(renderer, element, { attributes: [ attribute ], ...options }) { * console.log(`${await renderer.evaluate(element, attribute.value || "''", options)}`) * return { state: { $foo: true } } * }, * } as Directive & { name: string } * ``` */ readonly execute?: ( renderer: Renderer, element: HTMLElement | Comment, _: { cache: Cache; context: Context; state: DeepReadonly<State>; attributes: Readonly<Attr[]> }, ) => Promisable<void | Partial<{ element: HTMLElement | Comment; context: Context; state: State; final: boolean }>> /** * Directive cleanup callback. * * This callback is executed during {@linkcode Renderer.render()} after all {@linkcode Directive.execute()} have been applied and all {@linkcode https://developer.mozilla.org/docs/Web/API/Node/childNodes | Element.childNodes} have been processed. * * > [!IMPORTANT] * > This method is executed regardless of the directive's presence on the node, and regardless of whether a {@linkcode Directive.execute()} returned with `final: true`. * * ```ts * const foo = { * name: "*foo", * phase: Phase.UNKNOWN, * async cleanup(renderer, element, { cache, context, state }) { * console.log("Cleaning up") * } * } as Directive & { name: string } * ``` */ readonly cleanup?: (renderer: Renderer, element: HTMLElement | Comment, _: { cache: Cache; context: Context; state: DeepReadonly<State> }) => Promisable<void> } /** Extracts the cache type from a {@linkcode Directive}. */ export type Cache<T> = T extends Directive<infer U> ? U : never /** Extracts the typings values from a {@linkcode Directive}. */ // deno-lint-ignore no-explicit-any export type Modifiers<T> = T extends Directive<any, infer U> ? InferAttrTypings<U>["modifiers"] : never