@zenithbuild/core 0.6.2 → 0.6.4

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.
Files changed (112) hide show
  1. package/CORE_CONTRACT.md +145 -0
  2. package/README.md +14 -29
  3. package/bin/zenith.js +89 -0
  4. package/package.json +39 -56
  5. package/src/config.js +136 -0
  6. package/src/core-template.js +30 -0
  7. package/src/errors.js +54 -0
  8. package/src/guards.js +61 -0
  9. package/src/hash.js +52 -0
  10. package/src/index.js +26 -0
  11. package/src/ir/index.js +1 -0
  12. package/src/order.js +69 -0
  13. package/src/path.js +131 -0
  14. package/src/schema.js +28 -0
  15. package/src/version.js +67 -0
  16. package/bin/zen-build.ts +0 -2
  17. package/bin/zen-dev.ts +0 -2
  18. package/bin/zen-preview.ts +0 -2
  19. package/bin/zenith.ts +0 -2
  20. package/cli/commands/add.ts +0 -37
  21. package/cli/commands/build.ts +0 -37
  22. package/cli/commands/create.ts +0 -702
  23. package/cli/commands/dev.ts +0 -388
  24. package/cli/commands/index.ts +0 -112
  25. package/cli/commands/preview.ts +0 -62
  26. package/cli/commands/remove.ts +0 -33
  27. package/cli/index.ts +0 -10
  28. package/cli/main.ts +0 -101
  29. package/cli/utils/branding.ts +0 -178
  30. package/cli/utils/content.ts +0 -112
  31. package/cli/utils/logger.ts +0 -46
  32. package/cli/utils/plugin-manager.ts +0 -114
  33. package/cli/utils/project.ts +0 -77
  34. package/compiler/README.md +0 -380
  35. package/compiler/build-analyzer.ts +0 -122
  36. package/compiler/css/index.ts +0 -317
  37. package/compiler/discovery/componentDiscovery.ts +0 -178
  38. package/compiler/discovery/layouts.ts +0 -70
  39. package/compiler/errors/compilerError.ts +0 -56
  40. package/compiler/finalize/finalizeOutput.ts +0 -192
  41. package/compiler/finalize/generateFinalBundle.ts +0 -82
  42. package/compiler/index.ts +0 -83
  43. package/compiler/ir/types.ts +0 -174
  44. package/compiler/output/types.ts +0 -34
  45. package/compiler/parse/detectMapExpressions.ts +0 -102
  46. package/compiler/parse/importTypes.ts +0 -78
  47. package/compiler/parse/parseImports.ts +0 -309
  48. package/compiler/parse/parseScript.ts +0 -46
  49. package/compiler/parse/parseTemplate.ts +0 -599
  50. package/compiler/parse/parseZenFile.ts +0 -66
  51. package/compiler/parse/scriptAnalysis.ts +0 -91
  52. package/compiler/parse/trackLoopContext.ts +0 -82
  53. package/compiler/runtime/dataExposure.ts +0 -317
  54. package/compiler/runtime/generateDOM.ts +0 -246
  55. package/compiler/runtime/generateHydrationBundle.ts +0 -407
  56. package/compiler/runtime/hydration.ts +0 -309
  57. package/compiler/runtime/navigation.ts +0 -432
  58. package/compiler/runtime/thinRuntime.ts +0 -160
  59. package/compiler/runtime/transformIR.ts +0 -370
  60. package/compiler/runtime/wrapExpression.ts +0 -95
  61. package/compiler/runtime/wrapExpressionWithLoop.ts +0 -83
  62. package/compiler/spa-build.ts +0 -917
  63. package/compiler/ssg-build.ts +0 -422
  64. package/compiler/test/validate-test.ts +0 -104
  65. package/compiler/transform/classifyExpression.ts +0 -444
  66. package/compiler/transform/componentResolver.ts +0 -312
  67. package/compiler/transform/componentScriptTransformer.ts +0 -303
  68. package/compiler/transform/expressionTransformer.ts +0 -385
  69. package/compiler/transform/fragmentLowering.ts +0 -634
  70. package/compiler/transform/generateBindings.ts +0 -47
  71. package/compiler/transform/generateHTML.ts +0 -28
  72. package/compiler/transform/layoutProcessor.ts +0 -132
  73. package/compiler/transform/slotResolver.ts +0 -292
  74. package/compiler/transform/transformNode.ts +0 -126
  75. package/compiler/transform/transformTemplate.ts +0 -38
  76. package/compiler/validate/invariants.ts +0 -292
  77. package/compiler/validate/validateExpressions.ts +0 -168
  78. package/core/config/index.ts +0 -16
  79. package/core/config/loader.ts +0 -69
  80. package/core/config/types.ts +0 -89
  81. package/core/index.ts +0 -135
  82. package/core/lifecycle/index.ts +0 -49
  83. package/core/lifecycle/zen-mount.ts +0 -182
  84. package/core/lifecycle/zen-unmount.ts +0 -88
  85. package/core/plugins/index.ts +0 -7
  86. package/core/plugins/registry.ts +0 -81
  87. package/core/reactivity/index.ts +0 -54
  88. package/core/reactivity/tracking.ts +0 -167
  89. package/core/reactivity/zen-batch.ts +0 -57
  90. package/core/reactivity/zen-effect.ts +0 -139
  91. package/core/reactivity/zen-memo.ts +0 -146
  92. package/core/reactivity/zen-ref.ts +0 -52
  93. package/core/reactivity/zen-signal.ts +0 -121
  94. package/core/reactivity/zen-state.ts +0 -180
  95. package/core/reactivity/zen-untrack.ts +0 -44
  96. package/dist/cli.js +0 -11665
  97. package/dist/zen-build.js +0 -21172
  98. package/dist/zen-dev.js +0 -21172
  99. package/dist/zen-preview.js +0 -21172
  100. package/dist/zenith.js +0 -21172
  101. package/router/index.ts +0 -28
  102. package/router/manifest.ts +0 -314
  103. package/router/navigation/ZenLink.zen +0 -231
  104. package/router/navigation/index.ts +0 -78
  105. package/router/navigation/zen-link.ts +0 -584
  106. package/router/runtime.ts +0 -458
  107. package/router/types.ts +0 -168
  108. package/runtime/build.ts +0 -17
  109. package/runtime/bundle-generator.ts +0 -1247
  110. package/runtime/client-runtime.ts +0 -549
  111. package/runtime/serve.ts +0 -93
  112. package/tsconfig.json +0 -28
