@elurjs/core 3.5.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/LICENSE +21 -0
- package/README.md +157 -0
- package/dist/lib/async.cjs +16 -0
- package/dist/lib/async.js +123 -0
- package/dist/lib/component.cjs +2 -0
- package/dist/lib/component.js +66 -0
- package/dist/lib/context.cjs +2 -0
- package/dist/lib/context.js +52 -0
- package/dist/lib/devtools.cjs +59 -0
- package/dist/lib/devtools.js +419 -0
- package/dist/lib/elur/async.cjs +16 -0
- package/dist/lib/elur/async.cjs.map +1 -0
- package/dist/lib/elur/async.d.cts +32 -0
- package/dist/lib/elur/async.d.ts +32 -0
- package/dist/lib/elur/async.js +123 -0
- package/dist/lib/elur/async.js.map +1 -0
- package/dist/lib/elur/component.cjs +2 -0
- package/dist/lib/elur/component.cjs.map +1 -0
- package/dist/lib/elur/component.d.cts +17 -0
- package/dist/lib/elur/component.d.ts +17 -0
- package/dist/lib/elur/component.js +66 -0
- package/dist/lib/elur/component.js.map +1 -0
- package/dist/lib/elur/context.cjs +2 -0
- package/dist/lib/elur/context.cjs.map +1 -0
- package/dist/lib/elur/context.d.cts +29 -0
- package/dist/lib/elur/context.d.ts +29 -0
- package/dist/lib/elur/context.js +52 -0
- package/dist/lib/elur/context.js.map +1 -0
- package/dist/lib/elur/devtools.cjs +59 -0
- package/dist/lib/elur/devtools.cjs.map +1 -0
- package/dist/lib/elur/devtools.d.cts +24 -0
- package/dist/lib/elur/devtools.d.ts +24 -0
- package/dist/lib/elur/devtools.js +419 -0
- package/dist/lib/elur/devtools.js.map +1 -0
- package/dist/lib/elur/form.cjs +2 -0
- package/dist/lib/elur/form.cjs.map +1 -0
- package/dist/lib/elur/form.d.cts +283 -0
- package/dist/lib/elur/form.d.ts +283 -0
- package/dist/lib/elur/form.js +315 -0
- package/dist/lib/elur/form.js.map +1 -0
- package/dist/lib/elur/hydrate/index.cjs +2 -0
- package/dist/lib/elur/hydrate/index.cjs.map +1 -0
- package/dist/lib/elur/hydrate/index.d.cts +13 -0
- package/dist/lib/elur/hydrate/index.d.ts +13 -0
- package/dist/lib/elur/hydrate/index.js +410 -0
- package/dist/lib/elur/hydrate/index.js.map +1 -0
- package/dist/lib/elur/index.d.cts +19 -0
- package/dist/lib/elur/index.d.ts +19 -0
- package/dist/lib/elur/lifecycle.cjs +2 -0
- package/dist/lib/elur/lifecycle.cjs.map +1 -0
- package/dist/lib/elur/lifecycle.d.cts +45 -0
- package/dist/lib/elur/lifecycle.d.ts +45 -0
- package/dist/lib/elur/lifecycle.js +39 -0
- package/dist/lib/elur/lifecycle.js.map +1 -0
- package/dist/lib/elur/plugins.cjs +2 -0
- package/dist/lib/elur/plugins.cjs.map +1 -0
- package/dist/lib/elur/plugins.d.cts +48 -0
- package/dist/lib/elur/plugins.d.ts +48 -0
- package/dist/lib/elur/plugins.js +59 -0
- package/dist/lib/elur/plugins.js.map +1 -0
- package/dist/lib/elur/reactivity.cjs +2 -0
- package/dist/lib/elur/reactivity.cjs.map +1 -0
- package/dist/lib/elur/reactivity.d.cts +60 -0
- package/dist/lib/elur/reactivity.d.ts +60 -0
- package/dist/lib/elur/reactivity.js +188 -0
- package/dist/lib/elur/reactivity.js.map +1 -0
- package/dist/lib/elur/router.cjs +12 -0
- package/dist/lib/elur/router.cjs.map +1 -0
- package/dist/lib/elur/router.d.cts +137 -0
- package/dist/lib/elur/router.d.ts +137 -0
- package/dist/lib/elur/router.js +513 -0
- package/dist/lib/elur/router.js.map +1 -0
- package/dist/lib/elur/server/index.cjs +2 -0
- package/dist/lib/elur/server/index.cjs.map +1 -0
- package/dist/lib/elur/server/index.d.cts +44 -0
- package/dist/lib/elur/server/index.d.ts +44 -0
- package/dist/lib/elur/server/index.js +307 -0
- package/dist/lib/elur/server/index.js.map +1 -0
- package/dist/lib/elur/store.cjs +2 -0
- package/dist/lib/elur/store.cjs.map +1 -0
- package/dist/lib/elur/store.d.cts +137 -0
- package/dist/lib/elur/store.d.ts +137 -0
- package/dist/lib/elur/store.js +143 -0
- package/dist/lib/elur/store.js.map +1 -0
- package/dist/lib/elur/template/bindings.cjs +2 -0
- package/dist/lib/elur/template/bindings.cjs.map +1 -0
- package/dist/lib/elur/template/bindings.d.cts +40 -0
- package/dist/lib/elur/template/bindings.d.ts +40 -0
- package/dist/lib/elur/template/bindings.js +214 -0
- package/dist/lib/elur/template/bindings.js.map +1 -0
- package/dist/lib/elur/template/dom-write.cjs +2 -0
- package/dist/lib/elur/template/dom-write.cjs.map +1 -0
- package/dist/lib/elur/template/dom-write.d.cts +5 -0
- package/dist/lib/elur/template/dom-write.d.ts +5 -0
- package/dist/lib/elur/template/dom-write.js +16 -0
- package/dist/lib/elur/template/dom-write.js.map +1 -0
- package/dist/lib/elur/template/error-boundary.cjs +2 -0
- package/dist/lib/elur/template/error-boundary.cjs.map +1 -0
- package/dist/lib/elur/template/error-boundary.d.cts +7 -0
- package/dist/lib/elur/template/error-boundary.d.ts +7 -0
- package/dist/lib/elur/template/error-boundary.js +90 -0
- package/dist/lib/elur/template/error-boundary.js.map +1 -0
- package/dist/lib/elur/template/html.cjs +2 -0
- package/dist/lib/elur/template/html.cjs.map +1 -0
- package/dist/lib/elur/template/html.d.cts +8 -0
- package/dist/lib/elur/template/html.d.ts +8 -0
- package/dist/lib/elur/template/html.js +104 -0
- package/dist/lib/elur/template/html.js.map +1 -0
- package/dist/lib/elur/template/index.cjs +1 -0
- package/dist/lib/elur/template/index.d.cts +15 -0
- package/dist/lib/elur/template/index.d.ts +15 -0
- package/dist/lib/elur/template/index.js +13 -0
- package/dist/lib/elur/template/keyed-diff.cjs +2 -0
- package/dist/lib/elur/template/keyed-diff.cjs.map +1 -0
- package/dist/lib/elur/template/keyed-diff.d.cts +32 -0
- package/dist/lib/elur/template/keyed-diff.d.ts +32 -0
- package/dist/lib/elur/template/keyed-diff.js +100 -0
- package/dist/lib/elur/template/keyed-diff.js.map +1 -0
- package/dist/lib/elur/template/keyed.cjs +2 -0
- package/dist/lib/elur/template/keyed.cjs.map +1 -0
- package/dist/lib/elur/template/keyed.d.cts +27 -0
- package/dist/lib/elur/template/keyed.d.ts +27 -0
- package/dist/lib/elur/template/keyed.js +52 -0
- package/dist/lib/elur/template/keyed.js.map +1 -0
- package/dist/lib/elur/template/mount-helpers.cjs +2 -0
- package/dist/lib/elur/template/mount-helpers.cjs.map +1 -0
- package/dist/lib/elur/template/mount-helpers.d.cts +29 -0
- package/dist/lib/elur/template/mount-helpers.d.ts +29 -0
- package/dist/lib/elur/template/mount-helpers.js +132 -0
- package/dist/lib/elur/template/mount-helpers.js.map +1 -0
- package/dist/lib/elur/template/node-binding.cjs +2 -0
- package/dist/lib/elur/template/node-binding.cjs.map +1 -0
- package/dist/lib/elur/template/node-binding.d.cts +6 -0
- package/dist/lib/elur/template/node-binding.d.ts +6 -0
- package/dist/lib/elur/template/node-binding.js +95 -0
- package/dist/lib/elur/template/node-binding.js.map +1 -0
- package/dist/lib/elur/template/portal.cjs +2 -0
- package/dist/lib/elur/template/portal.cjs.map +1 -0
- package/dist/lib/elur/template/portal.d.cts +19 -0
- package/dist/lib/elur/template/portal.d.ts +19 -0
- package/dist/lib/elur/template/portal.js +49 -0
- package/dist/lib/elur/template/portal.js.map +1 -0
- package/dist/lib/elur/template/raw.cjs +2 -0
- package/dist/lib/elur/template/raw.cjs.map +1 -0
- package/dist/lib/elur/template/raw.d.cts +9 -0
- package/dist/lib/elur/template/raw.d.ts +9 -0
- package/dist/lib/elur/template/raw.js +29 -0
- package/dist/lib/elur/template/raw.js.map +1 -0
- package/dist/lib/elur/template/sanitize.cjs +2 -0
- package/dist/lib/elur/template/sanitize.cjs.map +1 -0
- package/dist/lib/elur/template/sanitize.d.cts +16 -0
- package/dist/lib/elur/template/sanitize.d.ts +16 -0
- package/dist/lib/elur/template/sanitize.js +29 -0
- package/dist/lib/elur/template/sanitize.js.map +1 -0
- package/dist/lib/elur/template/transitions.cjs +2 -0
- package/dist/lib/elur/template/transitions.cjs.map +1 -0
- package/dist/lib/elur/template/transitions.d.cts +43 -0
- package/dist/lib/elur/template/transitions.d.ts +43 -0
- package/dist/lib/elur/template/transitions.js +96 -0
- package/dist/lib/elur/template/transitions.js.map +1 -0
- package/dist/lib/elur/template/types.cjs +2 -0
- package/dist/lib/elur/template/types.cjs.map +1 -0
- package/dist/lib/elur/template/types.d.cts +114 -0
- package/dist/lib/elur/template/types.d.ts +114 -0
- package/dist/lib/elur/template/types.js +23 -0
- package/dist/lib/elur/template/types.js.map +1 -0
- package/dist/lib/elur.cjs +1 -0
- package/dist/lib/elur.js +18 -0
- package/dist/lib/form.cjs +2 -0
- package/dist/lib/form.js +315 -0
- package/dist/lib/hydrate.cjs +2 -0
- package/dist/lib/hydrate.js +410 -0
- package/dist/lib/index.cjs +1 -0
- package/dist/lib/index.d.cts +2 -0
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +18 -0
- package/dist/lib/lifecycle.cjs +2 -0
- package/dist/lib/lifecycle.js +39 -0
- package/dist/lib/plugins.cjs +2 -0
- package/dist/lib/plugins.js +59 -0
- package/dist/lib/router.cjs +12 -0
- package/dist/lib/router.js +513 -0
- package/dist/lib/server.cjs +2 -0
- package/dist/lib/server.js +307 -0
- package/dist/lib/signals.cjs +2 -0
- package/dist/lib/signals.js +188 -0
- package/dist/lib/store.cjs +2 -0
- package/dist/lib/store.js +143 -0
- package/dist/lib/template.cjs +1 -0
- package/dist/lib/template.js +13 -0
- package/package.json +216 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { _debugComponentMountEnd as e, _debugComponentMountStart as t, _debugComponentUnmount as n } from "../../lifecycle.js";
|
|
2
|
+
import { _popComponentContext as r, _pushComponentContext as i, _withComponentContext as a } from "../../context.js";
|
|
3
|
+
//#region src/elur/template/mount-helpers.ts
|
|
4
|
+
function o(a, o, s) {
|
|
5
|
+
t(a), i();
|
|
6
|
+
let c;
|
|
7
|
+
try {
|
|
8
|
+
try {
|
|
9
|
+
a.onInit?.();
|
|
10
|
+
} catch (e) {
|
|
11
|
+
if (a.onError) a.onError(e);
|
|
12
|
+
else throw e;
|
|
13
|
+
}
|
|
14
|
+
c = a.render()._render(o, s);
|
|
15
|
+
} finally {
|
|
16
|
+
e(a), r();
|
|
17
|
+
}
|
|
18
|
+
let l;
|
|
19
|
+
try {
|
|
20
|
+
let e = a.onMount?.();
|
|
21
|
+
typeof e == "function" && (l = e);
|
|
22
|
+
} catch (e) {
|
|
23
|
+
if (a.onError) a.onError(e);
|
|
24
|
+
else throw e;
|
|
25
|
+
}
|
|
26
|
+
return () => {
|
|
27
|
+
try {
|
|
28
|
+
a.onUnmount?.();
|
|
29
|
+
} catch {}
|
|
30
|
+
try {
|
|
31
|
+
l?.();
|
|
32
|
+
} catch {}
|
|
33
|
+
c(), n(a);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function s(a, o, s) {
|
|
37
|
+
t(a), i();
|
|
38
|
+
let c;
|
|
39
|
+
try {
|
|
40
|
+
try {
|
|
41
|
+
a.onInit?.();
|
|
42
|
+
} catch {}
|
|
43
|
+
c = a.render()._render(o, s);
|
|
44
|
+
} finally {
|
|
45
|
+
e(a), r();
|
|
46
|
+
}
|
|
47
|
+
let l;
|
|
48
|
+
try {
|
|
49
|
+
let e = a.onMount?.();
|
|
50
|
+
typeof e == "function" && (l = e);
|
|
51
|
+
} catch {}
|
|
52
|
+
return () => {
|
|
53
|
+
try {
|
|
54
|
+
a.onUnmount?.();
|
|
55
|
+
} catch {}
|
|
56
|
+
try {
|
|
57
|
+
l?.();
|
|
58
|
+
} catch {}
|
|
59
|
+
c(), n(a);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function c(r, i, o, s) {
|
|
63
|
+
t(r);
|
|
64
|
+
let c;
|
|
65
|
+
try {
|
|
66
|
+
a(s, () => {
|
|
67
|
+
try {
|
|
68
|
+
r.onInit?.();
|
|
69
|
+
} catch (e) {
|
|
70
|
+
if (r.onError) r.onError(e);
|
|
71
|
+
else throw e;
|
|
72
|
+
}
|
|
73
|
+
c = r.render()._render(i, o);
|
|
74
|
+
});
|
|
75
|
+
} finally {
|
|
76
|
+
e(r);
|
|
77
|
+
}
|
|
78
|
+
let l;
|
|
79
|
+
try {
|
|
80
|
+
let e = r.onMount?.();
|
|
81
|
+
typeof e == "function" && (l = e);
|
|
82
|
+
} catch (e) {
|
|
83
|
+
if (r.onError) r.onError(e);
|
|
84
|
+
else throw e;
|
|
85
|
+
}
|
|
86
|
+
return () => {
|
|
87
|
+
try {
|
|
88
|
+
r.onUnmount?.();
|
|
89
|
+
} catch {}
|
|
90
|
+
try {
|
|
91
|
+
l?.();
|
|
92
|
+
} catch {}
|
|
93
|
+
c(), n(r);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function l(a, o, s, c, l) {
|
|
97
|
+
t(a), i();
|
|
98
|
+
let u;
|
|
99
|
+
try {
|
|
100
|
+
try {
|
|
101
|
+
a.onInit?.();
|
|
102
|
+
} catch (e) {
|
|
103
|
+
if (a.onError) a.onError(e);
|
|
104
|
+
else throw e;
|
|
105
|
+
}
|
|
106
|
+
u = a.render()._render(o, s);
|
|
107
|
+
} finally {
|
|
108
|
+
e(a), r();
|
|
109
|
+
}
|
|
110
|
+
let d;
|
|
111
|
+
c.push(() => {
|
|
112
|
+
try {
|
|
113
|
+
let e = a.onMount?.();
|
|
114
|
+
typeof e == "function" && (d = e);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
if (a.onError) a.onError(e);
|
|
117
|
+
else throw e;
|
|
118
|
+
}
|
|
119
|
+
}), l.push(() => {
|
|
120
|
+
try {
|
|
121
|
+
a.onUnmount?.();
|
|
122
|
+
} catch {}
|
|
123
|
+
try {
|
|
124
|
+
d?.();
|
|
125
|
+
} catch {}
|
|
126
|
+
u(), n(a);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { o as _mountComponent, l as _mountComponentDeferred, s as _mountComponentSilent, c as _mountComponentWithCtx };
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=mount-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount-helpers.js","names":[],"sources":["../../../../src/elur/template/mount-helpers.ts"],"sourcesContent":["import {\n _debugComponentMountStart,\n _debugComponentMountEnd,\n _debugComponentUnmount,\n type ElurComponent,\n} from \"../lifecycle.js\";\nimport {\n _captureContextSnapshot,\n _pushComponentContext,\n _popComponentContext,\n _withComponentContext,\n} from \"../context.js\";\n\n// =============================================================================\n// --- Component mounting helpers ---\n// =============================================================================\n\n/**\n * Renders a ElurComponent into the DOM and calls onMount immediately.\n * Propagates errors through onError (or re-throws if not present).\n * Returns a full cleanup function (onUnmount + mountCleanup + renderCleanup).\n */\nexport function _mountComponent(\n inst: ElurComponent,\n parent: Node,\n before: Node | null,\n): () => void {\n _debugComponentMountStart(inst);\n _pushComponentContext();\n let renderCleanup!: () => void;\n try {\n try { inst.onInit?.(); } catch (e) { if (inst.onError) inst.onError(e); else throw e; }\n renderCleanup = inst.render()._render(parent, before);\n } finally {\n _debugComponentMountEnd(inst);\n _popComponentContext();\n }\n let mountCleanup: (() => void) | undefined;\n try {\n const ret = inst.onMount?.();\n if (typeof ret === \"function\") mountCleanup = ret;\n } catch (e) {\n if (inst.onError) inst.onError(e); else throw e;\n }\n return () => {\n try { inst.onUnmount?.(); } catch { /* ignore */ }\n try { mountCleanup?.(); } catch { /* ignore */ }\n renderCleanup();\n _debugComponentUnmount(inst);\n };\n}\n\n/**\n * Same as `_mountComponent` but silently swallows all lifecycle errors.\n * Used for transition content and error boundary fallbacks where errors\n * inside the fallback/transition itself must not propagate.\n */\nexport function _mountComponentSilent(\n inst: ElurComponent,\n parent: Node,\n before: Node | null,\n): () => void {\n _debugComponentMountStart(inst);\n _pushComponentContext();\n let renderCleanup!: () => void;\n try {\n try { inst.onInit?.(); } catch { /* ignore */ }\n renderCleanup = inst.render()._render(parent, before);\n } finally {\n _debugComponentMountEnd(inst);\n _popComponentContext();\n }\n let mountRet: (() => void) | undefined;\n try {\n const ret = inst.onMount?.();\n if (typeof ret === \"function\") mountRet = ret;\n } catch { /* ignore */ }\n return () => {\n try { inst.onUnmount?.(); } catch { /* ignore */ }\n try { mountRet?.(); } catch { /* ignore */ }\n renderCleanup();\n _debugComponentUnmount(inst);\n };\n}\n\n/**\n * Renders a ElurComponent using a captured context snapshot.\n * Used for dynamic/keyed rendering inside reactive effects, where the\n * provide/inject context must be inherited from the point of declaration.\n * Calls onMount immediately. Returns a full cleanup function.\n */\nexport function _mountComponentWithCtx(\n inst: ElurComponent,\n parent: Node,\n before: Node | null,\n ctxSnapshot: ReturnType<typeof _captureContextSnapshot>,\n): () => void {\n _debugComponentMountStart(inst);\n let renderCleanup!: () => void;\n try {\n _withComponentContext(ctxSnapshot, () => {\n try { inst.onInit?.(); } catch (e) { if (inst.onError) inst.onError(e); else throw e; }\n renderCleanup = inst.render()._render(parent, before);\n });\n } finally {\n _debugComponentMountEnd(inst);\n }\n let mountCleanup: (() => void) | undefined;\n try {\n const ret = inst.onMount?.();\n if (typeof ret === \"function\") mountCleanup = ret;\n } catch (e) {\n if (inst.onError) inst.onError(e); else throw e;\n }\n return () => {\n try { inst.onUnmount?.(); } catch { /* ignore */ }\n try { mountCleanup?.(); } catch { /* ignore */ }\n renderCleanup();\n _debugComponentUnmount(inst);\n };\n}\n\n/**\n * Renders a ElurComponent with *deferred* onMount — used inside `html` template\n * fragments where the DOM nodes are still in a DocumentFragment and onMount must\n * fire only after the fragment is inserted into the live document.\n *\n * Pushes the full cleanup into `disposes` and the onMount call into `postMountHooks`.\n */\nexport function _mountComponentDeferred(\n inst: ElurComponent,\n parent: Node,\n before: Node | null,\n postMountHooks: Array<() => void>,\n disposes: Array<() => void>,\n): void {\n _debugComponentMountStart(inst);\n _pushComponentContext();\n let renderCleanup!: () => void;\n try {\n try { inst.onInit?.(); } catch (e) { if (inst.onError) inst.onError(e); else throw e; }\n renderCleanup = inst.render()._render(parent, before);\n } finally {\n _debugComponentMountEnd(inst);\n _popComponentContext();\n }\n let mountCleanup: (() => void) | undefined;\n postMountHooks.push(() => {\n try {\n const ret = inst.onMount?.();\n if (typeof ret === \"function\") mountCleanup = ret;\n } catch (e) {\n if (inst.onError) inst.onError(e); else throw e;\n }\n });\n disposes.push(() => {\n try { inst.onUnmount?.(); } catch { /* ignore */ }\n try { mountCleanup?.(); } catch { /* ignore */ }\n renderCleanup();\n _debugComponentUnmount(inst);\n });\n}\n"],"mappings":";;;AAsBA,SAAgB,EACZ,GACA,GACA,GACU;AAEV,CADA,EAA0B,EAAK,EAC/B,GAAuB;CACvB,IAAI;AACJ,KAAI;AACA,MAAI;AAAE,KAAK,UAAU;WAAW,GAAG;AAAE,OAAI,EAAK,QAAS,GAAK,QAAQ,EAAE;OAAO,OAAM;;AACnF,MAAgB,EAAK,QAAQ,CAAC,QAAQ,GAAQ,EAAO;WAC/C;AAEN,EADA,EAAwB,EAAK,EAC7B,GAAsB;;CAE1B,IAAI;AACJ,KAAI;EACA,IAAM,IAAM,EAAK,WAAW;AAC5B,EAAI,OAAO,KAAQ,eAAY,IAAe;UACzC,GAAG;AACR,MAAI,EAAK,QAAS,GAAK,QAAQ,EAAE;MAAO,OAAM;;AAElD,cAAa;AACT,MAAI;AAAE,KAAK,aAAa;UAAU;AAClC,MAAI;AAAE,QAAgB;UAAU;AAEhC,EADA,GAAe,EACf,EAAuB,EAAK;;;AASpC,SAAgB,EACZ,GACA,GACA,GACU;AAEV,CADA,EAA0B,EAAK,EAC/B,GAAuB;CACvB,IAAI;AACJ,KAAI;AACA,MAAI;AAAE,KAAK,UAAU;UAAU;AAC/B,MAAgB,EAAK,QAAQ,CAAC,QAAQ,GAAQ,EAAO;WAC/C;AAEN,EADA,EAAwB,EAAK,EAC7B,GAAsB;;CAE1B,IAAI;AACJ,KAAI;EACA,IAAM,IAAM,EAAK,WAAW;AAC5B,EAAI,OAAO,KAAQ,eAAY,IAAW;SACtC;AACR,cAAa;AACT,MAAI;AAAE,KAAK,aAAa;UAAU;AAClC,MAAI;AAAE,QAAY;UAAU;AAE5B,EADA,GAAe,EACf,EAAuB,EAAK;;;AAUpC,SAAgB,EACZ,GACA,GACA,GACA,GACU;AACV,GAA0B,EAAK;CAC/B,IAAI;AACJ,KAAI;AACA,IAAsB,SAAmB;AACrC,OAAI;AAAE,MAAK,UAAU;YAAW,GAAG;AAAE,QAAI,EAAK,QAAS,GAAK,QAAQ,EAAE;QAAO,OAAM;;AACnF,OAAgB,EAAK,QAAQ,CAAC,QAAQ,GAAQ,EAAO;IACvD;WACI;AACN,IAAwB,EAAK;;CAEjC,IAAI;AACJ,KAAI;EACA,IAAM,IAAM,EAAK,WAAW;AAC5B,EAAI,OAAO,KAAQ,eAAY,IAAe;UACzC,GAAG;AACR,MAAI,EAAK,QAAS,GAAK,QAAQ,EAAE;MAAO,OAAM;;AAElD,cAAa;AACT,MAAI;AAAE,KAAK,aAAa;UAAU;AAClC,MAAI;AAAE,QAAgB;UAAU;AAEhC,EADA,GAAe,EACf,EAAuB,EAAK;;;AAWpC,SAAgB,EACZ,GACA,GACA,GACA,GACA,GACI;AAEJ,CADA,EAA0B,EAAK,EAC/B,GAAuB;CACvB,IAAI;AACJ,KAAI;AACA,MAAI;AAAE,KAAK,UAAU;WAAW,GAAG;AAAE,OAAI,EAAK,QAAS,GAAK,QAAQ,EAAE;OAAO,OAAM;;AACnF,MAAgB,EAAK,QAAQ,CAAC,QAAQ,GAAQ,EAAO;WAC/C;AAEN,EADA,EAAwB,EAAK,EAC7B,GAAsB;;CAE1B,IAAI;AASJ,CARA,EAAe,WAAW;AACtB,MAAI;GACA,IAAM,IAAM,EAAK,WAAW;AAC5B,GAAI,OAAO,KAAQ,eAAY,IAAe;WACzC,GAAG;AACR,OAAI,EAAK,QAAS,GAAK,QAAQ,EAAE;OAAO,OAAM;;GAEpD,EACF,EAAS,WAAW;AAChB,MAAI;AAAE,KAAK,aAAa;UAAU;AAClC,MAAI;AAAE,QAAgB;UAAU;AAEhC,EADA,GAAe,EACf,EAAuB,EAAK;GAC9B"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require(`../../signals.cjs`),t=require(`./types.cjs`),n=require(`../../lifecycle.cjs`),r=require(`../../context.cjs`),i=require(`./mount-helpers.cjs`),a=require(`./keyed-diff.cjs`),o=require(`./dom-write.cjs`);function s(s,c,l,u){if(typeof c!=`function`){if(typeof c==`object`&&c&&c[t.ELUR_RENDER_PROTOCOL]!=null){let e=c[t.ELUR_RENDER_PROTOCOL];if(e.mountDom){let t=e.mountDom({parent:s.parentNode,before:s});t&&l.push(t);return}}if(n.isElurComponent(c))i._mountComponentDeferred(c,s.parentNode,s,u,l);else if(t.isElurTemplate(c))l.push(c._render(s.parentNode,s));else if(t.isKeyedList(c)){let e=r._captureContextSnapshot(),t=new Map,n=document.createTextNode(``);s.parentNode.insertBefore(n,s),a.reconcileKeyedList({zoneStart:n,anchor:s,state:t,prevOrder:[],list:c,mount:a.createKeyedMount(e),ctxSnapshot:e,onDuplicateKey:e=>{console.warn(`[elur] repeat(): duplicate key "${e}". Keys must be unique; the previous entry leaks (orphaned nodes + live effects).`)}}),l.push(()=>{for(let e of t.values())e.cleanup();t.clear()})}else if(Array.isArray(c))for(let e of c)n.isElurComponent(e)?i._mountComponentDeferred(e,s.parentNode,s,u,l):t.isElurTemplate(e)?e._render(s.parentNode,s):e!=null&&e!==!1&&s.parentNode.insertBefore(document.createTextNode(String(e)),s);else c!=null&&c!==!1&&s.parentNode.insertBefore(document.createTextNode(String(c)),s);return}let d=null,f=null,p=null,m=[],h=null,g=r._captureContextSnapshot(),_=a.createKeyedMount(g),v=!1,y=``,b=!0,x=e.effect(()=>{let e=c();if(typeof e==`string`||typeof e==`number`){y=String(e);let t=()=>{v=!1,f&&=(f(),null),d?d.nodeValue=y:(d=document.createTextNode(y),s.parentNode.insertBefore(d,s))};b?(b=!1,t()):v||(v=!0,o.queueDOMWrite(t));return}if(v=!1,b=!1,d&&=(d.parentNode?.removeChild(d),null),f&&=(f(),null),!(e==null||e===!1))if(typeof e==`object`&&e&&e[t.ELUR_RENDER_PROTOCOL]!=null){let n=e[t.ELUR_RENDER_PROTOCOL];n.mountDom?f=n.mountDom({parent:s.parentNode,before:s})??null:(d=document.createTextNode(String(e)),s.parentNode.insertBefore(d,s))}else if(t.isElurTemplate(e))f=e._render(s.parentNode,s);else if(n.isElurComponent(e))f=i._mountComponentWithCtx(e,s.parentNode,s,g);else if(t.isKeyedList(e))p||(p=new Map,h=document.createTextNode(``),s.parentNode.insertBefore(h,s)),a.reconcileKeyedList({zoneStart:h,anchor:s,state:p,prevOrder:m,list:e,mount:_,onDuplicateKey:e=>{console.warn(`[elur] repeat(): duplicate key "${e}". Keys must be unique; the previous entry leaks (orphaned nodes + live effects).`)}});else if(Array.isArray(e)){let r=[];for(let a of e)if(n.isElurComponent(a))r.push(i._mountComponent(a,s.parentNode,s));else if(t.isElurTemplate(a))r.push(a._render(s.parentNode,s));else if(a!=null&&a!==!1){let e=document.createTextNode(String(a));s.parentNode.insertBefore(e,s),r.push(()=>e.parentNode?.removeChild(e))}f=()=>r.forEach(e=>e())}else d=document.createTextNode(String(e)),s.parentNode.insertBefore(d,s)});l.push(()=>{if(x(),f&&=(f(),null),d&&=(d.parentNode?.removeChild(d),null),p){for(let e of p.values())e.cleanup();p=null,h=null}})}exports.activateNodeBinding=s;
|
|
2
|
+
//# sourceMappingURL=node-binding.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-binding.cjs","names":[],"sources":["../../../../src/elur/template/node-binding.ts"],"sourcesContent":["import { effect } from \"../reactivity.js\";\nimport { isElurComponent } from \"../lifecycle.js\";\nimport { _captureContextSnapshot } from \"../context.js\";\nimport type { KEntry } from \"./types.js\";\nimport type { RepeatKey } from \"./keyed.js\";\nimport { isElurTemplate, isKeyedList, ELUR_RENDER_PROTOCOL } from \"./types.js\";\nimport {\n _mountComponent,\n _mountComponentWithCtx,\n _mountComponentDeferred,\n} from \"./mount-helpers.js\";\nimport { createKeyedMount, reconcileKeyedList } from \"./keyed-diff.js\";\nimport { queueDOMWrite } from \"./dom-write.js\";\n\n// =============================================================================\n// --- Reactive node binding ---\n// =============================================================================\n\n/**\n * Activates a reactive node binding at `anchor`.\n * Handles: text, ElurTemplate, ElurComponent, KeyedList, Array, and static values.\n * Pushes dispose functions into `disposes`.\n */\nexport function activateNodeBinding(\n anchor: Text,\n value: unknown,\n disposes: Array<() => void>,\n postMountHooks: Array<() => void>,\n): void {\n if (typeof value !== \"function\") {\n if (value != null && typeof value === \"object\" && (value as Record<PropertyKey, unknown>)[ELUR_RENDER_PROTOCOL] != null) {\n const proto = (value as Record<PropertyKey, unknown>)[ELUR_RENDER_PROTOCOL] as { mountDom?: (ctx: import(\"./types.js\").DomProtocolContext) => (() => void) | void };\n if (proto.mountDom) {\n const cleanup = proto.mountDom({\n parent: anchor.parentNode!,\n before: anchor,\n });\n if (cleanup) disposes.push(cleanup);\n return;\n }\n }\n if (isElurComponent(value)) {\n _mountComponentDeferred(value, anchor.parentNode!, anchor, postMountHooks, disposes);\n } else if (isElurTemplate(value)) {\n disposes.push(value._render(anchor.parentNode!, anchor));\n } else if (isKeyedList(value)) {\n // Static keyed list (`repeat(...)` directly, without a getter):\n // mirror the reactive path so a direct keyed value never falls\n // through to `String(value)`.\n const ctxSnapshot = _captureContextSnapshot();\n const keyedState = new Map<RepeatKey, KEntry>();\n const keyedZoneStart = document.createTextNode(\"\");\n anchor.parentNode!.insertBefore(keyedZoneStart, anchor);\n reconcileKeyedList({\n zoneStart: keyedZoneStart,\n anchor,\n state: keyedState,\n prevOrder: [],\n list: value,\n mount: createKeyedMount(ctxSnapshot),\n ctxSnapshot,\n onDuplicateKey: (key) => {\n console.warn(`[elur] repeat(): duplicate key \"${key}\". Keys must be unique; the previous entry leaks (orphaned nodes + live effects).`);\n },\n });\n disposes.push(() => {\n for (const entry of keyedState.values()) {\n entry.cleanup();\n }\n keyedState.clear();\n });\n } else if (Array.isArray(value)) {\n for (const item of value) {\n if (isElurComponent(item)) {\n _mountComponentDeferred(item, anchor.parentNode!, anchor, postMountHooks, disposes);\n } else if (isElurTemplate(item)) {\n item._render(anchor.parentNode!, anchor);\n } else if (item != null && item !== false) {\n anchor.parentNode!.insertBefore(\n document.createTextNode(String(item)),\n anchor\n );\n }\n }\n } else if (value != null && value !== false) {\n anchor.parentNode!.insertBefore(\n document.createTextNode(String(value)),\n anchor\n );\n }\n return;\n }\n\n // Reactive function path\n let textNode: Text | null = null;\n let innerCleanup: (() => void) | null = null;\n\n type Key = RepeatKey;\n let keyedState: Map<Key, KEntry> | null = null;\n let prevKeyOrder: Key[] = [];\n let keyedZoneStart: Node | null = null;\n\n const ctxSnapshot = _captureContextSnapshot();\n const keyedMount = createKeyedMount(ctxSnapshot);\n\n let _textQueued = false;\n let _pendingText = \"\";\n let _isFirstText = true;\n\n const dispose = effect(() => {\n const v = (value as () => unknown)();\n\n if (typeof v === \"string\" || typeof v === \"number\") {\n _pendingText = String(v);\n\n const update = () => {\n _textQueued = false;\n if (innerCleanup) {\n innerCleanup();\n innerCleanup = null;\n }\n if (!textNode) {\n textNode = document.createTextNode(_pendingText);\n anchor.parentNode!.insertBefore(textNode, anchor);\n } else {\n textNode.nodeValue = _pendingText;\n }\n };\n\n if (_isFirstText) {\n _isFirstText = false;\n update();\n } else if (!_textQueued) {\n _textQueued = true;\n queueDOMWrite(update);\n }\n return;\n }\n\n _textQueued = false;\n _isFirstText = false;\n\n if (textNode) {\n textNode.parentNode?.removeChild(textNode);\n textNode = null;\n }\n if (innerCleanup) {\n innerCleanup();\n innerCleanup = null;\n }\n\n if (v == null || v === false) {\n // Empty\n } else if (v != null && typeof v === \"object\" && (v as Record<PropertyKey, unknown>)[ELUR_RENDER_PROTOCOL] != null) {\n const proto = (v as Record<PropertyKey, unknown>)[ELUR_RENDER_PROTOCOL] as { mountDom?: (ctx: import(\"./types.js\").DomProtocolContext) => (() => void) | void };\n if (proto.mountDom) {\n innerCleanup = proto.mountDom({ parent: anchor.parentNode!, before: anchor }) ?? null;\n } else {\n textNode = document.createTextNode(String(v));\n anchor.parentNode!.insertBefore(textNode, anchor);\n }\n } else if (isElurTemplate(v)) {\n innerCleanup = v._render(anchor.parentNode!, anchor);\n } else if (isElurComponent(v)) {\n innerCleanup = _mountComponentWithCtx(v, anchor.parentNode!, anchor, ctxSnapshot);\n } else if (isKeyedList(v)) {\n\n if (!keyedState) {\n keyedState = new Map();\n keyedZoneStart = document.createTextNode(\"\");\n anchor.parentNode!.insertBefore(keyedZoneStart, anchor);\n }\n\n reconcileKeyedList({\n zoneStart: keyedZoneStart!,\n anchor,\n state: keyedState,\n prevOrder: prevKeyOrder,\n list: v,\n mount: keyedMount,\n onDuplicateKey: (key) => {\n console.warn(`[elur] repeat(): duplicate key \"${key}\". Keys must be unique; the previous entry leaks (orphaned nodes + live effects).`);\n },\n });\n } else if (Array.isArray(v)) {\n const cleanups: Array<() => void> = [];\n for (const item of v) {\n if (isElurComponent(item)) {\n cleanups.push(_mountComponent(item, anchor.parentNode!, anchor));\n } else if (isElurTemplate(item)) {\n cleanups.push(item._render(anchor.parentNode!, anchor));\n } else if (item != null && item !== false) {\n const t = document.createTextNode(String(item));\n anchor.parentNode!.insertBefore(t, anchor);\n cleanups.push(() => t.parentNode?.removeChild(t));\n }\n }\n innerCleanup = () => cleanups.forEach((c) => c());\n } else {\n textNode = document.createTextNode(String(v));\n anchor.parentNode!.insertBefore(textNode, anchor);\n }\n });\n\n disposes.push(() => {\n dispose();\n if (innerCleanup) {\n innerCleanup();\n innerCleanup = null;\n }\n if (textNode) {\n textNode.parentNode?.removeChild(textNode);\n textNode = null;\n }\n if (keyedState) {\n for (const entry of keyedState.values()) {\n entry.cleanup();\n }\n keyedState = null;\n keyedZoneStart = null;\n }\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activates a reactive node binding at `anchor`.
|
|
3
|
+
* Handles: text, ElurTemplate, ElurComponent, KeyedList, Array, and static values.
|
|
4
|
+
* Pushes dispose functions into `disposes`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function activateNodeBinding(anchor: Text, value: unknown, disposes: Array<() => void>, postMountHooks: Array<() => void>): void;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activates a reactive node binding at `anchor`.
|
|
3
|
+
* Handles: text, ElurTemplate, ElurComponent, KeyedList, Array, and static values.
|
|
4
|
+
* Pushes dispose functions into `disposes`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function activateNodeBinding(anchor: Text, value: unknown, disposes: Array<() => void>, postMountHooks: Array<() => void>): void;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { effect as e } from "../../signals.js";
|
|
2
|
+
import { ELUR_RENDER_PROTOCOL as t, isElurTemplate as n, isKeyedList as r } from "./types.js";
|
|
3
|
+
import { isElurComponent as i } from "../../lifecycle.js";
|
|
4
|
+
import { _captureContextSnapshot as a } from "../../context.js";
|
|
5
|
+
import { _mountComponent as o, _mountComponentDeferred as s, _mountComponentWithCtx as c } from "./mount-helpers.js";
|
|
6
|
+
import { createKeyedMount as l, reconcileKeyedList as u } from "./keyed-diff.js";
|
|
7
|
+
import { queueDOMWrite as d } from "./dom-write.js";
|
|
8
|
+
//#region src/elur/template/node-binding.ts
|
|
9
|
+
function f(f, p, m, h) {
|
|
10
|
+
if (typeof p != "function") {
|
|
11
|
+
if (typeof p == "object" && p && p[t] != null) {
|
|
12
|
+
let e = p[t];
|
|
13
|
+
if (e.mountDom) {
|
|
14
|
+
let t = e.mountDom({
|
|
15
|
+
parent: f.parentNode,
|
|
16
|
+
before: f
|
|
17
|
+
});
|
|
18
|
+
t && m.push(t);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (i(p)) s(p, f.parentNode, f, h, m);
|
|
23
|
+
else if (n(p)) m.push(p._render(f.parentNode, f));
|
|
24
|
+
else if (r(p)) {
|
|
25
|
+
let e = a(), t = /* @__PURE__ */ new Map(), n = document.createTextNode("");
|
|
26
|
+
f.parentNode.insertBefore(n, f), u({
|
|
27
|
+
zoneStart: n,
|
|
28
|
+
anchor: f,
|
|
29
|
+
state: t,
|
|
30
|
+
prevOrder: [],
|
|
31
|
+
list: p,
|
|
32
|
+
mount: l(e),
|
|
33
|
+
ctxSnapshot: e,
|
|
34
|
+
onDuplicateKey: (e) => {
|
|
35
|
+
console.warn(`[elur] repeat(): duplicate key "${e}". Keys must be unique; the previous entry leaks (orphaned nodes + live effects).`);
|
|
36
|
+
}
|
|
37
|
+
}), m.push(() => {
|
|
38
|
+
for (let e of t.values()) e.cleanup();
|
|
39
|
+
t.clear();
|
|
40
|
+
});
|
|
41
|
+
} else if (Array.isArray(p)) for (let e of p) i(e) ? s(e, f.parentNode, f, h, m) : n(e) ? e._render(f.parentNode, f) : e != null && e !== !1 && f.parentNode.insertBefore(document.createTextNode(String(e)), f);
|
|
42
|
+
else p != null && p !== !1 && f.parentNode.insertBefore(document.createTextNode(String(p)), f);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
let g = null, _ = null, v = null, y = [], b = null, x = a(), S = l(x), C = !1, w = "", T = !0, E = e(() => {
|
|
46
|
+
let e = p();
|
|
47
|
+
if (typeof e == "string" || typeof e == "number") {
|
|
48
|
+
w = String(e);
|
|
49
|
+
let t = () => {
|
|
50
|
+
C = !1, _ &&= (_(), null), g ? g.nodeValue = w : (g = document.createTextNode(w), f.parentNode.insertBefore(g, f));
|
|
51
|
+
};
|
|
52
|
+
T ? (T = !1, t()) : C || (C = !0, d(t));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (C = !1, T = !1, g &&= (g.parentNode?.removeChild(g), null), _ &&= (_(), null), !(e == null || e === !1)) if (typeof e == "object" && e && e[t] != null) {
|
|
56
|
+
let n = e[t];
|
|
57
|
+
n.mountDom ? _ = n.mountDom({
|
|
58
|
+
parent: f.parentNode,
|
|
59
|
+
before: f
|
|
60
|
+
}) ?? null : (g = document.createTextNode(String(e)), f.parentNode.insertBefore(g, f));
|
|
61
|
+
} else if (n(e)) _ = e._render(f.parentNode, f);
|
|
62
|
+
else if (i(e)) _ = c(e, f.parentNode, f, x);
|
|
63
|
+
else if (r(e)) v || (v = /* @__PURE__ */ new Map(), b = document.createTextNode(""), f.parentNode.insertBefore(b, f)), u({
|
|
64
|
+
zoneStart: b,
|
|
65
|
+
anchor: f,
|
|
66
|
+
state: v,
|
|
67
|
+
prevOrder: y,
|
|
68
|
+
list: e,
|
|
69
|
+
mount: S,
|
|
70
|
+
onDuplicateKey: (e) => {
|
|
71
|
+
console.warn(`[elur] repeat(): duplicate key "${e}". Keys must be unique; the previous entry leaks (orphaned nodes + live effects).`);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
else if (Array.isArray(e)) {
|
|
75
|
+
let t = [];
|
|
76
|
+
for (let r of e) if (i(r)) t.push(o(r, f.parentNode, f));
|
|
77
|
+
else if (n(r)) t.push(r._render(f.parentNode, f));
|
|
78
|
+
else if (r != null && r !== !1) {
|
|
79
|
+
let e = document.createTextNode(String(r));
|
|
80
|
+
f.parentNode.insertBefore(e, f), t.push(() => e.parentNode?.removeChild(e));
|
|
81
|
+
}
|
|
82
|
+
_ = () => t.forEach((e) => e());
|
|
83
|
+
} else g = document.createTextNode(String(e)), f.parentNode.insertBefore(g, f);
|
|
84
|
+
});
|
|
85
|
+
m.push(() => {
|
|
86
|
+
if (E(), _ &&= (_(), null), g &&= (g.parentNode?.removeChild(g), null), v) {
|
|
87
|
+
for (let e of v.values()) e.cleanup();
|
|
88
|
+
v = null, b = null;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { f as activateNodeBinding };
|
|
94
|
+
|
|
95
|
+
//# sourceMappingURL=node-binding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-binding.js","names":[],"sources":["../../../../src/elur/template/node-binding.ts"],"sourcesContent":["import { effect } from \"../reactivity.js\";\nimport { isElurComponent } from \"../lifecycle.js\";\nimport { _captureContextSnapshot } from \"../context.js\";\nimport type { KEntry } from \"./types.js\";\nimport type { RepeatKey } from \"./keyed.js\";\nimport { isElurTemplate, isKeyedList, ELUR_RENDER_PROTOCOL } from \"./types.js\";\nimport {\n _mountComponent,\n _mountComponentWithCtx,\n _mountComponentDeferred,\n} from \"./mount-helpers.js\";\nimport { createKeyedMount, reconcileKeyedList } from \"./keyed-diff.js\";\nimport { queueDOMWrite } from \"./dom-write.js\";\n\n// =============================================================================\n// --- Reactive node binding ---\n// =============================================================================\n\n/**\n * Activates a reactive node binding at `anchor`.\n * Handles: text, ElurTemplate, ElurComponent, KeyedList, Array, and static values.\n * Pushes dispose functions into `disposes`.\n */\nexport function activateNodeBinding(\n anchor: Text,\n value: unknown,\n disposes: Array<() => void>,\n postMountHooks: Array<() => void>,\n): void {\n if (typeof value !== \"function\") {\n if (value != null && typeof value === \"object\" && (value as Record<PropertyKey, unknown>)[ELUR_RENDER_PROTOCOL] != null) {\n const proto = (value as Record<PropertyKey, unknown>)[ELUR_RENDER_PROTOCOL] as { mountDom?: (ctx: import(\"./types.js\").DomProtocolContext) => (() => void) | void };\n if (proto.mountDom) {\n const cleanup = proto.mountDom({\n parent: anchor.parentNode!,\n before: anchor,\n });\n if (cleanup) disposes.push(cleanup);\n return;\n }\n }\n if (isElurComponent(value)) {\n _mountComponentDeferred(value, anchor.parentNode!, anchor, postMountHooks, disposes);\n } else if (isElurTemplate(value)) {\n disposes.push(value._render(anchor.parentNode!, anchor));\n } else if (isKeyedList(value)) {\n // Static keyed list (`repeat(...)` directly, without a getter):\n // mirror the reactive path so a direct keyed value never falls\n // through to `String(value)`.\n const ctxSnapshot = _captureContextSnapshot();\n const keyedState = new Map<RepeatKey, KEntry>();\n const keyedZoneStart = document.createTextNode(\"\");\n anchor.parentNode!.insertBefore(keyedZoneStart, anchor);\n reconcileKeyedList({\n zoneStart: keyedZoneStart,\n anchor,\n state: keyedState,\n prevOrder: [],\n list: value,\n mount: createKeyedMount(ctxSnapshot),\n ctxSnapshot,\n onDuplicateKey: (key) => {\n console.warn(`[elur] repeat(): duplicate key \"${key}\". Keys must be unique; the previous entry leaks (orphaned nodes + live effects).`);\n },\n });\n disposes.push(() => {\n for (const entry of keyedState.values()) {\n entry.cleanup();\n }\n keyedState.clear();\n });\n } else if (Array.isArray(value)) {\n for (const item of value) {\n if (isElurComponent(item)) {\n _mountComponentDeferred(item, anchor.parentNode!, anchor, postMountHooks, disposes);\n } else if (isElurTemplate(item)) {\n item._render(anchor.parentNode!, anchor);\n } else if (item != null && item !== false) {\n anchor.parentNode!.insertBefore(\n document.createTextNode(String(item)),\n anchor\n );\n }\n }\n } else if (value != null && value !== false) {\n anchor.parentNode!.insertBefore(\n document.createTextNode(String(value)),\n anchor\n );\n }\n return;\n }\n\n // Reactive function path\n let textNode: Text | null = null;\n let innerCleanup: (() => void) | null = null;\n\n type Key = RepeatKey;\n let keyedState: Map<Key, KEntry> | null = null;\n let prevKeyOrder: Key[] = [];\n let keyedZoneStart: Node | null = null;\n\n const ctxSnapshot = _captureContextSnapshot();\n const keyedMount = createKeyedMount(ctxSnapshot);\n\n let _textQueued = false;\n let _pendingText = \"\";\n let _isFirstText = true;\n\n const dispose = effect(() => {\n const v = (value as () => unknown)();\n\n if (typeof v === \"string\" || typeof v === \"number\") {\n _pendingText = String(v);\n\n const update = () => {\n _textQueued = false;\n if (innerCleanup) {\n innerCleanup();\n innerCleanup = null;\n }\n if (!textNode) {\n textNode = document.createTextNode(_pendingText);\n anchor.parentNode!.insertBefore(textNode, anchor);\n } else {\n textNode.nodeValue = _pendingText;\n }\n };\n\n if (_isFirstText) {\n _isFirstText = false;\n update();\n } else if (!_textQueued) {\n _textQueued = true;\n queueDOMWrite(update);\n }\n return;\n }\n\n _textQueued = false;\n _isFirstText = false;\n\n if (textNode) {\n textNode.parentNode?.removeChild(textNode);\n textNode = null;\n }\n if (innerCleanup) {\n innerCleanup();\n innerCleanup = null;\n }\n\n if (v == null || v === false) {\n // Empty\n } else if (v != null && typeof v === \"object\" && (v as Record<PropertyKey, unknown>)[ELUR_RENDER_PROTOCOL] != null) {\n const proto = (v as Record<PropertyKey, unknown>)[ELUR_RENDER_PROTOCOL] as { mountDom?: (ctx: import(\"./types.js\").DomProtocolContext) => (() => void) | void };\n if (proto.mountDom) {\n innerCleanup = proto.mountDom({ parent: anchor.parentNode!, before: anchor }) ?? null;\n } else {\n textNode = document.createTextNode(String(v));\n anchor.parentNode!.insertBefore(textNode, anchor);\n }\n } else if (isElurTemplate(v)) {\n innerCleanup = v._render(anchor.parentNode!, anchor);\n } else if (isElurComponent(v)) {\n innerCleanup = _mountComponentWithCtx(v, anchor.parentNode!, anchor, ctxSnapshot);\n } else if (isKeyedList(v)) {\n\n if (!keyedState) {\n keyedState = new Map();\n keyedZoneStart = document.createTextNode(\"\");\n anchor.parentNode!.insertBefore(keyedZoneStart, anchor);\n }\n\n reconcileKeyedList({\n zoneStart: keyedZoneStart!,\n anchor,\n state: keyedState,\n prevOrder: prevKeyOrder,\n list: v,\n mount: keyedMount,\n onDuplicateKey: (key) => {\n console.warn(`[elur] repeat(): duplicate key \"${key}\". Keys must be unique; the previous entry leaks (orphaned nodes + live effects).`);\n },\n });\n } else if (Array.isArray(v)) {\n const cleanups: Array<() => void> = [];\n for (const item of v) {\n if (isElurComponent(item)) {\n cleanups.push(_mountComponent(item, anchor.parentNode!, anchor));\n } else if (isElurTemplate(item)) {\n cleanups.push(item._render(anchor.parentNode!, anchor));\n } else if (item != null && item !== false) {\n const t = document.createTextNode(String(item));\n anchor.parentNode!.insertBefore(t, anchor);\n cleanups.push(() => t.parentNode?.removeChild(t));\n }\n }\n innerCleanup = () => cleanups.forEach((c) => c());\n } else {\n textNode = document.createTextNode(String(v));\n anchor.parentNode!.insertBefore(textNode, anchor);\n }\n });\n\n disposes.push(() => {\n dispose();\n if (innerCleanup) {\n innerCleanup();\n innerCleanup = null;\n }\n if (textNode) {\n textNode.parentNode?.removeChild(textNode);\n textNode = null;\n }\n if (keyedState) {\n for (const entry of keyedState.values()) {\n entry.cleanup();\n }\n keyedState = null;\n keyedZoneStart = null;\n }\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require(`../../lifecycle.cjs`),t=require(`../../context.cjs`),n=require(`./mount-helpers.cjs`);function r(){return{__isPortalOutlet:!0,_container:null}}function i(e){return{__isElurTemplate:!0,mount(e){let t=typeof e==`string`?document.querySelector(e)??document.body:e;return{unmount:this._render(t,null)}},_render(t,n){let r=document.createElement(`div`);return r.setAttribute(`data-elur-outlet`,``),e._container=r,t.insertBefore(r,n),()=>{e._container=null,r.remove()}}}}function a(t,r=document.body){return{__isElurTemplate:!0,mount(e){let t=typeof e==`string`?document.querySelector(e)??document.body:e;return{unmount:this._render(t,null)}},_render(i,a){let o;return o=typeof r==`string`?document.querySelector(r)??document.body:r instanceof Element?r:`__isPortalOutlet`in r?r._container??document.body:r.el??document.body,e.isElurComponent(t)?n._mountComponent(t,o,null):t._render(o,null)}}}var o=t.createInjectionKey(`elur:portal-outlet`);function s(e){t.provide(o,e)}function c(){return t.inject(o)}exports.createPortalOutlet=r,exports.injectOutlet=c,exports.portal=a,exports.portalOutlet=i,exports.provideOutlet=s;
|
|
2
|
+
//# sourceMappingURL=portal.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal.cjs","names":[],"sources":["../../../../src/elur/template/portal.ts"],"sourcesContent":["import { isElurComponent } from \"../lifecycle.js\";\nimport type { ElurComponent } from \"../lifecycle.js\";\nimport { provide, inject, createInjectionKey } from \"../context.js\";\nimport type { ElurTemplate, ElurMountHandle, ElurRef, PortalOutlet } from \"./types.js\";\nimport { _mountComponent } from \"./mount-helpers.js\";\n\n// =============================================================================\n// --- PortalOutlet ---\n// =============================================================================\n\n/** Creates a PortalOutlet token for decoupled portal targeting. */\nexport function createPortalOutlet(): PortalOutlet {\n return { __isPortalOutlet: true as const, _container: null };\n}\n\n/** Declares the DOM anchor for a PortalOutlet inside a template. */\nexport function portalOutlet(outlet: PortalOutlet): ElurTemplate {\n return {\n __isElurTemplate: true as const,\n mount(container: Element | string): ElurMountHandle {\n const el =\n typeof container === \"string\"\n ? (document.querySelector(container) ?? document.body)\n : container;\n const cleanup = this._render(el, null);\n return { unmount: cleanup };\n },\n _render(parent: Node, before: Node | null): () => void {\n const el = document.createElement(\"div\");\n el.setAttribute(\"data-elur-outlet\", \"\");\n outlet._container = el;\n parent.insertBefore(el, before);\n return () => {\n outlet._container = null;\n el.remove();\n };\n },\n };\n}\n\n// =============================================================================\n// --- portal() ---\n// =============================================================================\n\n/**\n * Renders `content` into `target` instead of the current tree position.\n * Useful for modals, tooltips, and overlays that must escape overflow clipping.\n * Returns a ElurTemplate — works inside reactive conditionals.\n *\n * @param content Template or component to render.\n * @param target CSS selector, Element, PortalOutlet, or ElurRef. Defaults to `document.body`.\n */\nexport function portal(\n content: ElurTemplate | ElurComponent,\n target: Element | string | PortalOutlet | ElurRef<Element> = document.body\n): ElurTemplate {\n return {\n __isElurTemplate: true as const,\n\n mount(container: Element | string): ElurMountHandle {\n const el =\n typeof container === \"string\"\n ? (document.querySelector(container) ?? document.body)\n : container;\n const cleanup = this._render(el, null);\n return { unmount: cleanup };\n },\n\n _render(_parent: Node, _before: Node | null): () => void {\n let targetEl: Element;\n if (typeof target === \"string\") {\n targetEl = document.querySelector(target) ?? document.body;\n } else if (target instanceof Element) {\n targetEl = target;\n } else if (\"__isPortalOutlet\" in target) {\n targetEl = (target as PortalOutlet)._container ?? document.body;\n } else {\n targetEl = (target as ElurRef<Element>).el ?? document.body;\n }\n\n if (isElurComponent(content)) {\n return _mountComponent(content, targetEl, null);\n }\n\n return content._render(targetEl, null);\n },\n };\n}\n\n// =============================================================================\n// --- Portal outlet via provide/inject ---\n// =============================================================================\n\nconst _OUTLET_KEY = createInjectionKey<PortalOutlet>(\"elur:portal-outlet\");\n\n/** Provides a PortalOutlet to descendant components via dependency injection. */\nexport function provideOutlet(outlet: PortalOutlet): void {\n provide(_OUTLET_KEY, outlet);\n}\n\n/** Injects the nearest PortalOutlet provided by an ancestor. */\nexport function injectOutlet(): PortalOutlet | undefined {\n return inject(_OUTLET_KEY);\n}\n"],"mappings":"uGAWA,SAAgB,GAAmC,CAC/C,MAAO,CAAE,iBAAkB,GAAe,WAAY,KAAM,CAIhE,SAAgB,EAAa,EAAoC,CAC7D,MAAO,CACH,iBAAkB,GAClB,MAAM,EAA8C,CAChD,IAAM,EACF,OAAO,GAAc,SACd,SAAS,cAAc,EAAU,EAAI,SAAS,KAC/C,EAEV,MAAO,CAAE,QADO,KAAK,QAAQ,EAAI,KAAK,CACX,EAE/B,QAAQ,EAAc,EAAiC,CACnD,IAAM,EAAK,SAAS,cAAc,MAAM,CAIxC,OAHA,EAAG,aAAa,mBAAoB,GAAG,CACvC,EAAO,WAAa,EACpB,EAAO,aAAa,EAAI,EAAO,KAClB,CACT,EAAO,WAAa,KACpB,EAAG,QAAQ,GAGtB,CAeL,SAAgB,EACZ,EACA,EAA6D,SAAS,KAC1D,CACZ,MAAO,CACH,iBAAkB,GAElB,MAAM,EAA8C,CAChD,IAAM,EACF,OAAO,GAAc,SACd,SAAS,cAAc,EAAU,EAAI,SAAS,KAC/C,EAEV,MAAO,CAAE,QADO,KAAK,QAAQ,EAAI,KAAK,CACX,EAG/B,QAAQ,EAAe,EAAkC,CACrD,IAAI,EAeJ,MAdA,CAOI,EAPA,OAAO,GAAW,SACP,SAAS,cAAc,EAAO,EAAI,SAAS,KAC/C,aAAkB,QACd,EACJ,qBAAsB,EACjB,EAAwB,YAAc,SAAS,KAE/C,EAA4B,IAAM,SAAS,KAGvD,EAAA,gBAAgB,EAAQ,CACjB,EAAA,gBAAgB,EAAS,EAAU,KAAK,CAG5C,EAAQ,QAAQ,EAAU,KAAK,EAE7C,CAOL,IAAM,EAAc,EAAA,mBAAiC,qBAAqB,CAG1E,SAAgB,EAAc,EAA4B,CACtD,EAAA,QAAQ,EAAa,EAAO,CAIhC,SAAgB,GAAyC,CACrD,OAAO,EAAA,OAAO,EAAY"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ElurComponent } from "../lifecycle.js";
|
|
2
|
+
import type { ElurTemplate, ElurRef, PortalOutlet } from "./types.js";
|
|
3
|
+
/** Creates a PortalOutlet token for decoupled portal targeting. */
|
|
4
|
+
export declare function createPortalOutlet(): PortalOutlet;
|
|
5
|
+
/** Declares the DOM anchor for a PortalOutlet inside a template. */
|
|
6
|
+
export declare function portalOutlet(outlet: PortalOutlet): ElurTemplate;
|
|
7
|
+
/**
|
|
8
|
+
* Renders `content` into `target` instead of the current tree position.
|
|
9
|
+
* Useful for modals, tooltips, and overlays that must escape overflow clipping.
|
|
10
|
+
* Returns a ElurTemplate — works inside reactive conditionals.
|
|
11
|
+
*
|
|
12
|
+
* @param content Template or component to render.
|
|
13
|
+
* @param target CSS selector, Element, PortalOutlet, or ElurRef. Defaults to `document.body`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function portal(content: ElurTemplate | ElurComponent, target?: Element | string | PortalOutlet | ElurRef<Element>): ElurTemplate;
|
|
16
|
+
/** Provides a PortalOutlet to descendant components via dependency injection. */
|
|
17
|
+
export declare function provideOutlet(outlet: PortalOutlet): void;
|
|
18
|
+
/** Injects the nearest PortalOutlet provided by an ancestor. */
|
|
19
|
+
export declare function injectOutlet(): PortalOutlet | undefined;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ElurComponent } from "../lifecycle.js";
|
|
2
|
+
import type { ElurTemplate, ElurRef, PortalOutlet } from "./types.js";
|
|
3
|
+
/** Creates a PortalOutlet token for decoupled portal targeting. */
|
|
4
|
+
export declare function createPortalOutlet(): PortalOutlet;
|
|
5
|
+
/** Declares the DOM anchor for a PortalOutlet inside a template. */
|
|
6
|
+
export declare function portalOutlet(outlet: PortalOutlet): ElurTemplate;
|
|
7
|
+
/**
|
|
8
|
+
* Renders `content` into `target` instead of the current tree position.
|
|
9
|
+
* Useful for modals, tooltips, and overlays that must escape overflow clipping.
|
|
10
|
+
* Returns a ElurTemplate — works inside reactive conditionals.
|
|
11
|
+
*
|
|
12
|
+
* @param content Template or component to render.
|
|
13
|
+
* @param target CSS selector, Element, PortalOutlet, or ElurRef. Defaults to `document.body`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function portal(content: ElurTemplate | ElurComponent, target?: Element | string | PortalOutlet | ElurRef<Element>): ElurTemplate;
|
|
16
|
+
/** Provides a PortalOutlet to descendant components via dependency injection. */
|
|
17
|
+
export declare function provideOutlet(outlet: PortalOutlet): void;
|
|
18
|
+
/** Injects the nearest PortalOutlet provided by an ancestor. */
|
|
19
|
+
export declare function injectOutlet(): PortalOutlet | undefined;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { isElurComponent as e } from "../../lifecycle.js";
|
|
2
|
+
import { createInjectionKey as t, inject as n, provide as r } from "../../context.js";
|
|
3
|
+
import { _mountComponent as i } from "./mount-helpers.js";
|
|
4
|
+
//#region src/elur/template/portal.ts
|
|
5
|
+
function a() {
|
|
6
|
+
return {
|
|
7
|
+
__isPortalOutlet: !0,
|
|
8
|
+
_container: null
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function o(e) {
|
|
12
|
+
return {
|
|
13
|
+
__isElurTemplate: !0,
|
|
14
|
+
mount(e) {
|
|
15
|
+
let t = typeof e == "string" ? document.querySelector(e) ?? document.body : e;
|
|
16
|
+
return { unmount: this._render(t, null) };
|
|
17
|
+
},
|
|
18
|
+
_render(t, n) {
|
|
19
|
+
let r = document.createElement("div");
|
|
20
|
+
return r.setAttribute("data-elur-outlet", ""), e._container = r, t.insertBefore(r, n), () => {
|
|
21
|
+
e._container = null, r.remove();
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function s(t, n = document.body) {
|
|
27
|
+
return {
|
|
28
|
+
__isElurTemplate: !0,
|
|
29
|
+
mount(e) {
|
|
30
|
+
let t = typeof e == "string" ? document.querySelector(e) ?? document.body : e;
|
|
31
|
+
return { unmount: this._render(t, null) };
|
|
32
|
+
},
|
|
33
|
+
_render(r, a) {
|
|
34
|
+
let o;
|
|
35
|
+
return o = typeof n == "string" ? document.querySelector(n) ?? document.body : n instanceof Element ? n : "__isPortalOutlet" in n ? n._container ?? document.body : n.el ?? document.body, e(t) ? i(t, o, null) : t._render(o, null);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
var c = t("elur:portal-outlet");
|
|
40
|
+
function l(e) {
|
|
41
|
+
r(c, e);
|
|
42
|
+
}
|
|
43
|
+
function u() {
|
|
44
|
+
return n(c);
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { a as createPortalOutlet, u as injectOutlet, s as portal, o as portalOutlet, l as provideOutlet };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=portal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal.js","names":[],"sources":["../../../../src/elur/template/portal.ts"],"sourcesContent":["import { isElurComponent } from \"../lifecycle.js\";\nimport type { ElurComponent } from \"../lifecycle.js\";\nimport { provide, inject, createInjectionKey } from \"../context.js\";\nimport type { ElurTemplate, ElurMountHandle, ElurRef, PortalOutlet } from \"./types.js\";\nimport { _mountComponent } from \"./mount-helpers.js\";\n\n// =============================================================================\n// --- PortalOutlet ---\n// =============================================================================\n\n/** Creates a PortalOutlet token for decoupled portal targeting. */\nexport function createPortalOutlet(): PortalOutlet {\n return { __isPortalOutlet: true as const, _container: null };\n}\n\n/** Declares the DOM anchor for a PortalOutlet inside a template. */\nexport function portalOutlet(outlet: PortalOutlet): ElurTemplate {\n return {\n __isElurTemplate: true as const,\n mount(container: Element | string): ElurMountHandle {\n const el =\n typeof container === \"string\"\n ? (document.querySelector(container) ?? document.body)\n : container;\n const cleanup = this._render(el, null);\n return { unmount: cleanup };\n },\n _render(parent: Node, before: Node | null): () => void {\n const el = document.createElement(\"div\");\n el.setAttribute(\"data-elur-outlet\", \"\");\n outlet._container = el;\n parent.insertBefore(el, before);\n return () => {\n outlet._container = null;\n el.remove();\n };\n },\n };\n}\n\n// =============================================================================\n// --- portal() ---\n// =============================================================================\n\n/**\n * Renders `content` into `target` instead of the current tree position.\n * Useful for modals, tooltips, and overlays that must escape overflow clipping.\n * Returns a ElurTemplate — works inside reactive conditionals.\n *\n * @param content Template or component to render.\n * @param target CSS selector, Element, PortalOutlet, or ElurRef. Defaults to `document.body`.\n */\nexport function portal(\n content: ElurTemplate | ElurComponent,\n target: Element | string | PortalOutlet | ElurRef<Element> = document.body\n): ElurTemplate {\n return {\n __isElurTemplate: true as const,\n\n mount(container: Element | string): ElurMountHandle {\n const el =\n typeof container === \"string\"\n ? (document.querySelector(container) ?? document.body)\n : container;\n const cleanup = this._render(el, null);\n return { unmount: cleanup };\n },\n\n _render(_parent: Node, _before: Node | null): () => void {\n let targetEl: Element;\n if (typeof target === \"string\") {\n targetEl = document.querySelector(target) ?? document.body;\n } else if (target instanceof Element) {\n targetEl = target;\n } else if (\"__isPortalOutlet\" in target) {\n targetEl = (target as PortalOutlet)._container ?? document.body;\n } else {\n targetEl = (target as ElurRef<Element>).el ?? document.body;\n }\n\n if (isElurComponent(content)) {\n return _mountComponent(content, targetEl, null);\n }\n\n return content._render(targetEl, null);\n },\n };\n}\n\n// =============================================================================\n// --- Portal outlet via provide/inject ---\n// =============================================================================\n\nconst _OUTLET_KEY = createInjectionKey<PortalOutlet>(\"elur:portal-outlet\");\n\n/** Provides a PortalOutlet to descendant components via dependency injection. */\nexport function provideOutlet(outlet: PortalOutlet): void {\n provide(_OUTLET_KEY, outlet);\n}\n\n/** Injects the nearest PortalOutlet provided by an ancestor. */\nexport function injectOutlet(): PortalOutlet | undefined {\n return inject(_OUTLET_KEY);\n}\n"],"mappings":";;;;AAWA,SAAgB,IAAmC;AAC/C,QAAO;EAAE,kBAAkB;EAAe,YAAY;EAAM;;AAIhE,SAAgB,EAAa,GAAoC;AAC7D,QAAO;EACH,kBAAkB;EAClB,MAAM,GAA8C;GAChD,IAAM,IACF,OAAO,KAAc,WACd,SAAS,cAAc,EAAU,IAAI,SAAS,OAC/C;AAEV,UAAO,EAAE,SADO,KAAK,QAAQ,GAAI,KAAK,EACX;;EAE/B,QAAQ,GAAc,GAAiC;GACnD,IAAM,IAAK,SAAS,cAAc,MAAM;AAIxC,UAHA,EAAG,aAAa,oBAAoB,GAAG,EACvC,EAAO,aAAa,GACpB,EAAO,aAAa,GAAI,EAAO,QAClB;AAET,IADA,EAAO,aAAa,MACpB,EAAG,QAAQ;;;EAGtB;;AAeL,SAAgB,EACZ,GACA,IAA6D,SAAS,MAC1D;AACZ,QAAO;EACH,kBAAkB;EAElB,MAAM,GAA8C;GAChD,IAAM,IACF,OAAO,KAAc,WACd,SAAS,cAAc,EAAU,IAAI,SAAS,OAC/C;AAEV,UAAO,EAAE,SADO,KAAK,QAAQ,GAAI,KAAK,EACX;;EAG/B,QAAQ,GAAe,GAAkC;GACrD,IAAI;AAeJ,UAdA,AAOI,IAPA,OAAO,KAAW,WACP,SAAS,cAAc,EAAO,IAAI,SAAS,OAC/C,aAAkB,UACd,IACJ,sBAAsB,IACjB,EAAwB,cAAc,SAAS,OAE/C,EAA4B,MAAM,SAAS,MAGvD,EAAgB,EAAQ,GACjB,EAAgB,GAAS,GAAU,KAAK,GAG5C,EAAQ,QAAQ,GAAU,KAAK;;EAE7C;;AAOL,IAAM,IAAc,EAAiC,qBAAqB;AAG1E,SAAgB,EAAc,GAA4B;AACtD,GAAQ,GAAa,EAAO;;AAIhC,SAAgB,IAAyC;AACrD,QAAO,EAAO,EAAY"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require(`./types.cjs`);function t(t){return{[e.ELUR_RENDER_PROTOCOL]:{renderServer(){return t},mountDom({parent:e,before:n}){let r=document.createElement(`template`);r.innerHTML=t;let i=document.createTextNode(``),a=document.createTextNode(``);return e.insertBefore(i,n),e.insertBefore(r.content,n),e.insertBefore(a,n),()=>{let e=i.nextSibling;for(;e&&e!==a;){let t=e.nextSibling;e.parentNode?.removeChild(e),e=t}i.parentNode?.removeChild(i),a.parentNode?.removeChild(a)}},hydrateDom(){return()=>{}}}}}exports.raw=t;
|
|
2
|
+
//# sourceMappingURL=raw.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raw.cjs","names":[],"sources":["../../../../src/elur/template/raw.ts"],"sourcesContent":["import { ELUR_RENDER_PROTOCOL, type ElurRenderProtocol } from \"./types.js\";\n\n/**\n * Marks a string as trusted raw HTML. Only use with sanitized/trusted content;\n * the markup is emitted verbatim on the server and mounted verbatim on the client\n * without escaping. This is the only explicit trusted path for raw HTML.\n */\nexport function raw(htmlString: string): { [ELUR_RENDER_PROTOCOL]: ElurRenderProtocol } {\n return {\n [ELUR_RENDER_PROTOCOL]: {\n renderServer() {\n return htmlString;\n },\n mountDom({ parent, before }) {\n const template = document.createElement(\"template\");\n template.innerHTML = htmlString;\n const start = document.createTextNode(\"\");\n const end = document.createTextNode(\"\");\n parent.insertBefore(start, before);\n parent.insertBefore(template.content, before);\n parent.insertBefore(end, before);\n return () => {\n let node: Node | null = start.nextSibling;\n while (node && node !== end) {\n const next = node.nextSibling;\n node.parentNode?.removeChild(node);\n node = next;\n }\n start.parentNode?.removeChild(start);\n end.parentNode?.removeChild(end);\n };\n },\n hydrateDom() {\n // The SSR markup is already present in the DOM; nothing to adopt.\n return () => { };\n },\n },\n };\n}"],"mappings":"+BAOA,SAAgB,EAAI,EAAoE,CACpF,MAAO,EACF,EAAA,sBAAuB,CACpB,cAAe,CACX,OAAO,GAEX,SAAS,CAAE,SAAQ,UAAU,CACzB,IAAM,EAAW,SAAS,cAAc,WAAW,CACnD,EAAS,UAAY,EACrB,IAAM,EAAQ,SAAS,eAAe,GAAG,CACnC,EAAM,SAAS,eAAe,GAAG,CAIvC,OAHA,EAAO,aAAa,EAAO,EAAO,CAClC,EAAO,aAAa,EAAS,QAAS,EAAO,CAC7C,EAAO,aAAa,EAAK,EAAO,KACnB,CACT,IAAI,EAAoB,EAAM,YAC9B,KAAO,GAAQ,IAAS,GAAK,CACzB,IAAM,EAAO,EAAK,YAClB,EAAK,YAAY,YAAY,EAAK,CAClC,EAAO,EAEX,EAAM,YAAY,YAAY,EAAM,CACpC,EAAI,YAAY,YAAY,EAAI,GAGxC,YAAa,CAET,UAAa,IAEpB,CACJ"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ELUR_RENDER_PROTOCOL, type ElurRenderProtocol } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Marks a string as trusted raw HTML. Only use with sanitized/trusted content;
|
|
4
|
+
* the markup is emitted verbatim on the server and mounted verbatim on the client
|
|
5
|
+
* without escaping. This is the only explicit trusted path for raw HTML.
|
|
6
|
+
*/
|
|
7
|
+
export declare function raw(htmlString: string): {
|
|
8
|
+
[ELUR_RENDER_PROTOCOL]: ElurRenderProtocol;
|
|
9
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ELUR_RENDER_PROTOCOL, type ElurRenderProtocol } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Marks a string as trusted raw HTML. Only use with sanitized/trusted content;
|
|
4
|
+
* the markup is emitted verbatim on the server and mounted verbatim on the client
|
|
5
|
+
* without escaping. This is the only explicit trusted path for raw HTML.
|
|
6
|
+
*/
|
|
7
|
+
export declare function raw(htmlString: string): {
|
|
8
|
+
[ELUR_RENDER_PROTOCOL]: ElurRenderProtocol;
|
|
9
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ELUR_RENDER_PROTOCOL as e } from "./types.js";
|
|
2
|
+
//#region src/elur/template/raw.ts
|
|
3
|
+
function t(t) {
|
|
4
|
+
return { [e]: {
|
|
5
|
+
renderServer() {
|
|
6
|
+
return t;
|
|
7
|
+
},
|
|
8
|
+
mountDom({ parent: e, before: n }) {
|
|
9
|
+
let r = document.createElement("template");
|
|
10
|
+
r.innerHTML = t;
|
|
11
|
+
let i = document.createTextNode(""), a = document.createTextNode("");
|
|
12
|
+
return e.insertBefore(i, n), e.insertBefore(r.content, n), e.insertBefore(a, n), () => {
|
|
13
|
+
let e = i.nextSibling;
|
|
14
|
+
for (; e && e !== a;) {
|
|
15
|
+
let t = e.nextSibling;
|
|
16
|
+
e.parentNode?.removeChild(e), e = t;
|
|
17
|
+
}
|
|
18
|
+
i.parentNode?.removeChild(i), a.parentNode?.removeChild(a);
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
hydrateDom() {
|
|
22
|
+
return () => {};
|
|
23
|
+
}
|
|
24
|
+
} };
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { t as raw };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=raw.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raw.js","names":[],"sources":["../../../../src/elur/template/raw.ts"],"sourcesContent":["import { ELUR_RENDER_PROTOCOL, type ElurRenderProtocol } from \"./types.js\";\n\n/**\n * Marks a string as trusted raw HTML. Only use with sanitized/trusted content;\n * the markup is emitted verbatim on the server and mounted verbatim on the client\n * without escaping. This is the only explicit trusted path for raw HTML.\n */\nexport function raw(htmlString: string): { [ELUR_RENDER_PROTOCOL]: ElurRenderProtocol } {\n return {\n [ELUR_RENDER_PROTOCOL]: {\n renderServer() {\n return htmlString;\n },\n mountDom({ parent, before }) {\n const template = document.createElement(\"template\");\n template.innerHTML = htmlString;\n const start = document.createTextNode(\"\");\n const end = document.createTextNode(\"\");\n parent.insertBefore(start, before);\n parent.insertBefore(template.content, before);\n parent.insertBefore(end, before);\n return () => {\n let node: Node | null = start.nextSibling;\n while (node && node !== end) {\n const next = node.nextSibling;\n node.parentNode?.removeChild(node);\n node = next;\n }\n start.parentNode?.removeChild(start);\n end.parentNode?.removeChild(end);\n };\n },\n hydrateDom() {\n // The SSR markup is already present in the DOM; nothing to adopt.\n return () => { };\n },\n },\n };\n}"],"mappings":";;AAOA,SAAgB,EAAI,GAAoE;AACpF,QAAO,GACF,IAAuB;EACpB,eAAe;AACX,UAAO;;EAEX,SAAS,EAAE,WAAQ,aAAU;GACzB,IAAM,IAAW,SAAS,cAAc,WAAW;AACnD,KAAS,YAAY;GACrB,IAAM,IAAQ,SAAS,eAAe,GAAG,EACnC,IAAM,SAAS,eAAe,GAAG;AAIvC,UAHA,EAAO,aAAa,GAAO,EAAO,EAClC,EAAO,aAAa,EAAS,SAAS,EAAO,EAC7C,EAAO,aAAa,GAAK,EAAO,QACnB;IACT,IAAI,IAAoB,EAAM;AAC9B,WAAO,KAAQ,MAAS,IAAK;KACzB,IAAM,IAAO,EAAK;AAElB,KADA,EAAK,YAAY,YAAY,EAAK,EAClC,IAAO;;AAGX,IADA,EAAM,YAAY,YAAY,EAAM,EACpC,EAAI,YAAY,YAAY,EAAI;;;EAGxC,aAAa;AAET,gBAAa;;EAEpB,EACJ"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=new Set([`href`,`src`,`action`,`formaction`,`xlink:href`,`poster`,`background`,`cite`,`ping`,`data`]);function t(t){return e.has(t.toLowerCase())}function n(e){let t=e.toLowerCase();return t.startsWith(`on`)||t===`srcdoc`}var r=/[\u0000-\u001F\u007F-\u009F\u2028\u2029\uFEFF]/g,i=/^(?:javascript|vbscript|livescript|mocha|data):/i,a=/^data:image\/(?:png|jpe?g|gif|webp|avif|bmp|x-icon|vnd\.microsoft\.icon)[;,]/i;function o(e){let t=e.replace(r,``).trim();return i.test(t)?/^data:/i.test(t)&&a.test(t)?e:(console.warn(`[elur] Blocked attribute URL with unsafe scheme: "${t.slice(0,48)}${t.length>48?`…`:``}"`),``):e}exports.isExecutableAttrName=n,exports.isUrlAttrName=t,exports.sanitizeUrl=o;
|
|
2
|
+
//# sourceMappingURL=sanitize.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.cjs","names":[],"sources":["../../../../src/elur/template/sanitize.ts"],"sourcesContent":["// =============================================================================\n// --- Attribute value sanitization ---\n// =============================================================================\n//\n// Threat model: in Elur the attribute *name* is always authored by the developer\n// (it lives in the static part of the html`` literal and is never interpolated),\n// so it is trusted. The attribute *value* is frequently untrusted (API data,\n// user input, route params). The only attribute family where an untrusted value\n// is interpreted as executable code is URL attributes via the `javascript:`,\n// `vbscript:` and `data:text/html` schemes.\n//\n// Performance: the *classification* (`isUrlAttrName`) runs once per template at\n// compile time (cached in the BindingContext). The actual `sanitizeUrl` runs\n// only for URL attributes and only when their value changes — never for\n// `class`, `style`, `aria-*`, `data-*` or any other attribute.\n\n/**\n * Attributes whose value the browser resolves as a URL. Only these go through\n * `sanitizeUrl`. Everything else (class, style, aria-*, data-*, custom) is\n * untouched and keeps its exact original code path.\n */\nconst URL_ATTRS = new Set([\n \"href\",\n \"src\",\n \"action\",\n \"formaction\",\n \"xlink:href\",\n \"poster\",\n \"background\",\n \"cite\",\n \"ping\",\n \"data\", // <object data=\"...\">\n]);\n\n/** True if `name` is a URL-bearing attribute that must be scheme-checked. */\nexport function isUrlAttrName(name: string): boolean {\n return URL_ATTRS.has(name.toLowerCase());\n}\n\n/**\n * True if `name` is an attribute that turns its value into executable code or\n * arbitrary markup (inline event handlers, `srcdoc`). In idiomatic Elur events\n * use `@click` (handled as an event binding, never as an attribute), so an\n * `on*`/`srcdoc` attribute binding is almost always a developer mistake. Used\n * only to emit a warning — never to block.\n */\nexport function isExecutableAttrName(name: string): boolean {\n const n = name.toLowerCase();\n return n.startsWith(\"on\") || n === \"srcdoc\";\n}\n\n// Characters the browser strips/ignores while resolving a scheme. Removing them\n// before testing the scheme prevents bypasses like \"java\\tscript:\" or leading\n// control bytes / BOM / line separators.\nconst CONTROL_RE = /[\\u0000-\\u001F\\u007F-\\u009F\\u2028\\u2029\\uFEFF]/g;\n\n// Dangerous URL schemes. `data:` is included because `data:text/html` and\n// `data:image/svg+xml` can execute script; safe raster data URIs are allowed\n// back in below.\nconst UNSAFE_SCHEME_RE = /^(?:javascript|vbscript|livescript|mocha|data):/i;\n\n// Raster image data URIs are safe (cannot execute script). SVG is intentionally\n// excluded because it can carry inline scripts.\nconst SAFE_DATA_RE = /^data:image\\/(?:png|jpe?g|gif|webp|avif|bmp|x-icon|vnd\\.microsoft\\.icon)[;,]/i;\n\n/**\n * Returns the URL unchanged if its scheme is safe; otherwise returns \"\" and\n * warns. Normalizes control characters and surrounding whitespace before the\n * scheme check so that obfuscated payloads cannot slip through.\n */\nexport function sanitizeUrl(raw: string): string {\n const normalized = raw.replace(CONTROL_RE, \"\").trim();\n\n if (UNSAFE_SCHEME_RE.test(normalized)) {\n if (/^data:/i.test(normalized) && SAFE_DATA_RE.test(normalized)) {\n return raw;\n }\n console.warn(\n `[elur] Blocked attribute URL with unsafe scheme: \"${normalized.slice(0, 48)}${normalized.length > 48 ? \"…\" : \"\"}\"`,\n );\n return \"\";\n }\n\n return raw;\n}\n"],"mappings":"AAqBA,IAAM,EAAY,IAAI,IAAI,CACtB,OACA,MACA,SACA,aACA,aACA,SACA,aACA,OACA,OACA,OACH,CAAC,CAGF,SAAgB,EAAc,EAAuB,CACjD,OAAO,EAAU,IAAI,EAAK,aAAa,CAAC,CAU5C,SAAgB,EAAqB,EAAuB,CACxD,IAAM,EAAI,EAAK,aAAa,CAC5B,OAAO,EAAE,WAAW,KAAK,EAAI,IAAM,SAMvC,IAAM,EAAa,kDAKb,EAAmB,mDAInB,EAAe,gFAOrB,SAAgB,EAAY,EAAqB,CAC7C,IAAM,EAAa,EAAI,QAAQ,EAAY,GAAG,CAAC,MAAM,CAYrD,OAVI,EAAiB,KAAK,EAAW,CAC7B,UAAU,KAAK,EAAW,EAAI,EAAa,KAAK,EAAW,CACpD,GAEX,QAAQ,KACJ,qDAAqD,EAAW,MAAM,EAAG,GAAG,GAAG,EAAW,OAAS,GAAK,IAAM,GAAG,GACpH,CACM,IAGJ"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** True if `name` is a URL-bearing attribute that must be scheme-checked. */
|
|
2
|
+
export declare function isUrlAttrName(name: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* True if `name` is an attribute that turns its value into executable code or
|
|
5
|
+
* arbitrary markup (inline event handlers, `srcdoc`). In idiomatic Elur events
|
|
6
|
+
* use `@click` (handled as an event binding, never as an attribute), so an
|
|
7
|
+
* `on*`/`srcdoc` attribute binding is almost always a developer mistake. Used
|
|
8
|
+
* only to emit a warning — never to block.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isExecutableAttrName(name: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Returns the URL unchanged if its scheme is safe; otherwise returns "" and
|
|
13
|
+
* warns. Normalizes control characters and surrounding whitespace before the
|
|
14
|
+
* scheme check so that obfuscated payloads cannot slip through.
|
|
15
|
+
*/
|
|
16
|
+
export declare function sanitizeUrl(raw: string): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** True if `name` is a URL-bearing attribute that must be scheme-checked. */
|
|
2
|
+
export declare function isUrlAttrName(name: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* True if `name` is an attribute that turns its value into executable code or
|
|
5
|
+
* arbitrary markup (inline event handlers, `srcdoc`). In idiomatic Elur events
|
|
6
|
+
* use `@click` (handled as an event binding, never as an attribute), so an
|
|
7
|
+
* `on*`/`srcdoc` attribute binding is almost always a developer mistake. Used
|
|
8
|
+
* only to emit a warning — never to block.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isExecutableAttrName(name: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Returns the URL unchanged if its scheme is safe; otherwise returns "" and
|
|
13
|
+
* warns. Normalizes control characters and surrounding whitespace before the
|
|
14
|
+
* scheme check so that obfuscated payloads cannot slip through.
|
|
15
|
+
*/
|
|
16
|
+
export declare function sanitizeUrl(raw: string): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/elur/template/sanitize.ts
|
|
2
|
+
var e = new Set([
|
|
3
|
+
"href",
|
|
4
|
+
"src",
|
|
5
|
+
"action",
|
|
6
|
+
"formaction",
|
|
7
|
+
"xlink:href",
|
|
8
|
+
"poster",
|
|
9
|
+
"background",
|
|
10
|
+
"cite",
|
|
11
|
+
"ping",
|
|
12
|
+
"data"
|
|
13
|
+
]);
|
|
14
|
+
function t(t) {
|
|
15
|
+
return e.has(t.toLowerCase());
|
|
16
|
+
}
|
|
17
|
+
function n(e) {
|
|
18
|
+
let t = e.toLowerCase();
|
|
19
|
+
return t.startsWith("on") || t === "srcdoc";
|
|
20
|
+
}
|
|
21
|
+
var r = /[\u0000-\u001F\u007F-\u009F\u2028\u2029\uFEFF]/g, i = /^(?:javascript|vbscript|livescript|mocha|data):/i, a = /^data:image\/(?:png|jpe?g|gif|webp|avif|bmp|x-icon|vnd\.microsoft\.icon)[;,]/i;
|
|
22
|
+
function o(e) {
|
|
23
|
+
let t = e.replace(r, "").trim();
|
|
24
|
+
return i.test(t) ? /^data:/i.test(t) && a.test(t) ? e : (console.warn(`[elur] Blocked attribute URL with unsafe scheme: "${t.slice(0, 48)}${t.length > 48 ? "…" : ""}"`), "") : e;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { n as isExecutableAttrName, t as isUrlAttrName, o as sanitizeUrl };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=sanitize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.js","names":[],"sources":["../../../../src/elur/template/sanitize.ts"],"sourcesContent":["// =============================================================================\n// --- Attribute value sanitization ---\n// =============================================================================\n//\n// Threat model: in Elur the attribute *name* is always authored by the developer\n// (it lives in the static part of the html`` literal and is never interpolated),\n// so it is trusted. The attribute *value* is frequently untrusted (API data,\n// user input, route params). The only attribute family where an untrusted value\n// is interpreted as executable code is URL attributes via the `javascript:`,\n// `vbscript:` and `data:text/html` schemes.\n//\n// Performance: the *classification* (`isUrlAttrName`) runs once per template at\n// compile time (cached in the BindingContext). The actual `sanitizeUrl` runs\n// only for URL attributes and only when their value changes — never for\n// `class`, `style`, `aria-*`, `data-*` or any other attribute.\n\n/**\n * Attributes whose value the browser resolves as a URL. Only these go through\n * `sanitizeUrl`. Everything else (class, style, aria-*, data-*, custom) is\n * untouched and keeps its exact original code path.\n */\nconst URL_ATTRS = new Set([\n \"href\",\n \"src\",\n \"action\",\n \"formaction\",\n \"xlink:href\",\n \"poster\",\n \"background\",\n \"cite\",\n \"ping\",\n \"data\", // <object data=\"...\">\n]);\n\n/** True if `name` is a URL-bearing attribute that must be scheme-checked. */\nexport function isUrlAttrName(name: string): boolean {\n return URL_ATTRS.has(name.toLowerCase());\n}\n\n/**\n * True if `name` is an attribute that turns its value into executable code or\n * arbitrary markup (inline event handlers, `srcdoc`). In idiomatic Elur events\n * use `@click` (handled as an event binding, never as an attribute), so an\n * `on*`/`srcdoc` attribute binding is almost always a developer mistake. Used\n * only to emit a warning — never to block.\n */\nexport function isExecutableAttrName(name: string): boolean {\n const n = name.toLowerCase();\n return n.startsWith(\"on\") || n === \"srcdoc\";\n}\n\n// Characters the browser strips/ignores while resolving a scheme. Removing them\n// before testing the scheme prevents bypasses like \"java\\tscript:\" or leading\n// control bytes / BOM / line separators.\nconst CONTROL_RE = /[\\u0000-\\u001F\\u007F-\\u009F\\u2028\\u2029\\uFEFF]/g;\n\n// Dangerous URL schemes. `data:` is included because `data:text/html` and\n// `data:image/svg+xml` can execute script; safe raster data URIs are allowed\n// back in below.\nconst UNSAFE_SCHEME_RE = /^(?:javascript|vbscript|livescript|mocha|data):/i;\n\n// Raster image data URIs are safe (cannot execute script). SVG is intentionally\n// excluded because it can carry inline scripts.\nconst SAFE_DATA_RE = /^data:image\\/(?:png|jpe?g|gif|webp|avif|bmp|x-icon|vnd\\.microsoft\\.icon)[;,]/i;\n\n/**\n * Returns the URL unchanged if its scheme is safe; otherwise returns \"\" and\n * warns. Normalizes control characters and surrounding whitespace before the\n * scheme check so that obfuscated payloads cannot slip through.\n */\nexport function sanitizeUrl(raw: string): string {\n const normalized = raw.replace(CONTROL_RE, \"\").trim();\n\n if (UNSAFE_SCHEME_RE.test(normalized)) {\n if (/^data:/i.test(normalized) && SAFE_DATA_RE.test(normalized)) {\n return raw;\n }\n console.warn(\n `[elur] Blocked attribute URL with unsafe scheme: \"${normalized.slice(0, 48)}${normalized.length > 48 ? \"…\" : \"\"}\"`,\n );\n return \"\";\n }\n\n return raw;\n}\n"],"mappings":";AAqBA,IAAM,IAAY,IAAI,IAAI;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACH,CAAC;AAGF,SAAgB,EAAc,GAAuB;AACjD,QAAO,EAAU,IAAI,EAAK,aAAa,CAAC;;AAU5C,SAAgB,EAAqB,GAAuB;CACxD,IAAM,IAAI,EAAK,aAAa;AAC5B,QAAO,EAAE,WAAW,KAAK,IAAI,MAAM;;AAMvC,IAAM,IAAa,mDAKb,IAAmB,oDAInB,IAAe;AAOrB,SAAgB,EAAY,GAAqB;CAC7C,IAAM,IAAa,EAAI,QAAQ,GAAY,GAAG,CAAC,MAAM;AAYrD,QAVI,EAAiB,KAAK,EAAW,GAC7B,UAAU,KAAK,EAAW,IAAI,EAAa,KAAK,EAAW,GACpD,KAEX,QAAQ,KACJ,qDAAqD,EAAW,MAAM,GAAG,GAAG,GAAG,EAAW,SAAS,KAAK,MAAM,GAAG,GACpH,EACM,MAGJ"}
|