@zenithbuild/runtime 0.5.0-beta.2.6 → 0.6.0
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 +216 -43
- package/dist/index.js +2 -0
- package/dist/platform.js +102 -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
|
@@ -32,8 +32,6 @@ const BOOLEAN_ATTRIBUTES = new Set([
|
|
|
32
32
|
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
33
|
const UNSAFE_MEMBER_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
|
|
34
34
|
|
|
35
|
-
const COMPILED_LITERAL_CACHE = new Map();
|
|
36
|
-
|
|
37
35
|
/**
|
|
38
36
|
* Hydrate a pre-rendered DOM tree using explicit payload tables.
|
|
39
37
|
*
|
|
@@ -43,6 +41,7 @@ const COMPILED_LITERAL_CACHE = new Map();
|
|
|
43
41
|
* 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
42
|
* markers: Array<{ index: number, kind: 'text' | 'attr' | 'event', selector: string, attr?: string }>,
|
|
45
43
|
* events: Array<{ index: number, event: string, selector: string }>,
|
|
44
|
+
* refs?: Array<{ index: number, state_index: number, selector: string }>,
|
|
46
45
|
* state_values: Array<*>,
|
|
47
46
|
* state_keys?: Array<string>,
|
|
48
47
|
* signals: Array<{ id: number, kind: 'signal', state_index: number }>,
|
|
@@ -55,7 +54,22 @@ export function hydrate(payload) {
|
|
|
55
54
|
try {
|
|
56
55
|
const normalized = _validatePayload(payload);
|
|
57
56
|
_deepFreezePayload(payload);
|
|
58
|
-
const {
|
|
57
|
+
const {
|
|
58
|
+
root,
|
|
59
|
+
expressions,
|
|
60
|
+
markers,
|
|
61
|
+
events,
|
|
62
|
+
refs,
|
|
63
|
+
stateValues,
|
|
64
|
+
stateKeys,
|
|
65
|
+
signals,
|
|
66
|
+
components,
|
|
67
|
+
route,
|
|
68
|
+
params,
|
|
69
|
+
ssrData,
|
|
70
|
+
props,
|
|
71
|
+
exprFns
|
|
72
|
+
} = normalized;
|
|
59
73
|
|
|
60
74
|
const componentBindings = Object.create(null);
|
|
61
75
|
|
|
@@ -72,6 +86,23 @@ export function hydrate(payload) {
|
|
|
72
86
|
signalMap.set(i, candidate);
|
|
73
87
|
}
|
|
74
88
|
|
|
89
|
+
const hydratedRefs = [];
|
|
90
|
+
for (let i = 0; i < refs.length; i++) {
|
|
91
|
+
const refBinding = refs[i];
|
|
92
|
+
const targetRef = stateValues[refBinding.state_index];
|
|
93
|
+
const nodes = _resolveNodes(root, refBinding.selector, refBinding.index, 'ref');
|
|
94
|
+
targetRef.current = nodes[0] || null;
|
|
95
|
+
hydratedRefs.push(targetRef);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (hydratedRefs.length > 0) {
|
|
99
|
+
_registerDisposer(() => {
|
|
100
|
+
for (let i = 0; i < hydratedRefs.length; i++) {
|
|
101
|
+
hydratedRefs[i].current = null;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
75
106
|
for (let i = 0; i < components.length; i++) {
|
|
76
107
|
const component = components[i];
|
|
77
108
|
const resolvedProps = Object.freeze(_resolveComponentProps(component.props || [], signalMap, {
|
|
@@ -149,7 +180,8 @@ export function hydrate(payload) {
|
|
|
149
180
|
params,
|
|
150
181
|
ssrData,
|
|
151
182
|
marker.kind,
|
|
152
|
-
props
|
|
183
|
+
props,
|
|
184
|
+
exprFns
|
|
153
185
|
);
|
|
154
186
|
_applyMarkerValue(nodes, marker, value);
|
|
155
187
|
}
|
|
@@ -176,7 +208,8 @@ export function hydrate(payload) {
|
|
|
176
208
|
params,
|
|
177
209
|
ssrData,
|
|
178
210
|
marker.kind,
|
|
179
|
-
props
|
|
211
|
+
props,
|
|
212
|
+
exprFns
|
|
180
213
|
);
|
|
181
214
|
_applyMarkerValue(nodes, marker, value);
|
|
182
215
|
}
|
|
@@ -184,13 +217,17 @@ export function hydrate(payload) {
|
|
|
184
217
|
const dependentMarkersBySignal = new Map();
|
|
185
218
|
for (let i = 0; i < expressions.length; i++) {
|
|
186
219
|
const expression = expressions[i];
|
|
187
|
-
|
|
220
|
+
const signalIndices = _resolveExpressionSignalIndices(expression);
|
|
221
|
+
if (signalIndices.length === 0) {
|
|
188
222
|
continue;
|
|
189
223
|
}
|
|
190
|
-
|
|
191
|
-
|
|
224
|
+
for (let j = 0; j < signalIndices.length; j++) {
|
|
225
|
+
const signalIndex = signalIndices[j];
|
|
226
|
+
if (!dependentMarkersBySignal.has(signalIndex)) {
|
|
227
|
+
dependentMarkersBySignal.set(signalIndex, []);
|
|
228
|
+
}
|
|
229
|
+
dependentMarkersBySignal.get(signalIndex).push(expression.marker_index);
|
|
192
230
|
}
|
|
193
|
-
dependentMarkersBySignal.get(expression.signal_index).push(expression.marker_index);
|
|
194
231
|
}
|
|
195
232
|
|
|
196
233
|
for (const [signalId, markerIndexes] of dependentMarkersBySignal.entries()) {
|
|
@@ -246,6 +283,7 @@ export function hydrate(payload) {
|
|
|
246
283
|
}
|
|
247
284
|
|
|
248
285
|
const eventIndices = new Set();
|
|
286
|
+
const escDispatchEntries = [];
|
|
249
287
|
for (let i = 0; i < events.length; i++) {
|
|
250
288
|
const eventBinding = events[i];
|
|
251
289
|
if (eventIndices.has(eventBinding.index)) {
|
|
@@ -262,7 +300,9 @@ export function hydrate(payload) {
|
|
|
262
300
|
componentBindings,
|
|
263
301
|
params,
|
|
264
302
|
ssrData,
|
|
265
|
-
'event'
|
|
303
|
+
'event',
|
|
304
|
+
props || {},
|
|
305
|
+
exprFns
|
|
266
306
|
);
|
|
267
307
|
if (typeof handler !== 'function') {
|
|
268
308
|
throwZenithRuntimeError({
|
|
@@ -291,11 +331,67 @@ export function hydrate(payload) {
|
|
|
291
331
|
});
|
|
292
332
|
}
|
|
293
333
|
};
|
|
334
|
+
if (eventBinding.event === 'esc') {
|
|
335
|
+
escDispatchEntries.push({
|
|
336
|
+
node,
|
|
337
|
+
handler: wrappedHandler
|
|
338
|
+
});
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
294
341
|
node.addEventListener(eventBinding.event, wrappedHandler);
|
|
295
342
|
_registerListener(node, eventBinding.event, wrappedHandler);
|
|
296
343
|
}
|
|
297
344
|
}
|
|
298
345
|
|
|
346
|
+
if (escDispatchEntries.length > 0) {
|
|
347
|
+
const doc = root && root.ownerDocument ? root.ownerDocument : (typeof document !== 'undefined' ? document : null);
|
|
348
|
+
if (doc && typeof doc.addEventListener === 'function') {
|
|
349
|
+
const escDispatchListener = function zenithEscDispatch(event) {
|
|
350
|
+
if (!event || event.key !== 'Escape') {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const activeElement = doc.activeElement || null;
|
|
355
|
+
let targetEntry = null;
|
|
356
|
+
|
|
357
|
+
if (activeElement && activeElement !== doc.body && activeElement !== doc.documentElement) {
|
|
358
|
+
for (let i = escDispatchEntries.length - 1; i >= 0; i--) {
|
|
359
|
+
const entry = escDispatchEntries[i];
|
|
360
|
+
if (!entry || !entry.node) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (!entry.node.isConnected) {
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (typeof entry.node.contains === 'function' && entry.node.contains(activeElement)) {
|
|
367
|
+
targetEntry = entry;
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (!targetEntry && (activeElement === null || activeElement === doc.body || activeElement === doc.documentElement)) {
|
|
374
|
+
for (let i = escDispatchEntries.length - 1; i >= 0; i--) {
|
|
375
|
+
const entry = escDispatchEntries[i];
|
|
376
|
+
if (!entry || !entry.node || !entry.node.isConnected) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
targetEntry = entry;
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (!targetEntry) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return targetEntry.handler.call(targetEntry.node, event);
|
|
389
|
+
};
|
|
390
|
+
doc.addEventListener('keydown', escDispatchListener);
|
|
391
|
+
_registerListener(doc, 'keydown', escDispatchListener);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
299
395
|
return cleanup;
|
|
300
396
|
} catch (error) {
|
|
301
397
|
rethrowZenithRuntimeError(error, {
|
|
@@ -336,6 +432,8 @@ function _validatePayload(payload) {
|
|
|
336
432
|
throw new Error('[Zenith Runtime] hydrate(payload) requires events[]');
|
|
337
433
|
}
|
|
338
434
|
|
|
435
|
+
const refs = Array.isArray(payload.refs) ? payload.refs : [];
|
|
436
|
+
|
|
339
437
|
const stateValues = payload.state_values;
|
|
340
438
|
if (!Array.isArray(stateValues)) {
|
|
341
439
|
throw new Error('[Zenith Runtime] hydrate(payload) requires state_values[]');
|
|
@@ -365,6 +463,7 @@ function _validatePayload(payload) {
|
|
|
365
463
|
const ssrData = payload.ssr_data && typeof payload.ssr_data === 'object'
|
|
366
464
|
? payload.ssr_data
|
|
367
465
|
: {};
|
|
466
|
+
const exprFns = Array.isArray(payload.expr_fns) ? payload.expr_fns : [];
|
|
368
467
|
|
|
369
468
|
if (markers.length !== expressions.length) {
|
|
370
469
|
throw new Error(
|
|
@@ -385,6 +484,23 @@ function _validatePayload(payload) {
|
|
|
385
484
|
`[Zenith Runtime] expression table out of order at position ${i}: marker_index=${expression.marker_index}`
|
|
386
485
|
);
|
|
387
486
|
}
|
|
487
|
+
if (expression.fn_index !== undefined && expression.fn_index !== null) {
|
|
488
|
+
if (!Number.isInteger(expression.fn_index) || expression.fn_index < 0) {
|
|
489
|
+
throw new Error(`[Zenith Runtime] expression at position ${i} has invalid fn_index`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (expression.signal_indices !== undefined) {
|
|
493
|
+
if (!Array.isArray(expression.signal_indices)) {
|
|
494
|
+
throw new Error(`[Zenith Runtime] expression at position ${i} must provide signal_indices[]`);
|
|
495
|
+
}
|
|
496
|
+
for (let j = 0; j < expression.signal_indices.length; j++) {
|
|
497
|
+
if (!Number.isInteger(expression.signal_indices[j]) || expression.signal_indices[j] < 0) {
|
|
498
|
+
throw new Error(
|
|
499
|
+
`[Zenith Runtime] expression at position ${i} has invalid signal_indices[${j}]`
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
388
504
|
}
|
|
389
505
|
|
|
390
506
|
for (let i = 0; i < markers.length; i++) {
|
|
@@ -425,6 +541,34 @@ function _validatePayload(payload) {
|
|
|
425
541
|
}
|
|
426
542
|
}
|
|
427
543
|
|
|
544
|
+
for (let i = 0; i < refs.length; i++) {
|
|
545
|
+
const refBinding = refs[i];
|
|
546
|
+
if (!refBinding || typeof refBinding !== 'object' || Array.isArray(refBinding)) {
|
|
547
|
+
throw new Error(`[Zenith Runtime] ref binding at position ${i} must be an object`);
|
|
548
|
+
}
|
|
549
|
+
if (!Number.isInteger(refBinding.index) || refBinding.index < 0) {
|
|
550
|
+
throw new Error(`[Zenith Runtime] ref binding at position ${i} requires non-negative index`);
|
|
551
|
+
}
|
|
552
|
+
if (
|
|
553
|
+
!Number.isInteger(refBinding.state_index) ||
|
|
554
|
+
refBinding.state_index < 0 ||
|
|
555
|
+
refBinding.state_index >= stateValues.length
|
|
556
|
+
) {
|
|
557
|
+
throw new Error(
|
|
558
|
+
`[Zenith Runtime] ref binding at position ${i} has out-of-bounds state_index`
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
if (typeof refBinding.selector !== 'string' || refBinding.selector.length === 0) {
|
|
562
|
+
throw new Error(`[Zenith Runtime] ref binding at position ${i} requires selector`);
|
|
563
|
+
}
|
|
564
|
+
const candidate = stateValues[refBinding.state_index];
|
|
565
|
+
if (!candidate || typeof candidate !== 'object' || !Object.prototype.hasOwnProperty.call(candidate, 'current')) {
|
|
566
|
+
throw new Error(
|
|
567
|
+
`[Zenith Runtime] ref binding at position ${i} must resolve to a ref-like object`
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
428
572
|
for (let i = 0; i < signals.length; i++) {
|
|
429
573
|
const signalDescriptor = signals[i];
|
|
430
574
|
if (!signalDescriptor || typeof signalDescriptor !== 'object' || Array.isArray(signalDescriptor)) {
|
|
@@ -478,6 +622,7 @@ function _validatePayload(payload) {
|
|
|
478
622
|
for (let i = 0; i < expressions.length; i++) Object.freeze(expressions[i]);
|
|
479
623
|
for (let i = 0; i < markers.length; i++) Object.freeze(markers[i]);
|
|
480
624
|
for (let i = 0; i < events.length; i++) Object.freeze(events[i]);
|
|
625
|
+
for (let i = 0; i < refs.length; i++) Object.freeze(refs[i]);
|
|
481
626
|
for (let i = 0; i < signals.length; i++) Object.freeze(signals[i]);
|
|
482
627
|
for (let i = 0; i < components.length; i++) {
|
|
483
628
|
const c = components[i];
|
|
@@ -501,6 +646,7 @@ function _validatePayload(payload) {
|
|
|
501
646
|
Object.freeze(expressions);
|
|
502
647
|
Object.freeze(markers);
|
|
503
648
|
Object.freeze(events);
|
|
649
|
+
Object.freeze(refs);
|
|
504
650
|
Object.freeze(signals);
|
|
505
651
|
Object.freeze(components);
|
|
506
652
|
|
|
@@ -509,6 +655,7 @@ function _validatePayload(payload) {
|
|
|
509
655
|
expressions,
|
|
510
656
|
markers,
|
|
511
657
|
events,
|
|
658
|
+
refs,
|
|
512
659
|
stateValues,
|
|
513
660
|
stateKeys,
|
|
514
661
|
signals,
|
|
@@ -516,7 +663,8 @@ function _validatePayload(payload) {
|
|
|
516
663
|
route,
|
|
517
664
|
params: Object.freeze(params),
|
|
518
665
|
ssrData: Object.freeze(ssrData),
|
|
519
|
-
props: Object.freeze(props)
|
|
666
|
+
props: Object.freeze(props),
|
|
667
|
+
exprFns: Object.freeze(exprFns)
|
|
520
668
|
};
|
|
521
669
|
|
|
522
670
|
return Object.freeze(validatedPayload);
|
|
@@ -580,7 +728,34 @@ function _resolveNodes(root, selector, index, kind) {
|
|
|
580
728
|
return nodes;
|
|
581
729
|
}
|
|
582
730
|
|
|
583
|
-
function
|
|
731
|
+
function _resolveExpressionSignalIndices(binding) {
|
|
732
|
+
if (!binding || typeof binding !== 'object') {
|
|
733
|
+
return [];
|
|
734
|
+
}
|
|
735
|
+
if (Array.isArray(binding.signal_indices) && binding.signal_indices.length > 0) {
|
|
736
|
+
return [...new Set(binding.signal_indices.filter((value) => Number.isInteger(value) && value >= 0))];
|
|
737
|
+
}
|
|
738
|
+
if (Number.isInteger(binding.signal_index) && binding.signal_index >= 0) {
|
|
739
|
+
return [binding.signal_index];
|
|
740
|
+
}
|
|
741
|
+
return [];
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function _evaluateExpression(binding, stateValues, stateKeys, signalMap, componentBindings, params, ssrData, mode, props, exprFns) {
|
|
745
|
+
if (binding.fn_index != null && binding.fn_index !== undefined) {
|
|
746
|
+
const fns = Array.isArray(exprFns) ? exprFns : [];
|
|
747
|
+
const fn = fns[binding.fn_index];
|
|
748
|
+
if (typeof fn === 'function') {
|
|
749
|
+
return fn({
|
|
750
|
+
signalMap,
|
|
751
|
+
params,
|
|
752
|
+
ssrData,
|
|
753
|
+
props: props || {},
|
|
754
|
+
componentBindings,
|
|
755
|
+
zenhtml: _zenhtml
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
}
|
|
584
759
|
if (binding.signal_index !== null && binding.signal_index !== undefined) {
|
|
585
760
|
const signalValue = signalMap.get(binding.signal_index);
|
|
586
761
|
if (!signalValue || typeof signalValue.get !== 'function') {
|
|
@@ -651,17 +826,9 @@ function _evaluateExpression(binding, stateValues, stateKeys, signalMap, compone
|
|
|
651
826
|
return props || {};
|
|
652
827
|
}
|
|
653
828
|
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
stateKeys,
|
|
658
|
-
params,
|
|
659
|
-
ssrData,
|
|
660
|
-
mode,
|
|
661
|
-
props
|
|
662
|
-
);
|
|
663
|
-
if (evaluated !== UNRESOLVED_LITERAL) {
|
|
664
|
-
return evaluated;
|
|
829
|
+
const primitiveValue = _resolvePrimitiveLiteral(trimmedLiteral);
|
|
830
|
+
if (primitiveValue !== UNRESOLVED_LITERAL) {
|
|
831
|
+
return primitiveValue;
|
|
665
832
|
}
|
|
666
833
|
if (_isLikelyExpressionLiteral(trimmedLiteral)) {
|
|
667
834
|
throwZenithRuntimeError({
|
|
@@ -784,33 +951,39 @@ function _resolveStrictMemberChainLiteral(
|
|
|
784
951
|
return cursor;
|
|
785
952
|
}
|
|
786
953
|
|
|
787
|
-
function
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
954
|
+
function _resolvePrimitiveLiteral(literal) {
|
|
955
|
+
if (typeof literal !== 'string') {
|
|
956
|
+
return UNRESOLVED_LITERAL;
|
|
957
|
+
}
|
|
958
|
+
if (literal === 'true') return true;
|
|
959
|
+
if (literal === 'false') return false;
|
|
960
|
+
if (literal === 'null') return null;
|
|
961
|
+
if (literal === 'undefined') return undefined;
|
|
962
|
+
|
|
963
|
+
if (/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/.test(literal)) {
|
|
964
|
+
return Number(literal);
|
|
965
|
+
}
|
|
793
966
|
|
|
794
|
-
|
|
795
|
-
if (!evaluator) {
|
|
967
|
+
if (literal.length >= 2 && literal.startsWith('"') && literal.endsWith('"')) {
|
|
796
968
|
try {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
`return (${rewritten});`
|
|
800
|
-
);
|
|
801
|
-
} catch (err) {
|
|
802
|
-
if (isZenithRuntimeError(err)) throw err;
|
|
969
|
+
return JSON.parse(literal);
|
|
970
|
+
} catch {
|
|
803
971
|
return UNRESOLVED_LITERAL;
|
|
804
972
|
}
|
|
805
|
-
COMPILED_LITERAL_CACHE.set(cacheKey, evaluator);
|
|
806
973
|
}
|
|
807
974
|
|
|
808
|
-
|
|
809
|
-
return
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
975
|
+
if (literal.length >= 2 && literal.startsWith('\'') && literal.endsWith('\'')) {
|
|
976
|
+
return literal
|
|
977
|
+
.slice(1, -1)
|
|
978
|
+
.replace(/\\\\/g, '\\')
|
|
979
|
+
.replace(/\\'/g, '\'');
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
if (literal.length >= 2 && literal.startsWith('`') && literal.endsWith('`')) {
|
|
983
|
+
return literal.slice(1, -1);
|
|
813
984
|
}
|
|
985
|
+
|
|
986
|
+
return UNRESOLVED_LITERAL;
|
|
814
987
|
}
|
|
815
988
|
|
|
816
989
|
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/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
|
}
|