UNPKG

@alwatr/synapse

Version:

Connect your TypeScript classes to the DOM, declaratively.

8 lines (7 loc) 9.7 kB
{ "version": 3, "sources": ["../src/lib.ts", "../src/bootstrap.ts", "../src/directiveDecorator.ts", "../src/directiveClass.ts", "../src/queryDecorator.ts"], "sourcesContent": ["import {createLogger} from '@alwatr/logger';\n\nimport type {DirectiveConstructor} from './directiveDecorator.js';\nimport type {} from '@alwatr/type-helper';\n\n/**\n * Alwatr Synapse Logger.\n */\nexport const logger = /* #__PURE__ */ createLogger('alwatr/synapse');\n\n/**\n * The registry for all directives.\n */\nexport const directiveRegistry_: {selector: string; constructor: DirectiveConstructor}[] = [];\n", "import {directiveRegistry_, logger} from './lib.js';\n\nconst initializedAttribute = '_synapseConnected';\n\n/**\n * Initializes all registered directives within a given root element.\n * If no root element is provided, it scans the entire body.\n *\n * This function is idempotent; it will not re-initialize a directive on an element\n * that has already been processed.\n *\n * @param rootElement The element to scan for directives. Defaults to `document.body`.\n *\n * @example\n * ```ts\n * // Initialize directives on the whole page after the DOM is loaded.\n * document.addEventListener('DOMContentLoaded', () => bootstrapDirectives());\n *\n * // Or, initialize directives on a dynamically added part of the page.\n * const newContent = document.createElement('div');\n * newContent.innerHTML = '<div class=\"my-button\">Click Me</div>';\n * document.body.appendChild(newContent);\n *\n * bootstrapDirectives(newContent);\n * ```\n */\nexport function bootstrapDirectives(rootElement: Element | Document = document.body): void {\n logger.logMethod?.('bootstrapDirectives');\n\n for (const {selector, constructor} of directiveRegistry_) {\n try {\n const uninitializedSelector = `${selector}:not([${initializedAttribute}])`;\n const elements = rootElement.querySelectorAll<HTMLElement>(uninitializedSelector);\n if (elements.length === 0) continue;\n\n logger.logOther?.(`Found ${elements.length} new element(s) for directive \"${selector}\"`);\n elements.forEach((element) => {\n // Mark the element as processed before creating an instance\n element.setAttribute(initializedAttribute, 'true');\n // Instantiate the directive with the element.\n new constructor(element, selector);\n });\n }\n catch (err) {\n logger.error('bootstrapDirectives', 'directive_instantiation_error', err, {selector});\n }\n }\n}\n", "import {directiveRegistry_, logger} from './lib.js';\n\nimport type {DirectiveBase} from './directiveClass.js';\n\n/**\n * Type definition for a directive constructor.\n * A directive class must have a constructor that accepts an HTMLElement.\n */\nexport type DirectiveConstructor<T extends DirectiveBase = DirectiveBase> = new (element: HTMLElement, selector: string) => T;\n\n/**\n * A class decorator that registers a class as a directive.\n *\n * @param selector The CSS selector to which this directive will be attached.\n *\n * @example\n * ```ts\n * @directive('.my-button')\n * class MyButtonDirective extends DirectiveBase {\n * protected update_(): void {\n * this.element_.addEventListener('click', () => console.log('Button clicked!'));\n * }\n * }\n * ```\n */\nexport function directive(selector: string) {\n logger.logMethodArgs?.('@directive', selector);\n\n /**\n * The decorator function that receives the class constructor.\n * @param constructor The class to be registered as a directive.\n */\n return function (constructor: DirectiveConstructor): void {\n directiveRegistry_.push({selector, constructor});\n };\n}\n", "import {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\n/**\n * Base class for creating directives that attach behavior to DOM elements.\n * Extend this class to define custom directives.\n */\nexport abstract class DirectiveBase {\n /**\n * The CSS selector for the directive.\n */\n protected readonly selector_;\n\n /**\n * Logger instance for the directive.\n */\n protected readonly logger_;\n\n /**\n * The target DOM element this directive is attached to.\n */\n protected readonly element_: HTMLElement;\n\n /**\n * Constructor to initialize the directive with the target element.\n * @param element - The DOM element this directive is attached to.\n * @param selector - The CSS selector for the directive.\n */\n public constructor(element: HTMLElement, selector: string) {\n this.logger_ = createLogger(`directive:${selector}`);\n this.logger_.logMethodArgs?.('new', {selector, element});\n\n this.selector_ = selector;\n this.element_ = element;\n\n (async () => {\n await delay.nextMicrotask();\n await this.init_();\n await this.update_();\n })();\n }\n\n /**\n * Called to update the directive's state or behavior.\n * Must be implemented by subclasses.\n */\n protected abstract update_(): Awaitable<void>;\n\n protected init_(): Awaitable<void> {\n this.logger_.logMethod?.('init');\n }\n\n protected destroy_(): Awaitable<void> {\n this.logger_.logMethod?.('destroy');\n this.element_.remove();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this as any).element_ = null;\n }\n\n /**\n * Dispatches a custom event from the target element.\n * @param eventName - The name of the event.\n * @param detail - Optional data to include in the event.\n */\n protected dispatch_(eventName: string, detail?: unknown): void {\n this.logger_.logMethodArgs?.('dispatch_', {eventName, detail});\n this.element_.dispatchEvent(new CustomEvent(eventName, {detail, bubbles: true}));\n }\n}\n", "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {DirectiveBase} from './directiveClass.js';\n\n/**\n * A property decorator that queries the directive's element for a selector.\n * The query is performed once and the result is cached.\n *\n * @param selector The CSS selector to query for.\n * @param cache Whether to cache the result on first access. Defaults is true.\n *\n * @example\n * ```ts\n * @directive('[my-directive]')\n * class MyDirective extends DirectiveBase {\n * @query('.my-element')\n * protected myElement?: HTMLDivElement;\n * }\n * ```\n */\nexport function query(selector: string, cache = true) {\n return function (target: DirectiveBase, propertyKey: string): void {\n const privateKey = Symbol(`${String(propertyKey)}__`);\n\n Object.defineProperty(target, propertyKey, {\n get(this: DirectiveBase) {\n if (cache === false || (this as any)[privateKey] === undefined) {\n (this as any)[privateKey] = this.element_.querySelector(selector);\n }\n return (this as any)[privateKey];\n },\n configurable: true,\n enumerable: true,\n });\n };\n}\n\n/**\n * A property decorator that queries the directive's element for all selectors.\n * The queries are performed once and the result is cached.\n *\n * @param selector The CSS selector to query for.\n *\n * @example\n * ```ts\n * @directive('[my-directive]')\n * class MyDirective extends DirectiveBase {\n * @queryAll('.my-elements')\n * protected myElements?: NodeListOf<HTMLDivElement>;\n * }\n * ```\n */\nexport function queryAll(selector: string, cache = true) {\n return function (target: DirectiveBase, propertyKey: string): void {\n const privateKey = Symbol(`${String(propertyKey)}__`);\n\n Object.defineProperty(target, propertyKey, {\n get(this: DirectiveBase) {\n if (cache === false || (this as any)[privateKey] === undefined) {\n (this as any)[privateKey] = this.element_.querySelectorAll(selector);\n }\n return (this as any)[privateKey];\n },\n configurable: true,\n enumerable: true,\n });\n };\n}\n"], "mappings": ";AAAA,OAAQ,iBAAmB,iBAQpB,IAAM,OAAyB,aAAa,gBAAgB,EAK5D,IAAM,mBAA8E,CAAC,ECX5F,IAAM,qBAAuB,oBAwBtB,SAAS,oBAAoB,YAAkC,SAAS,KAAY,CACzF,OAAO,YAAY,qBAAqB,EAExC,SAAW,CAAC,SAAU,WAAW,IAAK,mBAAoB,CACxD,GAAI,CACF,MAAM,sBAAwB,GAAG,QAAQ,SAAS,oBAAoB,KACtE,MAAM,SAAW,YAAY,iBAA8B,qBAAqB,EAChF,GAAI,SAAS,SAAW,EAAG,SAE3B,OAAO,WAAW,SAAS,SAAS,MAAM,kCAAkC,QAAQ,GAAG,EACvF,SAAS,QAAS,SAAY,CAE5B,QAAQ,aAAa,qBAAsB,MAAM,EAEjD,IAAI,YAAY,QAAS,QAAQ,CACnC,CAAC,CACH,OACO,IAAK,CACV,OAAO,MAAM,sBAAuB,gCAAiC,IAAK,CAAC,QAAQ,CAAC,CACtF,CACF,CACF,CCtBO,SAAS,UAAU,SAAkB,CAC1C,OAAO,gBAAgB,aAAc,QAAQ,EAM7C,OAAO,SAAU,YAAyC,CACxD,mBAAmB,KAAK,CAAC,SAAU,WAAW,CAAC,CACjD,CACF,CCnCA,OAAQ,UAAY,gBACpB,OAAQ,gBAAAA,kBAAmB,iBAMpB,IAAe,cAAf,KAA6B,CAqB3B,YAAY,QAAsB,SAAkB,CACzD,KAAK,QAAUA,cAAa,aAAa,QAAQ,EAAE,EACnD,KAAK,QAAQ,gBAAgB,MAAO,CAAC,SAAU,OAAO,CAAC,EAEvD,KAAK,UAAY,SACjB,KAAK,SAAW,SAEf,SAAY,CACX,MAAM,MAAM,cAAc,EAC1B,MAAM,KAAK,MAAM,EACjB,MAAM,KAAK,QAAQ,CACrB,GAAG,CACL,CAQU,OAAyB,CACjC,KAAK,QAAQ,YAAY,MAAM,CACjC,CAEU,UAA4B,CACpC,KAAK,QAAQ,YAAY,SAAS,EAClC,KAAK,SAAS,OAAO,EAEpB,KAAa,SAAW,IAC3B,CAOU,UAAU,UAAmB,OAAwB,CAC7D,KAAK,QAAQ,gBAAgB,YAAa,CAAC,UAAW,MAAM,CAAC,EAC7D,KAAK,SAAS,cAAc,IAAI,YAAY,UAAW,CAAC,OAAQ,QAAS,IAAI,CAAC,CAAC,CACjF,CACF,ECjDO,SAAS,MAAM,SAAkB,MAAQ,KAAM,CACpD,OAAO,SAAU,OAAuB,YAA2B,CACjE,MAAM,WAAa,OAAO,GAAG,OAAO,WAAW,CAAC,IAAI,EAEpD,OAAO,eAAe,OAAQ,YAAa,CACzC,KAAyB,CACvB,GAAI,QAAU,OAAU,KAAa,UAAU,IAAM,OAAW,CAC7D,KAAa,UAAU,EAAI,KAAK,SAAS,cAAc,QAAQ,CAClE,CACA,OAAQ,KAAa,UAAU,CACjC,EACA,aAAc,KACd,WAAY,IACd,CAAC,CACH,CACF,CAiBO,SAAS,SAAS,SAAkB,MAAQ,KAAM,CACvD,OAAO,SAAU,OAAuB,YAA2B,CACjE,MAAM,WAAa,OAAO,GAAG,OAAO,WAAW,CAAC,IAAI,EAEpD,OAAO,eAAe,OAAQ,YAAa,CACzC,KAAyB,CACvB,GAAI,QAAU,OAAU,KAAa,UAAU,IAAM,OAAW,CAC7D,KAAa,UAAU,EAAI,KAAK,SAAS,iBAAiB,QAAQ,CACrE,CACA,OAAQ,KAAa,UAAU,CACjC,EACA,aAAc,KACd,WAAY,IACd,CAAC,CACH,CACF", "names": ["createLogger"] }