@zenithbuild/runtime 0.7.9 → 0.7.11
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/dist/cleanup.js +8 -4
- package/dist/effect-runtime.d.ts +2 -2
- package/dist/effect-runtime.js +97 -62
- package/dist/effect-utils.d.ts +3 -1
- package/dist/effect-utils.js +27 -2
- package/dist/markup.js +123 -0
- package/dist/mount-runtime.js +2 -2
- package/dist/render.js +20 -9
- package/dist/side-effect-scope.d.ts +2 -2
- package/dist/side-effect-scope.js +5 -8
- package/dist/zeneffect.d.ts +3 -3
- package/package.json +1 -1
package/dist/cleanup.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// Calling cleanup() twice is a no-op.
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
import { resetGlobalSideEffects } from './side-effect-scope.js';
|
|
14
|
+
import { runCleanupCallback, throwCleanupErrors } from './effect-utils.js';
|
|
14
15
|
/** @type {function[]} */
|
|
15
16
|
const _disposers = [];
|
|
16
17
|
/** @type {{ element: Element, event: string, handler: function }[]} */
|
|
@@ -45,28 +46,31 @@ export function _registerListener(element, event, handler) {
|
|
|
45
46
|
* - Idempotent: calling twice is a no-op
|
|
46
47
|
*/
|
|
47
48
|
export function cleanup() {
|
|
49
|
+
const errors = [];
|
|
48
50
|
// Global zenMount/zenEffect registrations can be created outside hydrate's
|
|
49
51
|
// disposer table (e.g. page-level component bootstraps). cleanup() is the
|
|
50
52
|
// full runtime teardown boundary, so that scope must be reset on every pass,
|
|
51
53
|
// including the first real cleanup.
|
|
52
54
|
if (_cleaned) {
|
|
53
|
-
resetGlobalSideEffects();
|
|
55
|
+
resetGlobalSideEffects(errors);
|
|
56
|
+
throwCleanupErrors(errors, 'cleanup');
|
|
54
57
|
return;
|
|
55
58
|
}
|
|
56
59
|
// 1. Dispose all effects
|
|
57
60
|
for (let i = 0; i < _disposers.length; i++) {
|
|
58
|
-
_disposers[i]
|
|
61
|
+
runCleanupCallback(_disposers[i], errors);
|
|
59
62
|
}
|
|
60
63
|
_disposers.length = 0;
|
|
61
64
|
// 2. Remove all event listeners
|
|
62
65
|
for (let i = 0; i < _listeners.length; i++) {
|
|
63
66
|
const { element, event, handler } = _listeners[i];
|
|
64
|
-
element.removeEventListener(event, handler);
|
|
67
|
+
runCleanupCallback(() => element.removeEventListener(event, handler), errors);
|
|
65
68
|
}
|
|
66
69
|
_listeners.length = 0;
|
|
67
70
|
// 3. Dispose any top-level reactive work registered outside hydrate.
|
|
68
|
-
resetGlobalSideEffects();
|
|
71
|
+
resetGlobalSideEffects(errors);
|
|
69
72
|
_cleaned = true;
|
|
73
|
+
throwCleanupErrors(errors, 'cleanup');
|
|
70
74
|
}
|
|
71
75
|
/**
|
|
72
76
|
* Get counts for testing/debugging.
|
package/dist/effect-runtime.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function createAutoTrackedEffect(effect: any, options: any, scope: any): () => void;
|
|
2
|
-
export declare function createExplicitDependencyEffect(effect: any, dependencies: any, scope: any): () => void;
|
|
1
|
+
export declare function createAutoTrackedEffect(effect: any, options: any, scope: any): (errors?: null) => void;
|
|
2
|
+
export declare function createExplicitDependencyEffect(effect: any, dependencies: any, scope: any): (errors?: null) => void;
|
package/dist/effect-runtime.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import { runWithDependencyCollector } from './reactivity-core.js';
|
|
3
3
|
import { registerScopeDisposer, queueWhenScopeReady } from './side-effect-scope.js';
|
|
4
|
-
import { drainCleanupStack, applyCleanupResult, createEffectContext } from './effect-utils.js';
|
|
4
|
+
import { drainCleanupStack, applyCleanupResult, createEffectContext, runCleanupCallback } from './effect-utils.js';
|
|
5
5
|
import { createScheduler } from './effect-scheduler.js';
|
|
6
6
|
let _effectIdCounter = 0;
|
|
7
7
|
export function createAutoTrackedEffect(effect, options, scope) {
|
|
8
8
|
let disposed = false;
|
|
9
|
+
let completedSetup = false;
|
|
9
10
|
const activeSubscriptions = new Map();
|
|
10
11
|
const runCleanups = [];
|
|
11
12
|
_effectIdCounter += 1;
|
|
@@ -22,47 +23,56 @@ export function createAutoTrackedEffect(effect, options, scope) {
|
|
|
22
23
|
}
|
|
23
24
|
drainCleanupStack(runCleanups);
|
|
24
25
|
const nextDependenciesById = new Map();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
nextDependenciesById.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
26
|
+
try {
|
|
27
|
+
runWithDependencyCollector((source) => {
|
|
28
|
+
if (!source || typeof source.subscribe !== 'function') {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const reactiveId = Number.isInteger(source.__zenith_id) ? source.__zenith_id : 0;
|
|
32
|
+
if (!nextDependenciesById.has(reactiveId)) {
|
|
33
|
+
nextDependenciesById.set(reactiveId, source);
|
|
34
|
+
}
|
|
35
|
+
}, () => {
|
|
36
|
+
const result = effect(createEffectContext(registerCleanup));
|
|
37
|
+
applyCleanupResult(result, registerCleanup);
|
|
38
|
+
});
|
|
39
|
+
const nextDependencies = Array.from(nextDependenciesById.values()).sort((left, right) => {
|
|
40
|
+
const leftId = Number.isInteger(left.__zenith_id) ? left.__zenith_id : 0;
|
|
41
|
+
const rightId = Number.isInteger(right.__zenith_id) ? right.__zenith_id : 0;
|
|
42
|
+
return leftId - rightId;
|
|
43
|
+
});
|
|
44
|
+
const nextSet = new Set(nextDependencies);
|
|
45
|
+
for (const [dependency, unsubscribe] of activeSubscriptions.entries()) {
|
|
46
|
+
if (nextSet.has(dependency)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (typeof unsubscribe === 'function') {
|
|
50
|
+
unsubscribe();
|
|
51
|
+
}
|
|
52
|
+
activeSubscriptions.delete(dependency);
|
|
46
53
|
}
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
for (let i = 0; i < nextDependencies.length; i++) {
|
|
55
|
+
const dependency = nextDependencies[i];
|
|
56
|
+
if (activeSubscriptions.has(dependency)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const unsubscribe = dependency.subscribe(() => {
|
|
60
|
+
scheduler.schedule();
|
|
61
|
+
});
|
|
62
|
+
activeSubscriptions.set(dependency, typeof unsubscribe === 'function' ? unsubscribe : () => { });
|
|
49
63
|
}
|
|
50
|
-
|
|
64
|
+
completedSetup = true;
|
|
51
65
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
continue;
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (!completedSetup) {
|
|
68
|
+
disposeEffect();
|
|
56
69
|
}
|
|
57
|
-
|
|
58
|
-
scheduler.schedule();
|
|
59
|
-
});
|
|
60
|
-
activeSubscriptions.set(dependency, typeof unsubscribe === 'function' ? unsubscribe : () => { });
|
|
70
|
+
throw error;
|
|
61
71
|
}
|
|
62
72
|
void effectId;
|
|
63
73
|
}
|
|
64
74
|
const scheduler = createScheduler(runEffectNow, options);
|
|
65
|
-
function disposeEffect() {
|
|
75
|
+
function disposeEffect(errors = null) {
|
|
66
76
|
if (disposed) {
|
|
67
77
|
return;
|
|
68
78
|
}
|
|
@@ -70,11 +80,11 @@ export function createAutoTrackedEffect(effect, options, scope) {
|
|
|
70
80
|
scheduler.cancel();
|
|
71
81
|
for (const unsubscribe of activeSubscriptions.values()) {
|
|
72
82
|
if (typeof unsubscribe === 'function') {
|
|
73
|
-
unsubscribe
|
|
83
|
+
runCleanupCallback(unsubscribe, errors);
|
|
74
84
|
}
|
|
75
85
|
}
|
|
76
86
|
activeSubscriptions.clear();
|
|
77
|
-
drainCleanupStack(runCleanups);
|
|
87
|
+
drainCleanupStack(runCleanups, errors);
|
|
78
88
|
}
|
|
79
89
|
registerScopeDisposer(scope, disposeEffect);
|
|
80
90
|
queueWhenScopeReady(scope, () => scheduler.schedule());
|
|
@@ -91,7 +101,9 @@ export function createExplicitDependencyEffect(effect, dependencies, scope) {
|
|
|
91
101
|
throw new Error('[Zenith Runtime] zeneffect(deps, fn) requires a function');
|
|
92
102
|
}
|
|
93
103
|
let disposed = false;
|
|
104
|
+
let completedSetup = false;
|
|
94
105
|
const runCleanups = [];
|
|
106
|
+
const unsubscribers = [];
|
|
95
107
|
function registerCleanup(cleanup) {
|
|
96
108
|
if (typeof cleanup !== 'function') {
|
|
97
109
|
throw new Error('[Zenith Runtime] cleanup(fn) requires a function');
|
|
@@ -103,37 +115,60 @@ export function createExplicitDependencyEffect(effect, dependencies, scope) {
|
|
|
103
115
|
return;
|
|
104
116
|
}
|
|
105
117
|
drainCleanupStack(runCleanups);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (scope?.mountReady === true) {
|
|
115
|
-
runEffectNow();
|
|
116
|
-
return;
|
|
118
|
+
try {
|
|
119
|
+
const result = effect(createEffectContext(registerCleanup));
|
|
120
|
+
applyCleanupResult(result, registerCleanup);
|
|
121
|
+
completedSetup = true;
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (!completedSetup) {
|
|
125
|
+
dispose();
|
|
117
126
|
}
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
if (scope?.mountReady === true) {
|
|
122
|
-
runEffectNow();
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
queueWhenScopeReady(scope, runEffectNow);
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
126
129
|
}
|
|
127
|
-
|
|
130
|
+
function dispose(errors = null) {
|
|
128
131
|
if (disposed) {
|
|
129
132
|
return;
|
|
130
133
|
}
|
|
131
134
|
disposed = true;
|
|
132
|
-
for (let i =
|
|
133
|
-
unsubscribers[i]
|
|
135
|
+
for (let i = unsubscribers.length - 1; i >= 0; i--) {
|
|
136
|
+
if (typeof unsubscribers[i] === 'function') {
|
|
137
|
+
runCleanupCallback(unsubscribers[i], errors);
|
|
138
|
+
}
|
|
134
139
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
140
|
+
unsubscribers.length = 0;
|
|
141
|
+
drainCleanupStack(runCleanups, errors);
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
for (let index = 0; index < dependencies.length; index++) {
|
|
145
|
+
const dep = dependencies[index];
|
|
146
|
+
if (!dep || typeof dep.subscribe !== 'function') {
|
|
147
|
+
throw new Error(`[Zenith Runtime] zeneffect dependency at index ${index} must expose subscribe(fn)`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
for (let index = 0; index < dependencies.length; index++) {
|
|
151
|
+
const dep = dependencies[index];
|
|
152
|
+
const unsubscribe = dep.subscribe(() => {
|
|
153
|
+
if (scope?.mountReady === true) {
|
|
154
|
+
runEffectNow();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
queueWhenScopeReady(scope, runEffectNow);
|
|
158
|
+
});
|
|
159
|
+
unsubscribers.push(typeof unsubscribe === 'function' ? unsubscribe : () => { });
|
|
160
|
+
}
|
|
161
|
+
if (scope?.mountReady === true) {
|
|
162
|
+
runEffectNow();
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
queueWhenScopeReady(scope, runEffectNow);
|
|
166
|
+
}
|
|
167
|
+
registerScopeDisposer(scope, dispose);
|
|
168
|
+
return dispose;
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
dispose();
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
139
174
|
}
|
package/dist/effect-utils.d.ts
CHANGED
|
@@ -5,7 +5,9 @@ export declare function normalizeEffectOptions(options: any): {
|
|
|
5
5
|
raf: boolean;
|
|
6
6
|
flush: string;
|
|
7
7
|
};
|
|
8
|
-
export declare function drainCleanupStack(cleanups: any): void;
|
|
8
|
+
export declare function drainCleanupStack(cleanups: any, errors?: null): void;
|
|
9
|
+
export declare function runCleanupCallback(callback: any, errors?: null): void;
|
|
10
|
+
export declare function throwCleanupErrors(errors: any, label?: string): void;
|
|
9
11
|
export declare function applyCleanupResult(result: any, registerCleanup: any): void;
|
|
10
12
|
export declare function createMountContext(registerCleanup: any): {
|
|
11
13
|
cleanup: any;
|
package/dist/effect-utils.js
CHANGED
|
@@ -38,7 +38,8 @@ export function normalizeEffectOptions(options) {
|
|
|
38
38
|
}
|
|
39
39
|
return normalized;
|
|
40
40
|
}
|
|
41
|
-
export function drainCleanupStack(cleanups) {
|
|
41
|
+
export function drainCleanupStack(cleanups, errors = null) {
|
|
42
|
+
const errorSink = Array.isArray(errors) ? errors : null;
|
|
42
43
|
for (let i = cleanups.length - 1; i >= 0; i--) {
|
|
43
44
|
const cleanup = cleanups[i];
|
|
44
45
|
if (typeof cleanup !== 'function') {
|
|
@@ -47,11 +48,35 @@ export function drainCleanupStack(cleanups) {
|
|
|
47
48
|
try {
|
|
48
49
|
cleanup();
|
|
49
50
|
}
|
|
50
|
-
catch {
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (errorSink) {
|
|
53
|
+
errorSink.push(error);
|
|
54
|
+
}
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
cleanups.length = 0;
|
|
54
58
|
}
|
|
59
|
+
export function runCleanupCallback(callback, errors = null) {
|
|
60
|
+
try {
|
|
61
|
+
callback();
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (Array.isArray(errors)) {
|
|
65
|
+
errors.push(error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function throwCleanupErrors(errors, label = 'cleanup') {
|
|
70
|
+
if (!Array.isArray(errors) || errors.length === 0) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const message = `[Zenith Runtime] ${label} failed with ${errors.length} error(s)`;
|
|
74
|
+
const error = typeof AggregateError === 'function'
|
|
75
|
+
? new AggregateError(errors, message)
|
|
76
|
+
: new Error(message);
|
|
77
|
+
error.zenithCleanupErrors = errors;
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
55
80
|
export function applyCleanupResult(result, registerCleanup) {
|
|
56
81
|
if (typeof result === 'function') {
|
|
57
82
|
registerCleanup(result);
|
package/dist/markup.js
CHANGED
|
@@ -10,6 +10,19 @@ const _FRAGMENT_EVENT_ATTR_RE = /\bon[a-z]+\s*=/i;
|
|
|
10
10
|
const _FRAGMENT_JS_URL_RE = /javascript\s*:/i;
|
|
11
11
|
const _FRAGMENT_PROTO_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
12
12
|
const _FRAGMENT_SCRIPT_CLOSE_RE = /<\/script/gi;
|
|
13
|
+
const _FRAGMENT_URL_ATTRS = new Set(['href', 'src', 'srcset', 'action', 'formaction', 'poster', 'xlink:href']);
|
|
14
|
+
const _FRAGMENT_ALLOWED_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:']);
|
|
15
|
+
const _FRAGMENT_URL_ATTR_RE = /\s([:@A-Za-z0-9_.-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/g;
|
|
16
|
+
const _FRAGMENT_NAMED_ENTITIES = {
|
|
17
|
+
amp: '&',
|
|
18
|
+
apos: "'",
|
|
19
|
+
colon: ':',
|
|
20
|
+
gt: '>',
|
|
21
|
+
lt: '<',
|
|
22
|
+
newline: '\n',
|
|
23
|
+
quot: '"',
|
|
24
|
+
tab: '\t'
|
|
25
|
+
};
|
|
13
26
|
function _createHtmlFragment(html) {
|
|
14
27
|
return {
|
|
15
28
|
__zenith_fragment: true,
|
|
@@ -65,9 +78,119 @@ export function _fragment(strings, ...values) {
|
|
|
65
78
|
hint: 'javascript: URLs are forbidden.'
|
|
66
79
|
});
|
|
67
80
|
}
|
|
81
|
+
_validateFragmentUrlAttributes(result);
|
|
68
82
|
result = result.replace(_FRAGMENT_SCRIPT_CLOSE_RE, '<\\/script');
|
|
69
83
|
return _createHtmlFragment(result);
|
|
70
84
|
}
|
|
85
|
+
function _validateFragmentUrlAttributes(html) {
|
|
86
|
+
if (_validateFragmentUrlAttributesWithTemplate(html)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
_FRAGMENT_URL_ATTR_RE.lastIndex = 0;
|
|
90
|
+
for (const match of html.matchAll(_FRAGMENT_URL_ATTR_RE)) {
|
|
91
|
+
const attrName = String(match[1] || '').toLowerCase();
|
|
92
|
+
if (!_FRAGMENT_URL_ATTRS.has(attrName)) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
_validateFragmentUrlAttribute(attrName, match[2] ?? match[3] ?? match[4] ?? '');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function _validateFragmentUrlAttributesWithTemplate(html) {
|
|
99
|
+
const doc = globalThis.document;
|
|
100
|
+
if (!doc || typeof doc.createElement !== 'function') {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
const template = doc.createElement('template');
|
|
104
|
+
if (!template || !('innerHTML' in template)) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
template.innerHTML = html;
|
|
108
|
+
const stack = Array.from(template.content?.childNodes || []);
|
|
109
|
+
while (stack.length > 0) {
|
|
110
|
+
const node = stack.pop();
|
|
111
|
+
if (!node) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (node.nodeType === 1 && node.attributes) {
|
|
115
|
+
for (let i = 0; i < node.attributes.length; i++) {
|
|
116
|
+
const attr = node.attributes[i];
|
|
117
|
+
const attrName = String(attr.name || '').toLowerCase();
|
|
118
|
+
if (_FRAGMENT_URL_ATTRS.has(attrName)) {
|
|
119
|
+
_validateFragmentUrlAttribute(attrName, attr.value || '');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const children = node.childNodes ? Array.from(node.childNodes) : [];
|
|
124
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
125
|
+
stack.push(children[i]);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
function _validateFragmentUrlAttribute(attrName, value) {
|
|
131
|
+
if (attrName === 'srcset') {
|
|
132
|
+
_validateFragmentSrcsetAttribute(value);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
_validateFragmentSingleUrlAttribute(attrName, value);
|
|
136
|
+
}
|
|
137
|
+
function _validateFragmentSingleUrlAttribute(attrName, value) {
|
|
138
|
+
const protocol = _normalizedFragmentUrlProtocol(value);
|
|
139
|
+
if (protocol && !_FRAGMENT_ALLOWED_PROTOCOLS.has(protocol)) {
|
|
140
|
+
throwZenithRuntimeError({
|
|
141
|
+
phase: 'render',
|
|
142
|
+
code: 'NON_RENDERABLE_VALUE',
|
|
143
|
+
message: `Embedded markup expression contains unsafe URL protocol in ${attrName}`,
|
|
144
|
+
hint: 'Use a relative URL or an allowed absolute URL.'
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function _validateFragmentSrcsetAttribute(value) {
|
|
149
|
+
const candidates = String(value || '').split(',');
|
|
150
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
151
|
+
const candidate = candidates[i].trim();
|
|
152
|
+
if (!candidate) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const url = candidate.split(/[\u0000-\u001F\u007F\s]+/, 1)[0] || '';
|
|
156
|
+
_validateFragmentSingleUrlAttribute('srcset', url);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function _normalizedFragmentUrlProtocol(value) {
|
|
160
|
+
const normalized = _decodeFragmentAttributeValue(value)
|
|
161
|
+
.trim()
|
|
162
|
+
.replace(/[\u0000-\u001F\u007F\s]+/g, '');
|
|
163
|
+
const match = /^([A-Za-z][A-Za-z0-9+.-]*):/.exec(normalized);
|
|
164
|
+
return match ? `${match[1].toLowerCase()}:` : '';
|
|
165
|
+
}
|
|
166
|
+
function _decodeFragmentAttributeValue(value) {
|
|
167
|
+
let decoded = String(value || '');
|
|
168
|
+
for (let pass = 0; pass < 3; pass++) {
|
|
169
|
+
const next = decoded.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[A-Za-z][A-Za-z0-9]+);?/g, (_, body) => {
|
|
170
|
+
if (body[0] === '#') {
|
|
171
|
+
const radix = body[1] === 'x' || body[1] === 'X' ? 16 : 10;
|
|
172
|
+
const digits = radix === 16 ? body.slice(2) : body.slice(1);
|
|
173
|
+
const codePoint = Number.parseInt(digits, radix);
|
|
174
|
+
if (Number.isFinite(codePoint)) {
|
|
175
|
+
try {
|
|
176
|
+
return String.fromCodePoint(codePoint);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return '';
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return '';
|
|
183
|
+
}
|
|
184
|
+
const named = _FRAGMENT_NAMED_ENTITIES[String(body).toLowerCase()];
|
|
185
|
+
return named === undefined ? `&${body};` : named;
|
|
186
|
+
});
|
|
187
|
+
if (next === decoded) {
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
decoded = next;
|
|
191
|
+
}
|
|
192
|
+
return decoded;
|
|
193
|
+
}
|
|
71
194
|
function _fragmentInterpolate(val, interpolationIndex) {
|
|
72
195
|
if (val === null || val === undefined || val === false) {
|
|
73
196
|
return '';
|
package/dist/mount-runtime.js
CHANGED
|
@@ -23,8 +23,8 @@ export function createMountEffect(callback, scope) {
|
|
|
23
23
|
catch (error) {
|
|
24
24
|
console.error('[Zenith Runtime] Unhandled error during zenMount:', error);
|
|
25
25
|
}
|
|
26
|
-
registeredDisposer = registerScopeDisposer(scope, () => {
|
|
27
|
-
drainCleanupStack(cleanups);
|
|
26
|
+
registeredDisposer = registerScopeDisposer(scope, (errors = null) => {
|
|
27
|
+
drainCleanupStack(cleanups, errors);
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
queueWhenScopeReady(scope, runMount);
|
package/dist/render.js
CHANGED
|
@@ -21,13 +21,7 @@ export function _applyMarkerValue(nodes, marker, value) {
|
|
|
21
21
|
_mountStructuralFragment(node, value, textPath);
|
|
22
22
|
continue;
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
if (html !== null) {
|
|
26
|
-
node.innerHTML = html;
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
node.textContent = _coerceText(value, textPath);
|
|
30
|
-
}
|
|
24
|
+
_replaceTextMarkerContent(node, value, textPath);
|
|
31
25
|
continue;
|
|
32
26
|
}
|
|
33
27
|
if (marker.kind === 'attr') {
|
|
@@ -136,10 +130,22 @@ function _mountStructuralFragment(container, value, rootPath = 'renderable') {
|
|
|
136
130
|
if (_tryUpdateFragmentTarget(container, value, container, null, rootPath)) {
|
|
137
131
|
return;
|
|
138
132
|
}
|
|
139
|
-
|
|
140
|
-
container.
|
|
133
|
+
_clearStructuralTarget(container);
|
|
134
|
+
while (container.firstChild) {
|
|
135
|
+
container.removeChild(container.firstChild);
|
|
136
|
+
}
|
|
141
137
|
_mountFragmentRegion(container, value, container, null, rootPath);
|
|
142
138
|
}
|
|
139
|
+
function _replaceTextMarkerContent(node, value, rootPath) {
|
|
140
|
+
_clearStructuralTarget(node);
|
|
141
|
+
const html = _renderFragmentValue(value, rootPath);
|
|
142
|
+
if (html !== null) {
|
|
143
|
+
node.innerHTML = html;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
node.textContent = _coerceText(value, rootPath);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
143
149
|
export function _coerceText(value, path = 'renderable') {
|
|
144
150
|
if (value === null || value === undefined || value === false || value === true) {
|
|
145
151
|
return '';
|
|
@@ -258,6 +264,11 @@ function _runUnmounts(target) {
|
|
|
258
264
|
}
|
|
259
265
|
target.__z_unmounts = [];
|
|
260
266
|
}
|
|
267
|
+
function _clearStructuralTarget(target) {
|
|
268
|
+
_runUnmounts(target);
|
|
269
|
+
target.__z_fragment_region = null;
|
|
270
|
+
target.__z_fragment_region_active = false;
|
|
271
|
+
}
|
|
261
272
|
function _updateFragmentRegion(region, value, parent, insertBefore, rootPath) {
|
|
262
273
|
try {
|
|
263
274
|
region.update(value, { parent, insertBefore, rootPath });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare function isSideEffectScope(value: any): boolean;
|
|
2
2
|
export declare function resolveSideEffectScope(scopeOverride: any): any;
|
|
3
|
-
export declare function resetGlobalSideEffects(): void;
|
|
3
|
+
export declare function resetGlobalSideEffects(errors?: null): void;
|
|
4
4
|
export declare function createSideEffectScope(label?: string): {
|
|
5
5
|
__zenith_scope: boolean;
|
|
6
6
|
id: number;
|
|
@@ -12,5 +12,5 @@ export declare function createSideEffectScope(label?: string): {
|
|
|
12
12
|
};
|
|
13
13
|
export declare function activateSideEffectScope(scope: any): void;
|
|
14
14
|
export declare function registerScopeDisposer(scope: any, disposer: any): () => void;
|
|
15
|
-
export declare function disposeSideEffectScope(scope: any): void;
|
|
15
|
+
export declare function disposeSideEffectScope(scope: any, errors?: null): void;
|
|
16
16
|
export declare function queueWhenScopeReady(scope: any, callback: any): void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
|
+
import { runCleanupCallback } from './effect-utils.js';
|
|
2
3
|
let _scopeIdCounter = 0;
|
|
3
4
|
function createInternalScope(label, mountReady) {
|
|
4
5
|
_scopeIdCounter += 1;
|
|
@@ -22,8 +23,8 @@ export function resolveSideEffectScope(scopeOverride) {
|
|
|
22
23
|
}
|
|
23
24
|
return _globalScope;
|
|
24
25
|
}
|
|
25
|
-
export function resetGlobalSideEffects() {
|
|
26
|
-
disposeSideEffectScope(_globalScope);
|
|
26
|
+
export function resetGlobalSideEffects(errors = null) {
|
|
27
|
+
disposeSideEffectScope(_globalScope, errors);
|
|
27
28
|
_globalScope = createInternalScope('global', true);
|
|
28
29
|
}
|
|
29
30
|
export function createSideEffectScope(label = 'anonymous') {
|
|
@@ -67,7 +68,7 @@ export function registerScopeDisposer(scope, disposer) {
|
|
|
67
68
|
}
|
|
68
69
|
};
|
|
69
70
|
}
|
|
70
|
-
export function disposeSideEffectScope(scope) {
|
|
71
|
+
export function disposeSideEffectScope(scope, errors = null) {
|
|
71
72
|
if (!scope || scope.disposed) {
|
|
72
73
|
return;
|
|
73
74
|
}
|
|
@@ -80,11 +81,7 @@ export function disposeSideEffectScope(scope) {
|
|
|
80
81
|
if (typeof disposer !== 'function') {
|
|
81
82
|
continue;
|
|
82
83
|
}
|
|
83
|
-
|
|
84
|
-
disposer();
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
}
|
|
84
|
+
runCleanupCallback(() => disposer(errors), errors);
|
|
88
85
|
}
|
|
89
86
|
}
|
|
90
87
|
export function queueWhenScopeReady(scope, callback) {
|
package/dist/zeneffect.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export { _nextReactiveId, _trackDependency } from './reactivity-core.js';
|
|
2
2
|
export { resetGlobalSideEffects, createSideEffectScope, activateSideEffectScope, disposeSideEffectScope } from './side-effect-scope.js';
|
|
3
|
-
export declare function zenEffect(effect: any, options?: null, scopeOverride?: null): () => void;
|
|
4
|
-
export declare function zeneffect(effectOrDependencies: any, optionsOrEffect: any, scopeOverride?: null): () => void;
|
|
3
|
+
export declare function zenEffect(effect: any, options?: null, scopeOverride?: null): (errors?: null) => void;
|
|
4
|
+
export declare function zeneffect(effectOrDependencies: any, optionsOrEffect: any, scopeOverride?: null): (errors?: null) => void;
|
|
5
5
|
export declare function zenMount(callback: any, scopeOverride?: null): () => void;
|
|
6
6
|
/**
|
|
7
7
|
* @alias zeneffect
|
|
8
8
|
* @description Optional secondary alias for the canonical zeneffect primitive.
|
|
9
9
|
*/
|
|
10
|
-
export declare function effect(effectOrDependencies: any, optionsOrEffect: any, scopeOverride?: null): () => void;
|
|
10
|
+
export declare function effect(effectOrDependencies: any, optionsOrEffect: any, scopeOverride?: null): (errors?: null) => void;
|
|
11
11
|
/**
|
|
12
12
|
* @alias zenMount
|
|
13
13
|
* @description Optional secondary alias for the canonical zenMount primitive.
|