@zenithbuild/runtime 0.7.3 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/HYDRATION_CONTRACT.md +1 -1
  2. package/LICENSE +21 -0
  3. package/README.md +17 -18
  4. package/RUNTIME_CONTRACT.md +32 -12
  5. package/dist/cleanup.js +6 -3
  6. package/dist/diagnostics.d.ts +7 -0
  7. package/dist/diagnostics.js +7 -0
  8. package/dist/effect-runtime.d.ts +2 -0
  9. package/dist/effect-runtime.js +139 -0
  10. package/dist/effect-scheduler.d.ts +4 -0
  11. package/dist/effect-scheduler.js +88 -0
  12. package/dist/effect-utils.d.ts +19 -0
  13. package/dist/effect-utils.js +160 -0
  14. package/dist/env.d.ts +12 -0
  15. package/dist/env.js +18 -2
  16. package/dist/events.d.ts +1 -8
  17. package/dist/events.js +108 -46
  18. package/dist/expressions.d.ts +14 -0
  19. package/dist/expressions.js +295 -0
  20. package/dist/fragment-patch.d.ts +12 -0
  21. package/dist/fragment-patch.js +118 -0
  22. package/dist/hydrate.d.ts +3 -117
  23. package/dist/hydrate.js +235 -1817
  24. package/dist/index.d.ts +3 -2
  25. package/dist/index.js +3 -2
  26. package/dist/markup.d.ts +8 -0
  27. package/dist/markup.js +127 -0
  28. package/dist/mount-runtime.d.ts +1 -0
  29. package/dist/mount-runtime.js +39 -0
  30. package/dist/payload.d.ts +21 -0
  31. package/dist/payload.js +386 -0
  32. package/dist/presence.d.ts +67 -0
  33. package/dist/presence.js +297 -0
  34. package/dist/reactivity-core.d.ts +3 -0
  35. package/dist/reactivity-core.js +22 -0
  36. package/dist/render.d.ts +3 -0
  37. package/dist/render.js +340 -0
  38. package/dist/scanner.d.ts +1 -0
  39. package/dist/scanner.js +61 -0
  40. package/dist/side-effect-scope.d.ts +16 -0
  41. package/dist/side-effect-scope.js +99 -0
  42. package/dist/signal.js +1 -1
  43. package/dist/state.js +1 -1
  44. package/dist/template-parser.d.ts +1 -0
  45. package/dist/template-parser.js +268 -0
  46. package/dist/template.js +10 -11
  47. package/dist/zeneffect.d.ts +12 -14
  48. package/dist/zeneffect.js +25 -519
  49. package/package.json +5 -3
