@dooboostore/simple-web-component 1.0.15 → 1.0.17
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 +275 -91
- package/dist/cjs/decorators/addEventListener.js +2 -3
- package/dist/cjs/decorators/addEventListener.js.map +2 -2
- package/dist/cjs/decorators/elementDefine.js +161 -34
- package/dist/cjs/decorators/elementDefine.js.map +3 -3
- package/dist/cjs/decorators/emitCustomEvent.js +2 -14
- package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
- package/dist/cjs/decorators/query.js +47 -36
- package/dist/cjs/decorators/query.js.map +2 -2
- package/dist/cjs/decorators/queryAll.js +42 -36
- package/dist/cjs/decorators/queryAll.js.map +2 -2
- package/dist/cjs/elements/SwcAppEngine.js +2 -1
- package/dist/cjs/elements/SwcAppEngine.js.map +2 -2
- package/dist/cjs/elements/SwcAsync.js +188 -0
- package/dist/cjs/elements/SwcAsync.js.map +7 -0
- package/dist/cjs/elements/SwcChoose.js +155 -0
- package/dist/cjs/elements/SwcChoose.js.map +7 -0
- package/dist/cjs/elements/SwcIf.js +167 -0
- package/dist/cjs/elements/SwcIf.js.map +7 -0
- package/dist/cjs/elements/SwcLoop.js +246 -0
- package/dist/cjs/elements/SwcLoop.js.map +7 -0
- package/dist/cjs/elements/SwcSubTemplates.js +65 -0
- package/dist/cjs/elements/SwcSubTemplates.js.map +7 -0
- package/dist/cjs/elements/index.js +7 -0
- package/dist/cjs/elements/index.js.map +2 -2
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.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 +92 -162
- package/dist/cjs/utils/Utils.js.map +3 -3
- package/dist/esm/decorators/addEventListener.js +2 -3
- package/dist/esm/decorators/addEventListener.js.map +2 -2
- package/dist/esm/decorators/elementDefine.js +161 -34
- package/dist/esm/decorators/elementDefine.js.map +3 -3
- package/dist/esm/decorators/emitCustomEvent.js +2 -14
- package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
- package/dist/esm/decorators/query.js +47 -36
- package/dist/esm/decorators/query.js.map +2 -2
- package/dist/esm/decorators/queryAll.js +42 -36
- package/dist/esm/decorators/queryAll.js.map +2 -2
- package/dist/esm/elements/SwcAppEngine.js +2 -1
- package/dist/esm/elements/SwcAppEngine.js.map +2 -2
- package/dist/esm/elements/SwcAsync.js +169 -0
- package/dist/esm/elements/SwcAsync.js.map +7 -0
- package/dist/esm/elements/SwcChoose.js +136 -0
- package/dist/esm/elements/SwcChoose.js.map +7 -0
- package/dist/esm/elements/SwcIf.js +148 -0
- package/dist/esm/elements/SwcIf.js.map +7 -0
- package/dist/esm/elements/SwcLoop.js +227 -0
- package/dist/esm/elements/SwcLoop.js.map +7 -0
- package/dist/esm/elements/SwcSubTemplates.js +46 -0
- package/dist/esm/elements/SwcSubTemplates.js.map +7 -0
- package/dist/esm/elements/index.js +7 -0
- package/dist/esm/elements/index.js.map +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types.js +1 -0
- package/dist/esm/types.js.map +2 -2
- package/dist/esm/utils/Utils.js +92 -162
- package/dist/esm/utils/Utils.js.map +3 -3
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +1150 -326
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
- package/dist/types/decorators/addEventListener.d.ts +10 -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 +4 -5
- package/dist/types/decorators/query.d.ts.map +1 -1
- package/dist/types/decorators/queryAll.d.ts +4 -5
- package/dist/types/decorators/queryAll.d.ts.map +1 -1
- package/dist/types/elements/SwcAppEngine.d.ts.map +1 -1
- package/dist/types/elements/SwcAsync.d.ts +16 -0
- package/dist/types/elements/SwcAsync.d.ts.map +1 -0
- package/dist/types/elements/SwcChoose.d.ts +13 -0
- package/dist/types/elements/SwcChoose.d.ts.map +1 -0
- package/dist/types/elements/SwcIf.d.ts +15 -0
- package/dist/types/elements/SwcIf.d.ts.map +1 -0
- package/dist/types/elements/SwcLoop.d.ts +20 -0
- package/dist/types/elements/SwcLoop.d.ts.map +1 -0
- package/dist/types/elements/SwcSubTemplates.d.ts +13 -0
- package/dist/types/elements/SwcSubTemplates.d.ts.map +1 -0
- package/dist/types/elements/index.d.ts +7 -0
- package/dist/types/elements/index.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/types.d.ts +11 -1
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/utils/Utils.d.ts +19 -21
- package/dist/types/utils/Utils.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +1150 -326
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
- package/package.json +10 -3
- package/src/decorators/addEventListener.ts +11 -7
- package/src/decorators/elementDefine.ts +165 -39
- package/src/decorators/emitCustomEvent.ts +5 -21
- package/src/decorators/query.ts +46 -41
- package/src/decorators/queryAll.ts +45 -41
- package/src/elements/SwcAppEngine.ts +2 -1
- package/src/elements/SwcAsync.ts +163 -0
- package/src/elements/SwcChoose.ts +127 -0
- package/src/elements/SwcIf.ts +134 -0
- package/src/elements/SwcLoop.ts +215 -0
- package/src/elements/SwcSubTemplates.ts +19 -0
- package/src/elements/index.ts +8 -3
- package/src/index.ts +1 -1
- package/src/types.ts +15 -5
- package/src/utils/Utils.ts +111 -197
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;
|
|
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**.
|
|
100
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,247 @@ class EventDemo extends HTMLElement {
|
|
|
114
127
|
|
|
115
128
|
## 🌐 Powerful SPA & Routing
|
|
116
129
|
|
|
117
|
-
SWC
|
|
118
|
-
|
|
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.
|
|
130
|
+
SWC provides a revolutionary way to build SPAs where **each component scope is an independent application**.
|
|
121
131
|
|
|
122
|
-
###
|
|
123
|
-
Use the `<swc-app>` tag and
|
|
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>
|
|
159
|
+
```
|
|
160
|
+
|
|
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
|
+
}
|
|
136
226
|
```
|
|
137
227
|
|
|
138
|
-
### 3.
|
|
139
|
-
|
|
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`
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
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.
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## 🔄 Structural Directives (Advanced)
|
|
284
|
+
|
|
285
|
+
SWC provides 4 core templates that empower your HTML without wrapper elements: `swc-loop`, `swc-if`, `swc-choose`, and `swc-async`. They all share a consistent, high-performance API.
|
|
286
|
+
|
|
287
|
+
### Common Attributes:
|
|
288
|
+
- **`on-get-value`**: Script to fetch data (supports return-less expressions).
|
|
289
|
+
- **`on-clone-nodes`**: Callback after a group of nodes is cloned (Args: `$index`, `$item`, `$nodes`, `$elements`, `$firstElement`, `$state`).
|
|
290
|
+
- **`on-clone-node`**: Callback for each individual node (Args: `$node`, `$nodeIndex`, and all group args).
|
|
291
|
+
- **`on-get-portal`**: Script that returns an `Element` or selector string to render content in a different part of the DOM.
|
|
292
|
+
|
|
293
|
+
### 1. `swc-if` (Conditional Rendering)
|
|
294
|
+
Renders content only when the condition is truthy. Supports a fallback `swc-default` state.
|
|
295
|
+
```html
|
|
296
|
+
<template is="swc-if" on-get-value="isAdmin">
|
|
297
|
+
<template is="swc-default"><p>Please login as admin.</p></template>
|
|
298
|
+
<div class="admin-panel">Welcome, Master!</div>
|
|
299
|
+
</template>
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### 2. `swc-loop` (High-Performance Iteration)
|
|
303
|
+
Efficiently renders lists with surgical updates and index preservation.
|
|
304
|
+
```html
|
|
305
|
+
<template is="swc-loop" on-get-value="users"
|
|
306
|
+
on-clone-node="if($isElement) $node.querySelector('.name').textContent = $item.name">
|
|
307
|
+
<template is="swc-default"><p>No users found.</p></template>
|
|
308
|
+
<div class="user-row"><span class="name"></span></div>
|
|
309
|
+
</template>
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### 3. `swc-choose` (Switch-Case Logic)
|
|
313
|
+
Selects the first matching `swc-when` or falls back to `swc-otherwise`.
|
|
314
|
+
```html
|
|
315
|
+
<template is="swc-choose" on-get-value="user.role">
|
|
316
|
+
<template is="swc-default">Please select a role.</template>
|
|
317
|
+
<template is="swc-when" value="admin">Admin Dashboard</template>
|
|
318
|
+
<template is="swc-when" value="user">User Home</template>
|
|
319
|
+
<template is="swc-otherwise">Access Denied</template>
|
|
320
|
+
</template>
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### 4. `swc-async` (Declarative Promises)
|
|
324
|
+
Handles Promise states (Loading, Error, Success) without manual state management.
|
|
325
|
+
```html
|
|
326
|
+
<template is="swc-async" on-get-value="fetchData()">
|
|
327
|
+
<template is="swc-loading">🌀 Loading...</template>
|
|
328
|
+
<template is="swc-error">❌ Error: {{$error.message}}</template>
|
|
329
|
+
<template is="swc-success">✅ Data: {{$value.title}}</template>
|
|
330
|
+
</template>
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Advanced: Integrated Portal Support
|
|
334
|
+
All directives above support the **`on-get-portal`** attribute to render content elsewhere.
|
|
335
|
+
```html
|
|
336
|
+
<template is="swc-if" on-get-value="showModal" on-get-portal="'body'">
|
|
337
|
+
<div class="modal-overlay">I am rendered at the end of <body>!</div>
|
|
338
|
+
</template>
|
|
339
|
+
```
|
|
178
340
|
|
|
179
341
|
---
|
|
180
342
|
|
|
181
|
-
## ⚡
|
|
182
|
-
|
|
343
|
+
## ⚡ Integrated Event & Query Selectors
|
|
344
|
+
|
|
345
|
+
Both `@addEventListener` and `@query` support special selectors to navigate the environment and component hierarchy seamlessly.
|
|
346
|
+
|
|
347
|
+
| Selector | Target Description |
|
|
348
|
+
|---|---|
|
|
349
|
+
| `:host` | The component itself (default for empty selector). |
|
|
350
|
+
| `:parentHost` | The immediate parent SWC component. |
|
|
351
|
+
| `:appHost` | The nearest parent `<swc-app>` instance. |
|
|
352
|
+
| `:window` | The environment-aware global `window` object. |
|
|
353
|
+
| `:document` | The environment-aware global `document` object. |
|
|
354
|
+
| `:hosts` | Array of all SWC ancestors (Root -> Self). |
|
|
355
|
+
|
|
356
|
+
### Global Event Example
|
|
357
|
+
```typescript
|
|
358
|
+
@elementDefine('auto-cleanup-demo')
|
|
359
|
+
class CleanupDemo extends HTMLElement {
|
|
360
|
+
// Automatically attaches to window and REMOVES on disconnectedCallback
|
|
361
|
+
@addEventListener(':window', 'resize', { removeOnDisconnected: true })
|
|
362
|
+
onResize(event: UIEvent) {
|
|
363
|
+
console.log('Window resized!', this.id);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
183
367
|
|
|
184
|
-
|
|
185
|
-
- **
|
|
186
|
-
- **
|
|
368
|
+
## ⚡ Performance Features
|
|
369
|
+
- **Smart Delegation**: Host-level delegation with `__swc_handled` duplicate protection.
|
|
370
|
+
- **Index-Preserving Removal**: `SwcLoop` maintains stable indices during item removal for maximum UI consistency.
|
|
187
371
|
|
|
188
372
|
---
|
|
189
373
|
|
|
@@ -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 removeOnDisconnected?: 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;AAqBtB,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
|
}
|