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 +208 -0
- package/dist/index.cjs +99 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +79 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +74 -0
- package/dist/index.js.map +1 -0
- package/dist/index.min.cjs +6 -0
- package/dist/index.min.cjs.map +1 -0
- package/dist/index.min.js +6 -0
- package/dist/index.min.js.map +1 -0
- package/package.json +46 -0
- package/src/index.ts +211 -0
package/README.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# auto-wc
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
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":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|