pulse-js-framework 1.4.10 → 1.5.0
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/cli/analyze.js +8 -7
- package/cli/build.js +14 -13
- package/cli/dev.js +28 -7
- package/cli/format.js +13 -12
- package/cli/index.js +20 -1
- package/cli/lint.js +8 -7
- package/cli/release.js +493 -0
- package/compiler/parser.js +41 -20
- package/compiler/transformer/constants.js +54 -0
- package/compiler/transformer/export.js +33 -0
- package/compiler/transformer/expressions.js +273 -0
- package/compiler/transformer/imports.js +101 -0
- package/compiler/transformer/index.js +319 -0
- package/compiler/transformer/router.js +95 -0
- package/compiler/transformer/state.js +118 -0
- package/compiler/transformer/store.js +97 -0
- package/compiler/transformer/style.js +130 -0
- package/compiler/transformer/view.js +428 -0
- package/compiler/transformer.js +17 -1310
- package/core/errors.js +300 -0
- package/package.json +7 -2
- package/runtime/dom.js +61 -10
- package/runtime/lru-cache.js +145 -0
- package/runtime/native.js +6 -1
- package/runtime/pulse.js +46 -2
- package/runtime/router.js +4 -1
- package/runtime/store.js +35 -1
- package/runtime/utils.js +348 -0
- package/types/index.d.ts +19 -0
- package/types/lru-cache.d.ts +118 -0
- package/types/utils.d.ts +255 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transformer Style Module
|
|
3
|
+
* Handles style block transformation with CSS scoping
|
|
4
|
+
* @module pulse-js-framework/compiler/transformer/style
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Transform style block with optional scoping
|
|
9
|
+
* @param {Object} transformer - Transformer instance
|
|
10
|
+
* @param {Object} styleBlock - Style block from AST
|
|
11
|
+
* @returns {string} JavaScript code
|
|
12
|
+
*/
|
|
13
|
+
export function transformStyle(transformer, styleBlock) {
|
|
14
|
+
const lines = ['// Styles'];
|
|
15
|
+
|
|
16
|
+
if (transformer.scopeId) {
|
|
17
|
+
lines.push(`const SCOPE_ID = '${transformer.scopeId}';`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
lines.push('const styles = `');
|
|
21
|
+
|
|
22
|
+
for (const rule of styleBlock.rules) {
|
|
23
|
+
lines.push(transformStyleRule(transformer, rule, 0));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
lines.push('`;');
|
|
27
|
+
lines.push('');
|
|
28
|
+
lines.push('// Inject styles');
|
|
29
|
+
lines.push('const styleEl = document.createElement("style");');
|
|
30
|
+
|
|
31
|
+
if (transformer.scopeId) {
|
|
32
|
+
lines.push(`styleEl.setAttribute('data-p-scope', SCOPE_ID);`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
lines.push('styleEl.textContent = styles;');
|
|
36
|
+
lines.push('document.head.appendChild(styleEl);');
|
|
37
|
+
|
|
38
|
+
return lines.join('\n');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Transform style rule with optional scoping
|
|
43
|
+
* @param {Object} transformer - Transformer instance
|
|
44
|
+
* @param {Object} rule - CSS rule from AST
|
|
45
|
+
* @param {number} indent - Indentation level
|
|
46
|
+
* @returns {string} CSS code
|
|
47
|
+
*/
|
|
48
|
+
export function transformStyleRule(transformer, rule, indent) {
|
|
49
|
+
const pad = ' '.repeat(indent);
|
|
50
|
+
const lines = [];
|
|
51
|
+
|
|
52
|
+
// Apply scope to selector if enabled
|
|
53
|
+
let selector = rule.selector;
|
|
54
|
+
if (transformer.scopeId) {
|
|
55
|
+
selector = scopeStyleSelector(transformer, selector);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
lines.push(`${pad}${selector} {`);
|
|
59
|
+
|
|
60
|
+
for (const prop of rule.properties) {
|
|
61
|
+
lines.push(`${pad} ${prop.name}: ${prop.value};`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const nested of rule.nestedRules) {
|
|
65
|
+
// For nested rules, combine selectors (simplified nesting)
|
|
66
|
+
const nestedLines = transformStyleRule(transformer, nested, indent + 1);
|
|
67
|
+
lines.push(nestedLines);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
lines.push(`${pad}}`);
|
|
71
|
+
return lines.join('\n');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Add scope to CSS selector
|
|
76
|
+
* .container -> .container.p123abc
|
|
77
|
+
* div -> div.p123abc
|
|
78
|
+
* .a .b -> .a.p123abc .b.p123abc
|
|
79
|
+
* @media (max-width: 900px) -> @media (max-width: 900px) (unchanged)
|
|
80
|
+
* :root, body, *, html -> unchanged (global selectors)
|
|
81
|
+
* @param {Object} transformer - Transformer instance
|
|
82
|
+
* @param {string} selector - CSS selector
|
|
83
|
+
* @returns {string} Scoped selector
|
|
84
|
+
*/
|
|
85
|
+
export function scopeStyleSelector(transformer, selector) {
|
|
86
|
+
if (!transformer.scopeId) return selector;
|
|
87
|
+
|
|
88
|
+
// Don't scope at-rules (media queries, keyframes, etc.)
|
|
89
|
+
if (selector.startsWith('@')) {
|
|
90
|
+
return selector;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Global selectors that should not be scoped
|
|
94
|
+
const globalSelectors = new Set([':root', 'body', 'html', '*']);
|
|
95
|
+
|
|
96
|
+
// Check if entire selector is a global selector (possibly with classes like body.dark)
|
|
97
|
+
const trimmed = selector.trim();
|
|
98
|
+
const baseSelector = trimmed.split(/[.#\[:\s]/)[0];
|
|
99
|
+
if (globalSelectors.has(baseSelector) || globalSelectors.has(trimmed)) {
|
|
100
|
+
return selector;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Split by comma for multiple selectors
|
|
104
|
+
return selector.split(',').map(part => {
|
|
105
|
+
part = part.trim();
|
|
106
|
+
|
|
107
|
+
// Split by space for descendant selectors
|
|
108
|
+
return part.split(/\s+/).map(segment => {
|
|
109
|
+
// Check if this segment is a global selector
|
|
110
|
+
const segmentBase = segment.split(/[.#\[]/)[0];
|
|
111
|
+
if (globalSelectors.has(segmentBase) || globalSelectors.has(segment)) {
|
|
112
|
+
return segment;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Skip pseudo-elements and pseudo-classes at the end
|
|
116
|
+
const pseudoMatch = segment.match(/^([^:]+)(:.+)?$/);
|
|
117
|
+
if (pseudoMatch) {
|
|
118
|
+
const base = pseudoMatch[1];
|
|
119
|
+
const pseudo = pseudoMatch[2] || '';
|
|
120
|
+
|
|
121
|
+
// Skip if it's just a pseudo selector (like :root)
|
|
122
|
+
if (!base || globalSelectors.has(`:${pseudo.slice(1)}`)) return segment;
|
|
123
|
+
|
|
124
|
+
// Add scope class
|
|
125
|
+
return `${base}.${transformer.scopeId}${pseudo}`;
|
|
126
|
+
}
|
|
127
|
+
return `${segment}.${transformer.scopeId}`;
|
|
128
|
+
}).join(' ');
|
|
129
|
+
}).join(', ');
|
|
130
|
+
}
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transformer View Module
|
|
3
|
+
* Handles view block and element transformations
|
|
4
|
+
* @module pulse-js-framework/compiler/transformer/view
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { NodeType } from '../parser.js';
|
|
8
|
+
import { transformValue } from './state.js';
|
|
9
|
+
import { transformExpression, transformExpressionString } from './expressions.js';
|
|
10
|
+
|
|
11
|
+
/** View node transformers lookup table */
|
|
12
|
+
export const VIEW_NODE_HANDLERS = {
|
|
13
|
+
[NodeType.Element]: 'transformElement',
|
|
14
|
+
[NodeType.TextNode]: 'transformTextNode',
|
|
15
|
+
[NodeType.IfDirective]: 'transformIfDirective',
|
|
16
|
+
[NodeType.EachDirective]: 'transformEachDirective',
|
|
17
|
+
[NodeType.EventDirective]: 'transformEventDirective',
|
|
18
|
+
[NodeType.SlotElement]: 'transformSlot',
|
|
19
|
+
[NodeType.LinkDirective]: 'transformLinkDirective',
|
|
20
|
+
[NodeType.OutletDirective]: 'transformOutletDirective',
|
|
21
|
+
[NodeType.NavigateDirective]: 'transformNavigateDirective'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Transform view block
|
|
26
|
+
* @param {Object} transformer - Transformer instance
|
|
27
|
+
* @param {Object} viewBlock - View block from AST
|
|
28
|
+
* @returns {string} JavaScript code
|
|
29
|
+
*/
|
|
30
|
+
export function transformView(transformer, viewBlock) {
|
|
31
|
+
const lines = ['// View'];
|
|
32
|
+
|
|
33
|
+
// Generate render function with props parameter
|
|
34
|
+
lines.push('function render({ props = {}, slots = {} } = {}) {');
|
|
35
|
+
|
|
36
|
+
// Destructure props with defaults if component has props
|
|
37
|
+
if (transformer.propVars.size > 0) {
|
|
38
|
+
const propsDestructure = [...transformer.propVars].map(name => {
|
|
39
|
+
const defaultValue = transformer.propDefaults.get(name);
|
|
40
|
+
const defaultCode = defaultValue ? transformValue(transformer, defaultValue) : 'undefined';
|
|
41
|
+
return `${name} = ${defaultCode}`;
|
|
42
|
+
}).join(', ');
|
|
43
|
+
lines.push(` const { ${propsDestructure} } = props;`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
lines.push(' return (');
|
|
47
|
+
|
|
48
|
+
const children = viewBlock.children.map(child =>
|
|
49
|
+
transformViewNode(transformer, child, 4)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (children.length === 1) {
|
|
53
|
+
lines.push(children[0]);
|
|
54
|
+
} else {
|
|
55
|
+
lines.push(' [');
|
|
56
|
+
lines.push(children.map(c => ' ' + c.trim()).join(',\n'));
|
|
57
|
+
lines.push(' ]');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
lines.push(' );');
|
|
61
|
+
lines.push('}');
|
|
62
|
+
|
|
63
|
+
return lines.join('\n');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Transform a view node (element, directive, slot, text)
|
|
68
|
+
* @param {Object} transformer - Transformer instance
|
|
69
|
+
* @param {Object} node - AST node
|
|
70
|
+
* @param {number} indent - Indentation level
|
|
71
|
+
* @returns {string} JavaScript code
|
|
72
|
+
*/
|
|
73
|
+
export function transformViewNode(transformer, node, indent = 0) {
|
|
74
|
+
const handler = VIEW_NODE_HANDLERS[node.type];
|
|
75
|
+
if (handler) {
|
|
76
|
+
// Call the appropriate handler function
|
|
77
|
+
switch (node.type) {
|
|
78
|
+
case NodeType.Element:
|
|
79
|
+
return transformElement(transformer, node, indent);
|
|
80
|
+
case NodeType.TextNode:
|
|
81
|
+
return transformTextNode(transformer, node, indent);
|
|
82
|
+
case NodeType.IfDirective:
|
|
83
|
+
return transformIfDirective(transformer, node, indent);
|
|
84
|
+
case NodeType.EachDirective:
|
|
85
|
+
return transformEachDirective(transformer, node, indent);
|
|
86
|
+
case NodeType.EventDirective:
|
|
87
|
+
return transformEventDirective(transformer, node, indent);
|
|
88
|
+
case NodeType.SlotElement:
|
|
89
|
+
return transformSlot(transformer, node, indent);
|
|
90
|
+
case NodeType.LinkDirective:
|
|
91
|
+
return transformLinkDirective(transformer, node, indent);
|
|
92
|
+
case NodeType.OutletDirective:
|
|
93
|
+
return transformOutletDirective(transformer, node, indent);
|
|
94
|
+
case NodeType.NavigateDirective:
|
|
95
|
+
return transformNavigateDirective(transformer, node, indent);
|
|
96
|
+
default:
|
|
97
|
+
return `${' '.repeat(indent)}/* unknown node: ${node.type} */`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return `${' '.repeat(indent)}/* unknown node: ${node.type} */`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Transform slot element
|
|
105
|
+
* @param {Object} transformer - Transformer instance
|
|
106
|
+
* @param {Object} node - Slot node
|
|
107
|
+
* @param {number} indent - Indentation level
|
|
108
|
+
* @returns {string} JavaScript code
|
|
109
|
+
*/
|
|
110
|
+
export function transformSlot(transformer, node, indent) {
|
|
111
|
+
const pad = ' '.repeat(indent);
|
|
112
|
+
const slotName = node.name || 'default';
|
|
113
|
+
|
|
114
|
+
// If there's fallback content
|
|
115
|
+
if (node.fallback && node.fallback.length > 0) {
|
|
116
|
+
const fallbackCode = node.fallback.map(child =>
|
|
117
|
+
transformViewNode(transformer, child, indent + 2)
|
|
118
|
+
).join(',\n');
|
|
119
|
+
|
|
120
|
+
return `${pad}(slots?.${slotName} ? slots.${slotName}() : (\n${fallbackCode}\n${pad}))`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Simple slot reference
|
|
124
|
+
return `${pad}(slots?.${slotName} ? slots.${slotName}() : null)`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Transform @link directive
|
|
129
|
+
* @param {Object} transformer - Transformer instance
|
|
130
|
+
* @param {Object} node - Link directive node
|
|
131
|
+
* @param {number} indent - Indentation level
|
|
132
|
+
* @returns {string} JavaScript code
|
|
133
|
+
*/
|
|
134
|
+
export function transformLinkDirective(transformer, node, indent) {
|
|
135
|
+
const pad = ' '.repeat(indent);
|
|
136
|
+
const path = transformExpression(transformer, node.path);
|
|
137
|
+
|
|
138
|
+
let content;
|
|
139
|
+
if (Array.isArray(node.content)) {
|
|
140
|
+
content = node.content.map(c => transformViewNode(transformer, c, 0)).join(', ');
|
|
141
|
+
} else if (node.content) {
|
|
142
|
+
content = transformTextNode(transformer, node.content, 0).trim();
|
|
143
|
+
} else {
|
|
144
|
+
content = '""';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let options = '{}';
|
|
148
|
+
if (node.options) {
|
|
149
|
+
options = transformExpression(transformer, node.options);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return `${pad}router.link(${path}, ${content}, ${options})`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Transform @outlet directive
|
|
157
|
+
* @param {Object} transformer - Transformer instance
|
|
158
|
+
* @param {Object} node - Outlet directive node
|
|
159
|
+
* @param {number} indent - Indentation level
|
|
160
|
+
* @returns {string} JavaScript code
|
|
161
|
+
*/
|
|
162
|
+
export function transformOutletDirective(transformer, node, indent) {
|
|
163
|
+
const pad = ' '.repeat(indent);
|
|
164
|
+
const container = node.container ? `'${node.container}'` : "'#app'";
|
|
165
|
+
return `${pad}router.outlet(${container})`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Transform @navigate directive (used in event handlers)
|
|
170
|
+
* @param {Object} transformer - Transformer instance
|
|
171
|
+
* @param {Object} node - Navigate directive node
|
|
172
|
+
* @param {number} indent - Indentation level
|
|
173
|
+
* @returns {string} JavaScript code
|
|
174
|
+
*/
|
|
175
|
+
export function transformNavigateDirective(transformer, node, indent) {
|
|
176
|
+
const pad = ' '.repeat(indent);
|
|
177
|
+
|
|
178
|
+
// Handle @back and @forward
|
|
179
|
+
if (node.action === 'back') {
|
|
180
|
+
return `${pad}router.back()`;
|
|
181
|
+
}
|
|
182
|
+
if (node.action === 'forward') {
|
|
183
|
+
return `${pad}router.forward()`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Regular @navigate(path)
|
|
187
|
+
const path = transformExpression(transformer, node.path);
|
|
188
|
+
let options = '';
|
|
189
|
+
if (node.options) {
|
|
190
|
+
options = ', ' + transformExpression(transformer, node.options);
|
|
191
|
+
}
|
|
192
|
+
return `${pad}router.navigate(${path}${options})`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Transform element
|
|
197
|
+
* @param {Object} transformer - Transformer instance
|
|
198
|
+
* @param {Object} node - Element node
|
|
199
|
+
* @param {number} indent - Indentation level
|
|
200
|
+
* @returns {string} JavaScript code
|
|
201
|
+
*/
|
|
202
|
+
export function transformElement(transformer, node, indent) {
|
|
203
|
+
const pad = ' '.repeat(indent);
|
|
204
|
+
const parts = [];
|
|
205
|
+
|
|
206
|
+
// Check if this is a component (starts with uppercase)
|
|
207
|
+
const selectorParts = node.selector.match(/^([a-zA-Z][a-zA-Z0-9]*)/);
|
|
208
|
+
const tagName = selectorParts ? selectorParts[1] : '';
|
|
209
|
+
const isComponent = tagName && /^[A-Z]/.test(tagName) &&
|
|
210
|
+
transformer.importedComponents.has(tagName);
|
|
211
|
+
|
|
212
|
+
if (isComponent) {
|
|
213
|
+
// Render as component call
|
|
214
|
+
return transformComponentCall(transformer, node, indent);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Add scoped class to selector if CSS scoping is enabled
|
|
218
|
+
let selector = node.selector;
|
|
219
|
+
if (transformer.scopeId && selector) {
|
|
220
|
+
selector = addScopeToSelector(transformer, selector);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Start with el() call
|
|
224
|
+
parts.push(`${pad}el('${selector}'`);
|
|
225
|
+
|
|
226
|
+
// Add event handlers as on() chain
|
|
227
|
+
const eventHandlers = node.directives.filter(d => d.type === NodeType.EventDirective);
|
|
228
|
+
|
|
229
|
+
// Add text content
|
|
230
|
+
if (node.textContent.length > 0) {
|
|
231
|
+
for (const text of node.textContent) {
|
|
232
|
+
const textCode = transformTextNode(transformer, text, 0);
|
|
233
|
+
parts.push(`,\n${pad} ${textCode.trim()}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Add children
|
|
238
|
+
if (node.children.length > 0) {
|
|
239
|
+
for (const child of node.children) {
|
|
240
|
+
const childCode = transformViewNode(transformer, child, indent + 2);
|
|
241
|
+
parts.push(`,\n${childCode}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
parts.push(')');
|
|
246
|
+
|
|
247
|
+
// Chain event handlers
|
|
248
|
+
let result = parts.join('');
|
|
249
|
+
for (const handler of eventHandlers) {
|
|
250
|
+
const handlerCode = transformExpression(transformer, handler.handler);
|
|
251
|
+
result = `on(${result}, '${handler.event}', () => { ${handlerCode}; })`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Add scope class to a CSS selector
|
|
259
|
+
* @param {Object} transformer - Transformer instance
|
|
260
|
+
* @param {string} selector - CSS selector
|
|
261
|
+
* @returns {string} Scoped selector
|
|
262
|
+
*/
|
|
263
|
+
export function addScopeToSelector(transformer, selector) {
|
|
264
|
+
// If selector has classes, add scope class after the first class
|
|
265
|
+
// Otherwise add it at the end
|
|
266
|
+
if (selector.includes('.')) {
|
|
267
|
+
// Add scope after tag name and before first class
|
|
268
|
+
return selector.replace(/^([a-zA-Z0-9-]*)/, `$1.${transformer.scopeId}`);
|
|
269
|
+
}
|
|
270
|
+
// Just a tag name, add scope class
|
|
271
|
+
return `${selector}.${transformer.scopeId}`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Transform a component call (imported component)
|
|
276
|
+
* @param {Object} transformer - Transformer instance
|
|
277
|
+
* @param {Object} node - Element node (component)
|
|
278
|
+
* @param {number} indent - Indentation level
|
|
279
|
+
* @returns {string} JavaScript code
|
|
280
|
+
*/
|
|
281
|
+
export function transformComponentCall(transformer, node, indent) {
|
|
282
|
+
const pad = ' '.repeat(indent);
|
|
283
|
+
const selectorParts = node.selector.match(/^([a-zA-Z][a-zA-Z0-9]*)/);
|
|
284
|
+
const componentName = selectorParts[1];
|
|
285
|
+
|
|
286
|
+
// Extract slots from children
|
|
287
|
+
const slots = {};
|
|
288
|
+
|
|
289
|
+
// Children become the default slot
|
|
290
|
+
if (node.children.length > 0 || node.textContent.length > 0) {
|
|
291
|
+
const slotContent = [];
|
|
292
|
+
for (const text of node.textContent) {
|
|
293
|
+
slotContent.push(transformTextNode(transformer, text, 0).trim());
|
|
294
|
+
}
|
|
295
|
+
for (const child of node.children) {
|
|
296
|
+
slotContent.push(transformViewNode(transformer, child, 0).trim());
|
|
297
|
+
}
|
|
298
|
+
slots['default'] = slotContent;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Build component call
|
|
302
|
+
let code = `${pad}${componentName}.render({ `;
|
|
303
|
+
|
|
304
|
+
const renderArgs = [];
|
|
305
|
+
|
|
306
|
+
// Add props if any
|
|
307
|
+
if (node.props && node.props.length > 0) {
|
|
308
|
+
const propsCode = node.props.map(prop => {
|
|
309
|
+
const valueCode = transformExpression(transformer, prop.value);
|
|
310
|
+
return `${prop.name}: ${valueCode}`;
|
|
311
|
+
}).join(', ');
|
|
312
|
+
renderArgs.push(`props: { ${propsCode} }`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Add slots if any
|
|
316
|
+
if (Object.keys(slots).length > 0) {
|
|
317
|
+
const slotCode = Object.entries(slots).map(([name, content]) => {
|
|
318
|
+
return `${name}: () => ${content.length === 1 ? content[0] : `[${content.join(', ')}]`}`;
|
|
319
|
+
}).join(', ');
|
|
320
|
+
renderArgs.push(`slots: { ${slotCode} }`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
code += renderArgs.join(', ');
|
|
324
|
+
code += ' })';
|
|
325
|
+
return code;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Transform text node
|
|
330
|
+
* @param {Object} transformer - Transformer instance
|
|
331
|
+
* @param {Object} node - Text node
|
|
332
|
+
* @param {number} indent - Indentation level
|
|
333
|
+
* @returns {string} JavaScript code
|
|
334
|
+
*/
|
|
335
|
+
export function transformTextNode(transformer, node, indent) {
|
|
336
|
+
const pad = ' '.repeat(indent);
|
|
337
|
+
const parts = node.parts;
|
|
338
|
+
|
|
339
|
+
if (parts.length === 1 && typeof parts[0] === 'string') {
|
|
340
|
+
// Simple static text
|
|
341
|
+
return `${pad}${JSON.stringify(parts[0])}`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Has interpolations - use text() with a function
|
|
345
|
+
const textParts = parts.map(part => {
|
|
346
|
+
if (typeof part === 'string') {
|
|
347
|
+
return JSON.stringify(part);
|
|
348
|
+
}
|
|
349
|
+
// Interpolation
|
|
350
|
+
const expr = transformExpressionString(transformer, part.expression);
|
|
351
|
+
return `\${${expr}}`;
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
return `${pad}text(() => \`${textParts.join('')}\`)`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Transform @if directive
|
|
359
|
+
* @param {Object} transformer - Transformer instance
|
|
360
|
+
* @param {Object} node - If directive node
|
|
361
|
+
* @param {number} indent - Indentation level
|
|
362
|
+
* @returns {string} JavaScript code
|
|
363
|
+
*/
|
|
364
|
+
export function transformIfDirective(transformer, node, indent) {
|
|
365
|
+
const pad = ' '.repeat(indent);
|
|
366
|
+
const condition = transformExpression(transformer, node.condition);
|
|
367
|
+
|
|
368
|
+
const consequent = node.consequent.map(c =>
|
|
369
|
+
transformViewNode(transformer, c, indent + 2)
|
|
370
|
+
).join(',\n');
|
|
371
|
+
|
|
372
|
+
let code = `${pad}when(\n`;
|
|
373
|
+
code += `${pad} () => ${condition},\n`;
|
|
374
|
+
code += `${pad} () => (\n${consequent}\n${pad} )`;
|
|
375
|
+
|
|
376
|
+
if (node.alternate) {
|
|
377
|
+
const alternate = node.alternate.map(c =>
|
|
378
|
+
transformViewNode(transformer, c, indent + 2)
|
|
379
|
+
).join(',\n');
|
|
380
|
+
code += `,\n${pad} () => (\n${alternate}\n${pad} )`;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
code += `\n${pad})`;
|
|
384
|
+
return code;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Transform @each directive
|
|
389
|
+
* @param {Object} transformer - Transformer instance
|
|
390
|
+
* @param {Object} node - Each directive node
|
|
391
|
+
* @param {number} indent - Indentation level
|
|
392
|
+
* @returns {string} JavaScript code
|
|
393
|
+
*/
|
|
394
|
+
export function transformEachDirective(transformer, node, indent) {
|
|
395
|
+
const pad = ' '.repeat(indent);
|
|
396
|
+
const iterable = transformExpression(transformer, node.iterable);
|
|
397
|
+
|
|
398
|
+
const template = node.template.map(t =>
|
|
399
|
+
transformViewNode(transformer, t, indent + 2)
|
|
400
|
+
).join(',\n');
|
|
401
|
+
|
|
402
|
+
return `${pad}list(\n` +
|
|
403
|
+
`${pad} () => ${iterable},\n` +
|
|
404
|
+
`${pad} (${node.itemName}, _index) => (\n${template}\n${pad} )\n` +
|
|
405
|
+
`${pad})`;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Transform event directive
|
|
410
|
+
* @param {Object} transformer - Transformer instance
|
|
411
|
+
* @param {Object} node - Event directive node
|
|
412
|
+
* @param {number} indent - Indentation level
|
|
413
|
+
* @returns {string} JavaScript code
|
|
414
|
+
*/
|
|
415
|
+
export function transformEventDirective(transformer, node, indent) {
|
|
416
|
+
const pad = ' '.repeat(indent);
|
|
417
|
+
const handler = transformExpression(transformer, node.handler);
|
|
418
|
+
|
|
419
|
+
if (node.children && node.children.length > 0) {
|
|
420
|
+
const children = node.children.map(c =>
|
|
421
|
+
transformViewNode(transformer, c, indent + 2)
|
|
422
|
+
).join(',\n');
|
|
423
|
+
|
|
424
|
+
return `${pad}on(el('div',\n${children}\n${pad}), '${node.event}', () => { ${handler}; })`;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return `/* event: ${node.event} -> ${handler} */`;
|
|
428
|
+
}
|