@zenithbuild/runtime 0.5.0-beta.2.6 → 0.6.2
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/HYDRATION_CONTRACT.md +3 -0
- package/README.md +6 -0
- package/RUNTIME_CONTRACT.md +3 -0
- package/dist/env.js +22 -0
- package/dist/hydrate.js +223 -43
- package/dist/index.js +2 -0
- package/dist/platform.js +102 -0
- package/dist/signal.js +4 -0
- package/dist/state.js +3 -0
- package/dist/template.js +93 -6
- package/package.json +1 -1
package/HYDRATION_CONTRACT.md
CHANGED
package/README.md
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
The core runtime library for the Zenith framework.
|
|
7
7
|
|
|
8
|
+
## Canonical Docs
|
|
9
|
+
|
|
10
|
+
- Runtime contract: `../zenith-docs/documentation/contracts/runtime-contract.md`
|
|
11
|
+
- Hydration contract: `../zenith-docs/documentation/contracts/hydration-contract.md`
|
|
12
|
+
- Reactive binding model: `../zenith-docs/documentation/reference/reactive-binding-model.md`
|
|
13
|
+
|
|
8
14
|
## Overview
|
|
9
15
|
This package provides the reactivity system, hydration logic, and Virtual DOM primitives used by Zenith applications. It is designed to be lightweight, fast, and tree-shakeable.
|
|
10
16
|
|
package/RUNTIME_CONTRACT.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# RUNTIME_CONTRACT.md — Sealed Runtime Interface
|
|
2
2
|
|
|
3
|
+
Canonical public docs: `../zenith-docs/documentation/contracts/runtime-contract.md`
|
|
4
|
+
|
|
5
|
+
|
|
3
6
|
> **This document is a legal boundary.**
|
|
4
7
|
> The runtime is a consumer of bundler output.
|
|
5
8
|
> It does not reinterpret, normalize, or extend.
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// env.js — Zenith Runtime canonical environment accessors
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// SSR-safe access to window and document. Returns null when not in browser.
|
|
5
|
+
// Use zenWindow() / zenDocument() instead of direct window/document access.
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* SSR-safe window accessor.
|
|
10
|
+
* @returns {Window | null}
|
|
11
|
+
*/
|
|
12
|
+
export function zenWindow() {
|
|
13
|
+
return typeof window === 'undefined' ? null : window;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* SSR-safe document accessor.
|
|
18
|
+
* @returns {Document | null}
|
|
19
|
+
*/
|
|
20
|
+
export function zenDocument() {
|
|
21
|
+
return typeof document === 'undefined' ? null : document;
|
|
22
|
+
}
|
package/dist/hydrate.js
CHANGED
|
@@ -13,6 +13,7 @@ import { signal } from './signal.js';
|
|
|
13
13
|
import { state } from './state.js';
|
|
14
14
|
import {
|
|
15
15
|
zeneffect,
|
|
16
|
+
zenEffect,
|
|
16
17
|
zenMount,
|
|
17
18
|
createSideEffectScope,
|
|
18
19
|
activateSideEffectScope,
|
|
@@ -32,8 +33,6 @@ const BOOLEAN_ATTRIBUTES = new Set([
|
|
|
32
33
|
const STRICT_MEMBER_CHAIN_LITERAL_RE = /^(?:true|false|null|undefined|[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*)$/;
|
|
33
34
|
const UNSAFE_MEMBER_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
|
|
34
35
|
|
|
35
|
-
const COMPILED_LITERAL_CACHE = new Map();
|
|
36
|
-
|
|
37
36
|
/**
|
|
38
37
|
* Hydrate a pre-rendered DOM tree using explicit payload tables.
|
|
39
38
|
*
|
|
@@ -43,6 +42,7 @@ const COMPILED_LITERAL_CACHE = new Map();
|
|
|
43
42
|
* expressions: Array<{ marker_index: number, signal_index?: number|null, state_index?: number|null, component_instance?: string|null, component_binding?: string|null, literal?: string|null }>,
|
|
44
43
|
* markers: Array<{ index: number, kind: 'text' | 'attr' | 'event', selector: string, attr?: string }>,
|
|
45
44
|
* events: Array<{ index: number, event: string, selector: string }>,
|
|
45
|
+
* refs?: Array<{ index: number, state_index: number, selector: string }>,
|
|
46
46
|
* state_values: Array<*>,
|
|
47
47
|
* state_keys?: Array<string>,
|
|
48
48
|
* signals: Array<{ id: number, kind: 'signal', state_index: number }>,
|
|
@@ -55,7 +55,22 @@ export function hydrate(payload) {
|
|
|
55
55
|
try {
|
|
56
56
|
const normalized = _validatePayload(payload);
|
|
57
57
|
_deepFreezePayload(payload);
|
|
58
|
-
const {
|
|
58
|
+
const {
|
|
59
|
+
root,
|
|
60
|
+
expressions,
|
|
61
|
+
markers,
|
|
62
|
+
events,
|
|
63
|
+
refs,
|
|
64
|
+
stateValues,
|
|
65
|
+
stateKeys,
|
|
66
|
+
signals,
|
|
67
|
+
components,
|
|
68
|
+
route,
|
|
69
|
+
params,
|
|
70
|
+
ssrData,
|
|
71
|
+
props,
|
|
72
|
+
exprFns
|
|
73
|
+
} = normalized;
|
|
59
74
|
|
|
60
75
|
const componentBindings = Object.create(null);
|
|
61
76
|
|
|
@@ -72,6 +87,23 @@ export function hydrate(payload) {
|
|
|
72
87
|
signalMap.set(i, candidate);
|
|
73
88
|
}
|
|
74
89
|
|
|
90
|
+
const hydratedRefs = [];
|
|
91
|
+
for (let i = 0; i < refs.length; i++) {
|
|
92
|
+
const refBinding = refs[i];
|
|
93
|
+
const targetRef = stateValues[refBinding.state_index];
|
|
94
|
+
const nodes = _resolveNodes(root, refBinding.selector, refBinding.index, 'ref');
|
|
95
|
+
targetRef.current = nodes[0] || null;
|
|
96
|
+
hydratedRefs.push(targetRef);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (hydratedRefs.length > 0) {
|
|
100
|
+
_registerDisposer(() => {
|
|
101
|
+
for (let i = 0; i < hydratedRefs.length; i++) {
|
|
102
|
+
hydratedRefs[i].current = null;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
75
107
|
for (let i = 0; i < components.length; i++) {
|
|
76
108
|
const component = components[i];
|
|
77
109
|
const resolvedProps = Object.freeze(_resolveComponentProps(component.props || [], signalMap, {
|
|
@@ -149,7 +181,8 @@ export function hydrate(payload) {
|
|
|
149
181
|
params,
|
|
150
182
|
ssrData,
|
|
151
183
|
marker.kind,
|
|
152
|
-
props
|
|
184
|
+
props,
|
|
185
|
+
exprFns
|
|
153
186
|
);
|
|
154
187
|
_applyMarkerValue(nodes, marker, value);
|
|
155
188
|
}
|
|
@@ -176,7 +209,8 @@ export function hydrate(payload) {
|
|
|
176
209
|
params,
|
|
177
210
|
ssrData,
|
|
178
211
|
marker.kind,
|
|
179
|
-
props
|
|
212
|
+
props,
|
|
213
|
+
exprFns
|
|
180
214
|
);
|
|
181
215
|
_applyMarkerValue(nodes, marker, value);
|
|
182
216
|
}
|
|
@@ -184,13 +218,17 @@ export function hydrate(payload) {
|
|
|
184
218
|
const dependentMarkersBySignal = new Map();
|
|
185
219
|
for (let i = 0; i < expressions.length; i++) {
|
|
186
220
|
const expression = expressions[i];
|
|
187
|
-
|
|
221
|
+
const signalIndices = _resolveExpressionSignalIndices(expression);
|
|
222
|
+
if (signalIndices.length === 0) {
|
|
188
223
|
continue;
|
|
189
224
|
}
|
|
190
|
-
|
|
191
|
-
|
|
225
|
+
for (let j = 0; j < signalIndices.length; j++) {
|
|
226
|
+
const signalIndex = signalIndices[j];
|
|
227
|
+
if (!dependentMarkersBySignal.has(signalIndex)) {
|
|
228
|
+
dependentMarkersBySignal.set(signalIndex, []);
|
|
229
|
+
}
|
|
230
|
+
dependentMarkersBySignal.get(signalIndex).push(expression.marker_index);
|
|
192
231
|
}
|
|
193
|
-
dependentMarkersBySignal.get(expression.signal_index).push(expression.marker_index);
|
|
194
232
|
}
|
|
195
233
|
|
|
196
234
|
for (const [signalId, markerIndexes] of dependentMarkersBySignal.entries()) {
|
|
@@ -246,6 +284,7 @@ export function hydrate(payload) {
|
|
|
246
284
|
}
|
|
247
285
|
|
|
248
286
|
const eventIndices = new Set();
|
|
287
|
+
const escDispatchEntries = [];
|
|
249
288
|
for (let i = 0; i < events.length; i++) {
|
|
250
289
|
const eventBinding = events[i];
|
|
251
290
|
if (eventIndices.has(eventBinding.index)) {
|
|
@@ -262,7 +301,9 @@ export function hydrate(payload) {
|
|
|
262
301
|
componentBindings,
|
|
263
302
|
params,
|
|
264
303
|
ssrData,
|
|
265
|
-
'event'
|
|
304
|
+
'event',
|
|
305
|
+
props || {},
|
|
306
|
+
exprFns
|
|
266
307
|
);
|
|
267
308
|
if (typeof handler !== 'function') {
|
|
268
309
|
throwZenithRuntimeError({
|
|
@@ -291,11 +332,67 @@ export function hydrate(payload) {
|
|
|
291
332
|
});
|
|
292
333
|
}
|
|
293
334
|
};
|
|
335
|
+
if (eventBinding.event === 'esc') {
|
|
336
|
+
escDispatchEntries.push({
|
|
337
|
+
node,
|
|
338
|
+
handler: wrappedHandler
|
|
339
|
+
});
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
294
342
|
node.addEventListener(eventBinding.event, wrappedHandler);
|
|
295
343
|
_registerListener(node, eventBinding.event, wrappedHandler);
|
|
296
344
|
}
|
|
297
345
|
}
|
|
298
346
|
|
|
347
|
+
if (escDispatchEntries.length > 0) {
|
|
348
|
+
const doc = root && root.ownerDocument ? root.ownerDocument : (typeof document !== 'undefined' ? document : null);
|
|
349
|
+
if (doc && typeof doc.addEventListener === 'function') {
|
|
350
|
+
const escDispatchListener = function zenithEscDispatch(event) {
|
|
351
|
+
if (!event || event.key !== 'Escape') {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const activeElement = doc.activeElement || null;
|
|
356
|
+
let targetEntry = null;
|
|
357
|
+
|
|
358
|
+
if (activeElement && activeElement !== doc.body && activeElement !== doc.documentElement) {
|
|
359
|
+
for (let i = escDispatchEntries.length - 1; i >= 0; i--) {
|
|
360
|
+
const entry = escDispatchEntries[i];
|
|
361
|
+
if (!entry || !entry.node) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (!entry.node.isConnected) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (typeof entry.node.contains === 'function' && entry.node.contains(activeElement)) {
|
|
368
|
+
targetEntry = entry;
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (!targetEntry && (activeElement === null || activeElement === doc.body || activeElement === doc.documentElement)) {
|
|
375
|
+
for (let i = escDispatchEntries.length - 1; i >= 0; i--) {
|
|
376
|
+
const entry = escDispatchEntries[i];
|
|
377
|
+
if (!entry || !entry.node || !entry.node.isConnected) {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
targetEntry = entry;
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (!targetEntry) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return targetEntry.handler.call(targetEntry.node, event);
|
|
390
|
+
};
|
|
391
|
+
doc.addEventListener('keydown', escDispatchListener);
|
|
392
|
+
_registerListener(doc, 'keydown', escDispatchListener);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
299
396
|
return cleanup;
|
|
300
397
|
} catch (error) {
|
|
301
398
|
rethrowZenithRuntimeError(error, {
|
|
@@ -336,6 +433,8 @@ function _validatePayload(payload) {
|
|
|
336
433
|
throw new Error('[Zenith Runtime] hydrate(payload) requires events[]');
|
|
337
434
|
}
|
|
338
435
|
|
|
436
|
+
const refs = Array.isArray(payload.refs) ? payload.refs : [];
|
|
437
|
+
|
|
339
438
|
const stateValues = payload.state_values;
|
|
340
439
|
if (!Array.isArray(stateValues)) {
|
|
341
440
|
throw new Error('[Zenith Runtime] hydrate(payload) requires state_values[]');
|
|
@@ -365,6 +464,7 @@ function _validatePayload(payload) {
|
|
|
365
464
|
const ssrData = payload.ssr_data && typeof payload.ssr_data === 'object'
|
|
366
465
|
? payload.ssr_data
|
|
367
466
|
: {};
|
|
467
|
+
const exprFns = Array.isArray(payload.expr_fns) ? payload.expr_fns : [];
|
|
368
468
|
|
|
369
469
|
if (markers.length !== expressions.length) {
|
|
370
470
|
throw new Error(
|
|
@@ -385,6 +485,23 @@ function _validatePayload(payload) {
|
|
|
385
485
|
`[Zenith Runtime] expression table out of order at position ${i}: marker_index=${expression.marker_index}`
|
|
386
486
|
);
|
|
387
487
|
}
|
|
488
|
+
if (expression.fn_index !== undefined && expression.fn_index !== null) {
|
|
489
|
+
if (!Number.isInteger(expression.fn_index) || expression.fn_index < 0) {
|
|
490
|
+
throw new Error(`[Zenith Runtime] expression at position ${i} has invalid fn_index`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (expression.signal_indices !== undefined) {
|
|
494
|
+
if (!Array.isArray(expression.signal_indices)) {
|
|
495
|
+
throw new Error(`[Zenith Runtime] expression at position ${i} must provide signal_indices[]`);
|
|
496
|
+
}
|
|
497
|
+
for (let j = 0; j < expression.signal_indices.length; j++) {
|
|
498
|
+
if (!Number.isInteger(expression.signal_indices[j]) || expression.signal_indices[j] < 0) {
|
|
499
|
+
throw new Error(
|
|
500
|
+
`[Zenith Runtime] expression at position ${i} has invalid signal_indices[${j}]`
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
388
505
|
}
|
|
389
506
|
|
|
390
507
|
for (let i = 0; i < markers.length; i++) {
|
|
@@ -425,6 +542,34 @@ function _validatePayload(payload) {
|
|
|
425
542
|
}
|
|
426
543
|
}
|
|
427
544
|
|
|
545
|
+
for (let i = 0; i < refs.length; i++) {
|
|
546
|
+
const refBinding = refs[i];
|
|
547
|
+
if (!refBinding || typeof refBinding !== 'object' || Array.isArray(refBinding)) {
|
|
548
|
+
throw new Error(`[Zenith Runtime] ref binding at position ${i} must be an object`);
|
|
549
|
+
}
|
|
550
|
+
if (!Number.isInteger(refBinding.index) || refBinding.index < 0) {
|
|
551
|
+
throw new Error(`[Zenith Runtime] ref binding at position ${i} requires non-negative index`);
|
|
552
|
+
}
|
|
553
|
+
if (
|
|
554
|
+
!Number.isInteger(refBinding.state_index) ||
|
|
555
|
+
refBinding.state_index < 0 ||
|
|
556
|
+
refBinding.state_index >= stateValues.length
|
|
557
|
+
) {
|
|
558
|
+
throw new Error(
|
|
559
|
+
`[Zenith Runtime] ref binding at position ${i} has out-of-bounds state_index`
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
if (typeof refBinding.selector !== 'string' || refBinding.selector.length === 0) {
|
|
563
|
+
throw new Error(`[Zenith Runtime] ref binding at position ${i} requires selector`);
|
|
564
|
+
}
|
|
565
|
+
const candidate = stateValues[refBinding.state_index];
|
|
566
|
+
if (!candidate || typeof candidate !== 'object' || !Object.prototype.hasOwnProperty.call(candidate, 'current')) {
|
|
567
|
+
throw new Error(
|
|
568
|
+
`[Zenith Runtime] ref binding at position ${i} must resolve to a ref-like object`
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
428
573
|
for (let i = 0; i < signals.length; i++) {
|
|
429
574
|
const signalDescriptor = signals[i];
|
|
430
575
|
if (!signalDescriptor || typeof signalDescriptor !== 'object' || Array.isArray(signalDescriptor)) {
|
|
@@ -478,6 +623,7 @@ function _validatePayload(payload) {
|
|
|
478
623
|
for (let i = 0; i < expressions.length; i++) Object.freeze(expressions[i]);
|
|
479
624
|
for (let i = 0; i < markers.length; i++) Object.freeze(markers[i]);
|
|
480
625
|
for (let i = 0; i < events.length; i++) Object.freeze(events[i]);
|
|
626
|
+
for (let i = 0; i < refs.length; i++) Object.freeze(refs[i]);
|
|
481
627
|
for (let i = 0; i < signals.length; i++) Object.freeze(signals[i]);
|
|
482
628
|
for (let i = 0; i < components.length; i++) {
|
|
483
629
|
const c = components[i];
|
|
@@ -501,6 +647,7 @@ function _validatePayload(payload) {
|
|
|
501
647
|
Object.freeze(expressions);
|
|
502
648
|
Object.freeze(markers);
|
|
503
649
|
Object.freeze(events);
|
|
650
|
+
Object.freeze(refs);
|
|
504
651
|
Object.freeze(signals);
|
|
505
652
|
Object.freeze(components);
|
|
506
653
|
|
|
@@ -509,6 +656,7 @@ function _validatePayload(payload) {
|
|
|
509
656
|
expressions,
|
|
510
657
|
markers,
|
|
511
658
|
events,
|
|
659
|
+
refs,
|
|
512
660
|
stateValues,
|
|
513
661
|
stateKeys,
|
|
514
662
|
signals,
|
|
@@ -516,7 +664,8 @@ function _validatePayload(payload) {
|
|
|
516
664
|
route,
|
|
517
665
|
params: Object.freeze(params),
|
|
518
666
|
ssrData: Object.freeze(ssrData),
|
|
519
|
-
props: Object.freeze(props)
|
|
667
|
+
props: Object.freeze(props),
|
|
668
|
+
exprFns: Object.freeze(exprFns)
|
|
520
669
|
};
|
|
521
670
|
|
|
522
671
|
return Object.freeze(validatedPayload);
|
|
@@ -580,7 +729,40 @@ function _resolveNodes(root, selector, index, kind) {
|
|
|
580
729
|
return nodes;
|
|
581
730
|
}
|
|
582
731
|
|
|
583
|
-
function
|
|
732
|
+
function _resolveExpressionSignalIndices(binding) {
|
|
733
|
+
if (!binding || typeof binding !== 'object') {
|
|
734
|
+
return [];
|
|
735
|
+
}
|
|
736
|
+
if (Array.isArray(binding.signal_indices) && binding.signal_indices.length > 0) {
|
|
737
|
+
return [...new Set(binding.signal_indices.filter((value) => Number.isInteger(value) && value >= 0))];
|
|
738
|
+
}
|
|
739
|
+
if (Number.isInteger(binding.signal_index) && binding.signal_index >= 0) {
|
|
740
|
+
return [binding.signal_index];
|
|
741
|
+
}
|
|
742
|
+
return [];
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function _evaluateExpression(binding, stateValues, stateKeys, signalMap, componentBindings, params, ssrData, mode, props, exprFns) {
|
|
746
|
+
if (binding.fn_index != null && binding.fn_index !== undefined) {
|
|
747
|
+
const fns = Array.isArray(exprFns) ? exprFns : [];
|
|
748
|
+
const fn = fns[binding.fn_index];
|
|
749
|
+
if (typeof fn === 'function') {
|
|
750
|
+
return fn({
|
|
751
|
+
signalMap,
|
|
752
|
+
params,
|
|
753
|
+
ssrData,
|
|
754
|
+
props: props || {},
|
|
755
|
+
componentBindings,
|
|
756
|
+
zenhtml: _zenhtml,
|
|
757
|
+
fragment(html) {
|
|
758
|
+
return {
|
|
759
|
+
__zenith_fragment: true,
|
|
760
|
+
html: html === null || html === undefined || html === false ? '' : String(html)
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
}
|
|
584
766
|
if (binding.signal_index !== null && binding.signal_index !== undefined) {
|
|
585
767
|
const signalValue = signalMap.get(binding.signal_index);
|
|
586
768
|
if (!signalValue || typeof signalValue.get !== 'function') {
|
|
@@ -651,17 +833,9 @@ function _evaluateExpression(binding, stateValues, stateKeys, signalMap, compone
|
|
|
651
833
|
return props || {};
|
|
652
834
|
}
|
|
653
835
|
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
stateKeys,
|
|
658
|
-
params,
|
|
659
|
-
ssrData,
|
|
660
|
-
mode,
|
|
661
|
-
props
|
|
662
|
-
);
|
|
663
|
-
if (evaluated !== UNRESOLVED_LITERAL) {
|
|
664
|
-
return evaluated;
|
|
836
|
+
const primitiveValue = _resolvePrimitiveLiteral(trimmedLiteral);
|
|
837
|
+
if (primitiveValue !== UNRESOLVED_LITERAL) {
|
|
838
|
+
return primitiveValue;
|
|
665
839
|
}
|
|
666
840
|
if (_isLikelyExpressionLiteral(trimmedLiteral)) {
|
|
667
841
|
throwZenithRuntimeError({
|
|
@@ -784,33 +958,39 @@ function _resolveStrictMemberChainLiteral(
|
|
|
784
958
|
return cursor;
|
|
785
959
|
}
|
|
786
960
|
|
|
787
|
-
function
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
961
|
+
function _resolvePrimitiveLiteral(literal) {
|
|
962
|
+
if (typeof literal !== 'string') {
|
|
963
|
+
return UNRESOLVED_LITERAL;
|
|
964
|
+
}
|
|
965
|
+
if (literal === 'true') return true;
|
|
966
|
+
if (literal === 'false') return false;
|
|
967
|
+
if (literal === 'null') return null;
|
|
968
|
+
if (literal === 'undefined') return undefined;
|
|
793
969
|
|
|
794
|
-
|
|
795
|
-
|
|
970
|
+
if (/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/.test(literal)) {
|
|
971
|
+
return Number(literal);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
if (literal.length >= 2 && literal.startsWith('"') && literal.endsWith('"')) {
|
|
796
975
|
try {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
`return (${rewritten});`
|
|
800
|
-
);
|
|
801
|
-
} catch (err) {
|
|
802
|
-
if (isZenithRuntimeError(err)) throw err;
|
|
976
|
+
return JSON.parse(literal);
|
|
977
|
+
} catch {
|
|
803
978
|
return UNRESOLVED_LITERAL;
|
|
804
979
|
}
|
|
805
|
-
COMPILED_LITERAL_CACHE.set(cacheKey, evaluator);
|
|
806
980
|
}
|
|
807
981
|
|
|
808
|
-
|
|
809
|
-
return
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
982
|
+
if (literal.length >= 2 && literal.startsWith('\'') && literal.endsWith('\'')) {
|
|
983
|
+
return literal
|
|
984
|
+
.slice(1, -1)
|
|
985
|
+
.replace(/\\\\/g, '\\')
|
|
986
|
+
.replace(/\\'/g, '\'');
|
|
813
987
|
}
|
|
988
|
+
|
|
989
|
+
if (literal.length >= 2 && literal.startsWith('`') && literal.endsWith('`')) {
|
|
990
|
+
return literal.slice(1, -1);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
return UNRESOLVED_LITERAL;
|
|
814
994
|
}
|
|
815
995
|
|
|
816
996
|
function _buildLiteralScope(stateValues, stateKeys, params, ssrData, mode, props) {
|
package/dist/index.js
CHANGED
|
@@ -2,3 +2,5 @@ export { signal } from './signal.js';
|
|
|
2
2
|
export { state } from './state.js';
|
|
3
3
|
export { zeneffect } from './zeneffect.js';
|
|
4
4
|
export { hydrate } from './hydrate.js';
|
|
5
|
+
export { zenWindow, zenDocument } from './env.js';
|
|
6
|
+
export { zenOn, zenResize, collectRefs } from './platform.js';
|
package/dist/platform.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// platform.js — Zenith Runtime canonical DOM/platform helpers
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// zenOn: SSR-safe event subscription with disposer
|
|
5
|
+
// zenResize: window resize handler with rAF throttle
|
|
6
|
+
// collectRefs: deterministic null-filtered ref collection
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
import { zenWindow } from './env.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* SSR-safe event subscription. Returns disposer.
|
|
13
|
+
* @param {EventTarget | null} target
|
|
14
|
+
* @param {string} eventName
|
|
15
|
+
* @param {EventListener} handler
|
|
16
|
+
* @param {AddEventListenerOptions | boolean} [options]
|
|
17
|
+
* @returns {() => void}
|
|
18
|
+
*/
|
|
19
|
+
export function zenOn(target, eventName, handler, options) {
|
|
20
|
+
if (!target || typeof target.addEventListener !== 'function') {
|
|
21
|
+
return () => {};
|
|
22
|
+
}
|
|
23
|
+
target.addEventListener(eventName, handler, options);
|
|
24
|
+
return () => {
|
|
25
|
+
target.removeEventListener(eventName, handler, options);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Window resize handler with requestAnimationFrame throttle.
|
|
31
|
+
* Returns disposer.
|
|
32
|
+
* @param {(size: { w: number; h: number }) => void} handler
|
|
33
|
+
* @returns {() => void}
|
|
34
|
+
*/
|
|
35
|
+
export function zenResize(handler) {
|
|
36
|
+
const win = zenWindow();
|
|
37
|
+
if (!win || typeof win.addEventListener !== 'function') {
|
|
38
|
+
return () => {};
|
|
39
|
+
}
|
|
40
|
+
const hasRaf =
|
|
41
|
+
typeof win.requestAnimationFrame === 'function'
|
|
42
|
+
&& typeof win.cancelAnimationFrame === 'function';
|
|
43
|
+
let scheduledId = null;
|
|
44
|
+
let lastW = Number.NaN;
|
|
45
|
+
let lastH = Number.NaN;
|
|
46
|
+
|
|
47
|
+
const schedule = (callback) => {
|
|
48
|
+
if (hasRaf) {
|
|
49
|
+
return win.requestAnimationFrame(callback);
|
|
50
|
+
}
|
|
51
|
+
return win.setTimeout(callback, 0);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const cancel = (id) => {
|
|
55
|
+
if (hasRaf) {
|
|
56
|
+
win.cancelAnimationFrame(id);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
win.clearTimeout(id);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function onResize() {
|
|
63
|
+
if (scheduledId !== null) return;
|
|
64
|
+
scheduledId = schedule(() => {
|
|
65
|
+
scheduledId = null;
|
|
66
|
+
const w = win.innerWidth;
|
|
67
|
+
const h = win.innerHeight;
|
|
68
|
+
if (w !== lastW || h !== lastH) {
|
|
69
|
+
lastW = w;
|
|
70
|
+
lastH = h;
|
|
71
|
+
handler({ w, h });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
win.addEventListener('resize', onResize);
|
|
77
|
+
onResize();
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
if (scheduledId !== null) {
|
|
81
|
+
cancel(scheduledId);
|
|
82
|
+
scheduledId = null;
|
|
83
|
+
}
|
|
84
|
+
win.removeEventListener('resize', onResize);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Deterministic null-filtered collection of ref.current values.
|
|
90
|
+
* @param {...{ current?: Element | null }} refs
|
|
91
|
+
* @returns {Element[]}
|
|
92
|
+
*/
|
|
93
|
+
export function collectRefs(...refs) {
|
|
94
|
+
const out = [];
|
|
95
|
+
for (let i = 0; i < refs.length; i++) {
|
|
96
|
+
const node = refs[i] && refs[i].current;
|
|
97
|
+
if (node && typeof node === 'object' && typeof node.nodeType === 'number') {
|
|
98
|
+
out.push(node);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
package/dist/signal.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// - No scheduler
|
|
16
16
|
// - No async queue
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
|
+
import { _nextReactiveId, _trackDependency } from './zeneffect.js';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Create a deterministic signal with explicit subscription semantics.
|
|
@@ -25,9 +26,12 @@
|
|
|
25
26
|
export function signal(initialValue) {
|
|
26
27
|
let value = initialValue;
|
|
27
28
|
const subscribers = new Set();
|
|
29
|
+
const reactiveId = _nextReactiveId();
|
|
28
30
|
|
|
29
31
|
return {
|
|
32
|
+
__zenith_id: reactiveId,
|
|
30
33
|
get() {
|
|
34
|
+
_trackDependency(this);
|
|
31
35
|
return value;
|
|
32
36
|
},
|
|
33
37
|
set(nextValue) {
|
package/dist/state.js
CHANGED
|
@@ -37,9 +37,12 @@ function cloneSnapshot(value) {
|
|
|
37
37
|
export function state(initialValue) {
|
|
38
38
|
let current = Object.freeze(cloneSnapshot(initialValue));
|
|
39
39
|
const subscribers = new Set();
|
|
40
|
+
const reactiveId = _nextReactiveId();
|
|
40
41
|
|
|
41
42
|
return {
|
|
43
|
+
__zenith_id: reactiveId,
|
|
42
44
|
get() {
|
|
45
|
+
_trackDependency(this);
|
|
43
46
|
return current;
|
|
44
47
|
},
|
|
45
48
|
set(nextPatch) {
|
package/dist/template.js
CHANGED
|
@@ -22,6 +22,8 @@ function buildRuntimeModuleSource() {
|
|
|
22
22
|
const segments = [
|
|
23
23
|
stripImports(readRuntimeSourceFile('zeneffect.js')),
|
|
24
24
|
stripImports(readRuntimeSourceFile('ref.js')),
|
|
25
|
+
stripImports(readRuntimeSourceFile('env.js')),
|
|
26
|
+
stripImports(readRuntimeSourceFile('platform.js')),
|
|
25
27
|
stripImports(readRuntimeSourceFile('signal.js')),
|
|
26
28
|
stripImports(readRuntimeSourceFile('state.js')),
|
|
27
29
|
stripImports(readRuntimeSourceFile('diagnostics.js')),
|
|
@@ -119,18 +121,101 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
119
121
|
});
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
|
|
124
|
+
let cssSwapEpoch = 0;
|
|
125
|
+
|
|
126
|
+
function withCacheBuster(nextHref) {
|
|
127
|
+
const separator = nextHref.includes('?') ? '&' : '?';
|
|
128
|
+
return nextHref + separator + '__zenith_dev=' + Date.now();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isSameOriginStylesheet(href) {
|
|
132
|
+
if (typeof href !== 'string' || href.length === 0) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
if (href.startsWith('http://') || href.startsWith('https://')) {
|
|
136
|
+
try {
|
|
137
|
+
return new URL(href, window.location.href).origin === window.location.origin;
|
|
138
|
+
} catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function findPrimaryStylesheet() {
|
|
146
|
+
const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]'))
|
|
147
|
+
.filter(function (link) {
|
|
148
|
+
return isSameOriginStylesheet(link.getAttribute('href') || '');
|
|
149
|
+
});
|
|
150
|
+
if (links.length === 0) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
const marked = links.find(function (link) {
|
|
154
|
+
return link.getAttribute('data-zenith-dev-primary') === 'true';
|
|
155
|
+
});
|
|
156
|
+
if (marked) {
|
|
157
|
+
return marked;
|
|
158
|
+
}
|
|
159
|
+
links[0].setAttribute('data-zenith-dev-primary', 'true');
|
|
160
|
+
return links[0];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function scheduleCssRetry(previousHref, attempt) {
|
|
164
|
+
if (attempt >= 3) {
|
|
165
|
+
window.location.reload();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const delayMs = (attempt + 1) * 100;
|
|
169
|
+
setTimeout(function () {
|
|
170
|
+
fetchDevState().then(function (statePayload) {
|
|
171
|
+
const href = statePayload && typeof statePayload.cssHref === 'string' && statePayload.cssHref.length > 0
|
|
172
|
+
? statePayload.cssHref
|
|
173
|
+
: previousHref;
|
|
174
|
+
swapStylesheet(href, attempt + 1);
|
|
175
|
+
});
|
|
176
|
+
}, delayMs);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function swapStylesheet(nextHref, attempt) {
|
|
180
|
+
const tries = Number.isInteger(attempt) ? attempt : 0;
|
|
123
181
|
if (typeof nextHref !== 'string' || nextHref.length === 0) {
|
|
124
182
|
window.location.reload();
|
|
125
183
|
return;
|
|
126
184
|
}
|
|
127
|
-
const
|
|
128
|
-
if (
|
|
185
|
+
const activeLink = findPrimaryStylesheet();
|
|
186
|
+
if (!activeLink) {
|
|
129
187
|
window.location.reload();
|
|
130
188
|
return;
|
|
131
189
|
}
|
|
132
|
-
|
|
133
|
-
|
|
190
|
+
|
|
191
|
+
const swapId = ++cssSwapEpoch;
|
|
192
|
+
const nextLink = activeLink.cloneNode(true);
|
|
193
|
+
nextLink.setAttribute('href', withCacheBuster(nextHref));
|
|
194
|
+
nextLink.removeAttribute('data-zenith-dev-primary');
|
|
195
|
+
nextLink.setAttribute('data-zenith-dev-pending', 'true');
|
|
196
|
+
activeLink.removeAttribute('data-zenith-dev-primary');
|
|
197
|
+
|
|
198
|
+
nextLink.addEventListener('load', function () {
|
|
199
|
+
if (swapId !== cssSwapEpoch) {
|
|
200
|
+
try { nextLink.remove(); } catch { }
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
nextLink.removeAttribute('data-zenith-dev-pending');
|
|
204
|
+
nextLink.setAttribute('data-zenith-dev-primary', 'true');
|
|
205
|
+
try { activeLink.remove(); } catch { }
|
|
206
|
+
}, { once: true });
|
|
207
|
+
|
|
208
|
+
nextLink.addEventListener('error', function () {
|
|
209
|
+
if (swapId !== cssSwapEpoch) {
|
|
210
|
+
try { nextLink.remove(); } catch { }
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
try { nextLink.remove(); } catch { }
|
|
214
|
+
activeLink.setAttribute('data-zenith-dev-primary', 'true');
|
|
215
|
+
scheduleCssRetry(nextHref, tries);
|
|
216
|
+
}, { once: true });
|
|
217
|
+
|
|
218
|
+
activeLink.insertAdjacentElement('afterend', nextLink);
|
|
134
219
|
}
|
|
135
220
|
|
|
136
221
|
const state = ensureDevState();
|
|
@@ -341,8 +426,10 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
341
426
|
appendLog('[css_update] ' + (payload.href || ''));
|
|
342
427
|
emitDebug('css_update', payload);
|
|
343
428
|
fetchDevState().then(function (statePayload) {
|
|
344
|
-
if (statePayload
|
|
429
|
+
if (statePayload) {
|
|
345
430
|
updateInfo({ ...payload, ...statePayload });
|
|
431
|
+
}
|
|
432
|
+
if (statePayload && typeof statePayload.cssHref === 'string' && statePayload.cssHref.length > 0) {
|
|
346
433
|
swapStylesheet(statePayload.cssHref);
|
|
347
434
|
return;
|
|
348
435
|
}
|