@zenithbuild/core 0.1.0 → 0.3.1

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 (90) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +24 -40
  3. package/bin/zen-build.ts +2 -0
  4. package/bin/zen-dev.ts +2 -0
  5. package/bin/zen-preview.ts +2 -0
  6. package/bin/zenith.ts +2 -0
  7. package/cli/commands/add.ts +37 -0
  8. package/cli/commands/build.ts +37 -0
  9. package/cli/commands/create.ts +702 -0
  10. package/cli/commands/dev.ts +197 -0
  11. package/cli/commands/index.ts +112 -0
  12. package/cli/commands/preview.ts +62 -0
  13. package/cli/commands/remove.ts +33 -0
  14. package/cli/index.ts +10 -0
  15. package/cli/main.ts +101 -0
  16. package/cli/utils/branding.ts +153 -0
  17. package/cli/utils/logger.ts +40 -0
  18. package/cli/utils/plugin-manager.ts +114 -0
  19. package/cli/utils/project.ts +71 -0
  20. package/compiler/build-analyzer.ts +122 -0
  21. package/compiler/discovery/layouts.ts +61 -0
  22. package/compiler/index.ts +40 -24
  23. package/compiler/ir/types.ts +1 -0
  24. package/compiler/parse/parseScript.ts +29 -5
  25. package/compiler/parse/parseTemplate.ts +96 -58
  26. package/compiler/parse/scriptAnalysis.ts +77 -0
  27. package/compiler/runtime/dataExposure.ts +49 -31
  28. package/compiler/runtime/generateDOM.ts +18 -17
  29. package/compiler/runtime/generateHydrationBundle.ts +24 -5
  30. package/compiler/runtime/transformIR.ts +140 -49
  31. package/compiler/runtime/wrapExpressionWithLoop.ts +11 -11
  32. package/compiler/spa-build.ts +70 -153
  33. package/compiler/ssg-build.ts +412 -0
  34. package/compiler/transform/layoutProcessor.ts +132 -0
  35. package/compiler/transform/transformNode.ts +19 -19
  36. package/dist/cli.js +11648 -0
  37. package/dist/zen-build.js +11659 -0
  38. package/dist/zen-dev.js +11659 -0
  39. package/dist/zen-preview.js +11659 -0
  40. package/dist/zenith.js +11659 -0
  41. package/package.json +22 -2
  42. package/runtime/bundle-generator.ts +416 -0
  43. package/runtime/client-runtime.ts +532 -0
  44. package/.eslintignore +0 -15
  45. package/.gitattributes +0 -2
  46. package/.github/ISSUE_TEMPLATE/compiler-errors-for-invalid-state-declarations.md +0 -25
  47. package/.github/ISSUE_TEMPLATE/new_ticket.yaml +0 -34
  48. package/.github/pull_request_template.md +0 -15
  49. package/.github/workflows/discord-changelog.yml +0 -141
  50. package/.github/workflows/discord-notify.yml +0 -242
  51. package/.github/workflows/discord-version.yml +0 -195
  52. package/.prettierignore +0 -13
  53. package/.prettierrc +0 -21
  54. package/.zen.d.ts +0 -15
  55. package/app/components/Button.zen +0 -46
  56. package/app/components/Link.zen +0 -11
  57. package/app/favicon.ico +0 -0
  58. package/app/layouts/Main.zen +0 -59
  59. package/app/pages/about.zen +0 -23
  60. package/app/pages/blog/[id].zen +0 -53
  61. package/app/pages/blog/index.zen +0 -32
  62. package/app/pages/dynamic-dx.zen +0 -712
  63. package/app/pages/dynamic-primitives.zen +0 -453
  64. package/app/pages/index.zen +0 -154
  65. package/app/pages/navigation-demo.zen +0 -229
  66. package/app/pages/posts/[...slug].zen +0 -61
  67. package/app/pages/primitives-demo.zen +0 -273
  68. package/assets/logos/0E3B5DDD-605C-4839-BB2E-DFCA8ADC9604.PNG +0 -0
  69. package/assets/logos/760971E5-79A1-44F9-90B9-925DF30F4278.PNG +0 -0
  70. package/assets/logos/8A06ED80-9ED2-4689-BCBD-13B2E95EE8E4.JPG +0 -0
  71. package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.PNG +0 -0
  72. package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.svg +0 -601
  73. package/assets/logos/README.md +0 -54
  74. package/assets/logos/zen.icns +0 -0
  75. package/bun.lock +0 -39
  76. package/compiler/legacy/binding.ts +0 -254
  77. package/compiler/legacy/bindings.ts +0 -338
  78. package/compiler/legacy/component-process.ts +0 -1208
  79. package/compiler/legacy/component.ts +0 -301
  80. package/compiler/legacy/event.ts +0 -50
  81. package/compiler/legacy/expression.ts +0 -1149
  82. package/compiler/legacy/mutation.ts +0 -280
  83. package/compiler/legacy/parse.ts +0 -299
  84. package/compiler/legacy/split.ts +0 -608
  85. package/compiler/legacy/types.ts +0 -32
  86. package/docs/COMMENTS.md +0 -111
  87. package/docs/COMMITS.md +0 -36
  88. package/docs/CONTRIBUTING.md +0 -116
  89. package/docs/STYLEGUIDE.md +0 -62
  90. package/scripts/webhook-proxy.ts +0 -213
