@sprlab/wccompiler 0.9.5 → 0.9.6
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/adapters/angular-compiled/angular.d.ts +1 -1
- package/adapters/angular.js +46 -34
- package/adapters/angular.ts +71 -18
- package/bin/wcc.js +0 -0
- package/package.json +5 -2
|
@@ -66,7 +66,7 @@ export declare class WccSlotsDirective implements AfterContentInit, OnDestroy {
|
|
|
66
66
|
private classifyAndInitSlots;
|
|
67
67
|
/** Named Slot: immediate static rendering */
|
|
68
68
|
private initNamedSlot;
|
|
69
|
-
/** Scoped Slot:
|
|
69
|
+
/** Scoped Slot: registration + reactive rendering */
|
|
70
70
|
private initScopedSlot;
|
|
71
71
|
/**
|
|
72
72
|
* Builds the Angular context for createEmbeddedView.
|
package/adapters/angular.js
CHANGED
|
@@ -5,38 +5,50 @@
|
|
|
5
5
|
*
|
|
6
6
|
* ANGULAR INTEGRATION:
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
8
|
+
* The adapter ships as TypeScript source (adapters/angular.ts) because Angular
|
|
9
|
+
* AOT requires directives to be compiled within the consuming project's scope.
|
|
10
|
+
* The package.json "exports" map points directly to the .ts file, which Angular's
|
|
11
|
+
* esbuild-based `application` builder (Angular 17+) handles natively.
|
|
12
|
+
*
|
|
13
|
+
* SETUP:
|
|
14
|
+
*
|
|
15
|
+
* 1. Install the package:
|
|
16
|
+
* npm install @sprlab/wccompiler
|
|
17
|
+
*
|
|
18
|
+
* 2. Add a tsconfig path mapping (tsconfig.json):
|
|
19
|
+
* {
|
|
20
|
+
* "compilerOptions": {
|
|
21
|
+
* "paths": {
|
|
22
|
+
* "@sprlab/wccompiler/adapters/angular": ["node_modules/@sprlab/wccompiler/adapters/angular.ts"]
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* 3. Import in your component:
|
|
28
|
+
* import { WccSlotsDirective, WccSlotDef } from '@sprlab/wccompiler/adapters/angular';
|
|
29
|
+
*
|
|
30
|
+
* @Component({
|
|
31
|
+
* imports: [WccSlotsDirective, WccSlotDef],
|
|
32
|
+
* schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
33
|
+
* template: `
|
|
34
|
+
* <wcc-card wccSlots>
|
|
35
|
+
* <ng-template slot="header"><strong>Title</strong></ng-template>
|
|
36
|
+
* <ng-template slot="stats" let-likes>⭐ {{likes}} stars!</ng-template>
|
|
37
|
+
* </wcc-card>
|
|
38
|
+
* `
|
|
39
|
+
* })
|
|
40
|
+
*
|
|
41
|
+
* HOW IT WORKS:
|
|
42
|
+
*
|
|
43
|
+
* - WccSlotDef: Captures ng-template[slot] elements and their slot names
|
|
44
|
+
* - WccSlotsDirective: Activated via [wccSlots] attribute on the host element
|
|
45
|
+
* - Classifies slots as "named" or "scoped" using the component's __scopedSlots metadata
|
|
46
|
+
* - Named slots: rendered immediately into the component's [data-slot] container
|
|
47
|
+
* - Scoped slots: registered via registerSlotRenderer() for reactive updates
|
|
48
|
+
*
|
|
49
|
+
* REQUIREMENTS:
|
|
50
|
+
* - Angular 17+ with the `application` builder (esbuild-based)
|
|
51
|
+
* - The [wccSlots] attribute must be added to WCC elements that use slots
|
|
52
|
+
*
|
|
53
|
+
* NOTE: This .js file is documentation only. The actual source is adapters/angular.ts.
|
|
38
54
|
*/
|
|
39
|
-
|
|
40
|
-
// This file is intentionally a documentation-only .js file.
|
|
41
|
-
// The actual directive source is in adapters/angular.ts (TypeScript).
|
|
42
|
-
// Users copy it to their Angular project for AOT compilation.
|
package/adapters/angular.ts
CHANGED
|
@@ -123,9 +123,19 @@ export class WccSlotsDirective implements AfterContentInit, OnDestroy {
|
|
|
123
123
|
// ─── Classification ─────────────────────────────────────────────────────
|
|
124
124
|
|
|
125
125
|
/** Classifies slots using __scopedSlots from the host element and initializes them */
|
|
126
|
-
private classifyAndInitSlots(): void {
|
|
127
|
-
const
|
|
128
|
-
const
|
|
126
|
+
private async classifyAndInitSlots(): Promise<void> {
|
|
127
|
+
const hostEl = this.el.nativeElement;
|
|
128
|
+
const tagName = hostEl.tagName.toLowerCase();
|
|
129
|
+
|
|
130
|
+
// Wait for the custom element to be defined (ensures the class is upgraded)
|
|
131
|
+
await customElements.whenDefined(tagName);
|
|
132
|
+
if (this.destroyed) return;
|
|
133
|
+
|
|
134
|
+
const element = hostEl as any;
|
|
135
|
+
// Read from instance getter or static property
|
|
136
|
+
const scopedNames: string[] = element.__scopedSlots
|
|
137
|
+
|| (element.constructor && element.constructor.__scopedSlots)
|
|
138
|
+
|| [];
|
|
129
139
|
|
|
130
140
|
for (const slotDef of this.slotDefs) {
|
|
131
141
|
if (!slotDef.slotName) continue;
|
|
@@ -143,15 +153,27 @@ export class WccSlotsDirective implements AfterContentInit, OnDestroy {
|
|
|
143
153
|
/** Named Slot: immediate static rendering */
|
|
144
154
|
private initNamedSlot(slotDef: WccSlotDef): void {
|
|
145
155
|
const hostEl = this.el.nativeElement;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
156
|
+
|
|
157
|
+
// Strategy 1: Find [data-slot] container inside the component's internal DOM
|
|
158
|
+
const dataSlotEl = hostEl.querySelector(`[data-slot="${slotDef.slotName}"]`);
|
|
159
|
+
let wrapper: HTMLElement;
|
|
160
|
+
|
|
161
|
+
if (dataSlotEl) {
|
|
162
|
+
// Use the data-slot element directly — clear fallback content and insert rendered nodes
|
|
163
|
+
wrapper = dataSlotEl as HTMLElement;
|
|
164
|
+
wrapper.innerHTML = '';
|
|
165
|
+
} else {
|
|
166
|
+
// Strategy 2: Fallback for Shadow DOM / native <slot> elements
|
|
167
|
+
wrapper = document.createElement('div');
|
|
168
|
+
wrapper.setAttribute('slot', slotDef.slotName);
|
|
169
|
+
wrapper.style.display = 'contents';
|
|
170
|
+
hostEl.appendChild(wrapper);
|
|
171
|
+
}
|
|
149
172
|
|
|
150
173
|
const viewRef = this.vcr.createEmbeddedView(slotDef.templateRef);
|
|
151
174
|
for (const node of viewRef.rootNodes) {
|
|
152
175
|
wrapper.appendChild(node);
|
|
153
176
|
}
|
|
154
|
-
hostEl.appendChild(wrapper);
|
|
155
177
|
|
|
156
178
|
this.slots.set(slotDef.slotName, {
|
|
157
179
|
type: 'named',
|
|
@@ -161,18 +183,15 @@ export class WccSlotsDirective implements AfterContentInit, OnDestroy {
|
|
|
161
183
|
wrapperEl: wrapper,
|
|
162
184
|
context: null,
|
|
163
185
|
});
|
|
186
|
+
|
|
187
|
+
this.cdr.detectChanges();
|
|
164
188
|
}
|
|
165
189
|
|
|
166
190
|
// ─── Scoped Slot ────────────────────────────────────────────────────────
|
|
167
191
|
|
|
168
|
-
/** Scoped Slot:
|
|
169
|
-
private
|
|
192
|
+
/** Scoped Slot: registration + reactive rendering */
|
|
193
|
+
private initScopedSlot(slotDef: WccSlotDef): void {
|
|
170
194
|
const hostEl = this.el.nativeElement;
|
|
171
|
-
const tagName = hostEl.tagName.toLowerCase();
|
|
172
|
-
|
|
173
|
-
// Wait for the custom element to be defined
|
|
174
|
-
await customElements.whenDefined(tagName);
|
|
175
|
-
if (this.destroyed) return;
|
|
176
195
|
|
|
177
196
|
const state: SlotState = {
|
|
178
197
|
type: 'scoped',
|
|
@@ -245,23 +264,51 @@ export class WccSlotsDirective implements AfterContentInit, OnDestroy {
|
|
|
245
264
|
state.context = context;
|
|
246
265
|
|
|
247
266
|
if (state.viewRef) {
|
|
267
|
+
// Update existing view context
|
|
248
268
|
Object.assign(state.viewRef.context, context);
|
|
249
269
|
state.viewRef.markForCheck();
|
|
270
|
+
// Re-insert nodes to reflect updated content (Angular doesn't auto-update DOM for detached views)
|
|
271
|
+
if (state.wrapperEl) {
|
|
272
|
+
state.wrapperEl.innerHTML = '';
|
|
273
|
+
for (const node of state.viewRef.rootNodes) {
|
|
274
|
+
state.wrapperEl.appendChild(node);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
250
277
|
} else {
|
|
251
278
|
state.viewRef = this.vcr.createEmbeddedView(state.slotDef.templateRef, context);
|
|
252
279
|
this.insertView(slotName, state);
|
|
253
280
|
}
|
|
254
281
|
|
|
255
|
-
this.cdr.
|
|
282
|
+
this.cdr.detectChanges();
|
|
256
283
|
}
|
|
257
284
|
|
|
258
285
|
// ─── DOM Insertion ──────────────────────────────────────────────────────
|
|
259
286
|
|
|
260
|
-
/**
|
|
287
|
+
/**
|
|
288
|
+
* Inserts view root nodes into the custom element's DOM.
|
|
289
|
+
*
|
|
290
|
+
* Strategy:
|
|
291
|
+
* 1. Look for a [data-slot="slotName"] element inside the component (non-Shadow DOM)
|
|
292
|
+
* → clear its content and insert the rendered nodes there
|
|
293
|
+
* 2. Fallback: append a wrapper <div slot="slotName"> to the host (Shadow DOM / native slots)
|
|
294
|
+
*/
|
|
261
295
|
private insertView(slotName: string, state: SlotState): void {
|
|
262
296
|
if (!state.viewRef) return;
|
|
263
297
|
const hostEl = this.el.nativeElement;
|
|
264
298
|
|
|
299
|
+
// Strategy 1: Find [data-slot] container inside the component's internal DOM
|
|
300
|
+
const dataSlotEl = hostEl.querySelector(`[data-slot="${slotName}"]`);
|
|
301
|
+
if (dataSlotEl) {
|
|
302
|
+
// Use the data-slot element as the wrapper (no extra div needed)
|
|
303
|
+
state.wrapperEl = dataSlotEl as HTMLElement;
|
|
304
|
+
state.wrapperEl.innerHTML = '';
|
|
305
|
+
for (const node of state.viewRef.rootNodes) {
|
|
306
|
+
state.wrapperEl.appendChild(node);
|
|
307
|
+
}
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Strategy 2: Fallback for Shadow DOM / native <slot> elements
|
|
265
312
|
if (!state.wrapperEl) {
|
|
266
313
|
state.wrapperEl = document.createElement('div');
|
|
267
314
|
state.wrapperEl.setAttribute('slot', slotName);
|
|
@@ -286,8 +333,14 @@ export class WccSlotsDirective implements AfterContentInit, OnDestroy {
|
|
|
286
333
|
if (state.cleanup) {
|
|
287
334
|
state.cleanup();
|
|
288
335
|
}
|
|
289
|
-
if (state.wrapperEl
|
|
290
|
-
|
|
336
|
+
if (state.wrapperEl) {
|
|
337
|
+
// If the wrapper is a [data-slot] element (part of the component's internal DOM),
|
|
338
|
+
// just clear its content rather than removing it from the DOM
|
|
339
|
+
if (state.wrapperEl.hasAttribute('data-slot')) {
|
|
340
|
+
state.wrapperEl.innerHTML = '';
|
|
341
|
+
} else if (state.wrapperEl.parentNode) {
|
|
342
|
+
state.wrapperEl.parentNode.removeChild(state.wrapperEl);
|
|
343
|
+
}
|
|
291
344
|
}
|
|
292
345
|
}
|
|
293
346
|
this.slots.clear();
|
package/bin/wcc.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sprlab/wccompiler",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.6",
|
|
4
4
|
"description": "Zero-runtime compiler that transforms .wcc single-file components into native web components with signals-based reactivity",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
"./integrations/react": "./integrations/react.js",
|
|
10
10
|
"./integrations/angular": "./integrations/angular.js",
|
|
11
11
|
"./adapters/vue": "./adapters/vue.js",
|
|
12
|
-
"./adapters/angular":
|
|
12
|
+
"./adapters/angular": {
|
|
13
|
+
"types": "./adapters/angular-compiled/angular.d.ts",
|
|
14
|
+
"default": "./adapters/angular.ts"
|
|
15
|
+
},
|
|
13
16
|
"./adapters/react": "./adapters/react.js"
|
|
14
17
|
},
|
|
15
18
|
"bin": {
|