@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.
- package/LICENSE +1 -1
- package/README.md +24 -40
- package/bin/zen-build.ts +2 -0
- package/bin/zen-dev.ts +2 -0
- package/bin/zen-preview.ts +2 -0
- package/bin/zenith.ts +2 -0
- package/cli/commands/add.ts +37 -0
- package/cli/commands/build.ts +37 -0
- package/cli/commands/create.ts +702 -0
- package/cli/commands/dev.ts +197 -0
- package/cli/commands/index.ts +112 -0
- package/cli/commands/preview.ts +62 -0
- package/cli/commands/remove.ts +33 -0
- package/cli/index.ts +10 -0
- package/cli/main.ts +101 -0
- package/cli/utils/branding.ts +153 -0
- package/cli/utils/logger.ts +40 -0
- package/cli/utils/plugin-manager.ts +114 -0
- package/cli/utils/project.ts +71 -0
- package/compiler/build-analyzer.ts +122 -0
- package/compiler/discovery/layouts.ts +61 -0
- package/compiler/index.ts +40 -24
- package/compiler/ir/types.ts +1 -0
- package/compiler/parse/parseScript.ts +29 -5
- package/compiler/parse/parseTemplate.ts +96 -58
- package/compiler/parse/scriptAnalysis.ts +77 -0
- package/compiler/runtime/dataExposure.ts +49 -31
- package/compiler/runtime/generateDOM.ts +18 -17
- package/compiler/runtime/generateHydrationBundle.ts +24 -5
- package/compiler/runtime/transformIR.ts +140 -49
- package/compiler/runtime/wrapExpressionWithLoop.ts +11 -11
- package/compiler/spa-build.ts +70 -153
- package/compiler/ssg-build.ts +412 -0
- package/compiler/transform/layoutProcessor.ts +132 -0
- package/compiler/transform/transformNode.ts +19 -19
- package/dist/cli.js +11648 -0
- package/dist/zen-build.js +11659 -0
- package/dist/zen-dev.js +11659 -0
- package/dist/zen-preview.js +11659 -0
- package/dist/zenith.js +11659 -0
- package/package.json +22 -2
- package/runtime/bundle-generator.ts +416 -0
- package/runtime/client-runtime.ts +532 -0
- package/.eslintignore +0 -15
- package/.gitattributes +0 -2
- package/.github/ISSUE_TEMPLATE/compiler-errors-for-invalid-state-declarations.md +0 -25
- package/.github/ISSUE_TEMPLATE/new_ticket.yaml +0 -34
- package/.github/pull_request_template.md +0 -15
- package/.github/workflows/discord-changelog.yml +0 -141
- package/.github/workflows/discord-notify.yml +0 -242
- package/.github/workflows/discord-version.yml +0 -195
- package/.prettierignore +0 -13
- package/.prettierrc +0 -21
- package/.zen.d.ts +0 -15
- package/app/components/Button.zen +0 -46
- package/app/components/Link.zen +0 -11
- package/app/favicon.ico +0 -0
- package/app/layouts/Main.zen +0 -59
- package/app/pages/about.zen +0 -23
- package/app/pages/blog/[id].zen +0 -53
- package/app/pages/blog/index.zen +0 -32
- package/app/pages/dynamic-dx.zen +0 -712
- package/app/pages/dynamic-primitives.zen +0 -453
- package/app/pages/index.zen +0 -154
- package/app/pages/navigation-demo.zen +0 -229
- package/app/pages/posts/[...slug].zen +0 -61
- package/app/pages/primitives-demo.zen +0 -273
- package/assets/logos/0E3B5DDD-605C-4839-BB2E-DFCA8ADC9604.PNG +0 -0
- package/assets/logos/760971E5-79A1-44F9-90B9-925DF30F4278.PNG +0 -0
- package/assets/logos/8A06ED80-9ED2-4689-BCBD-13B2E95EE8E4.JPG +0 -0
- package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.PNG +0 -0
- package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.svg +0 -601
- package/assets/logos/README.md +0 -54
- package/assets/logos/zen.icns +0 -0
- package/bun.lock +0 -39
- package/compiler/legacy/binding.ts +0 -254
- package/compiler/legacy/bindings.ts +0 -338
- package/compiler/legacy/component-process.ts +0 -1208
- package/compiler/legacy/component.ts +0 -301
- package/compiler/legacy/event.ts +0 -50
- package/compiler/legacy/expression.ts +0 -1149
- package/compiler/legacy/mutation.ts +0 -280
- package/compiler/legacy/parse.ts +0 -299
- package/compiler/legacy/split.ts +0 -608
- package/compiler/legacy/types.ts +0 -32
- package/docs/COMMENTS.md +0 -111
- package/docs/COMMITS.md +0 -36
- package/docs/CONTRIBUTING.md +0 -116
- package/docs/STYLEGUIDE.md +0 -62
- package/scripts/webhook-proxy.ts +0 -213
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zenith Client Runtime
|
|
3
|
+
*
|
|
4
|
+
* Shared runtime module served as /runtime.js in dev mode.
|
|
5
|
+
* Includes:
|
|
6
|
+
* - Reactivity primitives (signal, state, effect, memo)
|
|
7
|
+
* - Lifecycle hooks (zenOnMount, zenOnUnmount)
|
|
8
|
+
* - Event wiring
|
|
9
|
+
* - Hydration functions
|
|
10
|
+
*
|
|
11
|
+
* This is a standalone module that can be imported/served separately
|
|
12
|
+
* from page-specific code.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// ============================================
|
|
16
|
+
// Dependency Tracking System
|
|
17
|
+
// ============================================
|
|
18
|
+
|
|
19
|
+
let currentEffect: any = null;
|
|
20
|
+
const effectStack: any[] = [];
|
|
21
|
+
let batchDepth = 0;
|
|
22
|
+
const pendingEffects = new Set<any>();
|
|
23
|
+
|
|
24
|
+
function pushContext(effect: any) {
|
|
25
|
+
effectStack.push(currentEffect);
|
|
26
|
+
currentEffect = effect;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function popContext() {
|
|
30
|
+
currentEffect = effectStack.pop() || null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function trackDependency(subscribers: Set<any>) {
|
|
34
|
+
if (currentEffect) {
|
|
35
|
+
subscribers.add(currentEffect);
|
|
36
|
+
currentEffect.dependencies.add(subscribers);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function notifySubscribers(subscribers: Set<any>) {
|
|
41
|
+
const effects = [...subscribers];
|
|
42
|
+
for (const effect of effects) {
|
|
43
|
+
if (batchDepth > 0) {
|
|
44
|
+
pendingEffects.add(effect);
|
|
45
|
+
} else {
|
|
46
|
+
effect.run();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function cleanupEffect(effect: any) {
|
|
52
|
+
for (const deps of effect.dependencies) {
|
|
53
|
+
deps.delete(effect);
|
|
54
|
+
}
|
|
55
|
+
effect.dependencies.clear();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ============================================
|
|
59
|
+
// zenSignal - Atomic reactive value
|
|
60
|
+
// ============================================
|
|
61
|
+
|
|
62
|
+
export function zenSignal<T>(initialValue: T): (newValue?: T) => T {
|
|
63
|
+
let value = initialValue;
|
|
64
|
+
const subscribers = new Set<any>();
|
|
65
|
+
|
|
66
|
+
function signal(newValue?: T): T {
|
|
67
|
+
if (arguments.length === 0) {
|
|
68
|
+
trackDependency(subscribers);
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
if (newValue !== value) {
|
|
72
|
+
value = newValue as T;
|
|
73
|
+
notifySubscribers(subscribers);
|
|
74
|
+
}
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return signal;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ============================================
|
|
82
|
+
// zenState - Deep reactive object with Proxy
|
|
83
|
+
// ============================================
|
|
84
|
+
|
|
85
|
+
export function zenState<T extends object>(initialObj: T): T {
|
|
86
|
+
const subscribers = new Map<string, Set<any>>();
|
|
87
|
+
|
|
88
|
+
function getSubscribers(path: string): Set<any> {
|
|
89
|
+
if (!subscribers.has(path)) {
|
|
90
|
+
subscribers.set(path, new Set());
|
|
91
|
+
}
|
|
92
|
+
return subscribers.get(path)!;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function createProxy(obj: any, parentPath: string = ''): any {
|
|
96
|
+
if (obj === null || typeof obj !== 'object') {
|
|
97
|
+
return obj;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return new Proxy(obj, {
|
|
101
|
+
get(target, prop) {
|
|
102
|
+
if (typeof prop === 'symbol') return target[prop];
|
|
103
|
+
|
|
104
|
+
const path = parentPath ? `${parentPath}.${String(prop)}` : String(prop);
|
|
105
|
+
trackDependency(getSubscribers(path));
|
|
106
|
+
|
|
107
|
+
const value = target[prop];
|
|
108
|
+
if (value !== null && typeof value === 'object') {
|
|
109
|
+
return createProxy(value, path);
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
set(target, prop, newValue) {
|
|
115
|
+
if (typeof prop === 'symbol') {
|
|
116
|
+
target[prop] = newValue;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const path = parentPath ? `${parentPath}.${String(prop)}` : String(prop);
|
|
121
|
+
const oldValue = target[prop];
|
|
122
|
+
|
|
123
|
+
if (oldValue !== newValue) {
|
|
124
|
+
target[prop] = newValue;
|
|
125
|
+
|
|
126
|
+
// Notify this path
|
|
127
|
+
const subs = subscribers.get(path);
|
|
128
|
+
if (subs) notifySubscribers(subs);
|
|
129
|
+
|
|
130
|
+
// Notify parent paths
|
|
131
|
+
const parts = path.split('.');
|
|
132
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
133
|
+
const parentPath = parts.slice(0, i).join('.');
|
|
134
|
+
if (parentPath) {
|
|
135
|
+
const parentSubs = subscribers.get(parentPath);
|
|
136
|
+
if (parentSubs) notifySubscribers(parentSubs);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return createProxy(initialObj);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ============================================
|
|
149
|
+
// zenEffect - Reactive effect
|
|
150
|
+
// ============================================
|
|
151
|
+
|
|
152
|
+
export function zenEffect(fn: () => void | (() => void)): () => void {
|
|
153
|
+
let cleanup: (() => void) | void;
|
|
154
|
+
|
|
155
|
+
const effect = {
|
|
156
|
+
dependencies: new Set<Set<any>>(),
|
|
157
|
+
run() {
|
|
158
|
+
cleanupEffect(effect);
|
|
159
|
+
pushContext(effect);
|
|
160
|
+
try {
|
|
161
|
+
if (cleanup) cleanup();
|
|
162
|
+
cleanup = fn();
|
|
163
|
+
} finally {
|
|
164
|
+
popContext();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
effect.run();
|
|
170
|
+
|
|
171
|
+
return () => {
|
|
172
|
+
cleanupEffect(effect);
|
|
173
|
+
if (cleanup) cleanup();
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ============================================
|
|
178
|
+
// zenMemo - Computed/derived value
|
|
179
|
+
// ============================================
|
|
180
|
+
|
|
181
|
+
export function zenMemo<T>(fn: () => T): () => T {
|
|
182
|
+
let value: T;
|
|
183
|
+
let dirty = true;
|
|
184
|
+
const subscribers = new Set<any>();
|
|
185
|
+
|
|
186
|
+
const effect = {
|
|
187
|
+
dependencies: new Set<Set<any>>(),
|
|
188
|
+
run() {
|
|
189
|
+
cleanupEffect(effect);
|
|
190
|
+
pushContext(effect);
|
|
191
|
+
try {
|
|
192
|
+
value = fn();
|
|
193
|
+
dirty = false;
|
|
194
|
+
notifySubscribers(subscribers);
|
|
195
|
+
} finally {
|
|
196
|
+
popContext();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
return () => {
|
|
202
|
+
trackDependency(subscribers);
|
|
203
|
+
if (dirty) {
|
|
204
|
+
effect.run();
|
|
205
|
+
}
|
|
206
|
+
return value;
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ============================================
|
|
211
|
+
// zenRef - Non-reactive mutable container
|
|
212
|
+
// ============================================
|
|
213
|
+
|
|
214
|
+
export function zenRef<T>(initialValue?: T): { current: T | null } {
|
|
215
|
+
return { current: initialValue !== undefined ? initialValue : null };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ============================================
|
|
219
|
+
// zenBatch - Batch updates
|
|
220
|
+
// ============================================
|
|
221
|
+
|
|
222
|
+
export function zenBatch(fn: () => void): void {
|
|
223
|
+
batchDepth++;
|
|
224
|
+
try {
|
|
225
|
+
fn();
|
|
226
|
+
} finally {
|
|
227
|
+
batchDepth--;
|
|
228
|
+
if (batchDepth === 0) {
|
|
229
|
+
const effects = [...pendingEffects];
|
|
230
|
+
pendingEffects.clear();
|
|
231
|
+
for (const effect of effects) {
|
|
232
|
+
effect.run();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ============================================
|
|
239
|
+
// zenUntrack - Read without tracking
|
|
240
|
+
// ============================================
|
|
241
|
+
|
|
242
|
+
export function zenUntrack<T>(fn: () => T): T {
|
|
243
|
+
const prevEffect = currentEffect;
|
|
244
|
+
currentEffect = null;
|
|
245
|
+
try {
|
|
246
|
+
return fn();
|
|
247
|
+
} finally {
|
|
248
|
+
currentEffect = prevEffect;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ============================================
|
|
253
|
+
// Lifecycle Hooks
|
|
254
|
+
// ============================================
|
|
255
|
+
|
|
256
|
+
const mountCallbacks: Array<() => void | (() => void)> = [];
|
|
257
|
+
const unmountCallbacks: Array<() => void> = [];
|
|
258
|
+
let isMounted = false;
|
|
259
|
+
|
|
260
|
+
export function zenOnMount(fn: () => void | (() => void)): void {
|
|
261
|
+
if (isMounted) {
|
|
262
|
+
const cleanup = fn();
|
|
263
|
+
if (typeof cleanup === 'function') {
|
|
264
|
+
unmountCallbacks.push(cleanup);
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
mountCallbacks.push(fn);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function zenOnUnmount(fn: () => void): void {
|
|
272
|
+
unmountCallbacks.push(fn);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function triggerMount(): void {
|
|
276
|
+
isMounted = true;
|
|
277
|
+
for (const cb of mountCallbacks) {
|
|
278
|
+
const cleanup = cb();
|
|
279
|
+
if (typeof cleanup === 'function') {
|
|
280
|
+
unmountCallbacks.push(cleanup);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
mountCallbacks.length = 0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function triggerUnmount(): void {
|
|
287
|
+
isMounted = false;
|
|
288
|
+
for (const cb of unmountCallbacks) {
|
|
289
|
+
try { cb(); } catch (e) { console.error('[Zenith] Unmount error:', e); }
|
|
290
|
+
}
|
|
291
|
+
unmountCallbacks.length = 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ============================================
|
|
295
|
+
// Expression Registry
|
|
296
|
+
// ============================================
|
|
297
|
+
|
|
298
|
+
const expressionRegistry = new Map<string, (state: any) => any>();
|
|
299
|
+
|
|
300
|
+
export function registerExpression(id: string, fn: (state: any) => any): void {
|
|
301
|
+
expressionRegistry.set(id, fn);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function getExpression(id: string): ((state: any) => any) | undefined {
|
|
305
|
+
return expressionRegistry.get(id);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ============================================
|
|
309
|
+
// Hydration Functions
|
|
310
|
+
// ============================================
|
|
311
|
+
|
|
312
|
+
const bindings: Array<{ node: Element; type: string; expressionId: string; attributeName?: string }> = [];
|
|
313
|
+
|
|
314
|
+
export function hydrate(state: any, container?: Element | Document): void {
|
|
315
|
+
const root = container || document;
|
|
316
|
+
|
|
317
|
+
// Clear existing bindings
|
|
318
|
+
bindings.length = 0;
|
|
319
|
+
|
|
320
|
+
// Find all text expression placeholders
|
|
321
|
+
const textPlaceholders = root.querySelectorAll('[data-zen-text]');
|
|
322
|
+
textPlaceholders.forEach((node) => {
|
|
323
|
+
const expressionId = node.getAttribute('data-zen-text');
|
|
324
|
+
if (!expressionId) return;
|
|
325
|
+
|
|
326
|
+
bindings.push({ node: node as Element, type: 'text', expressionId });
|
|
327
|
+
updateTextBinding(node as Element, expressionId, state);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Find attribute bindings
|
|
331
|
+
const attrSelectors = ['class', 'style', 'src', 'href', 'disabled', 'checked'];
|
|
332
|
+
for (const attr of attrSelectors) {
|
|
333
|
+
const attrPlaceholders = root.querySelectorAll(`[data-zen-attr-${attr}]`);
|
|
334
|
+
attrPlaceholders.forEach((node) => {
|
|
335
|
+
const expressionId = node.getAttribute(`data-zen-attr-${attr}`);
|
|
336
|
+
if (!expressionId) return;
|
|
337
|
+
|
|
338
|
+
bindings.push({ node: node as Element, type: 'attribute', expressionId, attributeName: attr });
|
|
339
|
+
updateAttributeBinding(node as Element, attr, expressionId, state);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Bind event handlers
|
|
344
|
+
bindEvents(root);
|
|
345
|
+
|
|
346
|
+
// Trigger mount
|
|
347
|
+
triggerMount();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function updateTextBinding(node: Element, expressionId: string, state: any): void {
|
|
351
|
+
const expression = expressionRegistry.get(expressionId);
|
|
352
|
+
if (!expression) {
|
|
353
|
+
console.warn(`[Zenith] Expression ${expressionId} not found`);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
const result = expression(state);
|
|
359
|
+
if (result === null || result === undefined || result === false) {
|
|
360
|
+
node.textContent = '';
|
|
361
|
+
} else {
|
|
362
|
+
node.textContent = String(result);
|
|
363
|
+
}
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.error(`[Zenith] Error evaluating expression ${expressionId}:`, error);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function updateAttributeBinding(element: Element, attrName: string, expressionId: string, state: any): void {
|
|
370
|
+
const expression = expressionRegistry.get(expressionId);
|
|
371
|
+
if (!expression) return;
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
const result = expression(state);
|
|
375
|
+
|
|
376
|
+
if (attrName === 'class' || attrName === 'className') {
|
|
377
|
+
(element as HTMLElement).className = String(result ?? '');
|
|
378
|
+
} else if (attrName === 'style' && typeof result === 'object') {
|
|
379
|
+
const styleStr = Object.entries(result).map(([k, v]) => `${k}: ${v}`).join('; ');
|
|
380
|
+
element.setAttribute('style', styleStr);
|
|
381
|
+
} else if (['disabled', 'checked', 'readonly'].includes(attrName)) {
|
|
382
|
+
if (result) {
|
|
383
|
+
element.setAttribute(attrName, '');
|
|
384
|
+
} else {
|
|
385
|
+
element.removeAttribute(attrName);
|
|
386
|
+
}
|
|
387
|
+
} else {
|
|
388
|
+
if (result === null || result === undefined || result === false) {
|
|
389
|
+
element.removeAttribute(attrName);
|
|
390
|
+
} else {
|
|
391
|
+
element.setAttribute(attrName, String(result));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
} catch (error) {
|
|
395
|
+
console.error(`[Zenith] Error updating attribute ${attrName}:`, error);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export function update(state: any): void {
|
|
400
|
+
for (const binding of bindings) {
|
|
401
|
+
if (binding.type === 'text') {
|
|
402
|
+
updateTextBinding(binding.node, binding.expressionId, state);
|
|
403
|
+
} else if (binding.type === 'attribute' && binding.attributeName) {
|
|
404
|
+
updateAttributeBinding(binding.node, binding.attributeName, binding.expressionId, state);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function bindEvents(container: Element | Document): void {
|
|
410
|
+
const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur', 'keyup', 'keydown'];
|
|
411
|
+
|
|
412
|
+
for (const eventType of eventTypes) {
|
|
413
|
+
const elements = container.querySelectorAll(`[data-zen-${eventType}]`);
|
|
414
|
+
|
|
415
|
+
elements.forEach((element) => {
|
|
416
|
+
const handlerName = element.getAttribute(`data-zen-${eventType}`);
|
|
417
|
+
if (!handlerName) return;
|
|
418
|
+
|
|
419
|
+
// Remove existing handler if any
|
|
420
|
+
const handlerKey = `__zen_${eventType}_handler`;
|
|
421
|
+
const existingHandler = (element as any)[handlerKey];
|
|
422
|
+
if (existingHandler) {
|
|
423
|
+
element.removeEventListener(eventType, existingHandler);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Create new handler
|
|
427
|
+
const handler = (event: Event) => {
|
|
428
|
+
try {
|
|
429
|
+
// Try window first, then expression registry
|
|
430
|
+
let handlerFunc = (window as any)[handlerName];
|
|
431
|
+
if (typeof handlerFunc !== 'function') {
|
|
432
|
+
handlerFunc = (window as any).__ZENITH_EXPRESSIONS__?.get(handlerName);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (typeof handlerFunc === 'function') {
|
|
436
|
+
handlerFunc(event, element);
|
|
437
|
+
} else {
|
|
438
|
+
console.warn(`[Zenith] Event handler "${handlerName}" not found`);
|
|
439
|
+
}
|
|
440
|
+
} catch (error) {
|
|
441
|
+
console.error(`[Zenith] Error executing handler "${handlerName}":`, error);
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
(element as any)[handlerKey] = handler;
|
|
446
|
+
element.addEventListener(eventType, handler);
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export function cleanup(container?: Element | Document): void {
|
|
452
|
+
const root = container || document;
|
|
453
|
+
const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur', 'keyup', 'keydown'];
|
|
454
|
+
|
|
455
|
+
for (const eventType of eventTypes) {
|
|
456
|
+
const elements = root.querySelectorAll(`[data-zen-${eventType}]`);
|
|
457
|
+
elements.forEach((element) => {
|
|
458
|
+
const handlerKey = `__zen_${eventType}_handler`;
|
|
459
|
+
const handler = (element as any)[handlerKey];
|
|
460
|
+
if (handler) {
|
|
461
|
+
element.removeEventListener(eventType, handler);
|
|
462
|
+
delete (element as any)[handlerKey];
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
bindings.length = 0;
|
|
468
|
+
triggerUnmount();
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ============================================
|
|
472
|
+
// Browser Globals Setup
|
|
473
|
+
// ============================================
|
|
474
|
+
|
|
475
|
+
export function setupGlobals(): void {
|
|
476
|
+
if (typeof window === 'undefined') return;
|
|
477
|
+
|
|
478
|
+
const w = window as any;
|
|
479
|
+
|
|
480
|
+
// Zenith namespace
|
|
481
|
+
w.__zenith = {
|
|
482
|
+
signal: zenSignal,
|
|
483
|
+
state: zenState,
|
|
484
|
+
effect: zenEffect,
|
|
485
|
+
memo: zenMemo,
|
|
486
|
+
ref: zenRef,
|
|
487
|
+
batch: zenBatch,
|
|
488
|
+
untrack: zenUntrack,
|
|
489
|
+
onMount: zenOnMount,
|
|
490
|
+
onUnmount: zenOnUnmount,
|
|
491
|
+
triggerMount,
|
|
492
|
+
triggerUnmount
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
// Expression registry
|
|
496
|
+
w.__ZENITH_EXPRESSIONS__ = expressionRegistry;
|
|
497
|
+
|
|
498
|
+
// Hydration functions
|
|
499
|
+
w.__zenith_hydrate = hydrate;
|
|
500
|
+
w.__zenith_update = update;
|
|
501
|
+
w.__zenith_bindEvents = bindEvents;
|
|
502
|
+
w.__zenith_cleanup = cleanup;
|
|
503
|
+
w.zenithHydrate = hydrate;
|
|
504
|
+
w.zenithUpdate = update;
|
|
505
|
+
w.zenithBindEvents = bindEvents;
|
|
506
|
+
w.zenithCleanup = cleanup;
|
|
507
|
+
|
|
508
|
+
// Direct primitives
|
|
509
|
+
w.zenSignal = zenSignal;
|
|
510
|
+
w.zenState = zenState;
|
|
511
|
+
w.zenEffect = zenEffect;
|
|
512
|
+
w.zenMemo = zenMemo;
|
|
513
|
+
w.zenRef = zenRef;
|
|
514
|
+
w.zenBatch = zenBatch;
|
|
515
|
+
w.zenUntrack = zenUntrack;
|
|
516
|
+
w.zenOnMount = zenOnMount;
|
|
517
|
+
w.zenOnUnmount = zenOnUnmount;
|
|
518
|
+
|
|
519
|
+
// Short aliases
|
|
520
|
+
w.signal = zenSignal;
|
|
521
|
+
w.state = zenState;
|
|
522
|
+
w.effect = zenEffect;
|
|
523
|
+
w.memo = zenMemo;
|
|
524
|
+
w.ref = zenRef;
|
|
525
|
+
w.batch = zenBatch;
|
|
526
|
+
w.untrack = zenUntrack;
|
|
527
|
+
w.onMount = zenOnMount;
|
|
528
|
+
w.onUnmount = zenOnUnmount;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Auto-setup globals on import
|
|
532
|
+
setupGlobals();
|
package/.eslintignore
DELETED
package/.gitattributes
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Compiler errors for invalid state declarations and usage
|
|
2
|
-
|
|
3
|
-
## Description
|
|
4
|
-
Add compiler enforcement for the following invalid scenarios:
|
|
5
|
-
- Use of undeclared state variables: `{{ unknown }}`.
|
|
6
|
-
- Use of expressions in placeholders: `{{ count + 1 }}`.
|
|
7
|
-
- Invalid state initialization: `state count = count + 1;`.
|
|
8
|
-
- State mutations outside allowed event handlers.
|
|
9
|
-
|
|
10
|
-
## Acceptance Criteria
|
|
11
|
-
|
|
12
|
-
1. Compiler throws errors for undeclared state usage in placeholders.
|
|
13
|
-
2. Compiler disallows expressions within placeholders.
|
|
14
|
-
3. Errors are thrown for invalid state initialization logic.
|
|
15
|
-
4. State mutations are only allowed inside event handlers.
|
|
16
|
-
5. Test cases ensure all invalid scenarios are caught at compile time.
|
|
17
|
-
|
|
18
|
-
### Example:
|
|
19
|
-
```html
|
|
20
|
-
<script>
|
|
21
|
-
state count = 5;
|
|
22
|
-
state other = count + 1; // Error
|
|
23
|
-
</script>
|
|
24
|
-
<p>{{ count + 1 }}</p> <!-- Error -->
|
|
25
|
-
<p>{{ unknown }}</p> <!-- Error -->
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
name: "\U0001F4A1 New Issue"
|
|
2
|
-
description: Suggest a new task
|
|
3
|
-
|
|
4
|
-
title: 'nn-your-issue'
|
|
5
|
-
|
|
6
|
-
body:
|
|
7
|
-
- type: markdown
|
|
8
|
-
attributes:
|
|
9
|
-
value: |
|
|
10
|
-
_Thank you for taking the time to propose a new idea_
|
|
11
|
-
|
|
12
|
-
- type: textarea
|
|
13
|
-
id: problem
|
|
14
|
-
attributes:
|
|
15
|
-
label: '<h2>Describe the Problem</h2>'
|
|
16
|
-
description: Please provide a short user story **DESCRIPTION** of the **PROBLEM**
|
|
17
|
-
placeholder: As a user I...
|
|
18
|
-
validations:
|
|
19
|
-
required: true
|
|
20
|
-
|
|
21
|
-
- type: textarea
|
|
22
|
-
id: solution
|
|
23
|
-
attributes:
|
|
24
|
-
label: '<h2>Acceptance Criteria</h2>'
|
|
25
|
-
description: Please provide the REQUIREMENTS that must be fulfilled to consider this issue resolved.
|
|
26
|
-
placeholder: X must show Y when Z...
|
|
27
|
-
validations:
|
|
28
|
-
required: true
|
|
29
|
-
|
|
30
|
-
- type: textarea
|
|
31
|
-
id: references
|
|
32
|
-
attributes:
|
|
33
|
-
label: '<h2>References</h2>'
|
|
34
|
-
description: Please provide any **SCREENSHOTS**, **CODE SNIPPETS** or **LINKS** to the codebase or documentation which will help implement a solution.
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# 🚀 Summary
|
|
2
|
-
|
|
3
|
-
This merge implements/resolves... (include relevant ticket)
|
|
4
|
-
|
|
5
|
-
## 📝 How can we Reproduce/Test?
|
|
6
|
-
|
|
7
|
-
Please provide instructions so we can reproduce:
|
|
8
|
-
|
|
9
|
-
- [ ] Step 1
|
|
10
|
-
- [ ] Step 2
|
|
11
|
-
- [ ] ...
|
|
12
|
-
|
|
13
|
-
## 📸 Any screenshots or links to points in your code or references elsewhere as needed
|
|
14
|
-
|
|
15
|
-
Please delete if not relevant.
|