@sprlab/wccompiler 0.10.9 → 0.10.10
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.
|
@@ -1,26 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Angular adapter for WCC Scoped Slots.
|
|
2
|
+
* Angular adapter for WCC Scoped Slots and Event Binding.
|
|
3
3
|
*
|
|
4
4
|
* Exports:
|
|
5
5
|
* - WccSlotDef: Auxiliary directive for ng-template[slot]
|
|
6
6
|
* - WccSlotsDirective: Main directive activated via [wccSlots] attribute
|
|
7
|
+
* - WccEvent: Single-event directive (wccEvent="name" + wccEmit output)
|
|
8
|
+
* - WccEvents: Multi-event bridging directive (kebab-case → camelCase)
|
|
7
9
|
* - SlotContext: Interface for template context typing
|
|
8
10
|
*
|
|
9
11
|
* Usage:
|
|
10
|
-
* import { WccSlotsDirective, WccSlotDef } from '@sprlab/wccompiler/adapters/angular';
|
|
12
|
+
* import { WccSlotsDirective, WccSlotDef, WccEvent, WccEvents } from '@sprlab/wccompiler/adapters/angular';
|
|
11
13
|
*
|
|
12
14
|
* @Component({
|
|
13
|
-
* imports: [WccSlotsDirective, WccSlotDef],
|
|
15
|
+
* imports: [WccSlotsDirective, WccSlotDef, WccEvent, WccEvents],
|
|
14
16
|
* schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
15
17
|
* template: `
|
|
16
18
|
* <wcc-card wccSlots>
|
|
17
19
|
* <ng-template slot="header"><strong>Header</strong></ng-template>
|
|
18
20
|
* <ng-template slot="stats" let-likes>{{ likes }} likes</ng-template>
|
|
19
21
|
* </wcc-card>
|
|
22
|
+
*
|
|
23
|
+
* <!-- Event binding option 1: single event with unwrapped detail -->
|
|
24
|
+
* <wcc-counter wccEvent="count-changed" (wccEmit)="onCount($event)"></wcc-counter>
|
|
25
|
+
*
|
|
26
|
+
* <!-- Event binding option 2: camelCase event names -->
|
|
27
|
+
* <wcc-counter wccEvents (countChanged)="onCount($event.detail)"></wcc-counter>
|
|
28
|
+
*
|
|
29
|
+
* <!-- Event binding option 3: standard Angular (always works) -->
|
|
30
|
+
* <wcc-counter (count-changed)="onCount($event.detail)"></wcc-counter>
|
|
20
31
|
* `
|
|
21
32
|
* })
|
|
22
33
|
*
|
|
23
|
-
* Note: Add the `wccSlots` attribute to any WCC
|
|
34
|
+
* Note: Add the `wccSlots` attribute to any WCC element that uses slots.
|
|
24
35
|
* This is required because Angular AOT cannot evaluate dynamic selectors.
|
|
25
36
|
*
|
|
26
37
|
* @module @sprlab/wccompiler/adapters/angular
|
|
@@ -65,6 +76,19 @@ export declare class WccSlotsDirective implements AfterContentInit, OnDestroy {
|
|
|
65
76
|
private destroyed;
|
|
66
77
|
ngAfterContentInit(): void;
|
|
67
78
|
ngOnDestroy(): void;
|
|
79
|
+
/**
|
|
80
|
+
* Normalizes Angular-style slot attributes to standard HTML slot attributes.
|
|
81
|
+
* Converts: <div slot-header> → <div slot="header">
|
|
82
|
+
*
|
|
83
|
+
* This enables the Angular ng-content select pattern:
|
|
84
|
+
* <wcc-card wccSlots>
|
|
85
|
+
* <nav slot-header>Title</nav>
|
|
86
|
+
* <span slot-footer>Footer</span>
|
|
87
|
+
* </wcc-card>
|
|
88
|
+
*
|
|
89
|
+
* Skips reserved prefixes: slot-props, slot-template-*
|
|
90
|
+
*/
|
|
91
|
+
private normalizeSlotAttributes;
|
|
68
92
|
/** Classifies slots using __scopedSlots from the host element and initializes them */
|
|
69
93
|
private classifyAndInitSlots;
|
|
70
94
|
/** Named Slot: immediate static rendering */
|
|
@@ -1,26 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Angular adapter for WCC Scoped Slots.
|
|
2
|
+
* Angular adapter for WCC Scoped Slots and Event Binding.
|
|
3
3
|
*
|
|
4
4
|
* Exports:
|
|
5
5
|
* - WccSlotDef: Auxiliary directive for ng-template[slot]
|
|
6
6
|
* - WccSlotsDirective: Main directive activated via [wccSlots] attribute
|
|
7
|
+
* - WccEvent: Single-event directive (wccEvent="name" + wccEmit output)
|
|
8
|
+
* - WccEvents: Multi-event bridging directive (kebab-case → camelCase)
|
|
7
9
|
* - SlotContext: Interface for template context typing
|
|
8
10
|
*
|
|
9
11
|
* Usage:
|
|
10
|
-
* import { WccSlotsDirective, WccSlotDef } from '@sprlab/wccompiler/adapters/angular';
|
|
12
|
+
* import { WccSlotsDirective, WccSlotDef, WccEvent, WccEvents } from '@sprlab/wccompiler/adapters/angular';
|
|
11
13
|
*
|
|
12
14
|
* @Component({
|
|
13
|
-
* imports: [WccSlotsDirective, WccSlotDef],
|
|
15
|
+
* imports: [WccSlotsDirective, WccSlotDef, WccEvent, WccEvents],
|
|
14
16
|
* schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
15
17
|
* template: `
|
|
16
18
|
* <wcc-card wccSlots>
|
|
17
19
|
* <ng-template slot="header"><strong>Header</strong></ng-template>
|
|
18
20
|
* <ng-template slot="stats" let-likes>{{ likes }} likes</ng-template>
|
|
19
21
|
* </wcc-card>
|
|
22
|
+
*
|
|
23
|
+
* <!-- Event binding option 1: single event with unwrapped detail -->
|
|
24
|
+
* <wcc-counter wccEvent="count-changed" (wccEmit)="onCount($event)"></wcc-counter>
|
|
25
|
+
*
|
|
26
|
+
* <!-- Event binding option 2: camelCase event names -->
|
|
27
|
+
* <wcc-counter wccEvents (countChanged)="onCount($event.detail)"></wcc-counter>
|
|
28
|
+
*
|
|
29
|
+
* <!-- Event binding option 3: standard Angular (always works) -->
|
|
30
|
+
* <wcc-counter (count-changed)="onCount($event.detail)"></wcc-counter>
|
|
20
31
|
* `
|
|
21
32
|
* })
|
|
22
33
|
*
|
|
23
|
-
* Note: Add the `wccSlots` attribute to any WCC
|
|
34
|
+
* Note: Add the `wccSlots` attribute to any WCC element that uses slots.
|
|
24
35
|
* This is required because Angular AOT cannot evaluate dynamic selectors.
|
|
25
36
|
*
|
|
26
37
|
* @module @sprlab/wccompiler/adapters/angular
|
|
@@ -76,12 +87,42 @@ export class WccSlotsDirective {
|
|
|
76
87
|
// Runtime guard: only proceed for custom elements (tag name contains hyphen)
|
|
77
88
|
if (!this.el.nativeElement.tagName.toLowerCase().includes('-'))
|
|
78
89
|
return;
|
|
90
|
+
// Normalize Angular-style slot attributes: slot-header → slot="header"
|
|
91
|
+
this.normalizeSlotAttributes();
|
|
79
92
|
this.classifyAndInitSlots();
|
|
80
93
|
}
|
|
81
94
|
ngOnDestroy() {
|
|
82
95
|
this.destroyed = true;
|
|
83
96
|
this.cleanup();
|
|
84
97
|
}
|
|
98
|
+
// ─── Slot Attribute Normalization ───────────────────────────────────────
|
|
99
|
+
/**
|
|
100
|
+
* Normalizes Angular-style slot attributes to standard HTML slot attributes.
|
|
101
|
+
* Converts: <div slot-header> → <div slot="header">
|
|
102
|
+
*
|
|
103
|
+
* This enables the Angular ng-content select pattern:
|
|
104
|
+
* <wcc-card wccSlots>
|
|
105
|
+
* <nav slot-header>Title</nav>
|
|
106
|
+
* <span slot-footer>Footer</span>
|
|
107
|
+
* </wcc-card>
|
|
108
|
+
*
|
|
109
|
+
* Skips reserved prefixes: slot-props, slot-template-*
|
|
110
|
+
*/
|
|
111
|
+
normalizeSlotAttributes() {
|
|
112
|
+
const hostEl = this.el.nativeElement;
|
|
113
|
+
for (const child of Array.from(hostEl.children)) {
|
|
114
|
+
for (const attr of Array.from(child.attributes)) {
|
|
115
|
+
if (attr.name.startsWith('slot-') &&
|
|
116
|
+
!attr.value &&
|
|
117
|
+
attr.name !== 'slot-props' &&
|
|
118
|
+
!attr.name.startsWith('slot-template-')) {
|
|
119
|
+
const slotName = attr.name.slice(5); // "slot-header" → "header"
|
|
120
|
+
child.removeAttribute(attr.name);
|
|
121
|
+
child.setAttribute('slot', slotName);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
85
126
|
// ─── Classification ─────────────────────────────────────────────────────
|
|
86
127
|
/** Classifies slots using __scopedSlots from the host element and initializes them */
|
|
87
128
|
async classifyAndInitSlots() {
|
package/adapters/angular.ts
CHANGED
|
@@ -127,6 +127,9 @@ export class WccSlotsDirective implements AfterContentInit, OnDestroy {
|
|
|
127
127
|
// Runtime guard: only proceed for custom elements (tag name contains hyphen)
|
|
128
128
|
if (!this.el.nativeElement.tagName.toLowerCase().includes('-')) return;
|
|
129
129
|
|
|
130
|
+
// Normalize Angular-style slot attributes: slot-header → slot="header"
|
|
131
|
+
this.normalizeSlotAttributes();
|
|
132
|
+
|
|
130
133
|
this.classifyAndInitSlots();
|
|
131
134
|
}
|
|
132
135
|
|
|
@@ -135,6 +138,38 @@ export class WccSlotsDirective implements AfterContentInit, OnDestroy {
|
|
|
135
138
|
this.cleanup();
|
|
136
139
|
}
|
|
137
140
|
|
|
141
|
+
// ─── Slot Attribute Normalization ───────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Normalizes Angular-style slot attributes to standard HTML slot attributes.
|
|
145
|
+
* Converts: <div slot-header> → <div slot="header">
|
|
146
|
+
*
|
|
147
|
+
* This enables the Angular ng-content select pattern:
|
|
148
|
+
* <wcc-card wccSlots>
|
|
149
|
+
* <nav slot-header>Title</nav>
|
|
150
|
+
* <span slot-footer>Footer</span>
|
|
151
|
+
* </wcc-card>
|
|
152
|
+
*
|
|
153
|
+
* Skips reserved prefixes: slot-props, slot-template-*
|
|
154
|
+
*/
|
|
155
|
+
private normalizeSlotAttributes(): void {
|
|
156
|
+
const hostEl = this.el.nativeElement;
|
|
157
|
+
for (const child of Array.from(hostEl.children)) {
|
|
158
|
+
for (const attr of Array.from(child.attributes)) {
|
|
159
|
+
if (
|
|
160
|
+
attr.name.startsWith('slot-') &&
|
|
161
|
+
!attr.value &&
|
|
162
|
+
attr.name !== 'slot-props' &&
|
|
163
|
+
!attr.name.startsWith('slot-template-')
|
|
164
|
+
) {
|
|
165
|
+
const slotName = attr.name.slice(5); // "slot-header" → "header"
|
|
166
|
+
child.removeAttribute(attr.name);
|
|
167
|
+
child.setAttribute('slot', slotName);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
138
173
|
// ─── Classification ─────────────────────────────────────────────────────
|
|
139
174
|
|
|
140
175
|
/** Classifies slots using __scopedSlots from the host element and initializes them */
|
package/bin/wcc.js
CHANGED
|
@@ -80,14 +80,28 @@ function generateFrameworkStubs(outputDir) {
|
|
|
80
80
|
|
|
81
81
|
for (const file of files) {
|
|
82
82
|
const content = readFileSync(join(outputDir, file), 'utf-8');
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
// Match static __meta = { ... }; with balanced braces
|
|
84
|
+
const metaStart = content.indexOf('static __meta = {');
|
|
85
|
+
if (metaStart === -1) continue;
|
|
86
|
+
|
|
87
|
+
// Find the balanced closing brace
|
|
88
|
+
let depth = 0;
|
|
89
|
+
let metaEnd = -1;
|
|
90
|
+
for (let i = metaStart + 'static __meta = '.length; i < content.length; i++) {
|
|
91
|
+
if (content[i] === '{') depth++;
|
|
92
|
+
else if (content[i] === '}') {
|
|
93
|
+
depth--;
|
|
94
|
+
if (depth === 0) { metaEnd = i + 1; break; }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (metaEnd === -1) continue;
|
|
98
|
+
|
|
99
|
+
const metaStr = content.slice(metaStart + 'static __meta = '.length, metaEnd);
|
|
85
100
|
|
|
86
101
|
try {
|
|
87
|
-
const metaStr = '{' + metaMatch[1] + '}';
|
|
88
102
|
const parsed = metaStr
|
|
89
103
|
.replace(/'/g, '"')
|
|
90
|
-
.replace(/(\w+)
|
|
104
|
+
.replace(/(\w+)\s*:/g, '"$1":')
|
|
91
105
|
.replace(/,\s*}/g, '}')
|
|
92
106
|
.replace(/,\s*]/g, ']');
|
|
93
107
|
const meta = JSON.parse(parsed);
|
|
@@ -153,8 +167,10 @@ function generateFrameworkStubs(outputDir) {
|
|
|
153
167
|
|
|
154
168
|
// TypeScript declaration with props/events/slots info
|
|
155
169
|
const propTypes = props.map(p => {
|
|
156
|
-
const
|
|
157
|
-
|
|
170
|
+
const def = String(p.default);
|
|
171
|
+
const type = def === 'true' || def === 'false' ? 'boolean'
|
|
172
|
+
: /^-?\d+(\.\d+)?$/.test(def) ? 'number'
|
|
173
|
+
: 'string';
|
|
158
174
|
return ` ${p.name}?: ${type};`;
|
|
159
175
|
}).join('\n');
|
|
160
176
|
|
|
@@ -164,7 +180,7 @@ function generateFrameworkStubs(outputDir) {
|
|
|
164
180
|
|
|
165
181
|
vueDts += `export declare const ${comp.pascalName}: '${comp.meta.tag}';\n`;
|
|
166
182
|
vueDts += `/** Component: ${comp.meta.tag} */\n`;
|
|
167
|
-
vueDts += `export interface ${comp.pascalName}Props {\n${propTypes}\n}\n`;
|
|
183
|
+
if (props.length) vueDts += `export interface ${comp.pascalName}Props {\n${propTypes}\n}\n`;
|
|
168
184
|
if (events.length) vueDts += `export interface ${comp.pascalName}Events {\n${eventTypes}\n}\n`;
|
|
169
185
|
if (models.length) vueDts += `export interface ${comp.pascalName}Models {\n${modelTypes}\n}\n`;
|
|
170
186
|
if (slots.filter(s => s).length) vueDts += `export interface ${comp.pascalName}Slots {\n${slotTypeEntries}\n}\n`;
|
package/package.json
CHANGED