@zenithbuild/runtime 0.7.4 → 0.7.7
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/README.md +1 -0
- package/RUNTIME_CONTRACT.md +8 -0
- package/dist/diagnostics-production.d.ts +11 -0
- package/dist/diagnostics-production.js +279 -0
- package/dist/events.js +2 -2
- package/dist/expressions.d.ts +0 -1
- package/dist/expressions.js +128 -150
- package/dist/hydrate.js +32 -67
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/markup.js +7 -7
- package/dist/payload.d.ts +9 -10
- package/dist/payload.js +128 -219
- package/dist/presence.d.ts +67 -0
- package/dist/presence.js +297 -0
- package/dist/render.js +99 -117
- package/dist/runtime-template-profile.d.ts +20 -0
- package/dist/runtime-template-profile.js +141 -0
- package/dist/scanner.js +2 -2
- package/dist/template.d.ts +7 -1
- package/dist/template.js +25 -23
- package/package.json +1 -1
package/dist/presence.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { zenOn } from './platform.js';
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {'hidden' | 'entering' | 'present' | 'exiting'} ZenPresencePhase
|
|
5
|
+
*/
|
|
6
|
+
function isRefLike(value) {
|
|
7
|
+
return !!value && typeof value === 'object' && 'current' in value;
|
|
8
|
+
}
|
|
9
|
+
function normalizeOptions(options) {
|
|
10
|
+
if (options === undefined || options === null) {
|
|
11
|
+
return {
|
|
12
|
+
timeoutMs: undefined,
|
|
13
|
+
onPhaseChange: null
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
17
|
+
throw new Error('[Zenith Runtime] zenPresence(ref, options) requires an options object when provided');
|
|
18
|
+
}
|
|
19
|
+
if (options.timeoutMs !== undefined) {
|
|
20
|
+
if (!Number.isFinite(options.timeoutMs) || options.timeoutMs < 0) {
|
|
21
|
+
throw new Error('[Zenith Runtime] zenPresence options.timeoutMs must be a non-negative number');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (options.onPhaseChange !== undefined && typeof options.onPhaseChange !== 'function') {
|
|
25
|
+
throw new Error('[Zenith Runtime] zenPresence options.onPhaseChange must be a function when provided');
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
timeoutMs: options.timeoutMs === undefined ? undefined : Math.floor(options.timeoutMs),
|
|
29
|
+
onPhaseChange: typeof options.onPhaseChange === 'function' ? options.onPhaseChange : null
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function parseCssTimeToken(token) {
|
|
33
|
+
const value = String(token || '').trim();
|
|
34
|
+
if (value.length === 0) {
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
if (value.endsWith('ms')) {
|
|
38
|
+
const ms = Number.parseFloat(value.slice(0, -2));
|
|
39
|
+
return Number.isFinite(ms) ? Math.max(0, ms) : 0;
|
|
40
|
+
}
|
|
41
|
+
if (value.endsWith('s')) {
|
|
42
|
+
const seconds = Number.parseFloat(value.slice(0, -1));
|
|
43
|
+
return Number.isFinite(seconds) ? Math.max(0, seconds * 1000) : 0;
|
|
44
|
+
}
|
|
45
|
+
const numeric = Number.parseFloat(value);
|
|
46
|
+
return Number.isFinite(numeric) ? Math.max(0, numeric) : 0;
|
|
47
|
+
}
|
|
48
|
+
function parseCssTimeList(value) {
|
|
49
|
+
return String(value || '')
|
|
50
|
+
.split(',')
|
|
51
|
+
.map((token) => parseCssTimeToken(token))
|
|
52
|
+
.filter((candidate) => Number.isFinite(candidate));
|
|
53
|
+
}
|
|
54
|
+
function computeMaxCssTotal(durations, delays) {
|
|
55
|
+
if (!Array.isArray(durations) || durations.length === 0) {
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
let maxTotal = 0;
|
|
59
|
+
for (let index = 0; index < durations.length; index += 1) {
|
|
60
|
+
const duration = durations[index] || 0;
|
|
61
|
+
const delay = Array.isArray(delays) && delays.length > 0
|
|
62
|
+
? delays[index % delays.length] || 0
|
|
63
|
+
: 0;
|
|
64
|
+
const total = duration + delay;
|
|
65
|
+
if (total > maxTotal) {
|
|
66
|
+
maxTotal = total;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return maxTotal;
|
|
70
|
+
}
|
|
71
|
+
function resolveFallbackTimeoutMs(node, explicitTimeoutMs) {
|
|
72
|
+
if (Number.isFinite(explicitTimeoutMs)) {
|
|
73
|
+
return explicitTimeoutMs;
|
|
74
|
+
}
|
|
75
|
+
const activeWindow = node?.ownerDocument?.defaultView;
|
|
76
|
+
if (!activeWindow || typeof activeWindow.getComputedStyle !== 'function') {
|
|
77
|
+
return 34;
|
|
78
|
+
}
|
|
79
|
+
const styles = activeWindow.getComputedStyle(node);
|
|
80
|
+
const transitionTotal = computeMaxCssTotal(parseCssTimeList(styles.transitionDuration), parseCssTimeList(styles.transitionDelay));
|
|
81
|
+
const animationTotal = computeMaxCssTotal(parseCssTimeList(styles.animationDuration), parseCssTimeList(styles.animationDelay));
|
|
82
|
+
const total = Math.max(transitionTotal, animationTotal);
|
|
83
|
+
return total > 0 ? Math.ceil(total + 34) : 34;
|
|
84
|
+
}
|
|
85
|
+
function getTimerApi(node) {
|
|
86
|
+
const activeWindow = node?.ownerDocument?.defaultView;
|
|
87
|
+
if (activeWindow && typeof activeWindow.setTimeout === 'function' && typeof activeWindow.clearTimeout === 'function') {
|
|
88
|
+
return {
|
|
89
|
+
setTimeout: activeWindow.setTimeout.bind(activeWindow),
|
|
90
|
+
clearTimeout: activeWindow.clearTimeout.bind(activeWindow)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
setTimeout: globalThis.setTimeout.bind(globalThis),
|
|
95
|
+
clearTimeout: globalThis.clearTimeout.bind(globalThis)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function isOwnedEvent(event, node) {
|
|
99
|
+
return !!event && event.target === node;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Ref-owned presence controller for always-mounted nodes.
|
|
103
|
+
*
|
|
104
|
+
* Canonical pattern:
|
|
105
|
+
* - create once per ref
|
|
106
|
+
* - call `presence.mount()` inside `zenMount`
|
|
107
|
+
* - drive `presence.setPresent(next)` from reactive state
|
|
108
|
+
*
|
|
109
|
+
* @template {Element} T
|
|
110
|
+
* @param {{ current?: T | null }} ref
|
|
111
|
+
* @param {{ timeoutMs?: number, onPhaseChange?: ((phase: ZenPresencePhase, context: { node: T | null, previousPhase: ZenPresencePhase | null, present: boolean }) => void) } | null | undefined} [options]
|
|
112
|
+
* @returns {{
|
|
113
|
+
* mount: () => () => void,
|
|
114
|
+
* destroy: () => void,
|
|
115
|
+
* getPhase: () => ZenPresencePhase,
|
|
116
|
+
* setPresent: (nextPresent: boolean) => void
|
|
117
|
+
* }}
|
|
118
|
+
*/
|
|
119
|
+
export function zenPresence(ref, options = null) {
|
|
120
|
+
if (!isRefLike(ref)) {
|
|
121
|
+
throw new Error('[Zenith Runtime] zenPresence(ref, options) requires a ref-like object with current');
|
|
122
|
+
}
|
|
123
|
+
const normalizedOptions = normalizeOptions(options);
|
|
124
|
+
let desiredPresent = false;
|
|
125
|
+
/** @type {ZenPresencePhase} */
|
|
126
|
+
let currentPhase = 'hidden';
|
|
127
|
+
let mounted = false;
|
|
128
|
+
let mountEpoch = 0;
|
|
129
|
+
let pendingCompletion = null;
|
|
130
|
+
function getNode() {
|
|
131
|
+
const candidate = ref.current;
|
|
132
|
+
if (!candidate || typeof candidate !== 'object' || typeof candidate.nodeType !== 'number') {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return candidate;
|
|
136
|
+
}
|
|
137
|
+
function notifyPhaseChange(previousPhase) {
|
|
138
|
+
if (typeof normalizedOptions.onPhaseChange !== 'function') {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
normalizedOptions.onPhaseChange(currentPhase, {
|
|
142
|
+
node: getNode(),
|
|
143
|
+
previousPhase,
|
|
144
|
+
present: desiredPresent
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function applyPhaseToNode() {
|
|
148
|
+
const node = getNode();
|
|
149
|
+
if (!node) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
node.setAttribute('data-zen-presence', currentPhase);
|
|
153
|
+
}
|
|
154
|
+
function setPhase(nextPhase, forceApply = false) {
|
|
155
|
+
const previousPhase = currentPhase;
|
|
156
|
+
const changed = previousPhase !== nextPhase;
|
|
157
|
+
if (!changed && !forceApply) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
currentPhase = nextPhase;
|
|
161
|
+
applyPhaseToNode();
|
|
162
|
+
if (changed) {
|
|
163
|
+
notifyPhaseChange(previousPhase);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function cancelPendingCompletion() {
|
|
167
|
+
if (!pendingCompletion) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
pendingCompletion.cancel();
|
|
171
|
+
pendingCompletion = null;
|
|
172
|
+
}
|
|
173
|
+
function scheduleCompletion(targetPhase, node) {
|
|
174
|
+
cancelPendingCompletion();
|
|
175
|
+
if (!mounted || !node) {
|
|
176
|
+
setPhase(targetPhase);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const timerApi = getTimerApi(node);
|
|
180
|
+
const timeoutMs = resolveFallbackTimeoutMs(node, normalizedOptions.timeoutMs);
|
|
181
|
+
const disposers = [];
|
|
182
|
+
let settled = false;
|
|
183
|
+
let timeoutId = null;
|
|
184
|
+
const settle = () => {
|
|
185
|
+
if (settled) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
settled = true;
|
|
189
|
+
while (disposers.length > 0) {
|
|
190
|
+
const dispose = disposers.pop();
|
|
191
|
+
try {
|
|
192
|
+
dispose();
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (timeoutId !== null) {
|
|
198
|
+
timerApi.clearTimeout(timeoutId);
|
|
199
|
+
timeoutId = null;
|
|
200
|
+
}
|
|
201
|
+
pendingCompletion = null;
|
|
202
|
+
setPhase(targetPhase);
|
|
203
|
+
};
|
|
204
|
+
const handleEnd = (event) => {
|
|
205
|
+
if (!isOwnedEvent(event, node)) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
settle();
|
|
209
|
+
};
|
|
210
|
+
disposers.push(zenOn(node, 'transitionend', handleEnd));
|
|
211
|
+
disposers.push(zenOn(node, 'animationend', handleEnd));
|
|
212
|
+
timeoutId = timerApi.setTimeout(settle, timeoutMs);
|
|
213
|
+
pendingCompletion = {
|
|
214
|
+
cancel() {
|
|
215
|
+
if (settled) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
settled = true;
|
|
219
|
+
while (disposers.length > 0) {
|
|
220
|
+
const dispose = disposers.pop();
|
|
221
|
+
try {
|
|
222
|
+
dispose();
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (timeoutId !== null) {
|
|
228
|
+
timerApi.clearTimeout(timeoutId);
|
|
229
|
+
timeoutId = null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function reconcile() {
|
|
235
|
+
if (!mounted) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const node = getNode();
|
|
239
|
+
if (!node) {
|
|
240
|
+
cancelPendingCompletion();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (desiredPresent) {
|
|
244
|
+
if (currentPhase === 'entering' || currentPhase === 'present') {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
setPhase('entering');
|
|
248
|
+
scheduleCompletion('present', node);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (currentPhase === 'hidden' || currentPhase === 'exiting') {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
setPhase('exiting');
|
|
255
|
+
scheduleCompletion('hidden', node);
|
|
256
|
+
}
|
|
257
|
+
function destroyCurrentMount() {
|
|
258
|
+
mounted = false;
|
|
259
|
+
cancelPendingCompletion();
|
|
260
|
+
currentPhase = 'hidden';
|
|
261
|
+
const node = getNode();
|
|
262
|
+
if (node) {
|
|
263
|
+
node.removeAttribute('data-zen-presence');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
mount() {
|
|
268
|
+
mountEpoch += 1;
|
|
269
|
+
const activeMount = mountEpoch;
|
|
270
|
+
mounted = true;
|
|
271
|
+
setPhase(currentPhase, true);
|
|
272
|
+
reconcile();
|
|
273
|
+
return () => {
|
|
274
|
+
if (activeMount !== mountEpoch) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
destroyCurrentMount();
|
|
278
|
+
};
|
|
279
|
+
},
|
|
280
|
+
destroy() {
|
|
281
|
+
mountEpoch += 1;
|
|
282
|
+
destroyCurrentMount();
|
|
283
|
+
},
|
|
284
|
+
getPhase() {
|
|
285
|
+
return currentPhase;
|
|
286
|
+
},
|
|
287
|
+
setPresent(nextPresent) {
|
|
288
|
+
desiredPresent = nextPresent === true;
|
|
289
|
+
reconcile();
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* @alias zenPresence
|
|
295
|
+
* @description Optional secondary alias for the canonical zenPresence helper.
|
|
296
|
+
*/
|
|
297
|
+
export const presence = zenPresence;
|
package/dist/render.js
CHANGED
|
@@ -12,20 +12,21 @@ export function _applyMarkerValue(nodes, marker, value) {
|
|
|
12
12
|
try {
|
|
13
13
|
const node = nodes[i];
|
|
14
14
|
if (marker.kind === 'text') {
|
|
15
|
+
const textPath = `${markerPath}.text`;
|
|
15
16
|
if (node && node.nodeType === 8) {
|
|
16
|
-
_applyCommentMarkerValue(node, value,
|
|
17
|
+
_applyCommentMarkerValue(node, value, textPath);
|
|
17
18
|
continue;
|
|
18
19
|
}
|
|
19
20
|
if (_isStructuralFragment(value)) {
|
|
20
|
-
_mountStructuralFragment(node, value,
|
|
21
|
+
_mountStructuralFragment(node, value, textPath);
|
|
21
22
|
continue;
|
|
22
23
|
}
|
|
23
|
-
const html = _renderFragmentValue(value,
|
|
24
|
+
const html = _renderFragmentValue(value, textPath);
|
|
24
25
|
if (html !== null) {
|
|
25
26
|
node.innerHTML = html;
|
|
26
27
|
}
|
|
27
28
|
else {
|
|
28
|
-
node.textContent = _coerceText(value,
|
|
29
|
+
node.textContent = _coerceText(value, textPath);
|
|
29
30
|
}
|
|
30
31
|
continue;
|
|
31
32
|
}
|
|
@@ -45,7 +46,7 @@ export function _applyMarkerValue(nodes, marker, value) {
|
|
|
45
46
|
path: marker.kind === 'attr'
|
|
46
47
|
? `${markerPath}.attr.${marker.attr}`
|
|
47
48
|
: `${markerPath}.${marker.kind}`,
|
|
48
|
-
hint: 'Check
|
|
49
|
+
hint: 'Check binding value and marker mapping.',
|
|
49
50
|
docsLink: DOCS_LINKS.markerTable,
|
|
50
51
|
source: marker.source
|
|
51
52
|
});
|
|
@@ -105,16 +106,7 @@ function _ensureCommentPlaceholderEnd(anchor) {
|
|
|
105
106
|
return end;
|
|
106
107
|
}
|
|
107
108
|
function _clearCommentPlaceholderContent(anchor) {
|
|
108
|
-
|
|
109
|
-
for (let i = 0; i < anchor.__z_unmounts.length; i++) {
|
|
110
|
-
try {
|
|
111
|
-
anchor.__z_unmounts[i]();
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
anchor.__z_unmounts = [];
|
|
109
|
+
_runUnmounts(anchor);
|
|
118
110
|
const end = _ensureCommentPlaceholderEnd(anchor);
|
|
119
111
|
if (!end) {
|
|
120
112
|
return anchor;
|
|
@@ -130,21 +122,7 @@ function _clearCommentPlaceholderContent(anchor) {
|
|
|
130
122
|
return end;
|
|
131
123
|
}
|
|
132
124
|
function _mountStructuralFragmentIntoCommentRange(anchor, value, rootPath = 'renderable') {
|
|
133
|
-
|
|
134
|
-
if (region && anchor.__z_fragment_region_active) {
|
|
135
|
-
try {
|
|
136
|
-
region.update(value, { parent: anchor.parentNode, insertBefore: anchor.__z_range_end, rootPath });
|
|
137
|
-
}
|
|
138
|
-
catch (error) {
|
|
139
|
-
rethrowZenithRuntimeError(error, {
|
|
140
|
-
phase: 'render',
|
|
141
|
-
code: 'FRAGMENT_MOUNT_FAILED',
|
|
142
|
-
message: 'Fragment update failed',
|
|
143
|
-
path: rootPath,
|
|
144
|
-
hint: 'Verify fragment values and nested renderable arrays.',
|
|
145
|
-
docsLink: DOCS_LINKS.markerTable
|
|
146
|
-
});
|
|
147
|
-
}
|
|
125
|
+
if (_tryUpdateFragmentTarget(anchor, value, anchor.parentNode, anchor.__z_range_end, rootPath)) {
|
|
148
126
|
return;
|
|
149
127
|
}
|
|
150
128
|
const end = _clearCommentPlaceholderContent(anchor);
|
|
@@ -152,75 +130,15 @@ function _mountStructuralFragmentIntoCommentRange(anchor, value, rootPath = 'ren
|
|
|
152
130
|
if (!parent) {
|
|
153
131
|
return;
|
|
154
132
|
}
|
|
155
|
-
|
|
156
|
-
anchor.__z_fragment_region = region;
|
|
157
|
-
anchor.__z_fragment_region_active = true;
|
|
158
|
-
try {
|
|
159
|
-
region.mount(value, { parent, insertBefore: end, rootPath });
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
rethrowZenithRuntimeError(error, {
|
|
163
|
-
phase: 'render',
|
|
164
|
-
code: 'FRAGMENT_MOUNT_FAILED',
|
|
165
|
-
message: 'Fragment mount failed',
|
|
166
|
-
path: rootPath,
|
|
167
|
-
hint: 'Verify fragment values and nested renderable arrays.',
|
|
168
|
-
docsLink: DOCS_LINKS.markerTable
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
anchor.__z_unmounts = [() => {
|
|
172
|
-
anchor.__z_fragment_region_active = false;
|
|
173
|
-
region.destroy();
|
|
174
|
-
}];
|
|
133
|
+
_mountFragmentRegion(anchor, value, parent, end, rootPath);
|
|
175
134
|
}
|
|
176
135
|
function _mountStructuralFragment(container, value, rootPath = 'renderable') {
|
|
177
|
-
|
|
178
|
-
if (region && container.__z_fragment_region_active) {
|
|
179
|
-
try {
|
|
180
|
-
region.update(value, { parent: container, insertBefore: null, rootPath });
|
|
181
|
-
}
|
|
182
|
-
catch (error) {
|
|
183
|
-
rethrowZenithRuntimeError(error, {
|
|
184
|
-
phase: 'render',
|
|
185
|
-
code: 'FRAGMENT_MOUNT_FAILED',
|
|
186
|
-
message: 'Fragment update failed',
|
|
187
|
-
path: rootPath,
|
|
188
|
-
hint: 'Verify fragment values and nested renderable arrays.',
|
|
189
|
-
docsLink: DOCS_LINKS.markerTable
|
|
190
|
-
});
|
|
191
|
-
}
|
|
136
|
+
if (_tryUpdateFragmentTarget(container, value, container, null, rootPath)) {
|
|
192
137
|
return;
|
|
193
138
|
}
|
|
194
|
-
|
|
195
|
-
for (let i = 0; i < container.__z_unmounts.length; i++) {
|
|
196
|
-
try {
|
|
197
|
-
container.__z_unmounts[i]();
|
|
198
|
-
}
|
|
199
|
-
catch {
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
139
|
+
_runUnmounts(container);
|
|
203
140
|
container.innerHTML = '';
|
|
204
|
-
|
|
205
|
-
container.__z_fragment_region = region;
|
|
206
|
-
container.__z_fragment_region_active = true;
|
|
207
|
-
try {
|
|
208
|
-
region.mount(value, { parent: container, insertBefore: null, rootPath });
|
|
209
|
-
}
|
|
210
|
-
catch (error) {
|
|
211
|
-
rethrowZenithRuntimeError(error, {
|
|
212
|
-
phase: 'render',
|
|
213
|
-
code: 'FRAGMENT_MOUNT_FAILED',
|
|
214
|
-
message: 'Fragment mount failed',
|
|
215
|
-
path: rootPath,
|
|
216
|
-
hint: 'Verify fragment values and nested renderable arrays.',
|
|
217
|
-
docsLink: DOCS_LINKS.markerTable
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
container.__z_unmounts = [() => {
|
|
221
|
-
container.__z_fragment_region_active = false;
|
|
222
|
-
region.destroy();
|
|
223
|
-
}];
|
|
141
|
+
_mountFragmentRegion(container, value, container, null, rootPath);
|
|
224
142
|
}
|
|
225
143
|
export function _coerceText(value, path = 'renderable') {
|
|
226
144
|
if (value === null || value === undefined || value === false || value === true) {
|
|
@@ -232,7 +150,7 @@ export function _coerceText(value, path = 'renderable') {
|
|
|
232
150
|
code: 'NON_RENDERABLE_VALUE',
|
|
233
151
|
message: `Zenith Render Error: non-renderable function at ${path}. Use map() to render fields.`,
|
|
234
152
|
path,
|
|
235
|
-
hint: 'Convert functions
|
|
153
|
+
hint: 'Convert functions to handlers or text.',
|
|
236
154
|
docsLink: DOCS_LINKS.expressionScope
|
|
237
155
|
});
|
|
238
156
|
}
|
|
@@ -242,7 +160,7 @@ export function _coerceText(value, path = 'renderable') {
|
|
|
242
160
|
code: 'NON_RENDERABLE_VALUE',
|
|
243
161
|
message: `Zenith Render Error: non-renderable object at ${path}. Use map() to render fields.`,
|
|
244
162
|
path,
|
|
245
|
-
hint: 'Use map() to render object fields
|
|
163
|
+
hint: 'Use map() to render object fields.',
|
|
246
164
|
docsLink: DOCS_LINKS.expressionScope
|
|
247
165
|
});
|
|
248
166
|
}
|
|
@@ -279,21 +197,22 @@ function _escapeHtml(input) {
|
|
|
279
197
|
.replace(/'/g, ''');
|
|
280
198
|
}
|
|
281
199
|
function _applyAttribute(node, attrName, value) {
|
|
282
|
-
|
|
200
|
+
const normalizedAttrName = typeof attrName === 'string' ? attrName.toLowerCase() : '';
|
|
201
|
+
if (normalizedAttrName === 'innerhtml') {
|
|
283
202
|
throwZenithRuntimeError({
|
|
284
203
|
phase: 'bind',
|
|
285
204
|
code: 'UNSAFE_HTML_REQUIRES_EXPLICIT_BOUNDARY',
|
|
286
205
|
message: 'innerHTML bindings are forbidden in Zenith',
|
|
287
206
|
path: `attr:${attrName}`,
|
|
288
|
-
hint: 'Use unsafeHTML={value}
|
|
207
|
+
hint: 'Use unsafeHTML={value} or compiler-owned markup fragments.'
|
|
289
208
|
});
|
|
290
209
|
}
|
|
291
|
-
if (
|
|
210
|
+
if (normalizedAttrName === 'unsafehtml') {
|
|
292
211
|
node.innerHTML = value === null || value === undefined || value === false ? '' : String(value);
|
|
293
212
|
return;
|
|
294
213
|
}
|
|
295
214
|
if (attrName === 'class' || attrName === 'className') {
|
|
296
|
-
const classValue = value
|
|
215
|
+
const classValue = _isEmptyAttrValue(value) ? '' : String(value);
|
|
297
216
|
if (node && node.namespaceURI === SVG_NAMESPACE && typeof node.setAttribute === 'function') {
|
|
298
217
|
node.setAttribute('class', classValue);
|
|
299
218
|
return;
|
|
@@ -302,25 +221,13 @@ function _applyAttribute(node, attrName, value) {
|
|
|
302
221
|
return;
|
|
303
222
|
}
|
|
304
223
|
if (attrName === 'style') {
|
|
305
|
-
|
|
224
|
+
const styleValue = _resolveStyleAttributeValue(value);
|
|
225
|
+
if (styleValue === null) {
|
|
306
226
|
node.removeAttribute('style');
|
|
307
|
-
return;
|
|
308
227
|
}
|
|
309
|
-
|
|
310
|
-
node.setAttribute('style',
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
if (typeof value === 'object') {
|
|
314
|
-
const entries = Object.entries(value);
|
|
315
|
-
let styleText = '';
|
|
316
|
-
for (let i = 0; i < entries.length; i++) {
|
|
317
|
-
const [key, rawValue] = entries[i];
|
|
318
|
-
styleText += `${key}: ${rawValue};`;
|
|
319
|
-
}
|
|
320
|
-
node.setAttribute('style', styleText);
|
|
321
|
-
return;
|
|
228
|
+
else {
|
|
229
|
+
node.setAttribute('style', styleValue);
|
|
322
230
|
}
|
|
323
|
-
node.setAttribute('style', String(value));
|
|
324
231
|
return;
|
|
325
232
|
}
|
|
326
233
|
if (BOOLEAN_ATTRIBUTES.has(attrName)) {
|
|
@@ -332,9 +239,84 @@ function _applyAttribute(node, attrName, value) {
|
|
|
332
239
|
}
|
|
333
240
|
return;
|
|
334
241
|
}
|
|
335
|
-
if (value
|
|
242
|
+
if (_isEmptyAttrValue(value)) {
|
|
336
243
|
node.removeAttribute(attrName);
|
|
337
244
|
return;
|
|
338
245
|
}
|
|
339
246
|
node.setAttribute(attrName, String(value));
|
|
340
247
|
}
|
|
248
|
+
function _runUnmounts(target) {
|
|
249
|
+
if (!target.__z_unmounts) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
for (let i = 0; i < target.__z_unmounts.length; i++) {
|
|
253
|
+
try {
|
|
254
|
+
target.__z_unmounts[i]();
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
target.__z_unmounts = [];
|
|
260
|
+
}
|
|
261
|
+
function _updateFragmentRegion(region, value, parent, insertBefore, rootPath) {
|
|
262
|
+
try {
|
|
263
|
+
region.update(value, { parent, insertBefore, rootPath });
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
_throwFragmentMountFailure(error, rootPath, 'Fragment update failed');
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function _mountFragmentRegion(target, value, parent, insertBefore, rootPath) {
|
|
270
|
+
const region = createFragmentRegion();
|
|
271
|
+
target.__z_fragment_region = region;
|
|
272
|
+
target.__z_fragment_region_active = true;
|
|
273
|
+
try {
|
|
274
|
+
region.mount(value, { parent, insertBefore, rootPath });
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
_throwFragmentMountFailure(error, rootPath, 'Fragment mount failed');
|
|
278
|
+
}
|
|
279
|
+
target.__z_unmounts = [() => {
|
|
280
|
+
target.__z_fragment_region_active = false;
|
|
281
|
+
region.destroy();
|
|
282
|
+
}];
|
|
283
|
+
}
|
|
284
|
+
function _tryUpdateFragmentTarget(target, value, parent, insertBefore, rootPath) {
|
|
285
|
+
const region = target.__z_fragment_region;
|
|
286
|
+
if (!region || !target.__z_fragment_region_active) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
_updateFragmentRegion(region, value, parent, insertBefore, rootPath);
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
function _throwFragmentMountFailure(error, path, message) {
|
|
293
|
+
rethrowZenithRuntimeError(error, {
|
|
294
|
+
phase: 'render',
|
|
295
|
+
code: 'FRAGMENT_MOUNT_FAILED',
|
|
296
|
+
message,
|
|
297
|
+
path,
|
|
298
|
+
hint: 'Verify fragment values and nested arrays.',
|
|
299
|
+
docsLink: DOCS_LINKS.markerTable
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
function _resolveStyleAttributeValue(value) {
|
|
303
|
+
if (_isEmptyAttrValue(value)) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
if (typeof value === 'string') {
|
|
307
|
+
return value;
|
|
308
|
+
}
|
|
309
|
+
if (value && typeof value === 'object') {
|
|
310
|
+
const entries = Object.entries(value);
|
|
311
|
+
let styleText = '';
|
|
312
|
+
for (let i = 0; i < entries.length; i++) {
|
|
313
|
+
const [key, rawValue] = entries[i];
|
|
314
|
+
styleText += `${key}: ${rawValue};`;
|
|
315
|
+
}
|
|
316
|
+
return styleText;
|
|
317
|
+
}
|
|
318
|
+
return String(value);
|
|
319
|
+
}
|
|
320
|
+
function _isEmptyAttrValue(value) {
|
|
321
|
+
return value === null || value === undefined || value === false;
|
|
322
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function normalizeRuntimeTemplateProfile(profile: any): "default" | "production-emitted" | "production-emitted-with-presence";
|
|
2
|
+
export function buildRuntimeTemplateProfile({ profile, normalizeNewlines, readRuntimeSourceFile }: {
|
|
3
|
+
profile: any;
|
|
4
|
+
normalizeNewlines: any;
|
|
5
|
+
readRuntimeSourceFile: any;
|
|
6
|
+
}): {
|
|
7
|
+
profile: "default" | "production-emitted" | "production-emitted-with-presence";
|
|
8
|
+
source: any;
|
|
9
|
+
contributors: {
|
|
10
|
+
id: string;
|
|
11
|
+
sourceFile: string;
|
|
12
|
+
bytes: number;
|
|
13
|
+
}[];
|
|
14
|
+
coverageBytes: number;
|
|
15
|
+
};
|
|
16
|
+
export const RUNTIME_TEMPLATE_PROFILES: Readonly<{
|
|
17
|
+
DEFAULT: "default";
|
|
18
|
+
PRODUCTION_EMITTED: "production-emitted";
|
|
19
|
+
PRODUCTION_EMITTED_WITH_PRESENCE: "production-emitted-with-presence";
|
|
20
|
+
}>;
|