@dooboostore/simple-web-component 1.0.15 → 1.0.16

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.
Files changed (53) hide show
  1. package/README.md +183 -92
  2. package/dist/cjs/decorators/addEventListener.js +2 -3
  3. package/dist/cjs/decorators/addEventListener.js.map +2 -2
  4. package/dist/cjs/decorators/elementDefine.js +89 -33
  5. package/dist/cjs/decorators/elementDefine.js.map +2 -2
  6. package/dist/cjs/decorators/emitCustomEvent.js +2 -14
  7. package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
  8. package/dist/cjs/decorators/query.js +25 -36
  9. package/dist/cjs/decorators/query.js.map +2 -2
  10. package/dist/cjs/decorators/queryAll.js +28 -36
  11. package/dist/cjs/decorators/queryAll.js.map +2 -2
  12. package/dist/cjs/types.js +1 -0
  13. package/dist/cjs/types.js.map +2 -2
  14. package/dist/cjs/utils/Utils.js +36 -143
  15. package/dist/cjs/utils/Utils.js.map +2 -2
  16. package/dist/esm/decorators/addEventListener.js +2 -3
  17. package/dist/esm/decorators/addEventListener.js.map +2 -2
  18. package/dist/esm/decorators/elementDefine.js +89 -33
  19. package/dist/esm/decorators/elementDefine.js.map +2 -2
  20. package/dist/esm/decorators/emitCustomEvent.js +2 -14
  21. package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
  22. package/dist/esm/decorators/query.js +25 -36
  23. package/dist/esm/decorators/query.js.map +2 -2
  24. package/dist/esm/decorators/queryAll.js +28 -36
  25. package/dist/esm/decorators/queryAll.js.map +2 -2
  26. package/dist/esm/types.js +1 -0
  27. package/dist/esm/types.js.map +2 -2
  28. package/dist/esm/utils/Utils.js +36 -143
  29. package/dist/esm/utils/Utils.js.map +2 -2
  30. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +183 -266
  31. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +3 -3
  32. package/dist/types/decorators/addEventListener.d.ts +9 -4
  33. package/dist/types/decorators/addEventListener.d.ts.map +1 -1
  34. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  35. package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
  36. package/dist/types/decorators/query.d.ts +6 -5
  37. package/dist/types/decorators/query.d.ts.map +1 -1
  38. package/dist/types/decorators/queryAll.d.ts +6 -5
  39. package/dist/types/decorators/queryAll.d.ts.map +1 -1
  40. package/dist/types/types.d.ts +3 -1
  41. package/dist/types/types.d.ts.map +1 -1
  42. package/dist/types/utils/Utils.d.ts +6 -14
  43. package/dist/types/utils/Utils.d.ts.map +1 -1
  44. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +183 -266
  45. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +3 -3
  46. package/package.json +3 -3
  47. package/src/decorators/addEventListener.ts +10 -7
  48. package/src/decorators/elementDefine.ts +86 -31
  49. package/src/decorators/emitCustomEvent.ts +5 -21
  50. package/src/decorators/query.ts +33 -46
  51. package/src/decorators/queryAll.ts +36 -46
  52. package/src/types.ts +6 -4
  53. package/src/utils/Utils.ts +40 -176
package/README.md CHANGED
@@ -1,111 +1,124 @@
1
1
  # @dooboostore/simple-web-component (SWC)
2
2
 
3
- > **"Pure, Standards-Compliant, and Blazing Fast Web Components."**
3
+ > **"Pure, Standards-Compliant, and Blazing Fast Web Components Framework."**
4
4
 
5
- `simple-web-component` (SWC) is a minimalist yet powerful library designed to build high-performance Web Components. It strips away heavy "magic" features like Proxy-based reactivity and constant `MutationObserver` overhead in favor of a **Pure Architecture** that adheres strictly to browser standards.
5
+ `simple-web-component` (SWC) is a minimalist library designed for building high-performance Web Components and Single Page Applications (SPA). It strips away heavy "magic" features like Proxy-based reactivity and continuous `MutationObserver` scans in favor of a **Pure Architecture** that stays out of your way while offering professional-grade Dependency Injection (DI) and Routing.
6
6
 
7
7
  ---
8
8
 
9
9
  ## 🚀 Why SWC?
10
10
 