@@ -1,246 +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
- code += `${indent}const ${conditionId}_result = (function() { with (state) { return ${condNode.condition}; } })();\n`
123
-
124
- // Generate consequent branch
125
- code += `${indent}if (${conditionId}_result) {\n`
126
- for (const child of condNode.consequent) {
127
- const childResult = generateDOMCode(child, expressions, indent + ' ', varCounter)
128
- code += `${childResult.code}\n`
129
- code += `${indent} ${containerVar}.appendChild(${childResult.varName});\n`
130
- }
131
- code += `${indent}} else {\n`
132
-
133
- // Generate alternate branch
134
- for (const child of condNode.alternate) {
135
- const childResult = generateDOMCode(child, expressions, indent + ' ', varCounter)
136
- code += `${childResult.code}\n`
137
- code += `${indent} ${containerVar}.appendChild(${childResult.varName});\n`
138
- }
139
- code += `${indent}}\n`
140
-
141
- return { code, varName: containerVar }
142
- }
143
-
144
- case 'optional-fragment': {
145
- // Optional fragment: {condition && <A />}
146
- // Fragment is precompiled, runtime mounts/unmounts based on condition
147
- const optNode = node as OptionalFragmentNode
148
- const containerVar = varName
149
- const conditionId = `opt_${varCounter.count++}`
150
-
151
- let code = `${indent}const ${containerVar} = document.createDocumentFragment();\n`
152
- code += `${indent}const ${conditionId}_result = (function() { with (state) { return ${optNode.condition}; } })();\n`
153
- code += `${indent}if (${conditionId}_result) {\n`
154
-
155
- for (const child of optNode.fragment) {
156
- const childResult = generateDOMCode(child, expressions, indent + ' ', varCounter)
157
- code += `${childResult.code}\n`
158
- code += `${indent} ${containerVar}.appendChild(${childResult.varName});\n`
159
- }
160
-
161
- code += `${indent}}\n`
162
-
163
- return { code, varName: containerVar }
164
- }
165
-
166
- case 'loop-fragment': {
167
- // Loop fragment: {items.map(item => <li>...</li>)}
168
- // Body is precompiled once, instantiated per item at runtime
169
- const loopNode = node as LoopFragmentNode
170
- const containerVar = varName
171
- const loopId = `loop_${varCounter.count++}`
172
-
173
- let code = `${indent}const ${containerVar} = document.createDocumentFragment();\n`
174
- code += `${indent}const ${loopId}_items = (function() { with (state) { return ${loopNode.source}; } })() || [];\n`
175
-
176
- // Loop parameters
177
- const itemVar = loopNode.itemVar
178
- const indexVar = loopNode.indexVar || `${loopId}_idx`
179
-
180
- code += `${indent}${loopId}_items.forEach(function(${itemVar}, ${indexVar}) {\n`
181
-
182
- // Generate loop body with loop context variables in scope
183
- for (const child of loopNode.body) {
184
- const childResult = generateDOMCode(child, expressions, indent + ' ', varCounter)
185
- // Inject loop variables into the child code
186
- let childCode = childResult.code
187
- code += `${childCode}\n`
188
- code += `${indent} ${containerVar}.appendChild(${childResult.varName});\n`
189
- }
190
-
191
- code += `${indent}});\n`
192
-
193
- return { code, varName: containerVar }
194
- }
195
-
196
- default: {
197
- throw new Error(`[Zenith] Unknown node type: ${(node as any).type}`)
198
- }
199
- }
200
- }
201
-
202
- /**
203
- * Generate DOM creation code for multiple nodes
204
- * Returns a function that creates DOM elements
205
- */
206
- export function generateDOMFunction(
207
- nodes: TemplateNode[],
208
- expressions: ExpressionIR[],
209
- functionName: string = 'renderTemplate'
210
- ): string {
211
- if (nodes.length === 0) {
212
- return `function ${functionName}(state) {
213
- const fragment = document.createDocumentFragment();
214
- return fragment;
215
- }`
216
- }
217
-
218
- const varCounter = { count: 0 }
219
- let code = `function ${functionName}(state) {
220
- `
221
-
222
- if (nodes.length === 1) {
223
- const node = nodes[0]
224
- if (!node) {
225
- throw new Error('Empty nodes array passed to generateDOMFunction')
226
- }
227
- const result = generateDOMCode(node, expressions, ' ', varCounter)
228
- code += result.code
229
- code += `\n return ${result.varName};\n}`
230
- return code
231
- }
232
-
233
- // Multiple nodes - create a fragment
234
- code += ` const fragment = document.createDocumentFragment();\n`
235
-
236
- for (const node of nodes) {
237
- const result = generateDOMCode(node, expressions, ' ', varCounter)
238
- code += `${result.code}\n`
239
- code += ` fragment.appendChild(${result.varName});\n`
240
- }
241
-
242
- code += ` return fragment;
243
- }`
244
-
245
- return code
246
- }
@@ -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
-