@alwatr/synapse 1.4.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js ADDED
@@ -0,0 +1,5 @@
1
+ /* 📦 @alwatr/synapse v2.0.0 */
2
+ import{createLogger as Z}from"@alwatr/logger";var Q=Z("alwatr/synapse"),V=[];var Y="synapse";function $(x=document.body){if(Q.logMethod?.("bootstrapDirectives"),document.readyState==="loading"){Q.incident?.("bootstrapDirectives","dom_not_ready","Delaying directive initialization until DOM is ready"),document.addEventListener("DOMContentLoaded",()=>{$(x)},{once:!0});return}for(let{selector:F,constructor:U}of V)try{let P=`${F}:not([${Y}])`,J=x.querySelectorAll(P);if(J.length===0)continue;Q.logOther?.(`Found ${J.length} new element(s) for directive "${F}"`),J.forEach((G)=>{G.setAttribute(Y,""),new U(G,F)})}catch(P){Q.error("bootstrapDirectives","directive_instantiation_error",P,{selector:F})}}function R(x){return Q.logMethodArgs?.("@directive",x),function(F){V.push({selector:x,constructor:F})}}import{delay as B}from"@alwatr/delay";import{createLogger as C}from"@alwatr/logger";class H{selector_;logger_;element_;cleanupTaskList__=[];constructor(x,F){this.logger_=C(`directive:${F}`),this.logger_.logMethodArgs?.("new",{selector:F,element:x}),this.selector_=F,this.element_=x,(async()=>{await B.nextMicrotask(),await this.init_()})()}init_(){this.logger_.logMethod?.("init_"),this.update_?.()}dispatch_(x,F){this.logger_.logMethodArgs?.("dispatch_",{eventName:x,detail:F}),this.element_.dispatchEvent(new CustomEvent(x,{detail:F,bubbles:!0}))}onDestroy_(x){this.logger_.logMethod?.("onDestroy_"),this.cleanupTaskList__.push(x)}destroy_(){if(this.logger_.logMethod?.("destroy_"),this.cleanupTaskList__.length>0){for(let x of this.cleanupTaskList__)try{x.call(this)}catch(F){this.logger_.error("destroy_","error_in_destroy_callback",F)}this.cleanupTaskList__.length=0}this.element_.remove(),this.element_=null}}function w(x,F=!0,U){return function(P,J){let G=Symbol(`${String(J)}__`);Object.defineProperty(P,J,{get(){if(F===!1||this[G]===void 0){let X=U??this.element_;this[G]=X.querySelector(x)}return this[G]},configurable:!0,enumerable:!0})}}function A(x,F=!0,U){return function(P,J){let G=Symbol(`${String(J)}__`);Object.defineProperty(P,J,{get(){if(F===!1||this[G]===void 0){let X=U??this.element_;this[G]=X.querySelectorAll(x)}return this[G]},configurable:!0,enumerable:!0})}}export{A as queryAll,w as query,R as directive,$ as bootstrapDirectives,H as DirectiveBase};
3
+
4
+ //# debugId=EF84B45CE83276C064756E2164756E21
5
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/lib.ts", "../src/bootstrap.ts", "../src/directiveDecorator.ts", "../src/directiveClass.ts", "../src/queryDecorator.ts"],
4
+ "sourcesContent": [
5
+ "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 = createLogger('alwatr/synapse');\n\n/**\n * The registry for all directives.\n */\nexport const directiveRegistry_: {selector: string; constructor: DirectiveConstructor}[] = [];\n",
6
+ "import {directiveRegistry_, logger} from './lib.js';\n\nconst initializedAttribute = 'synapse';\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 if (document.readyState === 'loading') {\n logger.incident?.('bootstrapDirectives', 'dom_not_ready', 'Delaying directive initialization until DOM is ready');\n document.addEventListener('DOMContentLoaded', () => {\n bootstrapDirectives(rootElement);\n }, {once: true});\n return;\n }\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, '');\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",
7
+ "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",
8
+ "/**\n * @package @alwatr/synapse\n *\n * This file defines the `DirectiveBase` class, which is the foundation for creating custom directives\n * in the Alwatr Synapse library. Directives are used to attach behavior and logic to DOM elements\n * declaratively.\n */\n\nimport {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\n/**\n * The abstract base class for all directives.\n *\n * Extend this class to create a new directive that can be registered with the `@directive` decorator.\n * It provides the core functionality for linking a TypeScript class to a DOM element and managing its lifecycle.\n *\n * @example\n * ```ts\n * import {DirectiveBase, directive} from '@alwatr/synapse';\n *\n * @directive('[my-directive]')\n * export class MyDirective extends DirectiveBase {\n * protected override init_(): void {\n * super.init_(); // فراخوانی متد والد برای حفظ سازگاری با نسخه‌های قبل ضروری است\n * this.element_.textContent = 'Hello from MyDirective!';\n * this.element_.addEventListener('click', () => this.log('Element clicked!'));\n * }\n * }\n * ```\n */\nexport abstract class DirectiveBase {\n /**\n * The CSS selector that this directive is associated with.\n * This is the selector string provided to the `@directive` decorator.\n */\n protected readonly selector_;\n\n /**\n * A dedicated logger instance for this directive, pre-configured with a context like `directive:[selector]`.\n * Use this for logging to provide clear, contextual messages.\n */\n protected readonly logger_;\n\n /**\n * The DOM element to which this directive instance is attached.\n * All directive logic operates on this element.\n */\n protected readonly element_: HTMLElement;\n\n /**\n * A list of callback functions to be executed when the directive is destroyed.\n */\n private readonly cleanupTaskList__: NoopFunc[] = [];\n\n /**\n * Initializes the directive. This constructor is called by the Synapse bootstrap process and should not be\n * overridden in subclasses.\n *\n * It sets up the logger, element, and selector, and then schedules the `init_` and `update_` lifecycle methods\n * to run in the next microtask.\n *\n * @param element The DOM element to which this directive is attached.\n * @param selector The CSS selector that matched this directive.\n */\n 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 })();\n }\n\n /**\n * Called once automatically after the directive is initialized.\n *\n * This method serves as the main entry point for your directive's logic,\n * such as modifying the element or setting up event listeners.\n *\n * **Note:** Do not call this method directly. It is designed to be called only once by the framework.\n */\n protected init_(): Awaitable<void> {\n this.logger_.logMethod?.('init_');\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this as any).update_?.(); // backward compatibility\n }\n\n /**\n * Dispatches a custom event from the target element.\n *\n * This is a convenience method for firing events that can be listened to by other parts of the application.\n * The event bubbles up through the DOM.\n *\n * @param eventName The name of the custom event.\n * @param detail Optional data to include in the event's `detail` property.\n *\n * @example\n * ```ts\n * this.dispatch_('user-action', {action: 'save', id: 123});\n * ```\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 /**\n * Registers a task to be executed when the directive is destroyed.\n * Follows the `on[Event]` pattern, similar to `onClick`.\n * Useful for cleaning up resources, such as unsubscribing from signals or removing global event listeners.\n *\n * @param task The cleanup task to register.\n *\n * @example\n * ```ts\n * this.onDestroy(\n * signal.subscribe(() => this.log('signal changed')).unsubscribe\n * );\n * ```\n */\n protected onDestroy_(task: NoopFunc): void {\n this.logger_.logMethod?.('onDestroy_');\n this.cleanupTaskList__.push(task);\n }\n\n /**\n * Cleans up the directive's resources.\n *\n * This method removes the element from the DOM and nullifies the internal reference to it,\n * helping with garbage collection. It can be extended by subclasses to perform additional cleanup,\n * such as removing event listeners.\n */\n protected destroy_(): Awaitable<void> {\n this.logger_.logMethod?.('destroy_');\n\n // Execute all registered cleanup tasks\n if (this.cleanupTaskList__.length > 0) {\n for (const task of this.cleanupTaskList__) {\n try {\n task.call(this);\n }\n catch (err) {\n this.logger_.error('destroy_', 'error_in_destroy_callback', err);\n }\n }\n\n this.cleanupTaskList__.length = 0; // clear the list after executing all tasks\n }\n\n this.element_.remove();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this as any).element_ = null;\n }\n}\n",
9
+ "/* 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 * @param root Optional root element to perform the query on. Defaults to the directive's element.\n *\n * @example\n * ```ts\n * @directive('[my-directive]')\n * class MyDirective extends DirectiveBase {\n * @query('.my-element')\n * protected myElement: HTMLDivElement | null;\n * }\n * ```\n */\nexport function query(selector: string, cache = true, root?: ParentNode) {\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 const parent = root ?? this.element_;\n (this as any)[privateKey] = parent.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 * @param cache Whether to cache the result on first access. Defaults is true.\n * @param root Optional root element to perform the query on. Defaults to the directive's element.\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, root?: ParentNode) {\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 const parent = root ?? this.element_;\n (this as any)[privateKey] = parent.querySelectorAll(selector);\n }\n return (this as any)[privateKey];\n },\n configurable: true,\n enumerable: true,\n });\n };\n}\n"
10
+ ],
11
+ "mappings": "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",
12
+ "debugId": "EF84B45CE83276C064756E2164756E21",
13
+ "names": []
14
+ }
package/package.json CHANGED
@@ -1,29 +1,29 @@
1
1
  {
2
2
  "name": "@alwatr/synapse",
3
3
  "description": "Connect your TypeScript classes to the DOM, declaratively.",
4
- "version": "1.4.1",
4
+ "version": "2.0.0",
5
5
  "author": "S. Ali Mihandoost <ali.mihandoost@gmail.com>",
6
6
  "bugs": "https://github.com/Alwatr/nanolib/issues",
7
7
  "dependencies": {
8
- "@alwatr/delay": "6.0.22",
9
- "@alwatr/logger": "6.0.18"
8
+ "@alwatr/delay": "7.0.0",
9
+ "@alwatr/logger": "7.0.0"
10
10
  },
11
11
  "devDependencies": {
12
- "@alwatr/nano-build": "6.4.2",
13
- "@alwatr/prettier-config": "6.0.2",
14
- "@alwatr/tsconfig-base": "6.0.4",
15
- "@alwatr/type-helper": "7.0.2",
12
+ "@alwatr/nano-build": "7.0.0",
13
+ "@alwatr/prettier-config": "7.0.0",
14
+ "@alwatr/tsconfig-base": "7.0.0",
15
+ "@alwatr/type-helper": "8.0.0",
16
16
  "typescript": "^5.9.3"
17
17
  },
18
18
  "exports": {
19
19
  ".": {
20
20
  "types": "./dist/main.d.ts",
21
- "import": "./dist/main.mjs",
22
- "require": "./dist/main.cjs"
21
+ "default": "./dist/main.js"
23
22
  }
24
23
  },
25
24
  "files": [
26
- "**/*.{js,mjs,cjs,map,d.ts,html,md,LEGAL.txt}",
25
+ "**/*.{js,mjs,cjs,ts,map,d.ts,html,LEGAL.txt}",
26
+ "README.md",
27
27
  "LICENSE",
28
28
  "!demo/**/*",
29
29
  "!**/*.test.js"
@@ -52,8 +52,6 @@
52
52
  "web-development"
53
53
  ],
54
54
  "license": "MPL-2.0",
55
- "main": "./dist/main.cjs",
56
- "module": "./dist/main.mjs",
57
55
  "prettier": "@alwatr/prettier-config",
58
56
  "publishConfig": {
59
57
  "access": "public"
@@ -66,12 +64,12 @@
66
64
  "scripts": {
67
65
  "b": "bun run build",
68
66
  "build": "bun run build:ts && bun run build:es",
69
- "build:es": "nano-build --preset=module",
67
+ "build:es": "nano-build --preset=module src/main.ts",
70
68
  "build:ts": "tsc --build",
71
69
  "c": "bun run clean",
72
70
  "cb": "bun run clean && bun run build",
73
71
  "clean": "rm -rfv dist *.tsbuildinfo",
74
- "d": "bun run build:es && bun --enable-source-maps --trace-warnings",
72
+ "d": "bun run build:es && bun",
75
73
  "w": "bun run watch",
76
74
  "watch": "bun run watch:ts & bun run watch:es",
77
75
  "watch:es": "bun run build:es --watch",
@@ -80,5 +78,5 @@
80
78
  "sideEffects": false,
81
79
  "type": "module",
82
80
  "types": "./dist/main.d.ts",
83
- "gitHead": "def1decf8f496f4b0d3d3ab952c346c689c30160"
81
+ "gitHead": "056102a1c8a563bbae8a290b6830450f467322a3"
84
82
  }
@@ -0,0 +1,56 @@
1
+ import {directiveRegistry_, logger} from './lib.js';
2
+
3
+ const initializedAttribute = 'synapse';
4
+
5
+ /**
6
+ * Initializes all registered directives within a given root element.
7
+ * If no root element is provided, it scans the entire body.
8
+ *
9
+ * This function is idempotent; it will not re-initialize a directive on an element
10
+ * that has already been processed.
11
+ *
12
+ * @param rootElement The element to scan for directives. Defaults to `document.body`.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * // Initialize directives on the whole page after the DOM is loaded.
17
+ * document.addEventListener('DOMContentLoaded', () => bootstrapDirectives());
18
+ *
19
+ * // Or, initialize directives on a dynamically added part of the page.
20
+ * const newContent = document.createElement('div');
21
+ * newContent.innerHTML = '<div class="my-button">Click Me</div>';
22
+ * document.body.appendChild(newContent);
23
+ *
24
+ * bootstrapDirectives(newContent);
25
+ * ```
26
+ */
27
+ export function bootstrapDirectives(rootElement: Element | Document = document.body): void {
28
+ logger.logMethod?.('bootstrapDirectives');
29
+
30
+ if (document.readyState === 'loading') {
31
+ logger.incident?.('bootstrapDirectives', 'dom_not_ready', 'Delaying directive initialization until DOM is ready');
32
+ document.addEventListener('DOMContentLoaded', () => {
33
+ bootstrapDirectives(rootElement);
34
+ }, {once: true});
35
+ return;
36
+ }
37
+
38
+ for (const {selector, constructor} of directiveRegistry_) {
39
+ try {
40
+ const uninitializedSelector = `${selector}:not([${initializedAttribute}])`;
41
+ const elements = rootElement.querySelectorAll<HTMLElement>(uninitializedSelector);
42
+ if (elements.length === 0) continue;
43
+
44
+ logger.logOther?.(`Found ${elements.length} new element(s) for directive "${selector}"`);
45
+ elements.forEach((element) => {
46
+ // Mark the element as processed before creating an instance
47
+ element.setAttribute(initializedAttribute, '');
48
+ // Instantiate the directive with the element.
49
+ new constructor(element, selector);
50
+ });
51
+ }
52
+ catch (err) {
53
+ logger.error('bootstrapDirectives', 'directive_instantiation_error', err, {selector});
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * @package @alwatr/synapse
3
+ *
4
+ * This file defines the `DirectiveBase` class, which is the foundation for creating custom directives
5
+ * in the Alwatr Synapse library. Directives are used to attach behavior and logic to DOM elements
6
+ * declaratively.
7
+ */
8
+
9
+ import {delay} from '@alwatr/delay';
10
+ import {createLogger} from '@alwatr/logger';
11
+
12
+ /**
13
+ * The abstract base class for all directives.
14
+ *
15
+ * Extend this class to create a new directive that can be registered with the `@directive` decorator.
16
+ * It provides the core functionality for linking a TypeScript class to a DOM element and managing its lifecycle.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import {DirectiveBase, directive} from '@alwatr/synapse';
21
+ *
22
+ * @directive('[my-directive]')
23
+ * export class MyDirective extends DirectiveBase {
24
+ * protected override init_(): void {
25
+ * super.init_(); // فراخوانی متد والد برای حفظ سازگاری با نسخه‌های قبل ضروری است
26
+ * this.element_.textContent = 'Hello from MyDirective!';
27
+ * this.element_.addEventListener('click', () => this.log('Element clicked!'));
28
+ * }
29
+ * }
30
+ * ```
31
+ */
32
+ export abstract class DirectiveBase {
33
+ /**
34
+ * The CSS selector that this directive is associated with.
35
+ * This is the selector string provided to the `@directive` decorator.
36
+ */
37
+ protected readonly selector_;
38
+
39
+ /**
40
+ * A dedicated logger instance for this directive, pre-configured with a context like `directive:[selector]`.
41
+ * Use this for logging to provide clear, contextual messages.
42
+ */
43
+ protected readonly logger_;
44
+
45
+ /**
46
+ * The DOM element to which this directive instance is attached.
47
+ * All directive logic operates on this element.
48
+ */
49
+ protected readonly element_: HTMLElement;
50
+
51
+ /**
52
+ * A list of callback functions to be executed when the directive is destroyed.
53
+ */
54
+ private readonly cleanupTaskList__: NoopFunc[] = [];
55
+
56
+ /**
57
+ * Initializes the directive. This constructor is called by the Synapse bootstrap process and should not be
58
+ * overridden in subclasses.
59
+ *
60
+ * It sets up the logger, element, and selector, and then schedules the `init_` and `update_` lifecycle methods
61
+ * to run in the next microtask.
62
+ *
63
+ * @param element The DOM element to which this directive is attached.
64
+ * @param selector The CSS selector that matched this directive.
65
+ */
66
+ constructor(element: HTMLElement, selector: string) {
67
+ this.logger_ = createLogger(`directive:${selector}`);
68
+ this.logger_.logMethodArgs?.('new', {selector, element});
69
+
70
+ this.selector_ = selector;
71
+ this.element_ = element;
72
+
73
+ (async () => {
74
+ await delay.nextMicrotask();
75
+ await this.init_();
76
+ })();
77
+ }
78
+
79
+ /**
80
+ * Called once automatically after the directive is initialized.
81
+ *
82
+ * This method serves as the main entry point for your directive's logic,
83
+ * such as modifying the element or setting up event listeners.
84
+ *
85
+ * **Note:** Do not call this method directly. It is designed to be called only once by the framework.
86
+ */
87
+ protected init_(): Awaitable<void> {
88
+ this.logger_.logMethod?.('init_');
89
+
90
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
91
+ (this as any).update_?.(); // backward compatibility
92
+ }
93
+
94
+ /**
95
+ * Dispatches a custom event from the target element.
96
+ *
97
+ * This is a convenience method for firing events that can be listened to by other parts of the application.
98
+ * The event bubbles up through the DOM.
99
+ *
100
+ * @param eventName The name of the custom event.
101
+ * @param detail Optional data to include in the event's `detail` property.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * this.dispatch_('user-action', {action: 'save', id: 123});
106
+ * ```
107
+ */
108
+ protected dispatch_(eventName: string, detail?: unknown): void {
109
+ this.logger_.logMethodArgs?.('dispatch_', {eventName, detail});
110
+ this.element_.dispatchEvent(new CustomEvent(eventName, {detail, bubbles: true}));
111
+ }
112
+
113
+ /**
114
+ * Registers a task to be executed when the directive is destroyed.
115
+ * Follows the `on[Event]` pattern, similar to `onClick`.
116
+ * Useful for cleaning up resources, such as unsubscribing from signals or removing global event listeners.
117
+ *
118
+ * @param task The cleanup task to register.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * this.onDestroy(
123
+ * signal.subscribe(() => this.log('signal changed')).unsubscribe
124
+ * );
125
+ * ```
126
+ */
127
+ protected onDestroy_(task: NoopFunc): void {
128
+ this.logger_.logMethod?.('onDestroy_');
129
+ this.cleanupTaskList__.push(task);
130
+ }
131
+
132
+ /**
133
+ * Cleans up the directive's resources.
134
+ *
135
+ * This method removes the element from the DOM and nullifies the internal reference to it,
136
+ * helping with garbage collection. It can be extended by subclasses to perform additional cleanup,
137
+ * such as removing event listeners.
138
+ */
139
+ protected destroy_(): Awaitable<void> {
140
+ this.logger_.logMethod?.('destroy_');
141
+
142
+ // Execute all registered cleanup tasks
143
+ if (this.cleanupTaskList__.length > 0) {
144
+ for (const task of this.cleanupTaskList__) {
145
+ try {
146
+ task.call(this);
147
+ }
148
+ catch (err) {
149
+ this.logger_.error('destroy_', 'error_in_destroy_callback', err);
150
+ }
151
+ }
152
+
153
+ this.cleanupTaskList__.length = 0; // clear the list after executing all tasks
154
+ }
155
+
156
+ this.element_.remove();
157
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
158
+ (this as any).element_ = null;
159
+ }
160
+ }
@@ -0,0 +1,36 @@
1
+ import {directiveRegistry_, logger} from './lib.js';
2
+
3
+ import type {DirectiveBase} from './directiveClass.js';
4
+
5
+ /**
6
+ * Type definition for a directive constructor.
7
+ * A directive class must have a constructor that accepts an HTMLElement.
8
+ */
9
+ export type DirectiveConstructor<T extends DirectiveBase = DirectiveBase> = new (element: HTMLElement, selector: string) => T;
10
+
11
+ /**
12
+ * A class decorator that registers a class as a directive.
13
+ *
14
+ * @param selector The CSS selector to which this directive will be attached.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * @directive('.my-button')
19
+ * class MyButtonDirective extends DirectiveBase {
20
+ * protected update_(): void {
21
+ * this.element_.addEventListener('click', () => console.log('Button clicked!'));
22
+ * }
23
+ * }
24
+ * ```
25
+ */
26
+ export function directive(selector: string) {
27
+ logger.logMethodArgs?.('@directive', selector);
28
+
29
+ /**
30
+ * The decorator function that receives the class constructor.
31
+ * @param constructor The class to be registered as a directive.
32
+ */
33
+ return function (constructor: DirectiveConstructor): void {
34
+ directiveRegistry_.push({selector, constructor});
35
+ };
36
+ }
package/src/lib.ts ADDED
@@ -0,0 +1,14 @@
1
+ import {createLogger} from '@alwatr/logger';
2
+
3
+ import type {DirectiveConstructor} from './directiveDecorator.js';
4
+ import type {} from '@alwatr/type-helper';
5
+
6
+ /**
7
+ * Alwatr Synapse Logger.
8
+ */
9
+ export const logger = createLogger('alwatr/synapse');
10
+
11
+ /**
12
+ * The registry for all directives.
13
+ */
14
+ export const directiveRegistry_: {selector: string; constructor: DirectiveConstructor}[] = [];
package/src/main.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './bootstrap.js';
2
+ export * from './directiveDecorator.js';
3
+ export * from './directiveClass.js';
4
+ export * from './queryDecorator.js';
@@ -0,0 +1,72 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type {DirectiveBase} from './directiveClass.js';
3
+
4
+ /**
5
+ * A property decorator that queries the directive's element for a selector.
6
+ * The query is performed once and the result is cached.
7
+ *
8
+ * @param selector The CSS selector to query for.
9
+ * @param cache Whether to cache the result on first access. Defaults is true.
10
+ * @param root Optional root element to perform the query on. Defaults to the directive's element.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * @directive('[my-directive]')
15
+ * class MyDirective extends DirectiveBase {
16
+ * @query('.my-element')
17
+ * protected myElement: HTMLDivElement | null;
18
+ * }
19
+ * ```
20
+ */
21
+ export function query(selector: string, cache = true, root?: ParentNode) {
22
+ return function (target: DirectiveBase, propertyKey: string): void {
23
+ const privateKey = Symbol(`${String(propertyKey)}__`);
24
+
25
+ Object.defineProperty(target, propertyKey, {
26
+ get(this: DirectiveBase) {
27
+ if (cache === false || (this as any)[privateKey] === undefined) {
28
+ const parent = root ?? this.element_;
29
+ (this as any)[privateKey] = parent.querySelector(selector);
30
+ }
31
+ return (this as any)[privateKey];
32
+ },
33
+ configurable: true,
34
+ enumerable: true,
35
+ });
36
+ };
37
+ }
38
+
39
+ /**
40
+ * A property decorator that queries the directive's element for all selectors.
41
+ * The queries are performed once and the result is cached.
42
+ *
43
+ * @param selector The CSS selector to query for.
44
+ * @param cache Whether to cache the result on first access. Defaults is true.
45
+ * @param root Optional root element to perform the query on. Defaults to the directive's element.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * @directive('[my-directive]')
50
+ * class MyDirective extends DirectiveBase {
51
+ * @queryAll('.my-elements')
52
+ * protected myElements: NodeListOf<HTMLDivElement>;
53
+ * }
54
+ * ```
55
+ */
56
+ export function queryAll(selector: string, cache = true, root?: ParentNode) {
57
+ return function (target: DirectiveBase, propertyKey: string): void {
58
+ const privateKey = Symbol(`${String(propertyKey)}__`);
59
+
60
+ Object.defineProperty(target, propertyKey, {
61
+ get(this: DirectiveBase) {
62
+ if (cache === false || (this as any)[privateKey] === undefined) {
63
+ const parent = root ?? this.element_;
64
+ (this as any)[privateKey] = parent.querySelectorAll(selector);
65
+ }
66
+ return (this as any)[privateKey];
67
+ },
68
+ configurable: true,
69
+ enumerable: true,
70
+ });
71
+ };
72
+ }
package/CHANGELOG.md DELETED
@@ -1,244 +0,0 @@
1
- # Change Log
2
-
3
- All notable changes to this project will be documented in this file.
4
- See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
-
6
- ## [1.4.1](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.4.0...@alwatr/synapse@1.4.1) (2026-03-18)
7
-
8
- **Note:** Version bump only for package @alwatr/synapse
9
-
10
- ## [1.4.0](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.3.6...@alwatr/synapse@1.4.0) (2026-03-16)
11
-
12
- ### ✨ Features
13
-
14
- * Utilize 'synapse' attribute for directive association ([f86ca7e](https://github.com/Alwatr/nanolib/commit/f86ca7eb2819425d6c44dd28c424321b7f66d227))
15
-
16
- ### 🔨 Code Refactoring
17
-
18
- * migrate build scripts from yarn to bun across multiple packages ([d90e962](https://github.com/Alwatr/nanolib/commit/d90e962f15e5c951e191d5f02341279b6472abc3))
19
-
20
- ## [1.3.6](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.3.5...@alwatr/synapse@1.3.6) (2026-02-18)
21
-
22
- **Note:** Version bump only for package @alwatr/synapse
23
-
24
- ## [1.3.5](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.3.4...@alwatr/synapse@1.3.5) (2025-12-23)
25
-
26
- **Note:** Version bump only for package @alwatr/synapse
27
-
28
- ## [1.3.4](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.3.3...@alwatr/synapse@1.3.4) (2025-12-13)
29
-
30
- ### 🐛 Bug Fixes
31
-
32
- * update NoopFunction type alias to NoopFunc ([553e586](https://github.com/Alwatr/nanolib/commit/553e586060aa78e1db7806aea207443db8e789ab))
33
-
34
- ## [1.3.3](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.3.2...@alwatr/synapse@1.3.3) (2025-12-13)
35
-
36
- **Note:** Version bump only for package @alwatr/synapse
37
-
38
- ## [1.3.2](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.3.1...@alwatr/synapse@1.3.2) (2025-12-10)
39
-
40
- **Note:** Version bump only for package @alwatr/synapse
41
-
42
- ## [1.3.1](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.3.0...@alwatr/synapse@1.3.1) (2025-11-19)
43
-
44
- ### 🐛 Bug Fixes
45
-
46
- * refactor query and queryAll to use a parent variable for improved clarity ([57b175b](https://github.com/Alwatr/nanolib/commit/57b175bddd5617b9a05aba3f3eb81ec0c34527f7))
47
-
48
- ## [1.3.0](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.2.1...@alwatr/synapse@1.3.0) (2025-11-18)
49
-
50
- ### ✨ Features
51
-
52
- * add optional root parameter to query and queryAll decorators ([95f33b6](https://github.com/Alwatr/nanolib/commit/95f33b60b937a572734b47dbb6887a0a84801beb))
53
-
54
- ### 🐛 Bug Fixes
55
-
56
- * update documentation for queryAll to clarify cache parameter ([0693612](https://github.com/Alwatr/nanolib/commit/06936122ee9aecfc11cdcf68278eac09fc08e8ff))
57
-
58
- ## [1.2.1](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.2.0...@alwatr/synapse@1.2.1) (2025-11-15)
59
-
60
- ### 🐛 Bug Fixes
61
-
62
- * ensure cleanup tasks are called with the correct context in DirectiveBase ([06ab04e](https://github.com/Alwatr/nanolib/commit/06ab04e0eaa3837c2bc55089723051364bc6e56b))
63
- * rename cleanupTaskList variable for consistency ([d51eee9](https://github.com/Alwatr/nanolib/commit/d51eee92883b851fb3fc5307d7eefd5dc536be8e))
64
-
65
- ## [1.2.0](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.20...@alwatr/synapse@1.2.0) (2025-11-15)
66
-
67
- ### ✨ Features
68
-
69
- * add cleanup task management to DirectiveBase for resource management ([d357e63](https://github.com/Alwatr/nanolib/commit/d357e63d61025105701628f480bbb95955822c82))
70
-
71
- ### 🐛 Bug Fixes
72
-
73
- * rename variable in cleanup task loop for improved clarity ([463ecf0](https://github.com/Alwatr/nanolib/commit/463ecf0ea741c51b61523105f7f10b4cef371002))
74
- * update onDestroy method to use NoopFunction type for better clarity ([1023aad](https://github.com/Alwatr/nanolib/commit/1023aadeaa5a608176d57773c8ae956a169789c6))
75
-
76
- ## [1.1.20](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.19...@alwatr/synapse@1.1.20) (2025-11-09)
77
-
78
- ### 🐛 Bug Fixes
79
-
80
- * delay directive initialization until DOM is ready ([07dd452](https://github.com/Alwatr/nanolib/commit/07dd452732b8d32b822fd99b4eba1617623ced09))
81
-
82
- ## [1.1.19](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.18...@alwatr/synapse@1.1.19) (2025-11-06)
83
-
84
- ### 🐛 Bug Fixes
85
-
86
- * rename update_ method to init_ and call super.init_ in CopyButtonDirective ([66ec105](https://github.com/Alwatr/nanolib/commit/66ec10508385d3bdc5e7e19d53d48294d48ed865))
87
-
88
- ## [1.1.18](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.17...@alwatr/synapse@1.1.18) (2025-11-04)
89
-
90
- ### 🔨 Code Refactoring
91
-
92
- * enhance documentation and structure of DirectiveBase class ([b2cf694](https://github.com/Alwatr/nanolib/commit/b2cf6941ffdf56adfc0a357331a1f155f782943e))
93
- * simplify update and destroy methods in DirectiveBase class ([e8a906d](https://github.com/Alwatr/nanolib/commit/e8a906d5e4346eda808fc3013287e620e31ef4e3))
94
- * update property types in query decorators to ensure proper null handling ([bc250dd](https://github.com/Alwatr/nanolib/commit/bc250dde37c72f9469d11ec6a49b9567f3d81d38))
95
-
96
- ## [1.1.17](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.16...@alwatr/synapse@1.1.17) (2025-10-06)
97
-
98
- ### 🔗 Dependencies update
99
-
100
- * bump the npm-dependencies group with 4 updates ([9825815](https://github.com/Alwatr/nanolib/commit/982581552bbb4b97dca52af5e93a80937f0c3109))
101
-
102
- ## [1.1.16](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.15...@alwatr/synapse@1.1.16) (2025-09-27)
103
-
104
- ### 🧹 Miscellaneous Chores
105
-
106
- * exclude test files from package distribution ([86f4f2f](https://github.com/Alwatr/nanolib/commit/86f4f2f5985845c5cf3a3a9398de7b2f98ce53e7))
107
-
108
- ## [1.1.15](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.14...@alwatr/synapse@1.1.15) (2025-09-22)
109
-
110
- **Note:** Version bump only for package @alwatr/synapse
111
-
112
- ## [1.1.14](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.13...@alwatr/synapse@1.1.14) (2025-09-22)
113
-
114
- **Note:** Version bump only for package @alwatr/synapse
115
-
116
- ## [1.1.13](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.12...@alwatr/synapse@1.1.13) (2025-09-21)
117
-
118
- **Note:** Version bump only for package @alwatr/synapse
119
-
120
- ## [1.1.12](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.11...@alwatr/synapse@1.1.12) (2025-09-20)
121
-
122
- ### 🐛 Bug Fixes
123
-
124
- * add sideEffects property to package.json files for better tree-shaking ([c7b9e74](https://github.com/Alwatr/nanolib/commit/c7b9e74e1920c8e35b438742de61883ca62da58c))
125
- * add sideEffects property to package.json files for better tree-shaking ([e8402c4](https://github.com/Alwatr/nanolib/commit/e8402c481a14a1f807a37aaa862a936713d26176))
126
- * remove unnecessary pure annotations ([adeb916](https://github.com/Alwatr/nanolib/commit/adeb9166f8e911f59269032b76c36cb1888332cf))
127
-
128
- ### 🧹 Miscellaneous Chores
129
-
130
- * remove duplicate sideEffects property from multiple package.json files ([b123f86](https://github.com/Alwatr/nanolib/commit/b123f86be81481de2314aae9bb2eeb629743d24c))
131
-
132
- ## [1.1.11](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.10...@alwatr/synapse@1.1.11) (2025-09-19)
133
-
134
- **Note:** Version bump only for package @alwatr/synapse
135
-
136
- ## [1.1.10](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.9...@alwatr/synapse@1.1.10) (2025-09-19)
137
-
138
- **Note:** Version bump only for package @alwatr/synapse
139
-
140
- ## [1.1.9](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.8...@alwatr/synapse@1.1.9) (2025-09-15)
141
-
142
- **Note:** Version bump only for package @alwatr/synapse
143
-
144
- ## [1.1.8](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.7...@alwatr/synapse@1.1.8) (2025-09-14)
145
-
146
- ### 🔨 Code Refactoring
147
-
148
- * **package:** update keywords in package.json for debounce, local-storage, and synapse packages ([09c9cca](https://github.com/Alwatr/nanolib/commit/09c9cca3cd600e9ffaf600fb1926c0ee884a1aa8))
149
-
150
- ## [1.1.7](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.6...@alwatr/synapse@1.1.7) (2025-09-13)
151
-
152
- ### 🐛 Bug Fixes
153
-
154
- * types ([c451e48](https://github.com/Alwatr/nanolib/commit/c451e48869bb4bb9a9ddbf9f272f0a303aae9bda))
155
-
156
- ### 🧹 Miscellaneous Chores
157
-
158
- * add reference to delay package in tsconfig ([6486d02](https://github.com/Alwatr/nanolib/commit/6486d02f0cb10ddf3c43c6d8d6efc7c77666f4c8))
159
-
160
- ## [1.1.6](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.5...@alwatr/synapse@1.1.6) (2025-09-13)
161
-
162
- **Note:** Version bump only for package @alwatr/synapse
163
-
164
- ## [1.1.5](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.4...@alwatr/synapse@1.1.5) (2025-09-13)
165
-
166
- **Note:** Version bump only for package @alwatr/synapse
167
-
168
- ## [1.1.4](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.3...@alwatr/synapse@1.1.4) (2025-09-09)
169
-
170
- ### 🧹 Miscellaneous Chores
171
-
172
- * remove trailing newlines from contributing sections in README files ([e8ab1bc](https://github.com/Alwatr/nanolib/commit/e8ab1bc43e0addea5ccd4c897c2cec597cb9e15f))
173
-
174
- ## [1.1.3](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.2...@alwatr/synapse@1.1.3) (2025-09-08)
175
-
176
- ### 🐛 Bug Fixes
177
-
178
- * **directive:** change delay from immediate to nextMicrotask ([218d134](https://github.com/Alwatr/nanolib/commit/218d1344a4b377382e7d2294864adb388caf9d1a))
179
- * **lib:** mark logger creation as pure for optimization ([d902bf8](https://github.com/Alwatr/nanolib/commit/d902bf872454fe5868c7b6bdbb4ddb340337f443))
180
-
181
- ## [1.1.2](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.1...@alwatr/synapse@1.1.2) (2025-09-06)
182
-
183
- ### 🔨 Code Refactoring
184
-
185
- * update method return types to Awaitable for consistency ([623a8f7](https://github.com/Alwatr/nanolib/commit/623a8f74e9f4ce8142363874ade116a87448df3a))
186
-
187
- ## [1.1.1](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.1.0...@alwatr/synapse@1.1.1) (2025-09-05)
188
-
189
- **Note:** Version bump only for package @alwatr/synapse
190
-
191
- ## [1.1.0](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.0.1...@alwatr/synapse@1.1.0) (2025-09-01)
192
-
193
- ### ✨ Features
194
-
195
- * add destroy_ method to clean up directive resources ([5b3878c](https://github.com/Alwatr/nanolib/commit/5b3878cd709dfda1cb224b296e8e36f900e1dfa3))
196
- * add init_ method for directive initialization ([30178d4](https://github.com/Alwatr/nanolib/commit/30178d40dcd59052068a334b0e811473fa184dd5))
197
- * add query and queryAll decorators for element selection ([7eac8d2](https://github.com/Alwatr/nanolib/commit/7eac8d241b9ad714145c42f20a7a8bf58dd4b1e1))
198
- * update main.ts to export queryDecorators ([66631e5](https://github.com/Alwatr/nanolib/commit/66631e5ddb5e1d0ed6b3883ff2008595a13c3a62))
199
-
200
- ### 🐛 Bug Fixes
201
-
202
- * ensure proper initialization sequence by awaiting init_ before update_ ([f251964](https://github.com/Alwatr/nanolib/commit/f2519648c89a0347978366749b681f8e4e3578a9))
203
- * ensure update_ method is abstract in DirectiveBase class ([beae25e](https://github.com/Alwatr/nanolib/commit/beae25e45a9cad40501b60efeb4fa6b1e566a16d))
204
-
205
- ## [1.0.1](https://github.com/Alwatr/nanolib/compare/@alwatr/synapse@1.0.0...@alwatr/synapse@1.0.1) (2025-08-26)
206
-
207
- ### 🐛 Bug Fixes
208
-
209
- * add missing dependency for @alwatr/delay in package.json ([616025e](https://github.com/Alwatr/nanolib/commit/616025e6d345f16ecedf62761ec96d2ad29c5856))
210
- * ensure directive update is delayed for proper initialization ([5bc0024](https://github.com/Alwatr/nanolib/commit/5bc0024c52c3813f463141d6508c39090638c4c8))
211
-
212
- ## 1.0.0 (2025-08-24)
213
-
214
- ### ✨ Features
215
-
216
- * add @alwatr/synapse package ([212ce48](https://github.com/Alwatr/nanolib/commit/212ce485cca32369e4185d5230bc328d1f3a5517))
217
- * add directive decorator for registering class directives ([19c840e](https://github.com/Alwatr/nanolib/commit/19c840e2aa4677d09c615efc7496bab4c0855f39))
218
- * export directiveClass from main.ts for improved module accessibility ([4c59be0](https://github.com/Alwatr/nanolib/commit/4c59be096ac106137d12f6bc69f82d95ddfe02fa))
219
- * implement bootstrapDirectives function to initialize registered directives ([edd5bef](https://github.com/Alwatr/nanolib/commit/edd5bef039a9a85baa7e9b116e7268ee6748eeb4))
220
- * initialize logger and directive registry in synapse ([08f961c](https://github.com/Alwatr/nanolib/commit/08f961c81ca1b303bcad9f227f379b70dfd92090))
221
- * **synapse:** add DirectiveBase class for creating custom directives ([275e71f](https://github.com/Alwatr/nanolib/commit/275e71f87d2aeeccb906194109053306aa1011d1))
222
- * **synapse:** implement directive decorator for class registration ([eca8781](https://github.com/Alwatr/nanolib/commit/eca8781550432a486446b1f7557bfdcc1a8fc178))
223
-
224
- ### 🐛 Bug Fixes
225
-
226
- * pass selector to constructor when instantiating directives in bootstrapDirectives ([17d4d5c](https://github.com/Alwatr/nanolib/commit/17d4d5c903abdcaaaefeef057874e75fd4342a93))
227
-
228
- ### 🔨 Code Refactoring
229
-
230
- * improve documentation and clarity in DirectiveBase class methods ([1fafc74](https://github.com/Alwatr/nanolib/commit/1fafc7413f22527b7937b7f8a42167929fae645a))
231
- * remove @types/node dependency from package.json ([c76b453](https://github.com/Alwatr/nanolib/commit/c76b4537e24751b7ad168df7c891df1e45297e7f))
232
- * remove obsolete CHANGELOG.md file ([50c2d63](https://github.com/Alwatr/nanolib/commit/50c2d63ecd7d39b40a297c88a5750b90cc2face5))
233
- * remove unnecessary types and library definitions from tsconfig ([e86d867](https://github.com/Alwatr/nanolib/commit/e86d8674ee0f86a81cd911a6ccc04f3707885f1e))
234
- * remove unused logger and directive registry code ([5df484c](https://github.com/Alwatr/nanolib/commit/5df484c1e3ab6b58457e8ca4799569f7f16b24fb))
235
- * rename logger variable for consistency in DirectiveBase class ([46d1e56](https://github.com/Alwatr/nanolib/commit/46d1e560f1f8bb402c327f75f6ff3b19a60d0d6e))
236
- * simplify logging method calls in DirectiveBase class ([1ec2212](https://github.com/Alwatr/nanolib/commit/1ec221229e632ba5618eeffc4e9bbcea31a737ee))
237
- * streamline constructor initialization and remove unused connection methods in DirectiveBase class ([814b670](https://github.com/Alwatr/nanolib/commit/814b670db633c90c737a237c244b14e677f312e4))
238
- * update DirectiveConstructor type to include selector parameter ([a1be15a](https://github.com/Alwatr/nanolib/commit/a1be15adfeeee30309d6574ffe0618d67776b5d7))
239
- * update package description and keywords in package.json ([6d7fa25](https://github.com/Alwatr/nanolib/commit/6d7fa2503765f13d90dc77b5cef4a3308e1fc9fd))
240
-
241
- ### 🧹 Miscellaneous Chores
242
-
243
- * remove demo HTML and TypeScript files for cleanup ([a39bc54](https://github.com/Alwatr/nanolib/commit/a39bc549537b3df4bd4d724bc396c5a42b259a9a))
244
- * update version to 1.0.0-rc in package.json ([f25b384](https://github.com/Alwatr/nanolib/commit/f25b384e13bf200079764bd82ead2349cd7b19ec))
package/dist/main.cjs DELETED
@@ -1,3 +0,0 @@
1
- /** 📦 @alwatr/synapse v1.4.1 */
2
- "use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:true})};var __copyProps=(to,from,except,desc)=>{if(from&&typeof from==="object"||typeof from==="function"){for(let key of __getOwnPropNames(from))if(!__hasOwnProp.call(to,key)&&key!==except)__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable})}return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:true}),mod);var main_exports={};__export(main_exports,{DirectiveBase:()=>DirectiveBase,bootstrapDirectives:()=>bootstrapDirectives,directive:()=>directive,query:()=>query,queryAll:()=>queryAll});module.exports=__toCommonJS(main_exports);var import_logger=require("@alwatr/logger");var logger=(0,import_logger.createLogger)("alwatr/synapse");var directiveRegistry_=[];var initializedAttribute="synapse";function bootstrapDirectives(rootElement=document.body){logger.logMethod?.("bootstrapDirectives");if(document.readyState==="loading"){logger.incident?.("bootstrapDirectives","dom_not_ready","Delaying directive initialization until DOM is ready");document.addEventListener("DOMContentLoaded",()=>{bootstrapDirectives(rootElement)},{once:true});return}for(const{selector,constructor}of directiveRegistry_){try{const uninitializedSelector=`${selector}:not([${initializedAttribute}])`;const elements=rootElement.querySelectorAll(uninitializedSelector);if(elements.length===0)continue;logger.logOther?.(`Found ${elements.length} new element(s) for directive "${selector}"`);elements.forEach(element=>{element.setAttribute(initializedAttribute,"");new constructor(element,selector)})}catch(err){logger.error("bootstrapDirectives","directive_instantiation_error",err,{selector})}}}function directive(selector){logger.logMethodArgs?.("@directive",selector);return function(constructor){directiveRegistry_.push({selector,constructor})}}var import_delay=require("@alwatr/delay");var import_logger2=require("@alwatr/logger");var DirectiveBase=class{constructor(element,selector){this.cleanupTaskList__=[];this.logger_=(0,import_logger2.createLogger)(`directive:${selector}`);this.logger_.logMethodArgs?.("new",{selector,element});this.selector_=selector;this.element_=element;(async()=>{await import_delay.delay.nextMicrotask();await this.init_()})()}init_(){this.logger_.logMethod?.("init_");this.update_?.()}dispatch_(eventName,detail){this.logger_.logMethodArgs?.("dispatch_",{eventName,detail});this.element_.dispatchEvent(new CustomEvent(eventName,{detail,bubbles:true}))}onDestroy_(task){this.logger_.logMethod?.("onDestroy_");this.cleanupTaskList__.push(task)}destroy_(){this.logger_.logMethod?.("destroy_");if(this.cleanupTaskList__.length>0){for(const task of this.cleanupTaskList__){try{task.call(this)}catch(err){this.logger_.error("destroy_","error_in_destroy_callback",err)}}this.cleanupTaskList__.length=0}this.element_.remove();this.element_=null}};function query(selector,cache=true,root){return function(target,propertyKey){const privateKey=Symbol(`${String(propertyKey)}__`);Object.defineProperty(target,propertyKey,{get(){if(cache===false||this[privateKey]===void 0){const parent=root??this.element_;this[privateKey]=parent.querySelector(selector)}return this[privateKey]},configurable:true,enumerable:true})}}function queryAll(selector,cache=true,root){return function(target,propertyKey){const privateKey=Symbol(`${String(propertyKey)}__`);Object.defineProperty(target,propertyKey,{get(){if(cache===false||this[privateKey]===void 0){const parent=root??this.element_;this[privateKey]=parent.querySelectorAll(selector)}return this[privateKey]},configurable:true,enumerable:true})}}0&&(module.exports={DirectiveBase,bootstrapDirectives,directive,query,queryAll});
3
- //# sourceMappingURL=main.cjs.map
package/dist/main.cjs.map DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/main.ts", "../src/lib.ts", "../src/bootstrap.ts", "../src/directiveDecorator.ts", "../src/directiveClass.ts", "../src/queryDecorator.ts"],
4
- "sourcesContent": ["export * from './bootstrap.js';\nexport * from './directiveDecorator.js';\nexport * from './directiveClass.js';\nexport * from './queryDecorator.js';\n", "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 = 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 = 'synapse';\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 if (document.readyState === 'loading') {\n logger.incident?.('bootstrapDirectives', 'dom_not_ready', 'Delaying directive initialization until DOM is ready');\n document.addEventListener('DOMContentLoaded', () => {\n bootstrapDirectives(rootElement);\n }, {once: true});\n return;\n }\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, '');\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", "/**\n * @package @alwatr/synapse\n *\n * This file defines the `DirectiveBase` class, which is the foundation for creating custom directives\n * in the Alwatr Synapse library. Directives are used to attach behavior and logic to DOM elements\n * declaratively.\n */\n\nimport {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\n/**\n * The abstract base class for all directives.\n *\n * Extend this class to create a new directive that can be registered with the `@directive` decorator.\n * It provides the core functionality for linking a TypeScript class to a DOM element and managing its lifecycle.\n *\n * @example\n * ```ts\n * import {DirectiveBase, directive} from '@alwatr/synapse';\n *\n * @directive('[my-directive]')\n * export class MyDirective extends DirectiveBase {\n * protected override init_(): void {\n * super.init_(); // فراخوانی متد والد برای حفظ سازگاری با نسخه‌های قبل ضروری است\n * this.element_.textContent = 'Hello from MyDirective!';\n * this.element_.addEventListener('click', () => this.log('Element clicked!'));\n * }\n * }\n * ```\n */\nexport abstract class DirectiveBase {\n /**\n * The CSS selector that this directive is associated with.\n * This is the selector string provided to the `@directive` decorator.\n */\n protected readonly selector_;\n\n /**\n * A dedicated logger instance for this directive, pre-configured with a context like `directive:[selector]`.\n * Use this for logging to provide clear, contextual messages.\n */\n protected readonly logger_;\n\n /**\n * The DOM element to which this directive instance is attached.\n * All directive logic operates on this element.\n */\n protected readonly element_: HTMLElement;\n\n /**\n * A list of callback functions to be executed when the directive is destroyed.\n */\n private readonly cleanupTaskList__: NoopFunc[] = [];\n\n /**\n * Initializes the directive. This constructor is called by the Synapse bootstrap process and should not be\n * overridden in subclasses.\n *\n * It sets up the logger, element, and selector, and then schedules the `init_` and `update_` lifecycle methods\n * to run in the next microtask.\n *\n * @param element The DOM element to which this directive is attached.\n * @param selector The CSS selector that matched this directive.\n */\n 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 })();\n }\n\n /**\n * Called once automatically after the directive is initialized.\n *\n * This method serves as the main entry point for your directive's logic,\n * such as modifying the element or setting up event listeners.\n *\n * **Note:** Do not call this method directly. It is designed to be called only once by the framework.\n */\n protected init_(): Awaitable<void> {\n this.logger_.logMethod?.('init_');\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this as any).update_?.(); // backward compatibility\n }\n\n /**\n * Dispatches a custom event from the target element.\n *\n * This is a convenience method for firing events that can be listened to by other parts of the application.\n * The event bubbles up through the DOM.\n *\n * @param eventName The name of the custom event.\n * @param detail Optional data to include in the event's `detail` property.\n *\n * @example\n * ```ts\n * this.dispatch_('user-action', {action: 'save', id: 123});\n * ```\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 /**\n * Registers a task to be executed when the directive is destroyed.\n * Follows the `on[Event]` pattern, similar to `onClick`.\n * Useful for cleaning up resources, such as unsubscribing from signals or removing global event listeners.\n *\n * @param task The cleanup task to register.\n *\n * @example\n * ```ts\n * this.onDestroy(\n * signal.subscribe(() => this.log('signal changed')).unsubscribe\n * );\n * ```\n */\n protected onDestroy_(task: NoopFunc): void {\n this.logger_.logMethod?.('onDestroy_');\n this.cleanupTaskList__.push(task);\n }\n\n /**\n * Cleans up the directive's resources.\n *\n * This method removes the element from the DOM and nullifies the internal reference to it,\n * helping with garbage collection. It can be extended by subclasses to perform additional cleanup,\n * such as removing event listeners.\n */\n protected destroy_(): Awaitable<void> {\n this.logger_.logMethod?.('destroy_');\n\n // Execute all registered cleanup tasks\n if (this.cleanupTaskList__.length > 0) {\n for (const task of this.cleanupTaskList__) {\n try {\n task.call(this);\n }\n catch (err) {\n this.logger_.error('destroy_', 'error_in_destroy_callback', err);\n }\n }\n\n this.cleanupTaskList__.length = 0; // clear the list after executing all tasks\n }\n\n this.element_.remove();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this as any).element_ = null;\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 * @param root Optional root element to perform the query on. Defaults to the directive's element.\n *\n * @example\n * ```ts\n * @directive('[my-directive]')\n * class MyDirective extends DirectiveBase {\n * @query('.my-element')\n * protected myElement: HTMLDivElement | null;\n * }\n * ```\n */\nexport function query(selector: string, cache = true, root?: ParentNode) {\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 const parent = root ?? this.element_;\n (this as any)[privateKey] = parent.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 * @param cache Whether to cache the result on first access. Defaults is true.\n * @param root Optional root element to perform the query on. Defaults to the directive's element.\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, root?: ParentNode) {\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 const parent = root ?? this.element_;\n (this as any)[privateKey] = parent.querySelectorAll(selector);\n }\n return (this as any)[privateKey];\n },\n configurable: true,\n enumerable: true,\n });\n };\n}\n"],
5
- "mappings": "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",
6
- "names": ["import_logger"]
7
- }
package/dist/main.mjs DELETED
@@ -1,3 +0,0 @@
1
- /** 📦 @alwatr/synapse v1.4.1 */
2
- import{createLogger}from"@alwatr/logger";var logger=createLogger("alwatr/synapse");var directiveRegistry_=[];var initializedAttribute="synapse";function bootstrapDirectives(rootElement=document.body){logger.logMethod?.("bootstrapDirectives");if(document.readyState==="loading"){logger.incident?.("bootstrapDirectives","dom_not_ready","Delaying directive initialization until DOM is ready");document.addEventListener("DOMContentLoaded",()=>{bootstrapDirectives(rootElement)},{once:true});return}for(const{selector,constructor}of directiveRegistry_){try{const uninitializedSelector=`${selector}:not([${initializedAttribute}])`;const elements=rootElement.querySelectorAll(uninitializedSelector);if(elements.length===0)continue;logger.logOther?.(`Found ${elements.length} new element(s) for directive "${selector}"`);elements.forEach(element=>{element.setAttribute(initializedAttribute,"");new constructor(element,selector)})}catch(err){logger.error("bootstrapDirectives","directive_instantiation_error",err,{selector})}}}function directive(selector){logger.logMethodArgs?.("@directive",selector);return function(constructor){directiveRegistry_.push({selector,constructor})}}import{delay}from"@alwatr/delay";import{createLogger as createLogger2}from"@alwatr/logger";var DirectiveBase=class{constructor(element,selector){this.cleanupTaskList__=[];this.logger_=createLogger2(`directive:${selector}`);this.logger_.logMethodArgs?.("new",{selector,element});this.selector_=selector;this.element_=element;(async()=>{await delay.nextMicrotask();await this.init_()})()}init_(){this.logger_.logMethod?.("init_");this.update_?.()}dispatch_(eventName,detail){this.logger_.logMethodArgs?.("dispatch_",{eventName,detail});this.element_.dispatchEvent(new CustomEvent(eventName,{detail,bubbles:true}))}onDestroy_(task){this.logger_.logMethod?.("onDestroy_");this.cleanupTaskList__.push(task)}destroy_(){this.logger_.logMethod?.("destroy_");if(this.cleanupTaskList__.length>0){for(const task of this.cleanupTaskList__){try{task.call(this)}catch(err){this.logger_.error("destroy_","error_in_destroy_callback",err)}}this.cleanupTaskList__.length=0}this.element_.remove();this.element_=null}};function query(selector,cache=true,root){return function(target,propertyKey){const privateKey=Symbol(`${String(propertyKey)}__`);Object.defineProperty(target,propertyKey,{get(){if(cache===false||this[privateKey]===void 0){const parent=root??this.element_;this[privateKey]=parent.querySelector(selector)}return this[privateKey]},configurable:true,enumerable:true})}}function queryAll(selector,cache=true,root){return function(target,propertyKey){const privateKey=Symbol(`${String(propertyKey)}__`);Object.defineProperty(target,propertyKey,{get(){if(cache===false||this[privateKey]===void 0){const parent=root??this.element_;this[privateKey]=parent.querySelectorAll(selector)}return this[privateKey]},configurable:true,enumerable:true})}}export{DirectiveBase,bootstrapDirectives,directive,query,queryAll};
3
- //# sourceMappingURL=main.mjs.map
package/dist/main.mjs.map DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/lib.ts", "../src/bootstrap.ts", "../src/directiveDecorator.ts", "../src/directiveClass.ts", "../src/queryDecorator.ts"],
4
- "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 = 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 = 'synapse';\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 if (document.readyState === 'loading') {\n logger.incident?.('bootstrapDirectives', 'dom_not_ready', 'Delaying directive initialization until DOM is ready');\n document.addEventListener('DOMContentLoaded', () => {\n bootstrapDirectives(rootElement);\n }, {once: true});\n return;\n }\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, '');\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", "/**\n * @package @alwatr/synapse\n *\n * This file defines the `DirectiveBase` class, which is the foundation for creating custom directives\n * in the Alwatr Synapse library. Directives are used to attach behavior and logic to DOM elements\n * declaratively.\n */\n\nimport {delay} from '@alwatr/delay';\nimport {createLogger} from '@alwatr/logger';\n\n/**\n * The abstract base class for all directives.\n *\n * Extend this class to create a new directive that can be registered with the `@directive` decorator.\n * It provides the core functionality for linking a TypeScript class to a DOM element and managing its lifecycle.\n *\n * @example\n * ```ts\n * import {DirectiveBase, directive} from '@alwatr/synapse';\n *\n * @directive('[my-directive]')\n * export class MyDirective extends DirectiveBase {\n * protected override init_(): void {\n * super.init_(); // فراخوانی متد والد برای حفظ سازگاری با نسخه‌های قبل ضروری است\n * this.element_.textContent = 'Hello from MyDirective!';\n * this.element_.addEventListener('click', () => this.log('Element clicked!'));\n * }\n * }\n * ```\n */\nexport abstract class DirectiveBase {\n /**\n * The CSS selector that this directive is associated with.\n * This is the selector string provided to the `@directive` decorator.\n */\n protected readonly selector_;\n\n /**\n * A dedicated logger instance for this directive, pre-configured with a context like `directive:[selector]`.\n * Use this for logging to provide clear, contextual messages.\n */\n protected readonly logger_;\n\n /**\n * The DOM element to which this directive instance is attached.\n * All directive logic operates on this element.\n */\n protected readonly element_: HTMLElement;\n\n /**\n * A list of callback functions to be executed when the directive is destroyed.\n */\n private readonly cleanupTaskList__: NoopFunc[] = [];\n\n /**\n * Initializes the directive. This constructor is called by the Synapse bootstrap process and should not be\n * overridden in subclasses.\n *\n * It sets up the logger, element, and selector, and then schedules the `init_` and `update_` lifecycle methods\n * to run in the next microtask.\n *\n * @param element The DOM element to which this directive is attached.\n * @param selector The CSS selector that matched this directive.\n */\n 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 })();\n }\n\n /**\n * Called once automatically after the directive is initialized.\n *\n * This method serves as the main entry point for your directive's logic,\n * such as modifying the element or setting up event listeners.\n *\n * **Note:** Do not call this method directly. It is designed to be called only once by the framework.\n */\n protected init_(): Awaitable<void> {\n this.logger_.logMethod?.('init_');\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this as any).update_?.(); // backward compatibility\n }\n\n /**\n * Dispatches a custom event from the target element.\n *\n * This is a convenience method for firing events that can be listened to by other parts of the application.\n * The event bubbles up through the DOM.\n *\n * @param eventName The name of the custom event.\n * @param detail Optional data to include in the event's `detail` property.\n *\n * @example\n * ```ts\n * this.dispatch_('user-action', {action: 'save', id: 123});\n * ```\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 /**\n * Registers a task to be executed when the directive is destroyed.\n * Follows the `on[Event]` pattern, similar to `onClick`.\n * Useful for cleaning up resources, such as unsubscribing from signals or removing global event listeners.\n *\n * @param task The cleanup task to register.\n *\n * @example\n * ```ts\n * this.onDestroy(\n * signal.subscribe(() => this.log('signal changed')).unsubscribe\n * );\n * ```\n */\n protected onDestroy_(task: NoopFunc): void {\n this.logger_.logMethod?.('onDestroy_');\n this.cleanupTaskList__.push(task);\n }\n\n /**\n * Cleans up the directive's resources.\n *\n * This method removes the element from the DOM and nullifies the internal reference to it,\n * helping with garbage collection. It can be extended by subclasses to perform additional cleanup,\n * such as removing event listeners.\n */\n protected destroy_(): Awaitable<void> {\n this.logger_.logMethod?.('destroy_');\n\n // Execute all registered cleanup tasks\n if (this.cleanupTaskList__.length > 0) {\n for (const task of this.cleanupTaskList__) {\n try {\n task.call(this);\n }\n catch (err) {\n this.logger_.error('destroy_', 'error_in_destroy_callback', err);\n }\n }\n\n this.cleanupTaskList__.length = 0; // clear the list after executing all tasks\n }\n\n this.element_.remove();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this as any).element_ = null;\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 * @param root Optional root element to perform the query on. Defaults to the directive's element.\n *\n * @example\n * ```ts\n * @directive('[my-directive]')\n * class MyDirective extends DirectiveBase {\n * @query('.my-element')\n * protected myElement: HTMLDivElement | null;\n * }\n * ```\n */\nexport function query(selector: string, cache = true, root?: ParentNode) {\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 const parent = root ?? this.element_;\n (this as any)[privateKey] = parent.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 * @param cache Whether to cache the result on first access. Defaults is true.\n * @param root Optional root element to perform the query on. Defaults to the directive's element.\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, root?: ParentNode) {\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 const parent = root ?? this.element_;\n (this as any)[privateKey] = parent.querySelectorAll(selector);\n }\n return (this as any)[privateKey];\n },\n configurable: true,\n enumerable: true,\n });\n };\n}\n"],
5
- "mappings": "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",
6
- "names": ["createLogger"]
7
- }