11
- - **Zero Magic**: No Proxy traps, no Virtual DOM diffing. Everything is explicit and predictable.
12
- - **Native Inheritance**: Inherit directly from `HTMLElement`, `HTMLDivElement`, or `HTMLTemplateElement`.
13
- - **Extreme Performance**: Designed for high-load applications (1,000+ nodes) with minimal memory footprint.
14
- - **Micro-Frontend Ready**: Independent routing and DI containers per component scope.
15
- - **Standard-First**: Fully compatible with native Custom Elements, Shadow DOM, and Slots.
11
+ - **Zero Magic**: No Proxy traps, no Virtual DOM diffing. Predictive and standard-compliant.
12
+ - **Native Inheritance**: Inherit directly from `HTMLElement`, `HTMLDivElement`, etc.
13
+ - **Extreme Performance**: Ideal for high-load UIs (1,000+ nodes) with minimal memory overhead.
14
+ - **Micro-Frontend Ready**: Independent routing and DI containers for every component scope.
15
+ - **Standard-First**: Works directly with native Custom Elements, Shadow DOM, and Slots.
16
16
 
17
17
  ---
18
18
 
19
- ## 📦 Installation
19
+ ## 📦 Installation & Setup
20
20
 
21
21
  ```bash
22
22
  npm install @dooboostore/simple-web-component reflect-metadata
23
23
  ```
24
24
 
25
- > **Note**: `reflect-metadata` is required for decorator support.
26
-
27
- ---
28
-
29
- ## 🌐 Browser Support & Polyfills
30
-
31
- SWC utilizes **Customized Built-in Elements** (e.g., `<table is="swc-app-table">`) for maximum performance and semantic HTML. Since **Safari** does not yet natively support the `is` attribute, we recommend adding the following polyfill to your HTML `<head>`:
32
-
25
+ ### Browser Support
26
+ For **Safari** support of the `is` attribute, add this to your `<head>`:
33
27
  ```html
34
- <!-- Polyfill for Safari 'is' attribute support -->
35
28
  <script src="https://unpkg.com/@ungap/custom-elements"></script>
36
29
  ```
37
30
 
38
31
  ---
39
32
 
40
- ## 💡 Core Usage
33
+ ## 💡 Core Decorators
41
34
 
42
- ### 1. Basic Component
43
- SWC components are just standard classes with a decorator.
35
+ ### 1. `@elementDefine(config)`
36
+ Registers the class as a custom element.
44
37
 
45
- ```typescript
46
- import { elementDefine, innerHtml } from '@dooboostore/simple-web-component';
38
+ | Option | Type | Description |
39
+ |---|---|---|
40
+ | `name` | `string` | The custom element tag name (e.g., `'my-el'`). |
41
+ | `extends` | `string` | (Optional) The native tag to extend. |
42
+ | `observedAttributes` | `string[]` | (Optional) Extra attributes to observe. |
43
+ | `window` | `Window` | (Optional) Custom window object (useful for SSR). |
47
44
 
48
- @elementDefine('hello-world')
49
- class HelloWorld extends HTMLElement {
50
- @innerHtml
45
+ ### 2. `@innerHtml(options?)`
46
+ Defines the template for the component.
47
+
48
+ ```typescript
49
+ @elementDefine('my-el')
50
+ class MyEl extends HTMLElement {
51
+ @innerHtml({ useShadow: true })
51
52
  render() {
52
- return `<h1>Hello Standard!</h1>`;
53
+ return `<style>:host { color: blue; }</style><div>Hello World</div>`;
53
54
  }
54
55
  }
55
56
  ```
56
57
 
57
- ### 2. Modern Attribute Management
58
- Say goodbye to complex property-attribute syncing. SWC uses a **Method-Based Reflection** approach for maximum control.
58
+ ### 3. Modern Attribute Management
59
+ SWC uses a **Method-Based Reflection** approach. You control exactly how data flows.
59
60
 
60
- ```typescript
61
- import { elementDefine, setAttribute, changedAttribute, query } from '@dooboostore/simple-web-component';
61
+ - **`@setAttribute(name?)`**: Reflects the method's **return value** to the DOM attribute.
62
+ - **`@changedAttribute(name?)`**: A handler triggered when the attribute changes.
62
63
 
