auto-wc 0.1.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/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # auto-wc
2
+
3
+ ![Tests](https://img.shields.io/badge/tests-passing-brightgreen)
4
+ ![Size](https://img.shields.io/badge/minified-1.23KB-blue)
5
+ ![AI](https://img.shields.io/badge/AI-Generated-orange)
6
+
7
+ Type-safe Web Components with automatic event wiring.
8
+
9
+ ## Installation
10
+
11
+ ### Node.js (npm, pnpm, bun)
12
+
13
+ ```bash
14
+ # npm
15
+ npm install auto-wc
16
+
17
+ # pnpm
18
+ pnpm add auto-wc
19
+
20
+ # bun
21
+ bun add auto-wc
22
+ ```
23
+
24
+ ### CDN Usage
25
+
26
+ You can import `auto-wc` directly from jsDelivr or unpkg without a build step.
27
+
28
+ #### Standard (ES Modules)
29
+
30
+ ```html
31
+ <script type="module">
32
+ import { defineAutoWebComponent } from 'https://cdn.jsdelivr.net/npm/auto-wc/+esm';
33
+ </script>
34
+ ```
35
+
36
+ #### Minified (Recommended for Production)
37
+
38
+ ```html
39
+ <!-- unpkg -->
40
+ <script type="module">
41
+ import { defineAutoWebComponent } from 'https://unpkg.com/auto-wc/dist/index.min.js';
42
+ </script>
43
+
44
+ <!-- jsDelivr -->
45
+ <script type="module">
46
+ import { defineAutoWebComponent } from 'https://cdn.jsdelivr.net/npm/auto-wc/dist/index.min.js';
47
+ </script>
48
+ ```
49
+
50
+ Example usage:
51
+
52
+ ```html
53
+ <script type="module">
54
+ import { defineAutoWebComponent } from 'https://unpkg.com/auto-wc/dist/index.min.js';
55
+
56
+ defineAutoWebComponent('my-button', 'button', (Base) => class extends Base {
57
+ onClick() {
58
+ console.log('Clicked!');
59
+ }
60
+ });
61
+ </script>
62
+ ```
63
+
64
+ #### Development (Unminified)
65
+
66
+ ```html
67
+ <!-- jsDelivr -->
68
+ <script type="module">
69
+ import { defineAutoWebComponent } from 'https://cdn.jsdelivr.net/npm/auto-wc/+esm';
70
+ </script>
71
+
72
+ <!-- unpkg -->
73
+ <script type="module">
74
+ import { defineAutoWebComponent } from 'https://unpkg.com/auto-wc/dist/index.js';
75
+ </script>
76
+ ```
77
+
78
+ Example usage:
79
+
80
+ ```html
81
+ <script type="module">
82
+ import { defineAutoWebComponent } from 'https://cdn.jsdelivr.net/npm/auto-wc/+esm';
83
+
84
+ defineAutoWebComponent('my-button', 'button', (Base) => class extends Base {
85
+ onClick() {
86
+ console.log('Clicked!');
87
+ }
88
+ });
89
+ </script>
90
+ ```
91
+
92
+ ## Usage
93
+
94
+ ### TypeScript / ES Modules
95
+
96
+ ```typescript
97
+ import { defineAutoWebComponent } from "auto-wc";
98
+
99
+ defineAutoWebComponent(
100
+ "my-button",
101
+ "button",
102
+ (Base) =>
103
+ class extends Base {
104
+ // ...
105
+ }
106
+ );
107
+ ```
108
+
109
+ ### CommonJS (require)
110
+
111
+ If you are using a legacy environment:
112
+
113
+ ```javascript
114
+ const { defineAutoWebComponent } = require("auto-wc");
115
+
116
+ defineAutoWebComponent("my-button", "button", (Base) => class extends Base {
117
+ // ...
118
+ });
119
+ ```
120
+
121
+ ### Full Example
122
+
123
+ ```typescript
124
+ import { defineAutoWebComponent } from "auto-wc";
125
+
126
+ defineAutoWebComponent(
127
+ "my-button",
128
+ "button",
129
+ (Base) =>
130
+ class extends Base {
131
+ // Automatically wired to 'click' event
132
+ onClick(e: MouseEvent) {
133
+ console.log("Button clicked!");
134
+ }
135
+
136
+ // Automatically wired to 'dblclick' event
137
+ onDblClick(e: MouseEvent) {
138
+ console.log("Double clicked!");
139
+ }
140
+
141
+ // Standard lifecycle methods work as expected
142
+ connectedCallback() {
143
+ super.connectedCallback();
144
+ console.log("Connected");
145
+ }
146
+ },
147
+ { observedAttributes: ["disabled"] },
148
+ );
149
+ ```
150
+
151
+ ### HTML Usage (is="" directive)
152
+
153
+ Since this library creates **customized built-in elements**, you must use the `is` attribute on the extended standard HTML element:
154
+
155
+ ```html
156
+ <!-- Correct -->
157
+ <button is="my-button">Click Me</button>
158
+
159
+ <!-- Incorrect (won't work for built-in extends) -->
160
+ <my-button>I will never work!</my-button>
161
+ ```
162
+
163
+ ### Using Import Maps
164
+
165
+ You can use Import Maps to manage your dependencies cleanly in the browser:
166
+
167
+ ```html
168
+ <script type="importmap">
169
+ {
170
+ "imports": {
171
+ "auto-wc": "https://unpkg.com/auto-wc/dist/index.min.js"
172
+ }
173
+ }
174
+ </script>
175
+
176
+ <script type="module">
177
+ import { defineAutoWebComponent } from "auto-wc";
178
+
179
+ defineAutoWebComponent("my-button", "button", (Base) => class extends Base {
180
+ // ...
181
+ });
182
+ </script>
183
+ ```
184
+
185
+ ## Features
186
+
187
+ - ✅ **Type-safe**: Full TypeScript support for DOM events.
188
+ - ✅ **Automatic Wiring**: Methods starting with `on` (e.g., `onClick`) are automatically bound to events.
189
+ - ✅ **Automatic Registration**: `customElements.define` is called automatically.
190
+ - ✅ **Strict**: Event handler properties are read-only to prevent runtime reassignment.
191
+ - ✅ **Cleanup**: Event listeners are automatically removed on disconnect.
192
+ - ✅ **Zero Dependencies**: Lightweight and fast.
193
+
194
+ ## API
195
+
196
+ ### `defineAutoWebComponent(tagName, extendsTag, factory, options)`
197
+
198
+ - `tagName`: The custom element tag name (e.g., `'my-button'`).
199
+ - `extendsTag`: The HTML tag to extend (e.g., `'button'`, `'div'`).
200
+ - `factory`: A function that receives the `Base` class and returns your implementation.
201
+ - `options`: Optional configuration object.
202
+ - `observedAttributes`: Array of attribute names to observe.
203
+
204
+ This function automatically calls `customElements.define` with the provided `tagName` and the generated class.
205
+
206
+ ## License
207
+
208
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ defineAutoWebComponent: () => defineAutoWebComponent
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ function isEventInterceptorMethod(method) {
27
+ return /^on[A-Z]/.test(method);
28
+ }
29
+ function getEventName(methodName) {
30
+ return methodName.substring(2).toLowerCase();
31
+ }
32
+ function withAutoEvents(Base) {
33
+ class AutoElement extends Base {
34
+ _cleanupFns = [];
35
+ connectedCallback() {
36
+ if (super.connectedCallback) super.connectedCallback();
37
+ this._wireAndLockEvents();
38
+ }
39
+ disconnectedCallback() {
40
+ if (super.disconnectedCallback) super.disconnectedCallback();
41
+ this._unwireEvents();
42
+ }
43
+ _wireAndLockEvents() {
44
+ const hostMethods = /* @__PURE__ */ new Set();
45
+ let curr = this;
46
+ while (curr && curr !== Object.prototype) {
47
+ Object.getOwnPropertyNames(curr).forEach((prop) => {
48
+ if (isEventInterceptorMethod(prop)) hostMethods.add(prop);
49
+ });
50
+ curr = Object.getPrototypeOf(curr);
51
+ }
52
+ for (const method of hostMethods) {
53
+ const eventName = getEventName(method);
54
+ const originalHostFn = this[method];
55
+ if (typeof originalHostFn !== "function") continue;
56
+ const handler = originalHostFn.bind(this);
57
+ this.addEventListener(eventName, handler);
58
+ this._cleanupFns.push(() => {
59
+ this.removeEventListener(eventName, handler);
60
+ });
61
+ Object.defineProperty(this, method, {
62
+ value: originalHostFn,
63
+ writable: false,
64
+ configurable: true
65
+ });
66
+ }
67
+ }
68
+ _unwireEvents() {
69
+ for (const cleanup of this._cleanupFns) {
70
+ cleanup();
71
+ }
72
+ this._cleanupFns = [];
73
+ }
74
+ }
75
+ return AutoElement;
76
+ }
77
+ function defineAutoWebComponent(tagName, extendsTag, factory, options = {}) {
78
+ const BaseClass = document.createElement(extendsTag).constructor;
79
+ const ImplementationClass = factory(withAutoEvents(BaseClass));
80
+ if (customElements.get(tagName)) {
81
+ console.warn(`Custom element ${tagName} is already registered`);
82
+ return;
83
+ }
84
+ if (options.observedAttributes) {
85
+ ImplementationClass.observedAttributes = options.observedAttributes;
86
+ }
87
+ customElements.define(tagName, ImplementationClass, {
88
+ extends: extendsTag
89
+ });
90
+ }
91
+ // Annotate the CommonJS export names for ESM import in node:
92
+ 0 && (module.exports = {
93
+ defineAutoWebComponent
94
+ });
95
+ /**
96
+ * @license MIT
97
+ * Auto Web Component - Type-safe Web Components with automatic event wiring.
98
+ */
99
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @license MIT\n * Auto Web Component - Type-safe Web Components with automatic event wiring.\n */\n\n// --- 1. Standard DOM Type Helpers ---\n\n/**\n * Represents any valid HTML tag name (e.g., 'div', 'button', 'input').\n */\nexport type TagName = keyof HTMLElementTagNameMap;\n\n/**\n * Maps a tag name to its specific HTMLElement class.\n * e.g., TagClass<'button'> resolves to HTMLButtonElement.\n */\nexport type TagClass<T extends TagName> = HTMLElementTagNameMap[T];\n\nexport type Constructor<T = {}> = new (...args: any[]) => T;\n\nexport interface AutoWebComponentOptions<\n ObservedAttributes extends string[] = string[],\n> {\n observedAttributes?: ObservedAttributes;\n}\n\n// --- 2. Event System Configuration ---\n\n/**\n * Interface defining all supported event interceptors.\n * Implement these methods in your class to automatically handle events.\n *\n * Naming Convention: on{EventName} -> eventname\n * Example: onClick -> click, onMouseDown -> mousedown, onDblClick -> dblclick\n */\nexport interface EventInterceptors {\n // Mouse\n onClick?(e: MouseEvent): void;\n onDblClick?(e: MouseEvent): void; // Maps to 'dblclick'\n onMouseDown?(e: MouseEvent): void;\n onMouseUp?(e: MouseEvent): void;\n onMouseEnter?(e: MouseEvent): void;\n onMouseLeave?(e: MouseEvent): void;\n onMouseMove?(e: MouseEvent): void;\n onMouseOver?(e: MouseEvent): void;\n onMouseOut?(e: MouseEvent): void;\n onContextMenu?(e: MouseEvent): void;\n\n // Keyboard\n onKeyDown?(e: KeyboardEvent): void;\n onKeyUp?(e: KeyboardEvent): void;\n onKeyPress?(e: KeyboardEvent): void;\n\n // Form / Input\n onInput?(e: Event): void;\n onChange?(e: Event): void;\n onSubmit?(e: SubmitEvent): void;\n onFocus?(e: FocusEvent): void;\n onBlur?(e: FocusEvent): void;\n onReset?(e: Event): void;\n onInvalid?(e: Event): void;\n onSelect?(e: Event): void;\n\n // Drag & Drop\n onDrag?(e: DragEvent): void;\n onDragEnd?(e: DragEvent): void;\n onDragEnter?(e: DragEvent): void;\n onDragLeave?(e: DragEvent): void;\n onDragOver?(e: DragEvent): void;\n onDragStart?(e: DragEvent): void;\n onDrop?(e: DragEvent): void;\n\n // Clipboard\n onCopy?(e: ClipboardEvent): void;\n onCut?(e: ClipboardEvent): void;\n onPaste?(e: ClipboardEvent): void;\n\n // UI / Window\n onScroll?(e: Event): void;\n onResize?(e: UIEvent): void;\n onWheel?(e: WheelEvent): void;\n\n // Media\n onLoad?(e: Event): void;\n onError?(e: ErrorEvent): void;\n onPlay?(e: Event): void;\n onPause?(e: Event): void;\n onEnded?(e: Event): void;\n\n // Details/Dialog\n onToggle?(e: Event): void;\n}\n\n// --- 3. Internal Logic ---\n\nfunction isEventInterceptorMethod(method: string): boolean {\n return /^on[A-Z]/.test(method);\n}\n\nfunction getEventName(methodName: string): string {\n // Simple rule: strip 'on' and lowercase the rest\n // onClick -> click\n // onDblClick -> dblclick\n // onMouseDown -> mousedown\n return methodName.substring(2).toLowerCase();\n}\n\n/**\n * Mixin that adds auto event wiring to a base class.\n */\nfunction withAutoEvents<T extends Constructor<HTMLElement>>(Base: T) {\n // @ts-expect-error - Dynamic class extension\n class AutoElement extends Base implements EventInterceptors {\n private _cleanupFns: Array<() => void> = [];\n\n connectedCallback() {\n // @ts-expect-error - super access in mixin\n if (super.connectedCallback) super.connectedCallback();\n this._wireAndLockEvents();\n }\n\n disconnectedCallback() {\n // @ts-expect-error - super access in mixin\n if (super.disconnectedCallback) super.disconnectedCallback();\n this._unwireEvents();\n }\n\n private _wireAndLockEvents() {\n // Scan the prototype chain for methods starting with 'on'\n const hostMethods = new Set<string>();\n let curr = this;\n\n // Walk up prototype chain to find methods\n while (curr && curr !== Object.prototype) {\n Object.getOwnPropertyNames(curr).forEach((prop) => {\n if (isEventInterceptorMethod(prop)) hostMethods.add(prop);\n });\n curr = Object.getPrototypeOf(curr);\n }\n\n for (const method of hostMethods) {\n const eventName = getEventName(method);\n const originalHostFn = this[method as keyof typeof this];\n\n if (typeof originalHostFn !== \"function\") continue;\n\n // 1. Wire the event\n const handler = originalHostFn.bind(this) as EventListener;\n this.addEventListener(eventName, handler);\n this._cleanupFns.push(() => {\n this.removeEventListener(eventName, handler);\n });\n\n // 2. Lock the property to prevent runtime reassignment\n Object.defineProperty(this, method, {\n value: originalHostFn,\n writable: false,\n configurable: true,\n });\n }\n }\n\n private _unwireEvents() {\n for (const cleanup of this._cleanupFns) {\n cleanup();\n }\n this._cleanupFns = [];\n }\n }\n\n return AutoElement as unknown as T & Constructor<EventInterceptors>;\n}\n\n// --- 4. Public API ---\n\n/**\n * Defines a type-safe Web Component with auto event handling.\n *\n * @param tagName - The custom element tag name (e.g., 'my-button')\n * @param extendsTag - The HTML tag to extend (e.g., 'button', 'div')\n * @param factory - A function that receives the Base class and returns your implementation\n * @param options - Optional configuration (observedAttributes, etc.)\n */\nexport function defineAutoWebComponent<\n T extends TagName,\n ObservedAttributes extends string[] = string[],\n>(\n tagName: string,\n extendsTag: T,\n factory: (\n base: Constructor<TagClass<T> & EventInterceptors>,\n ) => Constructor<TagClass<T>> & { observedAttributes?: ObservedAttributes },\n options: AutoWebComponentOptions<ObservedAttributes> = {},\n): void {\n const BaseClass = document.createElement(extendsTag)\n .constructor as Constructor<TagClass<T>>;\n const ImplementationClass = factory(withAutoEvents(BaseClass));\n\n if (customElements.get(tagName)) {\n console.warn(`Custom element ${tagName} is already registered`);\n return;\n }\n\n if (options.observedAttributes) {\n ImplementationClass.observedAttributes = options.observedAttributes;\n }\n\n customElements.define(tagName, ImplementationClass, {\n extends: extendsTag,\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA+FA,SAAS,yBAAyB,QAAyB;AACzD,SAAO,WAAW,KAAK,MAAM;AAC/B;AAEA,SAAS,aAAa,YAA4B;AAKhD,SAAO,WAAW,UAAU,CAAC,EAAE,YAAY;AAC7C;AAKA,SAAS,eAAmD,MAAS;AAAA,EAEnE,MAAM,oBAAoB,KAAkC;AAAA,IAClD,cAAiC,CAAC;AAAA,IAE1C,oBAAoB;AAElB,UAAI,MAAM,kBAAmB,OAAM,kBAAkB;AACrD,WAAK,mBAAmB;AAAA,IAC1B;AAAA,IAEA,uBAAuB;AAErB,UAAI,MAAM,qBAAsB,OAAM,qBAAqB;AAC3D,WAAK,cAAc;AAAA,IACrB;AAAA,IAEQ,qBAAqB;AAE3B,YAAM,cAAc,oBAAI,IAAY;AACpC,UAAI,OAAO;AAGX,aAAO,QAAQ,SAAS,OAAO,WAAW;AACxC,eAAO,oBAAoB,IAAI,EAAE,QAAQ,CAAC,SAAS;AACjD,cAAI,yBAAyB,IAAI,EAAG,aAAY,IAAI,IAAI;AAAA,QAC1D,CAAC;AACD,eAAO,OAAO,eAAe,IAAI;AAAA,MACnC;AAEA,iBAAW,UAAU,aAAa;AAChC,cAAM,YAAY,aAAa,MAAM;AACrC,cAAM,iBAAiB,KAAK,MAA2B;AAEvD,YAAI,OAAO,mBAAmB,WAAY;AAG1C,cAAM,UAAU,eAAe,KAAK,IAAI;AACxC,aAAK,iBAAiB,WAAW,OAAO;AACxC,aAAK,YAAY,KAAK,MAAM;AAC1B,eAAK,oBAAoB,WAAW,OAAO;AAAA,QAC7C,CAAC;AAGD,eAAO,eAAe,MAAM,QAAQ;AAAA,UAClC,OAAO;AAAA,UACP,UAAU;AAAA,UACV,cAAc;AAAA,QAChB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IAEQ,gBAAgB;AACtB,iBAAW,WAAW,KAAK,aAAa;AACtC,gBAAQ;AAAA,MACV;AACA,WAAK,cAAc,CAAC;AAAA,IACtB;AAAA,EACF;AAEA,SAAO;AACT;AAYO,SAAS,uBAId,SACA,YACA,SAGA,UAAuD,CAAC,GAClD;AACN,QAAM,YAAY,SAAS,cAAc,UAAU,EAChD;AACH,QAAM,sBAAsB,QAAQ,eAAe,SAAS,CAAC;AAE7D,MAAI,eAAe,IAAI,OAAO,GAAG;AAC/B,YAAQ,KAAK,kBAAkB,OAAO,wBAAwB;AAC9D;AAAA,EACF;AAEA,MAAI,QAAQ,oBAAoB;AAC9B,wBAAoB,qBAAqB,QAAQ;AAAA,EACnD;AAEA,iBAAe,OAAO,SAAS,qBAAqB;AAAA,IAClD,SAAS;AAAA,EACX,CAAC;AACH;","names":[]}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * @license MIT
3
+ * Auto Web Component - Type-safe Web Components with automatic event wiring.
4
+ */
5
+ /**
6
+ * Represents any valid HTML tag name (e.g., 'div', 'button', 'input').
7
+ */
8
+ type TagName = keyof HTMLElementTagNameMap;
9
+ /**
10
+ * Maps a tag name to its specific HTMLElement class.
11
+ * e.g., TagClass<'button'> resolves to HTMLButtonElement.
12
+ */
13
+ type TagClass<T extends TagName> = HTMLElementTagNameMap[T];
14
+ type Constructor<T = {}> = new (...args: any[]) => T;
15
+ interface AutoWebComponentOptions<ObservedAttributes extends string[] = string[]> {
16
+ observedAttributes?: ObservedAttributes;
17
+ }
18
+ /**
19
+ * Interface defining all supported event interceptors.
20
+ * Implement these methods in your class to automatically handle events.
21
+ *
22
+ * Naming Convention: on{EventName} -> eventname
23
+ * Example: onClick -> click, onMouseDown -> mousedown, onDblClick -> dblclick
24
+ */
25
+ interface EventInterceptors {
26
+ onClick?(e: MouseEvent): void;
27
+ onDblClick?(e: MouseEvent): void;
28
+ onMouseDown?(e: MouseEvent): void;
29
+ onMouseUp?(e: MouseEvent): void;
30
+ onMouseEnter?(e: MouseEvent): void;
31
+ onMouseLeave?(e: MouseEvent): void;
32
+ onMouseMove?(e: MouseEvent): void;
33
+ onMouseOver?(e: MouseEvent): void;
34
+ onMouseOut?(e: MouseEvent): void;
35
+ onContextMenu?(e: MouseEvent): void;
36
+ onKeyDown?(e: KeyboardEvent): void;
37
+ onKeyUp?(e: KeyboardEvent): void;
38
+ onKeyPress?(e: KeyboardEvent): void;
39
+ onInput?(e: Event): void;
40
+ onChange?(e: Event): void;
41
+ onSubmit?(e: SubmitEvent): void;
42
+ onFocus?(e: FocusEvent): void;
43
+ onBlur?(e: FocusEvent): void;
44
+ onReset?(e: Event): void;
45
+ onInvalid?(e: Event): void;
46
+ onSelect?(e: Event): void;
47
+ onDrag?(e: DragEvent): void;
48
+ onDragEnd?(e: DragEvent): void;
49
+ onDragEnter?(e: DragEvent): void;
50
+ onDragLeave?(e: DragEvent): void;
51
+ onDragOver?(e: DragEvent): void;
52
+ onDragStart?(e: DragEvent): void;
53
+ onDrop?(e: DragEvent): void;
54
+ onCopy?(e: ClipboardEvent): void;
55
+ onCut?(e: ClipboardEvent): void;
56
+ onPaste?(e: ClipboardEvent): void;
57
+ onScroll?(e: Event): void;
58
+ onResize?(e: UIEvent): void;
59
+ onWheel?(e: WheelEvent): void;
60
+ onLoad?(e: Event): void;
61
+ onError?(e: ErrorEvent): void;
62
+ onPlay?(e: Event): void;
63
+ onPause?(e: Event): void;
64
+ onEnded?(e: Event): void;
65
+ onToggle?(e: Event): void;
66
+ }
67
+ /**
68
+ * Defines a type-safe Web Component with auto event handling.
69
+ *
70
+ * @param tagName - The custom element tag name (e.g., 'my-button')
71
+ * @param extendsTag - The HTML tag to extend (e.g., 'button', 'div')
72
+ * @param factory - A function that receives the Base class and returns your implementation
73
+ * @param options - Optional configuration (observedAttributes, etc.)
74
+ */
75
+ declare function defineAutoWebComponent<T extends TagName, ObservedAttributes extends string[] = string[]>(tagName: string, extendsTag: T, factory: (base: Constructor<TagClass<T> & EventInterceptors>) => Constructor<TagClass<T>> & {
76
+ observedAttributes?: ObservedAttributes;
77
+ }, options?: AutoWebComponentOptions<ObservedAttributes>): void;
78
+
79
+ export { type AutoWebComponentOptions, type Constructor, type EventInterceptors, type TagClass, type TagName, defineAutoWebComponent };
@@ -0,0 +1,79 @@
1
+ /**
2
+ * @license MIT
3
+ * Auto Web Component - Type-safe Web Components with automatic event wiring.
4
+ */
5
+ /**
6
+ * Represents any valid HTML tag name (e.g., 'div', 'button', 'input').
7
+ */
8
+ type TagName = keyof HTMLElementTagNameMap;
9
+ /**
10
+ * Maps a tag name to its specific HTMLElement class.
11
+ * e.g., TagClass<'button'> resolves to HTMLButtonElement.
12
+ */
13
+ type TagClass<T extends TagName> = HTMLElementTagNameMap[T];
14
+ type Constructor<T = {}> = new (...args: any[]) => T;
15
+ interface AutoWebComponentOptions<ObservedAttributes extends string[] = string[]> {
16
+ observedAttributes?: ObservedAttributes;
17
+ }
18
+ /**
19
+ * Interface defining all supported event interceptors.
20
+ * Implement these methods in your class to automatically handle events.
21
+ *
22
+ * Naming Convention: on{EventName} -> eventname
23
+ * Example: onClick -> click, onMouseDown -> mousedown, onDblClick -> dblclick
24
+ */
25
+ interface EventInterceptors {
26
+ onClick?(e: MouseEvent): void;
27
+ onDblClick?(e: MouseEvent): void;
28
+ onMouseDown?(e: MouseEvent): void;
29
+ onMouseUp?(e: MouseEvent): void;
30
+ onMouseEnter?(e: MouseEvent): void;
31
+ onMouseLeave?(e: MouseEvent): void;
32
+ onMouseMove?(e: MouseEvent): void;
33
+ onMouseOver?(e: MouseEvent): void;
34
+ onMouseOut?(e: MouseEvent): void;
35
+ onContextMenu?(e: MouseEvent): void;
36
+ onKeyDown?(e: KeyboardEvent): void;
37
+ onKeyUp?(e: KeyboardEvent): void;
38
+ onKeyPress?(e: KeyboardEvent): void;
39
+ onInput?(e: Event): void;
40
+ onChange?(e: Event): void;
41
+ onSubmit?(e: SubmitEvent): void;
42
+ onFocus?(e: FocusEvent): void;
43
+ onBlur?(e: FocusEvent): void;
44
+ onReset?(e: Event): void;
45
+ onInvalid?(e: Event): void;
46
+ onSelect?(e: Event): void;
47
+ onDrag?(e: DragEvent): void;
48
+ onDragEnd?(e: DragEvent): void;
49
+ onDragEnter?(e: DragEvent): void;
50
+ onDragLeave?(e: DragEvent): void;
51
+ onDragOver?(e: DragEvent): void;
52
+ onDragStart?(e: DragEvent): void;
53
+ onDrop?(e: DragEvent): void;
54
+ onCopy?(e: ClipboardEvent): void;
55
+ onCut?(e: ClipboardEvent): void;
56
+ onPaste?(e: ClipboardEvent): void;
57
+ onScroll?(e: Event): void;
58
+ onResize?(e: UIEvent): void;
59
+ onWheel?(e: WheelEvent): void;
60
+ onLoad?(e: Event): void;
61
+ onError?(e: ErrorEvent): void;
62
+ onPlay?(e: Event): void;
63
+ onPause?(e: Event): void;
64
+ onEnded?(e: Event): void;
65
+ onToggle?(e: Event): void;
66
+ }
67
+ /**
68
+ * Defines a type-safe Web Component with auto event handling.
69
+ *
70
+ * @param tagName - The custom element tag name (e.g., 'my-button')
71
+ * @param extendsTag - The HTML tag to extend (e.g., 'button', 'div')
72
+ * @param factory - A function that receives the Base class and returns your implementation
73
+ * @param options - Optional configuration (observedAttributes, etc.)
74
+ */
75
+ declare function defineAutoWebComponent<T extends TagName, ObservedAttributes extends string[] = string[]>(tagName: string, extendsTag: T, factory: (base: Constructor<TagClass<T> & EventInterceptors>) => Constructor<TagClass<T>> & {
76
+ observedAttributes?: ObservedAttributes;
77
+ }, options?: AutoWebComponentOptions<ObservedAttributes>): void;
78
+
79
+ export { type AutoWebComponentOptions, type Constructor, type EventInterceptors, type TagClass, type TagName, defineAutoWebComponent };
package/dist/index.js ADDED
@@ -0,0 +1,74 @@
1
+ // src/index.ts
2
+ function isEventInterceptorMethod(method) {
3
+ return /^on[A-Z]/.test(method);
4
+ }
5
+ function getEventName(methodName) {
6
+ return methodName.substring(2).toLowerCase();
7
+ }
8
+ function withAutoEvents(Base) {
9
+ class AutoElement extends Base {
10
+ _cleanupFns = [];
11
+ connectedCallback() {
12
+ if (super.connectedCallback) super.connectedCallback();
13
+ this._wireAndLockEvents();
14
+ }
15
+ disconnectedCallback() {
16
+ if (super.disconnectedCallback) super.disconnectedCallback();
17
+ this._unwireEvents();
18
+ }
19
+ _wireAndLockEvents() {
20
+ const hostMethods = /* @__PURE__ */ new Set();
21
+ let curr = this;
22
+ while (curr && curr !== Object.prototype) {
23
+ Object.getOwnPropertyNames(curr).forEach((prop) => {
24
+ if (isEventInterceptorMethod(prop)) hostMethods.add(prop);
25
+ });
26
+ curr = Object.getPrototypeOf(curr);
27
+ }
28
+ for (const method of hostMethods) {
29
+ const eventName = getEventName(method);
30
+ const originalHostFn = this[method];
31
+ if (typeof originalHostFn !== "function") continue;
32
+ const handler = originalHostFn.bind(this);
33
+ this.addEventListener(eventName, handler);
34
+ this._cleanupFns.push(() => {
35
+ this.removeEventListener(eventName, handler);
36
+ });
37
+ Object.defineProperty(this, method, {
38
+ value: originalHostFn,
39
+ writable: false,
40
+ configurable: true
41
+ });
42
+ }
43
+ }
44
+ _unwireEvents() {
45
+ for (const cleanup of this._cleanupFns) {
46
+ cleanup();
47
+ }
48
+ this._cleanupFns = [];
49
+ }
50
+ }
51
+ return AutoElement;
52
+ }
53
+ function defineAutoWebComponent(tagName, extendsTag, factory, options = {}) {
54
+ const BaseClass = document.createElement(extendsTag).constructor;
55
+ const ImplementationClass = factory(withAutoEvents(BaseClass));
56
+ if (customElements.get(tagName)) {
57
+ console.warn(`Custom element ${tagName} is already registered`);
58
+ return;
59
+ }
60
+ if (options.observedAttributes) {
61
+ ImplementationClass.observedAttributes = options.observedAttributes;
62
+ }
63
+ customElements.define(tagName, ImplementationClass, {
64
+ extends: extendsTag
65
+ });
66
+ }
67
+ export {
68
+ defineAutoWebComponent
69
+ };
70
+ /**
71
+ * @license MIT
72
+ * Auto Web Component - Type-safe Web Components with automatic event wiring.
73
+ */
74
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @license MIT\n * Auto Web Component - Type-safe Web Components with automatic event wiring.\n */\n\n// --- 1. Standard DOM Type Helpers ---\n\n/**\n * Represents any valid HTML tag name (e.g., 'div', 'button', 'input').\n */\nexport type TagName = keyof HTMLElementTagNameMap;\n\n/**\n * Maps a tag name to its specific HTMLElement class.\n * e.g., TagClass<'button'> resolves to HTMLButtonElement.\n */\nexport type TagClass<T extends TagName> = HTMLElementTagNameMap[T];\n\nexport type Constructor<T = {}> = new (...args: any[]) => T;\n\nexport interface AutoWebComponentOptions<\n ObservedAttributes extends string[] = string[],\n> {\n observedAttributes?: ObservedAttributes;\n}\n\n// --- 2. Event System Configuration ---\n\n/**\n * Interface defining all supported event interceptors.\n * Implement these methods in your class to automatically handle events.\n *\n * Naming Convention: on{EventName} -> eventname\n * Example: onClick -> click, onMouseDown -> mousedown, onDblClick -> dblclick\n */\nexport interface EventInterceptors {\n // Mouse\n onClick?(e: MouseEvent): void;\n onDblClick?(e: MouseEvent): void; // Maps to 'dblclick'\n onMouseDown?(e: MouseEvent): void;\n onMouseUp?(e: MouseEvent): void;\n onMouseEnter?(e: MouseEvent): void;\n onMouseLeave?(e: MouseEvent): void;\n onMouseMove?(e: MouseEvent): void;\n onMouseOver?(e: MouseEvent): void;\n onMouseOut?(e: MouseEvent): void;\n onContextMenu?(e: MouseEvent): void;\n\n // Keyboard\n onKeyDown?(e: KeyboardEvent): void;\n onKeyUp?(e: KeyboardEvent): void;\n onKeyPress?(e: KeyboardEvent): void;\n\n // Form / Input\n onInput?(e: Event): void;\n onChange?(e: Event): void;\n onSubmit?(e: SubmitEvent): void;\n onFocus?(e: FocusEvent): void;\n onBlur?(e: FocusEvent): void;\n onReset?(e: Event): void;\n onInvalid?(e: Event): void;\n onSelect?(e: Event): void;\n\n // Drag & Drop\n onDrag?(e: DragEvent): void;\n onDragEnd?(e: DragEvent): void;\n onDragEnter?(e: DragEvent): void;\n onDragLeave?(e: DragEvent): void;\n onDragOver?(e: DragEvent): void;\n onDragStart?(e: DragEvent): void;\n onDrop?(e: DragEvent): void;\n\n // Clipboard\n onCopy?(e: ClipboardEvent): void;\n onCut?(e: ClipboardEvent): void;\n onPaste?(e: ClipboardEvent): void;\n\n // UI / Window\n onScroll?(e: Event): void;\n onResize?(e: UIEvent): void;\n onWheel?(e: WheelEvent): void;\n\n // Media\n onLoad?(e: Event): void;\n onError?(e: ErrorEvent): void;\n onPlay?(e: Event): void;\n onPause?(e: Event): void;\n onEnded?(e: Event): void;\n\n // Details/Dialog\n onToggle?(e: Event): void;\n}\n\n// --- 3. Internal Logic ---\n\nfunction isEventInterceptorMethod(method: string): boolean {\n return /^on[A-Z]/.test(method);\n}\n\nfunction getEventName(methodName: string): string {\n // Simple rule: strip 'on' and lowercase the rest\n // onClick -> click\n // onDblClick -> dblclick\n // onMouseDown -> mousedown\n return methodName.substring(2).toLowerCase();\n}\n\n/**\n * Mixin that adds auto event wiring to a base class.\n */\nfunction withAutoEvents<T extends Constructor<HTMLElement>>(Base: T) {\n // @ts-expect-error - Dynamic class extension\n class AutoElement extends Base implements EventInterceptors {\n private _cleanupFns: Array<() => void> = [];\n\n connectedCallback() {\n // @ts-expect-error - super access in mixin\n if (super.connectedCallback) super.connectedCallback();\n this._wireAndLockEvents();\n }\n\n disconnectedCallback() {\n // @ts-expect-error - super access in mixin\n if (super.disconnectedCallback) super.disconnectedCallback();\n this._unwireEvents();\n }\n\n private _wireAndLockEvents() {\n // Scan the prototype chain for methods starting with 'on'\n const hostMethods = new Set<string>();\n let curr = this;\n\n // Walk up prototype chain to find methods\n while (curr && curr !== Object.prototype) {\n Object.getOwnPropertyNames(curr).forEach((prop) => {\n if (isEventInterceptorMethod(prop)) hostMethods.add(prop);\n });\n curr = Object.getPrototypeOf(curr);\n }\n\n for (const method of hostMethods) {\n const eventName = getEventName(method);\n const originalHostFn = this[method as keyof typeof this];\n\n if (typeof originalHostFn !== \"function\") continue;\n\n // 1. Wire the event\n const handler = originalHostFn.bind(this) as EventListener;\n this.addEventListener(eventName, handler);\n this._cleanupFns.push(() => {\n this.removeEventListener(eventName, handler);\n });\n\n // 2. Lock the property to prevent runtime reassignment\n Object.defineProperty(this, method, {\n value: originalHostFn,\n writable: false,\n configurable: true,\n });\n }\n }\n\n private _unwireEvents() {\n for (const cleanup of this._cleanupFns) {\n cleanup();\n }\n this._cleanupFns = [];\n }\n }\n\n return AutoElement as unknown as T & Constructor<EventInterceptors>;\n}\n\n// --- 4. Public API ---\n\n/**\n * Defines a type-safe Web Component with auto event handling.\n *\n * @param tagName - The custom element tag name (e.g., 'my-button')\n * @param extendsTag - The HTML tag to extend (e.g., 'button', 'div')\n * @param factory - A function that receives the Base class and returns your implementation\n * @param options - Optional configuration (observedAttributes, etc.)\n */\nexport function defineAutoWebComponent<\n T extends TagName,\n ObservedAttributes extends string[] = string[],\n>(\n tagName: string,\n extendsTag: T,\n factory: (\n base: Constructor<TagClass<T> & EventInterceptors>,\n ) => Constructor<TagClass<T>> & { observedAttributes?: ObservedAttributes },\n options: AutoWebComponentOptions<ObservedAttributes> = {},\n): void {\n const BaseClass = document.createElement(extendsTag)\n .constructor as Constructor<TagClass<T>>;\n const ImplementationClass = factory(withAutoEvents(BaseClass));\n\n if (customElements.get(tagName)) {\n console.warn(`Custom element ${tagName} is already registered`);\n return;\n }\n\n if (options.observedAttributes) {\n ImplementationClass.observedAttributes = options.observedAttributes;\n }\n\n customElements.define(tagName, ImplementationClass, {\n extends: extendsTag,\n });\n}\n"],"mappings":";AA+FA,SAAS,yBAAyB,QAAyB;AACzD,SAAO,WAAW,KAAK,MAAM;AAC/B;AAEA,SAAS,aAAa,YAA4B;AAKhD,SAAO,WAAW,UAAU,CAAC,EAAE,YAAY;AAC7C;AAKA,SAAS,eAAmD,MAAS;AAAA,EAEnE,MAAM,oBAAoB,KAAkC;AAAA,IAClD,cAAiC,CAAC;AAAA,IAE1C,oBAAoB;AAElB,UAAI,MAAM,kBAAmB,OAAM,kBAAkB;AACrD,WAAK,mBAAmB;AAAA,IAC1B;AAAA,IAEA,uBAAuB;AAErB,UAAI,MAAM,qBAAsB,OAAM,qBAAqB;AAC3D,WAAK,cAAc;AAAA,IACrB;AAAA,IAEQ,qBAAqB;AAE3B,YAAM,cAAc,oBAAI,IAAY;AACpC,UAAI,OAAO;AAGX,aAAO,QAAQ,SAAS,OAAO,WAAW;AACxC,eAAO,oBAAoB,IAAI,EAAE,QAAQ,CAAC,SAAS;AACjD,cAAI,yBAAyB,IAAI,EAAG,aAAY,IAAI,IAAI;AAAA,QAC1D,CAAC;AACD,eAAO,OAAO,eAAe,IAAI;AAAA,MACnC;AAEA,iBAAW,UAAU,aAAa;AAChC,cAAM,YAAY,aAAa,MAAM;AACrC,cAAM,iBAAiB,KAAK,MAA2B;AAEvD,YAAI,OAAO,mBAAmB,WAAY;AAG1C,cAAM,UAAU,eAAe,KAAK,IAAI;AACxC,aAAK,iBAAiB,WAAW,OAAO;AACxC,aAAK,YAAY,KAAK,MAAM;AAC1B,eAAK,oBAAoB,WAAW,OAAO;AAAA,QAC7C,CAAC;AAGD,eAAO,eAAe,MAAM,QAAQ;AAAA,UAClC,OAAO;AAAA,UACP,UAAU;AAAA,UACV,cAAc;AAAA,QAChB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IAEQ,gBAAgB;AACtB,iBAAW,WAAW,KAAK,aAAa;AACtC,gBAAQ;AAAA,MACV;AACA,WAAK,cAAc,CAAC;AAAA,IACtB;AAAA,EACF;AAEA,SAAO;AACT;AAYO,SAAS,uBAId,SACA,YACA,SAGA,UAAuD,CAAC,GAClD;AACN,QAAM,YAAY,SAAS,cAAc,UAAU,EAChD;AACH,QAAM,sBAAsB,QAAQ,eAAe,SAAS,CAAC;AAE7D,MAAI,eAAe,IAAI,OAAO,GAAG;AAC/B,YAAQ,KAAK,kBAAkB,OAAO,wBAAwB;AAC9D;AAAA,EACF;AAEA,MAAI,QAAQ,oBAAoB;AAC9B,wBAAoB,qBAAqB,QAAQ;AAAA,EACnD;AAEA,iBAAe,OAAO,SAAS,qBAAqB;AAAA,IAClD,SAAS;AAAA,EACX,CAAC;AACH;","names":[]}
@@ -0,0 +1,6 @@
1
+ "use strict";var v=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var c=Object.getOwnPropertyNames;var E=Object.prototype.hasOwnProperty;var l=(e,t)=>{for(var r in t)v(e,r,{get:t[r],enumerable:!0})},b=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of c(t))!E.call(e,n)&&n!==r&&v(e,n,{get:()=>t[n],enumerable:!(o=u(t,n))||o.enumerable});return e};var p=e=>b(v({},"__esModule",{value:!0}),e);var M={};l(M,{defineAutoWebComponent:()=>f});module.exports=p(M);function g(e){return/^on[A-Z]/.test(e)}function C(e){return e.substring(2).toLowerCase()}function m(e){class t extends e{_cleanupFns=[];connectedCallback(){super.connectedCallback&&super.connectedCallback(),this._wireAndLockEvents()}disconnectedCallback(){super.disconnectedCallback&&super.disconnectedCallback(),this._unwireEvents()}_wireAndLockEvents(){let o=new Set,n=this;for(;n&&n!==Object.prototype;)Object.getOwnPropertyNames(n).forEach(s=>{g(s)&&o.add(s)}),n=Object.getPrototypeOf(n);for(let s of o){let a=C(s),i=this[s];if(typeof i!="function")continue;let d=i.bind(this);this.addEventListener(a,d),this._cleanupFns.push(()=>{this.removeEventListener(a,d)}),Object.defineProperty(this,s,{value:i,writable:!1,configurable:!0})}}_unwireEvents(){for(let o of this._cleanupFns)o();this._cleanupFns=[]}}return t}function f(e,t,r,o={}){let n=document.createElement(t).constructor,s=r(m(n));if(customElements.get(e)){console.warn(`Custom element ${e} is already registered`);return}o.observedAttributes&&(s.observedAttributes=o.observedAttributes),customElements.define(e,s,{extends:t})}0&&(module.exports={defineAutoWebComponent});
2
+ /**
3
+ * @license MIT
4
+ * Auto Web Component - Type-safe Web Components with automatic event wiring.
5
+ */
6
+ //# sourceMappingURL=index.min.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @license MIT\n * Auto Web Component - Type-safe Web Components with automatic event wiring.\n */\n\n// --- 1. Standard DOM Type Helpers ---\n\n/**\n * Represents any valid HTML tag name (e.g., 'div', 'button', 'input').\n */\nexport type TagName = keyof HTMLElementTagNameMap;\n\n/**\n * Maps a tag name to its specific HTMLElement class.\n * e.g., TagClass<'button'> resolves to HTMLButtonElement.\n */\nexport type TagClass<T extends TagName> = HTMLElementTagNameMap[T];\n\nexport type Constructor<T = {}> = new (...args: any[]) => T;\n\nexport interface AutoWebComponentOptions<\n ObservedAttributes extends string[] = string[],\n> {\n observedAttributes?: ObservedAttributes;\n}\n\n// --- 2. Event System Configuration ---\n\n/**\n * Interface defining all supported event interceptors.\n * Implement these methods in your class to automatically handle events.\n *\n * Naming Convention: on{EventName} -> eventname\n * Example: onClick -> click, onMouseDown -> mousedown, onDblClick -> dblclick\n */\nexport interface EventInterceptors {\n // Mouse\n onClick?(e: MouseEvent): void;\n onDblClick?(e: MouseEvent): void; // Maps to 'dblclick'\n onMouseDown?(e: MouseEvent): void;\n onMouseUp?(e: MouseEvent): void;\n onMouseEnter?(e: MouseEvent): void;\n onMouseLeave?(e: MouseEvent): void;\n onMouseMove?(e: MouseEvent): void;\n onMouseOver?(e: MouseEvent): void;\n onMouseOut?(e: MouseEvent): void;\n onContextMenu?(e: MouseEvent): void;\n\n // Keyboard\n onKeyDown?(e: KeyboardEvent): void;\n onKeyUp?(e: KeyboardEvent): void;\n onKeyPress?(e: KeyboardEvent): void;\n\n // Form / Input\n onInput?(e: Event): void;\n onChange?(e: Event): void;\n onSubmit?(e: SubmitEvent): void;\n onFocus?(e: FocusEvent): void;\n onBlur?(e: FocusEvent): void;\n onReset?(e: Event): void;\n onInvalid?(e: Event): void;\n onSelect?(e: Event): void;\n\n // Drag & Drop\n onDrag?(e: DragEvent): void;\n onDragEnd?(e: DragEvent): void;\n onDragEnter?(e: DragEvent): void;\n onDragLeave?(e: DragEvent): void;\n onDragOver?(e: DragEvent): void;\n onDragStart?(e: DragEvent): void;\n onDrop?(e: DragEvent): void;\n\n // Clipboard\n onCopy?(e: ClipboardEvent): void;\n onCut?(e: ClipboardEvent): void;\n onPaste?(e: ClipboardEvent): void;\n\n // UI / Window\n onScroll?(e: Event): void;\n onResize?(e: UIEvent): void;\n onWheel?(e: WheelEvent): void;\n\n // Media\n onLoad?(e: Event): void;\n onError?(e: ErrorEvent): void;\n onPlay?(e: Event): void;\n onPause?(e: Event): void;\n onEnded?(e: Event): void;\n\n // Details/Dialog\n onToggle?(e: Event): void;\n}\n\n// --- 3. Internal Logic ---\n\nfunction isEventInterceptorMethod(method: string): boolean {\n return /^on[A-Z]/.test(method);\n}\n\nfunction getEventName(methodName: string): string {\n // Simple rule: strip 'on' and lowercase the rest\n // onClick -> click\n // onDblClick -> dblclick\n // onMouseDown -> mousedown\n return methodName.substring(2).toLowerCase();\n}\n\n/**\n * Mixin that adds auto event wiring to a base class.\n */\nfunction withAutoEvents<T extends Constructor<HTMLElement>>(Base: T) {\n // @ts-expect-error - Dynamic class extension\n class AutoElement extends Base implements EventInterceptors {\n private _cleanupFns: Array<() => void> = [];\n\n connectedCallback() {\n // @ts-expect-error - super access in mixin\n if (super.connectedCallback) super.connectedCallback();\n this._wireAndLockEvents();\n }\n\n disconnectedCallback() {\n // @ts-expect-error - super access in mixin\n if (super.disconnectedCallback) super.disconnectedCallback();\n this._unwireEvents();\n }\n\n private _wireAndLockEvents() {\n // Scan the prototype chain for methods starting with 'on'\n const hostMethods = new Set<string>();\n let curr = this;\n\n // Walk up prototype chain to find methods\n while (curr && curr !== Object.prototype) {\n Object.getOwnPropertyNames(curr).forEach((prop) => {\n if (isEventInterceptorMethod(prop)) hostMethods.add(prop);\n });\n curr = Object.getPrototypeOf(curr);\n }\n\n for (const method of hostMethods) {\n const eventName = getEventName(method);\n const originalHostFn = this[method as keyof typeof this];\n\n if (typeof originalHostFn !== \"function\") continue;\n\n // 1. Wire the event\n const handler = originalHostFn.bind(this) as EventListener;\n this.addEventListener(eventName, handler);\n this._cleanupFns.push(() => {\n this.removeEventListener(eventName, handler);\n });\n\n // 2. Lock the property to prevent runtime reassignment\n Object.defineProperty(this, method, {\n value: originalHostFn,\n writable: false,\n configurable: true,\n });\n }\n }\n\n private _unwireEvents() {\n for (const cleanup of this._cleanupFns) {\n cleanup();\n }\n this._cleanupFns = [];\n }\n }\n\n return AutoElement as unknown as T & Constructor<EventInterceptors>;\n}\n\n// --- 4. Public API ---\n\n/**\n * Defines a type-safe Web Component with auto event handling.\n *\n * @param tagName - The custom element tag name (e.g., 'my-button')\n * @param extendsTag - The HTML tag to extend (e.g., 'button', 'div')\n * @param factory - A function that receives the Base class and returns your implementation\n * @param options - Optional configuration (observedAttributes, etc.)\n */\nexport function defineAutoWebComponent<\n T extends TagName,\n ObservedAttributes extends string[] = string[],\n>(\n tagName: string,\n extendsTag: T,\n factory: (\n base: Constructor<TagClass<T> & EventInterceptors>,\n ) => Constructor<TagClass<T>> & { observedAttributes?: ObservedAttributes },\n options: AutoWebComponentOptions<ObservedAttributes> = {},\n): void {\n const BaseClass = document.createElement(extendsTag)\n .constructor as Constructor<TagClass<T>>;\n const ImplementationClass = factory(withAutoEvents(BaseClass));\n\n if (customElements.get(tagName)) {\n console.warn(`Custom element ${tagName} is already registered`);\n return;\n }\n\n if (options.observedAttributes) {\n ImplementationClass.observedAttributes = options.observedAttributes;\n }\n\n customElements.define(tagName, ImplementationClass, {\n extends: extendsTag,\n });\n}\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,4BAAAE,IAAA,eAAAC,EAAAH,GA+FA,SAASI,EAAyBC,EAAyB,CACzD,MAAO,WAAW,KAAKA,CAAM,CAC/B,CAEA,SAASC,EAAaC,EAA4B,CAKhD,OAAOA,EAAW,UAAU,CAAC,EAAE,YAAY,CAC7C,CAKA,SAASC,EAAmDC,EAAS,CAEnE,MAAMC,UAAoBD,CAAkC,CAClD,YAAiC,CAAC,EAE1C,mBAAoB,CAEd,MAAM,mBAAmB,MAAM,kBAAkB,EACrD,KAAK,mBAAmB,CAC1B,CAEA,sBAAuB,CAEjB,MAAM,sBAAsB,MAAM,qBAAqB,EAC3D,KAAK,cAAc,CACrB,CAEQ,oBAAqB,CAE3B,IAAME,EAAc,IAAI,IACpBC,EAAO,KAGX,KAAOA,GAAQA,IAAS,OAAO,WAC7B,OAAO,oBAAoBA,CAAI,EAAE,QAASC,GAAS,CAC7CT,EAAyBS,CAAI,GAAGF,EAAY,IAAIE,CAAI,CAC1D,CAAC,EACDD,EAAO,OAAO,eAAeA,CAAI,EAGnC,QAAWP,KAAUM,EAAa,CAChC,IAAMG,EAAYR,EAAaD,CAAM,EAC/BU,EAAiB,KAAKV,CAA2B,EAEvD,GAAI,OAAOU,GAAmB,WAAY,SAG1C,IAAMC,EAAUD,EAAe,KAAK,IAAI,EACxC,KAAK,iBAAiBD,EAAWE,CAAO,EACxC,KAAK,YAAY,KAAK,IAAM,CAC1B,KAAK,oBAAoBF,EAAWE,CAAO,CAC7C,CAAC,EAGD,OAAO,eAAe,KAAMX,EAAQ,CAClC,MAAOU,EACP,SAAU,GACV,aAAc,EAChB,CAAC,CACH,CACF,CAEQ,eAAgB,CACtB,QAAWE,KAAW,KAAK,YACzBA,EAAQ,EAEV,KAAK,YAAc,CAAC,CACtB,CACF,CAEA,OAAOP,CACT,CAYO,SAASR,EAIdgB,EACAC,EACAC,EAGAC,EAAuD,CAAC,EAClD,CACN,IAAMC,EAAY,SAAS,cAAcH,CAAU,EAChD,YACGI,EAAsBH,EAAQZ,EAAec,CAAS,CAAC,EAE7D,GAAI,eAAe,IAAIJ,CAAO,EAAG,CAC/B,QAAQ,KAAK,kBAAkBA,CAAO,wBAAwB,EAC9D,MACF,CAEIG,EAAQ,qBACVE,EAAoB,mBAAqBF,EAAQ,oBAGnD,eAAe,OAAOH,EAASK,EAAqB,CAClD,QAASJ,CACX,CAAC,CACH","names":["index_exports","__export","defineAutoWebComponent","__toCommonJS","isEventInterceptorMethod","method","getEventName","methodName","withAutoEvents","Base","AutoElement","hostMethods","curr","prop","eventName","originalHostFn","handler","cleanup","tagName","extendsTag","factory","options","BaseClass","ImplementationClass"]}
@@ -0,0 +1,6 @@
1
+ function d(e){return/^on[A-Z]/.test(e)}function u(e){return e.substring(2).toLowerCase()}function c(e){class s extends e{_cleanupFns=[];connectedCallback(){super.connectedCallback&&super.connectedCallback(),this._wireAndLockEvents()}disconnectedCallback(){super.disconnectedCallback&&super.disconnectedCallback(),this._unwireEvents()}_wireAndLockEvents(){let n=new Set,o=this;for(;o&&o!==Object.prototype;)Object.getOwnPropertyNames(o).forEach(t=>{d(t)&&n.add(t)}),o=Object.getPrototypeOf(o);for(let t of n){let v=u(t),r=this[t];if(typeof r!="function")continue;let a=r.bind(this);this.addEventListener(v,a),this._cleanupFns.push(()=>{this.removeEventListener(v,a)}),Object.defineProperty(this,t,{value:r,writable:!1,configurable:!0})}}_unwireEvents(){for(let n of this._cleanupFns)n();this._cleanupFns=[]}}return s}function E(e,s,i,n={}){let o=document.createElement(s).constructor,t=i(c(o));if(customElements.get(e)){console.warn(`Custom element ${e} is already registered`);return}n.observedAttributes&&(t.observedAttributes=n.observedAttributes),customElements.define(e,t,{extends:s})}export{E as defineAutoWebComponent};
2
+ /**
3
+ * @license MIT
4
+ * Auto Web Component - Type-safe Web Components with automatic event wiring.
5
+ */
6
+ //# sourceMappingURL=index.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @license MIT\n * Auto Web Component - Type-safe Web Components with automatic event wiring.\n */\n\n// --- 1. Standard DOM Type Helpers ---\n\n/**\n * Represents any valid HTML tag name (e.g., 'div', 'button', 'input').\n */\nexport type TagName = keyof HTMLElementTagNameMap;\n\n/**\n * Maps a tag name to its specific HTMLElement class.\n * e.g., TagClass<'button'> resolves to HTMLButtonElement.\n */\nexport type TagClass<T extends TagName> = HTMLElementTagNameMap[T];\n\nexport type Constructor<T = {}> = new (...args: any[]) => T;\n\nexport interface AutoWebComponentOptions<\n ObservedAttributes extends string[] = string[],\n> {\n observedAttributes?: ObservedAttributes;\n}\n\n// --- 2. Event System Configuration ---\n\n/**\n * Interface defining all supported event interceptors.\n * Implement these methods in your class to automatically handle events.\n *\n * Naming Convention: on{EventName} -> eventname\n * Example: onClick -> click, onMouseDown -> mousedown, onDblClick -> dblclick\n */\nexport interface EventInterceptors {\n // Mouse\n onClick?(e: MouseEvent): void;\n onDblClick?(e: MouseEvent): void; // Maps to 'dblclick'\n onMouseDown?(e: MouseEvent): void;\n onMouseUp?(e: MouseEvent): void;\n onMouseEnter?(e: MouseEvent): void;\n onMouseLeave?(e: MouseEvent): void;\n onMouseMove?(e: MouseEvent): void;\n onMouseOver?(e: MouseEvent): void;\n onMouseOut?(e: MouseEvent): void;\n onContextMenu?(e: MouseEvent): void;\n\n // Keyboard\n onKeyDown?(e: KeyboardEvent): void;\n onKeyUp?(e: KeyboardEvent): void;\n onKeyPress?(e: KeyboardEvent): void;\n\n // Form / Input\n onInput?(e: Event): void;\n onChange?(e: Event): void;\n onSubmit?(e: SubmitEvent): void;\n onFocus?(e: FocusEvent): void;\n onBlur?(e: FocusEvent): void;\n onReset?(e: Event): void;\n onInvalid?(e: Event): void;\n onSelect?(e: Event): void;\n\n // Drag & Drop\n onDrag?(e: DragEvent): void;\n onDragEnd?(e: DragEvent): void;\n onDragEnter?(e: DragEvent): void;\n onDragLeave?(e: DragEvent): void;\n onDragOver?(e: DragEvent): void;\n onDragStart?(e: DragEvent): void;\n onDrop?(e: DragEvent): void;\n\n // Clipboard\n onCopy?(e: ClipboardEvent): void;\n onCut?(e: ClipboardEvent): void;\n onPaste?(e: ClipboardEvent): void;\n\n // UI / Window\n onScroll?(e: Event): void;\n onResize?(e: UIEvent): void;\n onWheel?(e: WheelEvent): void;\n\n // Media\n onLoad?(e: Event): void;\n onError?(e: ErrorEvent): void;\n onPlay?(e: Event): void;\n onPause?(e: Event): void;\n onEnded?(e: Event): void;\n\n // Details/Dialog\n onToggle?(e: Event): void;\n}\n\n// --- 3. Internal Logic ---\n\nfunction isEventInterceptorMethod(method: string): boolean {\n return /^on[A-Z]/.test(method);\n}\n\nfunction getEventName(methodName: string): string {\n // Simple rule: strip 'on' and lowercase the rest\n // onClick -> click\n // onDblClick -> dblclick\n // onMouseDown -> mousedown\n return methodName.substring(2).toLowerCase();\n}\n\n/**\n * Mixin that adds auto event wiring to a base class.\n */\nfunction withAutoEvents<T extends Constructor<HTMLElement>>(Base: T) {\n // @ts-expect-error - Dynamic class extension\n class AutoElement extends Base implements EventInterceptors {\n private _cleanupFns: Array<() => void> = [];\n\n connectedCallback() {\n // @ts-expect-error - super access in mixin\n if (super.connectedCallback) super.connectedCallback();\n this._wireAndLockEvents();\n }\n\n disconnectedCallback() {\n // @ts-expect-error - super access in mixin\n if (super.disconnectedCallback) super.disconnectedCallback();\n this._unwireEvents();\n }\n\n private _wireAndLockEvents() {\n // Scan the prototype chain for methods starting with 'on'\n const hostMethods = new Set<string>();\n let curr = this;\n\n // Walk up prototype chain to find methods\n while (curr && curr !== Object.prototype) {\n Object.getOwnPropertyNames(curr).forEach((prop) => {\n if (isEventInterceptorMethod(prop)) hostMethods.add(prop);\n });\n curr = Object.getPrototypeOf(curr);\n }\n\n for (const method of hostMethods) {\n const eventName = getEventName(method);\n const originalHostFn = this[method as keyof typeof this];\n\n if (typeof originalHostFn !== \"function\") continue;\n\n // 1. Wire the event\n const handler = originalHostFn.bind(this) as EventListener;\n this.addEventListener(eventName, handler);\n this._cleanupFns.push(() => {\n this.removeEventListener(eventName, handler);\n });\n\n // 2. Lock the property to prevent runtime reassignment\n Object.defineProperty(this, method, {\n value: originalHostFn,\n writable: false,\n configurable: true,\n });\n }\n }\n\n private _unwireEvents() {\n for (const cleanup of this._cleanupFns) {\n cleanup();\n }\n this._cleanupFns = [];\n }\n }\n\n return AutoElement as unknown as T & Constructor<EventInterceptors>;\n}\n\n// --- 4. Public API ---\n\n/**\n * Defines a type-safe Web Component with auto event handling.\n *\n * @param tagName - The custom element tag name (e.g., 'my-button')\n * @param extendsTag - The HTML tag to extend (e.g., 'button', 'div')\n * @param factory - A function that receives the Base class and returns your implementation\n * @param options - Optional configuration (observedAttributes, etc.)\n */\nexport function defineAutoWebComponent<\n T extends TagName,\n ObservedAttributes extends string[] = string[],\n>(\n tagName: string,\n extendsTag: T,\n factory: (\n base: Constructor<TagClass<T> & EventInterceptors>,\n ) => Constructor<TagClass<T>> & { observedAttributes?: ObservedAttributes },\n options: AutoWebComponentOptions<ObservedAttributes> = {},\n): void {\n const BaseClass = document.createElement(extendsTag)\n .constructor as Constructor<TagClass<T>>;\n const ImplementationClass = factory(withAutoEvents(BaseClass));\n\n if (customElements.get(tagName)) {\n console.warn(`Custom element ${tagName} is already registered`);\n return;\n }\n\n if (options.observedAttributes) {\n ImplementationClass.observedAttributes = options.observedAttributes;\n }\n\n customElements.define(tagName, ImplementationClass, {\n extends: extendsTag,\n });\n}\n"],"mappings":"AA+FA,SAASA,EAAyBC,EAAyB,CACzD,MAAO,WAAW,KAAKA,CAAM,CAC/B,CAEA,SAASC,EAAaC,EAA4B,CAKhD,OAAOA,EAAW,UAAU,CAAC,EAAE,YAAY,CAC7C,CAKA,SAASC,EAAmDC,EAAS,CAEnE,MAAMC,UAAoBD,CAAkC,CAClD,YAAiC,CAAC,EAE1C,mBAAoB,CAEd,MAAM,mBAAmB,MAAM,kBAAkB,EACrD,KAAK,mBAAmB,CAC1B,CAEA,sBAAuB,CAEjB,MAAM,sBAAsB,MAAM,qBAAqB,EAC3D,KAAK,cAAc,CACrB,CAEQ,oBAAqB,CAE3B,IAAME,EAAc,IAAI,IACpBC,EAAO,KAGX,KAAOA,GAAQA,IAAS,OAAO,WAC7B,OAAO,oBAAoBA,CAAI,EAAE,QAASC,GAAS,CAC7CT,EAAyBS,CAAI,GAAGF,EAAY,IAAIE,CAAI,CAC1D,CAAC,EACDD,EAAO,OAAO,eAAeA,CAAI,EAGnC,QAAWP,KAAUM,EAAa,CAChC,IAAMG,EAAYR,EAAaD,CAAM,EAC/BU,EAAiB,KAAKV,CAA2B,EAEvD,GAAI,OAAOU,GAAmB,WAAY,SAG1C,IAAMC,EAAUD,EAAe,KAAK,IAAI,EACxC,KAAK,iBAAiBD,EAAWE,CAAO,EACxC,KAAK,YAAY,KAAK,IAAM,CAC1B,KAAK,oBAAoBF,EAAWE,CAAO,CAC7C,CAAC,EAGD,OAAO,eAAe,KAAMX,EAAQ,CAClC,MAAOU,EACP,SAAU,GACV,aAAc,EAChB,CAAC,CACH,CACF,CAEQ,eAAgB,CACtB,QAAWE,KAAW,KAAK,YACzBA,EAAQ,EAEV,KAAK,YAAc,CAAC,CACtB,CACF,CAEA,OAAOP,CACT,CAYO,SAASQ,EAIdC,EACAC,EACAC,EAGAC,EAAuD,CAAC,EAClD,CACN,IAAMC,EAAY,SAAS,cAAcH,CAAU,EAChD,YACGI,EAAsBH,EAAQb,EAAee,CAAS,CAAC,EAE7D,GAAI,eAAe,IAAIJ,CAAO,EAAG,CAC/B,QAAQ,KAAK,kBAAkBA,CAAO,wBAAwB,EAC9D,MACF,CAEIG,EAAQ,qBACVE,EAAoB,mBAAqBF,EAAQ,oBAGnD,eAAe,OAAOH,EAASK,EAAqB,CAClD,QAASJ,CACX,CAAC,CACH","names":["isEventInterceptorMethod","method","getEventName","methodName","withAutoEvents","Base","AutoElement","hostMethods","curr","prop","eventName","originalHostFn","handler","cleanup","defineAutoWebComponent","tagName","extendsTag","factory","options","BaseClass","ImplementationClass"]}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "auto-wc",
3
+ "version": "0.1.0",
4
+ "description": "Type-safe Web Components with automatic event wiring",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
22
+ "test": "vitest run",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "keywords": [
26
+ "web-components",
27
+ "typescript",
28
+ "strict-events",
29
+ "custom-elements"
30
+ ],
31
+ "author": "Sagi",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/your-username/auto-wc.git"
36
+ },
37
+ "devDependencies": {
38
+ "jsdom": "^28.1.0",
39
+ "tsup": "^8.0.0",
40
+ "typescript": "^5.3.0",
41
+ "vitest": "^1.0.0"
42
+ },
43
+ "peerDependencies": {
44
+ "typescript": ">=5.0.0"
45
+ }
46
+ }
package/src/index.ts ADDED
@@ -0,0 +1,211 @@
1
+ /**
2
+ * @license MIT
3
+ * Auto Web Component - Type-safe Web Components with automatic event wiring.
4
+ */
5
+
6
+ // --- 1. Standard DOM Type Helpers ---
7
+
8
+ /**
9
+ * Represents any valid HTML tag name (e.g., 'div', 'button', 'input').
10
+ */
11
+ export type TagName = keyof HTMLElementTagNameMap;
12
+
13
+ /**
14
+ * Maps a tag name to its specific HTMLElement class.
15
+ * e.g., TagClass<'button'> resolves to HTMLButtonElement.
16
+ */
17
+ export type TagClass<T extends TagName> = HTMLElementTagNameMap[T];
18
+
19
+ export type Constructor<T = {}> = new (...args: any[]) => T;
20
+
21
+ export interface AutoWebComponentOptions<
22
+ ObservedAttributes extends string[] = string[],
23
+ > {
24
+ observedAttributes?: ObservedAttributes;
25
+ }
26
+
27
+ // --- 2. Event System Configuration ---
28
+
29
+ /**
30
+ * Interface defining all supported event interceptors.
31
+ * Implement these methods in your class to automatically handle events.
32
+ *
33
+ * Naming Convention: on{EventName} -> eventname
34
+ * Example: onClick -> click, onMouseDown -> mousedown, onDblClick -> dblclick
35
+ */
36
+ export interface EventInterceptors {
37
+ // Mouse
38
+ onClick?(e: MouseEvent): void;
39
+ onDblClick?(e: MouseEvent): void; // Maps to 'dblclick'
40
+ onMouseDown?(e: MouseEvent): void;
41
+ onMouseUp?(e: MouseEvent): void;
42
+ onMouseEnter?(e: MouseEvent): void;
43
+ onMouseLeave?(e: MouseEvent): void;
44
+ onMouseMove?(e: MouseEvent): void;
45
+ onMouseOver?(e: MouseEvent): void;
46
+ onMouseOut?(e: MouseEvent): void;
47
+ onContextMenu?(e: MouseEvent): void;
48
+
49
+ // Keyboard
50
+ onKeyDown?(e: KeyboardEvent): void;
51
+ onKeyUp?(e: KeyboardEvent): void;
52
+ onKeyPress?(e: KeyboardEvent): void;
53
+
54
+ // Form / Input
55
+ onInput?(e: Event): void;
56
+ onChange?(e: Event): void;
57
+ onSubmit?(e: SubmitEvent): void;
58
+ onFocus?(e: FocusEvent): void;
59
+ onBlur?(e: FocusEvent): void;
60
+ onReset?(e: Event): void;
61
+ onInvalid?(e: Event): void;
62
+ onSelect?(e: Event): void;
63
+
64
+ // Drag & Drop
65
+ onDrag?(e: DragEvent): void;
66
+ onDragEnd?(e: DragEvent): void;
67
+ onDragEnter?(e: DragEvent): void;
68
+ onDragLeave?(e: DragEvent): void;
69
+ onDragOver?(e: DragEvent): void;
70
+ onDragStart?(e: DragEvent): void;
71
+ onDrop?(e: DragEvent): void;
72
+
73
+ // Clipboard
74
+ onCopy?(e: ClipboardEvent): void;
75
+ onCut?(e: ClipboardEvent): void;
76
+ onPaste?(e: ClipboardEvent): void;
77
+
78
+ // UI / Window
79
+ onScroll?(e: Event): void;
80
+ onResize?(e: UIEvent): void;
81
+ onWheel?(e: WheelEvent): void;
82
+
83
+ // Media
84
+ onLoad?(e: Event): void;
85
+ onError?(e: ErrorEvent): void;
86
+ onPlay?(e: Event): void;
87
+ onPause?(e: Event): void;
88
+ onEnded?(e: Event): void;
89
+
90
+ // Details/Dialog
91
+ onToggle?(e: Event): void;
92
+ }
93
+
94
+ // --- 3. Internal Logic ---
95
+
96
+ function isEventInterceptorMethod(method: string): boolean {
97
+ return /^on[A-Z]/.test(method);
98
+ }
99
+
100
+ function getEventName(methodName: string): string {
101
+ // Simple rule: strip 'on' and lowercase the rest
102
+ // onClick -> click
103
+ // onDblClick -> dblclick
104
+ // onMouseDown -> mousedown
105
+ return methodName.substring(2).toLowerCase();
106
+ }
107
+
108
+ /**
109
+ * Mixin that adds auto event wiring to a base class.
110
+ */
111
+ function withAutoEvents<T extends Constructor<HTMLElement>>(Base: T) {
112
+ // @ts-expect-error - Dynamic class extension
113
+ class AutoElement extends Base implements EventInterceptors {
114
+ private _cleanupFns: Array<() => void> = [];
115
+
116
+ connectedCallback() {
117
+ // @ts-expect-error - super access in mixin
118
+ if (super.connectedCallback) super.connectedCallback();
119
+ this._wireAndLockEvents();
120
+ }
121
+
122
+ disconnectedCallback() {
123
+ // @ts-expect-error - super access in mixin
124
+ if (super.disconnectedCallback) super.disconnectedCallback();
125
+ this._unwireEvents();
126
+ }
127
+
128
+ private _wireAndLockEvents() {
129
+ // Scan the prototype chain for methods starting with 'on'
130
+ const hostMethods = new Set<string>();
131
+ let curr = this;
132
+
133
+ // Walk up prototype chain to find methods
134
+ while (curr && curr !== Object.prototype) {
135
+ Object.getOwnPropertyNames(curr).forEach((prop) => {
136
+ if (isEventInterceptorMethod(prop)) hostMethods.add(prop);
137
+ });
138
+ curr = Object.getPrototypeOf(curr);
139
+ }
140
+
141
+ for (const method of hostMethods) {
142
+ const eventName = getEventName(method);
143
+ const originalHostFn = this[method as keyof typeof this];
144
+
145
+ if (typeof originalHostFn !== "function") continue;
146
+
147
+ // 1. Wire the event
148
+ const handler = originalHostFn.bind(this) as EventListener;
149
+ this.addEventListener(eventName, handler);
150
+ this._cleanupFns.push(() => {
151
+ this.removeEventListener(eventName, handler);
152
+ });
153
+
154
+ // 2. Lock the property to prevent runtime reassignment
155
+ Object.defineProperty(this, method, {
156
+ value: originalHostFn,
157
+ writable: false,
158
+ configurable: true,
159
+ });
160
+ }
161
+ }
162
+
163
+ private _unwireEvents() {
164
+ for (const cleanup of this._cleanupFns) {
165
+ cleanup();
166
+ }
167
+ this._cleanupFns = [];
168
+ }
169
+ }
170
+
171
+ return AutoElement as unknown as T & Constructor<EventInterceptors>;
172
+ }
173
+
174
+ // --- 4. Public API ---
175
+
176
+ /**
177
+ * Defines a type-safe Web Component with auto event handling.
178
+ *
179
+ * @param tagName - The custom element tag name (e.g., 'my-button')
180
+ * @param extendsTag - The HTML tag to extend (e.g., 'button', 'div')
181
+ * @param factory - A function that receives the Base class and returns your implementation
182
+ * @param options - Optional configuration (observedAttributes, etc.)
183
+ */
184
+ export function defineAutoWebComponent<
185
+ T extends TagName,
186
+ ObservedAttributes extends string[] = string[],
187
+ >(
188
+ tagName: string,
189
+ extendsTag: T,
190
+ factory: (
191
+ base: Constructor<TagClass<T> & EventInterceptors>,
192
+ ) => Constructor<TagClass<T>> & { observedAttributes?: ObservedAttributes },
193
+ options: AutoWebComponentOptions<ObservedAttributes> = {},
194
+ ): void {
195
+ const BaseClass = document.createElement(extendsTag)
196
+ .constructor as Constructor<TagClass<T>>;
197
+ const ImplementationClass = factory(withAutoEvents(BaseClass));
198
+
199
+ if (customElements.get(tagName)) {
200
+ console.warn(`Custom element ${tagName} is already registered`);
201
+ return;
202
+ }
203
+
204
+ if (options.observedAttributes) {
205
+ ImplementationClass.observedAttributes = options.observedAttributes;
206
+ }
207
+
208
+ customElements.define(tagName, ImplementationClass, {
209
+ extends: extendsTag,
210
+ });
211
+ }