@@ -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;
@@ -0,0 +1,3 @@
1
+ export declare function _nextReactiveId(): number;
2
+ export declare function _trackDependency(source: any): void;
3
+ export declare function runWithDependencyCollector(collector: any, callback: any): any;
@@ -0,0 +1,22 @@
1
+ // @ts-nocheck
2
+ let _activeDependencyCollector = null;
3
+ let _reactiveIdCounter = 0;
4
+ export function _nextReactiveId() {
5
+ _reactiveIdCounter += 1;
6
+ return _reactiveIdCounter;
7
+ }
8
+ export function _trackDependency(source) {
9
+ if (typeof _activeDependencyCollector === 'function') {
10
+ _activeDependencyCollector(source);
11
+ }
12
+ }
13
+ export function runWithDependencyCollector(collector, callback) {
14
+ const previousCollector = _activeDependencyCollector;
15
+ _activeDependencyCollector = typeof collector === 'function' ? collector : null;
16
+ try {
17
+ return callback();
18
+ }
19
+ finally {
20
+ _activeDependencyCollector = previousCollector;
21
+ }
22
+ }
@@ -0,0 +1,3 @@
1
+ export function _applyMarkerValue(nodes: any, marker: any, value: any): void;
2
+ export function _createContextualFragment(parent: any, html: any): any;
3
+ export function _coerceText(value: any, path?: string): string;
package/dist/render.js ADDED
@@ -0,0 +1,340 @@
1
+ import { rethrowZenithRuntimeError, throwZenithRuntimeError, DOCS_LINKS } from './diagnostics.js';
2
+ import { createFragmentRegion } from './fragment-patch.js';
3
+ import { _isHtmlFragment } from './markup.js';
4
+ const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
5
+ const BOOLEAN_ATTRIBUTES = new Set([
6
+ 'disabled', 'checked', 'selected', 'readonly', 'multiple',
7
+ 'hidden', 'autofocus', 'required', 'open'
8
+ ]);
9
+ export function _applyMarkerValue(nodes, marker, value) {
10
+ const markerPath = `marker[${marker.index}]`;
11
+ for (let i = 0; i < nodes.length; i++) {
12
+ try {
13
+ const node = nodes[i];
14
+ if (marker.kind === 'text') {
15
+ if (node && node.nodeType === 8) {
16
+ _applyCommentMarkerValue(node, value, `${markerPath}.text`);
17
+ continue;
18
+ }
19
+ if (_isStructuralFragment(value)) {
20
+ _mountStructuralFragment(node, value, `${markerPath}.text`);
21
+ continue;
22
+ }
23
+ const html = _renderFragmentValue(value, `${markerPath}.text`);
24
+ if (html !== null) {
25
+ node.innerHTML = html;
26
+ }
27
+ else {
28
+ node.textContent = _coerceText(value, `${markerPath}.text`);
29
+ }
30
+ continue;
31
+ }
32
+ if (marker.kind === 'attr') {
33
+ _applyAttribute(node, marker.attr, value);
34
+ }
35
+ }
36
+ catch (error) {
37
+ rethrowZenithRuntimeError(error, {
38
+ phase: 'bind',
39
+ code: 'BINDING_APPLY_FAILED',
40
+ message: `Failed to apply ${marker.kind} binding at marker ${marker.index}`,
41
+ marker: {
42
+ type: marker.kind === 'attr' ? `attr:${marker.attr}` : marker.kind,
43
+ id: marker.index
44
+ },
45
+ path: marker.kind === 'attr'
46
+ ? `${markerPath}.attr.${marker.attr}`
47
+ : `${markerPath}.${marker.kind}`,
48
+ hint: 'Check the binding value type and marker mapping.',
49
+ docsLink: DOCS_LINKS.markerTable,
50
+ source: marker.source
51
+ });
52
+ }
53
+ }
54
+ }
55
+ function _applyCommentMarkerValue(anchor, value, rootPath) {
56
+ if (_isStructuralFragment(value)) {
57
+ _mountStructuralFragmentIntoCommentRange(anchor, value, rootPath);
58
+ return;
59
+ }
60
+ const end = _clearCommentPlaceholderContent(anchor);
61
+ const parent = end.parentNode;
62
+ if (!parent) {
63
+ return;
64
+ }
65
+ const html = _renderFragmentValue(value, rootPath);
66
+ if (html !== null) {
67
+ parent.insertBefore(_createContextualFragment(parent, html), end);
68
+ return;
69
+ }
70
+ const textNode = (parent.ownerDocument || document).createTextNode(_coerceText(value, rootPath));
71
+ parent.insertBefore(textNode, end);
72
+ }
73
+ export function _createContextualFragment(parent, html) {
74
+ const doc = parent.ownerDocument || document;
75
+ if (!doc || typeof doc.createRange !== 'function') {
76
+ throw new Error('[Zenith Runtime] comment placeholder HTML rendering requires Range#createContextualFragment');
77
+ }
78
+ const range = doc.createRange();
79
+ range.selectNode(parent);
80
+ return range.createContextualFragment(html);
81
+ }
82
+ function _isStructuralFragment(value) {
83
+ if (Array.isArray(value)) {
84
+ for (let i = 0; i < value.length; i++) {
85
+ if (_isStructuralFragment(value[i])) {
86
+ return true;
87
+ }
88
+ }
89
+ return false;
90
+ }
91
+ return value && typeof value === 'object' && value.__zenith_fragment === true && typeof value.mount === 'function';
92
+ }
93
+ function _ensureCommentPlaceholderEnd(anchor) {
94
+ let end = anchor.__z_range_end || null;
95
+ if (end && end.parentNode === anchor.parentNode) {
96
+ return end;
97
+ }
98
+ const parent = anchor.parentNode;
99
+ if (!parent) {
100
+ return null;
101
+ }
102
+ end = (anchor.ownerDocument || document).createComment(`/ ${anchor.data}`);
103
+ parent.insertBefore(end, anchor.nextSibling);
104
+ anchor.__z_range_end = end;
105
+ return end;
106
+ }
107
+ function _clearCommentPlaceholderContent(anchor) {
108
+ if (anchor.__z_unmounts) {
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 = [];
118
+ const end = _ensureCommentPlaceholderEnd(anchor);
119
+ if (!end) {
120
+ return anchor;
121
+ }
122
+ let current = anchor.nextSibling;
123
+ while (current && current !== end) {
124
+ const next = current.nextSibling;
125
+ if (current.parentNode) {
126
+ current.parentNode.removeChild(current);
127
+ }
128
+ current = next;
129
+ }
130
+ return end;
131
+ }
132
+ function _mountStructuralFragmentIntoCommentRange(anchor, value, rootPath = 'renderable') {
133
+ let region = anchor.__z_fragment_region;
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
+ }
148
+ return;
149
+ }
150
+ const end = _clearCommentPlaceholderContent(anchor);
151
+ const parent = end.parentNode;
152
+ if (!parent) {
153
+ return;
154
+ }
155
+ region = createFragmentRegion();
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
+ }];
175
+ }
176
+ function _mountStructuralFragment(container, value, rootPath = 'renderable') {
177
+ let region = container.__z_fragment_region;
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
+ }
192
+ return;
193
+ }
194
+ if (container.__z_unmounts) {
195
+ for (let i = 0; i < container.__z_unmounts.length; i++) {
196
+ try {
197
+ container.__z_unmounts[i]();
198
+ }
199
+ catch {
200
+ }
201
+ }
202
+ }
203
+ container.innerHTML = '';
204
+ region = createFragmentRegion();
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
+ }];
224
+ }
225
+ export function _coerceText(value, path = 'renderable') {
226
+ if (value === null || value === undefined || value === false || value === true) {
227
+ return '';
228
+ }
229
+ if (typeof value === 'function') {
230
+ throwZenithRuntimeError({
231
+ phase: 'render',
232
+ code: 'NON_RENDERABLE_VALUE',
233
+ message: `Zenith Render Error: non-renderable function at ${path}. Use map() to render fields.`,
234
+ path,
235
+ hint: 'Convert functions into explicit event handlers or renderable text.',
236
+ docsLink: DOCS_LINKS.expressionScope
237
+ });
238
+ }
239
+ if (value && typeof value === 'object') {
240
+ throwZenithRuntimeError({
241
+ phase: 'render',
242
+ code: 'NON_RENDERABLE_VALUE',
243
+ message: `Zenith Render Error: non-renderable object at ${path}. Use map() to render fields.`,
244
+ path,
245
+ hint: 'Use map() to render object fields into nodes.',
246
+ docsLink: DOCS_LINKS.expressionScope
247
+ });
248
+ }
249
+ return String(value);
250
+ }
251
+ function _renderFragmentValue(value, path = 'renderable') {
252
+ if (value === null || value === undefined || value === false || value === true) {
253
+ return '';
254
+ }
255
+ if (Array.isArray(value)) {
256
+ let out = '';
257
+ for (let i = 0; i < value.length; i++) {
258
+ const itemPath = `${path}[${i}]`;
259
+ const piece = _renderFragmentValue(value[i], itemPath);
260
+ if (piece !== null) {
261
+ out += piece;
262
+ continue;
263
+ }
264
+ out += _escapeHtml(_coerceText(value[i], itemPath));
265
+ }
266
+ return out;
267
+ }
268
+ if (_isHtmlFragment(value)) {
269
+ return value.html;
270
+ }
271
+ return null;
272
+ }
273
+ function _escapeHtml(input) {
274
+ return String(input)
275
+ .replace(/&/g, '&amp;')
276
+ .replace(/</g, '&lt;')
277
+ .replace(/>/g, '&gt;')
278
+ .replace(/"/g, '&quot;')
279
+ .replace(/'/g, '&#39;');
280
+ }
281
+ function _applyAttribute(node, attrName, value) {
282
+ if (typeof attrName === 'string' && attrName.toLowerCase() === 'innerhtml') {
283
+ throwZenithRuntimeError({
284
+ phase: 'bind',
285
+ code: 'UNSAFE_HTML_REQUIRES_EXPLICIT_BOUNDARY',
286
+ message: 'innerHTML bindings are forbidden in Zenith',
287
+ path: `attr:${attrName}`,
288
+ hint: 'Use unsafeHTML={value} for explicit raw HTML insertion, or embedded markup expressions for compiler-owned fragments.'
289
+ });
290
+ }
291
+ if (typeof attrName === 'string' && attrName.toLowerCase() === 'unsafehtml') {
292
+ node.innerHTML = value === null || value === undefined || value === false ? '' : String(value);
293
+ return;
294
+ }
295
+ if (attrName === 'class' || attrName === 'className') {
296
+ const classValue = value === null || value === undefined || value === false ? '' : String(value);
297
+ if (node && node.namespaceURI === SVG_NAMESPACE && typeof node.setAttribute === 'function') {
298
+ node.setAttribute('class', classValue);
299
+ return;
300
+ }
301
+ node.className = classValue;
302
+ return;
303
+ }
304
+ if (attrName === 'style') {
305
+ if (value === null || value === undefined || value === false) {
306
+ node.removeAttribute('style');
307
+ return;
308
+ }
309
+ if (typeof value === 'string') {
310
+ node.setAttribute('style', value);
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;
322
+ }
323
+ node.setAttribute('style', String(value));
324
+ return;
325
+ }
326
+ if (BOOLEAN_ATTRIBUTES.has(attrName)) {
327
+ if (value) {
328
+ node.setAttribute(attrName, '');
329
+ }
330
+ else {
331
+ node.removeAttribute(attrName);
332
+ }
333
+ return;
334
+ }
335
+ if (value === null || value === undefined || value === false) {
336
+ node.removeAttribute(attrName);
337
+ return;
338
+ }
339
+ node.setAttribute(attrName, String(value));
340
+ }
@@ -0,0 +1 @@
1
+ export function createNodeResolver(root: any): (selector: any, index: any, kind: any, source?: undefined) => any;