@zenithbuild/core 1.2.1 → 1.2.3
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 +20 -19
- package/cli/commands/add.ts +2 -2
- package/cli/commands/build.ts +2 -3
- package/cli/commands/dev.ts +93 -73
- package/cli/commands/index.ts +1 -1
- package/cli/commands/preview.ts +1 -1
- package/cli/commands/remove.ts +2 -2
- package/cli/index.ts +1 -1
- package/cli/main.ts +1 -1
- package/cli/utils/logger.ts +1 -1
- package/cli/utils/plugin-manager.ts +1 -1
- package/cli/utils/project.ts +4 -4
- package/core/components/ErrorPage.zen +218 -0
- package/core/components/index.ts +15 -0
- package/core/config.ts +1 -0
- package/core/index.ts +29 -0
- package/dist/compiler-native-frej59m4.node +0 -0
- package/dist/core/compiler-native-frej59m4.node +0 -0
- package/dist/core/index.js +6293 -0
- package/dist/runtime/lifecycle/index.js +1 -0
- package/dist/runtime/reactivity/index.js +1 -0
- package/dist/zen-build.js +1 -20118
- package/dist/zen-dev.js +1 -20118
- package/dist/zen-preview.js +1 -20118
- package/dist/zenith.js +1 -20118
- package/package.json +12 -21
- package/compiler/README.md +0 -380
- package/compiler/build-analyzer.ts +0 -122
- package/compiler/css/index.ts +0 -317
- package/compiler/discovery/componentDiscovery.ts +0 -242
- package/compiler/discovery/layouts.ts +0 -70
- package/compiler/errors/compilerError.ts +0 -56
- package/compiler/finalize/finalizeOutput.ts +0 -192
- package/compiler/finalize/generateFinalBundle.ts +0 -82
- package/compiler/index.ts +0 -83
- package/compiler/ir/types.ts +0 -174
- package/compiler/output/types.ts +0 -48
- package/compiler/parse/detectMapExpressions.ts +0 -102
- package/compiler/parse/importTypes.ts +0 -78
- package/compiler/parse/parseImports.ts +0 -309
- package/compiler/parse/parseScript.ts +0 -46
- package/compiler/parse/parseTemplate.ts +0 -628
- package/compiler/parse/parseZenFile.ts +0 -66
- package/compiler/parse/scriptAnalysis.ts +0 -91
- package/compiler/parse/trackLoopContext.ts +0 -82
- package/compiler/runtime/dataExposure.ts +0 -332
- package/compiler/runtime/generateDOM.ts +0 -255
- package/compiler/runtime/generateHydrationBundle.ts +0 -407
- package/compiler/runtime/hydration.ts +0 -309
- package/compiler/runtime/navigation.ts +0 -432
- package/compiler/runtime/thinRuntime.ts +0 -160
- package/compiler/runtime/transformIR.ts +0 -406
- package/compiler/runtime/wrapExpression.ts +0 -114
- package/compiler/runtime/wrapExpressionWithLoop.ts +0 -97
- package/compiler/spa-build.ts +0 -917
- package/compiler/ssg-build.ts +0 -486
- package/compiler/test/component-stacking.test.ts +0 -365
- package/compiler/test/validate-test.ts +0 -104
- package/compiler/transform/classifyExpression.ts +0 -444
- package/compiler/transform/componentResolver.ts +0 -350
- package/compiler/transform/componentScriptTransformer.ts +0 -303
- package/compiler/transform/expressionTransformer.ts +0 -385
- package/compiler/transform/fragmentLowering.ts +0 -692
- package/compiler/transform/generateBindings.ts +0 -68
- package/compiler/transform/generateHTML.ts +0 -28
- package/compiler/transform/layoutProcessor.ts +0 -132
- package/compiler/transform/slotResolver.ts +0 -292
- package/compiler/transform/transformNode.ts +0 -240
- package/compiler/transform/transformTemplate.ts +0 -38
- package/compiler/validate/invariants.ts +0 -292
- package/compiler/validate/validateExpressions.ts +0 -168
- package/core/config/index.ts +0 -18
- package/core/config/loader.ts +0 -69
- package/core/config/types.ts +0 -119
- package/core/plugins/bridge.ts +0 -193
- package/core/plugins/index.ts +0 -7
- package/core/plugins/registry.ts +0 -126
- package/dist/cli.js +0 -11675
- package/runtime/build.ts +0 -17
- package/runtime/bundle-generator.ts +0 -1266
- package/runtime/client-runtime.ts +0 -891
- package/runtime/serve.ts +0 -93
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DOM Generation
|
|
3
|
-
*
|
|
4
|
-
* Generates JavaScript code that creates DOM elements from template nodes
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type {
|
|
8
|
-
TemplateNode,
|
|
9
|
-
ElementNode,
|
|
10
|
-
TextNode,
|
|
11
|
-
ExpressionNode,
|
|
12
|
-
ExpressionIR,
|
|
13
|
-
ConditionalFragmentNode,
|
|
14
|
-
OptionalFragmentNode,
|
|
15
|
-
LoopFragmentNode
|
|
16
|
-
} from '../ir/types'
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Generate DOM creation code from a template node
|
|
20
|
-
* Returns JavaScript code that creates and returns a DOM element or text node
|
|
21
|
-
*/
|
|
22
|
-
export function generateDOMCode(
|
|
23
|
-
node: TemplateNode,
|
|
24
|
-
expressions: ExpressionIR[],
|
|
25
|
-
indent: string = ' ',
|
|
26
|
-
varCounter: { count: number } = { count: 0 }
|
|
27
|
-
): { code: string; varName: string } {
|
|
28
|
-
const varName = `node_${varCounter.count++}`
|
|
29
|
-
|
|
30
|
-
switch (node.type) {
|
|
31
|
-
case 'text': {
|
|
32
|
-
const textNode = node as TextNode
|
|
33
|
-
const escapedValue = JSON.stringify(textNode.value)
|
|
34
|
-
return {
|
|
35
|
-
code: `${indent}const ${varName} = document.createTextNode(${escapedValue});`,
|
|
36
|
-
varName
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
case 'expression': {
|
|
41
|
-
const exprNode = node as ExpressionNode
|
|
42
|
-
const expr = expressions.find(e => e.id === exprNode.expression)
|
|
43
|
-
if (!expr) {
|
|
44
|
-
throw new Error(`Expression ${exprNode.expression} not found`)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Create a span element to hold the expression result
|
|
48
|
-
return {
|
|
49
|
-
code: `${indent}const ${varName} = document.createElement('span');
|
|
50
|
-
${indent}${varName}.textContent = String(${expr.id}(state) ?? '');
|
|
51
|
-
${indent}${varName}.setAttribute('data-zen-expr', '${exprNode.expression}');`,
|
|
52
|
-
varName
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
case 'element': {
|
|
57
|
-
const elNode = node as ElementNode
|
|
58
|
-
const tag = elNode.tag
|
|
59
|
-
|
|
60
|
-
let code = `${indent}const ${varName} = document.createElement('${tag}');\n`
|
|
61
|
-
|
|
62
|
-
// Handle attributes
|
|
63
|
-
for (const attr of elNode.attributes) {
|
|
64
|
-
if (typeof attr.value === 'string') {
|
|
65
|
-
// Static attribute
|
|
66
|
-
const escapedValue = JSON.stringify(attr.value)
|
|
67
|
-
code += `${indent}${varName}.setAttribute('${attr.name}', ${escapedValue});\n`
|
|
68
|
-
} else {
|
|
69
|
-
// Expression attribute
|
|
70
|
-
const expr = attr.value as ExpressionIR
|
|
71
|
-
const attrName = attr.name === 'className' ? 'class' : attr.name
|
|
72
|
-
|
|
73
|
-
// Handle special attributes
|
|
74
|
-
if (attrName === 'class' || attrName === 'className') {
|
|
75
|
-
code += `${indent}${varName}.className = String(${expr.id}(state) ?? '');\n`
|
|
76
|
-
} else if (attrName === 'style') {
|
|
77
|
-
code += `${indent}const styleValue_${varCounter.count} = ${expr.id}(state);
|
|
78
|
-
${indent}if (typeof styleValue_${varCounter.count} === 'string') {
|
|
79
|
-
${indent} ${varName}.style.cssText = styleValue_${varCounter.count};
|
|
80
|
-
${indent}}\n`
|
|
81
|
-
} else if (attrName.startsWith('on')) {
|
|
82
|
-
// Event handler - store handler name/id, will be bound later
|
|
83
|
-
const eventType = attrName.slice(2).toLowerCase() // Remove 'on' prefix
|
|
84
|
-
const value = typeof attr.value === 'string' ? attr.value : (attr.value as ExpressionIR).id
|
|
85
|
-
code += `${indent}${varName}.setAttribute('data-zen-${eventType}', ${JSON.stringify(value)});\n`
|
|
86
|
-
} else {
|
|
87
|
-
const tempVar = `attr_${varCounter.count++}`
|
|
88
|
-
code += `${indent}const ${tempVar} = ${expr.id}(state);
|
|
89
|
-
${indent}if (${tempVar} != null && ${tempVar} !== false) {
|
|
90
|
-
${indent} ${varName}.setAttribute('${attrName}', String(${tempVar}));
|
|
91
|
-
${indent}}\n`
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Handle children
|
|
97
|
-
if (elNode.children.length > 0) {
|
|
98
|
-
for (const child of elNode.children) {
|
|
99
|
-
const childResult = generateDOMCode(child, expressions, indent, varCounter)
|
|
100
|
-
code += `${childResult.code}\n`
|
|
101
|
-
code += `${indent}${varName}.appendChild(${childResult.varName});\n`
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return { code, varName }
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
case 'component': {
|
|
109
|
-
// Components should be resolved before reaching DOM generation
|
|
110
|
-
// If we get here, it means component resolution failed
|
|
111
|
-
throw new Error(`[Zenith] Unresolved component: ${(node as any).name}. Components must be resolved before DOM generation.`)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
case 'conditional-fragment': {
|
|
115
|
-
// Conditional fragment: {condition ? <A /> : <B />}
|
|
116
|
-
// Both branches are precompiled, runtime toggles visibility
|
|
117
|
-
const condNode = node as ConditionalFragmentNode
|
|
118
|
-
const containerVar = varName
|
|
119
|
-
const conditionId = `cond_${varCounter.count++}`
|
|
120
|
-
|
|
121
|
-
let code = `${indent}const ${containerVar} = document.createDocumentFragment();\n`
|
|
122
|
-
// Evaluate condition with state context using new Function (sloppy mode allows 'with')
|
|
123
|
-
const escapedCondition = condNode.condition.replace(/\\/g, '\\\\').replace(/'/g, "\\'")
|
|
124
|
-
code += `${indent}const ${conditionId}_evalFn = new Function('state', 'with (state) { return (' + '${escapedCondition}' + '); }');\n`
|
|
125
|
-
code += `${indent}const ${conditionId}_result = (function() { try { return ${conditionId}_evalFn(state); } catch(e) { return false; } })();\n`
|
|
126
|
-
|
|
127
|
-
// Generate consequent branch
|
|
128
|
-
code += `${indent}if (${conditionId}_result) {\n`
|
|
129
|
-
for (const child of condNode.consequent) {
|
|
130
|
-
const childResult = generateDOMCode(child, expressions, indent + ' ', varCounter)
|
|
131
|
-
code += `${childResult.code}\n`
|
|
132
|
-
code += `${indent} ${containerVar}.appendChild(${childResult.varName});\n`
|
|
133
|
-
}
|
|
134
|
-
code += `${indent}} else {\n`
|
|
135
|
-
|
|
136
|
-
// Generate alternate branch
|
|
137
|
-
for (const child of condNode.alternate) {
|
|
138
|
-
const childResult = generateDOMCode(child, expressions, indent + ' ', varCounter)
|
|
139
|
-
code += `${childResult.code}\n`
|
|
140
|
-
code += `${indent} ${containerVar}.appendChild(${childResult.varName});\n`
|
|
141
|
-
}
|
|
142
|
-
code += `${indent}}\n`
|
|
143
|
-
|
|
144
|
-
return { code, varName: containerVar }
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
case 'optional-fragment': {
|
|
148
|
-
// Optional fragment: {condition && <A />}
|
|
149
|
-
// Fragment is precompiled, runtime mounts/unmounts based on condition
|
|
150
|
-
const optNode = node as OptionalFragmentNode
|
|
151
|
-
const containerVar = varName
|
|
152
|
-
const conditionId = `opt_${varCounter.count++}`
|
|
153
|
-
|
|
154
|
-
let code = `${indent}const ${containerVar} = document.createDocumentFragment();\n`
|
|
155
|
-
// Evaluate condition with state context using new Function (sloppy mode allows 'with')
|
|
156
|
-
const escapedCondition = optNode.condition.replace(/\\/g, '\\\\').replace(/'/g, "\\'")
|
|
157
|
-
code += `${indent}const ${conditionId}_evalFn = new Function('state', 'with (state) { return (' + '${escapedCondition}' + '); }');\n`
|
|
158
|
-
code += `${indent}const ${conditionId}_result = (function() { try { return ${conditionId}_evalFn(state); } catch(e) { return false; } })();\n`
|
|
159
|
-
code += `${indent}if (${conditionId}_result) {\n`
|
|
160
|
-
|
|
161
|
-
for (const child of optNode.fragment) {
|
|
162
|
-
const childResult = generateDOMCode(child, expressions, indent + ' ', varCounter)
|
|
163
|
-
code += `${childResult.code}\n`
|
|
164
|
-
code += `${indent} ${containerVar}.appendChild(${childResult.varName});\n`
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
code += `${indent}}\n`
|
|
168
|
-
|
|
169
|
-
return { code, varName: containerVar }
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
case 'loop-fragment': {
|
|
173
|
-
// Loop fragment: {items.map(item => <li>...</li>)}
|
|
174
|
-
// Body is precompiled once, instantiated per item at runtime
|
|
175
|
-
const loopNode = node as LoopFragmentNode
|
|
176
|
-
const containerVar = varName
|
|
177
|
-
const loopId = `loop_${varCounter.count++}`
|
|
178
|
-
|
|
179
|
-
let code = `${indent}const ${containerVar} = document.createDocumentFragment();\n`
|
|
180
|
-
// Evaluate loop source with state context using new Function (sloppy mode allows 'with')
|
|
181
|
-
const escapedSource = loopNode.source.replace(/\\/g, '\\\\').replace(/'/g, "\\'")
|
|
182
|
-
code += `${indent}const ${loopId}_evalFn = new Function('state', 'with (state) { return (' + '${escapedSource}' + '); }');\n`
|
|
183
|
-
code += `${indent}const ${loopId}_items = (function() { try { return ${loopId}_evalFn(state); } catch(e) { return []; } })() || [];\n`
|
|
184
|
-
|
|
185
|
-
// Loop parameters
|
|
186
|
-
const itemVar = loopNode.itemVar
|
|
187
|
-
const indexVar = loopNode.indexVar || `${loopId}_idx`
|
|
188
|
-
|
|
189
|
-
code += `${indent}${loopId}_items.forEach(function(${itemVar}, ${indexVar}) {\n`
|
|
190
|
-
|
|
191
|
-
// Generate loop body with loop context variables in scope
|
|
192
|
-
for (const child of loopNode.body) {
|
|
193
|
-
const childResult = generateDOMCode(child, expressions, indent + ' ', varCounter)
|
|
194
|
-
// Inject loop variables into the child code
|
|
195
|
-
let childCode = childResult.code
|
|
196
|
-
code += `${childCode}\n`
|
|
197
|
-
code += `${indent} ${containerVar}.appendChild(${childResult.varName});\n`
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
code += `${indent}});\n`
|
|
201
|
-
|
|
202
|
-
return { code, varName: containerVar }
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
default: {
|
|
206
|
-
throw new Error(`[Zenith] Unknown node type: ${(node as any).type}`)
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Generate DOM creation code for multiple nodes
|
|
213
|
-
* Returns a function that creates DOM elements
|
|
214
|
-
*/
|
|
215
|
-
export function generateDOMFunction(
|
|
216
|
-
nodes: TemplateNode[],
|
|
217
|
-
expressions: ExpressionIR[],
|
|
218
|
-
functionName: string = 'renderTemplate'
|
|
219
|
-
): string {
|
|
220
|
-
if (nodes.length === 0) {
|
|
221
|
-
return `function ${functionName}(state) {
|
|
222
|
-
const fragment = document.createDocumentFragment();
|
|
223
|
-
return fragment;
|
|
224
|
-
}`
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const varCounter = { count: 0 }
|
|
228
|
-
let code = `function ${functionName}(state) {
|
|
229
|
-
`
|
|
230
|
-
|
|
231
|
-
if (nodes.length === 1) {
|
|
232
|
-
const node = nodes[0]
|
|
233
|
-
if (!node) {
|
|
234
|
-
throw new Error('Empty nodes array passed to generateDOMFunction')
|
|
235
|
-
}
|
|
236
|
-
const result = generateDOMCode(node, expressions, ' ', varCounter)
|
|
237
|
-
code += result.code
|
|
238
|
-
code += `\n return ${result.varName};\n}`
|
|
239
|
-
return code
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Multiple nodes - create a fragment
|
|
243
|
-
code += ` const fragment = document.createDocumentFragment();\n`
|
|
244
|
-
|
|
245
|
-
for (const node of nodes) {
|
|
246
|
-
const result = generateDOMCode(node, expressions, ' ', varCounter)
|
|
247
|
-
code += `${result.code}\n`
|
|
248
|
-
code += ` fragment.appendChild(${result.varName});\n`
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
code += ` return fragment;
|
|
252
|
-
}`
|
|
253
|
-
|
|
254
|
-
return code
|
|
255
|
-
}
|
|
@@ -1,407 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generate Hydration Bundle
|
|
3
|
-
*
|
|
4
|
-
* Phase 5: Generates the complete runtime bundle including expressions and hydration code
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { ExpressionIR } from '../ir/types'
|
|
8
|
-
import { wrapExpression } from './wrapExpression'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Generate the hydration runtime code as a string
|
|
12
|
-
* This is the browser-side runtime that hydrates DOM placeholders
|
|
13
|
-
*/
|
|
14
|
-
export function generateHydrationRuntime(): string {
|
|
15
|
-
return `
|
|
16
|
-
// Zenith Runtime Hydration Layer (Phase 5)
|
|
17
|
-
(function() {
|
|
18
|
-
'use strict';
|
|
19
|
-
|
|
20
|
-
// Expression registry - maps expression IDs to their evaluation functions
|
|
21
|
-
if (typeof window !== 'undefined' && !window.__ZENITH_EXPRESSIONS__) {
|
|
22
|
-
window.__ZENITH_EXPRESSIONS__ = new Map();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Binding registry - tracks which DOM nodes are bound to which expressions
|
|
26
|
-
const __zen_bindings = [];
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Update a text binding
|
|
30
|
-
* Phase 6: Accepts explicit data arguments
|
|
31
|
-
*/
|
|
32
|
-
function updateTextBinding(node, expressionId, state, loaderData, props, stores) {
|
|
33
|
-
try {
|
|
34
|
-
const expression = window.__ZENITH_EXPRESSIONS__.get(expressionId);
|
|
35
|
-
if (!expression) {
|
|
36
|
-
console.warn('[Zenith] Expression ' + expressionId + ' not found in registry');
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Call expression with appropriate arguments based on function length
|
|
41
|
-
const result = expression.length === 1
|
|
42
|
-
? expression(state) // Legacy: state only
|
|
43
|
-
: expression(state, loaderData, props, stores); // Phase 6: explicit arguments
|
|
44
|
-
|
|
45
|
-
// Handle different result types
|
|
46
|
-
if (result === null || result === undefined || result === false) {
|
|
47
|
-
node.textContent = '';
|
|
48
|
-
} else if (typeof result === 'string') {
|
|
49
|
-
if (result.trim().startsWith('<')) {
|
|
50
|
-
// Render as HTML
|
|
51
|
-
node.innerHTML = result;
|
|
52
|
-
} else {
|
|
53
|
-
node.textContent = result;
|
|
54
|
-
}
|
|
55
|
-
} else if (typeof result === 'number') {
|
|
56
|
-
node.textContent = String(result);
|
|
57
|
-
} else if (result instanceof Node) {
|
|
58
|
-
// Clear node and append result
|
|
59
|
-
node.innerHTML = '';
|
|
60
|
-
node.appendChild(result);
|
|
61
|
-
} else if (Array.isArray(result)) {
|
|
62
|
-
// Handle array results (for map expressions)
|
|
63
|
-
node.innerHTML = '';
|
|
64
|
-
const fragment = document.createDocumentFragment();
|
|
65
|
-
for (let i = 0; i < result.length; i++) {
|
|
66
|
-
const item = result[i];
|
|
67
|
-
if (item instanceof Node) {
|
|
68
|
-
fragment.appendChild(item);
|
|
69
|
-
} else {
|
|
70
|
-
fragment.appendChild(document.createTextNode(String(item)));
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
node.appendChild(fragment);
|
|
74
|
-
} else {
|
|
75
|
-
node.textContent = String(result);
|
|
76
|
-
}
|
|
77
|
-
} catch (error) {
|
|
78
|
-
console.error('[Zenith] Error evaluating expression ' + expressionId + ':', error);
|
|
79
|
-
console.error('Expression ID:', expressionId, 'State:', state);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Update an attribute binding
|
|
85
|
-
* Phase 6: Accepts explicit data arguments
|
|
86
|
-
*/
|
|
87
|
-
function updateAttributeBinding(element, attributeName, expressionId, state, loaderData, props, stores) {
|
|
88
|
-
try {
|
|
89
|
-
const expression = window.__ZENITH_EXPRESSIONS__.get(expressionId);
|
|
90
|
-
if (!expression) {
|
|
91
|
-
console.warn('[Zenith] Expression ' + expressionId + ' not found in registry');
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Call expression with appropriate arguments based on function length
|
|
96
|
-
const result = expression.length === 1
|
|
97
|
-
? expression(state) // Legacy: state only
|
|
98
|
-
: expression(state, loaderData, props, stores); // Phase 6: explicit arguments
|
|
99
|
-
|
|
100
|
-
// Handle different attribute types
|
|
101
|
-
if (attributeName === 'class' || attributeName === 'className') {
|
|
102
|
-
element.className = String(result != null ? result : '');
|
|
103
|
-
} else if (attributeName === 'style') {
|
|
104
|
-
if (typeof result === 'string') {
|
|
105
|
-
element.setAttribute('style', result);
|
|
106
|
-
} else if (result && typeof result === 'object') {
|
|
107
|
-
// Handle style object
|
|
108
|
-
const styleStr = Object.keys(result).map(function(key) {
|
|
109
|
-
return key + ': ' + result[key];
|
|
110
|
-
}).join('; ');
|
|
111
|
-
element.setAttribute('style', styleStr);
|
|
112
|
-
}
|
|
113
|
-
} else if (attributeName === 'disabled' || attributeName === 'checked' || attributeName === 'readonly') {
|
|
114
|
-
// Boolean attributes
|
|
115
|
-
if (result) {
|
|
116
|
-
element.setAttribute(attributeName, '');
|
|
117
|
-
} else {
|
|
118
|
-
element.removeAttribute(attributeName);
|
|
119
|
-
}
|
|
120
|
-
} else {
|
|
121
|
-
// Regular attributes
|
|
122
|
-
if (result === null || result === undefined || result === false) {
|
|
123
|
-
element.removeAttribute(attributeName);
|
|
124
|
-
} else {
|
|
125
|
-
element.setAttribute(attributeName, String(result));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
} catch (error) {
|
|
129
|
-
console.error('[Zenith] Error updating attribute ' + attributeName + ' with expression ' + expressionId + ':', error);
|
|
130
|
-
console.error('Expression ID:', expressionId, 'State:', state);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Hydrate static HTML with dynamic expressions
|
|
136
|
-
* Phase 6: Accepts explicit loaderData, props, stores arguments
|
|
137
|
-
*/
|
|
138
|
-
function hydrate(state, loaderData, props, stores, container) {
|
|
139
|
-
if (!state) {
|
|
140
|
-
console.warn('[Zenith] hydrate called without state object');
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Handle optional arguments (backwards compatibility)
|
|
145
|
-
if (typeof container === 'undefined' && typeof stores === 'object' && stores && !stores.nodeType) {
|
|
146
|
-
// Called as hydrate(state, loaderData, props, stores, container)
|
|
147
|
-
container = document;
|
|
148
|
-
} else if (typeof props === 'object' && props && !props.nodeType && typeof stores === 'undefined') {
|
|
149
|
-
// Called as hydrate(state, loaderData, props) - container is props
|
|
150
|
-
container = props;
|
|
151
|
-
props = loaderData;
|
|
152
|
-
loaderData = undefined;
|
|
153
|
-
stores = undefined;
|
|
154
|
-
} else if (typeof loaderData === 'object' && loaderData && loaderData.nodeType) {
|
|
155
|
-
// Called as hydrate(state, container) - legacy signature
|
|
156
|
-
container = loaderData;
|
|
157
|
-
loaderData = undefined;
|
|
158
|
-
props = undefined;
|
|
159
|
-
stores = undefined;
|
|
160
|
-
} else {
|
|
161
|
-
container = container || document;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Default empty objects for missing arguments
|
|
165
|
-
loaderData = loaderData || {};
|
|
166
|
-
props = props || {};
|
|
167
|
-
stores = stores || {};
|
|
168
|
-
|
|
169
|
-
// Store state and data globally for event handlers
|
|
170
|
-
if (typeof window !== 'undefined') {
|
|
171
|
-
window.__ZENITH_STATE__ = state;
|
|
172
|
-
window.__ZENITH_LOADER_DATA__ = loaderData;
|
|
173
|
-
window.__ZENITH_PROPS__ = props;
|
|
174
|
-
window.__ZENITH_STORES__ = stores;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Clear existing bindings
|
|
178
|
-
__zen_bindings.length = 0;
|
|
179
|
-
|
|
180
|
-
// Find all text expression placeholders
|
|
181
|
-
const textPlaceholders = container.querySelectorAll('[data-zen-text]');
|
|
182
|
-
for (let i = 0; i < textPlaceholders.length; i++) {
|
|
183
|
-
const node = textPlaceholders[i];
|
|
184
|
-
const expressionId = node.getAttribute('data-zen-text');
|
|
185
|
-
if (!expressionId) continue;
|
|
186
|
-
|
|
187
|
-
__zen_bindings.push({
|
|
188
|
-
node: node,
|
|
189
|
-
type: 'text',
|
|
190
|
-
expressionId: expressionId
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
updateTextBinding(node, expressionId, state, loaderData, props, stores);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Find all attribute expression placeholders
|
|
197
|
-
const attrSelectors = [
|
|
198
|
-
'[data-zen-attr-class]',
|
|
199
|
-
'[data-zen-attr-style]',
|
|
200
|
-
'[data-zen-attr-src]',
|
|
201
|
-
'[data-zen-attr-href]',
|
|
202
|
-
'[data-zen-attr-disabled]',
|
|
203
|
-
'[data-zen-attr-checked]'
|
|
204
|
-
];
|
|
205
|
-
|
|
206
|
-
for (let s = 0; s < attrSelectors.length; s++) {
|
|
207
|
-
const attrPlaceholders = container.querySelectorAll(attrSelectors[s]);
|
|
208
|
-
for (let i = 0; i < attrPlaceholders.length; i++) {
|
|
209
|
-
const node = attrPlaceholders[i];
|
|
210
|
-
if (!(node instanceof Element)) continue;
|
|
211
|
-
|
|
212
|
-
// Extract attribute name from selector
|
|
213
|
-
const attrMatch = attrSelectors[s].match(/data-zen-attr-(\\w+)/);
|
|
214
|
-
if (!attrMatch) continue;
|
|
215
|
-
const attrName = attrMatch[1];
|
|
216
|
-
|
|
217
|
-
const expressionId = node.getAttribute('data-zen-attr-' + attrName);
|
|
218
|
-
if (!expressionId) continue;
|
|
219
|
-
|
|
220
|
-
__zen_bindings.push({
|
|
221
|
-
node: node,
|
|
222
|
-
type: 'attribute',
|
|
223
|
-
attributeName: attrName,
|
|
224
|
-
expressionId: expressionId
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
updateAttributeBinding(node, attrName, expressionId, state, loaderData, props, stores);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Bind event handlers
|
|
232
|
-
bindEvents(container);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Bind event handlers to DOM elements
|
|
237
|
-
*/
|
|
238
|
-
function bindEvents(container) {
|
|
239
|
-
container = container || document;
|
|
240
|
-
const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur', 'keyup', 'keydown'];
|
|
241
|
-
|
|
242
|
-
for (let e = 0; e < eventTypes.length; e++) {
|
|
243
|
-
const eventType = eventTypes[e];
|
|
244
|
-
const elements = container.querySelectorAll('[data-zen-' + eventType + ']');
|
|
245
|
-
|
|
246
|
-
for (let i = 0; i < elements.length; i++) {
|
|
247
|
-
const element = elements[i];
|
|
248
|
-
if (!(element instanceof Element)) continue;
|
|
249
|
-
|
|
250
|
-
const handlerName = element.getAttribute('data-zen-' + eventType);
|
|
251
|
-
if (!handlerName) continue;
|
|
252
|
-
|
|
253
|
-
// Remove existing listener if any (to avoid duplicates)
|
|
254
|
-
const handlerKey = '__zen_' + eventType + '_handler';
|
|
255
|
-
const existingHandler = element[handlerKey];
|
|
256
|
-
if (existingHandler) {
|
|
257
|
-
element.removeEventListener(eventType, existingHandler);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Create new handler
|
|
261
|
-
const handler = function(event) {
|
|
262
|
-
try {
|
|
263
|
-
// 1. Try to find handler function on window (for named functions)
|
|
264
|
-
let handlerFunc = window[handlerName];
|
|
265
|
-
|
|
266
|
-
// 2. If not found, try the expression registry (for inline expressions)
|
|
267
|
-
if (typeof handlerFunc !== 'function' && window.__ZENITH_EXPRESSIONS__) {
|
|
268
|
-
handlerFunc = window.__ZENITH_EXPRESSIONS__.get(handlerName);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (typeof handlerFunc === 'function') {
|
|
272
|
-
// Call the handler. For expressions, we pass the current state.
|
|
273
|
-
// Note: Phase 6 handles passing loaderData, props, etc. if needed.
|
|
274
|
-
const state = window.__ZENITH_STATE__ || {};
|
|
275
|
-
const loaderData = window.__ZENITH_LOADER_DATA__ || {};
|
|
276
|
-
const props = window.__ZENITH_PROPS__ || {};
|
|
277
|
-
const stores = window.__ZENITH_STORES__ || {};
|
|
278
|
-
|
|
279
|
-
if (handlerFunc.length === 1) {
|
|
280
|
-
// Legacy or simple handler
|
|
281
|
-
handlerFunc(event, element);
|
|
282
|
-
} else {
|
|
283
|
-
// Full context handler
|
|
284
|
-
handlerFunc(event, element, state, loaderData, props, stores);
|
|
285
|
-
}
|
|
286
|
-
} else {
|
|
287
|
-
console.warn('[Zenith] Event handler "' + handlerName + '" not found for ' + eventType + ' event');
|
|
288
|
-
}
|
|
289
|
-
} catch (error) {
|
|
290
|
-
console.error('[Zenith] Error executing event handler "' + handlerName + '":', error);
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
// Store handler reference to allow cleanup
|
|
295
|
-
element[handlerKey] = handler;
|
|
296
|
-
|
|
297
|
-
element.addEventListener(eventType, handler);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Update all bindings when state changes
|
|
304
|
-
* Phase 6: Accepts explicit data arguments
|
|
305
|
-
*/
|
|
306
|
-
function update(state, loaderData, props, stores) {
|
|
307
|
-
if (!state) {
|
|
308
|
-
console.warn('[Zenith] update called without state object');
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Handle optional arguments (backwards compatibility)
|
|
313
|
-
if (typeof loaderData === 'undefined') {
|
|
314
|
-
loaderData = window.__ZENITH_LOADER_DATA__ || {};
|
|
315
|
-
props = window.__ZENITH_PROPS__ || {};
|
|
316
|
-
stores = window.__ZENITH_STORES__ || {};
|
|
317
|
-
} else {
|
|
318
|
-
loaderData = loaderData || {};
|
|
319
|
-
props = props || {};
|
|
320
|
-
stores = stores || {};
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Update global state and data
|
|
324
|
-
if (typeof window !== 'undefined') {
|
|
325
|
-
window.__ZENITH_STATE__ = state;
|
|
326
|
-
window.__ZENITH_LOADER_DATA__ = loaderData;
|
|
327
|
-
window.__ZENITH_PROPS__ = props;
|
|
328
|
-
window.__ZENITH_STORES__ = stores;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Update all tracked bindings
|
|
332
|
-
for (let i = 0; i < __zen_bindings.length; i++) {
|
|
333
|
-
const binding = __zen_bindings[i];
|
|
334
|
-
if (binding.type === 'text') {
|
|
335
|
-
updateTextBinding(binding.node, binding.expressionId, state, loaderData, props, stores);
|
|
336
|
-
} else if (binding.type === 'attribute' && binding.attributeName) {
|
|
337
|
-
if (binding.node instanceof Element) {
|
|
338
|
-
updateAttributeBinding(binding.node, binding.attributeName, binding.expressionId, state, loaderData, props, stores);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Clear all bindings and event listeners
|
|
346
|
-
*/
|
|
347
|
-
function cleanup(container) {
|
|
348
|
-
container = container || document;
|
|
349
|
-
|
|
350
|
-
// Remove event listeners
|
|
351
|
-
const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur', 'keyup', 'keydown'];
|
|
352
|
-
for (let e = 0; e < eventTypes.length; e++) {
|
|
353
|
-
const eventType = eventTypes[e];
|
|
354
|
-
const elements = container.querySelectorAll('[data-zen-' + eventType + ']');
|
|
355
|
-
for (let i = 0; i < elements.length; i++) {
|
|
356
|
-
const element = elements[i];
|
|
357
|
-
if (!(element instanceof Element)) continue;
|
|
358
|
-
const handlerKey = '__zen_' + eventType + '_handler';
|
|
359
|
-
const handler = element[handlerKey];
|
|
360
|
-
if (handler) {
|
|
361
|
-
element.removeEventListener(eventType, handler);
|
|
362
|
-
delete element[handlerKey];
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Clear bindings
|
|
368
|
-
__zen_bindings.length = 0;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Export functions to window
|
|
372
|
-
if (typeof window !== 'undefined') {
|
|
373
|
-
window.__zenith_hydrate = hydrate;
|
|
374
|
-
window.__zenith_bindEvents = bindEvents;
|
|
375
|
-
window.__zenith_update = update;
|
|
376
|
-
window.__zenith_cleanup = cleanup;
|
|
377
|
-
}
|
|
378
|
-
})();
|
|
379
|
-
`
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Generate expression registry initialization code
|
|
384
|
-
*/
|
|
385
|
-
export function generateExpressionRegistry(expressions: ExpressionIR[]): string {
|
|
386
|
-
if (expressions.length === 0) {
|
|
387
|
-
return `
|
|
388
|
-
// No expressions to register
|
|
389
|
-
if (typeof window !== 'undefined' && window.__ZENITH_EXPRESSIONS__) {
|
|
390
|
-
// Registry already initialized
|
|
391
|
-
}`
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const registryCode = expressions.map(expr => {
|
|
395
|
-
return ` window.__ZENITH_EXPRESSIONS__.set('${expr.id}', ${expr.id});`
|
|
396
|
-
}).join('\n')
|
|
397
|
-
|
|
398
|
-
return `
|
|
399
|
-
// Initialize expression registry
|
|
400
|
-
if (typeof window !== 'undefined') {
|
|
401
|
-
if (!window.__ZENITH_EXPRESSIONS__) {
|
|
402
|
-
window.__ZENITH_EXPRESSIONS__ = new Map();
|
|
403
|
-
}
|
|
404
|
-
${registryCode}
|
|
405
|
-
}`
|
|
406
|
-
}
|
|
407
|
-
|