@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.
- package/README.md +183 -92
- package/dist/cjs/decorators/addEventListener.js +2 -3
- package/dist/cjs/decorators/addEventListener.js.map +2 -2
- package/dist/cjs/decorators/elementDefine.js +89 -33
- package/dist/cjs/decorators/elementDefine.js.map +2 -2
- package/dist/cjs/decorators/emitCustomEvent.js +2 -14
- package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
- package/dist/cjs/decorators/query.js +25 -36
- package/dist/cjs/decorators/query.js.map +2 -2
- package/dist/cjs/decorators/queryAll.js +28 -36
- package/dist/cjs/decorators/queryAll.js.map +2 -2
- package/dist/cjs/types.js +1 -0
- package/dist/cjs/types.js.map +2 -2
- package/dist/cjs/utils/Utils.js +36 -143
- package/dist/cjs/utils/Utils.js.map +2 -2
- package/dist/esm/decorators/addEventListener.js +2 -3
- package/dist/esm/decorators/addEventListener.js.map +2 -2
- package/dist/esm/decorators/elementDefine.js +89 -33
- package/dist/esm/decorators/elementDefine.js.map +2 -2
- package/dist/esm/decorators/emitCustomEvent.js +2 -14
- package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
- package/dist/esm/decorators/query.js +25 -36
- package/dist/esm/decorators/query.js.map +2 -2
- package/dist/esm/decorators/queryAll.js +28 -36
- package/dist/esm/decorators/queryAll.js.map +2 -2
- package/dist/esm/types.js +1 -0
- package/dist/esm/types.js.map +2 -2
- package/dist/esm/utils/Utils.js +36 -143
- package/dist/esm/utils/Utils.js.map +2 -2
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +183 -266
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +3 -3
- package/dist/types/decorators/addEventListener.d.ts +9 -4
- package/dist/types/decorators/addEventListener.d.ts.map +1 -1
- package/dist/types/decorators/elementDefine.d.ts.map +1 -1
- package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
- package/dist/types/decorators/query.d.ts +6 -5
- package/dist/types/decorators/query.d.ts.map +1 -1
- package/dist/types/decorators/queryAll.d.ts +6 -5
- package/dist/types/decorators/queryAll.d.ts.map +1 -1
- package/dist/types/types.d.ts +3 -1
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/utils/Utils.d.ts +6 -14
- package/dist/types/utils/Utils.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +183 -266
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +3 -3
- package/package.json +3 -3
- package/src/decorators/addEventListener.ts +10 -7
- package/src/decorators/elementDefine.ts +86 -31
- package/src/decorators/emitCustomEvent.ts +5 -21
- package/src/decorators/query.ts +33 -46
- package/src/decorators/queryAll.ts +36 -46
- package/src/types.ts +6 -4
- 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
|
|
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.
|
|
12
|
-
- **Native Inheritance**: Inherit directly from `HTMLElement`, `HTMLDivElement`,
|
|
13
|
-
- **Extreme Performance**:
|
|
14
|
-
- **Micro-Frontend Ready**: Independent routing and DI containers
|
|
15
|
-
- **Standard-First**:
|
|
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
|
-
|
|
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
|
|
33
|
+
## 💡 Core Decorators
|
|
41
34
|
|
|
42
|
-
### 1.
|
|
43
|
-
|
|
35
|
+
### 1. `@elementDefine(config)`
|
|
36
|
+
Registers the class as a custom element.
|
|
44
37
|
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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 `<
|
|
53
|
+
return `<style>:host { color: blue; }</style><div>Hello World</div>`;
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
```
|
|
56
57
|
|
|
57
|
-
###
|
|
58
|
-
|
|
58
|
+
### 3. Modern Attribute Management
|
|
59
|
+
SWC uses a **Method-Based Reflection** approach. You control exactly how data flows.
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
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
|
-
###
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
130
|
+
SWC provides a revolutionary way to build SPAs where **each component scope is an independent application**.
|
|
118
131
|
|
|
119
|
-
### 1.
|
|
120
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
###
|
|
139
|
-
|
|
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:
|
|
242
|
+
@Inject({ type: Router }) router: Router,
|
|
243
|
+
@Inject({ type: UserService }) api: UserService
|
|
152
244
|
) {
|
|
153
|
-
console.log('
|
|
154
|
-
api.
|
|
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
|
|
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
|
|
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
|
|
172
|
-
|
|
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`
|
|
270
|
+
- `@onAddEventListener`
|
|
178
271
|
|
|
179
272
|
---
|
|
180
273
|
|
|
181
|
-
## ⚡ Performance
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
- **
|
|
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(
|
|
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
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAA6B;
|
|
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 (!
|
|
117
|
-
|
|
118
|
-
|
|
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 {
|
|
180
|
-
const
|
|
181
|
-
if (
|
|
182
|
-
|
|
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
|
-
|
|
187
|
+
bindRoots.push(this.shadowRoot || this);
|
|
188
188
|
} else if (r === "light") {
|
|
189
|
-
|
|
189
|
+
bindRoots.push(this);
|
|
190
190
|
} else if (r === "shadow") {
|
|
191
191
|
if (this.shadowRoot)
|
|
192
|
-
|
|
192
|
+
bindRoots.push(this.shadowRoot);
|
|
193
193
|
} else if (r === "all") {
|
|
194
|
-
|
|
194
|
+
bindRoots.push(this);
|
|
195
195
|
if (this.shadowRoot)
|
|
196
|
-
|
|
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
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
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
|
-
|
|
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) => {
|