64
+ ```typescript
63
65
  @elementDefine('my-counter')
64
66
  class MyCounter extends HTMLElement {
65
67
  private count = 0;
66
-
67
68
  @query('#display') displayEl?: HTMLElement;
68
69
 
69
- // 1. Reflect method return value to DOM attribute 'count'
70
70
  @setAttribute('count')
71
- updateCount(val: number) {
72
- return val;
73
- }
71
+ updateCount(val: number) { return val; } // Sets 'count' attribute in DOM
74
72
 
75
- // 2. React to attribute changes (triggered via setAttribute or externally)
76
73
  @changedAttribute('count')
77
- onCountChange(newVal: string, oldVal: string) {
74
+ onCountChange(newVal: string, oldVal: string, name: string) {
78
75
  this.count = Number(newVal);
79
- // Surgical update: no full re-render!
76
+ // Surgical Update: Only update the specific node
80
77
  if (this.displayEl) this.displayEl.textContent = newVal;
81
78
  }
82
-
83
- @innerHtml({ useShadow: true })
84
- render() {
85
- return `
86
- <div>
87
- Count: <span id="display">0</span>
88
- <button id="add-btn">Add</button>
89
- </div>
90
- `;
91
- }
92
79
  }
93
80
  ```
94
81
 
95
- ### 3. Declarative Event Binding
96
- Bind events to children or the host itself without manual `addEventListener` in the constructor.
82
+ ### 4. Powerful Queries (`@query`, `@queryAll`)
83
+ Inject internal elements directly into properties. Supports the **`:host`** selector.
84
+
85
+ | Root Option | Description |
86
+ |---|---|
87
+ | `auto` (default) | Search in `shadowRoot` if exists, otherwise `this` (Light DOM). |
88
+ | `shadow` | **Strictly** search in `shadowRoot`. Returns `null/[]` if no shadow. |
89
+ | `light` | **Strictly** search in Light DOM (`this`). |
90
+ | `all` | Search in **both** Shadow and Light DOM. |
91
+
92
+ Inject elements directly into properties. Supports `:host` and scoped root control.
97
93
 
98
94
  ```typescript
99
- import { addEventListener, HostSet } from '@dooboostore/simple-web-component';
95
+ @elementDefine('query-demo')
96
+ class QueryDemo extends HTMLElement {
97
+ @query('.title') titleEl!: HTMLElement;
98
+ @queryAll('.items') list!: NodeListOf<HTMLElement>;
99
+
100
+ // Reference the component itself
101
+ @query(':host') self!: HTMLElement;
100
102
 
103
+ // Root options: 'light' | 'shadow' | 'all' | 'auto' (default)
104
+ @query('.btn', { root: 'shadow' }) shadowBtn!: HTMLButtonElement;
105
+ }
106
+ ```
107
+
108
+ ### 5. Smart Event Binding (`@addEventListener`)
109
+ Bind events declaratively with support for **Event Delegation**.
110
+
111
+ ```typescript
101
112
  @elementDefine('event-demo')
102
113
  class EventDemo extends HTMLElement {
103
-
104
- // Injects $host, $appHost, and event object automatically
105
- @addEventListener('click', '#add-btn')
106
- onAddClick(event: MouseEvent, { $host, $appHost }: HostSet) {
107
- console.log('Action in:', $host.tagName);
108
- (this as any).updateCount(this.count + 1);
114
+ // 1. Direct binding (Pure Mode)
115
+ @addEventListener('.static-btn', 'click')
116
+ onStaticClick(event: MouseEvent) { ... }
117
+
118
+ // 2. Event Delegation (Works for dynamic nodes added LATER)
119
+ @addEventListener('.item', 'click', { delegate: true })
120
+ onItemClick(event: MouseEvent, { $host }: HostSet, target: HTMLElement) {
121
+ console.log('Clicked dynamic item:', target.textContent);
109
122
  }
110
123
  }
111
124
  ```