@@ -1,54 +0,0 @@
1
- # Zenith Logos
2
-
3
- This directory contains the Zenith framework logo assets.
4
-
5
- ## Logo Files
6
-
7
- Based on the logo designs, you should place the following files here:
8
-
9
- 1. **zenith-logo-icon.png** (or .svg) - The file icon version with the folded corner and ".ZENITH" button
10
- 2. **zenith-logo-z.png** (or .svg) - The stylized "Z" logo with gradient and light trails
11
- 3. **zenith-logo-full.png** (or .svg) - The full logo with "ZENITH" text below
12
- 4. **zenith-logo-soon.png** (or .svg) - The logo variant with "Soon" text
13
-
14
- ## File Icon for .zen Files
15
-
16
- The SVG logo has been converted to a macOS `.icns` file to use as the file icon for `.zen` files.
17
-
18
- ### Setting the Icon
19
-
20
- **Option 1: Use the automated script (recommended)**
21
- ```bash
22
- cd assets/logos
23
- ./set-zen-icon.sh path/to/your/file.zen
24
- ```
25
-
26
- **Option 2: Set icon for all .zen files in a directory**
27
- ```bash
28
- cd assets/logos
29
- find ../app -name "*.zen" -exec ./set-zen-icon.sh {} \;
30
- ```
31
-
32
- **Option 3: Manual method**
33
- 1. Open Finder and navigate to a `.zen` file
34
- 2. Right-click the file and select "Get Info"
35
- 3. Drag `zen.icns` onto the small icon in the top-left of the Get Info window
36
- 4. Close the Get Info window
37
-
38
- ### Recreating the .icns File
39
-
40
- If you update the SVG and need to regenerate the `.icns` file:
41
- ```bash
42
- cd assets/logos
43
- ./create-zen-icon.sh
44
- ```
45
-
46
- ## Usage
47
-
48
- These logos can be used in:
49
- - Documentation (`docs/`)
50
- - README files
51
- - Website/marketing materials
52
- - Framework branding
53
- - File type icons (`.zen` files)
54
-
Binary file
package/bun.lock DELETED
@@ -1,39 +0,0 @@
1
- {
2
- "lockfileVersion": 1,
3
- "configVersion": 1,
4
- "workspaces": {
5
- "": {
6
- "name": "zenith",
7
- "dependencies": {
8
- "@types/parse5": "^7.0.0",
9
- "parse5": "^8.0.0",
10
- },
11
- "devDependencies": {
12
- "@types/bun": "latest",
13
- "prettier": "^3.7.4",
14
- },
15
- "peerDependencies": {
16
- "typescript": "^5",
17
- },
18
- },
19
- },
20
- "packages": {
21
- "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
22
-
23
- "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
24
-
25
- "@types/parse5": ["@types/parse5@7.0.0", "", { "dependencies": { "parse5": "*" } }, "sha512-f2SeAxumolBmhuR62vNGTsSAvdz/Oj0k682xNrcKJ4dmRnTPODB74j6CPoNPzBPTHsu7Y7W7u93Mgp8Ovo8vWw=="],
26
-
27
- "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
28
-
29
- "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
30
-
31
- "parse5": ["parse5@8.0.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="],
32
-
33
- "prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="],
34
-
35
- "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
36
-
37
- "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
38
- }
39
- }
@@ -1,254 +0,0 @@
1
- // compiler/binding.ts
2
- // Phase 2: Reactive text bindings runtime generator
3
- // Generates code to update DOM when state values change
4
- // Extended to support component instance-scoped state
5
-
6
- import type { StateBinding } from "./types"
7
-
8
- // Extract instance ID and base state name from instance-scoped state name
9
- // e.g., "__zen_comp_0_clicks" -> { instanceId: "comp-0", baseState: "clicks" }
10
- function parseInstanceState(stateName: string): { instanceId: string; baseState: string } | null {
11
- const match = stateName.match(/^__zen_comp_(\d+)_(.+)$/);
12
- if (match) {
13
- const instanceNum = match[1];
14
- const baseState = match[2];
15
- return { instanceId: `comp-${instanceNum}`, baseState };
16
- }
17
- return null;
18
- }
19
-
20
- export function generateBindingRuntime(
21
- stateBindings: StateBinding[],
22
- stateDeclarations: Map<string, string>
23
- ): string {
24
- if (stateBindings.length === 0 && stateDeclarations.size === 0) {
25
- return "";
26
- }
27
-
28
- const stateNames = Array.from(stateDeclarations.keys());
29
-
30
- // Separate global state and instance-scoped state
31
- const globalStates = new Set<string>();
32
- const instanceStates = new Map<string, Map<string, string[]>>(); // instanceId -> baseState -> fullStateNames
33
-
34
- for (const stateName of stateNames) {
35
- const instanceInfo = parseInstanceState(stateName);
36
- if (instanceInfo) {
37
- if (!instanceStates.has(instanceInfo.instanceId)) {
38
- instanceStates.set(instanceInfo.instanceId, new Map());
39
- }
40
- const instanceMap = instanceStates.get(instanceInfo.instanceId)!;
41
- if (!instanceMap.has(instanceInfo.baseState)) {
42
- instanceMap.set(instanceInfo.baseState, []);
43
- }
44
- instanceMap.get(instanceInfo.baseState)!.push(stateName);
45
- } else {
46
- globalStates.add(stateName);
47
- }
48
- }
49
-
50
- // Generate binding update map - collect all nodes for each state
51
- // Order is preserved: bindings are processed in the order they appear in stateBindings array
52
- // (which matches DOM traversal order from compilation)
53
- const bindingMapEntries: string[] = [];
54
- const bindingMap = new Map<string, string[]>();
55
-
56
- // Iterate over stateBindings array to preserve compile-time order
57
- // Maps preserve insertion order, so this maintains deterministic ordering
58
- for (const stateBinding of stateBindings) {
59
- if (!bindingMap.has(stateBinding.stateName)) {
60
- bindingMap.set(stateBinding.stateName, []);
61
- }
62
- const selectors = bindingMap.get(stateBinding.stateName)!;
63
- // Push bindings in the order they appear in stateBinding.bindings array
64
- // This preserves the DOM traversal order from compilation
65
- for (const binding of stateBinding.bindings) {
66
- const bindId = `bind-${binding.nodeIndex}`;
67
-
68
- // Check if this is an instance-scoped binding
69
- const instanceInfo = parseInstanceState(stateBinding.stateName);
70
- if (instanceInfo) {
71
- // Scope selector to component instance root
72
- selectors.push(`[data-zen-instance="${instanceInfo.instanceId}"] span[data-zen-bind="${stateBinding.stateName}"][data-zen-bind-id="${bindId}"]`);
73
- } else {
74
- // Global binding
75
- selectors.push(`span[data-zen-bind="${stateBinding.stateName}"][data-zen-bind-id="${bindId}"]`);
76
- }
77
- }
78
- }
79
-
80
- // Generate update functions for each binding
81
- // Each function captures a DOM node reference directly
82
- // Map.entries() preserves insertion order, maintaining compile-time binding order
83
- for (const [stateName, selectors] of bindingMap.entries()) {
84
- if (selectors.length > 0) {
85
- // Generate an array of update functions, each capturing a node reference
86
- const updateFunctions = selectors.map((selector, index) => {
87
- const escapedSelector = selector.replace(/"/g, '\\"');
88
- // Create a function that captures the node and updates it
89
- // We'll query the node once during init, then the function captures it
90
- return `(function() {
91
- const node = document.querySelector("${escapedSelector}");
92
- return function(value) {
93
- if (node) node.textContent = String(value);
94
- };
95
- })()`;
96
- }).join(",\n ");
97
-
98
- bindingMapEntries.push(
99
- ` "${stateName}": [\n ${updateFunctions}\n ]`
100
- );
101
- }
102
- }
103
-
104
- const bindingMapCode = bindingMapEntries.length > 0
105
- ? `__zen_bindings = {\n${bindingMapEntries.join(",\n")}\n };`
106
- : `__zen_bindings = {};`;
107
-
108
- // Generate global state initialization code
109
- const globalStateInitCode = Array.from(globalStates).map(name => {
110
- const initialValue = stateDeclarations.get(name) || "undefined";
111
- return `
112
- // Initialize global state: ${name}
113
- (function() {
114
- let __zen_${name} = ${initialValue};
115
- Object.defineProperty(window, "${name}", {
116
- get: function() { return __zen_${name}; },
117
- set: function(value) {
118
- __zen_${name} = value;
119
- // Immediately trigger synchronous updates - no batching, no async
120
- __zen_update_bindings("${name}", value);
121
- // Also trigger dynamic expression updates
122
- if (window.__zen_trigger_expression_updates) {
123
- window.__zen_trigger_expression_updates("${name}");
124
- }
125
- },
126
- enumerable: true,
127
- configurable: true
128
- });
129
- })();`;
130
- }).join("");
131
-
132
- // Generate instance state initialization code
133
- const instanceStateInitCode: string[] = [];
134
- for (const [instanceId, baseStateMap] of instanceStates.entries()) {
135
- const safeInstanceId = instanceId.replace(/-/g, '_');
136
-
137
- for (const [baseState, fullStateNames] of baseStateMap.entries()) {
138
- // For each instance, create a state proxy scoped to that instance
139
- // Only process the first fullStateName (they should all have the same instance)
140
- const fullStateName = fullStateNames[0];
141
- const initialValue = stateDeclarations.get(fullStateName) || "undefined";
142
-
143
- instanceStateInitCode.push(`
144
- // Initialize instance-scoped state: ${fullStateName} (${instanceId}.${baseState})
145
- (function() {
146
- const instanceRoot = document.querySelector('[data-zen-instance="${instanceId}"]');
147
- if (!instanceRoot) {
148
- console.warn('[Zenith] Component instance "${instanceId}" not found in DOM');
149
- return;
150
- }
151
-
152
- let __zen_${safeInstanceId}_${baseState} = ${initialValue};
153
-
154
- // Create instance-scoped state proxy
155
- const instanceState = new Proxy({}, {
156
- get(target, prop) {
157
- if (prop === '${baseState}') {
158
- return __zen_${safeInstanceId}_${baseState};
159
- }
160
- return undefined;
161
- },
162
- set(target, prop, value) {
163
- if (prop === '${baseState}') {
164
- __zen_${safeInstanceId}_${baseState} = value;
165
- // Trigger updates only for bindings within this instance
166
- __zen_update_bindings("${fullStateName}", value);
167
- return true;
168
- }
169
- return false;
170
- }
171
- });
172
-
173
- // Store instance state on window for component access
174
- if (!window.__zen_instances) {
175
- window.__zen_instances = {};
176
- }
177
- if (!window.__zen_instances["${instanceId}"]) {
178
- window.__zen_instances["${instanceId}"] = instanceState;
179
- } else {
180
- window.__zen_instances["${instanceId}"].${baseState} = __zen_${safeInstanceId}_${baseState};
181
- }
182
-
183
- // Create global property accessor for instance-scoped state
184
- Object.defineProperty(window, "${fullStateName}", {
185
- get: function() { return __zen_${safeInstanceId}_${baseState}; },
186
- set: function(value) {
187
- __zen_${safeInstanceId}_${baseState} = value;
188
- // Update instance state object
189
- if (window.__zen_instances && window.__zen_instances["${instanceId}"]) {
190
- window.__zen_instances["${instanceId}"].${baseState} = value;
191
- }
192
- __zen_update_bindings("${fullStateName}", value);
193
- // Also trigger attribute binding updates for this instance
194
- if (window.__zen_update_attribute_bindings) {
195
- window.__zen_update_attribute_bindings("${instanceId}");
196
- }
197
- // Also trigger dynamic expression updates
198
- if (window.__zen_trigger_expression_updates) {
199
- window.__zen_trigger_expression_updates("${fullStateName}");
200
- }
201
- },
202
- enumerable: true,
203
- configurable: true
204
- });
205
- })();`);
206
- }
207
- }
208
-
209
- // Generate initialization call (after DOM is ready)
210
- const initBindingsCode = stateNames.map(name => {
211
- return ` __zen_update_bindings("${name}", ${name});`;
212
- }).join("\n");
213
-
214
- return `
215
- // Phase 2: Reactive text bindings runtime (with component instance support)
216
- (function() {
217
- let __zen_bindings = {};
218
-
219
- // Update function for a specific state
220
- // Calls all registered update functions for the given state property
221
- // Executes synchronously, immediately, in compile-order (array order)
222
- // No batching, no async scheduling, no reordering
223
- function __zen_update_bindings(stateName, value) {
224
- const updaters = __zen_bindings[stateName];
225
- if (updaters) {
226
- // Execute update functions in deterministic order (compile-time order)
227
- // forEach executes synchronously, preserving array order
228
- updaters.forEach(updateFn => {
229
- if (typeof updateFn === 'function') {
230
- updateFn(value);
231
- }
232
- });
233
- }
234
- }
235
-
236
- ${globalStateInitCode}
237
- ${instanceStateInitCode.join('')}
238
-
239
- // Initialize binding map and bindings after DOM is ready
240
- function __zen_init_bindings() {
241
- ${bindingMapCode.split('\n').map(line => ' ' + line).join('\n')}
242
- ${initBindingsCode.split('\n').map(line => ' ' + line).join('\n')}
243
- }
244
-
245
- // Initialize bindings when DOM is ready (scripts are deferred, so DOM should be ready)
246
- if (document.readyState === 'loading') {
247
- document.addEventListener('DOMContentLoaded', __zen_init_bindings);
248
- } else {
249
- __zen_init_bindings();
250
- }
251
- })();
252
- `;
253
- }
254
-
@@ -1,338 +0,0 @@
1
- // compiler/bindings.ts
2
- // Phase 2: Object-style dynamic attribute bindings with quoted expressions
3
- // Supports :class and :value attributes with synchronous, deterministic updates
4
-
5
- export function generateAttributeBindingRuntime(bindings: Array<{ type: 'class' | 'value'; expression: string }>): string {
6
- if (bindings.length === 0) {
7
- return ''; // No bindings, no runtime needed
8
- }
9
-
10
- // Generate unique expression IDs for tracking
11
- const expressionIds = bindings.map((_, i) => `expr_${i}`);
12
- const expressionsStr = JSON.stringify(bindings.map(b => b.expression));
13
- const typesStr = JSON.stringify(bindings.map(b => b.type));
14
-
15
- return `
16
- // Phase 2: Attribute binding runtime - synchronous, deterministic updates
17
- // Note: 'use strict' is omitted to allow 'with' statement for expression evaluation
18
- (function() {
19
-
20
- // Store all binding elements and their expressions
21
- const bindingExpressions = ${expressionsStr};
22
- const bindingTypes = ${typesStr};
23
- const bindingElements = [];
24
-
25
- // Reactive state proxy - tracks property access and updates DOM synchronously
26
- // Initialize with empty object - properties will be added dynamically
27
- const stateTarget = {};
28
- const stateProxy = new Proxy(stateTarget, {
29
- set(target, prop, value) {
30
- const oldValue = target[prop];
31
- target[prop] = value;
32
-
33
- // Log state change for debugging
34
- // console.log('[Zenith] State change:', prop, '=', value);
35
-
36
- // Synchronously update all affected bindings
37
- bindingElements.forEach(binding => {
38
- try {
39
- // Re-evaluate expression in context of current state
40
- // Pass target (the state object) as parameter to the evaluator function
41
- // If this binding has instance state, merge it with global state
42
- const mergedState = binding.instanceState
43
- ? Object.assign({}, target, binding.instanceState)
44
- : target;
45
- const result = binding.fn(mergedState);
46
-
47
- if (binding.type === 'class') {
48
- updateClassBinding(binding.el, result);
49
- // console.log('[Zenith] Updated :class binding for element:', binding.el, 'result:', result);
50
- } else if (binding.type === 'value') {
51
- updateValueBinding(binding.el, result);
52
- // console.log('[Zenith] Updated :value binding for element:', binding.el, 'result:', result);
53
- }
54
- } catch (e) {
55
- // Log errors for debugging (Phase 2: graceful degradation)
56
- console.warn('[Zenith] Binding evaluation error:', e, 'for expression:', binding.expression);
57
- }
58
- });
59
-
60
- return true;
61
- },
62
- get(target, prop) {
63
- // Return undefined for missing properties (don't throw errors)
64
- return target[prop];
65
- }
66
- });
67
-
68
- // Make stateProxy available globally as 'state'
69
- window.state = stateProxy;
70
-
71
- // Function to update attribute bindings for a specific component instance
72
- // Called when instance-scoped state changes
73
- function updateAttributeBindingsForInstance(instanceId) {
74
- const instanceRoot = document.querySelector('[data-zen-instance="' + instanceId + '"]');
75
- if (!instanceRoot) return;
76
-
77
- // Update all bindings within this instance
78
- bindingElements.forEach(binding => {
79
- // Check if this binding belongs to the instance
80
- const bindingInstanceRoot = findInstanceRoot(binding.el);
81
- if (bindingInstanceRoot === instanceRoot) {
82
- try {
83
- const instanceState = getInstanceStateForElement(binding.el);
84
- const mergedState = instanceState
85
- ? Object.assign({}, stateProxy, instanceState)
86
- : stateProxy;
87
- const result = binding.fn(mergedState);
88
-
89
- if (binding.type === 'class') {
90
- updateClassBinding(binding.el, result);
91
- } else if (binding.type === 'value') {
92
- updateValueBinding(binding.el, result);
93
- }
94
- } catch (e) {
95
- console.warn('[Zenith] Attribute binding evaluation error:', e, 'for expression:', binding.expression);
96
- }
97
- }
98
- });
99
- }
100
-
101
- // Expose update function globally so text binding runtime can trigger it
102
- window.__zen_update_attribute_bindings = updateAttributeBindingsForInstance;
103
-
104
- // Helper: Evaluate class binding expression
105
- // Handles: objects, strings, empty objects, falsy values
106
- // Preserves existing static classes from the class attribute
107
- function updateClassBinding(el, result) {
108
- // Store static classes on first update (from class attribute)
109
- if (!el._zenStaticClasses) {
110
- const staticClasses = el.getAttribute('class') || '';
111
- el._zenStaticClasses = staticClasses.split(/\\s+/).filter(c => c);
112
- }
113
- const staticClassList = el._zenStaticClasses;
114
-
115
- if (typeof result === 'string') {
116
- // String value: treat as raw class names, merge with static classes
117
- const dynamicClasses = result.split(/\\s+/).filter(c => c);
118
- el.className = [...staticClassList, ...dynamicClasses].join(' ').trim();
119
- } else if (result && typeof result === 'object' && !Array.isArray(result)) {
120
- // Object value: extract keys with true values
121
- const dynamicClasses = [];
122
- for (const key in result) {
123
- if (result.hasOwnProperty(key) && result[key] === true) {
124
- dynamicClasses.push(key);
125
- }
126
- }
127
- // Merge static and dynamic classes
128
- el.className = [...staticClassList, ...dynamicClasses].join(' ').trim();
129
- } else {
130
- // Falsy, null, undefined, or non-object: keep only static classes
131
- el.className = staticClassList.join(' ').trim();
132
- }
133
- }
134
-
135
- // Helper: Evaluate value binding expression
136
- // Handles: primitives, falsy values
137
- function updateValueBinding(el, result) {
138
- if (result === null || result === undefined) {
139
- el.value = '';
140
- } else {
141
- el.value = String(result);
142
- }
143
- }
144
-
145
- // Helper: Safely evaluate expression string
146
- // Creates a function that evaluates the expression with state properties in scope
147
- // Supports both global state (via state object) and instance-scoped state (via window)
148
- function createEvaluator(expression) {
149
- // Trim whitespace from expression
150
- let trimmed = expression.trim();
151
-
152
- // Check if expression is a quoted string (single or double quotes)
153
- // If so, unquote it and check if it's a simple identifier
154
- let isSimpleIdentifier = false;
155
- let evalExpression = trimmed;
156
-
157
- // Handle quoted strings: "username" or 'username' -> username
158
- if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
159
- (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
160
- // Extract the unquoted value
161
- const unquoted = trimmed.slice(1, -1);
162
- // Check if unquoted value is a simple identifier
163
- if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(unquoted)) {
164
- isSimpleIdentifier = true;
165
- evalExpression = unquoted; // Use unquoted identifier for evaluation
166
- }
167
- // Otherwise, treat as string literal (e.g., "'static-class'" -> "static-class")
168
- } else {
169
- // Not quoted: check if it's a simple identifier
170
- isSimpleIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(trimmed);
171
- if (isSimpleIdentifier) {
172
- evalExpression = trimmed; // Already unquoted identifier
173
- }
174
- }
175
-
176
- try {
177
- // Create a function that evaluates the expression
178
- // The expression is written as if state properties are directly accessible
179
- // We use Function constructor with 'with' statement to make state properties available
180
- // Note: 'with' is deprecated but necessary for this use case in non-strict mode
181
- return function(state) {
182
- try {
183
- // Use window.__zen_eval_expr for consistent expression evaluation
184
- // This handles window properties (state variables) correctly
185
- if (typeof window !== 'undefined' && window.__zen_eval_expr) {
186
- return window.__zen_eval_expr(evalExpression);
187
- }
188
-
189
- // Fallback: Merge state with window to access instance-scoped state variables
190
- // This allows expressions to reference both global state and instance-scoped state
191
- // Copy window properties that look like instance-scoped state to the merged context
192
- const mergedContext = Object.assign({}, state);
193
- for (const key in window) {
194
- if (key.startsWith('__zen_comp_') && !(key in mergedContext)) {
195
- mergedContext[key] = window[key];
196
- }
197
- }
198
-
199
- // Use Function constructor to create evaluator
200
- // The 'with' statement makes state properties and instance-scoped state available as variables
201
- // This allows expressions like "{ active: isActive }" where isActive refers to state.isActive
202
- // or instance-scoped variables like __zen_comp_0_clicks
203
- const func = new Function('state',
204
- 'try {' +
205
- ' with (state) {' +
206
- ' return (' + evalExpression + ');' +
207
- ' }' +
208
- '} catch (e) {' +
209
- ' console.warn("[Zenith] Expression evaluation error:", ' + JSON.stringify(trimmed) + ', e);' +
210
- ' return null;' +
211
- '}'
212
- );
213
- const result = func(mergedContext);
214
- // console.log('[Zenith] Evaluated expression:', trimmed, 'result:', result, 'state:', state);
215
- return result;
216
- } catch (e) {
217
- // Last resort: return safe default
218
- console.warn('Expression evaluation error:', trimmed, e);
219
- const bindingIndex = bindingExpressions.indexOf(expression);
220
- return bindingIndex >= 0 && bindingTypes[bindingIndex] === 'class' ? {} : '';
221
- }
222
- };
223
- } catch (e) {
224
- // If expression is invalid, return a function that returns empty string/object
225
- console.warn('Invalid binding expression:', expression, e);
226
- const bindingIndex = bindingExpressions.indexOf(expression);
227
- return function() {
228
- return bindingIndex >= 0 && bindingTypes[bindingIndex] === 'class' ? {} : '';
229
- };
230
- }
231
- }
232
-
233
- // Helper: Find component instance root for an element
234
- function findInstanceRoot(el) {
235
- let current = el;
236
- while (current) {
237
- if (current.hasAttribute && current.hasAttribute('data-zen-instance')) {
238
- return current;
239
- }
240
- current = current.parentElement;
241
- }
242
- return null;
243
- }
244
-
245
- // Helper: Get instance-scoped state for an element
246
- function getInstanceStateForElement(el) {
247
- const instanceRoot = findInstanceRoot(el);
248
- if (instanceRoot) {
249
- const instanceId = instanceRoot.getAttribute('data-zen-instance');
250
- if (instanceId && window.__zen_instances && window.__zen_instances[instanceId]) {
251
- return window.__zen_instances[instanceId];
252
- }
253
- }
254
- return null;
255
- }
256
-
257
- // Enhanced evaluator that supports both global and instance-scoped state
258
- function createEnhancedEvaluator(expression, instanceState) {
259
- const baseEvaluator = createEvaluator(expression);
260
- return function(state) {
261
- // Merge global state and instance state
262
- const mergedState = Object.assign({}, state);
263
- if (instanceState) {
264
- Object.assign(mergedState, instanceState);
265
- }
266
- // Also check window for instance-scoped state variables (e.g., __zen_comp_0_clicks)
267
- // These are set up by the binding runtime
268
- return baseEvaluator(mergedState);
269
- };
270
- }
271
-
272
- // Initialize bindings after DOM is ready
273
- function initializeBindings() {
274
- // console.log('[Zenith] Initializing attribute bindings...');
275
-
276
- // Find all elements with data-zen-class or data-zen-value attributes
277
- const classElements = document.querySelectorAll('[data-zen-class]');
278
- const valueElements = document.querySelectorAll('[data-zen-value]');
279
-
280
- // console.log('[Zenith] Found', classElements.length, ':class bindings and', valueElements.length, ':value bindings');
281
-
282
- // Process :class bindings
283
- classElements.forEach((el) => {
284
- const expression = el.getAttribute('data-zen-class');
285
- if (expression) {
286
- // console.log('[Zenith] Setting up :class binding:', expression, 'for element:', el);
287
- const instanceState = getInstanceStateForElement(el);
288
- const fn = createEnhancedEvaluator(expression, instanceState);
289
-
290
- // Use merged state for initial evaluation
291
- const mergedState = Object.assign({}, stateProxy);
292
- if (instanceState) {
293
- Object.assign(mergedState, instanceState);
294
- }
295
- const result = fn(mergedState);
296
- updateClassBinding(el, result);
297
- bindingElements.push({ el: el, type: 'class', expression, fn, instanceState });
298
- // console.log('[Zenith] Initial :class result:', result, 'applied classes:', el.className);
299
- }
300
- });
301
-
302
- // Process :value bindings
303
- valueElements.forEach((el) => {
304
- const expression = el.getAttribute('data-zen-value');
305
- if (expression) {
306
- // console.log('[Zenith] Setting up :value binding:', expression, 'for element:', el);
307
- const instanceState = getInstanceStateForElement(el);
308
- const fn = createEnhancedEvaluator(expression, instanceState);
309
-
310
- // Use merged state for initial evaluation
311
- const mergedState = Object.assign({}, stateProxy);
312
- if (instanceState) {
313
- Object.assign(mergedState, instanceState);
314
- }
315
- const result = fn(mergedState);
316
- updateValueBinding(el, result);
317
- bindingElements.push({ el: el, type: 'value', expression, fn, instanceState });
318
- // console.log('[Zenith] Initial :value result:', result, 'applied value:', el.value);
319
- }
320
- });
321
-
322
- // Instance state proxies are set up by the text binding runtime
323
- // Attribute binding updates are triggered via window.__zen_update_attribute_bindings
324
- // when instance-scoped state changes
325
-
326
- // console.log('[Zenith] Initialized', bindingElements.length, 'bindings. State object:', stateProxy);
327
- }
328
-
329
- // Initialize when DOM is ready
330
- if (document.readyState === 'loading') {
331
- document.addEventListener('DOMContentLoaded', initializeBindings);
332
- } else {
333
- initializeBindings();
334
- }
335
- })();
336
- `;
337
- }
338
-