@@ -114,76 +127,154 @@ class EventDemo extends HTMLElement {
114
127
 
115
128
  ## 🌐 Powerful SPA & Routing
116
129
 
117
- SWC excels at building Single Page Applications by providing **Isolated App Scopes**. Each app has its own Dependency Injection (DI) container and Router.
130
+ SWC provides a revolutionary way to build SPAs where **each component scope is an independent application**.
118
131
 
119
- ### 1. The Scoped "Element" Router
120
- Unlike traditional routers that force URL changes, SWC's default `ElementRouter` uses an **in-memory history stack**. This allows you to have multiple independent SPAs on the same page (Micro-Frontends) without URL conflicts.
121
-
122
- ### 2. Bootstrapping an App
123
- Use the `<swc-app>` tag and provide a configuration function.
132
+ ### 1. Isolated App Scopes (Micro-Frontends)
133
+ Use the `<swc-app>` tag to create an isolated universe. Each instance has its own **DI Container** and **Router**. You can run multiple independent SPAs on a single page without URL conflicts.
124
134
 
125
135
  ```html
126
136
  <!-- index.html -->
127
137
  <swc-app swc-get-application-config="myAppConfig(this)"></swc-app>
128
138
 
129
139
  <script>
130
- window.myAppConfig = (host) => ({
131
- rootRouter: RootRouter, // The entry point class
132
- routeType: 'element', // 'element' (in-memory), 'hash', or 'path'
133
- path: '/home' // Initial path
134
- });
140
+ // This function returns the configuration needed to bootstrap the SWC App
141
+ window.myAppConfig = (host) => {
142
+ return {
143
+ rootRouter: RootRouter, // The entry point class (must be @Sim)
144
+ routeType: 'element', // 'element' (in-memory history), 'hash', or 'path'
145
+ path: '/users/list', // Initial route path
146
+ window: window // Optional: custom window object
147
+ };
148
+ };
149
+
150
+ // 4. External Control (Anywhere in your JS)
151
+ function navigateFromOutside() {
152
+ const app = document.querySelector('swc-app');
153
+ // Programmatically trigger routing from outside the component
154
+ app.routing('/users/detail/2');
155
+ }
135
156
  </script>
157
+
158
+ <button onclick="navigateFromOutside()">External Link to User 2</button>
136
159
  ```
137
160
 
138
- ### 3. Dependency Injection (DI)
139
- Integration with `@dooboostore/simple-boot` allows you to inject services into lifecycle methods.
161
+ ### 2. DI-Powered Routing
162
+ Define routes using decorators. Components are automatically instantiated and managed by the DI container.
163
+
164
+ ```typescript
165
+ import { Router } from '@dooboostore/simple-boot/decorators/route/Router';
166
+ import { Sim } from '@dooboostore/simple-boot/decorators/SimDecorator';
167
+
168
+ @Sim
169
+ @Router({
170
+ path: '/users',
171
+ route: {
172
+ '/list': UserList,
173
+ '/detail/:id': UserDetail
174
+ }
175
+ })
176
+ @elementDefine('user-router')
177
+ class UserRouter extends HTMLElement implements RouterAction.CanActivate {
178
+ @innerHtml({ useShadow: true })
179
+ render() {
180
+ return `<header>User System</header><main><slot></slot></main>`;
181
+ }
182
+
183
+ async canActivate(url: RoutingDataSet, data?: any) {
184
+ if (data instanceof Node) this.replaceChildren(data);
185
+ }
186
+ }
187
+
188
+ // Pure Service (Business Logic)
189
+ @Sim
190
+ class UserService {
191
+ async getProfile(id: string) {
192
+ return { id, name: 'Standard User' };
193
+ }
194
+ }
195
+
196
+ // Example Page Components
197
+ @Sim
198
+ @elementDefine('user-list')
199
+ class UserList extends HTMLElement {
200
+ constructor(private router: Router, private userService: UserService) { super(); }
201
+
202
+ @innerHtml
203
+ render() {
204
+ return `
205
+ <ul><li>User A</li><li>User B</li></ul>
206
+ <button swc-on-click="$host.router.go('/users/detail/1')">Go to Detail</button>
207
+ `;
208
+ }
209
+ }
210
+
211
+ @Sim
212
+ @elementDefine('user-detail')
213
+ class UserDetail extends HTMLElement {
214
+ constructor(private router: Router, private userService: UserService) { super(); }
215
+
216
+ @onConnected
217
+ async init() {
218
+ const id = this.router.getIntent()?.params?.id;
219
+ const profile = await this.userService.getProfile(id);
220
+ console.log('Viewing User:', profile.name);
221
+ }
222
+
223
+ @innerHtml
224
+ render() { return `<div>User Detail View <button swc-on-click="$host.router.back()">Back</button></div>`; }
225
+ }
226
+ ```
227
+
228
+ ### 3. Lifecycle @Inject
229
+ Inject services or component context directly into lifecycle methods. This is the ultimate way to handle side effects cleanly.
140
230
 
141
231
  ```typescript
142
232
  import { Inject } from '@dooboostore/simple-boot/decorators/inject/Inject';
143
233
  import { InjectSituationType, onConnected, HostSet } from '@dooboostore/simple-web-component';
234
+ import { Router } from '@dooboostore/core-web/routers/Router';
235
+
236
+ @elementDefine('profile-page')
237
+ class ProfilePage extends HTMLElement {
144
238
 
145
- @elementDefine('test-component')
146
- class TestComponent extends HTMLElement {
147
-
148
239
  @onConnected
149
- init(
240
+ async init(
150
241
  @Inject({ situationType: InjectSituationType.HOST_SET }) hostSet: HostSet,
151
- @Inject({ type: MyApiService }) api: MyApiService
242
+ @Inject({ type: Router }) router: Router,
243
+ @Inject({ type: UserService }) api: UserService
152
244
  ) {
153
- console.log('Context injected:', hostSet.$appHost);
154
- api.fetchData();
245
+ console.log('App scope:', hostSet.$appHost);
246
+ const user = await api.getProfile();
247
+ // Navigate programmatically using injected router
248
+ if (!user) router.go('/login');
155
249
  }
156
250
  }
157
251
  ```
158
252
 
159
253
  ---
160
254
 
161
- ## 🏰 Host Hierarchy ($host)
162
- SWC automatically traverses the DOM to provide meaningful context:
255
+ ## 🏰 Host Hierarchy & Context
256
+ SWC automatically traverses the DOM to provide meaningful context to your scripts and listeners:
163
257
 
164
- - **`$host`**: The current component handling the event.
258
+ - **`$host`**: The current component Handling the event (**Self**).
259
+ - **`$parentHost`**: The direct parent SWC component.
165
260
  - **`$appHost`**: The nearest parent `<swc-app>` (the SPA boundary).
166
- - **`$hosts`**: Array of all SWC ancestors.
167
- - **`$firstHost`**: The top-most SWC ancestor.
261
+ - **`$hosts`**: Array of all SWC ancestors `[Root, ..., Parent, Self]`.
168
262
 
169
263
  ---
170
264
 
171
- ## 🛠 Lifecycle Decorators
172
- Full control over the component lifecycle with parent-to-child execution order.
173
-
265
+ ## 🛠 Lifecycle Hooks
266
+ Executed in **Parent -> Child** order.
174
267
  - `@onBeforeConnected` / `@onAfterConnected` (alias: `@onConnected`)
175
268
  - `@onBeforeDisconnected` / `@onAfterDisconnected` (alias: `@onDisconnected`)
176
269
  - `@onBeforeAdopted` / `@onAfterAdopted` (alias: `@onAdopted`)
177
- - `@onAddEventListener` (triggers when decorators bind listeners)
270
+ - `@onAddEventListener`
178
271
 
179
272
  ---
180
273
 
181
- ## ⚡ Performance Benchmark
182
- By removing "Deep Scanning" and "Automatic DOM Observers", SWC achieves near-native performance.
183
-
184
- - **Initialization**: 2x faster than Proxy-based libraries.
185
- - **Memory**: 40% less overhead in high-node scenarios.
186
- - **Stability**: Standard `attributeChangedCallback` ensures reliable updates without "magic" side effects.
274
+ ## ⚡ Performance
275
+ - **Zero "Deep Scan"**: Listeners are only bound to specific selectors.
276
+ - **Surgical Updates**: Manipulate DOM directly in `@changedAttribute` instead of full re-renders.
277
+ - **Zero Background Overhead**: No polling, no background observers.
187
278
 
188
279
  ---
189
280
 
@@ -24,16 +24,15 @@ __export(addEventListener_exports, {
24
24
  module.exports = __toCommonJS(addEventListener_exports);
25
25
  var import_ReflectUtils = require("@dooboostore/core/reflect/ReflectUtils");
26
26
  const ADD_EVENT_LISTENER_METADATA_KEY = Symbol("simple-web-component:add-event-listener");
27
- function addEventListener(arg1, arg2) {
27
+ function addEventListener(selector, type, options = {}) {
28
28
  return (target, propertyKey, descriptor) => {
29
29
  const constructor = target.constructor;
30
- const options = typeof arg1 === "string" ? { type: arg1, query: arg2 } : arg1;
31
30
  let listeners = import_ReflectUtils.ReflectUtils.getMetadata(ADD_EVENT_LISTENER_METADATA_KEY, constructor);
32
31
  if (!listeners) {
33
32
  listeners = [];
34
33
  import_ReflectUtils.ReflectUtils.defineMetadata(ADD_EVENT_LISTENER_METADATA_KEY, listeners, constructor);
35
34
  }
36
- listeners.push({ options, propertyKey });
35
+ listeners.push({ selector, type, options, propertyKey });
37
36
  };
38
37
  }
39
38
  const getAddEventListenerMetadata = (target) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/decorators/addEventListener.ts"],
4
- "sourcesContent": ["import { ReflectUtils } from '@dooboostore/core/reflect/ReflectUtils';\n\nexport interface AddEventListenerOptions extends EventListenerOptions {\n query?: string;\n type: string;\n root?: 'light' | 'shadow' | 'all' | 'auto';\n capture?: boolean;\n once?: boolean;\n passive?: boolean;\n stopPropagation?: boolean;\n stopImmediatePropagation?: boolean;\n preventDefault?: boolean;\n}\n\nexport interface AddEventListenerMetadata {\n options: AddEventListenerOptions;\n propertyKey: string | symbol;\n}\n\nexport const ADD_EVENT_LISTENER_METADATA_KEY = Symbol('simple-web-component:add-event-listener');\n\nexport function addEventListener(type: string, query?: string): MethodDecorator;\nexport function addEventListener(options: AddEventListenerOptions): MethodDecorator;\nexport function addEventListener(arg1: string | AddEventListenerOptions, arg2?: string): MethodDecorator {\n return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {\n const constructor = target.constructor;\n const options: AddEventListenerOptions = typeof arg1 === 'string' ? { type: arg1, query: arg2 } : arg1;\n\n let listeners = ReflectUtils.getMetadata<AddEventListenerMetadata[]>(ADD_EVENT_LISTENER_METADATA_KEY, constructor);\n if (!listeners) {\n listeners = [];\n ReflectUtils.defineMetadata(ADD_EVENT_LISTENER_METADATA_KEY, listeners, constructor);\n }\n listeners.push({ options, propertyKey });\n };\n}\n\nexport const getAddEventListenerMetadata = (target: any): AddEventListenerMetadata[] | undefined => {\n const constructor = target instanceof Function ? target : target.constructor;\n return ReflectUtils.getMetadata(ADD_EVENT_LISTENER_METADATA_KEY, constructor);\n};\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAA6B;AAmBtB,MAAM,kCAAkC,OAAO,yCAAyC;AAIxF,SAAS,iBAAiB,MAAwC,MAAgC;AACvG,SAAO,CAAC,QAAgB,aAA8B,eAAmC;AACvF,UAAM,cAAc,OAAO;AAC3B,UAAM,UAAmC,OAAO,SAAS,WAAW,EAAE,MAAM,MAAM,OAAO,KAAK,IAAI;AAElG,QAAI,YAAY,iCAAa,YAAwC,iCAAiC,WAAW;AACjH,QAAI,CAAC,WAAW;AACd,kBAAY,CAAC;AACb,uCAAa,eAAe,iCAAiC,WAAW,WAAW;AAAA,IACrF;AACA,cAAU,KAAK,EAAE,SAAS,YAAY,CAAC;AAAA,EACzC;AACF;AAEO,MAAM,8BAA8B,CAAC,WAAwD;AAClG,QAAM,cAAc,kBAAkB,WAAW,SAAS,OAAO;AACjE,SAAO,iCAAa,YAAY,iCAAiC,WAAW;AAC9E;",
4
+ "sourcesContent": ["import { ReflectUtils } from '@dooboostore/core/reflect/ReflectUtils';\n\nexport interface AddEventListenerOptions extends EventListenerOptions {\n root?: 'light' | 'shadow' | 'all' | 'auto';\n capture?: boolean;\n once?: boolean;\n passive?: boolean;\n stopPropagation?: boolean;\n stopImmediatePropagation?: boolean;\n preventDefault?: boolean;\n delegate?: boolean;\n}\n\nexport interface AddEventListenerMetadata {\n selector: string;\n type: string;\n options: AddEventListenerOptions;\n propertyKey: string | symbol;\n}\n\nexport const ADD_EVENT_LISTENER_METADATA_KEY = Symbol('simple-web-component:add-event-listener');\n\n/**\n * @addEventListener decorator to bind events to elements.\n * Usage: @addEventListener('.btn', 'click') onClick() { ... }\n * Usage: @addEventListener('', 'scroll', { passive: true }) onScroll() { ... }\n */\nexport function addEventListener(selector: string, type: string, options: AddEventListenerOptions = {}): MethodDecorator {\n return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {\n const constructor = target.constructor;\n\n let listeners = ReflectUtils.getMetadata<AddEventListenerMetadata[]>(ADD_EVENT_LISTENER_METADATA_KEY, constructor);\n if (!listeners) {\n listeners = [];\n ReflectUtils.defineMetadata(ADD_EVENT_LISTENER_METADATA_KEY, listeners, constructor);\n }\n listeners.push({ selector, type, options, propertyKey });\n };\n}\n\nexport const getAddEventListenerMetadata = (target: any): AddEventListenerMetadata[] | undefined => {\n const constructor = target instanceof Function ? target : target.constructor;\n return ReflectUtils.getMetadata(ADD_EVENT_LISTENER_METADATA_KEY, constructor);\n};\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAA6B;AAoBtB,MAAM,kCAAkC,OAAO,yCAAyC;AAOxF,SAAS,iBAAiB,UAAkB,MAAc,UAAmC,CAAC,GAAoB;AACvH,SAAO,CAAC,QAAgB,aAA8B,eAAmC;AACvF,UAAM,cAAc,OAAO;AAE3B,QAAI,YAAY,iCAAa,YAAwC,iCAAiC,WAAW;AACjH,QAAI,CAAC,WAAW;AACd,kBAAY,CAAC;AACb,uCAAa,eAAe,iCAAiC,WAAW,WAAW;AAAA,IACrF;AACA,cAAU,KAAK,EAAE,UAAU,MAAM,SAAS,YAAY,CAAC;AAAA,EACzD;AACF;AAEO,MAAM,8BAA8B,CAAC,WAAwD;AAClG,QAAM,cAAc,kBAAkB,WAAW,SAAS,OAAO;AACjE,SAAO,iCAAa,YAAY,iCAAiC,WAAW;AAC9E;",
6
6
  "names": []
7
7
  }
@@ -112,10 +112,12 @@ const elementDefine = (inConfig) => (constructor) => {
112
112
  return this[methodName](...extraArgs);
113
113
  }
114
114
  }
115
- _bindAttributeEvent(el, attrName, script) {
116
- if (!attrName.startsWith("swc-on-"))
117
- return;
118
- const eventName = attrName.substring(7);
115
+ _bindAttributeEvent(el, attrName, script, eventName) {
116
+ if (!eventName) {
117
+ if (!attrName.startsWith("swc-on-"))
118
+ return;
119
+ eventName = attrName.substring(7);
120
+ }
119
121
  if (swcLifecycleAttributes.includes(attrName))
120
122
  return;
121
123
  const elHandlers = getHandlers(this);
@@ -176,44 +178,79 @@ const elementDefine = (inConfig) => (constructor) => {
176
178
  this.innerHTML = lContent;
177
179
  }
178
180
  addEventListenerList.forEach((meta) => {
179
- const { type, query, root, capture, once, passive, stopPropagation, stopImmediatePropagation, preventDefault } = meta.options;
180
- const targetEls = [];
181
- if (!query) {
182
- targetEls.push(this);
183
- } else {
184
- const searchRoots = [];
181
+ const { selector, type, options } = meta;
182
+ const { root, capture, once, passive, stopPropagation, stopImmediatePropagation, preventDefault, delegate } = options;
183
+ if (delegate && selector && selector !== ":host") {
184
+ const bindRoots = [];
185
185
  const r = root || "auto";
186
186
  if (r === "auto") {
187
- searchRoots.push(this.shadowRoot || this);
187
+ bindRoots.push(this.shadowRoot || this);
188
188
  } else if (r === "light") {
189
- searchRoots.push(this);
189
+ bindRoots.push(this);
190
190
  } else if (r === "shadow") {
191
191
  if (this.shadowRoot)
192
- searchRoots.push(this.shadowRoot);
192
+ bindRoots.push(this.shadowRoot);
193
193
  } else if (r === "all") {
194
- searchRoots.push(this);
194
+ bindRoots.push(this);
195
195
  if (this.shadowRoot)
196
- searchRoots.push(this.shadowRoot);
196
+ bindRoots.push(this.shadowRoot);
197
+ }
198
+ bindRoots.forEach((bindRoot) => {
199
+ const handler = (event) => {
200
+ const target = event.target;
201
+ const matchedEl = target.closest(selector);
202
+ if (matchedEl && bindRoot.contains(matchedEl)) {
203
+ if (stopPropagation)
204
+ event.stopPropagation();
205
+ if (stopImmediatePropagation)
206
+ event.stopImmediatePropagation();
207
+ if (preventDefault)
208
+ event.preventDefault();
209
+ const hostSet2 = import_Utils.SwcUtils.getHostSet(this);
210
+ this._invokeLifecycleMethod(meta.propertyKey, hostSet2, [event, hostSet2, matchedEl]);
211
+ }
212
+ };
213
+ bindRoot.addEventListener(type, handler, { capture, once, passive });
214
+ });
215
+ } else {
216
+ const targetEls = [];
217
+ if (!selector || selector === ":host") {
218
+ targetEls.push(this);
219
+ } else {
220
+ const searchRoots = [];
221
+ const r = root || "auto";
222
+ if (r === "auto") {
223
+ searchRoots.push(this.shadowRoot || this);
224
+ } else if (r === "light") {
225
+ searchRoots.push(this);
226
+ } else if (r === "shadow") {
227
+ if (this.shadowRoot)
228
+ searchRoots.push(this.shadowRoot);
229
+ } else if (r === "all") {
230
+ searchRoots.push(this);
231
+ if (this.shadowRoot)
232
+ searchRoots.push(this.shadowRoot);
233
+ }
234
+ searchRoots.forEach((sr) => {
235
+ const found = sr.querySelectorAll(selector);
236
+ found.forEach((el) => targetEls.push(el));
237
+ });
197
238
  }
198
- searchRoots.forEach((sr) => {
199
- const found = sr.querySelectorAll(query);
200
- found.forEach((el) => targetEls.push(el));
239
+ targetEls.forEach((el) => {
240
+ const handler = (event) => {
241
+ if (stopPropagation)
242
+ event.stopPropagation();
243
+ if (stopImmediatePropagation)
244
+ event.stopImmediatePropagation();
245
+ if (preventDefault)
246
+ event.preventDefault();
247
+ const hostSet2 = import_Utils.SwcUtils.getHostSet(this);
248
+ this._invokeLifecycleMethod(meta.propertyKey, hostSet2, [event, hostSet2]);
249
+ };
250
+ const opts = { capture, once, passive };
251
+ el.addEventListener(type, handler, opts);
201
252
  });
202
253
  }
203
- targetEls.forEach((el) => {
204
- const handler = (event) => {
205
- if (stopPropagation)
206
- event.stopPropagation();
207
- if (stopImmediatePropagation)
208
- event.stopImmediatePropagation();
209
- if (preventDefault)
210
- event.preventDefault();
211
- const hostSet2 = import_Utils.SwcUtils.getHostSet(this);
212
- this._invokeLifecycleMethod(meta.propertyKey, hostSet2, [event, hostSet2]);
213
- };
214
- const options = { capture, once, passive };
215
- el.addEventListener(type, handler, options);
216
- });
217
254
  });
218
255
  if (super.connectedCallback)
219
256
  await super.connectedCallback();
@@ -258,7 +295,9 @@ const elementDefine = (inConfig) => (constructor) => {
258
295
  super.attributeChangedCallback(name, old, newVal);
259
296
  if (name.startsWith("swc-on-") && !swcLifecycleAttributes.includes(name)) {
260
297
  if (newVal !== null) {
261
- this._bindAttributeEvent(this, name, newVal);
298
+ const customEventMeta = emitCustomEventList.find((it) => it.options.attributeName === name);
299
+ const eventName = customEventMeta ? customEventMeta.options.type : name.substring(7);
300
+ this._bindAttributeEvent(this, name, newVal, eventName);
262
301
  }
263
302
  }
264
303
  const mKeys = attrChangeMap.get(name);
@@ -283,6 +322,23 @@ const elementDefine = (inConfig) => (constructor) => {
283
322
  };
284
323
  }
285
324
  });
325
+ emitCustomEventList.forEach((meta) => {
326
+ const { propertyKey, options } = meta;
327
+ const original = NewClass.prototype[propertyKey];
328
+ if (typeof original === "function") {
329
+ NewClass.prototype[propertyKey] = function(...args) {
330
+ const detail = original.apply(this, args);
331
+ const event = new CustomEvent(options.type, {
332
+ detail,
333
+ bubbles: options.bubbles,
334
+ composed: options.composed,
335
+ cancelable: options.cancelable
336
+ });
337
+ this.dispatchEvent(event);
338
+ return detail;
339
+ };
340
+ }
341
+ });
286
342
  const originalProto = constructor.prototype;
287
343
  const newProto = NewClass.prototype;
288
344
  Object.getOwnPropertyNames(originalProto).forEach((name) => {