mirror of
https://github.com/jaandrle/deka-dom-el
synced 2025-07-02 04:32:14 +02:00
Compare commits
18 Commits
v0.9.4-alp
...
1b0312f6bd
Author | SHA1 | Date | |
---|---|---|---|
1b0312f6bd | |||
508d93bb1a | |||
f2ce23d9f7 | |||
b08f75bfb0 | |||
4edc509646 | |||
56232a9f64 | |||
dcf389e28e | |||
bdb20ec298 | |||
7ec50e1660
|
|||
6c4ddd655f
|
|||
198f4a3777
|
|||
3435ea6cfe
|
|||
ed7e6c7963 | |||
3168f452ae
|
|||
b53f3926b3 | |||
8f2fd5a68c | |||
f53b97a89c | |||
f8a94ab9f8
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
dist/docs/
|
||||
dist/.*
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
20
README.md
20
README.md
@ -18,19 +18,19 @@ function HelloWorldComponent({ initial }){
|
||||
/** @param {HTMLOptionElement} el */
|
||||
const isSelected= el=> (el.selected= el.value===initial);
|
||||
// @ts-expect-error 2339: The <select> has only two options with {@link Emoji}
|
||||
const onChange= on("change", event=> emoji(event.target.value));
|
||||
const onChange= on("change", event=> emoji.set(event.target.value));
|
||||
|
||||
return el().append(
|
||||
el("p", {
|
||||
textContent: S(() => `Hello World ${emoji().repeat(clicks())}`),
|
||||
textContent: S(() => `Hello World ${emoji.get().repeat(clicks.get())}`),
|
||||
className: "example",
|
||||
ariaLive: "polite", //OR ariaset: { live: "polite" },
|
||||
dataset: { example: "Example" }, //OR dataExample: "Example",
|
||||
}),
|
||||
el("button",
|
||||
{ textContent: "Fire", type: "button" },
|
||||
on("click", ()=> clicks(clicks() + 1)),
|
||||
on("keyup", ()=> clicks(clicks() - 2)),
|
||||
on("click", ()=> clicks.set(clicks.get() + 1)),
|
||||
on("keyup", ()=> clicks.set(clicks.get() - 2)),
|
||||
),
|
||||
el("select", null, onChange).append(
|
||||
el(OptionComponent, "🎉", isSelected),//OR { textContent: "🎉" }
|
||||
@ -86,10 +86,10 @@ To balance these requirements, numerous compromises have been made. To summarize
|
||||
- [dist/](dist/) (`https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/`…)
|
||||
|
||||
## Signals
|
||||
- [Signals — whats going on behind the scenes | by Ryan Hoffnan | ITNEXT](https://itnext.io/
|
||||
signals-whats-going-on-behind-the-scenes-ec858589ea63)
|
||||
- [The Evolution of Signals in JavaScript - DEV Community](https://dev.to/this-is-learning/the-evolution-of-signals-in-
|
||||
javascript-8ob)
|
||||
- there is also [tc39/proposal-signals: A proposal to add signals to JavaScript.](https://github.com/tc39/proposal-
|
||||
signals)
|
||||
- [Signals — whats going on behind the scenes \| by Ryan Hoffnan \|
|
||||
ITNEXT](https://itnext.io/signals-whats-going-on-behind-the-scenes-ec858589ea63)
|
||||
- [The Evolution of Signals in JavaScript - DEV
|
||||
Community](https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob)
|
||||
- there is also [tc39/proposal-signals:
|
||||
A proposal to add signals to JavaScript.](https://github.com/tc39/proposal-signals)
|
||||
- [Observer pattern - Wikipedia](https://en.wikipedia.org/wiki/Observer_pattern)
|
||||
|
96
bs/build.js
96
bs/build.js
@ -1,71 +1,37 @@
|
||||
#!/usr/bin/env -S npx nodejsscript
|
||||
import { bundle as bundleDTS } from "dts-bundler";
|
||||
import { build } from "./dev/.build.js"
|
||||
const files= [ "index", "index-with-signals" ];
|
||||
const filesOut= (file, mark= "esm")=> "dist/"+file.replace("index", mark);
|
||||
const css= echo.css`
|
||||
.info{ color: gray; }
|
||||
`;
|
||||
|
||||
$.api("", true)
|
||||
.option("--minify", "Level of minification [ no, full, partial (default) ]")
|
||||
.action(async function main({ minify= "partial" }){
|
||||
for(const file_root of files){
|
||||
const file= file_root+".js";
|
||||
echo("Processing: "+ file);
|
||||
const out= filesOut(file);
|
||||
const esbuild_output= s.$().run([
|
||||
"npx esbuild '::file::'",
|
||||
"--platform=neutral",
|
||||
"--bundle",
|
||||
minifyOption(minify),
|
||||
"--legal-comments=inline",
|
||||
"--packages=external",
|
||||
"--outfile='::out::'"
|
||||
].filter(Boolean).join(" "), { file, out });
|
||||
if(esbuild_output.code)
|
||||
return $.exit(esbuild_output.code, echo(esbuild_output.stderr));
|
||||
echoVariant(esbuild_output.stderr.split("\n")[1].trim()+ " (esbuild)");
|
||||
pipe(
|
||||
f=> f.replace(/^ +/gm, m=> "\t".repeat(m.length/2)),
|
||||
f=> s.echo(f).to(out)
|
||||
)(s.cat(out));
|
||||
|
||||
const file_dts= file_root+".d.ts";
|
||||
const file_dts_out= filesOut(file_dts);
|
||||
echoVariant(file_dts_out);
|
||||
s.echo(bundleDTS(file_dts)).to(file_dts_out);
|
||||
|
||||
await toDDE(out, file_root);
|
||||
}
|
||||
$.exit(0);
|
||||
|
||||
async function toDDE(file, file_root){
|
||||
const name= "dde";
|
||||
const out= filesOut(file_root+".js", name);
|
||||
echoVariant(`${out} (${file} → globalThis.${name})`)
|
||||
|
||||
let content= s.cat(file).toString().split(/export ?{/);
|
||||
content.splice(1, 0, `\nglobalThis.${name}= {`);
|
||||
content[2]= content[2]
|
||||
.replace(/,(?!\n)/g, ",\n")
|
||||
.replace(/(?<!\n)}/, "\n}")
|
||||
.replace(/^(\t*)(.*) as ([^,\n]*)(,?)$/mg, "$1$3: $2$4");
|
||||
s.echo([
|
||||
`//deka-dom-el library is available via global namespace \`${name}\``,
|
||||
"(()=> {",
|
||||
content.join(""),
|
||||
"})();"
|
||||
].join("\n")).to(out);
|
||||
}
|
||||
$.api("")
|
||||
.command("main", "Build main files", { default: true })
|
||||
.action(async function main(){
|
||||
const regular = await build({
|
||||
files,
|
||||
filesOut,
|
||||
minify: "no",
|
||||
});
|
||||
const min = await build({
|
||||
files,
|
||||
filesOut(file, mark= "esm"){
|
||||
const out= filesOut(file, mark);
|
||||
const idx= out.lastIndexOf(".");
|
||||
return out.slice(0, idx)+".min"+out.slice(idx);
|
||||
},
|
||||
minify: "full",
|
||||
});
|
||||
return $.exit(regular + min);
|
||||
})
|
||||
.command("signals", "Build only signals (for example for analysis)")
|
||||
.action(async function signals(){
|
||||
const regular = await build({
|
||||
files: [ "signals" ],
|
||||
filesOut(file){ return "dist/."+file; },
|
||||
minify: "no",
|
||||
dde: false,
|
||||
});
|
||||
return $.exit(regular);
|
||||
})
|
||||
.parse();
|
||||
|
||||
/** @param {"no"|"full"|"partial"} level */
|
||||
function minifyOption(level= "partial"){
|
||||
if("no"===level) return undefined;
|
||||
if("full"===level) return "--minify";
|
||||
return "--minify-syntax --minify-identifiers";
|
||||
}
|
||||
function echoVariant(name){
|
||||
return echo("%c✓ "+name, css.info+css);
|
||||
}
|
||||
|
||||
function filesOut(file, mark= "esm"){ return "dist/"+file.replace("index", mark); }
|
||||
|
65
bs/dev/.build.js
Normal file
65
bs/dev/.build.js
Normal file
@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env -S npx nodejsscript
|
||||
import { bundle as bundleDTS } from "dts-bundler";
|
||||
const css= echo.css`
|
||||
.info{ color: gray; }
|
||||
`;
|
||||
|
||||
export async function build({ files, filesOut, minify= "partiala", dde= true }){
|
||||
for(const file_root of files){
|
||||
const file= file_root+".js";
|
||||
echo(`Processing ${file} (minified: ${minify})`);
|
||||
const out= filesOut(file);
|
||||
const esbuild_output= s.$().run([
|
||||
"npx esbuild '::file::'",
|
||||
"--platform=neutral",
|
||||
"--bundle",
|
||||
minifyOption(minify),
|
||||
"--legal-comments=inline",
|
||||
"--packages=external",
|
||||
"--outfile='::out::'"
|
||||
].filter(Boolean).join(" "), { file, out });
|
||||
if(esbuild_output.code)
|
||||
return $.exit(esbuild_output.code, echo(esbuild_output.stderr));
|
||||
echoVariant(esbuild_output.stderr.split("\n")[1].trim()+ " (esbuild)");
|
||||
pipe(
|
||||
f=> f.replace(/^ +/gm, m=> "\t".repeat(m.length/2)),
|
||||
f=> s.echo(f).to(out)
|
||||
)(s.cat(out));
|
||||
|
||||
const file_dts= file_root+".d.ts";
|
||||
const file_dts_out= filesOut(file_dts);
|
||||
echoVariant(file_dts_out);
|
||||
s.echo(bundleDTS(file_dts)).to(file_dts_out);
|
||||
|
||||
if(dde) await toDDE(out, file_root);
|
||||
}
|
||||
return 0;
|
||||
|
||||
async function toDDE(file, file_root){
|
||||
const name= "dde";
|
||||
const out= filesOut(file_root+".js", name);
|
||||
echoVariant(`${out} (${file} → globalThis.${name})`)
|
||||
|
||||
let content= s.cat(file).toString().split(/export ?{/);
|
||||
content.splice(1, 0, `\nglobalThis.${name}= {`);
|
||||
content[2]= content[2]
|
||||
.replace(/,(?!\n)/g, ",\n")
|
||||
.replace(/(?<!\n)}/, "\n}")
|
||||
.replace(/^(\t*)(.*) as ([^,\n]*)(,?)$/mg, "$1$3: $2$4");
|
||||
s.echo([
|
||||
`//deka-dom-el library is available via global namespace \`${name}\``,
|
||||
"(()=> {",
|
||||
content.join(""),
|
||||
"})();"
|
||||
].join("\n")).to(out);
|
||||
}
|
||||
}
|
||||
/** @param {"no"|"full"|"partial"} level */
|
||||
function minifyOption(level= "partial"){
|
||||
if("no"===level) return undefined;
|
||||
if("full"===level) return "--minify";
|
||||
return "--minify-syntax --minify-identifiers";
|
||||
}
|
||||
function echoVariant(name){
|
||||
return echo("%c✓ "+name, css.info+css);
|
||||
}
|
1316
dist/dde-with-signals.js
vendored
1316
dist/dde-with-signals.js
vendored
File diff suppressed because it is too large
Load Diff
31
dist/dde-with-signals.min.js
vendored
Normal file
31
dist/dde-with-signals.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
849
dist/dde.js
vendored
849
dist/dde.js
vendored
@ -1,456 +1,663 @@
|
||||
//deka-dom-el library is available via global namespace `dde`
|
||||
(()=> {
|
||||
// src/signals-common.js
|
||||
var C = {
|
||||
isSignal(t) {
|
||||
return !1;
|
||||
},
|
||||
processReactiveAttribute(t, e, r, n) {
|
||||
return r;
|
||||
}
|
||||
};
|
||||
function Z(t, e = !0) {
|
||||
return e ? Object.assign(C, t) : (Object.setPrototypeOf(t, C), t);
|
||||
}
|
||||
function S(t) {
|
||||
return C.isPrototypeOf(t) && t !== C ? t : C;
|
||||
}
|
||||
|
||||
// src/helpers.js
|
||||
function m(t) {
|
||||
return typeof t > "u";
|
||||
function isUndef(value) {
|
||||
return typeof value === "undefined";
|
||||
}
|
||||
function L(t, e) {
|
||||
if (!t || !(t instanceof AbortSignal))
|
||||
return !0;
|
||||
if (!t.aborted)
|
||||
return t.addEventListener("abort", e), function() {
|
||||
t.removeEventListener("abort", e);
|
||||
};
|
||||
function isInstance(obj, cls) {
|
||||
return obj instanceof cls;
|
||||
}
|
||||
function q(t, e) {
|
||||
let { observedAttributes: r = [] } = t.constructor;
|
||||
return r.reduce(function(n, o) {
|
||||
return n[G(o)] = e(t, o), n;
|
||||
function isProtoFrom(obj, cls) {
|
||||
return Object.prototype.isPrototypeOf.call(cls, obj);
|
||||
}
|
||||
function oAssign(...o) {
|
||||
return Object.assign(...o);
|
||||
}
|
||||
function onAbort(signal, listener) {
|
||||
if (!signal || !isInstance(signal, AbortSignal))
|
||||
return true;
|
||||
if (signal.aborted)
|
||||
return;
|
||||
signal.addEventListener("abort", listener);
|
||||
return function cleanUp() {
|
||||
signal.removeEventListener("abort", listener);
|
||||
};
|
||||
}
|
||||
function observedAttributes(instance, observedAttribute) {
|
||||
const { observedAttributes: observedAttributes3 = [] } = instance.constructor;
|
||||
return observedAttributes3.reduce(function(out, name) {
|
||||
out[kebabToCamel(name)] = observedAttribute(instance, name);
|
||||
return out;
|
||||
}, {});
|
||||
}
|
||||
function G(t) {
|
||||
return t.replace(/-./g, (e) => e[1].toUpperCase());
|
||||
function kebabToCamel(name) {
|
||||
return name.replace(/-./g, (x) => x[1].toUpperCase());
|
||||
}
|
||||
|
||||
// src/signals-lib/common.js
|
||||
var signals_global = {
|
||||
/**
|
||||
* Checks if a value is a signal
|
||||
* @param {any} attributes - Value to check
|
||||
* @returns {boolean} Whether the value is a signal
|
||||
*/
|
||||
isSignal(attributes) {
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* Processes an attribute that might be reactive
|
||||
* @param {Element} obj - Element that owns the attribute
|
||||
* @param {string} key - Attribute name
|
||||
* @param {any} attr - Attribute value
|
||||
* @param {Function} set - Function to set the attribute
|
||||
* @returns {any} Processed attribute value
|
||||
*/
|
||||
processReactiveAttribute(obj, key, attr, set) {
|
||||
return attr;
|
||||
}
|
||||
};
|
||||
function registerReactivity(def, global = true) {
|
||||
if (global) return oAssign(signals_global, def);
|
||||
Object.setPrototypeOf(def, signals_global);
|
||||
return def;
|
||||
}
|
||||
function signals(_this) {
|
||||
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
|
||||
}
|
||||
|
||||
// src/dom-common.js
|
||||
var a = {
|
||||
setDeleteAttr: V,
|
||||
var enviroment = {
|
||||
setDeleteAttr,
|
||||
ssr: "",
|
||||
D: globalThis.document,
|
||||
F: globalThis.DocumentFragment,
|
||||
H: globalThis.HTMLElement,
|
||||
S: globalThis.SVGElement,
|
||||
M: globalThis.MutationObserver,
|
||||
q: (t) => t || Promise.resolve()
|
||||
q: (p) => p || Promise.resolve()
|
||||
};
|
||||
function V(t, e, r) {
|
||||
if (Reflect.set(t, e, r), !!m(r)) {
|
||||
if (Reflect.deleteProperty(t, e), t instanceof a.H && t.getAttribute(e) === "undefined")
|
||||
return t.removeAttribute(e);
|
||||
if (Reflect.get(t, e) === "undefined")
|
||||
return Reflect.set(t, e, "");
|
||||
}
|
||||
function setDeleteAttr(obj, prop, val) {
|
||||
Reflect.set(obj, prop, val);
|
||||
if (!isUndef(val)) return;
|
||||
Reflect.deleteProperty(obj, prop);
|
||||
if (isInstance(obj, enviroment.H) && obj.getAttribute(prop) === "undefined")
|
||||
return obj.removeAttribute(prop);
|
||||
if (Reflect.get(obj, prop) === "undefined")
|
||||
return Reflect.set(obj, prop, "");
|
||||
}
|
||||
var x = "__dde_lifecyclesToEvents", v = "dde:connected", w = "dde:disconnected", y = "dde:attributeChanged";
|
||||
var keyLTE = "__dde_lifecyclesToEvents";
|
||||
var evc = "dde:connected";
|
||||
var evd = "dde:disconnected";
|
||||
var eva = "dde:attributeChanged";
|
||||
|
||||
// src/dom.js
|
||||
function dt(t) {
|
||||
return a.q(t);
|
||||
function queue(promise) {
|
||||
return enviroment.q(promise);
|
||||
}
|
||||
var g = [{
|
||||
var scopes = [{
|
||||
get scope() {
|
||||
return a.D.body;
|
||||
return enviroment.D.body;
|
||||
},
|
||||
host: (t) => t ? t(a.D.body) : a.D.body,
|
||||
prevent: !0
|
||||
}], O = {
|
||||
host: (c) => c ? c(enviroment.D.body) : enviroment.D.body,
|
||||
prevent: true
|
||||
}];
|
||||
var scope = {
|
||||
/**
|
||||
* Gets the current scope
|
||||
* @returns {Object} Current scope context
|
||||
*/
|
||||
get current() {
|
||||
return g[g.length - 1];
|
||||
return scopes[scopes.length - 1];
|
||||
},
|
||||
/**
|
||||
* Gets the host element of the current scope
|
||||
* @returns {Function} Host accessor function
|
||||
*/
|
||||
get host() {
|
||||
return this.current.host;
|
||||
},
|
||||
/**
|
||||
* Prevents default behavior in the current scope
|
||||
* @returns {Object} Current scope context
|
||||
*/
|
||||
preventDefault() {
|
||||
let { current: t } = this;
|
||||
return t.prevent = !0, t;
|
||||
const { current } = this;
|
||||
current.prevent = true;
|
||||
return current;
|
||||
},
|
||||
/**
|
||||
* Gets a copy of the current scope stack
|
||||
* @returns {Array} Copy of scope stack
|
||||
*/
|
||||
get state() {
|
||||
return [...g];
|
||||
return [...scopes];
|
||||
},
|
||||
push(t = {}) {
|
||||
return g.push(Object.assign({}, this.current, { prevent: !1 }, t));
|
||||
/**
|
||||
* Pushes a new scope to the stack
|
||||
* @param {Object} [s={}] - Scope object to push
|
||||
* @returns {number} New length of the scope stack
|
||||
*/
|
||||
push(s = {}) {
|
||||
return scopes.push(oAssign({}, this.current, { prevent: false }, s));
|
||||
},
|
||||
/**
|
||||
* Pushes the root scope to the stack
|
||||
* @returns {number} New length of the scope stack
|
||||
*/
|
||||
pushRoot() {
|
||||
return g.push(g[0]);
|
||||
return scopes.push(scopes[0]);
|
||||
},
|
||||
/**
|
||||
* Pops the current scope from the stack
|
||||
* @returns {Object|undefined} Popped scope or undefined if only one scope remains
|
||||
*/
|
||||
pop() {
|
||||
if (g.length !== 1)
|
||||
return g.pop();
|
||||
if (scopes.length === 1) return;
|
||||
return scopes.pop();
|
||||
}
|
||||
};
|
||||
function k(...t) {
|
||||
return this.appendOriginal(...t), this;
|
||||
function append(...els) {
|
||||
this.appendOriginal(...els);
|
||||
return this;
|
||||
}
|
||||
function J(t) {
|
||||
return t.append === k || (t.appendOriginal = t.append, t.append = k), t;
|
||||
function chainableAppend(el) {
|
||||
if (el.append === append) return el;
|
||||
el.appendOriginal = el.append;
|
||||
el.append = append;
|
||||
return el;
|
||||
}
|
||||
var T;
|
||||
function P(t, e, ...r) {
|
||||
let n = S(this), o = 0, c, d;
|
||||
switch ((Object(e) !== e || n.isSignal(e)) && (e = { textContent: e }), !0) {
|
||||
case typeof t == "function": {
|
||||
o = 1;
|
||||
let f = (...l) => l.length ? (o === 1 ? r.unshift(...l) : l.forEach((E) => E(d)), void 0) : d;
|
||||
O.push({ scope: t, host: f }), c = t(e || void 0);
|
||||
let p = c instanceof a.F;
|
||||
if (c.nodeName === "#comment") break;
|
||||
let b = P.mark({
|
||||
var namespace;
|
||||
function createElement(tag, attributes, ...addons) {
|
||||
const s = signals(this);
|
||||
let scoped = 0;
|
||||
let el, el_host;
|
||||
if (Object(attributes) !== attributes || s.isSignal(attributes))
|
||||
attributes = { textContent: attributes };
|
||||
switch (true) {
|
||||
case typeof tag === "function": {
|
||||
scoped = 1;
|
||||
const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0);
|
||||
scope.push({ scope: tag, host });
|
||||
el = tag(attributes || void 0);
|
||||
const is_fragment = isInstance(el, enviroment.F);
|
||||
if (el.nodeName === "#comment") break;
|
||||
const el_mark = createElement.mark({
|
||||
type: "component",
|
||||
name: t.name,
|
||||
host: p ? "this" : "parentElement"
|
||||
name: tag.name,
|
||||
host: is_fragment ? "this" : "parentElement"
|
||||
});
|
||||
c.prepend(b), p && (d = b);
|
||||
el.prepend(el_mark);
|
||||
if (is_fragment) el_host = el_mark;
|
||||
break;
|
||||
}
|
||||
case t === "#text":
|
||||
c = R.call(this, a.D.createTextNode(""), e);
|
||||
case tag === "#text":
|
||||
el = assign.call(this, enviroment.D.createTextNode(""), attributes);
|
||||
break;
|
||||
case (t === "<>" || !t):
|
||||
c = R.call(this, a.D.createDocumentFragment(), e);
|
||||
case (tag === "<>" || !tag):
|
||||
el = assign.call(this, enviroment.D.createDocumentFragment(), attributes);
|
||||
break;
|
||||
case !!T:
|
||||
c = R.call(this, a.D.createElementNS(T, t), e);
|
||||
case Boolean(namespace):
|
||||
el = assign.call(this, enviroment.D.createElementNS(namespace, tag), attributes);
|
||||
break;
|
||||
case !c:
|
||||
c = R.call(this, a.D.createElement(t), e);
|
||||
case !el:
|
||||
el = assign.call(this, enviroment.D.createElement(tag), attributes);
|
||||
}
|
||||
return J(c), d || (d = c), r.forEach((f) => f(d)), o && O.pop(), o = 2, c;
|
||||
chainableAppend(el);
|
||||
if (!el_host) el_host = el;
|
||||
addons.forEach((c) => c(el_host));
|
||||
if (scoped) scope.pop();
|
||||
scoped = 2;
|
||||
return el;
|
||||
}
|
||||
P.mark = function(t, e = !1) {
|
||||
t = Object.entries(t).map(([o, c]) => o + `="${c}"`).join(" ");
|
||||
let r = e ? "" : "/", n = a.D.createComment(`<dde:mark ${t}${a.ssr}${r}>`);
|
||||
return e && (n.end = a.D.createComment("</dde:mark>")), n;
|
||||
createElement.mark = function(attrs, is_open = false) {
|
||||
attrs = Object.entries(attrs).map(([n, v]) => n + `="${v}"`).join(" ");
|
||||
const end = is_open ? "" : "/";
|
||||
const out = enviroment.D.createComment(`<dde:mark ${attrs}${enviroment.ssr}${end}>`);
|
||||
if (is_open) out.end = enviroment.D.createComment("</dde:mark>");
|
||||
return out;
|
||||
};
|
||||
function pt(t) {
|
||||
let e = this;
|
||||
return function(...n) {
|
||||
T = t;
|
||||
let o = P.call(e, ...n);
|
||||
return T = void 0, o;
|
||||
function createElementNS(ns) {
|
||||
const _this = this;
|
||||
return function createElementNSCurried(...rest) {
|
||||
namespace = ns;
|
||||
const el = createElement.call(_this, ...rest);
|
||||
namespace = void 0;
|
||||
return el;
|
||||
};
|
||||
}
|
||||
function lt(t, e = t) {
|
||||
let r = "\xB9\u2070", n = "\u2713", o = Object.fromEntries(
|
||||
Array.from(e.querySelectorAll("slot")).filter((c) => !c.name.endsWith(r)).map((c) => [c.name += r, c])
|
||||
function simulateSlots(element, root = element) {
|
||||
const mark_e = "\xB9\u2070", mark_s = "\u2713";
|
||||
const slots = Object.fromEntries(
|
||||
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
|
||||
);
|
||||
if (t.append = new Proxy(t.append, {
|
||||
apply(c, d, f) {
|
||||
if (f[0] === e) return c.apply(t, f);
|
||||
for (let p of f) {
|
||||
let b = (p.slot || "") + r;
|
||||
element.append = new Proxy(element.append, {
|
||||
apply(orig, _, els) {
|
||||
if (els[0] === root) return orig.apply(element, els);
|
||||
for (const el of els) {
|
||||
const name = (el.slot || "") + mark_e;
|
||||
try {
|
||||
Q(p, "remove", "slot");
|
||||
} catch {
|
||||
elementAttribute(el, "remove", "slot");
|
||||
} catch (_error) {
|
||||
}
|
||||
let l = o[b];
|
||||
if (!l) return;
|
||||
l.name.startsWith(n) || (l.childNodes.forEach((E) => E.remove()), l.name = n + b), l.append(p);
|
||||
const slot = slots[name];
|
||||
if (!slot) return;
|
||||
if (!slot.name.startsWith(mark_s)) {
|
||||
slot.childNodes.forEach((c) => c.remove());
|
||||
slot.name = mark_s + name;
|
||||
}
|
||||
slot.append(el);
|
||||
}
|
||||
return t.append = c, t;
|
||||
element.append = orig;
|
||||
return element;
|
||||
}
|
||||
}), t !== e) {
|
||||
let c = Array.from(t.childNodes);
|
||||
t.append(...c);
|
||||
});
|
||||
if (element !== root) {
|
||||
const els = Array.from(element.childNodes);
|
||||
element.append(...els);
|
||||
}
|
||||
return e;
|
||||
return root;
|
||||
}
|
||||
var N = /* @__PURE__ */ new WeakMap(), { setDeleteAttr: $ } = a;
|
||||
function R(t, ...e) {
|
||||
if (!e.length) return t;
|
||||
N.set(t, H(t, this));
|
||||
for (let [r, n] of Object.entries(Object.assign({}, ...e)))
|
||||
U.call(this, t, r, n);
|
||||
return N.delete(t), t;
|
||||
var assign_context = /* @__PURE__ */ new WeakMap();
|
||||
var { setDeleteAttr: setDeleteAttr2 } = enviroment;
|
||||
function assign(element, ...attributes) {
|
||||
if (!attributes.length) return element;
|
||||
assign_context.set(element, assignContext(element, this));
|
||||
for (const [key, value] of Object.entries(oAssign({}, ...attributes)))
|
||||
assignAttribute.call(this, element, key, value);
|
||||
assign_context.delete(element);
|
||||
return element;
|
||||
}
|
||||
function U(t, e, r) {
|
||||
let { setRemoveAttr: n, s: o } = H(t, this), c = this;
|
||||
r = o.processReactiveAttribute(
|
||||
t,
|
||||
e,
|
||||
r,
|
||||
(f, p) => U.call(c, t, f, p)
|
||||
function assignAttribute(element, key, value) {
|
||||
const { setRemoveAttr, s } = assignContext(element, this);
|
||||
const _this = this;
|
||||
value = s.processReactiveAttribute(
|
||||
element,
|
||||
key,
|
||||
value,
|
||||
(key2, value2) => assignAttribute.call(_this, element, key2, value2)
|
||||
);
|
||||
let [d] = e;
|
||||
if (d === "=") return n(e.slice(1), r);
|
||||
if (d === ".") return F(t, e.slice(1), r);
|
||||
if (/(aria|data)([A-Z])/.test(e))
|
||||
return e = e.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(), n(e, r);
|
||||
switch (e === "className" && (e = "class"), e) {
|
||||
const [k] = key;
|
||||
if ("=" === k) return setRemoveAttr(key.slice(1), value);
|
||||
if ("." === k) return setDelete(element, key.slice(1), value);
|
||||
if (/(aria|data)([A-Z])/.test(key)) {
|
||||
key = key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
||||
return setRemoveAttr(key, value);
|
||||
}
|
||||
if ("className" === key) key = "class";
|
||||
switch (key) {
|
||||
case "xlink:href":
|
||||
return n(e, r, "http://www.w3.org/1999/xlink");
|
||||
return setRemoveAttr(key, value, "http://www.w3.org/1999/xlink");
|
||||
case "textContent":
|
||||
return $(t, e, r);
|
||||
return setDeleteAttr2(element, key, value);
|
||||
case "style":
|
||||
if (typeof r != "object") break;
|
||||
if (typeof value !== "object") break;
|
||||
/* falls through */
|
||||
case "dataset":
|
||||
return M(o, e, t, r, F.bind(null, t[e]));
|
||||
return forEachEntries(s, key, element, value, setDelete.bind(null, element[key]));
|
||||
case "ariaset":
|
||||
return M(o, e, t, r, (f, p) => n("aria-" + f, p));
|
||||
return forEachEntries(s, key, element, value, (key2, val) => setRemoveAttr("aria-" + key2, val));
|
||||
case "classList":
|
||||
return K.call(c, t, r);
|
||||
return classListDeclarative.call(_this, element, value);
|
||||
}
|
||||
return X(t, e) ? $(t, e, r) : n(e, r);
|
||||
return isPropSetter(element, key) ? setDeleteAttr2(element, key, value) : setRemoveAttr(key, value);
|
||||
}
|
||||
function H(t, e) {
|
||||
if (N.has(t)) return N.get(t);
|
||||
let n = (t instanceof a.S ? tt : Y).bind(null, t, "Attribute"), o = S(e);
|
||||
return { setRemoveAttr: n, s: o };
|
||||
function assignContext(element, _this) {
|
||||
if (assign_context.has(element)) return assign_context.get(element);
|
||||
const is_svg = isInstance(element, enviroment.S);
|
||||
const setRemoveAttr = (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
|
||||
const s = signals(_this);
|
||||
return { setRemoveAttr, s };
|
||||
}
|
||||
function K(t, e) {
|
||||
let r = S(this);
|
||||
return M(
|
||||
r,
|
||||
function classListDeclarative(element, toggle) {
|
||||
const s = signals(this);
|
||||
forEachEntries(
|
||||
s,
|
||||
"classList",
|
||||
t,
|
||||
e,
|
||||
(n, o) => t.classList.toggle(n, o === -1 ? void 0 : !!o)
|
||||
), t;
|
||||
element,
|
||||
toggle,
|
||||
(class_name, val) => element.classList.toggle(class_name, val === -1 ? void 0 : Boolean(val))
|
||||
);
|
||||
return element;
|
||||
}
|
||||
function Q(t, e, r, n) {
|
||||
return t instanceof a.H ? t[e + "Attribute"](r, n) : t[e + "AttributeNS"](null, r, n);
|
||||
function elementAttribute(element, op, key, value) {
|
||||
if (isInstance(element, enviroment.H))
|
||||
return element[op + "Attribute"](key, value);
|
||||
return element[op + "AttributeNS"](null, key, value);
|
||||
}
|
||||
function X(t, e) {
|
||||
if (!(e in t)) return !1;
|
||||
let r = z(t, e);
|
||||
return !m(r.set);
|
||||
function isPropSetter(el, key) {
|
||||
if (!(key in el)) return false;
|
||||
const des = getPropDescriptor(el, key);
|
||||
return !isUndef(des.set);
|
||||
}
|
||||
function z(t, e) {
|
||||
if (t = Object.getPrototypeOf(t), !t) return {};
|
||||
let r = Object.getOwnPropertyDescriptor(t, e);
|
||||
return r || z(t, e);
|
||||
function getPropDescriptor(p, key) {
|
||||
p = Object.getPrototypeOf(p);
|
||||
if (!p) return {};
|
||||
const des = Object.getOwnPropertyDescriptor(p, key);
|
||||
if (!des) return getPropDescriptor(p, key);
|
||||
return des;
|
||||
}
|
||||
function M(t, e, r, n, o) {
|
||||
let c = String;
|
||||
if (!(typeof n != "object" || n === null))
|
||||
return Object.entries(n).forEach(function([f, p]) {
|
||||
f && (f = new c(f), f.target = e, p = t.processReactiveAttribute(r, f, p, o), o(f, p));
|
||||
});
|
||||
function forEachEntries(s, target, element, obj, cb) {
|
||||
const S = String;
|
||||
if (typeof obj !== "object" || obj === null) return;
|
||||
return Object.entries(obj).forEach(function process([key, val]) {
|
||||
if (!key) return;
|
||||
key = new S(key);
|
||||
key.target = target;
|
||||
val = s.processReactiveAttribute(element, key, val, cb);
|
||||
cb(key, val);
|
||||
});
|
||||
}
|
||||
function Y(t, e, r, n) {
|
||||
return t[(m(n) ? "remove" : "set") + e](r, n);
|
||||
function setRemove(obj, prop, key, val) {
|
||||
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
|
||||
}
|
||||
function tt(t, e, r, n, o = null) {
|
||||
return t[(m(n) ? "remove" : "set") + e + "NS"](o, r, n);
|
||||
function setRemoveNS(obj, prop, key, val, ns = null) {
|
||||
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
|
||||
}
|
||||
function F(t, e, r) {
|
||||
if (Reflect.set(t, e, r), !!m(r))
|
||||
return Reflect.deleteProperty(t, e);
|
||||
function setDelete(obj, key, val) {
|
||||
Reflect.set(obj, key, val);
|
||||
if (!isUndef(val)) return;
|
||||
return Reflect.deleteProperty(obj, key);
|
||||
}
|
||||
|
||||
// src/events-observer.js
|
||||
var _ = a.M ? et() : new Proxy({}, {
|
||||
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
|
||||
get() {
|
||||
return () => {
|
||||
};
|
||||
}
|
||||
});
|
||||
function et() {
|
||||
let t = /* @__PURE__ */ new Map(), e = !1, r = (s) => function(u) {
|
||||
for (let i of u)
|
||||
if (i.type === "childList") {
|
||||
if (l(i.addedNodes, !0)) {
|
||||
s();
|
||||
continue;
|
||||
}
|
||||
E(i.removedNodes, !0) && s();
|
||||
function connectionsChangesObserverConstructor() {
|
||||
const store = /* @__PURE__ */ new Map();
|
||||
let is_observing = false;
|
||||
const observerListener = (stop2) => function(mutations) {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type !== "childList") continue;
|
||||
if (observerAdded(mutation.addedNodes, true)) {
|
||||
stop2();
|
||||
continue;
|
||||
}
|
||||
}, n = new a.M(r(f));
|
||||
return {
|
||||
observe(s) {
|
||||
let u = new a.M(r(() => {
|
||||
}));
|
||||
return u.observe(s, { childList: !0, subtree: !0 }), () => u.disconnect();
|
||||
},
|
||||
onConnected(s, u) {
|
||||
d();
|
||||
let i = c(s);
|
||||
i.connected.has(u) || (i.connected.add(u), i.length_c += 1);
|
||||
},
|
||||
offConnected(s, u) {
|
||||
if (!t.has(s)) return;
|
||||
let i = t.get(s);
|
||||
i.connected.has(u) && (i.connected.delete(u), i.length_c -= 1, o(s, i));
|
||||
},
|
||||
onDisconnected(s, u) {
|
||||
d();
|
||||
let i = c(s);
|
||||
i.disconnected.has(u) || (i.disconnected.add(u), i.length_d += 1);
|
||||
},
|
||||
offDisconnected(s, u) {
|
||||
if (!t.has(s)) return;
|
||||
let i = t.get(s);
|
||||
i.disconnected.has(u) && (i.disconnected.delete(u), i.length_d -= 1, o(s, i));
|
||||
if (observerRemoved(mutation.removedNodes, true))
|
||||
stop2();
|
||||
}
|
||||
};
|
||||
function o(s, u) {
|
||||
u.length_c || u.length_d || (t.delete(s), f());
|
||||
const observer = new enviroment.M(observerListener(stop));
|
||||
return {
|
||||
/**
|
||||
* Creates an observer for a specific element
|
||||
* @param {Element} element - Element to observe
|
||||
* @returns {Function} Cleanup function
|
||||
*/
|
||||
observe(element) {
|
||||
const o = new enviroment.M(observerListener(() => {
|
||||
}));
|
||||
o.observe(element, { childList: true, subtree: true });
|
||||
return () => o.disconnect();
|
||||
},
|
||||
/**
|
||||
* Register a connection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for connection event
|
||||
*/
|
||||
onConnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.connected.has(listener)) return;
|
||||
listeners.connected.add(listener);
|
||||
listeners.length_c += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a connection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offConnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
if (!ls.connected.has(listener)) return;
|
||||
ls.connected.delete(listener);
|
||||
ls.length_c -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
},
|
||||
/**
|
||||
* Register a disconnection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for disconnection event
|
||||
*/
|
||||
onDisconnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.disconnected.has(listener)) return;
|
||||
listeners.disconnected.add(listener);
|
||||
listeners.length_d += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a disconnection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offDisconnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
ls.disconnected.delete(listener);
|
||||
ls.length_d -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
}
|
||||
};
|
||||
function cleanWhenOff(element, ls) {
|
||||
if (ls.length_c || ls.length_d)
|
||||
return;
|
||||
store.delete(element);
|
||||
stop();
|
||||
}
|
||||
function c(s) {
|
||||
if (t.has(s)) return t.get(s);
|
||||
let u = {
|
||||
function getElementStore(element) {
|
||||
if (store.has(element)) return store.get(element);
|
||||
const out = {
|
||||
connected: /* @__PURE__ */ new WeakSet(),
|
||||
length_c: 0,
|
||||
disconnected: /* @__PURE__ */ new WeakSet(),
|
||||
length_d: 0
|
||||
};
|
||||
return t.set(s, u), u;
|
||||
store.set(element, out);
|
||||
return out;
|
||||
}
|
||||
function d() {
|
||||
e || (e = !0, n.observe(a.D.body, { childList: !0, subtree: !0 }));
|
||||
function start() {
|
||||
if (is_observing) return;
|
||||
is_observing = true;
|
||||
observer.observe(enviroment.D.body, { childList: true, subtree: true });
|
||||
}
|
||||
function f() {
|
||||
!e || t.size || (e = !1, n.disconnect());
|
||||
function stop() {
|
||||
if (!is_observing || store.size) return;
|
||||
is_observing = false;
|
||||
observer.disconnect();
|
||||
}
|
||||
function p() {
|
||||
return new Promise(function(s) {
|
||||
(requestIdleCallback || requestAnimationFrame)(s);
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
async function b(s) {
|
||||
t.size > 30 && await p();
|
||||
let u = [];
|
||||
if (!(s instanceof Node)) return u;
|
||||
for (let i of t.keys())
|
||||
i === s || !(i instanceof Node) || s.contains(i) && u.push(i);
|
||||
return u;
|
||||
}
|
||||
function l(s, u) {
|
||||
let i = !1;
|
||||
for (let h of s) {
|
||||
if (u && b(h).then(l), !t.has(h)) continue;
|
||||
let A = t.get(h);
|
||||
A.length_c && (h.dispatchEvent(new Event(v)), A.connected = /* @__PURE__ */ new WeakSet(), A.length_c = 0, A.length_d || t.delete(h), i = !0);
|
||||
async function collectChildren(element) {
|
||||
if (store.size > 30)
|
||||
await requestIdle();
|
||||
const out = [];
|
||||
if (!isInstance(element, Node)) return out;
|
||||
for (const el of store.keys()) {
|
||||
if (el === element || !isInstance(el, Node)) continue;
|
||||
if (element.contains(el))
|
||||
out.push(el);
|
||||
}
|
||||
return i;
|
||||
return out;
|
||||
}
|
||||
function E(s, u) {
|
||||
let i = !1;
|
||||
for (let h of s)
|
||||
u && b(h).then(E), !(!t.has(h) || !t.get(h).length_d) && ((globalThis.queueMicrotask || setTimeout)(I(h)), i = !0);
|
||||
return i;
|
||||
function observerAdded(addedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of addedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerAdded);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_c) continue;
|
||||
element.dispatchEvent(new Event(evc));
|
||||
ls.connected = /* @__PURE__ */ new WeakSet();
|
||||
ls.length_c = 0;
|
||||
if (!ls.length_d) store.delete(element);
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function I(s) {
|
||||
function observerRemoved(removedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of removedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerRemoved);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_d) continue;
|
||||
(globalThis.queueMicrotask || setTimeout)(dispatchRemove(element));
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function dispatchRemove(element) {
|
||||
return () => {
|
||||
s.isConnected || (s.dispatchEvent(new Event(w)), t.delete(s));
|
||||
if (element.isConnected) return;
|
||||
element.dispatchEvent(new Event(evd));
|
||||
store.delete(element);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// src/customElement.js
|
||||
function wt(t, e, r = rt) {
|
||||
let n = t.host || t;
|
||||
O.push({
|
||||
scope: n,
|
||||
host: (...d) => d.length ? d.forEach((f) => f(n)) : n
|
||||
}), typeof r == "function" && (r = r.call(n, n));
|
||||
let o = n[x];
|
||||
o || nt(n);
|
||||
let c = e.call(n, r);
|
||||
return o || n.dispatchEvent(new Event(v)), t.nodeType === 11 && typeof t.mode == "string" && n.addEventListener(w, _.observe(t), { once: !0 }), O.pop(), t.append(c);
|
||||
function customElementRender(target, render, props = observedAttributes2) {
|
||||
const custom_element = target.host || target;
|
||||
scope.push({
|
||||
scope: custom_element,
|
||||
host: (...c) => c.length ? c.forEach((c2) => c2(custom_element)) : custom_element
|
||||
});
|
||||
if (typeof props === "function") props = props.call(custom_element, custom_element);
|
||||
const is_lte = custom_element[keyLTE];
|
||||
if (!is_lte) lifecyclesToEvents(custom_element);
|
||||
const out = render.call(custom_element, props);
|
||||
if (!is_lte) custom_element.dispatchEvent(new Event(evc));
|
||||
if (target.nodeType === 11 && typeof target.mode === "string")
|
||||
custom_element.addEventListener(evd, c_ch_o.observe(target), { once: true });
|
||||
scope.pop();
|
||||
return target.append(out);
|
||||
}
|
||||
function nt(t) {
|
||||
return j(t.prototype, "connectedCallback", function(e, r, n) {
|
||||
e.apply(r, n), r.dispatchEvent(new Event(v));
|
||||
}), j(t.prototype, "disconnectedCallback", function(e, r, n) {
|
||||
e.apply(r, n), (globalThis.queueMicrotask || setTimeout)(
|
||||
() => !r.isConnected && r.dispatchEvent(new Event(w))
|
||||
function lifecyclesToEvents(class_declaration) {
|
||||
wrapMethod(class_declaration.prototype, "connectedCallback", function(target, thisArg, detail) {
|
||||
target.apply(thisArg, detail);
|
||||
thisArg.dispatchEvent(new Event(evc));
|
||||
});
|
||||
wrapMethod(class_declaration.prototype, "disconnectedCallback", function(target, thisArg, detail) {
|
||||
target.apply(thisArg, detail);
|
||||
(globalThis.queueMicrotask || setTimeout)(
|
||||
() => !thisArg.isConnected && thisArg.dispatchEvent(new Event(evd))
|
||||
);
|
||||
}), j(t.prototype, "attributeChangedCallback", function(e, r, n) {
|
||||
let [o, , c] = n;
|
||||
r.dispatchEvent(new CustomEvent(y, {
|
||||
detail: [o, c]
|
||||
})), e.apply(r, n);
|
||||
}), t.prototype[x] = !0, t;
|
||||
});
|
||||
wrapMethod(class_declaration.prototype, "attributeChangedCallback", function(target, thisArg, detail) {
|
||||
const [attribute, , value] = detail;
|
||||
thisArg.dispatchEvent(new CustomEvent(eva, {
|
||||
detail: [attribute, value]
|
||||
}));
|
||||
target.apply(thisArg, detail);
|
||||
});
|
||||
class_declaration.prototype[keyLTE] = true;
|
||||
return class_declaration;
|
||||
}
|
||||
function j(t, e, r) {
|
||||
t[e] = new Proxy(t[e] || (() => {
|
||||
}), { apply: r });
|
||||
function wrapMethod(obj, method, apply) {
|
||||
obj[method] = new Proxy(obj[method] || (() => {
|
||||
}), { apply });
|
||||
}
|
||||
function rt(t) {
|
||||
return q(t, (e, r) => e.getAttribute(r));
|
||||
function observedAttributes2(instance) {
|
||||
return observedAttributes(instance, (i, n) => i.getAttribute(n));
|
||||
}
|
||||
|
||||
// src/events.js
|
||||
function yt(t, e, r) {
|
||||
return e || (e = {}), function(o, ...c) {
|
||||
r && (c.unshift(o), o = typeof r == "function" ? r() : r);
|
||||
let d = c.length ? new CustomEvent(t, Object.assign({ detail: c[0] }, e)) : new Event(t, e);
|
||||
return o.dispatchEvent(d);
|
||||
function dispatchEvent(name, options, host) {
|
||||
if (!options) options = {};
|
||||
return function dispatch(element, ...d) {
|
||||
if (host) {
|
||||
d.unshift(element);
|
||||
element = typeof host === "function" ? host() : host;
|
||||
}
|
||||
const event = d.length ? new CustomEvent(name, oAssign({ detail: d[0] }, options)) : new Event(name, options);
|
||||
return element.dispatchEvent(event);
|
||||
};
|
||||
}
|
||||
function D(t, e, r) {
|
||||
return function(o) {
|
||||
return o.addEventListener(t, e, r), o;
|
||||
function on(event, listener, options) {
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(event, listener, options);
|
||||
return element;
|
||||
};
|
||||
}
|
||||
var B = (t) => Object.assign({}, typeof t == "object" ? t : null, { once: !0 });
|
||||
D.connected = function(t, e) {
|
||||
return e = B(e), function(n) {
|
||||
return n.addEventListener(v, t, e), n[x] ? n : n.isConnected ? (n.dispatchEvent(new Event(v)), n) : (L(e.signal, () => _.offConnected(n, t)) && _.onConnected(n, t), n);
|
||||
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
|
||||
on.connected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evc, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
if (element.isConnected) return element.dispatchEvent(new Event(evc)), element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offConnected(element, listener));
|
||||
if (c) c_ch_o.onConnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
D.disconnected = function(t, e) {
|
||||
return e = B(e), function(n) {
|
||||
return n.addEventListener(w, t, e), n[x] || L(e.signal, () => _.offDisconnected(n, t)) && _.onDisconnected(n, t), n;
|
||||
on.disconnected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evd, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offDisconnected(element, listener));
|
||||
if (c) c_ch_o.onDisconnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
var W = /* @__PURE__ */ new WeakMap();
|
||||
D.disconnectedAsAbort = function(t) {
|
||||
if (W.has(t)) return W.get(t);
|
||||
let e = new AbortController();
|
||||
return W.set(t, e), t(D.disconnected(() => e.abort())), e;
|
||||
var store_abort = /* @__PURE__ */ new WeakMap();
|
||||
on.disconnectedAsAbort = function(host) {
|
||||
if (store_abort.has(host)) return store_abort.get(host);
|
||||
const a = new AbortController();
|
||||
store_abort.set(host, a);
|
||||
host(on.disconnected(() => a.abort()));
|
||||
return a;
|
||||
};
|
||||
var ot = /* @__PURE__ */ new WeakSet();
|
||||
D.attributeChanged = function(t, e) {
|
||||
return typeof e != "object" && (e = {}), function(n) {
|
||||
if (n.addEventListener(y, t, e), n[x] || ot.has(n) || !a.M) return n;
|
||||
let o = new a.M(function(d) {
|
||||
for (let { attributeName: f, target: p } of d)
|
||||
p.dispatchEvent(
|
||||
new CustomEvent(y, { detail: [f, p.getAttribute(f)] })
|
||||
var els_attribute_store = /* @__PURE__ */ new WeakSet();
|
||||
on.attributeChanged = function(listener, options) {
|
||||
if (typeof options !== "object")
|
||||
options = {};
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(eva, listener, options);
|
||||
if (element[keyLTE] || els_attribute_store.has(element))
|
||||
return element;
|
||||
if (!enviroment.M) return element;
|
||||
const observer = new enviroment.M(function(mutations) {
|
||||
for (const { attributeName, target } of mutations)
|
||||
target.dispatchEvent(
|
||||
new CustomEvent(eva, { detail: [attributeName, target.getAttribute(attributeName)] })
|
||||
);
|
||||
});
|
||||
return L(e.signal, () => o.disconnect()) && o.observe(n, { attributes: !0 }), n;
|
||||
const c = onAbort(options.signal, () => observer.disconnect());
|
||||
if (c) observer.observe(element, { attributes: true });
|
||||
return element;
|
||||
};
|
||||
};
|
||||
|
||||
globalThis.dde= {
|
||||
assign: R,
|
||||
assignAttribute: U,
|
||||
chainableAppend: J,
|
||||
classListDeclarative: K,
|
||||
createElement: P,
|
||||
createElementNS: pt,
|
||||
customElementRender: wt,
|
||||
customElementWithDDE: nt,
|
||||
dispatchEvent: yt,
|
||||
el: P,
|
||||
elNS: pt,
|
||||
elementAttribute: Q,
|
||||
lifecyclesToEvents: nt,
|
||||
observedAttributes: rt,
|
||||
on: D,
|
||||
queue: dt,
|
||||
registerReactivity: Z,
|
||||
scope: O,
|
||||
simulateSlots: lt
|
||||
assign,
|
||||
assignAttribute,
|
||||
chainableAppend,
|
||||
classListDeclarative,
|
||||
createElement,
|
||||
createElementNS,
|
||||
customElementRender,
|
||||
customElementWithDDE: lifecyclesToEvents,
|
||||
dispatchEvent,
|
||||
el: createElement,
|
||||
elNS: createElementNS,
|
||||
elementAttribute,
|
||||
lifecyclesToEvents,
|
||||
observedAttributes: observedAttributes2,
|
||||
on,
|
||||
queue,
|
||||
registerReactivity,
|
||||
scope,
|
||||
simulateSlots
|
||||
};
|
||||
|
||||
})();
|
25
dist/dde.min.js
vendored
Normal file
25
dist/dde.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
593
dist/esm-with-signals.d.min.ts
vendored
Normal file
593
dist/esm-with-signals.d.min.ts
vendored
Normal file
@ -0,0 +1,593 @@
|
||||
declare global{ /* ddeSignal */ }
|
||||
type CustomElementTagNameMap= { '#text': Text, '#comment': Comment }
|
||||
type SupportedElement=
|
||||
HTMLElementTagNameMap[keyof HTMLElementTagNameMap]
|
||||
| SVGElementTagNameMap[keyof SVGElementTagNameMap]
|
||||
| MathMLElementTagNameMap[keyof MathMLElementTagNameMap]
|
||||
| CustomElementTagNameMap[keyof CustomElementTagNameMap]
|
||||
declare global {
|
||||
type ddeComponentAttributes= Record<any, any> | undefined;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node>= (element: El)=> any;
|
||||
type ddeString= string | ddeSignal<string>
|
||||
type ddeStringable= ddeString | number | ddeSignal<number>
|
||||
}
|
||||
type PascalCase=
|
||||
`${Capitalize<string>}${string}`;
|
||||
type AttrsModified= {
|
||||
/**
|
||||
* Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API).
|
||||
*/
|
||||
style: Partial<CSSStyleDeclaration> | ddeString
|
||||
| Partial<{ [K in keyof CSSStyleDeclaration]: ddeSignal<CSSStyleDeclaration[K]> }>
|
||||
/**
|
||||
* Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1.
|
||||
* In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))`
|
||||
* for others.
|
||||
*/
|
||||
classList: Record<string,-1|0|1|boolean|ddeSignal<-1|0|1|boolean>>,
|
||||
/**
|
||||
* Used by the dataset HTML attribute to represent data for custom attributes added to elements.
|
||||
* Values are converted to string (see {@link DOMStringMap}).
|
||||
*
|
||||
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMStringMap)
|
||||
* */
|
||||
dataset: Record<string, ddeStringable>,
|
||||
/**
|
||||
* Sets `aria-*` simiraly to `dataset`
|
||||
* */
|
||||
ariaset: Record<string, ddeString>,
|
||||
} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, ddeString>
|
||||
& Record<`.${string}`, any>
|
||||
type _fromElsInterfaces<EL extends SupportedElement>= Omit<EL, keyof AttrsModified>;
|
||||
type IsReadonly<T, K extends keyof T> =
|
||||
T extends { readonly [P in K]: T[K] } ? true : false;
|
||||
/**
|
||||
* Just element attributtes
|
||||
*
|
||||
* In most cases, you can use native propertie such as
|
||||
* [MDN WEB/API/Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) and so on
|
||||
* (e.g. [`Text`](https://developer.mozilla.org/en-US/docs/Web/API/Text)).
|
||||
*
|
||||
* There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives.
|
||||
* @private
|
||||
*/
|
||||
type ElementAttributes<T extends SupportedElement>= Partial<{
|
||||
[K in keyof _fromElsInterfaces<T>]:
|
||||
_fromElsInterfaces<T>[K] extends ((...p: any[])=> any)
|
||||
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=> ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
|
||||
: (IsReadonly<_fromElsInterfaces<T>, K> extends false
|
||||
? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
|
||||
: ddeStringable)
|
||||
} & AttrsModified> & Record<string, any>;
|
||||
export function classListDeclarative<El extends SupportedElement>(
|
||||
element: El,
|
||||
classList: AttrsModified["classList"]
|
||||
): El
|
||||
export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El
|
||||
export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(
|
||||
element: El,
|
||||
attr: ATT,
|
||||
value: ElementAttributes<El>[ATT]
|
||||
): ElementAttributes<El>[ATT]
|
||||
|
||||
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
|
||||
export function el<
|
||||
TAG extends keyof ExtendedHTMLElementTagNameMap,
|
||||
>(
|
||||
tag_name: TAG,
|
||||
attrs?: ElementAttributes<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]> | ddeStringable,
|
||||
...addons: ddeElementAddon<
|
||||
ExtendedHTMLElementTagNameMap[NoInfer<TAG>]
|
||||
>[], // TODO: for now addons must have the same element
|
||||
): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement
|
||||
export function el(
|
||||
tag_name?: "<>",
|
||||
): ddeDocumentFragment
|
||||
export function el(
|
||||
tag_name: string,
|
||||
attrs?: ElementAttributes<HTMLElement> | ddeStringable,
|
||||
...addons: ddeElementAddon<HTMLElement>[]
|
||||
): ddeHTMLElement
|
||||
|
||||
export function el<
|
||||
C extends (attr: ddeComponentAttributes)=> SupportedElement | ddeDocumentFragment
|
||||
>(
|
||||
component: C,
|
||||
attrs?: Parameters<C>[0] | ddeStringable,
|
||||
...addons: ddeElementAddon<ReturnType<C>>[]
|
||||
): ReturnType<C> extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
|
||||
? ReturnType<C>
|
||||
: ( ReturnType<C> extends ddeDocumentFragment ? ReturnType<C> : ddeHTMLElement )
|
||||
export { el as createElement }
|
||||
|
||||
export function elNS(
|
||||
namespace: "http://www.w3.org/2000/svg"
|
||||
): <
|
||||
TAG extends keyof SVGElementTagNameMap & string,
|
||||
EL extends ( TAG extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[TAG] : SVGElement ),
|
||||
>(
|
||||
tag_name: TAG,
|
||||
attrs?: ElementAttributes<NoInfer<EL>> | ddeStringable,
|
||||
...addons: ddeElementAddon<NoInfer<EL>>[]
|
||||
)=> TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement
|
||||
export function elNS(
|
||||
namespace: "http://www.w3.org/1998/Math/MathML"
|
||||
): <
|
||||
TAG extends keyof MathMLElementTagNameMap & string,
|
||||
EL extends ( TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement ),
|
||||
>(
|
||||
tag_name: TAG,
|
||||
attrs?: ddeStringable | Partial<{
|
||||
[key in keyof EL]: EL[key] | ddeSignal<EL[key]> | string | number | boolean
|
||||
}>,
|
||||
...addons: ddeElementAddon<NoInfer<EL>>[]
|
||||
)=> ddeMathMLElement
|
||||
export function elNS(
|
||||
namespace: string
|
||||
): (
|
||||
tag_name: string,
|
||||
attrs?: string | ddeStringable | Record<string, any>,
|
||||
...addons: ddeElementAddon<SupportedElement>[]
|
||||
)=> SupportedElement
|
||||
export { elNS as createElementNS }
|
||||
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL;
|
||||
/** Simulate slots for ddeComponents */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
|
||||
root: EL,
|
||||
): EL
|
||||
/**
|
||||
* Simulate slots in Custom Elements without using `shadowRoot`.
|
||||
* @param el Custom Element root element
|
||||
* @param body Body of the custom element
|
||||
* */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
|
||||
el: HTMLElement,
|
||||
body: EL,
|
||||
): EL
|
||||
|
||||
export function dispatchEvent(name: keyof DocumentEventMap | string, options?: EventInit):
|
||||
(element: SupportedElement, data?: any)=> void;
|
||||
export function dispatchEvent(
|
||||
name: keyof DocumentEventMap | string,
|
||||
options: EventInit | null,
|
||||
element: SupportedElement | (()=> SupportedElement)
|
||||
): (data?: any)=> void;
|
||||
interface On{
|
||||
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
||||
<
|
||||
Event extends keyof DocumentEventMap,
|
||||
EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>,
|
||||
>(
|
||||
type: Event,
|
||||
listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any,
|
||||
options?: AddEventListenerOptions
|
||||
) : EE;
|
||||
<
|
||||
EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>,
|
||||
>(
|
||||
type: string,
|
||||
listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent ) => any,
|
||||
options?: AddEventListenerOptions
|
||||
) : EE;
|
||||
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
|
||||
connected<
|
||||
EE extends ddeElementAddon<SupportedElement>,
|
||||
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
|
||||
>(
|
||||
listener: (this: El, event: CustomEvent<El>) => any,
|
||||
options?: AddEventListenerOptions
|
||||
) : EE;
|
||||
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
|
||||
disconnected<
|
||||
EE extends ddeElementAddon<SupportedElement>,
|
||||
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
|
||||
>(
|
||||
listener: (this: El, event: CustomEvent<void>) => any,
|
||||
options?: AddEventListenerOptions
|
||||
) : EE;
|
||||
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
|
||||
attributeChanged<
|
||||
EE extends ddeElementAddon<SupportedElement>,
|
||||
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
|
||||
>(
|
||||
listener: (this: El, event: CustomEvent<[ string, string ]>) => any,
|
||||
options?: AddEventListenerOptions
|
||||
) : EE;
|
||||
}
|
||||
export const on: On;
|
||||
|
||||
type Scope= {
|
||||
scope: Node | Function | Object,
|
||||
host: ddeElementAddon<any>,
|
||||
custom_element: false | HTMLElement,
|
||||
prevent: boolean
|
||||
};
|
||||
/** Current scope created last time the `el(Function)` was invoke. (Or {@link scope.push}) */
|
||||
export const scope: {
|
||||
current: Scope,
|
||||
/** Stops all automatizations. E. g. signals used as attributes in current scope
|
||||
* registers removing these listeners (and clean signal if no other listeners are detected)
|
||||
* on `disconnected` event. */
|
||||
preventDefault<T extends boolean>(prevent: T): T,
|
||||
/**
|
||||
* This represents reference to the current host element — `scope.host()`.
|
||||
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||
* — `scope.host(on.connected(console.log))`.
|
||||
* */
|
||||
host: (...addons: ddeElementAddon<SupportedElement>[])=> HTMLElement,
|
||||
|
||||
state: Scope[],
|
||||
/** Adds new child scope. All attributes are inherited by default. */
|
||||
push(scope: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
|
||||
/** Adds root scope as a child of the current scope. */
|
||||
pushRoot(): ReturnType<Array<Scope>["push"]>,
|
||||
/** Removes last/current child scope. */
|
||||
pop(): ReturnType<Array<Scope>["pop"]>,
|
||||
};
|
||||
|
||||
export function customElementRender<
|
||||
EL extends HTMLElement,
|
||||
P extends any = Record<string, string | ddeSignal<string>>
|
||||
>(
|
||||
target: ShadowRoot | EL,
|
||||
render: (props: P)=> SupportedElement | DocumentFragment,
|
||||
props?: P | ((el: EL)=> P)
|
||||
): EL
|
||||
export function customElementWithDDE<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL
|
||||
export function lifecyclesToEvents<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL
|
||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>
|
||||
|
||||
/* TypeScript MEH */
|
||||
declare global{
|
||||
type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
|
||||
|
||||
interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; }
|
||||
interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; }
|
||||
interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; }
|
||||
interface ddeMathMLElement extends MathMLElement{ append: ddeAppend<ddeMathMLElement>; }
|
||||
|
||||
interface ddeHTMLElementTagNameMap {
|
||||
"a": ddeHTMLAnchorElement;
|
||||
"area": ddeHTMLAreaElement;
|
||||
"audio": ddeHTMLAudioElement;
|
||||
"base": ddeHTMLBaseElement;
|
||||
"blockquote": ddeHTMLQuoteElement;
|
||||
"body": ddeHTMLBodyElement;
|
||||
"br": ddeHTMLBRElement;
|
||||
"button": ddeHTMLButtonElement;
|
||||
"canvas": ddeHTMLCanvasElement;
|
||||
"caption": ddeHTMLTableCaptionElement;
|
||||
"col": ddeHTMLTableColElement;
|
||||
"colgroup": ddeHTMLTableColElement;
|
||||
"data": ddeHTMLDataElement;
|
||||
"datalist": ddeHTMLDataListElement;
|
||||
"del": ddeHTMLModElement;
|
||||
"details": ddeHTMLDetailsElement;
|
||||
"dialog": ddeHTMLDialogElement;
|
||||
"div": ddeHTMLDivElement;
|
||||
"dl": ddeHTMLDListElement;
|
||||
"embed": ddeHTMLEmbedElement;
|
||||
"fieldset": ddeHTMLFieldSetElement;
|
||||
"form": ddeHTMLFormElement;
|
||||
"h1": ddeHTMLHeadingElement;
|
||||
"h2": ddeHTMLHeadingElement;
|
||||
"h3": ddeHTMLHeadingElement;
|
||||
"h4": ddeHTMLHeadingElement;
|
||||
"h5": ddeHTMLHeadingElement;
|
||||
"h6": ddeHTMLHeadingElement;
|
||||
"head": ddeHTMLHeadElement;
|
||||
"hr": ddeHTMLHRElement;
|
||||
"html": ddeHTMLHtmlElement;
|
||||
"iframe": ddeHTMLIFrameElement;
|
||||
"img": ddeHTMLImageElement;
|
||||
"input": ddeHTMLInputElement;
|
||||
"ins": ddeHTMLModElement;
|
||||
"label": ddeHTMLLabelElement;
|
||||
"legend": ddeHTMLLegendElement;
|
||||
"li": ddeHTMLLIElement;
|
||||
"link": ddeHTMLLinkElement;
|
||||
"map": ddeHTMLMapElement;
|
||||
"menu": ddeHTMLMenuElement;
|
||||
"meta": ddeHTMLMetaElement;
|
||||
"meter": ddeHTMLMeterElement;
|
||||
"object": ddeHTMLObjectElement;
|
||||
"ol": ddeHTMLOListElement;
|
||||
"optgroup": ddeHTMLOptGroupElement;
|
||||
"option": ddeHTMLOptionElement;
|
||||
"output": ddeHTMLOutputElement;
|
||||
"p": ddeHTMLParagraphElement;
|
||||
"picture": ddeHTMLPictureElement;
|
||||
"pre": ddeHTMLPreElement;
|
||||
"progress": ddeHTMLProgressElement;
|
||||
"q": ddeHTMLQuoteElement;
|
||||
"script": ddeHTMLScriptElement;
|
||||
"select": ddeHTMLSelectElement;
|
||||
"slot": ddeHTMLSlotElement;
|
||||
"source": ddeHTMLSourceElement;
|
||||
"span": ddeHTMLSpanElement;
|
||||
"style": ddeHTMLStyleElement;
|
||||
"table": ddeHTMLTableElement;
|
||||
"tbody": ddeHTMLTableSectionElement;
|
||||
"td": ddeHTMLTableCellElement;
|
||||
"template": ddeHTMLTemplateElement;
|
||||
"textarea": ddeHTMLTextAreaElement;
|
||||
"tfoot": ddeHTMLTableSectionElement;
|
||||
"th": ddeHTMLTableCellElement;
|
||||
"thead": ddeHTMLTableSectionElement;
|
||||
"time": ddeHTMLTimeElement;
|
||||
"title": ddeHTMLTitleElement;
|
||||
"tr": ddeHTMLTableRowElement;
|
||||
"track": ddeHTMLTrackElement;
|
||||
"ul": ddeHTMLUListElement;
|
||||
"video": ddeHTMLVideoElement;
|
||||
}
|
||||
interface ddeSVGElementTagNameMap {
|
||||
"a": ddeSVGAElement;
|
||||
"animate": ddeSVGAnimateElement;
|
||||
"animateMotion": ddeSVGAnimateMotionElement;
|
||||
"animateTransform": ddeSVGAnimateTransformElement;
|
||||
"circle": ddeSVGCircleElement;
|
||||
"clipPath": ddeSVGClipPathElement;
|
||||
"defs": ddeSVGDefsElement;
|
||||
"desc": ddeSVGDescElement;
|
||||
"ellipse": ddeSVGEllipseElement;
|
||||
"feBlend": ddeSVGFEBlendElement;
|
||||
"feColorMatrix": ddeSVGFEColorMatrixElement;
|
||||
"feComponentTransfer": ddeSVGFEComponentTransferElement;
|
||||
"feComposite": ddeSVGFECompositeElement;
|
||||
"feConvolveMatrix": ddeSVGFEConvolveMatrixElement;
|
||||
"feDiffuseLighting": ddeSVGFEDiffuseLightingElement;
|
||||
"feDisplacementMap": ddeSVGFEDisplacementMapElement;
|
||||
"feDistantLight": ddeSVGFEDistantLightElement;
|
||||
"feDropShadow": ddeSVGFEDropShadowElement;
|
||||
"feFlood": ddeSVGFEFloodElement;
|
||||
"feFuncA": ddeSVGFEFuncAElement;
|
||||
"feFuncB": ddeSVGFEFuncBElement;
|
||||
"feFuncG": ddeSVGFEFuncGElement;
|
||||
"feFuncR": ddeSVGFEFuncRElement;
|
||||
"feGaussianBlur": ddeSVGFEGaussianBlurElement;
|
||||
"feImage": ddeSVGFEImageElement;
|
||||
"feMerge": ddeSVGFEMergeElement;
|
||||
"feMergeNode": ddeSVGFEMergeNodeElement;
|
||||
"feMorphology": ddeSVGFEMorphologyElement;
|
||||
"feOffset": ddeSVGFEOffsetElement;
|
||||
"fePointLight": ddeSVGFEPointLightElement;
|
||||
"feSpecularLighting": ddeSVGFESpecularLightingElement;
|
||||
"feSpotLight": ddeSVGFESpotLightElement;
|
||||
"feTile": ddeSVGFETileElement;
|
||||
"feTurbulence": ddeSVGFETurbulenceElement;
|
||||
"filter": ddeSVGFilterElement;
|
||||
"foreignObject": ddeSVGForeignObjectElement;
|
||||
"g": ddeSVGGElement;
|
||||
"image": ddeSVGImageElement;
|
||||
"line": ddeSVGLineElement;
|
||||
"linearGradient": ddeSVGLinearGradientElement;
|
||||
"marker": ddeSVGMarkerElement;
|
||||
"mask": ddeSVGMaskElement;
|
||||
"metadata": ddeSVGMetadataElement;
|
||||
"mpath": ddeSVGMPathElement;
|
||||
"path": ddeSVGPathElement;
|
||||
"pattern": ddeSVGPatternElement;
|
||||
"polygon": ddeSVGPolygonElement;
|
||||
"polyline": ddeSVGPolylineElement;
|
||||
"radialGradient": ddeSVGRadialGradientElement;
|
||||
"rect": ddeSVGRectElement;
|
||||
"script": ddeSVGScriptElement;
|
||||
"set": ddeSVGSetElement;
|
||||
"stop": ddeSVGStopElement;
|
||||
"style": ddeSVGStyleElement;
|
||||
"svg": ddeSVGSVGElement;
|
||||
"switch": ddeSVGSwitchElement;
|
||||
"symbol": ddeSVGSymbolElement;
|
||||
"text": ddeSVGTextElement;
|
||||
"textPath": ddeSVGTextPathElement;
|
||||
"title": ddeSVGTitleElement;
|
||||
"tspan": ddeSVGTSpanElement;
|
||||
"use": ddeSVGUseElement;
|
||||
"view": ddeSVGViewElement;
|
||||
}
|
||||
}
|
||||
|
||||
// editorconfig-checker-disable
|
||||
interface ddeHTMLAnchorElement extends HTMLAnchorElement{ append: ddeAppend<ddeHTMLAnchorElement>; }
|
||||
interface ddeHTMLAreaElement extends HTMLAreaElement{ append: ddeAppend<ddeHTMLAreaElement>; }
|
||||
interface ddeHTMLAudioElement extends HTMLAudioElement{ append: ddeAppend<ddeHTMLAudioElement>; }
|
||||
interface ddeHTMLBaseElement extends HTMLBaseElement{ append: ddeAppend<ddeHTMLBaseElement>; }
|
||||
interface ddeHTMLQuoteElement extends HTMLQuoteElement{ append: ddeAppend<ddeHTMLQuoteElement>; }
|
||||
interface ddeHTMLBodyElement extends HTMLBodyElement{ append: ddeAppend<ddeHTMLBodyElement>; }
|
||||
interface ddeHTMLBRElement extends HTMLBRElement{ append: ddeAppend<ddeHTMLBRElement>; }
|
||||
interface ddeHTMLButtonElement extends HTMLButtonElement{ append: ddeAppend<ddeHTMLButtonElement>; }
|
||||
interface ddeHTMLCanvasElement extends HTMLCanvasElement{ append: ddeAppend<ddeHTMLCanvasElement>; }
|
||||
interface ddeHTMLTableCaptionElement extends HTMLTableCaptionElement{ append: ddeAppend<ddeHTMLTableCaptionElement>; }
|
||||
interface ddeHTMLTableColElement extends HTMLTableColElement{ append: ddeAppend<ddeHTMLTableColElement>; }
|
||||
interface ddeHTMLTableColElement extends HTMLTableColElement{ append: ddeAppend<ddeHTMLTableColElement>; }
|
||||
interface ddeHTMLDataElement extends HTMLDataElement{ append: ddeAppend<ddeHTMLDataElement>; }
|
||||
interface ddeHTMLDataListElement extends HTMLDataListElement{ append: ddeAppend<ddeHTMLDataListElement>; }
|
||||
interface ddeHTMLModElement extends HTMLModElement{ append: ddeAppend<ddeHTMLModElement>; }
|
||||
interface ddeHTMLDetailsElement extends HTMLDetailsElement{ append: ddeAppend<ddeHTMLDetailsElement>; }
|
||||
interface ddeHTMLDialogElement extends HTMLDialogElement{ append: ddeAppend<ddeHTMLDialogElement>; }
|
||||
interface ddeHTMLDivElement extends HTMLDivElement{ append: ddeAppend<ddeHTMLDivElement>; }
|
||||
interface ddeHTMLDListElement extends HTMLDListElement{ append: ddeAppend<ddeHTMLDListElement>; }
|
||||
interface ddeHTMLEmbedElement extends HTMLEmbedElement{ append: ddeAppend<ddeHTMLEmbedElement>; }
|
||||
interface ddeHTMLFieldSetElement extends HTMLFieldSetElement{ append: ddeAppend<ddeHTMLFieldSetElement>; }
|
||||
interface ddeHTMLFormElement extends HTMLFormElement{ append: ddeAppend<ddeHTMLFormElement>; }
|
||||
interface ddeHTMLHeadingElement extends HTMLHeadingElement{ append: ddeAppend<ddeHTMLHeadingElement>; }
|
||||
interface ddeHTMLHeadElement extends HTMLHeadElement{ append: ddeAppend<ddeHTMLHeadElement>; }
|
||||
interface ddeHTMLHRElement extends HTMLHRElement{ append: ddeAppend<ddeHTMLHRElement>; }
|
||||
interface ddeHTMLHtmlElement extends HTMLHtmlElement{ append: ddeAppend<ddeHTMLHtmlElement>; }
|
||||
interface ddeHTMLIFrameElement extends HTMLIFrameElement{ append: ddeAppend<ddeHTMLIFrameElement>; }
|
||||
interface ddeHTMLImageElement extends HTMLImageElement{ append: ddeAppend<ddeHTMLImageElement>; }
|
||||
interface ddeHTMLInputElement extends HTMLInputElement{ append: ddeAppend<ddeHTMLInputElement>; }
|
||||
interface ddeHTMLLabelElement extends HTMLLabelElement{ append: ddeAppend<ddeHTMLLabelElement>; }
|
||||
interface ddeHTMLLegendElement extends HTMLLegendElement{ append: ddeAppend<ddeHTMLLegendElement>; }
|
||||
interface ddeHTMLLIElement extends HTMLLIElement{ append: ddeAppend<ddeHTMLLIElement>; }
|
||||
interface ddeHTMLLinkElement extends HTMLLinkElement{ append: ddeAppend<ddeHTMLLinkElement>; }
|
||||
interface ddeHTMLMapElement extends HTMLMapElement{ append: ddeAppend<ddeHTMLMapElement>; }
|
||||
interface ddeHTMLMenuElement extends HTMLMenuElement{ append: ddeAppend<ddeHTMLMenuElement>; }
|
||||
interface ddeHTMLMetaElement extends HTMLMetaElement{ append: ddeAppend<ddeHTMLMetaElement>; }
|
||||
interface ddeHTMLMeterElement extends HTMLMeterElement{ append: ddeAppend<ddeHTMLMeterElement>; }
|
||||
interface ddeHTMLObjectElement extends HTMLObjectElement{ append: ddeAppend<ddeHTMLObjectElement>; }
|
||||
interface ddeHTMLOListElement extends HTMLOListElement{ append: ddeAppend<ddeHTMLOListElement>; }
|
||||
interface ddeHTMLOptGroupElement extends HTMLOptGroupElement{ append: ddeAppend<ddeHTMLOptGroupElement>; }
|
||||
interface ddeHTMLOptionElement extends HTMLOptionElement{ append: ddeAppend<ddeHTMLOptionElement>; }
|
||||
interface ddeHTMLOutputElement extends HTMLOutputElement{ append: ddeAppend<ddeHTMLOutputElement>; }
|
||||
interface ddeHTMLParagraphElement extends HTMLParagraphElement{ append: ddeAppend<ddeHTMLParagraphElement>; }
|
||||
interface ddeHTMLPictureElement extends HTMLPictureElement{ append: ddeAppend<ddeHTMLPictureElement>; }
|
||||
interface ddeHTMLPreElement extends HTMLPreElement{ append: ddeAppend<ddeHTMLPreElement>; }
|
||||
interface ddeHTMLProgressElement extends HTMLProgressElement{ append: ddeAppend<ddeHTMLProgressElement>; }
|
||||
interface ddeHTMLScriptElement extends HTMLScriptElement{ append: ddeAppend<ddeHTMLScriptElement>; }
|
||||
interface ddeHTMLSelectElement extends HTMLSelectElement{ append: ddeAppend<ddeHTMLSelectElement>; }
|
||||
interface ddeHTMLSlotElement extends HTMLSlotElement{ append: ddeAppend<ddeHTMLSlotElement>; }
|
||||
interface ddeHTMLSourceElement extends HTMLSourceElement{ append: ddeAppend<ddeHTMLSourceElement>; }
|
||||
interface ddeHTMLSpanElement extends HTMLSpanElement{ append: ddeAppend<ddeHTMLSpanElement>; }
|
||||
interface ddeHTMLStyleElement extends HTMLStyleElement{ append: ddeAppend<ddeHTMLStyleElement>; }
|
||||
interface ddeHTMLTableElement extends HTMLTableElement{ append: ddeAppend<ddeHTMLTableElement>; }
|
||||
interface ddeHTMLTableSectionElement extends HTMLTableSectionElement{ append: ddeAppend<ddeHTMLTableSectionElement>; }
|
||||
interface ddeHTMLTableCellElement extends HTMLTableCellElement{ append: ddeAppend<ddeHTMLTableCellElement>; }
|
||||
interface ddeHTMLTemplateElement extends HTMLTemplateElement{ append: ddeAppend<ddeHTMLTemplateElement>; }
|
||||
interface ddeHTMLTextAreaElement extends HTMLTextAreaElement{ append: ddeAppend<ddeHTMLTextAreaElement>; }
|
||||
interface ddeHTMLTableCellElement extends HTMLTableCellElement{ append: ddeAppend<ddeHTMLTableCellElement>; }
|
||||
interface ddeHTMLTimeElement extends HTMLTimeElement{ append: ddeAppend<ddeHTMLTimeElement>; }
|
||||
interface ddeHTMLTitleElement extends HTMLTitleElement{ append: ddeAppend<ddeHTMLTitleElement>; }
|
||||
interface ddeHTMLTableRowElement extends HTMLTableRowElement{ append: ddeAppend<ddeHTMLTableRowElement>; }
|
||||
interface ddeHTMLTrackElement extends HTMLTrackElement{ append: ddeAppend<ddeHTMLTrackElement>; }
|
||||
interface ddeHTMLUListElement extends HTMLUListElement{ append: ddeAppend<ddeHTMLUListElement>; }
|
||||
interface ddeHTMLVideoElement extends HTMLVideoElement{ append: ddeAppend<ddeHTMLVideoElement>; }
|
||||
interface ddeSVGAElement extends SVGAElement{ append: ddeAppend<ddeSVGAElement>; }
|
||||
interface ddeSVGAnimateElement extends SVGAnimateElement{ append: ddeAppend<ddeSVGAnimateElement>; }
|
||||
interface ddeSVGAnimateMotionElement extends SVGAnimateMotionElement{ append: ddeAppend<ddeSVGAnimateMotionElement>; }
|
||||
interface ddeSVGAnimateTransformElement extends SVGAnimateTransformElement{ append: ddeAppend<ddeSVGAnimateTransformElement>; }
|
||||
interface ddeSVGCircleElement extends SVGCircleElement{ append: ddeAppend<ddeSVGCircleElement>; }
|
||||
interface ddeSVGClipPathElement extends SVGClipPathElement{ append: ddeAppend<ddeSVGClipPathElement>; }
|
||||
interface ddeSVGDefsElement extends SVGDefsElement{ append: ddeAppend<ddeSVGDefsElement>; }
|
||||
interface ddeSVGDescElement extends SVGDescElement{ append: ddeAppend<ddeSVGDescElement>; }
|
||||
interface ddeSVGEllipseElement extends SVGEllipseElement{ append: ddeAppend<ddeSVGEllipseElement>; }
|
||||
interface ddeSVGFEBlendElement extends SVGFEBlendElement{ append: ddeAppend<ddeSVGFEBlendElement>; }
|
||||
interface ddeSVGFEColorMatrixElement extends SVGFEColorMatrixElement{ append: ddeAppend<ddeSVGFEColorMatrixElement>; }
|
||||
interface ddeSVGFEComponentTransferElement extends SVGFEComponentTransferElement{ append: ddeAppend<ddeSVGFEComponentTransferElement>; }
|
||||
interface ddeSVGFECompositeElement extends SVGFECompositeElement{ append: ddeAppend<ddeSVGFECompositeElement>; }
|
||||
interface ddeSVGFEConvolveMatrixElement extends SVGFEConvolveMatrixElement{ append: ddeAppend<ddeSVGFEConvolveMatrixElement>; }
|
||||
interface ddeSVGFEDiffuseLightingElement extends SVGFEDiffuseLightingElement{ append: ddeAppend<ddeSVGFEDiffuseLightingElement>; }
|
||||
interface ddeSVGFEDisplacementMapElement extends SVGFEDisplacementMapElement{ append: ddeAppend<ddeSVGFEDisplacementMapElement>; }
|
||||
interface ddeSVGFEDistantLightElement extends SVGFEDistantLightElement{ append: ddeAppend<ddeSVGFEDistantLightElement>; }
|
||||
interface ddeSVGFEDropShadowElement extends SVGFEDropShadowElement{ append: ddeAppend<ddeSVGFEDropShadowElement>; }
|
||||
interface ddeSVGFEFloodElement extends SVGFEFloodElement{ append: ddeAppend<ddeSVGFEFloodElement>; }
|
||||
interface ddeSVGFEFuncAElement extends SVGFEFuncAElement{ append: ddeAppend<ddeSVGFEFuncAElement>; }
|
||||
interface ddeSVGFEFuncBElement extends SVGFEFuncBElement{ append: ddeAppend<ddeSVGFEFuncBElement>; }
|
||||
interface ddeSVGFEFuncGElement extends SVGFEFuncGElement{ append: ddeAppend<ddeSVGFEFuncGElement>; }
|
||||
interface ddeSVGFEFuncRElement extends SVGFEFuncRElement{ append: ddeAppend<ddeSVGFEFuncRElement>; }
|
||||
interface ddeSVGFEGaussianBlurElement extends SVGFEGaussianBlurElement{ append: ddeAppend<ddeSVGFEGaussianBlurElement>; }
|
||||
interface ddeSVGFEImageElement extends SVGFEImageElement{ append: ddeAppend<ddeSVGFEImageElement>; }
|
||||
interface ddeSVGFEMergeElement extends SVGFEMergeElement{ append: ddeAppend<ddeSVGFEMergeElement>; }
|
||||
interface ddeSVGFEMergeNodeElement extends SVGFEMergeNodeElement{ append: ddeAppend<ddeSVGFEMergeNodeElement>; }
|
||||
interface ddeSVGFEMorphologyElement extends SVGFEMorphologyElement{ append: ddeAppend<ddeSVGFEMorphologyElement>; }
|
||||
interface ddeSVGFEOffsetElement extends SVGFEOffsetElement{ append: ddeAppend<ddeSVGFEOffsetElement>; }
|
||||
interface ddeSVGFEPointLightElement extends SVGFEPointLightElement{ append: ddeAppend<ddeSVGFEPointLightElement>; }
|
||||
interface ddeSVGFESpecularLightingElement extends SVGFESpecularLightingElement{ append: ddeAppend<ddeSVGFESpecularLightingElement>; }
|
||||
interface ddeSVGFESpotLightElement extends SVGFESpotLightElement{ append: ddeAppend<ddeSVGFESpotLightElement>; }
|
||||
interface ddeSVGFETileElement extends SVGFETileElement{ append: ddeAppend<ddeSVGFETileElement>; }
|
||||
interface ddeSVGFETurbulenceElement extends SVGFETurbulenceElement{ append: ddeAppend<ddeSVGFETurbulenceElement>; }
|
||||
interface ddeSVGFilterElement extends SVGFilterElement{ append: ddeAppend<ddeSVGFilterElement>; }
|
||||
interface ddeSVGForeignObjectElement extends SVGForeignObjectElement{ append: ddeAppend<ddeSVGForeignObjectElement>; }
|
||||
interface ddeSVGGElement extends SVGGElement{ append: ddeAppend<ddeSVGGElement>; }
|
||||
interface ddeSVGImageElement extends SVGImageElement{ append: ddeAppend<ddeSVGImageElement>; }
|
||||
interface ddeSVGLineElement extends SVGLineElement{ append: ddeAppend<ddeSVGLineElement>; }
|
||||
interface ddeSVGLinearGradientElement extends SVGLinearGradientElement{ append: ddeAppend<ddeSVGLinearGradientElement>; }
|
||||
interface ddeSVGMarkerElement extends SVGMarkerElement{ append: ddeAppend<ddeSVGMarkerElement>; }
|
||||
interface ddeSVGMaskElement extends SVGMaskElement{ append: ddeAppend<ddeSVGMaskElement>; }
|
||||
interface ddeSVGMetadataElement extends SVGMetadataElement{ append: ddeAppend<ddeSVGMetadataElement>; }
|
||||
interface ddeSVGMPathElement extends SVGMPathElement{ append: ddeAppend<ddeSVGMPathElement>; }
|
||||
interface ddeSVGPathElement extends SVGPathElement{ append: ddeAppend<ddeSVGPathElement>; }
|
||||
interface ddeSVGPatternElement extends SVGPatternElement{ append: ddeAppend<ddeSVGPatternElement>; }
|
||||
interface ddeSVGPolygonElement extends SVGPolygonElement{ append: ddeAppend<ddeSVGPolygonElement>; }
|
||||
interface ddeSVGPolylineElement extends SVGPolylineElement{ append: ddeAppend<ddeSVGPolylineElement>; }
|
||||
interface ddeSVGRadialGradientElement extends SVGRadialGradientElement{ append: ddeAppend<ddeSVGRadialGradientElement>; }
|
||||
interface ddeSVGRectElement extends SVGRectElement{ append: ddeAppend<ddeSVGRectElement>; }
|
||||
interface ddeSVGScriptElement extends SVGScriptElement{ append: ddeAppend<ddeSVGScriptElement>; }
|
||||
interface ddeSVGSetElement extends SVGSetElement{ append: ddeAppend<ddeSVGSetElement>; }
|
||||
interface ddeSVGStopElement extends SVGStopElement{ append: ddeAppend<ddeSVGStopElement>; }
|
||||
interface ddeSVGStyleElement extends SVGStyleElement{ append: ddeAppend<ddeSVGStyleElement>; }
|
||||
interface ddeSVGSVGElement extends SVGSVGElement{ append: ddeAppend<ddeSVGSVGElement>; }
|
||||
interface ddeSVGSwitchElement extends SVGSwitchElement{ append: ddeAppend<ddeSVGSwitchElement>; }
|
||||
interface ddeSVGSymbolElement extends SVGSymbolElement{ append: ddeAppend<ddeSVGSymbolElement>; }
|
||||
interface ddeSVGTextElement extends SVGTextElement{ append: ddeAppend<ddeSVGTextElement>; }
|
||||
interface ddeSVGTextPathElement extends SVGTextPathElement{ append: ddeAppend<ddeSVGTextPathElement>; }
|
||||
interface ddeSVGTitleElement extends SVGTitleElement{ append: ddeAppend<ddeSVGTitleElement>; }
|
||||
interface ddeSVGTSpanElement extends SVGTSpanElement{ append: ddeAppend<ddeSVGTSpanElement>; }
|
||||
interface ddeSVGUseElement extends SVGUseElement{ append: ddeAppend<ddeSVGUseElement>; }
|
||||
interface ddeSVGViewElement extends SVGViewElement{ append: ddeAppend<ddeSVGViewElement>; }
|
||||
// editorconfig-checker-enable
|
||||
export interface Signal<V, A> {
|
||||
/** The current value of the signal */
|
||||
get(): V;
|
||||
/** Set new value of the signal */
|
||||
set(value: V): V;
|
||||
toJSON(): V;
|
||||
valueOf(): V;
|
||||
}
|
||||
type Action<V>= (this: { value: V, stopPropagation(): void }, ...a: any[])=> typeof signal._ | void;
|
||||
//type SymbolSignal= Symbol;
|
||||
type SymbolOnclear= symbol;
|
||||
type Actions<V>= Record<string | SymbolOnclear, Action<V>>;
|
||||
type OnListenerOptions= Pick<AddEventListenerOptions, "signal"> & { first_time?: boolean };
|
||||
interface signal{
|
||||
_: Symbol
|
||||
/**
|
||||
* Computations signal. This creates a signal which is computed from other signals.
|
||||
* */
|
||||
<V extends ()=> any>(computation: V): Signal<ReturnType<V>, {}>
|
||||
/**
|
||||
* Simple example:
|
||||
* ```js
|
||||
* const hello= S("Hello Signal");
|
||||
* ```
|
||||
* …simple todo signal:
|
||||
* ```js
|
||||
* const todos= S([], {
|
||||
* add(v){ this.value.push(S(v)); },
|
||||
* remove(i){ this.value.splice(i, 1); },
|
||||
* [S.symbols.onclear](){ S.clear(...this.value); },
|
||||
* });
|
||||
* ```
|
||||
* …computed signal:
|
||||
* ```js
|
||||
* const name= S("Jan");
|
||||
* const surname= S("Andrle");
|
||||
* const fullname= S(()=> name.get()+" "+surname.get());
|
||||
* ```
|
||||
* @param value Initial signal value. Or function computing value from other signals.
|
||||
* @param actions Use to define actions on the signal. Such as add item to the array.
|
||||
* There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared
|
||||
* by `S.clear`.
|
||||
* */
|
||||
<V, A extends Actions<V>>(value: V, actions?: A): Signal<V, A>;
|
||||
action<S extends Signal<any, Actions<any>>, A extends (S extends Signal<any, infer A> ? A : never), N extends keyof A>(
|
||||
signal: S,
|
||||
name: N,
|
||||
...params: A[N] extends (...args: infer P)=> any ? P : never
|
||||
): void;
|
||||
clear(...signals: Signal<any, any>[]): void;
|
||||
on<T>(signal: Signal<T, any>, onchange: (a: T)=> void, options?: OnListenerOptions): void;
|
||||
symbols: {
|
||||
//signal: SymbolSignal;
|
||||
onclear: SymbolOnclear;
|
||||
}
|
||||
/**
|
||||
* Reactive element, which is rendered based on the given signal.
|
||||
* ```js
|
||||
* S.el(signal, value=> value ? el("b", "True") : el("i", "False"));
|
||||
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||
* ```
|
||||
* */
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S)=> Element | Element[] | DocumentFragment): DocumentFragment;
|
||||
|
||||
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
|
||||
}
|
||||
export const signal: signal;
|
||||
export const S: signal;
|
||||
declare global {
|
||||
type ddeSignal<T, A= {}>= Signal<T, A>;
|
||||
type ddeAction<V>= Action<V>
|
||||
type ddeActions<V>= Actions<V>
|
||||
}
|
20
dist/esm-with-signals.d.ts
vendored
20
dist/esm-with-signals.d.ts
vendored
@ -52,9 +52,12 @@ type IsReadonly<T, K extends keyof T> =
|
||||
* @private
|
||||
*/
|
||||
type ElementAttributes<T extends SupportedElement>= Partial<{
|
||||
[K in keyof _fromElsInterfaces<T>]: IsReadonly<_fromElsInterfaces<T>, K> extends false
|
||||
? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
|
||||
: ddeStringable
|
||||
[K in keyof _fromElsInterfaces<T>]:
|
||||
_fromElsInterfaces<T>[K] extends ((...p: any[])=> any)
|
||||
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=> ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
|
||||
: (IsReadonly<_fromElsInterfaces<T>, K> extends false
|
||||
? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
|
||||
: ddeStringable)
|
||||
} & AttrsModified> & Record<string, any>;
|
||||
export function classListDeclarative<El extends SupportedElement>(
|
||||
element: El,
|
||||
@ -515,7 +518,14 @@ interface ddeSVGTSpanElement extends SVGTSpanElement{ append: ddeAppend<ddeSVGTS
|
||||
interface ddeSVGUseElement extends SVGUseElement{ append: ddeAppend<ddeSVGUseElement>; }
|
||||
interface ddeSVGViewElement extends SVGViewElement{ append: ddeAppend<ddeSVGViewElement>; }
|
||||
// editorconfig-checker-enable
|
||||
export type Signal<V, A>= (set?: V)=> V & A;
|
||||
export interface Signal<V, A> {
|
||||
/** The current value of the signal */
|
||||
get(): V;
|
||||
/** Set new value of the signal */
|
||||
set(value: V): V;
|
||||
toJSON(): V;
|
||||
valueOf(): V;
|
||||
}
|
||||
type Action<V>= (this: { value: V, stopPropagation(): void }, ...a: any[])=> typeof signal._ | void;
|
||||
//type SymbolSignal= Symbol;
|
||||
type SymbolOnclear= symbol;
|
||||
@ -544,7 +554,7 @@ interface signal{
|
||||
* ```js
|
||||
* const name= S("Jan");
|
||||
* const surname= S("Andrle");
|
||||
* const fullname= S(()=> name()+" "+surname());
|
||||
* const fullname= S(()=> name.get()+" "+surname.get());
|
||||
* ```
|
||||
* @param value Initial signal value. Or function computing value from other signals.
|
||||
* @param actions Use to define actions on the signal. Such as add item to the array.
|
||||
|
1316
dist/esm-with-signals.js
vendored
1316
dist/esm-with-signals.js
vendored
File diff suppressed because it is too large
Load Diff
4
dist/esm-with-signals.min.js
vendored
Normal file
4
dist/esm-with-signals.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
520
dist/esm.d.min.ts
vendored
Normal file
520
dist/esm.d.min.ts
vendored
Normal file
@ -0,0 +1,520 @@
|
||||
declare global{ /* ddeSignal */ }
|
||||
type CustomElementTagNameMap= { '#text': Text, '#comment': Comment }
|
||||
type SupportedElement=
|
||||
HTMLElementTagNameMap[keyof HTMLElementTagNameMap]
|
||||
| SVGElementTagNameMap[keyof SVGElementTagNameMap]
|
||||
| MathMLElementTagNameMap[keyof MathMLElementTagNameMap]
|
||||
| CustomElementTagNameMap[keyof CustomElementTagNameMap]
|
||||
declare global {
|
||||
type ddeComponentAttributes= Record<any, any> | undefined;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node>= (element: El)=> any;
|
||||
type ddeString= string | ddeSignal<string>
|
||||
type ddeStringable= ddeString | number | ddeSignal<number>
|
||||
}
|
||||
type PascalCase=
|
||||
`${Capitalize<string>}${string}`;
|
||||
type AttrsModified= {
|
||||
/**
|
||||
* Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API).
|
||||
*/
|
||||
style: Partial<CSSStyleDeclaration> | ddeString
|
||||
| Partial<{ [K in keyof CSSStyleDeclaration]: ddeSignal<CSSStyleDeclaration[K]> }>
|
||||
/**
|
||||
* Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1.
|
||||
* In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))`
|
||||
* for others.
|
||||
*/
|
||||
classList: Record<string,-1|0|1|boolean|ddeSignal<-1|0|1|boolean>>,
|
||||
/**
|
||||
* Used by the dataset HTML attribute to represent data for custom attributes added to elements.
|
||||
* Values are converted to string (see {@link DOMStringMap}).
|
||||
*
|
||||
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMStringMap)
|
||||
* */
|
||||
dataset: Record<string, ddeStringable>,
|
||||
/**
|
||||
* Sets `aria-*` simiraly to `dataset`
|
||||
* */
|
||||
ariaset: Record<string, ddeString>,
|
||||
} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, ddeString>
|
||||
& Record<`.${string}`, any>
|
||||
type _fromElsInterfaces<EL extends SupportedElement>= Omit<EL, keyof AttrsModified>;
|
||||
type IsReadonly<T, K extends keyof T> =
|
||||
T extends { readonly [P in K]: T[K] } ? true : false;
|
||||
/**
|
||||
* Just element attributtes
|
||||
*
|
||||
* In most cases, you can use native propertie such as
|
||||
* [MDN WEB/API/Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) and so on
|
||||
* (e.g. [`Text`](https://developer.mozilla.org/en-US/docs/Web/API/Text)).
|
||||
*
|
||||
* There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives.
|
||||
* @private
|
||||
*/
|
||||
type ElementAttributes<T extends SupportedElement>= Partial<{
|
||||
[K in keyof _fromElsInterfaces<T>]:
|
||||
_fromElsInterfaces<T>[K] extends ((...p: any[])=> any)
|
||||
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=> ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
|
||||
: (IsReadonly<_fromElsInterfaces<T>, K> extends false
|
||||
? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
|
||||
: ddeStringable)
|
||||
} & AttrsModified> & Record<string, any>;
|
||||
export function classListDeclarative<El extends SupportedElement>(
|
||||
element: El,
|
||||
classList: AttrsModified["classList"]
|
||||
): El
|
||||
export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El
|
||||
export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(
|
||||
element: El,
|
||||
attr: ATT,
|
||||
value: ElementAttributes<El>[ATT]
|
||||
): ElementAttributes<El>[ATT]
|
||||
|
||||
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
|
||||
export function el<
|
||||
TAG extends keyof ExtendedHTMLElementTagNameMap,
|
||||
>(
|
||||
tag_name: TAG,
|
||||
attrs?: ElementAttributes<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]> | ddeStringable,
|
||||
...addons: ddeElementAddon<
|
||||
ExtendedHTMLElementTagNameMap[NoInfer<TAG>]
|
||||
>[], // TODO: for now addons must have the same element
|
||||
): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement
|
||||
export function el(
|
||||
tag_name?: "<>",
|
||||
): ddeDocumentFragment
|
||||
export function el(
|
||||
tag_name: string,
|
||||
attrs?: ElementAttributes<HTMLElement> | ddeStringable,
|
||||
...addons: ddeElementAddon<HTMLElement>[]
|
||||
): ddeHTMLElement
|
||||
|
||||
export function el<
|
||||
C extends (attr: ddeComponentAttributes)=> SupportedElement | ddeDocumentFragment
|
||||
>(
|
||||
component: C,
|
||||
attrs?: Parameters<C>[0] | ddeStringable,
|
||||
...addons: ddeElementAddon<ReturnType<C>>[]
|
||||
): ReturnType<C> extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
|
||||
? ReturnType<C>
|
||||
: ( ReturnType<C> extends ddeDocumentFragment ? ReturnType<C> : ddeHTMLElement )
|
||||
export { el as createElement }
|
||||
|
||||
export function elNS(
|
||||
namespace: "http://www.w3.org/2000/svg"
|
||||
): <
|
||||
TAG extends keyof SVGElementTagNameMap & string,
|
||||
EL extends ( TAG extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[TAG] : SVGElement ),
|
||||
>(
|
||||
tag_name: TAG,
|
||||
attrs?: ElementAttributes<NoInfer<EL>> | ddeStringable,
|
||||
...addons: ddeElementAddon<NoInfer<EL>>[]
|
||||
)=> TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement
|
||||
export function elNS(
|
||||
namespace: "http://www.w3.org/1998/Math/MathML"
|
||||
): <
|
||||
TAG extends keyof MathMLElementTagNameMap & string,
|
||||
EL extends ( TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement ),
|
||||
>(
|
||||
tag_name: TAG,
|
||||
attrs?: ddeStringable | Partial<{
|
||||
[key in keyof EL]: EL[key] | ddeSignal<EL[key]> | string | number | boolean
|
||||
}>,
|
||||
...addons: ddeElementAddon<NoInfer<EL>>[]
|
||||
)=> ddeMathMLElement
|
||||
export function elNS(
|
||||
namespace: string
|
||||
): (
|
||||
tag_name: string,
|
||||
attrs?: string | ddeStringable | Record<string, any>,
|
||||
...addons: ddeElementAddon<SupportedElement>[]
|
||||
)=> SupportedElement
|
||||
export { elNS as createElementNS }
|
||||
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL;
|
||||
/** Simulate slots for ddeComponents */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
|
||||
root: EL,
|
||||
): EL
|
||||
/**
|
||||
* Simulate slots in Custom Elements without using `shadowRoot`.
|
||||
* @param el Custom Element root element
|
||||
* @param body Body of the custom element
|
||||
* */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
|
||||
el: HTMLElement,
|
||||
body: EL,
|
||||
): EL
|
||||
|
||||
export function dispatchEvent(name: keyof DocumentEventMap | string, options?: EventInit):
|
||||
(element: SupportedElement, data?: any)=> void;
|
||||
export function dispatchEvent(
|
||||
name: keyof DocumentEventMap | string,
|
||||
options: EventInit | null,
|
||||
element: SupportedElement | (()=> SupportedElement)
|
||||
): (data?: any)=> void;
|
||||
interface On{
|
||||
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
||||
<
|
||||
Event extends keyof DocumentEventMap,
|
||||
EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>,
|
||||
>(
|
||||
type: Event,
|
||||
listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any,
|
||||
options?: AddEventListenerOptions
|
||||
) : EE;
|
||||
<
|
||||
EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>,
|
||||
>(
|
||||
type: string,
|
||||
listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent ) => any,
|
||||
options?: AddEventListenerOptions
|
||||
) : EE;
|
||||
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
|
||||
connected<
|
||||
EE extends ddeElementAddon<SupportedElement>,
|
||||
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
|
||||
>(
|
||||
listener: (this: El, event: CustomEvent<El>) => any,
|
||||
options?: AddEventListenerOptions
|
||||
) : EE;
|
||||
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
|
||||
disconnected<
|
||||
EE extends ddeElementAddon<SupportedElement>,
|
||||
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
|
||||
>(
|
||||
listener: (this: El, event: CustomEvent<void>) => any,
|
||||
options?: AddEventListenerOptions
|
||||
) : EE;
|
||||
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
|
||||
attributeChanged<
|
||||
EE extends ddeElementAddon<SupportedElement>,
|
||||
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
|
||||
>(
|
||||
listener: (this: El, event: CustomEvent<[ string, string ]>) => any,
|
||||
options?: AddEventListenerOptions
|
||||
) : EE;
|
||||
}
|
||||
export const on: On;
|
||||
|
||||
type Scope= {
|
||||
scope: Node | Function | Object,
|
||||
host: ddeElementAddon<any>,
|
||||
custom_element: false | HTMLElement,
|
||||
prevent: boolean
|
||||
};
|
||||
/** Current scope created last time the `el(Function)` was invoke. (Or {@link scope.push}) */
|
||||
export const scope: {
|
||||
current: Scope,
|
||||
/** Stops all automatizations. E. g. signals used as attributes in current scope
|
||||
* registers removing these listeners (and clean signal if no other listeners are detected)
|
||||
* on `disconnected` event. */
|
||||
preventDefault<T extends boolean>(prevent: T): T,
|
||||
/**
|
||||
* This represents reference to the current host element — `scope.host()`.
|
||||
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||
* — `scope.host(on.connected(console.log))`.
|
||||
* */
|
||||
host: (...addons: ddeElementAddon<SupportedElement>[])=> HTMLElement,
|
||||
|
||||
state: Scope[],
|
||||
/** Adds new child scope. All attributes are inherited by default. */
|
||||
push(scope: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
|
||||
/** Adds root scope as a child of the current scope. */
|
||||
pushRoot(): ReturnType<Array<Scope>["push"]>,
|
||||
/** Removes last/current child scope. */
|
||||
pop(): ReturnType<Array<Scope>["pop"]>,
|
||||
};
|
||||
|
||||
export function customElementRender<
|
||||
EL extends HTMLElement,
|
||||
P extends any = Record<string, string | ddeSignal<string>>
|
||||
>(
|
||||
target: ShadowRoot | EL,
|
||||
render: (props: P)=> SupportedElement | DocumentFragment,
|
||||
props?: P | ((el: EL)=> P)
|
||||
): EL
|
||||
export function customElementWithDDE<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL
|
||||
export function lifecyclesToEvents<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL
|
||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>
|
||||
|
||||
/* TypeScript MEH */
|
||||
declare global{
|
||||
type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
|
||||
|
||||
interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; }
|
||||
interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; }
|
||||
interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; }
|
||||
interface ddeMathMLElement extends MathMLElement{ append: ddeAppend<ddeMathMLElement>; }
|
||||
|
||||
interface ddeHTMLElementTagNameMap {
|
||||
"a": ddeHTMLAnchorElement;
|
||||
"area": ddeHTMLAreaElement;
|
||||
"audio": ddeHTMLAudioElement;
|
||||
"base": ddeHTMLBaseElement;
|
||||
"blockquote": ddeHTMLQuoteElement;
|
||||
"body": ddeHTMLBodyElement;
|
||||
"br": ddeHTMLBRElement;
|
||||
"button": ddeHTMLButtonElement;
|
||||
"canvas": ddeHTMLCanvasElement;
|
||||
"caption": ddeHTMLTableCaptionElement;
|
||||
"col": ddeHTMLTableColElement;
|
||||
"colgroup": ddeHTMLTableColElement;
|
||||
"data": ddeHTMLDataElement;
|
||||
"datalist": ddeHTMLDataListElement;
|
||||
"del": ddeHTMLModElement;
|
||||
"details": ddeHTMLDetailsElement;
|
||||
"dialog": ddeHTMLDialogElement;
|
||||
"div": ddeHTMLDivElement;
|
||||
"dl": ddeHTMLDListElement;
|
||||
"embed": ddeHTMLEmbedElement;
|
||||
"fieldset": ddeHTMLFieldSetElement;
|
||||
"form": ddeHTMLFormElement;
|
||||
"h1": ddeHTMLHeadingElement;
|
||||
"h2": ddeHTMLHeadingElement;
|
||||
"h3": ddeHTMLHeadingElement;
|
||||
"h4": ddeHTMLHeadingElement;
|
||||
"h5": ddeHTMLHeadingElement;
|
||||
"h6": ddeHTMLHeadingElement;
|
||||
"head": ddeHTMLHeadElement;
|
||||
"hr": ddeHTMLHRElement;
|
||||
"html": ddeHTMLHtmlElement;
|
||||
"iframe": ddeHTMLIFrameElement;
|
||||
"img": ddeHTMLImageElement;
|
||||
"input": ddeHTMLInputElement;
|
||||
"ins": ddeHTMLModElement;
|
||||
"label": ddeHTMLLabelElement;
|
||||
"legend": ddeHTMLLegendElement;
|
||||
"li": ddeHTMLLIElement;
|
||||
"link": ddeHTMLLinkElement;
|
||||
"map": ddeHTMLMapElement;
|
||||
"menu": ddeHTMLMenuElement;
|
||||
"meta": ddeHTMLMetaElement;
|
||||
"meter": ddeHTMLMeterElement;
|
||||
"object": ddeHTMLObjectElement;
|
||||
"ol": ddeHTMLOListElement;
|
||||
"optgroup": ddeHTMLOptGroupElement;
|
||||
"option": ddeHTMLOptionElement;
|
||||
"output": ddeHTMLOutputElement;
|
||||
"p": ddeHTMLParagraphElement;
|
||||
"picture": ddeHTMLPictureElement;
|
||||
"pre": ddeHTMLPreElement;
|
||||
"progress": ddeHTMLProgressElement;
|
||||
"q": ddeHTMLQuoteElement;
|
||||
"script": ddeHTMLScriptElement;
|
||||
"select": ddeHTMLSelectElement;
|
||||
"slot": ddeHTMLSlotElement;
|
||||
"source": ddeHTMLSourceElement;
|
||||
"span": ddeHTMLSpanElement;
|
||||
"style": ddeHTMLStyleElement;
|
||||
"table": ddeHTMLTableElement;
|
||||
"tbody": ddeHTMLTableSectionElement;
|
||||
"td": ddeHTMLTableCellElement;
|
||||
"template": ddeHTMLTemplateElement;
|
||||
"textarea": ddeHTMLTextAreaElement;
|
||||
"tfoot": ddeHTMLTableSectionElement;
|
||||
"th": ddeHTMLTableCellElement;
|
||||
"thead": ddeHTMLTableSectionElement;
|
||||
"time": ddeHTMLTimeElement;
|
||||
"title": ddeHTMLTitleElement;
|
||||
"tr": ddeHTMLTableRowElement;
|
||||
"track": ddeHTMLTrackElement;
|
||||
"ul": ddeHTMLUListElement;
|
||||
"video": ddeHTMLVideoElement;
|
||||
}
|
||||
interface ddeSVGElementTagNameMap {
|
||||
"a": ddeSVGAElement;
|
||||
"animate": ddeSVGAnimateElement;
|
||||
"animateMotion": ddeSVGAnimateMotionElement;
|
||||
"animateTransform": ddeSVGAnimateTransformElement;
|
||||
"circle": ddeSVGCircleElement;
|
||||
"clipPath": ddeSVGClipPathElement;
|
||||
"defs": ddeSVGDefsElement;
|
||||
"desc": ddeSVGDescElement;
|
||||
"ellipse": ddeSVGEllipseElement;
|
||||
"feBlend": ddeSVGFEBlendElement;
|
||||
"feColorMatrix": ddeSVGFEColorMatrixElement;
|
||||
"feComponentTransfer": ddeSVGFEComponentTransferElement;
|
||||
"feComposite": ddeSVGFECompositeElement;
|
||||
"feConvolveMatrix": ddeSVGFEConvolveMatrixElement;
|
||||
"feDiffuseLighting": ddeSVGFEDiffuseLightingElement;
|
||||
"feDisplacementMap": ddeSVGFEDisplacementMapElement;
|
||||
"feDistantLight": ddeSVGFEDistantLightElement;
|
||||
"feDropShadow": ddeSVGFEDropShadowElement;
|
||||
"feFlood": ddeSVGFEFloodElement;
|
||||
"feFuncA": ddeSVGFEFuncAElement;
|
||||
"feFuncB": ddeSVGFEFuncBElement;
|
||||
"feFuncG": ddeSVGFEFuncGElement;
|
||||
"feFuncR": ddeSVGFEFuncRElement;
|
||||
"feGaussianBlur": ddeSVGFEGaussianBlurElement;
|
||||
"feImage": ddeSVGFEImageElement;
|
||||
"feMerge": ddeSVGFEMergeElement;
|
||||
"feMergeNode": ddeSVGFEMergeNodeElement;
|
||||
"feMorphology": ddeSVGFEMorphologyElement;
|
||||
"feOffset": ddeSVGFEOffsetElement;
|
||||
"fePointLight": ddeSVGFEPointLightElement;
|
||||
"feSpecularLighting": ddeSVGFESpecularLightingElement;
|
||||
"feSpotLight": ddeSVGFESpotLightElement;
|
||||
"feTile": ddeSVGFETileElement;
|
||||
"feTurbulence": ddeSVGFETurbulenceElement;
|
||||
"filter": ddeSVGFilterElement;
|
||||
"foreignObject": ddeSVGForeignObjectElement;
|
||||
"g": ddeSVGGElement;
|
||||
"image": ddeSVGImageElement;
|
||||
"line": ddeSVGLineElement;
|
||||
"linearGradient": ddeSVGLinearGradientElement;
|
||||
"marker": ddeSVGMarkerElement;
|
||||
"mask": ddeSVGMaskElement;
|
||||
"metadata": ddeSVGMetadataElement;
|
||||
"mpath": ddeSVGMPathElement;
|
||||
"path": ddeSVGPathElement;
|
||||
"pattern": ddeSVGPatternElement;
|
||||
"polygon": ddeSVGPolygonElement;
|
||||
"polyline": ddeSVGPolylineElement;
|
||||
"radialGradient": ddeSVGRadialGradientElement;
|
||||
"rect": ddeSVGRectElement;
|
||||
"script": ddeSVGScriptElement;
|
||||
"set": ddeSVGSetElement;
|
||||
"stop": ddeSVGStopElement;
|
||||
"style": ddeSVGStyleElement;
|
||||
"svg": ddeSVGSVGElement;
|
||||
"switch": ddeSVGSwitchElement;
|
||||
"symbol": ddeSVGSymbolElement;
|
||||
"text": ddeSVGTextElement;
|
||||
"textPath": ddeSVGTextPathElement;
|
||||
"title": ddeSVGTitleElement;
|
||||
"tspan": ddeSVGTSpanElement;
|
||||
"use": ddeSVGUseElement;
|
||||
"view": ddeSVGViewElement;
|
||||
}
|
||||
}
|
||||
|
||||
// editorconfig-checker-disable
|
||||
interface ddeHTMLAnchorElement extends HTMLAnchorElement{ append: ddeAppend<ddeHTMLAnchorElement>; }
|
||||
interface ddeHTMLAreaElement extends HTMLAreaElement{ append: ddeAppend<ddeHTMLAreaElement>; }
|
||||
interface ddeHTMLAudioElement extends HTMLAudioElement{ append: ddeAppend<ddeHTMLAudioElement>; }
|
||||
interface ddeHTMLBaseElement extends HTMLBaseElement{ append: ddeAppend<ddeHTMLBaseElement>; }
|
||||
interface ddeHTMLQuoteElement extends HTMLQuoteElement{ append: ddeAppend<ddeHTMLQuoteElement>; }
|
||||
interface ddeHTMLBodyElement extends HTMLBodyElement{ append: ddeAppend<ddeHTMLBodyElement>; }
|
||||
interface ddeHTMLBRElement extends HTMLBRElement{ append: ddeAppend<ddeHTMLBRElement>; }
|
||||
interface ddeHTMLButtonElement extends HTMLButtonElement{ append: ddeAppend<ddeHTMLButtonElement>; }
|
||||
interface ddeHTMLCanvasElement extends HTMLCanvasElement{ append: ddeAppend<ddeHTMLCanvasElement>; }
|
||||
interface ddeHTMLTableCaptionElement extends HTMLTableCaptionElement{ append: ddeAppend<ddeHTMLTableCaptionElement>; }
|
||||
interface ddeHTMLTableColElement extends HTMLTableColElement{ append: ddeAppend<ddeHTMLTableColElement>; }
|
||||
interface ddeHTMLTableColElement extends HTMLTableColElement{ append: ddeAppend<ddeHTMLTableColElement>; }
|
||||
interface ddeHTMLDataElement extends HTMLDataElement{ append: ddeAppend<ddeHTMLDataElement>; }
|
||||
interface ddeHTMLDataListElement extends HTMLDataListElement{ append: ddeAppend<ddeHTMLDataListElement>; }
|
||||
interface ddeHTMLModElement extends HTMLModElement{ append: ddeAppend<ddeHTMLModElement>; }
|
||||
interface ddeHTMLDetailsElement extends HTMLDetailsElement{ append: ddeAppend<ddeHTMLDetailsElement>; }
|
||||
interface ddeHTMLDialogElement extends HTMLDialogElement{ append: ddeAppend<ddeHTMLDialogElement>; }
|
||||
interface ddeHTMLDivElement extends HTMLDivElement{ append: ddeAppend<ddeHTMLDivElement>; }
|
||||
interface ddeHTMLDListElement extends HTMLDListElement{ append: ddeAppend<ddeHTMLDListElement>; }
|
||||
interface ddeHTMLEmbedElement extends HTMLEmbedElement{ append: ddeAppend<ddeHTMLEmbedElement>; }
|
||||
interface ddeHTMLFieldSetElement extends HTMLFieldSetElement{ append: ddeAppend<ddeHTMLFieldSetElement>; }
|
||||
interface ddeHTMLFormElement extends HTMLFormElement{ append: ddeAppend<ddeHTMLFormElement>; }
|
||||
interface ddeHTMLHeadingElement extends HTMLHeadingElement{ append: ddeAppend<ddeHTMLHeadingElement>; }
|
||||
interface ddeHTMLHeadElement extends HTMLHeadElement{ append: ddeAppend<ddeHTMLHeadElement>; }
|
||||
interface ddeHTMLHRElement extends HTMLHRElement{ append: ddeAppend<ddeHTMLHRElement>; }
|
||||
interface ddeHTMLHtmlElement extends HTMLHtmlElement{ append: ddeAppend<ddeHTMLHtmlElement>; }
|
||||
interface ddeHTMLIFrameElement extends HTMLIFrameElement{ append: ddeAppend<ddeHTMLIFrameElement>; }
|
||||
interface ddeHTMLImageElement extends HTMLImageElement{ append: ddeAppend<ddeHTMLImageElement>; }
|
||||
interface ddeHTMLInputElement extends HTMLInputElement{ append: ddeAppend<ddeHTMLInputElement>; }
|
||||
interface ddeHTMLLabelElement extends HTMLLabelElement{ append: ddeAppend<ddeHTMLLabelElement>; }
|
||||
interface ddeHTMLLegendElement extends HTMLLegendElement{ append: ddeAppend<ddeHTMLLegendElement>; }
|
||||
interface ddeHTMLLIElement extends HTMLLIElement{ append: ddeAppend<ddeHTMLLIElement>; }
|
||||
interface ddeHTMLLinkElement extends HTMLLinkElement{ append: ddeAppend<ddeHTMLLinkElement>; }
|
||||
interface ddeHTMLMapElement extends HTMLMapElement{ append: ddeAppend<ddeHTMLMapElement>; }
|
||||
interface ddeHTMLMenuElement extends HTMLMenuElement{ append: ddeAppend<ddeHTMLMenuElement>; }
|
||||
interface ddeHTMLMetaElement extends HTMLMetaElement{ append: ddeAppend<ddeHTMLMetaElement>; }
|
||||
interface ddeHTMLMeterElement extends HTMLMeterElement{ append: ddeAppend<ddeHTMLMeterElement>; }
|
||||
interface ddeHTMLObjectElement extends HTMLObjectElement{ append: ddeAppend<ddeHTMLObjectElement>; }
|
||||
interface ddeHTMLOListElement extends HTMLOListElement{ append: ddeAppend<ddeHTMLOListElement>; }
|
||||
interface ddeHTMLOptGroupElement extends HTMLOptGroupElement{ append: ddeAppend<ddeHTMLOptGroupElement>; }
|
||||
interface ddeHTMLOptionElement extends HTMLOptionElement{ append: ddeAppend<ddeHTMLOptionElement>; }
|
||||
interface ddeHTMLOutputElement extends HTMLOutputElement{ append: ddeAppend<ddeHTMLOutputElement>; }
|
||||
interface ddeHTMLParagraphElement extends HTMLParagraphElement{ append: ddeAppend<ddeHTMLParagraphElement>; }
|
||||
interface ddeHTMLPictureElement extends HTMLPictureElement{ append: ddeAppend<ddeHTMLPictureElement>; }
|
||||
interface ddeHTMLPreElement extends HTMLPreElement{ append: ddeAppend<ddeHTMLPreElement>; }
|
||||
interface ddeHTMLProgressElement extends HTMLProgressElement{ append: ddeAppend<ddeHTMLProgressElement>; }
|
||||
interface ddeHTMLScriptElement extends HTMLScriptElement{ append: ddeAppend<ddeHTMLScriptElement>; }
|
||||
interface ddeHTMLSelectElement extends HTMLSelectElement{ append: ddeAppend<ddeHTMLSelectElement>; }
|
||||
interface ddeHTMLSlotElement extends HTMLSlotElement{ append: ddeAppend<ddeHTMLSlotElement>; }
|
||||
interface ddeHTMLSourceElement extends HTMLSourceElement{ append: ddeAppend<ddeHTMLSourceElement>; }
|
||||
interface ddeHTMLSpanElement extends HTMLSpanElement{ append: ddeAppend<ddeHTMLSpanElement>; }
|
||||
interface ddeHTMLStyleElement extends HTMLStyleElement{ append: ddeAppend<ddeHTMLStyleElement>; }
|
||||
interface ddeHTMLTableElement extends HTMLTableElement{ append: ddeAppend<ddeHTMLTableElement>; }
|
||||
interface ddeHTMLTableSectionElement extends HTMLTableSectionElement{ append: ddeAppend<ddeHTMLTableSectionElement>; }
|
||||
interface ddeHTMLTableCellElement extends HTMLTableCellElement{ append: ddeAppend<ddeHTMLTableCellElement>; }
|
||||
interface ddeHTMLTemplateElement extends HTMLTemplateElement{ append: ddeAppend<ddeHTMLTemplateElement>; }
|
||||
interface ddeHTMLTextAreaElement extends HTMLTextAreaElement{ append: ddeAppend<ddeHTMLTextAreaElement>; }
|
||||
interface ddeHTMLTableCellElement extends HTMLTableCellElement{ append: ddeAppend<ddeHTMLTableCellElement>; }
|
||||
interface ddeHTMLTimeElement extends HTMLTimeElement{ append: ddeAppend<ddeHTMLTimeElement>; }
|
||||
interface ddeHTMLTitleElement extends HTMLTitleElement{ append: ddeAppend<ddeHTMLTitleElement>; }
|
||||
interface ddeHTMLTableRowElement extends HTMLTableRowElement{ append: ddeAppend<ddeHTMLTableRowElement>; }
|
||||
interface ddeHTMLTrackElement extends HTMLTrackElement{ append: ddeAppend<ddeHTMLTrackElement>; }
|
||||
interface ddeHTMLUListElement extends HTMLUListElement{ append: ddeAppend<ddeHTMLUListElement>; }
|
||||
interface ddeHTMLVideoElement extends HTMLVideoElement{ append: ddeAppend<ddeHTMLVideoElement>; }
|
||||
interface ddeSVGAElement extends SVGAElement{ append: ddeAppend<ddeSVGAElement>; }
|
||||
interface ddeSVGAnimateElement extends SVGAnimateElement{ append: ddeAppend<ddeSVGAnimateElement>; }
|
||||
interface ddeSVGAnimateMotionElement extends SVGAnimateMotionElement{ append: ddeAppend<ddeSVGAnimateMotionElement>; }
|
||||
interface ddeSVGAnimateTransformElement extends SVGAnimateTransformElement{ append: ddeAppend<ddeSVGAnimateTransformElement>; }
|
||||
interface ddeSVGCircleElement extends SVGCircleElement{ append: ddeAppend<ddeSVGCircleElement>; }
|
||||
interface ddeSVGClipPathElement extends SVGClipPathElement{ append: ddeAppend<ddeSVGClipPathElement>; }
|
||||
interface ddeSVGDefsElement extends SVGDefsElement{ append: ddeAppend<ddeSVGDefsElement>; }
|
||||
interface ddeSVGDescElement extends SVGDescElement{ append: ddeAppend<ddeSVGDescElement>; }
|
||||
interface ddeSVGEllipseElement extends SVGEllipseElement{ append: ddeAppend<ddeSVGEllipseElement>; }
|
||||
interface ddeSVGFEBlendElement extends SVGFEBlendElement{ append: ddeAppend<ddeSVGFEBlendElement>; }
|
||||
interface ddeSVGFEColorMatrixElement extends SVGFEColorMatrixElement{ append: ddeAppend<ddeSVGFEColorMatrixElement>; }
|
||||
interface ddeSVGFEComponentTransferElement extends SVGFEComponentTransferElement{ append: ddeAppend<ddeSVGFEComponentTransferElement>; }
|
||||
interface ddeSVGFECompositeElement extends SVGFECompositeElement{ append: ddeAppend<ddeSVGFECompositeElement>; }
|
||||
interface ddeSVGFEConvolveMatrixElement extends SVGFEConvolveMatrixElement{ append: ddeAppend<ddeSVGFEConvolveMatrixElement>; }
|
||||
interface ddeSVGFEDiffuseLightingElement extends SVGFEDiffuseLightingElement{ append: ddeAppend<ddeSVGFEDiffuseLightingElement>; }
|
||||
interface ddeSVGFEDisplacementMapElement extends SVGFEDisplacementMapElement{ append: ddeAppend<ddeSVGFEDisplacementMapElement>; }
|
||||
interface ddeSVGFEDistantLightElement extends SVGFEDistantLightElement{ append: ddeAppend<ddeSVGFEDistantLightElement>; }
|
||||
interface ddeSVGFEDropShadowElement extends SVGFEDropShadowElement{ append: ddeAppend<ddeSVGFEDropShadowElement>; }
|
||||
interface ddeSVGFEFloodElement extends SVGFEFloodElement{ append: ddeAppend<ddeSVGFEFloodElement>; }
|
||||
interface ddeSVGFEFuncAElement extends SVGFEFuncAElement{ append: ddeAppend<ddeSVGFEFuncAElement>; }
|
||||
interface ddeSVGFEFuncBElement extends SVGFEFuncBElement{ append: ddeAppend<ddeSVGFEFuncBElement>; }
|
||||
interface ddeSVGFEFuncGElement extends SVGFEFuncGElement{ append: ddeAppend<ddeSVGFEFuncGElement>; }
|
||||
interface ddeSVGFEFuncRElement extends SVGFEFuncRElement{ append: ddeAppend<ddeSVGFEFuncRElement>; }
|
||||
interface ddeSVGFEGaussianBlurElement extends SVGFEGaussianBlurElement{ append: ddeAppend<ddeSVGFEGaussianBlurElement>; }
|
||||
interface ddeSVGFEImageElement extends SVGFEImageElement{ append: ddeAppend<ddeSVGFEImageElement>; }
|
||||
interface ddeSVGFEMergeElement extends SVGFEMergeElement{ append: ddeAppend<ddeSVGFEMergeElement>; }
|
||||
interface ddeSVGFEMergeNodeElement extends SVGFEMergeNodeElement{ append: ddeAppend<ddeSVGFEMergeNodeElement>; }
|
||||
interface ddeSVGFEMorphologyElement extends SVGFEMorphologyElement{ append: ddeAppend<ddeSVGFEMorphologyElement>; }
|
||||
interface ddeSVGFEOffsetElement extends SVGFEOffsetElement{ append: ddeAppend<ddeSVGFEOffsetElement>; }
|
||||
interface ddeSVGFEPointLightElement extends SVGFEPointLightElement{ append: ddeAppend<ddeSVGFEPointLightElement>; }
|
||||
interface ddeSVGFESpecularLightingElement extends SVGFESpecularLightingElement{ append: ddeAppend<ddeSVGFESpecularLightingElement>; }
|
||||
interface ddeSVGFESpotLightElement extends SVGFESpotLightElement{ append: ddeAppend<ddeSVGFESpotLightElement>; }
|
||||
interface ddeSVGFETileElement extends SVGFETileElement{ append: ddeAppend<ddeSVGFETileElement>; }
|
||||
interface ddeSVGFETurbulenceElement extends SVGFETurbulenceElement{ append: ddeAppend<ddeSVGFETurbulenceElement>; }
|
||||
interface ddeSVGFilterElement extends SVGFilterElement{ append: ddeAppend<ddeSVGFilterElement>; }
|
||||
interface ddeSVGForeignObjectElement extends SVGForeignObjectElement{ append: ddeAppend<ddeSVGForeignObjectElement>; }
|
||||
interface ddeSVGGElement extends SVGGElement{ append: ddeAppend<ddeSVGGElement>; }
|
||||
interface ddeSVGImageElement extends SVGImageElement{ append: ddeAppend<ddeSVGImageElement>; }
|
||||
interface ddeSVGLineElement extends SVGLineElement{ append: ddeAppend<ddeSVGLineElement>; }
|
||||
interface ddeSVGLinearGradientElement extends SVGLinearGradientElement{ append: ddeAppend<ddeSVGLinearGradientElement>; }
|
||||
interface ddeSVGMarkerElement extends SVGMarkerElement{ append: ddeAppend<ddeSVGMarkerElement>; }
|
||||
interface ddeSVGMaskElement extends SVGMaskElement{ append: ddeAppend<ddeSVGMaskElement>; }
|
||||
interface ddeSVGMetadataElement extends SVGMetadataElement{ append: ddeAppend<ddeSVGMetadataElement>; }
|
||||
interface ddeSVGMPathElement extends SVGMPathElement{ append: ddeAppend<ddeSVGMPathElement>; }
|
||||
interface ddeSVGPathElement extends SVGPathElement{ append: ddeAppend<ddeSVGPathElement>; }
|
||||
interface ddeSVGPatternElement extends SVGPatternElement{ append: ddeAppend<ddeSVGPatternElement>; }
|
||||
interface ddeSVGPolygonElement extends SVGPolygonElement{ append: ddeAppend<ddeSVGPolygonElement>; }
|
||||
interface ddeSVGPolylineElement extends SVGPolylineElement{ append: ddeAppend<ddeSVGPolylineElement>; }
|
||||
interface ddeSVGRadialGradientElement extends SVGRadialGradientElement{ append: ddeAppend<ddeSVGRadialGradientElement>; }
|
||||
interface ddeSVGRectElement extends SVGRectElement{ append: ddeAppend<ddeSVGRectElement>; }
|
||||
interface ddeSVGScriptElement extends SVGScriptElement{ append: ddeAppend<ddeSVGScriptElement>; }
|
||||
interface ddeSVGSetElement extends SVGSetElement{ append: ddeAppend<ddeSVGSetElement>; }
|
||||
interface ddeSVGStopElement extends SVGStopElement{ append: ddeAppend<ddeSVGStopElement>; }
|
||||
interface ddeSVGStyleElement extends SVGStyleElement{ append: ddeAppend<ddeSVGStyleElement>; }
|
||||
interface ddeSVGSVGElement extends SVGSVGElement{ append: ddeAppend<ddeSVGSVGElement>; }
|
||||
interface ddeSVGSwitchElement extends SVGSwitchElement{ append: ddeAppend<ddeSVGSwitchElement>; }
|
||||
interface ddeSVGSymbolElement extends SVGSymbolElement{ append: ddeAppend<ddeSVGSymbolElement>; }
|
||||
interface ddeSVGTextElement extends SVGTextElement{ append: ddeAppend<ddeSVGTextElement>; }
|
||||
interface ddeSVGTextPathElement extends SVGTextPathElement{ append: ddeAppend<ddeSVGTextPathElement>; }
|
||||
interface ddeSVGTitleElement extends SVGTitleElement{ append: ddeAppend<ddeSVGTitleElement>; }
|
||||
interface ddeSVGTSpanElement extends SVGTSpanElement{ append: ddeAppend<ddeSVGTSpanElement>; }
|
||||
interface ddeSVGUseElement extends SVGUseElement{ append: ddeAppend<ddeSVGUseElement>; }
|
||||
interface ddeSVGViewElement extends SVGViewElement{ append: ddeAppend<ddeSVGViewElement>; }
|
||||
// editorconfig-checker-enable
|
9
dist/esm.d.ts
vendored
9
dist/esm.d.ts
vendored
@ -52,9 +52,12 @@ type IsReadonly<T, K extends keyof T> =
|
||||
* @private
|
||||
*/
|
||||
type ElementAttributes<T extends SupportedElement>= Partial<{
|
||||
[K in keyof _fromElsInterfaces<T>]: IsReadonly<_fromElsInterfaces<T>, K> extends false
|
||||
? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
|
||||
: ddeStringable
|
||||
[K in keyof _fromElsInterfaces<T>]:
|
||||
_fromElsInterfaces<T>[K] extends ((...p: any[])=> any)
|
||||
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=> ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
|
||||
: (IsReadonly<_fromElsInterfaces<T>, K> extends false
|
||||
? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
|
||||
: ddeStringable)
|
||||
} & AttrsModified> & Record<string, any>;
|
||||
export function classListDeclarative<El extends SupportedElement>(
|
||||
element: El,
|
||||
|
849
dist/esm.js
vendored
849
dist/esm.js
vendored
@ -1,451 +1,658 @@
|
||||
// src/signals-common.js
|
||||
var C = {
|
||||
isSignal(t) {
|
||||
return !1;
|
||||
},
|
||||
processReactiveAttribute(t, e, r, n) {
|
||||
return r;
|
||||
}
|
||||
};
|
||||
function Z(t, e = !0) {
|
||||
return e ? Object.assign(C, t) : (Object.setPrototypeOf(t, C), t);
|
||||
}
|
||||
function S(t) {
|
||||
return C.isPrototypeOf(t) && t !== C ? t : C;
|
||||
}
|
||||
|
||||
// src/helpers.js
|
||||
function m(t) {
|
||||
return typeof t > "u";
|
||||
function isUndef(value) {
|
||||
return typeof value === "undefined";
|
||||
}
|
||||
function L(t, e) {
|
||||
if (!t || !(t instanceof AbortSignal))
|
||||
return !0;
|
||||
if (!t.aborted)
|
||||
return t.addEventListener("abort", e), function() {
|
||||
t.removeEventListener("abort", e);
|
||||
};
|
||||
function isInstance(obj, cls) {
|
||||
return obj instanceof cls;
|
||||
}
|
||||
function q(t, e) {
|
||||
let { observedAttributes: r = [] } = t.constructor;
|
||||
return r.reduce(function(n, o) {
|
||||
return n[G(o)] = e(t, o), n;
|
||||
function isProtoFrom(obj, cls) {
|
||||
return Object.prototype.isPrototypeOf.call(cls, obj);
|
||||
}
|
||||
function oAssign(...o) {
|
||||
return Object.assign(...o);
|
||||
}
|
||||
function onAbort(signal, listener) {
|
||||
if (!signal || !isInstance(signal, AbortSignal))
|
||||
return true;
|
||||
if (signal.aborted)
|
||||
return;
|
||||
signal.addEventListener("abort", listener);
|
||||
return function cleanUp() {
|
||||
signal.removeEventListener("abort", listener);
|
||||
};
|
||||
}
|
||||
function observedAttributes(instance, observedAttribute) {
|
||||
const { observedAttributes: observedAttributes3 = [] } = instance.constructor;
|
||||
return observedAttributes3.reduce(function(out, name) {
|
||||
out[kebabToCamel(name)] = observedAttribute(instance, name);
|
||||
return out;
|
||||
}, {});
|
||||
}
|
||||
function G(t) {
|
||||
return t.replace(/-./g, (e) => e[1].toUpperCase());
|
||||
function kebabToCamel(name) {
|
||||
return name.replace(/-./g, (x) => x[1].toUpperCase());
|
||||
}
|
||||
|
||||
// src/signals-lib/common.js
|
||||
var signals_global = {
|
||||
/**
|
||||
* Checks if a value is a signal
|
||||
* @param {any} attributes - Value to check
|
||||
* @returns {boolean} Whether the value is a signal
|
||||
*/
|
||||
isSignal(attributes) {
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* Processes an attribute that might be reactive
|
||||
* @param {Element} obj - Element that owns the attribute
|
||||
* @param {string} key - Attribute name
|
||||
* @param {any} attr - Attribute value
|
||||
* @param {Function} set - Function to set the attribute
|
||||
* @returns {any} Processed attribute value
|
||||
*/
|
||||
processReactiveAttribute(obj, key, attr, set) {
|
||||
return attr;
|
||||
}
|
||||
};
|
||||
function registerReactivity(def, global = true) {
|
||||
if (global) return oAssign(signals_global, def);
|
||||
Object.setPrototypeOf(def, signals_global);
|
||||
return def;
|
||||
}
|
||||
function signals(_this) {
|
||||
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
|
||||
}
|
||||
|
||||
// src/dom-common.js
|
||||
var a = {
|
||||
setDeleteAttr: V,
|
||||
var enviroment = {
|
||||
setDeleteAttr,
|
||||
ssr: "",
|
||||
D: globalThis.document,
|
||||
F: globalThis.DocumentFragment,
|
||||
H: globalThis.HTMLElement,
|
||||
S: globalThis.SVGElement,
|
||||
M: globalThis.MutationObserver,
|
||||
q: (t) => t || Promise.resolve()
|
||||
q: (p) => p || Promise.resolve()
|
||||
};
|
||||
function V(t, e, r) {
|
||||
if (Reflect.set(t, e, r), !!m(r)) {
|
||||
if (Reflect.deleteProperty(t, e), t instanceof a.H && t.getAttribute(e) === "undefined")
|
||||
return t.removeAttribute(e);
|
||||
if (Reflect.get(t, e) === "undefined")
|
||||
return Reflect.set(t, e, "");
|
||||
}
|
||||
function setDeleteAttr(obj, prop, val) {
|
||||
Reflect.set(obj, prop, val);
|
||||
if (!isUndef(val)) return;
|
||||
Reflect.deleteProperty(obj, prop);
|
||||
if (isInstance(obj, enviroment.H) && obj.getAttribute(prop) === "undefined")
|
||||
return obj.removeAttribute(prop);
|
||||
if (Reflect.get(obj, prop) === "undefined")
|
||||
return Reflect.set(obj, prop, "");
|
||||
}
|
||||
var x = "__dde_lifecyclesToEvents", v = "dde:connected", w = "dde:disconnected", y = "dde:attributeChanged";
|
||||
var keyLTE = "__dde_lifecyclesToEvents";
|
||||
var evc = "dde:connected";
|
||||
var evd = "dde:disconnected";
|
||||
var eva = "dde:attributeChanged";
|
||||
|
||||
// src/dom.js
|
||||
function dt(t) {
|
||||
return a.q(t);
|
||||
function queue(promise) {
|
||||
return enviroment.q(promise);
|
||||
}
|
||||
var g = [{
|
||||
var scopes = [{
|
||||
get scope() {
|
||||
return a.D.body;
|
||||
return enviroment.D.body;
|
||||
},
|
||||
host: (t) => t ? t(a.D.body) : a.D.body,
|
||||
prevent: !0
|
||||
}], O = {
|
||||
host: (c) => c ? c(enviroment.D.body) : enviroment.D.body,
|
||||
prevent: true
|
||||
}];
|
||||
var scope = {
|
||||
/**
|
||||
* Gets the current scope
|
||||
* @returns {Object} Current scope context
|
||||
*/
|
||||
get current() {
|
||||
return g[g.length - 1];
|
||||
return scopes[scopes.length - 1];
|
||||
},
|
||||
/**
|
||||
* Gets the host element of the current scope
|
||||
* @returns {Function} Host accessor function
|
||||
*/
|
||||
get host() {
|
||||
return this.current.host;
|
||||
},
|
||||
/**
|
||||
* Prevents default behavior in the current scope
|
||||
* @returns {Object} Current scope context
|
||||
*/
|
||||
preventDefault() {
|
||||
let { current: t } = this;
|
||||
return t.prevent = !0, t;
|
||||
const { current } = this;
|
||||
current.prevent = true;
|
||||
return current;
|
||||
},
|
||||
/**
|
||||
* Gets a copy of the current scope stack
|
||||
* @returns {Array} Copy of scope stack
|
||||
*/
|
||||
get state() {
|
||||
return [...g];
|
||||
return [...scopes];
|
||||
},
|
||||
push(t = {}) {
|
||||
return g.push(Object.assign({}, this.current, { prevent: !1 }, t));
|
||||
/**
|
||||
* Pushes a new scope to the stack
|
||||
* @param {Object} [s={}] - Scope object to push
|
||||
* @returns {number} New length of the scope stack
|
||||
*/
|
||||
push(s = {}) {
|
||||
return scopes.push(oAssign({}, this.current, { prevent: false }, s));
|
||||
},
|
||||
/**
|
||||
* Pushes the root scope to the stack
|
||||
* @returns {number} New length of the scope stack
|
||||
*/
|
||||
pushRoot() {
|
||||
return g.push(g[0]);
|
||||
return scopes.push(scopes[0]);
|
||||
},
|
||||
/**
|
||||
* Pops the current scope from the stack
|
||||
* @returns {Object|undefined} Popped scope or undefined if only one scope remains
|
||||
*/
|
||||
pop() {
|
||||
if (g.length !== 1)
|
||||
return g.pop();
|
||||
if (scopes.length === 1) return;
|
||||
return scopes.pop();
|
||||
}
|
||||
};
|
||||
function k(...t) {
|
||||
return this.appendOriginal(...t), this;
|
||||
function append(...els) {
|
||||
this.appendOriginal(...els);
|
||||
return this;
|
||||
}
|
||||
function J(t) {
|
||||
return t.append === k || (t.appendOriginal = t.append, t.append = k), t;
|
||||
function chainableAppend(el) {
|
||||
if (el.append === append) return el;
|
||||
el.appendOriginal = el.append;
|
||||
el.append = append;
|
||||
return el;
|
||||
}
|
||||
var T;
|
||||
function P(t, e, ...r) {
|
||||
let n = S(this), o = 0, c, d;
|
||||
switch ((Object(e) !== e || n.isSignal(e)) && (e = { textContent: e }), !0) {
|
||||
case typeof t == "function": {
|
||||
o = 1;
|
||||
let f = (...l) => l.length ? (o === 1 ? r.unshift(...l) : l.forEach((E) => E(d)), void 0) : d;
|
||||
O.push({ scope: t, host: f }), c = t(e || void 0);
|
||||
let p = c instanceof a.F;
|
||||
if (c.nodeName === "#comment") break;
|
||||
let b = P.mark({
|
||||
var namespace;
|
||||
function createElement(tag, attributes, ...addons) {
|
||||
const s = signals(this);
|
||||
let scoped = 0;
|
||||
let el, el_host;
|
||||
if (Object(attributes) !== attributes || s.isSignal(attributes))
|
||||
attributes = { textContent: attributes };
|
||||
switch (true) {
|
||||
case typeof tag === "function": {
|
||||
scoped = 1;
|
||||
const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0);
|
||||
scope.push({ scope: tag, host });
|
||||
el = tag(attributes || void 0);
|
||||
const is_fragment = isInstance(el, enviroment.F);
|
||||
if (el.nodeName === "#comment") break;
|
||||
const el_mark = createElement.mark({
|
||||
type: "component",
|
||||
name: t.name,
|
||||
host: p ? "this" : "parentElement"
|
||||
name: tag.name,
|
||||
host: is_fragment ? "this" : "parentElement"
|
||||
});
|
||||
c.prepend(b), p && (d = b);
|
||||
el.prepend(el_mark);
|
||||
if (is_fragment) el_host = el_mark;
|
||||
break;
|
||||
}
|
||||
case t === "#text":
|
||||
c = R.call(this, a.D.createTextNode(""), e);
|
||||
case tag === "#text":
|
||||
el = assign.call(this, enviroment.D.createTextNode(""), attributes);
|
||||
break;
|
||||
case (t === "<>" || !t):
|
||||
c = R.call(this, a.D.createDocumentFragment(), e);
|
||||
case (tag === "<>" || !tag):
|
||||
el = assign.call(this, enviroment.D.createDocumentFragment(), attributes);
|
||||
break;
|
||||
case !!T:
|
||||
c = R.call(this, a.D.createElementNS(T, t), e);
|
||||
case Boolean(namespace):
|
||||
el = assign.call(this, enviroment.D.createElementNS(namespace, tag), attributes);
|
||||
break;
|
||||
case !c:
|
||||
c = R.call(this, a.D.createElement(t), e);
|
||||
case !el:
|
||||
el = assign.call(this, enviroment.D.createElement(tag), attributes);
|
||||
}
|
||||
return J(c), d || (d = c), r.forEach((f) => f(d)), o && O.pop(), o = 2, c;
|
||||
chainableAppend(el);
|
||||
if (!el_host) el_host = el;
|
||||
addons.forEach((c) => c(el_host));
|
||||
if (scoped) scope.pop();
|
||||
scoped = 2;
|
||||
return el;
|
||||
}
|
||||
P.mark = function(t, e = !1) {
|
||||
t = Object.entries(t).map(([o, c]) => o + `="${c}"`).join(" ");
|
||||
let r = e ? "" : "/", n = a.D.createComment(`<dde:mark ${t}${a.ssr}${r}>`);
|
||||
return e && (n.end = a.D.createComment("</dde:mark>")), n;
|
||||
createElement.mark = function(attrs, is_open = false) {
|
||||
attrs = Object.entries(attrs).map(([n, v]) => n + `="${v}"`).join(" ");
|
||||
const end = is_open ? "" : "/";
|
||||
const out = enviroment.D.createComment(`<dde:mark ${attrs}${enviroment.ssr}${end}>`);
|
||||
if (is_open) out.end = enviroment.D.createComment("</dde:mark>");
|
||||
return out;
|
||||
};
|
||||
function pt(t) {
|
||||
let e = this;
|
||||
return function(...n) {
|
||||
T = t;
|
||||
let o = P.call(e, ...n);
|
||||
return T = void 0, o;
|
||||
function createElementNS(ns) {
|
||||
const _this = this;
|
||||
return function createElementNSCurried(...rest) {
|
||||
namespace = ns;
|
||||
const el = createElement.call(_this, ...rest);
|
||||
namespace = void 0;
|
||||
return el;
|
||||
};
|
||||
}
|
||||
function lt(t, e = t) {
|
||||
let r = "\xB9\u2070", n = "\u2713", o = Object.fromEntries(
|
||||
Array.from(e.querySelectorAll("slot")).filter((c) => !c.name.endsWith(r)).map((c) => [c.name += r, c])
|
||||
function simulateSlots(element, root = element) {
|
||||
const mark_e = "\xB9\u2070", mark_s = "\u2713";
|
||||
const slots = Object.fromEntries(
|
||||
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
|
||||
);
|
||||
if (t.append = new Proxy(t.append, {
|
||||
apply(c, d, f) {
|
||||
if (f[0] === e) return c.apply(t, f);
|
||||
for (let p of f) {
|
||||
let b = (p.slot || "") + r;
|
||||
element.append = new Proxy(element.append, {
|
||||
apply(orig, _, els) {
|
||||
if (els[0] === root) return orig.apply(element, els);
|
||||
for (const el of els) {
|
||||
const name = (el.slot || "") + mark_e;
|
||||
try {
|
||||
Q(p, "remove", "slot");
|
||||
} catch {
|
||||
elementAttribute(el, "remove", "slot");
|
||||
} catch (_error) {
|
||||
}
|
||||
let l = o[b];
|
||||
if (!l) return;
|
||||
l.name.startsWith(n) || (l.childNodes.forEach((E) => E.remove()), l.name = n + b), l.append(p);
|
||||
const slot = slots[name];
|
||||
if (!slot) return;
|
||||
if (!slot.name.startsWith(mark_s)) {
|
||||
slot.childNodes.forEach((c) => c.remove());
|
||||
slot.name = mark_s + name;
|
||||
}
|
||||
slot.append(el);
|
||||
}
|
||||
return t.append = c, t;
|
||||
element.append = orig;
|
||||
return element;
|
||||
}
|
||||
}), t !== e) {
|
||||
let c = Array.from(t.childNodes);
|
||||
t.append(...c);
|
||||
});
|
||||
if (element !== root) {
|
||||
const els = Array.from(element.childNodes);
|
||||
element.append(...els);
|
||||
}
|
||||
return e;
|
||||
return root;
|
||||
}
|
||||
var N = /* @__PURE__ */ new WeakMap(), { setDeleteAttr: $ } = a;
|
||||
function R(t, ...e) {
|
||||
if (!e.length) return t;
|
||||
N.set(t, H(t, this));
|
||||
for (let [r, n] of Object.entries(Object.assign({}, ...e)))
|
||||
U.call(this, t, r, n);
|
||||
return N.delete(t), t;
|
||||
var assign_context = /* @__PURE__ */ new WeakMap();
|
||||
var { setDeleteAttr: setDeleteAttr2 } = enviroment;
|
||||
function assign(element, ...attributes) {
|
||||
if (!attributes.length) return element;
|
||||
assign_context.set(element, assignContext(element, this));
|
||||
for (const [key, value] of Object.entries(oAssign({}, ...attributes)))
|
||||
assignAttribute.call(this, element, key, value);
|
||||
assign_context.delete(element);
|
||||
return element;
|
||||
}
|
||||
function U(t, e, r) {
|
||||
let { setRemoveAttr: n, s: o } = H(t, this), c = this;
|
||||
r = o.processReactiveAttribute(
|
||||
t,
|
||||
e,
|
||||
r,
|
||||
(f, p) => U.call(c, t, f, p)
|
||||
function assignAttribute(element, key, value) {
|
||||
const { setRemoveAttr, s } = assignContext(element, this);
|
||||
const _this = this;
|
||||
value = s.processReactiveAttribute(
|
||||
element,
|
||||
key,
|
||||
value,
|
||||
(key2, value2) => assignAttribute.call(_this, element, key2, value2)
|
||||
);
|
||||
let [d] = e;
|
||||
if (d === "=") return n(e.slice(1), r);
|
||||
if (d === ".") return F(t, e.slice(1), r);
|
||||
if (/(aria|data)([A-Z])/.test(e))
|
||||
return e = e.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(), n(e, r);
|
||||
switch (e === "className" && (e = "class"), e) {
|
||||
const [k] = key;
|
||||
if ("=" === k) return setRemoveAttr(key.slice(1), value);
|
||||
if ("." === k) return setDelete(element, key.slice(1), value);
|
||||
if (/(aria|data)([A-Z])/.test(key)) {
|
||||
key = key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
||||
return setRemoveAttr(key, value);
|
||||
}
|
||||
if ("className" === key) key = "class";
|
||||
switch (key) {
|
||||
case "xlink:href":
|
||||
return n(e, r, "http://www.w3.org/1999/xlink");
|
||||
return setRemoveAttr(key, value, "http://www.w3.org/1999/xlink");
|
||||
case "textContent":
|
||||
return $(t, e, r);
|
||||
return setDeleteAttr2(element, key, value);
|
||||
case "style":
|
||||
if (typeof r != "object") break;
|
||||
if (typeof value !== "object") break;
|
||||
/* falls through */
|
||||
case "dataset":
|
||||
return M(o, e, t, r, F.bind(null, t[e]));
|
||||
return forEachEntries(s, key, element, value, setDelete.bind(null, element[key]));
|
||||
case "ariaset":
|
||||
return M(o, e, t, r, (f, p) => n("aria-" + f, p));
|
||||
return forEachEntries(s, key, element, value, (key2, val) => setRemoveAttr("aria-" + key2, val));
|
||||
case "classList":
|
||||
return K.call(c, t, r);
|
||||
return classListDeclarative.call(_this, element, value);
|
||||
}
|
||||
return X(t, e) ? $(t, e, r) : n(e, r);
|
||||
return isPropSetter(element, key) ? setDeleteAttr2(element, key, value) : setRemoveAttr(key, value);
|
||||
}
|
||||
function H(t, e) {
|
||||
if (N.has(t)) return N.get(t);
|
||||
let n = (t instanceof a.S ? tt : Y).bind(null, t, "Attribute"), o = S(e);
|
||||
return { setRemoveAttr: n, s: o };
|
||||
function assignContext(element, _this) {
|
||||
if (assign_context.has(element)) return assign_context.get(element);
|
||||
const is_svg = isInstance(element, enviroment.S);
|
||||
const setRemoveAttr = (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
|
||||
const s = signals(_this);
|
||||
return { setRemoveAttr, s };
|
||||
}
|
||||
function K(t, e) {
|
||||
let r = S(this);
|
||||
return M(
|
||||
r,
|
||||
function classListDeclarative(element, toggle) {
|
||||
const s = signals(this);
|
||||
forEachEntries(
|
||||
s,
|
||||
"classList",
|
||||
t,
|
||||
e,
|
||||
(n, o) => t.classList.toggle(n, o === -1 ? void 0 : !!o)
|
||||
), t;
|
||||
element,
|
||||
toggle,
|
||||
(class_name, val) => element.classList.toggle(class_name, val === -1 ? void 0 : Boolean(val))
|
||||
);
|
||||
return element;
|
||||
}
|
||||
function Q(t, e, r, n) {
|
||||
return t instanceof a.H ? t[e + "Attribute"](r, n) : t[e + "AttributeNS"](null, r, n);
|
||||
function elementAttribute(element, op, key, value) {
|
||||
if (isInstance(element, enviroment.H))
|
||||
return element[op + "Attribute"](key, value);
|
||||
return element[op + "AttributeNS"](null, key, value);
|
||||
}
|
||||
function X(t, e) {
|
||||
if (!(e in t)) return !1;
|
||||
let r = z(t, e);
|
||||
return !m(r.set);
|
||||
function isPropSetter(el, key) {
|
||||
if (!(key in el)) return false;
|
||||
const des = getPropDescriptor(el, key);
|
||||
return !isUndef(des.set);
|
||||
}
|
||||
function z(t, e) {
|
||||
if (t = Object.getPrototypeOf(t), !t) return {};
|
||||
let r = Object.getOwnPropertyDescriptor(t, e);
|
||||
return r || z(t, e);
|
||||
function getPropDescriptor(p, key) {
|
||||
p = Object.getPrototypeOf(p);
|
||||
if (!p) return {};
|
||||
const des = Object.getOwnPropertyDescriptor(p, key);
|
||||
if (!des) return getPropDescriptor(p, key);
|
||||
return des;
|
||||
}
|
||||
function M(t, e, r, n, o) {
|
||||
let c = String;
|
||||
if (!(typeof n != "object" || n === null))
|
||||
return Object.entries(n).forEach(function([f, p]) {
|
||||
f && (f = new c(f), f.target = e, p = t.processReactiveAttribute(r, f, p, o), o(f, p));
|
||||
});
|
||||
function forEachEntries(s, target, element, obj, cb) {
|
||||
const S = String;
|
||||
if (typeof obj !== "object" || obj === null) return;
|
||||
return Object.entries(obj).forEach(function process([key, val]) {
|
||||
if (!key) return;
|
||||
key = new S(key);
|
||||
key.target = target;
|
||||
val = s.processReactiveAttribute(element, key, val, cb);
|
||||
cb(key, val);
|
||||
});
|
||||
}
|
||||
function Y(t, e, r, n) {
|
||||
return t[(m(n) ? "remove" : "set") + e](r, n);
|
||||
function setRemove(obj, prop, key, val) {
|
||||
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
|
||||
}
|
||||
function tt(t, e, r, n, o = null) {
|
||||
return t[(m(n) ? "remove" : "set") + e + "NS"](o, r, n);
|
||||
function setRemoveNS(obj, prop, key, val, ns = null) {
|
||||
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
|
||||
}
|
||||
function F(t, e, r) {
|
||||
if (Reflect.set(t, e, r), !!m(r))
|
||||
return Reflect.deleteProperty(t, e);
|
||||
function setDelete(obj, key, val) {
|
||||
Reflect.set(obj, key, val);
|
||||
if (!isUndef(val)) return;
|
||||
return Reflect.deleteProperty(obj, key);
|
||||
}
|
||||
|
||||
// src/events-observer.js
|
||||
var _ = a.M ? et() : new Proxy({}, {
|
||||
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
|
||||
get() {
|
||||
return () => {
|
||||
};
|
||||
}
|
||||
});
|
||||
function et() {
|
||||
let t = /* @__PURE__ */ new Map(), e = !1, r = (s) => function(u) {
|
||||
for (let i of u)
|
||||
if (i.type === "childList") {
|
||||
if (l(i.addedNodes, !0)) {
|
||||
s();
|
||||
continue;
|
||||
}
|
||||
E(i.removedNodes, !0) && s();
|
||||
function connectionsChangesObserverConstructor() {
|
||||
const store = /* @__PURE__ */ new Map();
|
||||
let is_observing = false;
|
||||
const observerListener = (stop2) => function(mutations) {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type !== "childList") continue;
|
||||
if (observerAdded(mutation.addedNodes, true)) {
|
||||
stop2();
|
||||
continue;
|
||||
}
|
||||
}, n = new a.M(r(f));
|
||||
return {
|
||||
observe(s) {
|
||||
let u = new a.M(r(() => {
|
||||
}));
|
||||
return u.observe(s, { childList: !0, subtree: !0 }), () => u.disconnect();
|
||||
},
|
||||
onConnected(s, u) {
|
||||
d();
|
||||
let i = c(s);
|
||||
i.connected.has(u) || (i.connected.add(u), i.length_c += 1);
|
||||
},
|
||||
offConnected(s, u) {
|
||||
if (!t.has(s)) return;
|
||||
let i = t.get(s);
|
||||
i.connected.has(u) && (i.connected.delete(u), i.length_c -= 1, o(s, i));
|
||||
},
|
||||
onDisconnected(s, u) {
|
||||
d();
|
||||
let i = c(s);
|
||||
i.disconnected.has(u) || (i.disconnected.add(u), i.length_d += 1);
|
||||
},
|
||||
offDisconnected(s, u) {
|
||||
if (!t.has(s)) return;
|
||||
let i = t.get(s);
|
||||
i.disconnected.has(u) && (i.disconnected.delete(u), i.length_d -= 1, o(s, i));
|
||||
if (observerRemoved(mutation.removedNodes, true))
|
||||
stop2();
|
||||
}
|
||||
};
|
||||
function o(s, u) {
|
||||
u.length_c || u.length_d || (t.delete(s), f());
|
||||
const observer = new enviroment.M(observerListener(stop));
|
||||
return {
|
||||
/**
|
||||
* Creates an observer for a specific element
|
||||
* @param {Element} element - Element to observe
|
||||
* @returns {Function} Cleanup function
|
||||
*/
|
||||
observe(element) {
|
||||
const o = new enviroment.M(observerListener(() => {
|
||||
}));
|
||||
o.observe(element, { childList: true, subtree: true });
|
||||
return () => o.disconnect();
|
||||
},
|
||||
/**
|
||||
* Register a connection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for connection event
|
||||
*/
|
||||
onConnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.connected.has(listener)) return;
|
||||
listeners.connected.add(listener);
|
||||
listeners.length_c += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a connection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offConnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
if (!ls.connected.has(listener)) return;
|
||||
ls.connected.delete(listener);
|
||||
ls.length_c -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
},
|
||||
/**
|
||||
* Register a disconnection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for disconnection event
|
||||
*/
|
||||
onDisconnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.disconnected.has(listener)) return;
|
||||
listeners.disconnected.add(listener);
|
||||
listeners.length_d += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a disconnection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offDisconnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
ls.disconnected.delete(listener);
|
||||
ls.length_d -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
}
|
||||
};
|
||||
function cleanWhenOff(element, ls) {
|
||||
if (ls.length_c || ls.length_d)
|
||||
return;
|
||||
store.delete(element);
|
||||
stop();
|
||||
}
|
||||
function c(s) {
|
||||
if (t.has(s)) return t.get(s);
|
||||
let u = {
|
||||
function getElementStore(element) {
|
||||
if (store.has(element)) return store.get(element);
|
||||
const out = {
|
||||
connected: /* @__PURE__ */ new WeakSet(),
|
||||
length_c: 0,
|
||||
disconnected: /* @__PURE__ */ new WeakSet(),
|
||||
length_d: 0
|
||||
};
|
||||
return t.set(s, u), u;
|
||||
store.set(element, out);
|
||||
return out;
|
||||
}
|
||||
function d() {
|
||||
e || (e = !0, n.observe(a.D.body, { childList: !0, subtree: !0 }));
|
||||
function start() {
|
||||
if (is_observing) return;
|
||||
is_observing = true;
|
||||
observer.observe(enviroment.D.body, { childList: true, subtree: true });
|
||||
}
|
||||
function f() {
|
||||
!e || t.size || (e = !1, n.disconnect());
|
||||
function stop() {
|
||||
if (!is_observing || store.size) return;
|
||||
is_observing = false;
|
||||
observer.disconnect();
|
||||
}
|
||||
function p() {
|
||||
return new Promise(function(s) {
|
||||
(requestIdleCallback || requestAnimationFrame)(s);
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
async function b(s) {
|
||||
t.size > 30 && await p();
|
||||
let u = [];
|
||||
if (!(s instanceof Node)) return u;
|
||||
for (let i of t.keys())
|
||||
i === s || !(i instanceof Node) || s.contains(i) && u.push(i);
|
||||
return u;
|
||||
}
|
||||
function l(s, u) {
|
||||
let i = !1;
|
||||
for (let h of s) {
|
||||
if (u && b(h).then(l), !t.has(h)) continue;
|
||||
let A = t.get(h);
|
||||
A.length_c && (h.dispatchEvent(new Event(v)), A.connected = /* @__PURE__ */ new WeakSet(), A.length_c = 0, A.length_d || t.delete(h), i = !0);
|
||||
async function collectChildren(element) {
|
||||
if (store.size > 30)
|
||||
await requestIdle();
|
||||
const out = [];
|
||||
if (!isInstance(element, Node)) return out;
|
||||
for (const el of store.keys()) {
|
||||
if (el === element || !isInstance(el, Node)) continue;
|
||||
if (element.contains(el))
|
||||
out.push(el);
|
||||
}
|
||||
return i;
|
||||
return out;
|
||||
}
|
||||
function E(s, u) {
|
||||
let i = !1;
|
||||
for (let h of s)
|
||||
u && b(h).then(E), !(!t.has(h) || !t.get(h).length_d) && ((globalThis.queueMicrotask || setTimeout)(I(h)), i = !0);
|
||||
return i;
|
||||
function observerAdded(addedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of addedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerAdded);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_c) continue;
|
||||
element.dispatchEvent(new Event(evc));
|
||||
ls.connected = /* @__PURE__ */ new WeakSet();
|
||||
ls.length_c = 0;
|
||||
if (!ls.length_d) store.delete(element);
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function I(s) {
|
||||
function observerRemoved(removedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of removedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerRemoved);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_d) continue;
|
||||
(globalThis.queueMicrotask || setTimeout)(dispatchRemove(element));
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function dispatchRemove(element) {
|
||||
return () => {
|
||||
s.isConnected || (s.dispatchEvent(new Event(w)), t.delete(s));
|
||||
if (element.isConnected) return;
|
||||
element.dispatchEvent(new Event(evd));
|
||||
store.delete(element);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// src/customElement.js
|
||||
function wt(t, e, r = rt) {
|
||||
let n = t.host || t;
|
||||
O.push({
|
||||
scope: n,
|
||||
host: (...d) => d.length ? d.forEach((f) => f(n)) : n
|
||||
}), typeof r == "function" && (r = r.call(n, n));
|
||||
let o = n[x];
|
||||
o || nt(n);
|
||||
let c = e.call(n, r);
|
||||
return o || n.dispatchEvent(new Event(v)), t.nodeType === 11 && typeof t.mode == "string" && n.addEventListener(w, _.observe(t), { once: !0 }), O.pop(), t.append(c);
|
||||
function customElementRender(target, render, props = observedAttributes2) {
|
||||
const custom_element = target.host || target;
|
||||
scope.push({
|
||||
scope: custom_element,
|
||||
host: (...c) => c.length ? c.forEach((c2) => c2(custom_element)) : custom_element
|
||||
});
|
||||
if (typeof props === "function") props = props.call(custom_element, custom_element);
|
||||
const is_lte = custom_element[keyLTE];
|
||||
if (!is_lte) lifecyclesToEvents(custom_element);
|
||||
const out = render.call(custom_element, props);
|
||||
if (!is_lte) custom_element.dispatchEvent(new Event(evc));
|
||||
if (target.nodeType === 11 && typeof target.mode === "string")
|
||||
custom_element.addEventListener(evd, c_ch_o.observe(target), { once: true });
|
||||
scope.pop();
|
||||
return target.append(out);
|
||||
}
|
||||
function nt(t) {
|
||||
return j(t.prototype, "connectedCallback", function(e, r, n) {
|
||||
e.apply(r, n), r.dispatchEvent(new Event(v));
|
||||
}), j(t.prototype, "disconnectedCallback", function(e, r, n) {
|
||||
e.apply(r, n), (globalThis.queueMicrotask || setTimeout)(
|
||||
() => !r.isConnected && r.dispatchEvent(new Event(w))
|
||||
function lifecyclesToEvents(class_declaration) {
|
||||
wrapMethod(class_declaration.prototype, "connectedCallback", function(target, thisArg, detail) {
|
||||
target.apply(thisArg, detail);
|
||||
thisArg.dispatchEvent(new Event(evc));
|
||||
});
|
||||
wrapMethod(class_declaration.prototype, "disconnectedCallback", function(target, thisArg, detail) {
|
||||
target.apply(thisArg, detail);
|
||||
(globalThis.queueMicrotask || setTimeout)(
|
||||
() => !thisArg.isConnected && thisArg.dispatchEvent(new Event(evd))
|
||||
);
|
||||
}), j(t.prototype, "attributeChangedCallback", function(e, r, n) {
|
||||
let [o, , c] = n;
|
||||
r.dispatchEvent(new CustomEvent(y, {
|
||||
detail: [o, c]
|
||||
})), e.apply(r, n);
|
||||
}), t.prototype[x] = !0, t;
|
||||
});
|
||||
wrapMethod(class_declaration.prototype, "attributeChangedCallback", function(target, thisArg, detail) {
|
||||
const [attribute, , value] = detail;
|
||||
thisArg.dispatchEvent(new CustomEvent(eva, {
|
||||
detail: [attribute, value]
|
||||
}));
|
||||
target.apply(thisArg, detail);
|
||||
});
|
||||
class_declaration.prototype[keyLTE] = true;
|
||||
return class_declaration;
|
||||
}
|
||||
function j(t, e, r) {
|
||||
t[e] = new Proxy(t[e] || (() => {
|
||||
}), { apply: r });
|
||||
function wrapMethod(obj, method, apply) {
|
||||
obj[method] = new Proxy(obj[method] || (() => {
|
||||
}), { apply });
|
||||
}
|
||||
function rt(t) {
|
||||
return q(t, (e, r) => e.getAttribute(r));
|
||||
function observedAttributes2(instance) {
|
||||
return observedAttributes(instance, (i, n) => i.getAttribute(n));
|
||||
}
|
||||
|
||||
// src/events.js
|
||||
function yt(t, e, r) {
|
||||
return e || (e = {}), function(o, ...c) {
|
||||
r && (c.unshift(o), o = typeof r == "function" ? r() : r);
|
||||
let d = c.length ? new CustomEvent(t, Object.assign({ detail: c[0] }, e)) : new Event(t, e);
|
||||
return o.dispatchEvent(d);
|
||||
function dispatchEvent(name, options, host) {
|
||||
if (!options) options = {};
|
||||
return function dispatch(element, ...d) {
|
||||
if (host) {
|
||||
d.unshift(element);
|
||||
element = typeof host === "function" ? host() : host;
|
||||
}
|
||||
const event = d.length ? new CustomEvent(name, oAssign({ detail: d[0] }, options)) : new Event(name, options);
|
||||
return element.dispatchEvent(event);
|
||||
};
|
||||
}
|
||||
function D(t, e, r) {
|
||||
return function(o) {
|
||||
return o.addEventListener(t, e, r), o;
|
||||
function on(event, listener, options) {
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(event, listener, options);
|
||||
return element;
|
||||
};
|
||||
}
|
||||
var B = (t) => Object.assign({}, typeof t == "object" ? t : null, { once: !0 });
|
||||
D.connected = function(t, e) {
|
||||
return e = B(e), function(n) {
|
||||
return n.addEventListener(v, t, e), n[x] ? n : n.isConnected ? (n.dispatchEvent(new Event(v)), n) : (L(e.signal, () => _.offConnected(n, t)) && _.onConnected(n, t), n);
|
||||
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
|
||||
on.connected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evc, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
if (element.isConnected) return element.dispatchEvent(new Event(evc)), element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offConnected(element, listener));
|
||||
if (c) c_ch_o.onConnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
D.disconnected = function(t, e) {
|
||||
return e = B(e), function(n) {
|
||||
return n.addEventListener(w, t, e), n[x] || L(e.signal, () => _.offDisconnected(n, t)) && _.onDisconnected(n, t), n;
|
||||
on.disconnected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evd, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offDisconnected(element, listener));
|
||||
if (c) c_ch_o.onDisconnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
var W = /* @__PURE__ */ new WeakMap();
|
||||
D.disconnectedAsAbort = function(t) {
|
||||
if (W.has(t)) return W.get(t);
|
||||
let e = new AbortController();
|
||||
return W.set(t, e), t(D.disconnected(() => e.abort())), e;
|
||||
var store_abort = /* @__PURE__ */ new WeakMap();
|
||||
on.disconnectedAsAbort = function(host) {
|
||||
if (store_abort.has(host)) return store_abort.get(host);
|
||||
const a = new AbortController();
|
||||
store_abort.set(host, a);
|
||||
host(on.disconnected(() => a.abort()));
|
||||
return a;
|
||||
};
|
||||
var ot = /* @__PURE__ */ new WeakSet();
|
||||
D.attributeChanged = function(t, e) {
|
||||
return typeof e != "object" && (e = {}), function(n) {
|
||||
if (n.addEventListener(y, t, e), n[x] || ot.has(n) || !a.M) return n;
|
||||
let o = new a.M(function(d) {
|
||||
for (let { attributeName: f, target: p } of d)
|
||||
p.dispatchEvent(
|
||||
new CustomEvent(y, { detail: [f, p.getAttribute(f)] })
|
||||
var els_attribute_store = /* @__PURE__ */ new WeakSet();
|
||||
on.attributeChanged = function(listener, options) {
|
||||
if (typeof options !== "object")
|
||||
options = {};
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(eva, listener, options);
|
||||
if (element[keyLTE] || els_attribute_store.has(element))
|
||||
return element;
|
||||
if (!enviroment.M) return element;
|
||||
const observer = new enviroment.M(function(mutations) {
|
||||
for (const { attributeName, target } of mutations)
|
||||
target.dispatchEvent(
|
||||
new CustomEvent(eva, { detail: [attributeName, target.getAttribute(attributeName)] })
|
||||
);
|
||||
});
|
||||
return L(e.signal, () => o.disconnect()) && o.observe(n, { attributes: !0 }), n;
|
||||
const c = onAbort(options.signal, () => observer.disconnect());
|
||||
if (c) observer.observe(element, { attributes: true });
|
||||
return element;
|
||||
};
|
||||
};
|
||||
export {
|
||||
R as assign,
|
||||
U as assignAttribute,
|
||||
J as chainableAppend,
|
||||
K as classListDeclarative,
|
||||
P as createElement,
|
||||
pt as createElementNS,
|
||||
wt as customElementRender,
|
||||
nt as customElementWithDDE,
|
||||
yt as dispatchEvent,
|
||||
P as el,
|
||||
pt as elNS,
|
||||
Q as elementAttribute,
|
||||
nt as lifecyclesToEvents,
|
||||
rt as observedAttributes,
|
||||
D as on,
|
||||
dt as queue,
|
||||
Z as registerReactivity,
|
||||
O as scope,
|
||||
lt as simulateSlots
|
||||
assign,
|
||||
assignAttribute,
|
||||
chainableAppend,
|
||||
classListDeclarative,
|
||||
createElement,
|
||||
createElementNS,
|
||||
customElementRender,
|
||||
lifecyclesToEvents as customElementWithDDE,
|
||||
dispatchEvent,
|
||||
createElement as el,
|
||||
createElementNS as elNS,
|
||||
elementAttribute,
|
||||
lifecyclesToEvents,
|
||||
observedAttributes2 as observedAttributes,
|
||||
on,
|
||||
queue,
|
||||
registerReactivity,
|
||||
scope,
|
||||
simulateSlots
|
||||
};
|
||||
|
1
dist/esm.min.js
vendored
Normal file
1
dist/esm.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1,28 +1,179 @@
|
||||
import { registerClientFile, styles } from "../ssr.js";
|
||||
const host= "."+code.name;
|
||||
styles.css`
|
||||
${host}{
|
||||
--shiki-color-text: #e9eded;
|
||||
--shiki-color-background: #212121;
|
||||
/* Code block styling */
|
||||
${host} {
|
||||
/* Theme for dark mode - matches Flems/CodeMirror dark theme */
|
||||
--shiki-color-text: #f8f8f2;
|
||||
--shiki-color-background: var(--code-bg);
|
||||
--shiki-token-constant: #82b1ff;
|
||||
--shiki-token-string: #c3e88d;
|
||||
--shiki-token-comment: #546e7a;
|
||||
--shiki-token-keyword: #c792ea;
|
||||
--shiki-token-parameter: #AA0000;
|
||||
--shiki-token-parameter: #fd971f;
|
||||
--shiki-token-function: #80cbae;
|
||||
--shiki-token-string-expression: #c3e88d;
|
||||
--shiki-token-punctuation: var(--code);
|
||||
--shiki-token-link: #EE0000;
|
||||
--shiki-token-punctuation: #89ddff;
|
||||
--shiki-token-link: #82aaff;
|
||||
--shiki-token-variable: #f8f8f2;
|
||||
--shiki-token-number: #f78c6c;
|
||||
--shiki-token-boolean: #82b1ff;
|
||||
--shiki-token-tag: #f07178;
|
||||
--shiki-token-attribute: #ffcb6b;
|
||||
--shiki-token-property: #82b1ff;
|
||||
--shiki-token-operator: #89ddff;
|
||||
--shiki-token-regex: #c3e88d;
|
||||
--shiki-token-class: #ffcb6b;
|
||||
|
||||
/* Basic styling */
|
||||
white-space: pre;
|
||||
tab-size: 2; /* TODO: allow custom tab size?! */
|
||||
overflow: scroll;
|
||||
tab-size: 2;
|
||||
overflow: auto;
|
||||
border-radius: var(--border-radius);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.5;
|
||||
position: relative;
|
||||
margin-block: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
${host}[data-js=todo]{
|
||||
|
||||
/* Light mode overrides to match GitHub-like theme */
|
||||
@media (prefers-color-scheme: light) {
|
||||
${host} {
|
||||
--shiki-color-text: #24292e;
|
||||
--shiki-color-background: var(--code-bg);
|
||||
--shiki-token-constant: #005cc5;
|
||||
--shiki-token-string: #22863a;
|
||||
--shiki-token-comment: #6a737d;
|
||||
--shiki-token-keyword: #d73a49;
|
||||
--shiki-token-parameter: #e36209;
|
||||
--shiki-token-function: #6f42c1;
|
||||
--shiki-token-string-expression: #22863a;
|
||||
--shiki-token-punctuation: #24292e;
|
||||
--shiki-token-link: #0366d6;
|
||||
--shiki-token-variable: #24292e;
|
||||
--shiki-token-number: #005cc5;
|
||||
--shiki-token-boolean: #005cc5;
|
||||
--shiki-token-tag: #22863a;
|
||||
--shiki-token-attribute: #005cc5;
|
||||
--shiki-token-property: #005cc5;
|
||||
--shiki-token-operator: #d73a49;
|
||||
--shiki-token-regex: #032f62;
|
||||
--shiki-token-class: #6f42c1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Support for theme toggles */
|
||||
html[data-theme="light"] ${host} {
|
||||
--shiki-color-text: #24292e;
|
||||
--shiki-color-background: var(--code-bg);
|
||||
--shiki-token-constant: #005cc5;
|
||||
--shiki-token-string: #22863a;
|
||||
--shiki-token-comment: #6a737d;
|
||||
--shiki-token-keyword: #d73a49;
|
||||
--shiki-token-parameter: #e36209;
|
||||
--shiki-token-function: #6f42c1;
|
||||
--shiki-token-string-expression: #22863a;
|
||||
--shiki-token-punctuation: #24292e;
|
||||
--shiki-token-link: #0366d6;
|
||||
--shiki-token-variable: #24292e;
|
||||
--shiki-token-number: #005cc5;
|
||||
--shiki-token-boolean: #005cc5;
|
||||
--shiki-token-tag: #22863a;
|
||||
--shiki-token-attribute: #005cc5;
|
||||
--shiki-token-property: #005cc5;
|
||||
--shiki-token-operator: #d73a49;
|
||||
--shiki-token-regex: #032f62;
|
||||
--shiki-token-class: #6f42c1;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] ${host} {
|
||||
--shiki-color-text: #f8f8f2;
|
||||
--shiki-color-background: var(--code-bg);
|
||||
--shiki-token-constant: #82b1ff;
|
||||
--shiki-token-string: #c3e88d;
|
||||
--shiki-token-comment: #546e7a;
|
||||
--shiki-token-keyword: #c792ea;
|
||||
--shiki-token-parameter: #fd971f;
|
||||
--shiki-token-function: #80cbae;
|
||||
--shiki-token-string-expression: #c3e88d;
|
||||
--shiki-token-punctuation: #89ddff;
|
||||
--shiki-token-link: #82aaff;
|
||||
--shiki-token-variable: #f8f8f2;
|
||||
--shiki-token-number: #f78c6c;
|
||||
--shiki-token-boolean: #82b1ff;
|
||||
--shiki-token-tag: #f07178;
|
||||
--shiki-token-attribute: #ffcb6b;
|
||||
--shiki-token-property: #82b1ff;
|
||||
--shiki-token-operator: #89ddff;
|
||||
--shiki-token-regex: #c3e88d;
|
||||
--shiki-token-class: #ffcb6b;
|
||||
}
|
||||
|
||||
/* Code block with syntax highlighting waiting for JS */
|
||||
${host}[data-js=todo] {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--standard-border-radius);
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 18.4px; /* to fix shift when → dataJS=done */
|
||||
padding: 1rem 1.4rem;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1rem;
|
||||
background-color: var(--code-bg);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Add a subtle loading indicator */
|
||||
${host}[data-js=todo]::before {
|
||||
content: "Loading syntax highlighting...";
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-light);
|
||||
background-color: var(--bg);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: var(--border-radius);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* All code blocks should have consistent font and sizing */
|
||||
${host} code {
|
||||
font-family: var(--font-mono);
|
||||
font-size: inherit;
|
||||
line-height: 1.5;
|
||||
padding: 0;
|
||||
}
|
||||
${host} pre {
|
||||
margin-block: 0;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
/* Ensure line numbers (if added) are styled appropriately */
|
||||
${host} .line-number {
|
||||
user-select: none;
|
||||
opacity: 0.5;
|
||||
margin-right: 1rem;
|
||||
min-width: 1.5rem;
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* If there's a copy button, style it */
|
||||
${host} .copy-button {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
background-color: var(--bg);
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
${host}:hover .copy-button {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
import { el } from "deka-dom-el";
|
||||
@ -52,12 +203,46 @@ let is_registered= {};
|
||||
function registerClientPart(page_id){
|
||||
if(is_registered[page_id]) return;
|
||||
|
||||
// Add Shiki with a more reliable loading method
|
||||
document.head.append(
|
||||
el("script", { src: "https://cdn.jsdelivr.net/npm/shiki@0.9", defer: true }),
|
||||
// Use a newer version of Shiki with better performance
|
||||
el("script", { src: "https://cdn.jsdelivr.net/npm/shiki@0.14.3/dist/index.unpkg.iife.js", defer: true }),
|
||||
// Make sure we can match Flems styling in dark/light mode
|
||||
el("style", `
|
||||
/* Ensure CodeMirror and Shiki use the same font */
|
||||
.CodeMirror *, .shiki * {
|
||||
font-family: var(--font-mono) !important;
|
||||
}
|
||||
|
||||
/* Style Shiki's output to match our theme */
|
||||
.shiki {
|
||||
background-color: var(--shiki-color-background) !important;
|
||||
color: var(--shiki-color-text) !important;
|
||||
padding: 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
tab-size: 2;
|
||||
}
|
||||
|
||||
/* Ensure Shiki code tokens use our CSS variables */
|
||||
.shiki .keyword { color: var(--shiki-token-keyword) !important; }
|
||||
.shiki .constant { color: var(--shiki-token-constant) !important; }
|
||||
.shiki .string { color: var(--shiki-token-string) !important; }
|
||||
.shiki .comment { color: var(--shiki-token-comment) !important; }
|
||||
.shiki .function { color: var(--shiki-token-function) !important; }
|
||||
.shiki .operator, .shiki .punctuation { color: var(--shiki-token-punctuation) !important; }
|
||||
.shiki .parameter { color: var(--shiki-token-parameter) !important; }
|
||||
.shiki .variable { color: var(--shiki-token-variable) !important; }
|
||||
.shiki .property { color: var(--shiki-token-property) !important; }
|
||||
`),
|
||||
);
|
||||
|
||||
// Register our highlighting script to run after Shiki loads
|
||||
const scriptElement = el("script", { type: "module" });
|
||||
|
||||
registerClientFile(
|
||||
new URL("./code.js.js", import.meta.url),
|
||||
el("script", { type: "module" })
|
||||
scriptElement
|
||||
);
|
||||
|
||||
is_registered[page_id]= true;
|
||||
}
|
||||
|
@ -1,12 +1,61 @@
|
||||
const highlighter= await globalThis.shiki.getHighlighter({
|
||||
theme: "css-variables",
|
||||
langs: ["js", "ts", "css", "html", "shell"],
|
||||
});
|
||||
const codeBlocks= document.querySelectorAll('code[class*="language-"]');
|
||||
try {
|
||||
// Initialize Shiki with our custom theme
|
||||
const highlighter = await globalThis.shiki.getHighlighter({
|
||||
theme: "css-variables",
|
||||
langs: ["javascript", "typescript", "css", "html", "shell"],
|
||||
});
|
||||
|
||||
codeBlocks.forEach((block)=> {
|
||||
const lang= block.className.replace("language-", "");
|
||||
block.parentElement.dataset.js= "done";
|
||||
const html= highlighter.codeToHtml(block.textContent, { lang });
|
||||
block.innerHTML= html;
|
||||
});
|
||||
// Find all code blocks that need highlighting
|
||||
const codeBlocks = document.querySelectorAll('div[data-js="todo"] code[class*="language-"]');
|
||||
|
||||
// Process each code block
|
||||
codeBlocks.forEach((block) => {
|
||||
try {
|
||||
// Get the language from the class
|
||||
const langClass = block.className.match(/language-(\w+)/);
|
||||
|
||||
// Map the language to Shiki format
|
||||
let lang = langClass ? langClass[1] : 'javascript';
|
||||
if (lang === 'js') lang = 'javascript';
|
||||
if (lang === 'ts') lang = 'typescript';
|
||||
|
||||
// Mark the container as processed
|
||||
block.parentElement.dataset.js = "done";
|
||||
|
||||
// Highlight the code
|
||||
const code = block.textContent;
|
||||
const html = highlighter.codeToHtml(code, { lang });
|
||||
|
||||
// Insert the highlighted HTML
|
||||
block.innerHTML = html;
|
||||
|
||||
// Add copy button functionality
|
||||
const copyBtn = document.createElement('button');
|
||||
copyBtn.className = 'copy-button';
|
||||
copyBtn.textContent = 'Copy';
|
||||
copyBtn.setAttribute('aria-label', 'Copy code to clipboard');
|
||||
copyBtn.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(code).then(() => {
|
||||
copyBtn.textContent = 'Copied!';
|
||||
setTimeout(() => {
|
||||
copyBtn.textContent = 'Copy';
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
// Add the copy button to the code block container
|
||||
block.parentElement.appendChild(copyBtn);
|
||||
} catch (err) {
|
||||
console.error('Error highlighting code block:', err);
|
||||
// Make sure we don't leave the block in a pending state
|
||||
block.parentElement.dataset.js = "error";
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Failed to initialize Shiki:', err);
|
||||
|
||||
// Fallback: at least mark blocks as processed so they don't show loading indicator
|
||||
document.querySelectorAll('div[data-js="todo"]').forEach(block => {
|
||||
block.dataset.js = "error";
|
||||
});
|
||||
}
|
||||
|
@ -1,16 +1,103 @@
|
||||
import { styles } from "../ssr.js";
|
||||
const host= "."+example.name;
|
||||
styles.css`
|
||||
${host}{
|
||||
${host} {
|
||||
grid-column: full-main;
|
||||
width: 100%;
|
||||
max-width: calc(9/5 * var(--body-max-width));
|
||||
height: calc(3/5 * var(--body-max-width));
|
||||
margin-inline: auto;
|
||||
margin: 2rem auto;
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--shadow);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
${host} .runtime {
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
/* CodeMirror styling to match our theme */
|
||||
.CodeMirror {
|
||||
height: 100% !important;
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 0.8rem !important;
|
||||
line-height: 1.5 !important;
|
||||
}
|
||||
|
||||
/* Dark mode styles for CodeMirror */
|
||||
.CodeMirror, .CodeMirror-gutters {
|
||||
background: #212121 !important;
|
||||
border: 1px solid white;
|
||||
background: var(--code-bg) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
color: var(--text) !important;
|
||||
}
|
||||
|
||||
/* Light mode adjustments for CodeMirror - using CSS variables */
|
||||
@media (prefers-color-scheme: light) {
|
||||
/* Core syntax elements */
|
||||
.cm-s-material .cm-keyword { color: var(--shiki-token-keyword, #d73a49) !important; }
|
||||
.cm-s-material .cm-atom { color: var(--shiki-token-constant, #005cc5) !important; }
|
||||
.cm-s-material .cm-number { color: var(--shiki-token-number, #005cc5) !important; }
|
||||
.cm-s-material .cm-def { color: var(--shiki-token-function, #6f42c1) !important; }
|
||||
.cm-s-material .cm-variable { color: var(--shiki-token-variable, #24292e) !important; }
|
||||
.cm-s-material .cm-variable-2 { color: var(--shiki-token-variable, #24292e) !important; }
|
||||
.cm-s-material .cm-variable-3 { color: var(--shiki-token-variable, #24292e) !important; }
|
||||
.cm-s-material .cm-property { color: var(--shiki-token-property, #005cc5) !important; }
|
||||
.cm-s-material .cm-operator { color: var(--shiki-token-operator, #d73a49) !important; }
|
||||
.cm-s-material .cm-comment { color: var(--shiki-token-comment, #6a737d) !important; }
|
||||
.cm-s-material .cm-string { color: var(--shiki-token-string, #22863a) !important; }
|
||||
.cm-s-material .cm-string-2 { color: var(--shiki-token-string, #22863a) !important; }
|
||||
.cm-s-material .cm-tag { color: var(--shiki-token-tag, #22863a) !important; }
|
||||
.cm-s-material .cm-attribute { color: var(--shiki-token-attribute, #005cc5) !important; }
|
||||
.cm-s-material .cm-bracket { color: var(--shiki-token-punctuation, #24292e) !important; }
|
||||
.cm-s-material .cm-punctuation { color: var(--shiki-token-punctuation, #24292e) !important; }
|
||||
.cm-s-material .cm-link { color: var(--shiki-token-link, #0366d6) !important; }
|
||||
.cm-s-material .cm-error { color: #f44336 !important; }
|
||||
}
|
||||
|
||||
/* Handle theme toggle */
|
||||
html[data-theme="light"] .CodeMirror {
|
||||
background: #f5f7fa !important;
|
||||
}
|
||||
|
||||
html[data-theme="light"] .CodeMirror-gutters {
|
||||
background: #f5f7fa !important;
|
||||
border-right: 1px solid #e5e7eb !important;
|
||||
}
|
||||
|
||||
/* Also apply the same styles to CodeMirror with data-theme */
|
||||
html[data-theme="light"] .cm-s-material .cm-keyword { color: var(--shiki-token-keyword, #d73a49) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-atom { color: var(--shiki-token-constant, #005cc5) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-number { color: var(--shiki-token-number, #005cc5) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-def { color: var(--shiki-token-function, #6f42c1) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-variable { color: var(--shiki-token-variable, #24292e) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-variable-2 { color: var(--shiki-token-variable, #24292e) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-variable-3 { color: var(--shiki-token-variable, #24292e) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-property { color: var(--shiki-token-property, #005cc5) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-operator { color: var(--shiki-token-operator, #d73a49) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-comment { color: var(--shiki-token-comment, #6a737d) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-string { color: var(--shiki-token-string, #22863a) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-string-2 { color: var(--shiki-token-string, #22863a) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-tag { color: var(--shiki-token-tag, #22863a) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-attribute { color: var(--shiki-token-attribute, #005cc5) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-bracket { color: var(--shiki-token-punctuation, #24292e) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-punctuation { color: var(--shiki-token-punctuation, #24292e) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-link { color: var(--shiki-token-link, #0366d6) !important; }
|
||||
html[data-theme="light"] .cm-s-material .cm-error { color: #f44336 !important; }
|
||||
|
||||
/* Mobile adjustments */
|
||||
@media (max-width: 767px) {
|
||||
${host} {
|
||||
height: 50vh;
|
||||
max-width: 100%;
|
||||
}
|
||||
${host} main {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
${host} main > * {
|
||||
width: 100%;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -26,7 +26,7 @@ function ddeComponent({ attr }){
|
||||
on.connected(e=> console.log(( /** @type {HTMLParagraphElement} */ (e.target)).outerHTML)),
|
||||
);
|
||||
return el().append(
|
||||
el("p", S(()=> `Hello from Custom Element with attribute '${attr()}'`))
|
||||
el("p", S(()=> `Hello from Custom Element with attribute '${attr.get()}'`))
|
||||
);
|
||||
}
|
||||
customElementWithDDE(HTMLCustomElement);
|
||||
|
14
docs/components/examples/debugging/consoleLog.js
Normal file
14
docs/components/examples/debugging/consoleLog.js
Normal file
@ -0,0 +1,14 @@
|
||||
// Debugging a (derived) signal with `console.log`
|
||||
import { S } from "deka-dom-el/signals";
|
||||
const name= S("Alice");
|
||||
const greeting = S(() => {
|
||||
// log derived signals
|
||||
const log = "Hello, " + name.get();
|
||||
console.log(log);
|
||||
return log;
|
||||
});
|
||||
|
||||
// log signals in general
|
||||
S.on(greeting, value => console.log("Greeting changed to:", value));
|
||||
|
||||
name.set("Bob"); // Should trigger computation and listener`)
|
15
docs/components/examples/debugging/debouncing.js
Normal file
15
docs/components/examples/debugging/debouncing.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { S } from "deka-dom-el/signals";
|
||||
// Debouncing signal updates
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return (...args)=> {
|
||||
clearTimeout(timeout);
|
||||
timeout= setTimeout(() => func(...args), wait);
|
||||
};
|
||||
}
|
||||
|
||||
const inputSignal= S("");
|
||||
const debouncedSet= debounce(value => inputSignal.set(value), 300);
|
||||
|
||||
// In your input handler
|
||||
inputElement.addEventListener("input", e=> debouncedSet(e.target.value));
|
4
docs/components/examples/debugging/dom-reactive-mark.js
Normal file
4
docs/components/examples/debugging/dom-reactive-mark.js
Normal file
@ -0,0 +1,4 @@
|
||||
// Example of reactive element marker
|
||||
<!--<dde:mark type=\"reactive\" source=\"...\">-->
|
||||
<!-- content that updates when signal changes -->
|
||||
<!--</dde:mark>-->
|
15
docs/components/examples/debugging/mutations.js
Normal file
15
docs/components/examples/debugging/mutations.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { S } from "deka-dom-el/signals";
|
||||
// Wrong - direct mutation doesn't trigger updates
|
||||
const todos1 = S([{ text: "Learn signals", completed: false }]);
|
||||
todos1.get().push({ text: "Debug signals", completed: false }); // Won't trigger updates!
|
||||
|
||||
// Correct - using .set() with a new array
|
||||
todos1.set([...todos1.get(), { text: "Debug signals", completed: false }]);
|
||||
|
||||
// Better - using actions
|
||||
const todos2 = S([], {
|
||||
add(text) {
|
||||
this.value.push({ text, completed: false });
|
||||
}
|
||||
});
|
||||
S.action(todos2, "add", "Debug signals");
|
@ -4,11 +4,11 @@ const threePS= ({ emoji= "🚀" })=> {
|
||||
const clicks= S(0); // A
|
||||
return el().append(
|
||||
el("p", S(()=>
|
||||
"Hello World "+emoji.repeat(clicks()) // B
|
||||
"Hello World "+emoji.repeat(clicks.get()) // B
|
||||
)),
|
||||
el("button", {
|
||||
type: "button",
|
||||
onclick: ()=> clicks(clicks()+1), // C
|
||||
onclick: ()=> clicks.set(clicks.get()+1), // C
|
||||
textContent: "Fire",
|
||||
})
|
||||
);
|
||||
|
@ -22,9 +22,9 @@ const onsubmit= on("submit", function(event){
|
||||
S.action(todos, "push", data.get("todo"));
|
||||
break;
|
||||
case "E"/*dit*/: {
|
||||
const last= todos().at(-1);
|
||||
const last= todos.get().at(-1);
|
||||
if(!last) break;
|
||||
last(data.get("todo"));
|
||||
last.set(data.get("todo"));
|
||||
break;
|
||||
}
|
||||
case "R"/*emove*/:
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { S } from "deka-dom-el/signals";
|
||||
const signal= S(0);
|
||||
// computation pattern
|
||||
const double= S(()=> 2*signal());
|
||||
const double= S(()=> 2*signal.get());
|
||||
|
||||
const ac= new AbortController();
|
||||
S.on(signal, v=> console.log("signal", v), { signal: ac.signal });
|
||||
S.on(double, v=> console.log("double", v), { signal: ac.signal });
|
||||
|
||||
signal(signal()+1);
|
||||
signal.set(signal.get()+1);
|
||||
const interval= 5 * 1000;
|
||||
const id= setInterval(()=> signal(signal()+1), interval);
|
||||
const id= setInterval(()=> signal.set(signal.get()+1), interval);
|
||||
ac.signal.addEventListener("abort",
|
||||
()=> setTimeout(()=> clearInterval(id), 2*interval));
|
||||
|
||||
|
20
docs/components/examples/signals/debugging-console.js
Normal file
20
docs/components/examples/signals/debugging-console.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { S } from "deka-dom-el/signals";
|
||||
|
||||
// Debugging a derived signal
|
||||
const name = S('Alice');
|
||||
const greeting = S(() => {
|
||||
console.log('Computing greeting...');
|
||||
return 'Hello, ' + name.get();
|
||||
});
|
||||
|
||||
// Monitor the derived signal
|
||||
S.on(greeting, value => console.log('Greeting changed to:', value));
|
||||
|
||||
// Later update the dependency
|
||||
name.set('Bob'); // Should trigger computation and listener
|
||||
|
||||
// Console output:
|
||||
// Computing greeting...
|
||||
// Greeting changed to: Hello, Alice
|
||||
// Computing greeting...
|
||||
// Greeting changed to: Hello, Bob
|
38
docs/components/examples/signals/debugging-dom.js
Normal file
38
docs/components/examples/signals/debugging-dom.js
Normal file
@ -0,0 +1,38 @@
|
||||
import { el, on, scope } from "deka-dom-el";
|
||||
import { S } from "deka-dom-el/signals";
|
||||
|
||||
// Create a component with reactive elements
|
||||
function ReactiveCounter() {
|
||||
const count = S(0);
|
||||
scope.host(on.connected(ev=>
|
||||
console.log(ev.target.__dde_reactive)
|
||||
));
|
||||
|
||||
const counter = el('div', {
|
||||
// This element will be added into the __dde_reactive property
|
||||
textContent: count,
|
||||
});
|
||||
|
||||
const incrementBtn = el('button', {
|
||||
textContent: 'Increment',
|
||||
onclick: () => count.set(count.get() + 1)
|
||||
});
|
||||
|
||||
// Dynamic section will be added into __dde_signal property
|
||||
const counterInfo = S.el(count, value =>
|
||||
el('p', `Current count is ${value}`)
|
||||
);
|
||||
|
||||
return el('div', { id: 'counter' }).append(
|
||||
counter,
|
||||
incrementBtn,
|
||||
counterInfo
|
||||
);
|
||||
}
|
||||
document.body.append(
|
||||
el(ReactiveCounter),
|
||||
);
|
||||
|
||||
// In DevTools console:
|
||||
const counter = document.querySelector('#counter');
|
||||
setTimeout(()=> console.log(counter.__dde_reactive), 1000); // See reactive bindings
|
13
docs/components/examples/signals/derived.js
Normal file
13
docs/components/examples/signals/derived.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { S } from "deka-dom-el/signals";
|
||||
|
||||
// Create base signals
|
||||
const firstName = S("John");
|
||||
const lastName = S("Doe");
|
||||
|
||||
// Create a derived signal
|
||||
const fullName = S(() => firstName.get() + " " + lastName.get());
|
||||
|
||||
// The fullName signal updates automatically when either dependency changes
|
||||
S.on(fullName, name => console.log("Name changed to:", name));
|
||||
|
||||
firstName.set("Jane"); // logs: "Name changed to: Jane Doe"
|
@ -3,8 +3,8 @@ const count= S(0);
|
||||
|
||||
import { el } from "deka-dom-el";
|
||||
document.body.append(
|
||||
el("p", S(()=> "Currently: "+count())),
|
||||
el("p", { classList: { red: S(()=> count()%2 === 0) }, dataset: { count }, textContent: "Attributes example" }),
|
||||
el("p", S(()=> "Currently: "+count.get())),
|
||||
el("p", { classList: { red: S(()=> count.get()%2 === 0) }, dataset: { count }, textContent: "Attributes example" }),
|
||||
);
|
||||
document.head.append(
|
||||
el("style", ".red { color: red; }")
|
||||
@ -12,4 +12,4 @@ document.head.append(
|
||||
|
||||
const interval= 5 * 1000;
|
||||
setTimeout(clearInterval, 10*interval,
|
||||
setInterval(()=> count(count()+1), interval));
|
||||
setInterval(()=> count.set(count.get()+1), interval));
|
@ -2,7 +2,7 @@ import { S } from "deka-dom-el/signals";
|
||||
const count= S(0, {
|
||||
add(){ this.value= this.value + Math.round(Math.random()*10); }
|
||||
});
|
||||
const numbers= S([ count() ], {
|
||||
const numbers= S([ count.get() ], {
|
||||
push(next){ this.value.push(next); }
|
||||
});
|
||||
|
||||
@ -22,5 +22,5 @@ document.body.append(
|
||||
const interval= 5*1000;
|
||||
setTimeout(clearInterval, 10*interval, setInterval(function(){
|
||||
S.action(count, "add");
|
||||
S.action(numbers, "push", count());
|
||||
S.action(numbers, "push", count.get());
|
||||
}, interval));
|
@ -4,7 +4,7 @@ const signal= S(0);
|
||||
// β — just reacts on signal changes
|
||||
S.on(signal, console.log);
|
||||
// γ — just updates the value
|
||||
const update= ()=> signal(signal()+1);
|
||||
const update= ()=> signal.set(signal.get()+1);
|
||||
|
||||
update();
|
||||
const interval= 5*1000;
|
||||
|
43
docs/components/examples/ssr/async-data.js
Normal file
43
docs/components/examples/ssr/async-data.js
Normal file
@ -0,0 +1,43 @@
|
||||
// Handling async data in SSR
|
||||
import { JSDOM } from "jsdom";
|
||||
import { S } from "deka-dom-el/signals";
|
||||
import { register, queue } from "deka-dom-el/jsdom";
|
||||
|
||||
async function renderWithAsyncData() {
|
||||
const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>");
|
||||
const { el } = await register(dom);
|
||||
|
||||
// Create a component that fetches data
|
||||
function AsyncComponent() {
|
||||
const title= S("-");
|
||||
const description= S("-");
|
||||
|
||||
// Use the queue to track the async operation
|
||||
queue(fetch("https://api.example.com/data")
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
title.set(data.title);
|
||||
description.set(data.description);
|
||||
}));
|
||||
|
||||
return el("div", { className: "async-content" }).append(
|
||||
el("h2", title),
|
||||
el("p", description)
|
||||
);
|
||||
}
|
||||
|
||||
// Render the page
|
||||
dom.window.document.body.append(
|
||||
el("h1", "Page with Async Data"),
|
||||
el(AsyncComponent)
|
||||
);
|
||||
|
||||
// IMPORTANT: Wait for all queued operations to complete
|
||||
await queue();
|
||||
|
||||
// Now the HTML includes all async content
|
||||
const html = dom.serialize();
|
||||
console.log(html);
|
||||
}
|
||||
|
||||
renderWithAsyncData();
|
47
docs/components/examples/ssr/basic-example.js
Normal file
47
docs/components/examples/ssr/basic-example.js
Normal file
@ -0,0 +1,47 @@
|
||||
// Basic SSR Example
|
||||
import { JSDOM } from "jsdom";
|
||||
import { register, queue } from "deka-dom-el/jsdom";
|
||||
import { writeFileSync } from "node:fs";
|
||||
|
||||
async function renderPage() {
|
||||
// Create a jsdom instance
|
||||
const dom = new JSDOM("<!DOCTYPE html><html><head><meta charset=\"utf-8\"></head><body></body></html>");
|
||||
|
||||
// Register with deka-dom-el and get the el function
|
||||
const { el } = await register(dom);
|
||||
|
||||
// Create a simple header component
|
||||
function Header({ title }) {
|
||||
return el("header").append(
|
||||
el("h1", title),
|
||||
el("nav").append(
|
||||
el("ul").append(
|
||||
el("li").append(el("a", { href: "/" }, "Home")),
|
||||
el("li").append(el("a", { href: "/about" }, "About")),
|
||||
el("li").append(el("a", { href: "/contact" }, "Contact"))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Create the page content
|
||||
dom.window.document.body.append(
|
||||
el(Header, { title: "My Static Site" }),
|
||||
el("main").append(
|
||||
el("h2", "Welcome!"),
|
||||
el("p", "This page was rendered with deka-dom-el on the server.")
|
||||
),
|
||||
el("footer", "© 2025 My Company")
|
||||
);
|
||||
|
||||
// Wait for any async operations
|
||||
await queue();
|
||||
|
||||
// Get the HTML and write it to a file
|
||||
const html = dom.serialize();
|
||||
writeFileSync("index.html", html);
|
||||
|
||||
console.log("Page rendered successfully!");
|
||||
}
|
||||
|
||||
renderPage().catch(console.error);
|
2
docs/components/examples/ssr/intro.js
Normal file
2
docs/components/examples/ssr/intro.js
Normal file
@ -0,0 +1,2 @@
|
||||
// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js
|
||||
import { register, unregister, queue } from "deka-dom-el/jsdom";
|
35
docs/components/examples/ssr/pages.js
Normal file
35
docs/components/examples/ssr/pages.js
Normal file
@ -0,0 +1,35 @@
|
||||
// ❌ WRONG: Static imports are hoisted and will register before JSDOM is created
|
||||
import { register } from "deka-dom-el/jsdom";
|
||||
import { el } from "deka-dom-el";
|
||||
import { Header } from "./components/Header.js";
|
||||
|
||||
// ✅ CORRECT: Use dynamic imports to ensure proper initialization order
|
||||
import { JSDOM } from "jsdom";
|
||||
|
||||
async function renderPage() {
|
||||
// 1. Create JSDOM instance first
|
||||
const dom = new JSDOM(`<!DOCTYPE html><html><body></body></html>`);
|
||||
|
||||
// 2. Dynamically import jsdom module
|
||||
const { register, queue } = await import("deka-dom-el/jsdom");
|
||||
|
||||
// 3. Register and get el function
|
||||
const { el } = await register(dom);
|
||||
|
||||
// 4. Dynamically import page components
|
||||
const { Header } = await import("./components/Header.js");
|
||||
const { Content } = await import("./components/Content.js");
|
||||
|
||||
// 5. Render components
|
||||
const body = dom.window.document.body;
|
||||
el(body).append(
|
||||
el(Header, { title: "My Page" }),
|
||||
el(Content, { text: "This is server-rendered content" })
|
||||
);
|
||||
|
||||
// 6. Wait for async operations
|
||||
await queue();
|
||||
|
||||
// 7. Get HTML and clean up
|
||||
return dom.serialize();
|
||||
}
|
27
docs/components/examples/ssr/start.js
Normal file
27
docs/components/examples/ssr/start.js
Normal file
@ -0,0 +1,27 @@
|
||||
// Basic jsdom integration example
|
||||
import { JSDOM } from "jsdom";
|
||||
import { register, unregister, queue } from "deka-dom-el/jsdom.js";
|
||||
|
||||
// Create a jsdom instance
|
||||
const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>");
|
||||
|
||||
// Register the dom with deka-dom-el
|
||||
const { el } = await register(dom);
|
||||
|
||||
// Use deka-dom-el normally
|
||||
dom.window.document.body.append(
|
||||
el("div", { className: "container" }).append(
|
||||
el("h1", "Hello, SSR World!"),
|
||||
el("p", "This content was rendered on the server.")
|
||||
)
|
||||
);
|
||||
|
||||
// Wait for any async operations to complete
|
||||
await queue();
|
||||
|
||||
// Get the rendered HTML
|
||||
const html = dom.serialize();
|
||||
console.log(html);
|
||||
|
||||
// Clean up when done
|
||||
unregister();
|
44
docs/components/examples/ssr/static-site-generator.js
Normal file
44
docs/components/examples/ssr/static-site-generator.js
Normal file
@ -0,0 +1,44 @@
|
||||
// Building a simple static site generator
|
||||
import { JSDOM } from "jsdom";
|
||||
import { register, queue } from "deka-dom-el/jsdom";
|
||||
import { writeFileSync, mkdirSync } from "node:fs";
|
||||
|
||||
async function buildSite() {
|
||||
// Define pages to build
|
||||
const pages = [
|
||||
{ id: "index", title: "Home", component: "./pages/home.js" },
|
||||
{ id: "about", title: "About", component: "./pages/about.js" },
|
||||
{ id: "docs", title: "Documentation", component: "./pages/docs.js" }
|
||||
];
|
||||
|
||||
// Create output directory
|
||||
mkdirSync("./dist", { recursive: true });
|
||||
|
||||
// Build each page
|
||||
for (const page of pages) {
|
||||
// Create a fresh jsdom instance for each page
|
||||
const dom = new JSDOM("<!DOCTYPE html><html><head><meta charset=\"utf-8\"></head><body></body></html>");
|
||||
|
||||
// Register with deka-dom-el
|
||||
const { el } = await register(dom);
|
||||
|
||||
// Import the page component
|
||||
const { default: PageComponent } = await import(page.component);
|
||||
|
||||
// Render the page with its metadata
|
||||
dom.window.document.body.append(
|
||||
el(PageComponent, { title: page.title, pages })
|
||||
);
|
||||
|
||||
// Wait for any async operations
|
||||
await queue();
|
||||
|
||||
// Write the HTML to a file
|
||||
const html = dom.serialize();
|
||||
writeFileSync(`./dist/${page.id}.html`, html);
|
||||
|
||||
console.log(`Built page: ${page.id}.html`);
|
||||
}
|
||||
}
|
||||
|
||||
buildSite().catch(console.error);
|
@ -1,18 +1,67 @@
|
||||
import { pages, styles } from "../ssr.js";
|
||||
const host= "."+prevNext.name;
|
||||
styles.css`
|
||||
${host}{
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr 1fr;
|
||||
margin-top: 1rem;
|
||||
/* Previous/Next navigation */
|
||||
${host} {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 3rem;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid var(--border);
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
${host} [rel=prev]{
|
||||
grid-column: 1;
|
||||
|
||||
${host} a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--primary-dark); /* Darker background for better contrast */
|
||||
color: white;
|
||||
font-weight: 600; /* Bolder text for better readability */
|
||||
text-decoration: none;
|
||||
transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
||||
max-width: 45%;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); /* Subtle shadow for better visibility */
|
||||
}
|
||||
${host} [rel=next]{
|
||||
grid-column: 3;
|
||||
text-align: right;
|
||||
|
||||
${host} a:hover {
|
||||
background-color: var(--primary);
|
||||
transform: translateY(-2px);
|
||||
text-decoration: none;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Enhanced shadow on hover */
|
||||
}
|
||||
|
||||
${host} [rel=prev] {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
${host} [rel=next] {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
${host} [rel=prev]::before {
|
||||
content: "←";
|
||||
margin-right: 0.75rem;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
${host} [rel=next]::after {
|
||||
content: "→";
|
||||
margin-left: 0.75rem;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
/* If there's no previous/next, ensure the spacing still works */
|
||||
${host} a:only-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
${host} a {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
`;
|
||||
import { el } from "../../index.js";
|
||||
@ -22,21 +71,28 @@ import { el } from "../../index.js";
|
||||
* @param {string} [attrs.id]
|
||||
* */
|
||||
export function h3({ textContent, id }){
|
||||
if(!id) id= "h-"+textContent.toLowerCase().replaceAll(/\s/g, "-").replaceAll(/[^a-z-]/g, "");
|
||||
return el("h3", { id }).append(
|
||||
el("a", { textContent: "#", href: "#"+id, tabIndex: -1 }),
|
||||
" ", textContent
|
||||
);
|
||||
if(!id) id= "h-"+textContent.toLowerCase().replaceAll(/\s/g, "-").replaceAll(/[^a-z-]/g, "");
|
||||
return el("h3", { id }).append(
|
||||
el("a", {
|
||||
className: "heading-anchor",
|
||||
href: "#"+id,
|
||||
textContent: "#",
|
||||
title: `Link to this section: ${textContent}`,
|
||||
"aria-label": `Link to section ${textContent}`
|
||||
}),
|
||||
" ",
|
||||
textContent,
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @param {import("../types.d.ts").Info} page
|
||||
* */
|
||||
export function prevNext(page){
|
||||
const page_index= pages.indexOf(page);
|
||||
return el("div", { className: prevNext.name }).append(
|
||||
el(pageLink, { rel: "prev", page: pages[page_index-1] }),
|
||||
el(pageLink, { rel: "next", page: pages[page_index+1] })
|
||||
);
|
||||
const page_index= pages.indexOf(page);
|
||||
return el("div", { className: prevNext.name }).append(
|
||||
el(pageLink, { rel: "prev", page: pages[page_index-1] }),
|
||||
el(pageLink, { rel: "next", page: pages[page_index+1] })
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @param {Object} attrs
|
||||
@ -44,11 +100,22 @@ export function prevNext(page){
|
||||
* @param {import("../types.d.ts").Info} [attrs.page]
|
||||
* */
|
||||
function pageLink({ rel, page }){
|
||||
if(!page) return el();
|
||||
let { href, title, description }= page;
|
||||
return el("a", { rel, href, title: description }).append(
|
||||
rel==="next" ?"(next) " : "",
|
||||
title,
|
||||
rel==="prev" ? " (previous)" : "",
|
||||
);
|
||||
if(!page) return el();
|
||||
let { href, title, description }= page;
|
||||
|
||||
// Find the page index to show numbering
|
||||
const pageIndex = pages.findIndex(p => p === page);
|
||||
const pageNumber = pageIndex + 1;
|
||||
|
||||
const linkTitle = rel === "prev"
|
||||
? `Previous: ${pageNumber}. ${title}`
|
||||
: `Next: ${pageNumber}. ${title}`;
|
||||
|
||||
return el("a", {
|
||||
rel,
|
||||
href,
|
||||
title: description || linkTitle
|
||||
}).append(
|
||||
title
|
||||
);
|
||||
}
|
||||
|
@ -1,124 +1,420 @@
|
||||
import { styles } from "./ssr.js";
|
||||
styles.css`
|
||||
@import url(https://cdn.simplecss.org/simple.min.css);
|
||||
:root{
|
||||
--body-max-width: 45rem;
|
||||
--marked: #fb3779;
|
||||
--code: #0d47a1;
|
||||
--accent: #d81b60;
|
||||
:root {
|
||||
--primary: #b71c1c;
|
||||
--primary-light: #f05545;
|
||||
--primary-dark: #7f0000;
|
||||
--primary-rgb: 183, 28, 28;
|
||||
--secondary: #700037;
|
||||
--secondary-light: #ae1357;
|
||||
--secondary-dark: #4a0027;
|
||||
--secondary-rgb: 112, 0, 55;
|
||||
|
||||
--font-main: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
||||
Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
--font-mono: 'Fira Code', 'JetBrains Mono', 'SF Mono',
|
||||
SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
|
||||
|
||||
--body-max-width: 40rem;
|
||||
--sidebar-width: 20rem;
|
||||
--header-height: 4rem;
|
||||
--border-radius: 0.375rem;
|
||||
|
||||
--bg: #ffffff;
|
||||
--bg-sidebar: #fff5f5;
|
||||
--text: #1a1313;
|
||||
--text-light: #555050;
|
||||
--code-bg: #f9f2f2;
|
||||
--code-text: #9a0000;
|
||||
--border: #d8c0c0;
|
||||
--selection: rgba(183, 28, 28, 0.15);
|
||||
--marked: #b71c1c;
|
||||
--accent: var(--secondary);
|
||||
--shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
||||
|
||||
--link-color: #9a0000;
|
||||
--link-hover: #7f0000;
|
||||
--button-text: #ffffff;
|
||||
}
|
||||
@media (prefers-color-scheme:dark) {
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--accent: #f06292;
|
||||
--code: #62c1f0;
|
||||
--bg: #121212;
|
||||
--bg-sidebar: #1a1212;
|
||||
--text: #ffffff;
|
||||
--text-light: #cccccc;
|
||||
--code-bg: #2c2020;
|
||||
--code-text: #ff9e80;
|
||||
--border: #4d3939;
|
||||
--selection: rgba(255, 99, 71, 0.25);
|
||||
--primary: #b74141;
|
||||
--primary-light: #ff867f;
|
||||
--primary-dark: #c62828;
|
||||
--secondary: #f02b47;
|
||||
--secondary-light: #ff6090;
|
||||
--secondary-dark: #b0003a;
|
||||
--accent: var(--secondary);
|
||||
|
||||
--link-color: #ff5252;
|
||||
--link-hover: #ff867f;
|
||||
--button-text: #ffffff;
|
||||
|
||||
--nav-current-bg: #aa2222;
|
||||
--nav-current-text: #ffffff;
|
||||
|
||||
--primary-rgb: 255, 82, 82;
|
||||
--secondary-rgb: 233, 30, 99;
|
||||
}
|
||||
}
|
||||
body {
|
||||
grid-template-columns: 100%;
|
||||
grid-template-areas: "header" "sidebar" "content";
|
||||
|
||||
/* Base styling */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@media (min-width:768px) {
|
||||
body{
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-columns: calc(10 * var(--body-max-width) / 27) auto;
|
||||
grid-template-areas:
|
||||
"header header"
|
||||
"sidebar content"
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Accessibility improvements */
|
||||
:focus {
|
||||
outline: 3px solid rgba(63, 81, 181, 0.5);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 3px solid rgba(63, 81, 181, 0.5);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Ensure reduced motion preferences are respected */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
body > *{
|
||||
grid-column: unset;
|
||||
}
|
||||
body > header{
|
||||
grid-area: header;
|
||||
padding: 0;
|
||||
}
|
||||
body > nav{
|
||||
grid-area: sidebar;
|
||||
background-color: var(--accent-bg);
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
body > nav {
|
||||
font-size: 1rem;
|
||||
line-height: 2;
|
||||
padding: 1rem 0 0 0;
|
||||
}
|
||||
body > nav ol,
|
||||
body > nav ul {
|
||||
align-content: space-around;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
body > nav ol li,
|
||||
body > nav ul li {
|
||||
display:inline-block
|
||||
}
|
||||
body > nav a,
|
||||
body > nav a:visited {
|
||||
margin: 0 .5rem 1rem .5rem;
|
||||
border: 1px solid currentColor;
|
||||
border-radius: var(--standard-border-radius);
|
||||
color: var(--text-light);
|
||||
display: inline-block;
|
||||
padding: .1rem 1rem;
|
||||
|
||||
/* Skip link for better keyboard navigation */
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform: translateX(-100%);
|
||||
z-index: 9999;
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: all .15s;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
body > nav a.current,
|
||||
body > nav a[aria-current=page] {
|
||||
|
||||
.skip-link:focus {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-main);
|
||||
background-color: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
font-size: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: 100%;
|
||||
grid-template-areas:
|
||||
"header"
|
||||
"sidebar"
|
||||
"content";
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
body > nav a:hover{
|
||||
background-color: var(--bg);
|
||||
color: var(--accent);
|
||||
|
||||
::selection {
|
||||
background-color: var(--selection);
|
||||
}
|
||||
@media only screen and (max-width:720px) {
|
||||
body > nav{
|
||||
flex-flow: row wrap;
|
||||
padding-block: .5rem;
|
||||
}
|
||||
body > nav a {
|
||||
border:none;
|
||||
text-decoration:underline;
|
||||
margin-block: .1rem;
|
||||
padding-block:.1rem;
|
||||
line-height: 1rem;
|
||||
font-size: .9rem;
|
||||
|
||||
/* Typography */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 2rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.25rem;
|
||||
margin-top: 0;
|
||||
color: var(--primary-dark);
|
||||
}
|
||||
h1 > a {
|
||||
font-weight: unset;
|
||||
color: unset;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.25rem;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link-color, var(--primary));
|
||||
transition: color 0.2s ease;
|
||||
font-weight: 500;
|
||||
text-underline-offset: 3px;
|
||||
transition: color 0.2s ease, text-underline-offset 0.2s ease;
|
||||
}
|
||||
a:visited {
|
||||
--link-color: var(--secondary, #700037);
|
||||
}
|
||||
a:hover {
|
||||
--link-color: var(--link-hover, var(--primary-light));
|
||||
text-underline-offset: 5px;
|
||||
}
|
||||
|
||||
code, pre {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.9em;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
padding: 0.2em 0.4em;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: var(--code-bg);
|
||||
padding: 1rem;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 1.5rem;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
pre code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
@media (min-width: 768px) {
|
||||
body {
|
||||
grid-template-rows: var(--header-height) 1fr;
|
||||
grid-template-columns: var(--sidebar-width) 1fr;
|
||||
grid-template-areas:
|
||||
"header header"
|
||||
"sidebar content";
|
||||
}
|
||||
}
|
||||
main{
|
||||
|
||||
/* Main content */
|
||||
body > main {
|
||||
grid-area: content;
|
||||
padding: 2rem;
|
||||
max-width: 100%;
|
||||
overflow-x: hidden;
|
||||
display: grid;
|
||||
grid-template-columns:
|
||||
[full-main-start] 1fr
|
||||
[main-start] min(var(--body-max-width), 90%) [main-end]
|
||||
1fr [full-main-end];
|
||||
}
|
||||
main > *, main slot > *{
|
||||
|
||||
body > main > *, body > main slot > * {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-inline: auto;
|
||||
grid-column: main;
|
||||
}
|
||||
|
||||
/* Page title with ID anchor for skip link */
|
||||
body > main .page-title {
|
||||
margin-top: 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding-bottom: 0.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--primary);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Section headings with better visual hierarchy */
|
||||
body > main h2, body > main h3 {
|
||||
scroll-margin-top: calc(var(--header-height) + 1rem);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body > main h3 {
|
||||
border-left: 3px solid var(--primary);
|
||||
position: relative;
|
||||
left: -1.5rem;
|
||||
padding-inline-start: 1em;
|
||||
}
|
||||
|
||||
/* Make clickable heading links for better navigation */
|
||||
.heading-anchor {
|
||||
position: absolute;
|
||||
color: var(--text-light);
|
||||
left: -2rem;
|
||||
text-decoration: none;
|
||||
font-weight: normal;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
h2:hover .heading-anchor,
|
||||
h3:hover .heading-anchor {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
body > main {
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
body > main h2, body > main h3 {
|
||||
left: 1rem;
|
||||
width: calc(100% - 1rem);
|
||||
}
|
||||
.heading-anchor {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
/* Example boxes */
|
||||
.example {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--border-radius);
|
||||
margin: 2rem 0;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.example:hover {
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.example-header {
|
||||
background-color: var(--bg-sidebar);
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.example-content {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
/* Icon styling */
|
||||
.icon {
|
||||
vertical-align: sub;
|
||||
padding-right: .25rem;
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1.3em;
|
||||
margin-right: 0.2rem;
|
||||
height: 1em;
|
||||
vertical-align: -0.125em;
|
||||
stroke-width: 0;
|
||||
stroke: currentColor;
|
||||
fill: currentColor;
|
||||
}
|
||||
.note{
|
||||
font-size: .9rem;
|
||||
font-style: italic;
|
||||
|
||||
/* Information blocks */
|
||||
.note, .tip, .warning {
|
||||
padding: 1rem 1.25rem;
|
||||
margin: 1.5rem 0;
|
||||
border-radius: var(--border-radius);
|
||||
position: relative;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.note {
|
||||
background-color: rgba(63, 81, 181, 0.08);
|
||||
border-left: 4px solid var(--primary);
|
||||
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
||||
}
|
||||
|
||||
.tip {
|
||||
background-color: rgba(46, 204, 113, 0.08);
|
||||
border-left: 4px solid #2ecc71;
|
||||
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: rgba(241, 196, 15, 0.08);
|
||||
border-left: 4px solid #f1c40f;
|
||||
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
||||
}
|
||||
|
||||
.note::before, .tip::before, .warning::before {
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.note::before {
|
||||
content: "Note";
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.tip::before {
|
||||
content: "Tip";
|
||||
color: #2ecc71;
|
||||
}
|
||||
|
||||
.warning::before {
|
||||
content: "Warning";
|
||||
color: #f1c40f;
|
||||
}
|
||||
|
||||
/* Prev/Next buttons */
|
||||
.prev-next {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 3rem;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.prev-next a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.prev-next a:hover {
|
||||
background-color: var(--primary-dark);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.prev-next a:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.prev-next a[rel="prev"]::before {
|
||||
content: "←";
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.prev-next a[rel="next"]::after {
|
||||
content: "→";
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
@ -1,38 +1,242 @@
|
||||
import { el, elNS } from "deka-dom-el";
|
||||
import { pages } from "../ssr.js";
|
||||
import { pages, styles } from "../ssr.js";
|
||||
const host= "."+header.name;
|
||||
const host_nav= "."+nav.name;
|
||||
styles.css`
|
||||
/* Header */
|
||||
${host} {
|
||||
grid-area: header;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
box-shadow: var(--shadow);
|
||||
min-height: calc(var(--header-height) - 1em);
|
||||
--_m: .75em;
|
||||
margin: var(--_m) var(--_m) 0 var(--_m);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
${host} .header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
${host} h1 {
|
||||
font-size: 1.25rem;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: white;
|
||||
}
|
||||
|
||||
${host} .version-badge {
|
||||
font-size: 0.75rem;
|
||||
background-color: rgba(150, 150, 150, 0.2);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
${host} p {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
opacity: 0.9;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
${host} .github-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: white;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
text-decoration: none;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
${host} .github-link:hover {
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
${host_nav} {
|
||||
grid-area: sidebar;
|
||||
background-color: var(--bg-sidebar);
|
||||
border-right: 1px solid var(--border);
|
||||
padding: 1.5rem 1rem;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
${host_nav} a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.625rem 0.75rem;
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
${host_nav} a:hover {
|
||||
background-color: rgba(var(--primary-rgb), 0.08); /* Using CSS variables for better theming */
|
||||
text-decoration: none;
|
||||
transform: translateY(-1px);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
${host_nav} a.current,
|
||||
${host_nav} a[aria-current=page] {
|
||||
background-color: var(--nav-current-bg, var(--primary-dark));
|
||||
color: var(--nav-current-text, white);
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
${host_nav} a.current:hover,
|
||||
${host_nav} a[aria-current=page]:hover {
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
${host_nav} a .nav-number {
|
||||
display: inline-block;
|
||||
width: 1.5rem;
|
||||
text-align: right;
|
||||
margin-right: 0.5rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
${host_nav} a:first-child {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* Mobile navigation */
|
||||
@media (max-width: 767px) {
|
||||
${host_nav} {
|
||||
padding: 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
border-right: none;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
${host_nav} a {
|
||||
font-size: 0.875rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
${host_nav} a .nav-number {
|
||||
width: auto;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
${host_nav} a:first-child {
|
||||
margin-bottom: 0;
|
||||
margin-right: 0.5rem;
|
||||
min-width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
`;
|
||||
/**
|
||||
* @param {object} def
|
||||
* @param {import("../types.d.ts").Info} def.info
|
||||
* @param {import("../types.d.ts").Pkg} def.pkg Package information.
|
||||
* */
|
||||
export function header({ info: { href, title, description }, pkg }){
|
||||
title= `\`${pkg.name}\` — ${title}`;
|
||||
const pageTitle = `${pkg.name} — ${title}`;
|
||||
|
||||
// Add meta elements to the head
|
||||
document.head.append(
|
||||
head({ title, description, pkg })
|
||||
head({ title: pageTitle, description, pkg })
|
||||
);
|
||||
|
||||
// Add theme color meta tag
|
||||
document.head.append(
|
||||
el("meta", { name: "theme-color", content: "#3f51b5" })
|
||||
);
|
||||
|
||||
return el().append(
|
||||
el("header").append(
|
||||
el("h1", title),
|
||||
el("p", description)
|
||||
),
|
||||
el("nav").append(
|
||||
el("a", { href: pkg.homepage }).append(
|
||||
el(iconGitHub),
|
||||
"GitHub"
|
||||
// Header section with accessibility support
|
||||
el("header", { role: "banner", className: header.name }).append(
|
||||
el("div", { className: "header-title" }).append(
|
||||
el("a", {
|
||||
href: pkg.homepage,
|
||||
className: "github-link",
|
||||
"aria-label": "View on GitHub",
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer"
|
||||
}).append(
|
||||
el(iconGitHub),
|
||||
),
|
||||
el("h1").append(
|
||||
el("a", { href: pages[0].href, textContent: pkg.name, title: "Go to documentation homepage" }),
|
||||
),
|
||||
el("span", {
|
||||
className: "version-badge",
|
||||
"aria-label": "Version",
|
||||
textContent: pkg.version || ""
|
||||
})
|
||||
),
|
||||
...pages.map((p, i)=> el("a", {
|
||||
href: p.href==="index" ? "./" : p.href,
|
||||
textContent: (i+1) + ". " + p.title,
|
||||
title: p.description,
|
||||
classList: { current: p.href===href }
|
||||
}))
|
||||
)
|
||||
el("p", description),
|
||||
),
|
||||
|
||||
// Navigation between pages
|
||||
nav({ href })
|
||||
);
|
||||
}
|
||||
function nav({ href }){
|
||||
return el("nav", {
|
||||
role: "navigation",
|
||||
"aria-label": "Main navigation",
|
||||
className: nav.name
|
||||
}).append(
|
||||
...pages.map((p, i) => {
|
||||
const isIndex = p.href === "index";
|
||||
const isCurrent = p.href === href;
|
||||
|
||||
return el("a", {
|
||||
href: isIndex ? "./" : p.href,
|
||||
title: p.description || `Go to ${p.title}`,
|
||||
"aria-current": isCurrent ? "page" : null,
|
||||
classList: { current: isCurrent }
|
||||
}).append(
|
||||
el("span", {
|
||||
className: "nav-number",
|
||||
"aria-hidden": "true",
|
||||
textContent: `${i+1}.`
|
||||
}),
|
||||
p.title
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
function head({ title, description, pkg }){
|
||||
return el().append(
|
||||
el("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
|
||||
el("meta", { name: "description", content: description }),
|
||||
el("meta", { name: "theme-color", content: "#b71c1c" }),
|
||||
el("title", title),
|
||||
el(metaAuthor),
|
||||
el(metaTwitter, pkg),
|
||||
|
@ -7,9 +7,25 @@ import { prevNext } from "../components/pageUtils.html.js";
|
||||
/** @param {Pick<import("../types.d.ts").PageAttrs, "pkg" | "info">} attrs */
|
||||
export function simplePage({ pkg, info }){
|
||||
return simulateSlots(el().append(
|
||||
// Skip link for keyboard navigation
|
||||
el("a", {
|
||||
href: "#main-content",
|
||||
className: "skip-link",
|
||||
textContent: "Skip to main content"
|
||||
}),
|
||||
|
||||
// Header with site information
|
||||
el(header, { info, pkg }),
|
||||
el("main").append(
|
||||
|
||||
// Main content area
|
||||
el("main", { id: "main-content", role: "main" }).append(
|
||||
// Page title as an h1
|
||||
el("h1", { className: "page-title", textContent: info.title }),
|
||||
|
||||
// Main content from child elements
|
||||
el("slot"),
|
||||
|
||||
// Navigation between pages
|
||||
el(prevNext, info)
|
||||
)
|
||||
));
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { T, t } from "./utils/index.js";
|
||||
export const info= {
|
||||
title: t`Signals and reactivity`,
|
||||
description: t`Handling reactivity in UI via signals.`,
|
||||
title: t`Signals and Reactivity`,
|
||||
description: t`Managing reactive UI state with signals.`,
|
||||
};
|
||||
|
||||
import { el } from "deka-dom-el";
|
||||
@ -43,86 +43,243 @@ const references= {
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("h2", t`Using signals to manage reactivity`),
|
||||
el("h2", t`Building Reactive UIs with Signals`),
|
||||
el("p").append(...T`
|
||||
How a program responds to variable data or user interactions is one of the fundamental problems of
|
||||
programming. If we desire to solve the issue in a declarative manner, signals may be a viable approach.
|
||||
Signals provide a simple yet powerful way to create reactive applications with DDE. They handle the
|
||||
fundamental challenge of keeping your UI in sync with changing data in a declarative, efficient way.
|
||||
`),
|
||||
el("div", { class: "dde-callout" }).append(
|
||||
el("h4", t`What Makes Signals Special?`),
|
||||
el("ul").append(
|
||||
el("li", t`Fine-grained reactivity without complex state management`),
|
||||
el("li", t`Automatic UI updates when data changes`),
|
||||
el("li", t`Clean separation between data, logic, and UI`),
|
||||
el("li", t`Small runtime with minimal overhead`),
|
||||
el("li", t`Works seamlessly with DDE's DOM creation`),
|
||||
el("li", t`No dependencies or framework lock-in`)
|
||||
)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }),
|
||||
|
||||
el(h3, t`Introducing signals`),
|
||||
el(h3, t`The 3-Part Structure of Signals`),
|
||||
el("p").append(...T`
|
||||
Let’s re-introduce
|
||||
${el("a", { textContent: t`3PS principle`, href: "./#h-event-driven-programming--parts-separation--ps" })}.
|
||||
`),
|
||||
el("p").append(...T`
|
||||
Using signals, we split program logic into the three parts. Firstly (α), we create a variable (constant)
|
||||
representing reactive value. Somewhere later, we can register (β) a logic reacting to the signal value
|
||||
changes. Similarly, in a remaining part (γ), we can update the signal value.
|
||||
Signals organize your code into three distinct parts, following the
|
||||
${el("a", { textContent: t`3PS principle`, href: "./#h-event-driven-programming--parts-separation--ps" })}:
|
||||
`),
|
||||
el("div", { class: "dde-signal-diagram" }).append(
|
||||
el("div", { class: "signal-part" }).append(
|
||||
el("h4", t`α: Create Signal`),
|
||||
el(code, { content: "const count = S(0);", page_id }),
|
||||
el("p", t`Define a reactive value that can be observed and changed`)
|
||||
),
|
||||
el("div", { class: "signal-part" }).append(
|
||||
el("h4", t`β: React to Changes`),
|
||||
el(code, { content: "S.on(count, value => updateUI(value));", page_id }),
|
||||
el("p", t`Subscribe to signal changes with callbacks or effects`)
|
||||
),
|
||||
el("div", { class: "signal-part" }).append(
|
||||
el("h4", t`γ: Update Signal`),
|
||||
el(code, { content: "count.set(count.get() + 1);", page_id }),
|
||||
el("p", t`Modify the signal value, which automatically triggers updates`)
|
||||
)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }),
|
||||
|
||||
el("div", { class: "dde-note" }).append(
|
||||
el("p").append(...T`
|
||||
Signals implement the ${el("a", { textContent: t`Publish–subscribe pattern`, ...references.wiki_pubsub })},
|
||||
a form of ${el("a", { textContent: t`Event-driven programming`, ...references.wiki_event_driven })}.
|
||||
This architecture allows different parts of your application to stay synchronized through a shared signal,
|
||||
without direct dependencies on each other.
|
||||
`)
|
||||
),
|
||||
|
||||
el(h3, t`Signal Essentials: Core API`),
|
||||
el("div", { class: "dde-function-table" }).append(
|
||||
el("dl").append(
|
||||
el("dt", t`Creating a Signal`),
|
||||
el("dd", t`S(initialValue) → creates a signal with the given value`),
|
||||
|
||||
el("dt", t`Reading a Signal`),
|
||||
el("dd", t`signal.get() → returns the current value`),
|
||||
|
||||
el("dt", t`Writing to a Signal`),
|
||||
el("dd", t`signal.set(newValue) → updates the value and notifies subscribers`),
|
||||
|
||||
el("dt", t`Subscribing to Changes`),
|
||||
el("dd", t`S.on(signal, callback) → runs callback whenever signal changes`),
|
||||
|
||||
el("dt", t`Unsubscribing`),
|
||||
el("dd", t`S.on(signal, callback, { signal: abortController.signal })`)
|
||||
)
|
||||
),
|
||||
el("p").append(...T`
|
||||
All this is just an example of
|
||||
${el("a", { textContent: t`Event-driven programming`, ...references.wiki_event_driven })} and
|
||||
${el("a", { textContent: t`Publish–subscribe pattern`, ...references.wiki_pubsub })} (compare for example
|
||||
with ${el("a", { textContent: t`fpubsub library`, ...references.fpubsub })}). All three parts can be in
|
||||
some manner independent and still connected to the same reactive entity.
|
||||
Signals can be created with any type of value, but they work best with
|
||||
${el("a", { textContent: t`primitive types`, ...references.mdn_primitive })} like strings, numbers, and booleans.
|
||||
For complex data types like objects and arrays, you'll want to use Actions (covered below).
|
||||
`),
|
||||
|
||||
el(h3, t`Derived Signals: Computed Values`),
|
||||
el("p").append(...T`
|
||||
Signals are implemented in the library as functions. To see current value of signal, just call it without
|
||||
any arguments ${el("code", "console.log(signal())")}. To update the signal value, pass any argument
|
||||
${el("code", `signal('${t`a new value`}')`)}. For listenning the signal value changes, use
|
||||
${el("code", "S.on(signal, console.log)")}.
|
||||
Computed values (also called derived signals) automatically update when their dependencies change.
|
||||
Create them by passing a function to S():
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/signals/derived.js"), page_id }),
|
||||
el("p").append(...T`
|
||||
Similarly to the ${el("code", "on")} function to register DOM events listener. You can use
|
||||
${el("code", "AbortController")}/${el("code", "AbortSignal")} to ${el("em", "off")}/stop listenning. In
|
||||
example, you also found the way for representing “live” piece of code computation pattern (derived signal):
|
||||
Derived signals are read-only - you can't call .set() on them. Their value is always computed
|
||||
from their dependencies. They're perfect for transforming or combining data from other signals.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/signals/computations-abort.js"), page_id }),
|
||||
|
||||
el(h3, t`Signals and actions`),
|
||||
el(h3, t`Signal Actions: For Complex State`),
|
||||
el("p").append(...T`
|
||||
${el("code", `S(/* ${t`primitive`} */)`)} allows you to declare simple reactive variables, typically, around
|
||||
${el("em", t`immutable`)} ${el("a", { textContent: t`primitive types`, ...references.mdn_primitive })}.
|
||||
However, it may also be necessary to use reactive arrays, objects, or other complex reactive structures.
|
||||
When working with objects, arrays, or other complex data structures, Signal Actions provide
|
||||
a structured way to modify state while maintaining reactivity.
|
||||
`),
|
||||
el("div", { class: "dde-illustration" }).append(
|
||||
el("h4", t`Actions vs. Direct Mutation`),
|
||||
el("div", { class: "comparison" }).append(
|
||||
el("div", { class: "bad-practice" }).append(
|
||||
el("h5", t`❌ Without Actions`),
|
||||
el(code, { content: `
|
||||
const todos = S([]);
|
||||
// Directly mutating the array
|
||||
const items = todos.get();
|
||||
items.push("New todo");
|
||||
// This WON'T trigger updates!`, page_id }))
|
||||
),
|
||||
el("div", { class: "good-practice" }).append(
|
||||
el("h5", t`✅ With Actions`),
|
||||
el(code, { content: `const todos = S([], {
|
||||
add(text) {
|
||||
this.value.push(text);
|
||||
// Subscribers notified automatically
|
||||
}
|
||||
});
|
||||
|
||||
// Use the action
|
||||
S.action(todos, "add", "New todo");`, page_id })
|
||||
)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/signals/actions-demo.js"), page_id }),
|
||||
el("p", t`…but typical user-case is object/array (maps, sets and other mutable objects):`),
|
||||
el(example, { src: fileURL("./components/examples/signals/actions-todos.js"), page_id }),
|
||||
|
||||
el("p").append(...T`
|
||||
In some way, you can compare it with ${el("a", { textContent: "useReducer", ...references.mdn_use_reducer })}
|
||||
hook from React. So, the ${el("code", "S(<data>, <actions>)")} pattern creates a store “machine”. We can
|
||||
then invoke (dispatch) registered action by calling ${el("code", "S.action(<signal>, <name>, ...<args>)")}
|
||||
after the action call the signal calls all its listeners. This can be stopped by calling
|
||||
${el("code", "this.stopPropagation()")} in the method representing the given action. As it can be seen in
|
||||
examples, the “store” value is available also in the function for given action (${el("code", "this.value")}).
|
||||
Actions provide these benefits:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", t`Encapsulate state change logic in named methods`),
|
||||
el("li", t`Guarantee notifications when state changes`),
|
||||
el("li", t`Prevent accidental direct mutations`),
|
||||
el("li", t`Act similar to reducers in other state management libraries`)
|
||||
),
|
||||
el("p").append(...T`
|
||||
Here's a more complete example of a todo list using signal actions:
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/signals/actions-todos.js"), page_id }),
|
||||
|
||||
el("div", { class: "dde-tip" }).append(
|
||||
el("p").append(...T`
|
||||
${el("strong", "Special Action Methods")}: Signal actions can implement special lifecycle hooks:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", t`[S.symbols.onclear]() - Called when the signal is cleared`),
|
||||
el("li", t`[S.symbols.onget]() - Called when the signal value is read`),
|
||||
el("li", t`[S.symbols.onset]() - Called after the signal value is changed`)
|
||||
)
|
||||
),
|
||||
|
||||
el(h3, t`Connecting Signals to the DOM`),
|
||||
el("p").append(...T`
|
||||
Signals really shine when connected to your UI. DDE provides several ways to bind signals to DOM elements:
|
||||
`),
|
||||
|
||||
el(h3, t`Reactive DOM attributes and elements`),
|
||||
el("p", t`There are on basic level two distinc situation to mirror dynamic value into the DOM/UI`),
|
||||
el("ol").append(
|
||||
el("li", t`to change some attribute(s) of existing element(s)`),
|
||||
el("li", t`to generate elements itself dynamically – this covers conditions and loops`)
|
||||
el("div", { class: "dde-tabs" }).append(
|
||||
el("div", { class: "tab", "data-tab": "attributes" }).append(
|
||||
el("h4", t`Reactive Attributes`),
|
||||
el("p", t`Bind signal values directly to element attributes, properties, or styles:`),
|
||||
el(code, { content: `// Create a signal
|
||||
const color = S("blue");
|
||||
|
||||
// Bind it to an element's style
|
||||
el("div", {
|
||||
style: {
|
||||
color, // Updates when signal changes
|
||||
fontWeight: S(() => color.get() === "red" ? "bold" : "normal")
|
||||
}
|
||||
}, "This text changes color");
|
||||
|
||||
// Later:
|
||||
color.set("red"); // UI updates automatically`, page_id })
|
||||
),
|
||||
el("div", { class: "tab", "data-tab": "elements" }).append(
|
||||
el("h4", t`Reactive Elements`),
|
||||
el("p", t`Dynamically create or update elements based on signal values:`),
|
||||
el(code, { content: `// Create an array signal
|
||||
const items = S(["Apple", "Banana", "Cherry"]);
|
||||
|
||||
// Create a dynamic list that updates when items change
|
||||
el("ul").append(
|
||||
S.el(items, items =>
|
||||
items.map(item => el("li", item))
|
||||
)
|
||||
);
|
||||
|
||||
// Later:
|
||||
S.action(items, "push", "Dragonfruit"); // List updates automatically`, page_id })
|
||||
)
|
||||
),
|
||||
|
||||
el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }),
|
||||
el("p").append(...T`
|
||||
To derived attribute based on value of signal variable just use the signal as a value of the attribute
|
||||
(${el("code", "assign(element, { attribute: S('value') })")}). ${el("code", "assign")}/${el("code", "el")}
|
||||
provides ways to glue reactive attributes/classes more granularly into the DOM. Just use dedicated build-in
|
||||
attributes ${el("code", "dataset")}, ${el("code", "ariaset")} and ${el("code", "classList")}.
|
||||
The ${el("code", "assign")} and ${el("code", "el")} functions detect signals automatically and handle binding.
|
||||
You can use special properties like ${el("code", "dataset")}, ${el("code", "ariaset")}, and
|
||||
${el("code", "classList")} for fine-grained control over specific attribute types.
|
||||
`),
|
||||
|
||||
el("p").append(...T`
|
||||
For computation, you can use the “derived signal” (see above) like
|
||||
${el("code", "assign(element, { textContent: S(()=> 'Hello '+WorldSignal()) })")}. This is read-only signal
|
||||
its value is computed based on given function and updated when any signal used in the function changes.
|
||||
`),
|
||||
el("p").append(...T`
|
||||
To represent part of the template filled dynamically based on the signal value use
|
||||
${el("code", "S.el(signal, DOMgenerator)")}. This was already used in the todo example above or see:
|
||||
${el("code", "S.el()")} is especially powerful for conditional rendering and lists:
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/signals/dom-el.js"), page_id }),
|
||||
|
||||
el(h3, t`Best Practices for Signals`),
|
||||
el("p").append(...T`
|
||||
Follow these guidelines to get the most out of signals:
|
||||
`),
|
||||
el("ol").append(
|
||||
el("li").append(...T`
|
||||
${el("strong", "Keep signals small and focused")}: Use many small signals rather than a few large ones
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Use derived signals for computations")}: Don't recompute values in multiple places
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Clean up signal subscriptions")}: Use AbortController or scope.host() to prevent memory leaks
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Use actions for complex state")}: Don't directly mutate objects or arrays in signals
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Avoid infinite loops")}: Be careful when one signal updates another in a subscription
|
||||
`)
|
||||
),
|
||||
|
||||
el("div", { class: "dde-troubleshooting" }).append(
|
||||
el("h4", t`Common Signal Pitfalls`),
|
||||
el("dl").append(
|
||||
el("dt", t`UI not updating when array/object changes`),
|
||||
el("dd", t`Use signal actions instead of direct mutation`),
|
||||
|
||||
el("dt", t`Infinite update loops`),
|
||||
el("dd", t`Check for circular dependencies between signals`),
|
||||
|
||||
el("dt", t`Memory leaks`),
|
||||
el("dd", t`Use AbortController or scope.host() to clean up subscriptions`),
|
||||
|
||||
el("dt", t`Multiple elements updating unnecessarily`),
|
||||
el("dd", t`Split large signals into smaller, more focused ones`)
|
||||
)
|
||||
),
|
||||
|
||||
el(mnemonic)
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { T, t } from "./utils/index.js";
|
||||
export const info= {
|
||||
title: t`Scopes and components`,
|
||||
description: t`Organizing UI into components`,
|
||||
title: t`Scopes and Components`,
|
||||
description: t`Organizing UI into reusable, manageable components`,
|
||||
};
|
||||
|
||||
import { el } from "deka-dom-el";
|
||||
@ -28,60 +28,226 @@ const references= {
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("h2", t`Using functions as UI components`),
|
||||
el("h2", t`Building Maintainable UIs with Scopes and Components`),
|
||||
el("p").append(...T`
|
||||
For state-less components we can use functions as UI components (see “Elements” page). But in real life,
|
||||
we may need to handle the component live-cycle and provide JavaScript the way to properly use
|
||||
the ${el("a", { textContent: t`Garbage collection`, ...references.garbage_collection })}.
|
||||
Scopes provide a structured way to organize your UI code into reusable components that properly
|
||||
manage their lifecycle, handle cleanup, and maintain clear boundaries between different parts of your application.
|
||||
`),
|
||||
el("div", { class: "dde-callout" }).append(
|
||||
el("h4", t`Why Use Scopes?`),
|
||||
el("ul").append(
|
||||
el("li", t`Automatic resource cleanup when components are removed from DOM`),
|
||||
el("li", t`Clear component boundaries with explicit host elements`),
|
||||
el("li", t`Simplified event handling with proper "this" binding`),
|
||||
el("li", t`Seamless integration with signals for reactive components`),
|
||||
el("li", t`Better memory management with ${el("a", { textContent: t`GC`, ...references.garbage_collection })}`)
|
||||
)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/scopes/intro.js"), page_id }),
|
||||
el("p").append(...T`The library therefore use ${el("em", t`scopes`)} to provide these functionalities.`),
|
||||
|
||||
el(h3, t`Scopes and hosts`),
|
||||
el(h3, t`Understanding Host Elements and Scopes`),
|
||||
el("div", { class: "dde-illustration" }).append(
|
||||
el("h4", t`Component Anatomy`),
|
||||
el("pre").append(el("code", `
|
||||
┌─────────────────────────────────┐
|
||||
│ // 1. Component scope created │
|
||||
│ el(MyComponent); │
|
||||
│ │
|
||||
│ function MyComponent() { │
|
||||
│ // 2. access the host element │
|
||||
│ const { host } = scope; │
|
||||
│ │
|
||||
│ // 3. Add behavior to host │
|
||||
│ host( │
|
||||
│ on.click(handleClick) │
|
||||
│ ); │
|
||||
│ │
|
||||
│ // 4. Return the host element │
|
||||
│ return el("div", { │
|
||||
│ className: "my-component" │
|
||||
│ }).append( │
|
||||
│ el("h2", "Title"), │
|
||||
│ el("p", "Content") │
|
||||
│ ); │
|
||||
│ } │
|
||||
└─────────────────────────────────┘
|
||||
`))
|
||||
),
|
||||
el("p").append(...T`
|
||||
The ${el("strong", "host")} is the name for the element representing the component. This is typically
|
||||
element returned by function. To get reference, you can use ${el("code", "scope.host()")} to applly addons
|
||||
just use ${el("code", "scope.host(...<addons>)")}.
|
||||
The ${el("strong", "host element")} is the root element of your component - typically the element returned
|
||||
by your component function. It serves as the identity of your component in the DOM.
|
||||
`),
|
||||
el("div", { class: "dde-function-table" }).append(
|
||||
el("h4", t`scope.host()`),
|
||||
el("dl").append(
|
||||
el("dt", t`When called with no arguments`),
|
||||
el("dd", t`Returns a reference to the host element (the root element of your component)`),
|
||||
|
||||
el("dt", t`When called with addons/callbacks`),
|
||||
el("dd", t`Applies the addons to the host element and returns the host element`)
|
||||
)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/scopes/scopes-and-hosts.js"), page_id }),
|
||||
|
||||
el("div", { class: "dde-tip" }).append(
|
||||
el("p").append(...T`
|
||||
${el("strong", "Best Practice:")} Always capture the host reference at the beginning of your component function
|
||||
using ${el("code", "const { host } = scope")} to avoid scope-related issues, especially with asynchronous code.
|
||||
`)
|
||||
),
|
||||
|
||||
el(h3, t`Class-Based Components`),
|
||||
el("p").append(...T`
|
||||
To better understanding we implement function ${el("code", "elClass")} helping to create component as
|
||||
class instances.
|
||||
While functional components are the primary pattern in DDE, you can also create class-based components
|
||||
for more structured organization of component logic.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }),
|
||||
|
||||
el("p").append(...T`
|
||||
As you can see, the ${el("code", "scope.host()")} is stored temporarily and synchronously. Therefore, at
|
||||
least in the beginning of using library, it is the good practise to store ${el("code", "host")} in the root
|
||||
of your component. As it may be changed, typically when there is asynchronous code in the component.
|
||||
This pattern can be useful when:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", t`You have complex component logic that benefits from object-oriented organization`),
|
||||
el("li", t`You need private methods and properties for your component`),
|
||||
el("li", t`You're transitioning from another class-based component system`)
|
||||
),
|
||||
el("div", { class: "dde-tip" }).append(
|
||||
el("p").append(...T`
|
||||
${el("strong", "Note:")} Even with class-based components, follow the best practice of storing the host reference
|
||||
early in your component code. This ensures proper access to the host throughout the component's lifecycle.
|
||||
`)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/scopes/good-practise.js"), page_id }),
|
||||
|
||||
el(h3, t`Scopes, signals and cleaning magic`),
|
||||
el(h3, t`Automatic Cleanup with Scopes`),
|
||||
el("p").append(...T`
|
||||
The ${el("code", "host")} is internally used to register the cleaning procedure, when the component
|
||||
(${el("code", "host")} element) is removed from the DOM.
|
||||
One of the most powerful features of scopes is automatic cleanup when components are removed from the DOM.
|
||||
This prevents memory leaks and ensures resources are properly released.
|
||||
`),
|
||||
el("div", { class: "dde-illustration" }).append(
|
||||
el("h4", t`Lifecycle Flow`),
|
||||
el("pre").append(el("code", `
|
||||
1. Component created → scope established
|
||||
2. Component added to DOM → connected event
|
||||
3. Component interactions happen
|
||||
4. Component removed from DOM → disconnected event
|
||||
5. Automatic cleanup of:
|
||||
- Event listeners
|
||||
- Signal subscriptions
|
||||
- Custom cleanup code
|
||||
`))
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/scopes/cleaning.js"), page_id }),
|
||||
|
||||
el("div", { class: "dde-note" }).append(
|
||||
el("p").append(...T`
|
||||
In this example, when you click "Remove", the component is removed from the DOM, and all its associated
|
||||
resources are automatically cleaned up, including the signal subscription that updates the text content.
|
||||
This happens because the library internally registers a disconnected event handler on the host element.
|
||||
`)
|
||||
),
|
||||
|
||||
el(h3, t`Declarative vs Imperative Components`),
|
||||
el("p").append(...T`
|
||||
The text content of the paragraph is changing when the value of the signal ${el("code", "textContent")}
|
||||
is changed. Internally, there is association between ${el("code", "textContent")} and the paragraph,
|
||||
similar to using ${el("code", `S.on(textContent, /* ${t`update the paragraph`} */)`)}.
|
||||
`),
|
||||
el("p").append(...T`
|
||||
This listener must be removed when the component is removed from the DOM. To do it, the library assign
|
||||
internally ${el("code", `on.disconnected(/* ${t`remove the listener`} */)(host())`)} to the host element.
|
||||
`),
|
||||
el("p", { className: "notice" }).append(...T`
|
||||
The library DOM API and signals works ideally when used declaratively. It means, you split your app logic
|
||||
into three parts as it was itroduced in ${el("a", { textContent: "Signals", ...references.signals })}.
|
||||
Scopes work best with a declarative approach to UI building, especially when combined
|
||||
with ${el("a", { textContent: "signals", ...references.signals })} for state management.
|
||||
`),
|
||||
el("div", { class: "dde-tabs" }).append(
|
||||
el("div", { class: "tab", "data-tab": "declarative" }).append(
|
||||
el("h4", t`✅ Declarative Approach`),
|
||||
el("p", t`Define what your UI should look like based on state:`),
|
||||
el("pre").append(el("code", `function Counter() {
|
||||
const { host } = scope;
|
||||
|
||||
// Define state
|
||||
const count = S(0);
|
||||
|
||||
// Define behavior
|
||||
const increment = () => count.set(count.get() + 1);
|
||||
|
||||
// UI automatically updates when count changes
|
||||
return el("div").append(
|
||||
el("p", S(() => "Count: " + count.get())),
|
||||
el("button", {
|
||||
onclick: increment,
|
||||
textContent: "Increment"
|
||||
})
|
||||
);
|
||||
}`))
|
||||
),
|
||||
el("div", { class: "tab", "data-tab": "imperative" }).append(
|
||||
el("h4", t`⚠️ Imperative Approach`),
|
||||
el("p", t`Manually update the DOM in response to events:`),
|
||||
el("pre").append(el("code", `function Counter() {
|
||||
const { host } = scope;
|
||||
|
||||
let count = 0;
|
||||
const counterText = el("p", "Count: 0");
|
||||
|
||||
// Manually update DOM element
|
||||
const increment = () => {
|
||||
count++;
|
||||
counterText.textContent = "Count: " + count;
|
||||
};
|
||||
|
||||
return el("div").append(
|
||||
counterText,
|
||||
el("button", {
|
||||
onclick: increment,
|
||||
textContent: "Increment"
|
||||
})
|
||||
);
|
||||
}`))
|
||||
)
|
||||
),
|
||||
|
||||
el(code, { src: fileURL("./components/examples/scopes/declarative.js"), page_id }),
|
||||
el("p").append(...T`
|
||||
Strictly speaking, the imperative way of using the library is not prohibited. Just be careful (rather avoid)
|
||||
mixing declarative approach (using signals) and imperative manipulation of elements.
|
||||
`),
|
||||
|
||||
el("div", { class: "dde-note" }).append(
|
||||
el("p").append(...T`
|
||||
While DDE supports both declarative and imperative approaches, the declarative style is recommended
|
||||
as it leads to more maintainable code with fewer opportunities for bugs. Signals handle the complexity
|
||||
of keeping your UI in sync with your data.
|
||||
`)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/scopes/imperative.js"), page_id }),
|
||||
|
||||
el(h3, t`Best Practices for Scopes and Components`),
|
||||
el("ol").append(
|
||||
el("li").append(...T`
|
||||
${el("strong", "Capture host early:")} Use ${el("code", "const { host } = scope")} at component start
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Return a single root element:")} Components should have one host element that contains all others
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Prefer declarative patterns:")} Use signals to drive UI updates rather than manual DOM manipulation
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Keep components focused:")} Each component should do one thing well
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Add explicit cleanup:")} For resources not managed by DDE, use ${el("code", "on.disconnected")}
|
||||
`)
|
||||
),
|
||||
|
||||
el("div", { class: "dde-troubleshooting" }).append(
|
||||
el("h4", t`Common Scope Pitfalls`),
|
||||
el("dl").append(
|
||||
el("dt", t`Losing host reference in async code`),
|
||||
el("dd", t`Store host reference early with const { host } = scope`),
|
||||
|
||||
el("dt", t`Memory leaks from custom resources`),
|
||||
el("dd", t`Use host(on.disconnected(cleanup)) for manual resource cleanup`),
|
||||
|
||||
el("dt", t`Event handlers with incorrect 'this'`),
|
||||
el("dd", t`Use arrow functions or .bind() to preserve context`),
|
||||
|
||||
el("dt", t`Mixing declarative and imperative styles`),
|
||||
el("dd", t`Choose one approach and be consistent throughout a component`)
|
||||
)
|
||||
),
|
||||
|
||||
el(mnemonic)
|
||||
);
|
||||
}
|
||||
|
@ -53,90 +53,236 @@ const references= {
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("h2", t`Using web components in combinantion with DDE`),
|
||||
el("h2", t`Using Web Components with DDE: Better Together`),
|
||||
el("p").append(...T`
|
||||
The DDE library allows for use within ${el("a", references.mdn_web_components).append( el("strong",
|
||||
t`Web Components`) )} for dom-tree generation. However, in order to be able to use signals (possibly
|
||||
mapping to registered ${el("a", references.mdn_observedAttributes).append( el("code", "observedAttributes")
|
||||
)}) and additional functionality is (unfortunately) required to use helpers provided by the library.
|
||||
DDE pairs powerfully with ${el("a", references.mdn_web_components).append(el("strong", t`Web Components`))}
|
||||
to create reusable, encapsulated custom elements with all the benefits of DDE's declarative DOM
|
||||
construction and reactivity system.
|
||||
`),
|
||||
el("div", { class: "dde-callout" }).append(
|
||||
el("h4", t`Why Combine DDE with Web Components?`),
|
||||
el("ul").append(
|
||||
el("li", t`Declarative DOM creation within your components`),
|
||||
el("li", t`Reactive attribute updates through signals`),
|
||||
el("li", t`Simplified event handling with the same events API`),
|
||||
el("li", t`Clean component lifecycle management`),
|
||||
el("li", t`Improved code organization with scopes`)
|
||||
)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/customElement/intro.js"), page_id }),
|
||||
|
||||
el(h3, t`Custom Elements Introduction`),
|
||||
el(h3, t`Getting Started: Web Components Basics`),
|
||||
el("p").append(...T`
|
||||
Web Components, specifically Custom Elements, are a set of web platform APIs that allow you to create
|
||||
new HTML tags with custom functionality encapsulated within them. This allows for the creation of reusable
|
||||
components that can be used across web applications.
|
||||
`),
|
||||
el("p").append(...T`
|
||||
To start with, let’s see how to use native Custom Elements. As starting point please read
|
||||
${el("a", references.mdn_custom_elements).append( el("strong", t`Using Custom Elements`), t` on MDN` )}.
|
||||
To sum up and for mnemonic see following code overview:
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/customElement/native-basic.js"), page_id }),
|
||||
el("p").append(...T`
|
||||
For more advanced use of Custom Elements, the summary ${el("a", references.custom_elements_tips)
|
||||
.append( el("strong", t`Handy Custom Elements Patterns`) )} may be useful. Especially pay attention to
|
||||
linking HTML attributes and defining setters/getters, this is very helpful to use in combination with
|
||||
the library (${el("code", `el(HTMLCustomElement.tagName, { customAttribute: "${t`new-value`}" });`)}).
|
||||
`),
|
||||
el("p").append(...T`
|
||||
Also see the Life Cycle Events sections, very similarly we would like to use
|
||||
${el("a", { textContent: t`DDE events`, href: "./p03-events.html", title: t`See events part of the library
|
||||
documentation` })}. To do it, the library provides function ${el("code", "customElementWithDDE")}…
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js"), page_id }),
|
||||
|
||||
el("h3", t`Custom Elements with DDE`),
|
||||
el("p").append(...T`
|
||||
The ${el("code", "customElementWithDDE")} function is only (small) part of the inregration of the library.
|
||||
More important for coexistence is render component function as a body of the Custom Element. For that, you
|
||||
can use ${el("code", "customElementRender")} with arguments instance reference, target for connection,
|
||||
render function and optional properties (will be passed to the render function) see later…
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/customElement/dde.js"), page_id }),
|
||||
el("p").append(...T`
|
||||
…as you can see, you can use components created based on the documentation previously introduced. To unlock
|
||||
full potential, use with combination ${el("code", "customElementWithDDE")} (allows to use livecycle events)
|
||||
and ${el("code", "observedAttributes")} (converts attributes to render function arguments —
|
||||
${el("em", "default")}) or ${el("code", "S.observedAttributes")} (converts attributes to signals).
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/customElement/observedAttributes.js"), page_id }),
|
||||
|
||||
|
||||
el(h3, t`Shadow DOM`),
|
||||
el("p").append(...T`
|
||||
Shadow DOM is a web platform feature that allows for the encapsulation of a component’s internal DOM tree
|
||||
from the rest of the document. This means that styles and scripts applied to the document will not affect
|
||||
the component’s internal DOM, and vice versa.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js"), page_id }),
|
||||
el("p").append(...T`
|
||||
Regarding to ${el("code", "this.attachShadow({ mode: 'open' })")} see quick overview
|
||||
${el("a", { textContent: t`Using Shadow DOM`, ...references.mdn_shadow_dom_depth })}. An another source of
|
||||
information can be ${el("a", { textContent: t`Shadow DOM in Depth`, ...references.shadow_dom_depth })}.
|
||||
`),
|
||||
el("p").append(...T`
|
||||
Besides the encapsulation, the Shadow DOM allows for using the ${el("a", references.mdn_shadow_dom_slot).append(
|
||||
el("strong", t`<slot>`), t` element(s)`)}. You can simulate this feature using ${el("code", "simulateSlots")}:
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js"), page_id }),
|
||||
el("p").append(...T`
|
||||
To sum up:
|
||||
Web Components are a set of standard browser APIs that let you create custom HTML elements with
|
||||
encapsulated functionality. They consist of three main technologies:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li").append(...T`
|
||||
The use of shadow DOM to encapsulate the internal structure of the custom element, which affects how
|
||||
the custom element can be styled and modified using JavaScript and CSS.
|
||||
${el("strong", "Custom Elements:")} Create your own HTML tags with JS-defined behavior
|
||||
`),
|
||||
el("li").append(...T`
|
||||
The ability to access and modify the internal structure of the custom element using JavaScript, which
|
||||
is affected by the use of shadow DOM and the mode of the shadow DOM.
|
||||
${el("strong", "Shadow DOM:")} Encapsulate styles and markup within a component
|
||||
`),
|
||||
el("li").append(...T`
|
||||
The use of slots to allow for the insertion of content from the parent document into the custom
|
||||
element, which is affected by the use of shadow DOM and the mode of the shadow DOM.
|
||||
${el("strong", "HTML Templates:")} Define reusable markup structures
|
||||
`)
|
||||
),
|
||||
el("p").append(...T`
|
||||
Let's start with a basic Custom Element example without DDE to establish the foundation:
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/customElement/native-basic.js"), page_id }),
|
||||
|
||||
el("div", { class: "dde-note" }).append(
|
||||
el("p").append(...T`
|
||||
For complete information on Web Components, see the
|
||||
${el("a", references.mdn_custom_elements).append(el("strong", t`MDN documentation`))}.
|
||||
Also, ${el("a", references.custom_elements_tips).append(el("strong", t`Handy Custom Elements Patterns`))}
|
||||
provides useful techniques for connecting attributes with properties.
|
||||
`)
|
||||
),
|
||||
|
||||
el(h3, t`DDE Integration: Step 1 - Event Handling`),
|
||||
el("p").append(...T`
|
||||
The first step in integrating DDE with Web Components is enabling DDE's event system to work with your
|
||||
Custom Elements. This is done with ${el("code", "customElementWithDDE")}, which makes your Custom Element
|
||||
compatible with DDE's event handling.
|
||||
`),
|
||||
el("div", { class: "dde-function-table" }).append(
|
||||
el("h4", t`customElementWithDDE`),
|
||||
el("dl").append(
|
||||
el("dt", t`Purpose`),
|
||||
el("dd", t`Enables DDE's event system to work with your Custom Element`),
|
||||
el("dt", t`Usage`),
|
||||
el("dd", t`customElementWithDDE(YourElementClass)`),
|
||||
el("dt", t`Benefits`),
|
||||
el("dd", t`Allows using on.connected(), on.disconnected(), etc. with your element`)
|
||||
)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js"), page_id }),
|
||||
|
||||
el("div", { class: "dde-tip" }).append(
|
||||
el("p").append(...T`
|
||||
${el("strong", "Key Point:")} The ${el("code", "customElementWithDDE")} function adds event dispatching
|
||||
to your Custom Element lifecycle methods, making them work seamlessly with DDE's event system.
|
||||
`)
|
||||
),
|
||||
|
||||
el(h3, t`DDE Integration: Step 2 - Rendering Components`),
|
||||
el("p").append(...T`
|
||||
The next step is to use DDE's component rendering within your Custom Element. This is done with
|
||||
${el("code", "customElementRender")}, which connects your DDE component function to the Custom Element.
|
||||
`),
|
||||
el("div", { class: "dde-function-table" }).append(
|
||||
el("h4", t`customElementRender`),
|
||||
el("dl").append(
|
||||
el("dt", t`Purpose`),
|
||||
el("dd", t`Connects a DDE component function to a Custom Element`),
|
||||
el("dt", t`Parameters`),
|
||||
el("dd").append(
|
||||
el("ol").append(
|
||||
el("li", t`Target (usually this or this.shadowRoot)`),
|
||||
el("li", t`Component function that returns a DOM tree`),
|
||||
el("li", t`Optional: Attributes transformer function (default or S.observedAttributes)`)
|
||||
)
|
||||
),
|
||||
el("dt", t`Returns`),
|
||||
el("dd", t`The rendered DOM tree`)
|
||||
)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/customElement/dde.js"), page_id }),
|
||||
|
||||
el("div", { class: "dde-note" }).append(
|
||||
el("p").append(...T`
|
||||
In this example, we're using Shadow DOM (${el("code", "this.attachShadow()")}) for encapsulation,
|
||||
but you can also render directly to the element with ${el("code", "customElementRender(this, ...)")}.
|
||||
`)
|
||||
),
|
||||
|
||||
el(h3, t`Reactive Web Components with Signals`),
|
||||
el("p").append(...T`
|
||||
One of the most powerful features of integrating DDE with Web Components is connecting HTML attributes
|
||||
to DDE's reactive signals system. This creates truly reactive custom elements.
|
||||
`),
|
||||
el("div", { class: "dde-tip" }).append(
|
||||
el("p").append(...T`
|
||||
${el("strong", "Two Ways to Handle Attributes:")}
|
||||
`),
|
||||
el("ol").append(
|
||||
el("li").append(...T`
|
||||
${el("code", "observedAttributes")} - Passes attributes as regular values (static)
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("code", "S.observedAttributes")} - Transforms attributes into signals (reactive)
|
||||
`)
|
||||
)
|
||||
),
|
||||
el("p").append(...T`
|
||||
Using ${el("code", "S.observedAttributes")} creates a reactive connection between your element's attributes
|
||||
and its internal rendering. When attributes change, your component automatically updates!
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/customElement/observedAttributes.js"), page_id }),
|
||||
|
||||
el("div", { class: "dde-callout" }).append(
|
||||
el("h4", t`How S.observedAttributes Works`),
|
||||
el("p").append(...T`
|
||||
1. Takes each attribute listed in static observedAttributes
|
||||
2. Creates a DDE signal for each one
|
||||
3. Automatically updates these signals when attributes change
|
||||
4. Passes the signals to your component function
|
||||
5. Your component reacts to changes through signal subscriptions
|
||||
`)
|
||||
),
|
||||
|
||||
el(h3, t`Working with Shadow DOM`),
|
||||
el("p").append(...T`
|
||||
Shadow DOM provides encapsulation for your component's styles and markup. When using DDE with Shadow DOM,
|
||||
you get the best of both worlds: encapsulation plus declarative DOM creation.
|
||||
`),
|
||||
el("div", { class: "dde-illustration" }).append(
|
||||
el("h4", t`Shadow DOM Encapsulation`),
|
||||
el("pre").append(el("code", `
|
||||
┌─────────────────────────────────┐
|
||||
│ <my-custom-element> │
|
||||
│ │
|
||||
│ ┌─────────────────────────┐ │
|
||||
│ │ #shadow-root │ │
|
||||
│ │ │ │
|
||||
│ │ Created with DDE: │ │
|
||||
│ │ ┌─────────────────┐ │ │
|
||||
│ │ │ <div> │ │ │
|
||||
│ │ │ <h2>Title</h2> │ │ │
|
||||
│ │ │ <p>Content</p> │ │ │
|
||||
│ │ └─────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────┘
|
||||
`))
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js"), page_id }),
|
||||
|
||||
el("p").append(...T`
|
||||
For more information on Shadow DOM, see
|
||||
${el("a", { textContent: t`Using Shadow DOM`, ...references.mdn_shadow_dom_depth })}, or the comprehensive
|
||||
${el("a", { textContent: t`Shadow DOM in Depth`, ...references.shadow_dom_depth })}.
|
||||
`),
|
||||
|
||||
el(h3, t`Working with Slots`),
|
||||
el("p").append(...T`
|
||||
Slots allow users of your component to insert content inside it. When using DDE, you can simulate the
|
||||
slot mechanism with the ${el("code", "simulateSlots")} function:
|
||||
`),
|
||||
el("div", { class: "dde-function-table" }).append(
|
||||
el("h4", t`simulateSlots`),
|
||||
el("dl").append(
|
||||
el("dt", t`Purpose`),
|
||||
el("dd", t`Provides slot functionality when you cannot/do not want to use shadow DOM`),
|
||||
el("dt", t`Parameters`),
|
||||
el("dd", t`A mapping object of slot names to DOM elements`)
|
||||
)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js"), page_id }),
|
||||
|
||||
el("div", { class: "dde-tip" }).append(
|
||||
el("p").append(...T`
|
||||
${el("strong", "When to use simulateSlots:")} This approach is useful when you need to distribute
|
||||
content from the light DOM into specific locations in the shadow DOM, particularly in environments
|
||||
where native slots might not be fully supported.
|
||||
`)
|
||||
),
|
||||
|
||||
el(h3, t`Best Practices for Web Components with DDE`),
|
||||
el("p").append(...T`
|
||||
When combining DDE with Web Components, follow these recommendations:
|
||||
`),
|
||||
el("ol").append(
|
||||
el("li").append(...T`
|
||||
${el("strong", "Always use customElementWithDDE")} to enable event integration
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Prefer S.observedAttributes")} for reactive attribute connections
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Create reusable component functions")} that your custom elements render
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Use scope.host()")} to clean up event listeners and subscriptions
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Add setters and getters")} for better property access to your element
|
||||
`)
|
||||
),
|
||||
|
||||
el("div", { class: "dde-troubleshooting" }).append(
|
||||
el("h4", t`Common Issues`),
|
||||
el("dl").append(
|
||||
el("dt", t`Events not firing properly`),
|
||||
el("dd", t`Make sure you called customElementWithDDE before defining the element`),
|
||||
el("dt", t`Attributes not updating`),
|
||||
el("dd", t`Check that you've properly listed them in static observedAttributes`),
|
||||
el("dt", t`Component not rendering`),
|
||||
el("dd", t`Verify customElementRender is called in connectedCallback, not constructor`)
|
||||
)
|
||||
),
|
||||
|
||||
el(mnemonic)
|
||||
|
186
docs/p07-debugging.html.js
Normal file
186
docs/p07-debugging.html.js
Normal file
@ -0,0 +1,186 @@
|
||||
import { T, t } from "./utils/index.js";
|
||||
export const info= {
|
||||
title: t`Debugging`,
|
||||
description: t`Techniques for debugging applications using deka-dom-el, especially signals.`,
|
||||
};
|
||||
|
||||
import { el } from "deka-dom-el";
|
||||
import { simplePage } from "./layout/simplePage.html.js";
|
||||
import { example } from "./components/example.html.js";
|
||||
import { h3 } from "./components/pageUtils.html.js";
|
||||
import { code } from "./components/code.html.js";
|
||||
/** @param {string} url */
|
||||
const fileURL= url=> new URL(url, import.meta.url);
|
||||
|
||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("h2", t`Debugging applications with deka-dom-el`),
|
||||
el("p").append(...T`
|
||||
Debugging is an essential part of application development. This guide provides techniques
|
||||
and best practices for debugging applications built with deka-dom-el, with a focus on signals.
|
||||
`),
|
||||
|
||||
el(h3, t`Debugging signals`),
|
||||
el("p").append(...T`
|
||||
Signals are reactive primitives that update the UI when their values change. When debugging signals,
|
||||
you need to track their values, understand their dependencies, and identify why updates are or aren't happening.
|
||||
`),
|
||||
|
||||
el("h4", t`Inspecting signal values`),
|
||||
el("p").append(...T`
|
||||
The simplest way to debug a signal is to log its current value by calling the get method:
|
||||
`),
|
||||
el(code, { content: "const signal = S(0);\nconsole.log('Current value:', signal.get());", page_id }),
|
||||
el("p").append(...T`
|
||||
You can also monitor signal changes by adding a listener:
|
||||
`),
|
||||
el(code, {
|
||||
content:
|
||||
"// Log every time the signal changes\nS.on(signal, value => console.log('Signal changed:', value));",
|
||||
page_id }),
|
||||
|
||||
el("h4", t`Debugging derived signals`),
|
||||
el("p").append(...T`
|
||||
With derived signals (created with S(() => computation)), debugging is a bit more complex
|
||||
because the value depends on other signals. To understand why a derived signal isn't updating correctly:
|
||||
`),
|
||||
el("ol").append(
|
||||
el("li", t`Check that all dependency signals are updating correctly`),
|
||||
el("li", t`Add logging inside the computation function to see when it runs`),
|
||||
el("li", t`Verify that the computation function actually accesses the signal values with .get()`)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/debugging/consoleLog.js"), page_id }),
|
||||
|
||||
el(h3, t`Common signal debugging issues`),
|
||||
el("h4", t`Signal updates not triggering UI changes`),
|
||||
el("p").append(...T`
|
||||
If signal updates aren't reflected in the UI, check:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", t`That you're using signal.set() to update the value, not modifying objects/arrays directly`),
|
||||
el("li", t`For mutable objects, ensure you're using actions or making proper copies before updating`),
|
||||
el("li", t`That the signal is actually connected to the DOM element (check your S.el or attribute binding code)`)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }),
|
||||
|
||||
el("h4", t`Memory leaks with signal listeners`),
|
||||
el("p").append(...T`
|
||||
Signal listeners can cause memory leaks if not properly cleaned up. Always use AbortSignal
|
||||
to cancel listeners.
|
||||
`),
|
||||
|
||||
el("h4", t`Performance issues with frequently updating signals`),
|
||||
el("p").append(...T`
|
||||
If you notice performance issues with signals that update very frequently:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", t`Consider debouncing or throttling signal updates`),
|
||||
el("li", t`Make sure derived signals don't perform expensive calculations unnecessarily`),
|
||||
el("li", t`Keep signal computations focused and minimal`)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/debugging/debouncing.js"), page_id }),
|
||||
|
||||
el(h3, t`Browser DevTools tips for deka-dom-el`),
|
||||
el("p").append(...T`
|
||||
When debugging in the browser, deka-dom-el provides several helpful DevTools-friendly features:
|
||||
`),
|
||||
|
||||
el("h4", t`Identifying components in the DOM`),
|
||||
el("p").append(...T`
|
||||
deka-dom-el marks components in the DOM with special comment nodes to help you identify component boundaries.
|
||||
Components created with ${el("code", "el(ComponentFunction)")} are marked with comment nodes
|
||||
${el("code", "<!--<dde:mark type=\"component\" name=\"MyComponent\" host=\"parentElement\"/>-->")} and
|
||||
includes:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", "type - Identifies the type of marker (\"component\", \"reactive\", or \"later\")"),
|
||||
el("li", "name - The name of the component function"),
|
||||
el("li", "host - Indicates whether the host is \"this\" (for DocumentFragments) or \"parentElement\""),
|
||||
),
|
||||
|
||||
el("h4", t`Finding reactive elements in the DOM`),
|
||||
el("p").append(...T`
|
||||
When using ${el("code", "S.el()")}, deka-dom-el creates reactive elements in the DOM
|
||||
that are automatically updated when signal values change. These elements are wrapped in special
|
||||
comment nodes for debugging (to be true they are also used internaly, so please do not edit them by hand):
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/debugging/dom-reactive-mark.js"), page_id }),
|
||||
el("p").append(...T`
|
||||
This is particularly useful when debugging why a reactive section isn't updating as expected.
|
||||
You can inspect the elements between the comment nodes to see their current state and the
|
||||
signal connections through \`__dde_reactive\` of the host element.
|
||||
`),
|
||||
|
||||
el("h4", t`DOM inspection properties`),
|
||||
el("p").append(...T`
|
||||
Elements created with the deka-dom-el library have special properties to aid in debugging:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li").append(...T`
|
||||
${el("code", "__dde_reactive")} - An array property on DOM elements that tracks signal-to-element relationships.
|
||||
This allows you to quickly identify which elements are reactive and what signals they're bound to.
|
||||
Each entry in the array contains:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", "A pair of signal and listener function: [signal, listener]"),
|
||||
el("li", "Additional context information about the element or attribute"),
|
||||
el("li", "Automatically managed by signal.el(), signal.observedAttributes(), and processReactiveAttribute()")
|
||||
),
|
||||
el("li").append(...T`
|
||||
${el("code", "__dde_signal")} - A Symbol property used to identify and store the internal state of signal objects.
|
||||
It contains the following information:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", "value: The current value of the signal"),
|
||||
el("li", "listeners: A Set of functions called when the signal value changes"),
|
||||
el("li", "actions: Custom actions that can be performed on the signal"),
|
||||
el("li", "onclear: Functions to run when the signal is cleared"),
|
||||
el("li", "host: Reference to the host element or scope"),
|
||||
el("li", "defined: Stack trace information for debugging"),
|
||||
el("li", "readonly: Boolean flag indicating if the signal is read-only")
|
||||
),
|
||||
),
|
||||
el("p").append(...T`
|
||||
These properties make it easier to understand the reactive structure of your application when inspecting elements.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/signals/debugging-dom.js"), page_id }),
|
||||
|
||||
el("h4", t`Examining signal connections`),
|
||||
el("p").append(...T`
|
||||
You can inspect signal relationships and bindings in the DevTools console using ${el("code", "$0.__dde_reactive")}.
|
||||
In console you will see list of ${el("code", "[ [ signal, listener ], element, property ]")}, where:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", "signal — the signal triggering the changes"),
|
||||
el("li", "listener — the listener function (this is internal function for dde)"),
|
||||
el("li", "element — the DOM element that is bound to the signal"),
|
||||
el("li", "property — the attribute or property name which is changing based on the signal"),
|
||||
),
|
||||
el("p").append(...T`
|
||||
…the structure of \`__dde_reactive\` use the behavior of the browser that packs the first field,
|
||||
so you can see the element and property that changes in the console right away
|
||||
`),
|
||||
|
||||
el("h4", t`Debugging with breakpoints`),
|
||||
el("p").append(...T`
|
||||
Effective use of breakpoints can help track signal flow:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li").append(...T`
|
||||
Set breakpoints in signal update methods to track when values change
|
||||
`),
|
||||
el("li").append(...T`
|
||||
Use conditional breakpoints to only break when specific signals change to certain values
|
||||
`),
|
||||
el("li").append(...T`
|
||||
Set breakpoints in your signal computation functions to see when derived signals recalculate
|
||||
`),
|
||||
el("li").append(...T`
|
||||
Use performance profiling to identify bottlenecks in signal updates
|
||||
`)
|
||||
),
|
||||
|
||||
);
|
||||
}
|
102
docs/p07-debugging.html.old.js
Normal file
102
docs/p07-debugging.html.old.js
Normal file
@ -0,0 +1,102 @@
|
||||
import { T, t } from "./utils/index.js";
|
||||
export const info= {
|
||||
title: t`Debugging`,
|
||||
description: t`Techniques for debugging applications using deka-dom-el, especially signals.`,
|
||||
};
|
||||
|
||||
import { el } from "deka-dom-el";
|
||||
import { simplePage } from "./layout/simplePage.html.js";
|
||||
import { example } from "./components/example.html.js";
|
||||
import { h3 } from "./components/pageUtils.html.js";
|
||||
import { code } from "./components/code.html.js";
|
||||
/** @param {string} url */
|
||||
const fileURL= url=> new URL(url, import.meta.url);
|
||||
|
||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("h2", t`Debugging applications with deka-dom-el`),
|
||||
el("p").append(...T`
|
||||
Debugging is an essential part of application development. This guide provides techniques
|
||||
and best practices for debugging applications built with deka-dom-el, with a focus on signals.
|
||||
`),
|
||||
|
||||
el(h3, t`Debugging signals`),
|
||||
el("p").append(...T`
|
||||
Signals are reactive primitives that update the UI when their values change. When debugging signals,
|
||||
you need to track their values, understand their dependencies, and identify why updates are or aren't happening.
|
||||
`),
|
||||
|
||||
el("h4", t`Inspecting signal values`),
|
||||
el("p").append(...T`
|
||||
The simplest way to debug a signal is to log its current value by calling the get method:
|
||||
`),
|
||||
el("pre").append(
|
||||
el("code", "const signal = S(0);\nconsole.log('Current value:', signal.get());")
|
||||
),
|
||||
el("p").append(...T`
|
||||
You can also monitor signal changes by adding a listener:
|
||||
`),
|
||||
el("pre").append(
|
||||
el("code", "// Log every time the signal changes\nS.on(signal, value => console.log('Signal changed:', value));")
|
||||
),
|
||||
|
||||
el("h4", t`Debugging derived signals`),
|
||||
el("p").append(...T`
|
||||
With derived signals (created with S(() => computation)), debugging is a bit more complex
|
||||
because the value depends on other signals. To understand why a derived signal isn't updating correctly:
|
||||
`),
|
||||
el("ol").append(
|
||||
el("li", t`Check that all dependency signals are updating correctly`),
|
||||
el("li", t`Add logging inside the computation function to see when it runs`),
|
||||
el("li", t`Verify that the computation function actually accesses the signal values with .get()`)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/debugging/consoleLog.js"), page_id }),
|
||||
|
||||
el(h3, t`Common signal debugging issues`),
|
||||
el("h4", t`Signal updates not triggering UI changes`),
|
||||
el("p").append(...T`
|
||||
If signal updates aren't reflected in the UI, check:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", t`That you're using signal.set() to update the value, not modifying objects/arrays directly`),
|
||||
el("li", t`For mutable objects, ensure you're using actions or making proper copies before updating`),
|
||||
el("li", t`That the signal is actually connected to the DOM element (check your S.el or attribute binding code)`)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }),
|
||||
|
||||
el("h4", t`Memory leaks with signal listeners`),
|
||||
el("p").append(...T`
|
||||
Signal listeners can cause memory leaks if not properly cleaned up. Always use AbortSignal
|
||||
to cancel listeners.
|
||||
`),
|
||||
|
||||
el("h4", t`Performance issues with frequently updating signals`),
|
||||
el("p").append(...T`
|
||||
If you notice performance issues with signals that update very frequently:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", t`Consider debouncing or throttling signal updates`),
|
||||
el("li", t`Make sure derived signals don't perform expensive calculations unnecessarily`),
|
||||
el("li", t`Keep signal computations focused and minimal`)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/debugging/debouncing.js"), page_id }),
|
||||
|
||||
el(h3, t`Browser DevTools tips for deka-dom-el`),
|
||||
el("p").append(...T`
|
||||
When debugging in the browser, here are some helpful techniques:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li").append(...T`
|
||||
Use the Elements panel to inspect the DOM structure created by deka-dom-el
|
||||
`),
|
||||
el("li").append(...T`
|
||||
Set breakpoints in your signal handlers and actions
|
||||
`),
|
||||
el("li").append(...T`
|
||||
Use performance profiling to identify bottlenecks in signal updates
|
||||
`),
|
||||
),
|
||||
);
|
||||
}
|
129
docs/p08-ssr.html.js
Normal file
129
docs/p08-ssr.html.js
Normal file
@ -0,0 +1,129 @@
|
||||
import { T, t } from "./utils/index.js";
|
||||
export const info= {
|
||||
title: t`Server-Side Rendering (SSR)`,
|
||||
description: t`Using deka-dom-el for server-side rendering with jsdom to generate static HTML.`,
|
||||
};
|
||||
|
||||
import { el } from "deka-dom-el";
|
||||
import { simplePage } from "./layout/simplePage.html.js";
|
||||
import { h3 } from "./components/pageUtils.html.js";
|
||||
import { code } from "./components/code.html.js";
|
||||
/** @param {string} url */
|
||||
const fileURL= url=> new URL(url, import.meta.url);
|
||||
|
||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("h2", t`Server-Side Rendering with deka-dom-el`),
|
||||
el("p").append(...T`
|
||||
deka-dom-el isn't limited to browser environments. Thanks to its flexible architecture,
|
||||
it can be used for server-side rendering (SSR) to generate static HTML files.
|
||||
This is achieved through integration with for example ${el("a", { href: "https://github.com/tmpvar/jsdom",
|
||||
textContent: "jsdom" })}, a JavaScript implementation of web standards for Node.js.
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/ssr/intro.js"), page_id }),
|
||||
|
||||
el(h3, t`Why Server-Side Rendering?`),
|
||||
el("p").append(...T`
|
||||
SSR offers several benefits:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", t`Improved SEO - Search engines can easily index fully rendered content`),
|
||||
el("li", t`Faster initial page load - Users see content immediately without waiting for JavaScript to load`),
|
||||
el("li", t`Better performance on low-powered devices - Less JavaScript processing on the client`),
|
||||
el("li", t`Content available without JavaScript - Useful for users who have disabled JavaScript`),
|
||||
el("li", t`Static site generation - Build files once, serve them many times`)
|
||||
),
|
||||
|
||||
el(h3, t`How jsdom Integration Works`),
|
||||
el("p").append(...T`
|
||||
The jsdom export in deka-dom-el provides the necessary tools to use the library in Node.js
|
||||
by integrating with jsdom. Here's what it does:
|
||||
`),
|
||||
el("ol").append(
|
||||
el("li", t`Creates a virtual DOM environment in Node.js using jsdom`),
|
||||
el("li", t`Registers DOM globals like HTMLElement, document, etc. for deka-dom-el to use`),
|
||||
el("li", t`Sets an SSR flag in the environment to enable SSR-specific behaviors`),
|
||||
el("li", t`Provides a promise queue system for managing async operations during rendering`),
|
||||
el("li", t`Handles DOM property/attribute mapping differences between browsers and jsdom`)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/ssr/start.js"), page_id }),
|
||||
|
||||
el(h3, t`Basic SSR Example`),
|
||||
el("p").append(...T`
|
||||
Here's a simple example of how to use deka-dom-el for server-side rendering in a Node.js script:
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/ssr/basic-example.js"), page_id }),
|
||||
|
||||
el(h3, t`Building a Static Site Generator`),
|
||||
el("p").append(...T`
|
||||
You can build a complete static site generator with deka-dom-el. In fact, this documentation site
|
||||
is built using deka-dom-el for server-side rendering! Here's how the documentation build process works:
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js"), page_id }),
|
||||
|
||||
el(h3, t`Working with Async Content in SSR`),
|
||||
el("p").append(...T`
|
||||
The jsdom export includes a queue system to handle asynchronous operations during rendering.
|
||||
This is crucial for components that fetch data or perform other async tasks.
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/ssr/async-data.js"), page_id }),
|
||||
|
||||
el(h3, t`Working with Dynamic Imports for SSR`),
|
||||
el("p").append(...T`
|
||||
When structuring server-side rendering code, a crucial pattern to follow is using dynamic imports
|
||||
for both the deka-dom-el/jsdom module and your page components.
|
||||
`),
|
||||
el("p").append(...T`
|
||||
Why is this important?
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li").append(...T`
|
||||
${el("strong", "Static imports are hoisted:")} JavaScript hoists import statements to the top of the file,
|
||||
executing them before any other code
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Environment registration timing:")} The jsdom module auto-registers the DOM environment
|
||||
when imported, which must happen ${el("em", "after")} you've created your JSDOM instance and
|
||||
${el("em", "before")} you import your components using ${el("code", "import { el } from \"deka-dom-el\";")}.
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Correct initialization order:")} You need to control the exact sequence of:
|
||||
create JSDOM → register environment → import components
|
||||
`)
|
||||
),
|
||||
el("p").append(...T`
|
||||
Follow this pattern when creating server-side rendered pages:
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/ssr/pages.js"), page_id }),
|
||||
|
||||
el(h3, t`SSR Considerations and Limitations`),
|
||||
el("p").append(...T`
|
||||
When using deka-dom-el for SSR, keep these considerations in mind:
|
||||
`),
|
||||
el("ul").append(
|
||||
el("li", t`Browser-specific APIs like window.localStorage are not available in jsdom by default`),
|
||||
el("li", t`Event listeners added during SSR won't be functional in the final HTML unless hydrated on the client`),
|
||||
el("li", t`Some DOM features may behave differently in jsdom compared to real browsers`),
|
||||
el("li", t`For large sites, you may need to optimize memory usage by creating a new jsdom instance for each page`)
|
||||
),
|
||||
el("p").append(...T`
|
||||
For advanced SSR applications, consider implementing hydration on the client-side to restore
|
||||
interactivity after the initial render.
|
||||
`),
|
||||
|
||||
el(h3, t`Real Example: How This Documentation is Built`),
|
||||
el("p").append(...T`
|
||||
This documentation site itself is built using deka-dom-el's SSR capabilities.
|
||||
The build process collects all page components, renders them with jsdom, and outputs static HTML files.
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js"), page_id }),
|
||||
|
||||
el("p").append(...T`
|
||||
The resulting static files can be deployed to any static hosting service,
|
||||
providing fast loading times and excellent SEO without the need for client-side JavaScript
|
||||
to render the initial content.
|
||||
`),
|
||||
);
|
||||
}
|
@ -15,7 +15,7 @@ export function thirdParty(){
|
||||
}, {
|
||||
set(key, value){
|
||||
const p= this.value[key] || S();
|
||||
p(value);
|
||||
p.set(value);
|
||||
this.value[key]= p;
|
||||
}
|
||||
});
|
||||
@ -32,7 +32,7 @@ export function thirdParty(){
|
||||
})();
|
||||
return el("input", {
|
||||
className,
|
||||
value: store().value(),
|
||||
value: store.get().value.get(),
|
||||
type: "text",
|
||||
onchange: ev=> S.action(store, "set", "value", ev.target.value)
|
||||
});
|
||||
|
@ -9,24 +9,24 @@ export function fullNameComponent(){
|
||||
const labels= [ "Name", "Surname" ];
|
||||
const name= labels.map(_=> S(""));
|
||||
const full_name= S(()=>
|
||||
name.map(l=> l()).filter(Boolean).join(" ") || "-");
|
||||
name.map(l=> l.get()).filter(Boolean).join(" ") || "-");
|
||||
scope.host(
|
||||
on.connected(()=> console.log(fullNameComponent)),
|
||||
on.disconnected(()=> console.log(fullNameComponent))
|
||||
);
|
||||
|
||||
const count= S(0);
|
||||
setInterval(()=> count(count()+1), 5000);
|
||||
setInterval(()=> count.set(count.get()+1), 5000);
|
||||
const style= { height: "80px", display: "block", fill: "currentColor" };
|
||||
const elSVG= elNS("http://www.w3.org/2000/svg");
|
||||
return el("div", { className }).append(
|
||||
el("h2", "Simple form:"),
|
||||
el("p", { textContent: S(()=> "Count: "+count()),
|
||||
dataset: { count }, classList: { count: S(() => count()%2 === 0) } }),
|
||||
el("p", { textContent: S(()=> "Count: "+count.get()),
|
||||
dataset: { count }, classList: { count: S(() => count.get()%2 === 0) } }),
|
||||
el("form", { onsubmit: ev=> ev.preventDefault() }).append(
|
||||
...name.map((v, i)=>
|
||||
el("label", labels[i]).append(
|
||||
el("input", { type: "text", name: labels[i], value: v() }, on("change", ev=> v(ev.target.value)))
|
||||
el("input", { type: "text", name: labels[i], value: v.get() }, on("change", ev=> v.set(ev.target.value)))
|
||||
))
|
||||
),
|
||||
el("p").append(
|
||||
|
@ -58,7 +58,7 @@ export function todosComponent({ todos= [ "Task A" ] }= {}){
|
||||
),
|
||||
el("div").append(
|
||||
el("h3", "Output (JSON):"),
|
||||
el("output", S(()=> JSON.stringify(Array.from(todosO()), null, "\t")))
|
||||
el("output", S(()=> JSON.stringify(Array.from(todosO.get()), null, "\t")))
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -80,13 +80,13 @@ function todoComponent({ textContent, value }){
|
||||
const is_editable= S(false);
|
||||
const onedited= on("change", ev=> {
|
||||
const el= /** @type {HTMLInputElement} */ (ev.target);
|
||||
textContent(el.value);
|
||||
is_editable(false);
|
||||
textContent.set(el.value);
|
||||
is_editable.set(false);
|
||||
});
|
||||
return el("li").append(
|
||||
S.el(is_editable, is=> is
|
||||
? el("input", { value: textContent(), type: "text" }, onedited)
|
||||
: el("span", { textContent, onclick: is_editable.bind(null, true) })
|
||||
? el("input", { value: textContent.get(), type: "text" }, onedited)
|
||||
: el("span", { textContent, onclick: ()=> is_editable.set(true) })
|
||||
),
|
||||
el("button", { type: "button", value, textContent: "-" }, onclick)
|
||||
);
|
||||
|
@ -34,7 +34,7 @@ export class CustomHTMLTestElement extends HTMLElement{
|
||||
text(test),
|
||||
text(name),
|
||||
text(preName),
|
||||
el("button", { type: "button", textContent: "pre-name", onclick: ()=> preName("Ahoj") }),
|
||||
el("button", { type: "button", textContent: "pre-name", onclick: ()=> preName.set("Ahoj") }),
|
||||
" | ",
|
||||
el("slot", { className: "test", name: "test" }),
|
||||
);
|
||||
|
@ -20,7 +20,7 @@ document.body.append(
|
||||
el("span", { textContent: "test", slot: "test" }),
|
||||
),
|
||||
el(thirdParty),
|
||||
el(CustomSlottingHTMLElement.tagName, { onclick: ()=> toggle(!toggle()) }).append(
|
||||
el(CustomSlottingHTMLElement.tagName, { onclick: ()=> toggle.set(!toggle.get()) }).append(
|
||||
el("strong", { slot: "name", textContent: "Honzo" }),
|
||||
S.el(toggle, is=> is
|
||||
? el("span", "…default slot")
|
||||
|
10
index.d.ts
vendored
10
index.d.ts
vendored
@ -52,9 +52,13 @@ type IsReadonly<T, K extends keyof T> =
|
||||
* @private
|
||||
*/
|
||||
type ElementAttributes<T extends SupportedElement>= Partial<{
|
||||
[K in keyof _fromElsInterfaces<T>]: IsReadonly<_fromElsInterfaces<T>, K> extends false
|
||||
? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
|
||||
: ddeStringable
|
||||
[K in keyof _fromElsInterfaces<T>]:
|
||||
_fromElsInterfaces<T>[K] extends ((...p: any[])=> any)
|
||||
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=>
|
||||
ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
|
||||
: (IsReadonly<_fromElsInterfaces<T>, K> extends false
|
||||
? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
|
||||
: ddeStringable)
|
||||
} & AttrsModified> & Record<string, any>;
|
||||
export function classListDeclarative<El extends SupportedElement>(
|
||||
element: El,
|
||||
|
1437
package-lock.json
generated
1437
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "deka-dom-el",
|
||||
"version": "0.8.0",
|
||||
"version": "0.9.0",
|
||||
"description": "A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks.",
|
||||
"author": "Jan Andrle <andrle.jan@centrum.cz>",
|
||||
"license": "MIT",
|
||||
@ -29,8 +29,8 @@
|
||||
"types": "./jsdom.d.ts"
|
||||
},
|
||||
"./src/signals-lib": {
|
||||
"import": "./src/signals-lib.js",
|
||||
"types": "./src/signals-lib.d.ts"
|
||||
"import": "./src/signals-lib/signals-lib.js",
|
||||
"types": "./src/signals-lib/signals-lib.d.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
@ -53,7 +53,8 @@
|
||||
"globals": {
|
||||
"requestIdleCallback": false,
|
||||
"AbortController": false,
|
||||
"AbortSignal": false
|
||||
"AbortSignal": false,
|
||||
"FinalizationRegistry": false
|
||||
}
|
||||
},
|
||||
"size-limit": [
|
||||
@ -65,7 +66,7 @@
|
||||
},
|
||||
{
|
||||
"path": "./signals.js",
|
||||
"limit": "12 kB",
|
||||
"limit": "12.5 kB",
|
||||
"gzip": false,
|
||||
"brotli": false
|
||||
},
|
||||
@ -77,7 +78,7 @@
|
||||
},
|
||||
{
|
||||
"path": "./index-with-signals.js",
|
||||
"limit": "5.25 kB"
|
||||
"limit": "5.5 kB"
|
||||
}
|
||||
],
|
||||
"modifyEsbuildConfig": {
|
||||
@ -91,11 +92,11 @@
|
||||
"typescript"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@size-limit/preset-small-lib": "~11.0",
|
||||
"@size-limit/preset-small-lib": "~11.2",
|
||||
"dts-bundler": "~0.1",
|
||||
"editorconfig-checker": "~6.0",
|
||||
"esbuild": "~0.24",
|
||||
"jsdom": "~25.0",
|
||||
"esbuild": "~0.25",
|
||||
"jsdom": "~26.0",
|
||||
"jshint": "~2.13",
|
||||
"nodejsscript": "^1.0.2",
|
||||
"size-limit-node-esbuild": "~0.3"
|
||||
|
12
signals.d.ts
vendored
12
signals.d.ts
vendored
@ -1,4 +1,12 @@
|
||||
export type Signal<V, A>= (set?: V)=> V & A;
|
||||
export interface Signal<V, A> {
|
||||
/** The current value of the signal */
|
||||
get(): V;
|
||||
/** Set new value of the signal */
|
||||
set(value: V): V;
|
||||
toJSON(): V;
|
||||
valueOf(): V;
|
||||
}
|
||||
|
||||
type Action<V>= (this: { value: V, stopPropagation(): void }, ...a: any[])=> typeof signal._ | void;
|
||||
//type SymbolSignal= Symbol;
|
||||
type SymbolOnclear= symbol;
|
||||
@ -27,7 +35,7 @@ interface signal{
|
||||
* ```js
|
||||
* const name= S("Jan");
|
||||
* const surname= S("Andrle");
|
||||
* const fullname= S(()=> name()+" "+surname());
|
||||
* const fullname= S(()=> name.get()+" "+surname.get());
|
||||
* ```
|
||||
* @param value Initial signal value. Or function computing value from other signals.
|
||||
* @param actions Use to define actions on the signal. Such as add item to the array.
|
||||
|
@ -1,4 +1,4 @@
|
||||
export { signal, S, isSignal } from "./src/signals-lib.js";
|
||||
import { signals_config } from "./src/signals-lib.js";
|
||||
import { registerReactivity } from "./src/signals-common.js";
|
||||
export { signal, S, isSignal } from "./src/signals-lib/signals-lib.js";
|
||||
import { signals_config } from "./src/signals-lib/signals-lib.js";
|
||||
import { registerReactivity } from "./src/signals-lib/common.js";
|
||||
registerReactivity(signals_config);
|
||||
|
@ -1,6 +1,15 @@
|
||||
import { keyLTE, evc, evd, eva } from "./dom-common.js";
|
||||
import { scope } from "./dom.js";
|
||||
import { c_ch_o } from "./events-observer.js";
|
||||
|
||||
/**
|
||||
* Renders content into a custom element or shadow root
|
||||
*
|
||||
* @param {Element|ShadowRoot} target - The custom element or shadow root to render into
|
||||
* @param {Function} render - The render function that returns content
|
||||
* @param {Function|Object} [props=observedAttributes] - Props to pass to the render function
|
||||
* @returns {Node} The rendered content
|
||||
*/
|
||||
export function customElementRender(target, render, props= observedAttributes){
|
||||
const custom_element= target.host || target;
|
||||
scope.push({
|
||||
@ -17,6 +26,13 @@ export function customElementRender(target, render, props= observedAttributes){
|
||||
scope.pop();
|
||||
return target.append(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms custom element lifecycle callbacks into events
|
||||
*
|
||||
* @param {Function|Object} class_declaration - Custom element class or instance
|
||||
* @returns {Function|Object} The modified class or instance
|
||||
*/
|
||||
export function lifecyclesToEvents(class_declaration){
|
||||
wrapMethod(class_declaration.prototype, "connectedCallback", function(target, thisArg, detail){
|
||||
target.apply(thisArg, detail);
|
||||
@ -38,12 +54,30 @@ export function lifecyclesToEvents(class_declaration){
|
||||
class_declaration.prototype[keyLTE]= true;
|
||||
return class_declaration;
|
||||
}
|
||||
|
||||
/** Public API */
|
||||
export { lifecyclesToEvents as customElementWithDDE };
|
||||
|
||||
/**
|
||||
* Wraps a method with a proxy to intercept calls
|
||||
*
|
||||
* @param {Object} obj - Object containing the method
|
||||
* @param {string} method - Method name to wrap
|
||||
* @param {Function} apply - Function to execute when method is called
|
||||
* @private
|
||||
*/
|
||||
function wrapMethod(obj, method, apply){
|
||||
obj[method]= new Proxy(obj[method] || (()=> {}), { apply });
|
||||
}
|
||||
|
||||
import { observedAttributes as oA } from "./helpers.js";
|
||||
|
||||
/**
|
||||
* Gets observed attributes for a custom element
|
||||
*
|
||||
* @param {Element} instance - Custom element instance
|
||||
* @returns {Object} Object mapping camelCase attribute names to their values
|
||||
*/
|
||||
export function observedAttributes(instance){
|
||||
return oA(instance, (i, n)=> i.getAttribute(n));
|
||||
}
|
||||
|
@ -1,3 +1,15 @@
|
||||
/**
|
||||
* Environment configuration and globals for the library
|
||||
* @typedef {Object} Environment
|
||||
* @property {typeof setDeleteAttr} setDeleteAttr - Function to safely set or delete attributes
|
||||
* @property {string} ssr - Server-side rendering flag
|
||||
* @property {Document} D - Document global
|
||||
* @property {typeof DocumentFragment} F - DocumentFragment constructor
|
||||
* @property {typeof HTMLElement} H - HTMLElement constructor
|
||||
* @property {typeof SVGElement} S - SVGElement constructor
|
||||
* @property {typeof MutationObserver} M - MutationObserver constructor
|
||||
* @property {Function} q - Promise wrapper for Promse queue feature
|
||||
*/
|
||||
export const enviroment= {
|
||||
setDeleteAttr,
|
||||
ssr: "",
|
||||
@ -8,26 +20,43 @@ export const enviroment= {
|
||||
M: globalThis.MutationObserver,
|
||||
q: p=> p || Promise.resolve(),
|
||||
};
|
||||
import { isUndef } from './helpers.js';
|
||||
import { isInstance, isUndef } from './helpers.js';
|
||||
|
||||
/**
|
||||
* Handles attribute setting with special undefined handling
|
||||
*
|
||||
* @param {Object} obj - The object to set the property on
|
||||
* @param {string} prop - The property name
|
||||
* @param {any} val - The value to set
|
||||
* @returns {void}
|
||||
*
|
||||
* Issue:
|
||||
* For some native attrs you can unset only to set empty string.
|
||||
* This can be confusing as it is seen in inspector `<… id=""`.
|
||||
* Options:
|
||||
* 1. Leave it, as it is native behaviour
|
||||
* 2. Sets as empty string and removes the corresponding attribute when also has empty string
|
||||
* 3. (*) Sets as undefined and removes the corresponding attribute when "undefined" string discovered
|
||||
* 4. Point 2. with checks for coincidence (e.g. use special string)
|
||||
*/
|
||||
function setDeleteAttr(obj, prop, val){
|
||||
/* Issue
|
||||
For some native attrs you can unset only to set empty string.
|
||||
This can be confusing as it is seen in inspector `<… id=""`.
|
||||
Options:
|
||||
1. Leave it, as it is native behaviour
|
||||
2. Sets as empty string and removes the corresponding attribute when also has empty string
|
||||
3. (*) Sets as undefined and removes the corresponding attribute when "undefined" string discovered
|
||||
4. Point 2. with checks for coincidence (e.g. use special string)
|
||||
*/
|
||||
Reflect.set(obj, prop, val);
|
||||
if(!isUndef(val)) return;
|
||||
Reflect.deleteProperty(obj, prop);
|
||||
if(obj instanceof enviroment.H && obj.getAttribute(prop)==="undefined")
|
||||
if(isInstance(obj, enviroment.H) && obj.getAttribute(prop)==="undefined")
|
||||
return obj.removeAttribute(prop);
|
||||
if(Reflect.get(obj, prop)==="undefined")
|
||||
return Reflect.set(obj, prop, "");
|
||||
}
|
||||
|
||||
/** Property key for tracking lifecycle events */
|
||||
export const keyLTE= "__dde_lifecyclesToEvents"; //boolean
|
||||
|
||||
/** Event name for connected lifecycle event */
|
||||
export const evc= "dde:connected";
|
||||
|
||||
/** Event name for disconnected lifecycle event */
|
||||
export const evd= "dde:disconnected";
|
||||
|
||||
/** Event name for attribute changed lifecycle event */
|
||||
export const eva= "dde:attributeChanged";
|
||||
|
215
src/dom.js
215
src/dom.js
@ -1,38 +1,104 @@
|
||||
import { signals } from "./signals-common.js";
|
||||
import { signals } from "./signals-lib/common.js";
|
||||
import { enviroment as env } from './dom-common.js';
|
||||
import { isInstance, isUndef, oAssign } from "./helpers.js";
|
||||
|
||||
//TODO: add type, docs ≡ make it public
|
||||
/**
|
||||
* Queues a promise, this is helpful for crossplatform components (on server side we can wait for all registered
|
||||
* promises to be resolved before rendering).
|
||||
* @param {Promise} promise - Promise to process
|
||||
* @returns {Promise} Processed promise
|
||||
*/
|
||||
export function queue(promise){ return env.q(promise); }
|
||||
/** @type {{ scope: object, prevent: boolean, host: function }[]} */
|
||||
|
||||
/**
|
||||
* Array of scope contexts for tracking component hierarchies
|
||||
* @type {{ scope: object, prevent: boolean, host: function }[]}
|
||||
*/
|
||||
const scopes= [ {
|
||||
get scope(){ return env.D.body; },
|
||||
host: c=> c ? c(env.D.body) : env.D.body,
|
||||
prevent: true,
|
||||
} ];
|
||||
/**
|
||||
* Scope management utility for tracking component hierarchies
|
||||
*/
|
||||
export const scope= {
|
||||
/**
|
||||
* Gets the current scope
|
||||
* @returns {Object} Current scope context
|
||||
*/
|
||||
get current(){ return scopes[scopes.length-1]; },
|
||||
|
||||
/**
|
||||
* Gets the host element of the current scope
|
||||
* @returns {Function} Host accessor function
|
||||
*/
|
||||
get host(){ return this.current.host; },
|
||||
|
||||
/**
|
||||
* Prevents default behavior in the current scope
|
||||
* @returns {Object} Current scope context
|
||||
*/
|
||||
preventDefault(){
|
||||
const { current }= this;
|
||||
current.prevent= true;
|
||||
return current;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a copy of the current scope stack
|
||||
* @returns {Array} Copy of scope stack
|
||||
*/
|
||||
get state(){ return [ ...scopes ]; },
|
||||
push(s= {}){ return scopes.push(Object.assign({}, this.current, { prevent: false }, s)); },
|
||||
|
||||
/**
|
||||
* Pushes a new scope to the stack
|
||||
* @param {Object} [s={}] - Scope object to push
|
||||
* @returns {number} New length of the scope stack
|
||||
*/
|
||||
push(s= {}){ return scopes.push(oAssign({}, this.current, { prevent: false }, s)); },
|
||||
|
||||
/**
|
||||
* Pushes the root scope to the stack
|
||||
* @returns {number} New length of the scope stack
|
||||
*/
|
||||
pushRoot(){ return scopes.push(scopes[0]); },
|
||||
|
||||
/**
|
||||
* Pops the current scope from the stack
|
||||
* @returns {Object|undefined} Popped scope or undefined if only one scope remains
|
||||
*/
|
||||
pop(){
|
||||
if(scopes.length===1) return;
|
||||
return scopes.pop();
|
||||
},
|
||||
};
|
||||
//NOTE: following chainableAppend implementation is OK as the ElementPrototype.append description already is { writable: true, enumerable: true, configurable: true } // editorconfig-checker-disable-line
|
||||
/**
|
||||
* Chainable append function for elements
|
||||
* @private
|
||||
*/
|
||||
function append(...els){ this.appendOriginal(...els); return this; }
|
||||
|
||||
/**
|
||||
* Makes an element's append method chainable. NOTE: following chainableAppend implementation is OK as the
|
||||
* ElementPrototype.append description already is { writable: true, enumerable: true, configurable: true }
|
||||
* @param {Element} el - Element to modify
|
||||
* @returns {Element} Modified element
|
||||
*/
|
||||
export function chainableAppend(el){
|
||||
if(el.append===append) return el; el.appendOriginal= el.append; el.append= append; return el;
|
||||
}
|
||||
/** Current namespace for element creation */
|
||||
let namespace;
|
||||
|
||||
/**
|
||||
* Creates a DOM element with specified tag, attributes and addons
|
||||
*
|
||||
* @param {string|Function} tag - Element tag name or component function
|
||||
* @param {Object|string|number} [attributes] - Element attributes
|
||||
* @param {...Function} addons - Functions to call with the created element
|
||||
* @returns {Element|DocumentFragment} Created element
|
||||
*/
|
||||
export function createElement(tag, attributes, ...addons){
|
||||
/* jshint maxcomplexity: 15 */
|
||||
const s= signals(this);
|
||||
@ -47,7 +113,7 @@ export function createElement(tag, attributes, ...addons){
|
||||
(scoped===1 ? addons.unshift(...c) : c.forEach(c=> c(el_host)), undefined);
|
||||
scope.push({ scope: tag, host });
|
||||
el= tag(attributes || undefined);
|
||||
const is_fragment= el instanceof env.F;
|
||||
const is_fragment= isInstance(el, env.F);
|
||||
if(el.nodeName==="#comment") break;
|
||||
const el_mark= createElement.mark({
|
||||
type: "component",
|
||||
@ -70,10 +136,15 @@ export function createElement(tag, attributes, ...addons){
|
||||
scoped= 2;
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { { type: "component", name: string, host: "this" | "parentElement" } | { type: "reactive" | "later" } } attrs
|
||||
* @param {boolean} [is_open=false]
|
||||
* */
|
||||
* Creates a marker comment for elements
|
||||
*
|
||||
* @param {{ type: "component"|"reactive"|"later", name?: string, host?: "this"|"parentElement" }} attrs - Marker
|
||||
* attributes
|
||||
* @param {boolean} [is_open=false] - Whether the marker is open-ended
|
||||
* @returns {Comment} Comment node marker
|
||||
*/
|
||||
createElement.mark= function(attrs, is_open= false){
|
||||
attrs= Object.entries(attrs).map(([ n, v ])=> n+`="${v}"`).join(" ");
|
||||
const end= is_open ? "" : "/";
|
||||
@ -81,8 +152,16 @@ createElement.mark= function(attrs, is_open= false){
|
||||
if(is_open) out.end= env.D.createComment("</dde:mark>");
|
||||
return out;
|
||||
};
|
||||
/** Alias for createElement */
|
||||
export { createElement as el };
|
||||
|
||||
//TODO?: const namespaceHelper= ns=> ns==="svg" ? "http://www.w3.org/2000/svg" : ns;
|
||||
/**
|
||||
* Creates a namespaced element creation function
|
||||
*
|
||||
* @param {string} ns - Namespace URI
|
||||
* @returns {Function} Element creation function for the namespace
|
||||
*/
|
||||
export function createElementNS(ns){
|
||||
const _this= this;
|
||||
return function createElementNSCurried(...rest){
|
||||
@ -92,9 +171,17 @@ export function createElementNS(ns){
|
||||
return el;
|
||||
};
|
||||
}
|
||||
|
||||
/** Alias for createElementNS */
|
||||
export { createElementNS as elNS };
|
||||
|
||||
/** @param {HTMLElement} element @param {HTMLElement} [root] */
|
||||
/**
|
||||
* Simulates slot functionality for elements
|
||||
*
|
||||
* @param {HTMLElement} element - Parent element
|
||||
* @param {HTMLElement} [root=element] - Root element containing slots
|
||||
* @returns {HTMLElement} The root element
|
||||
*/
|
||||
export function simulateSlots(element, root= element){
|
||||
const mark_e= "¹⁰", mark_s= "✓"; //NOTE: Markers to identify slots processed by this function. Also “prevents” native behavior as it is unlikely to use these in names. // editorconfig-checker-disable-line
|
||||
const slots= Object.fromEntries(
|
||||
@ -128,17 +215,34 @@ export function simulateSlots(element, root= element){
|
||||
return root;
|
||||
}
|
||||
|
||||
/** Store for element assignment contexts */
|
||||
const assign_context= new WeakMap();
|
||||
const { setDeleteAttr }= env;
|
||||
|
||||
/**
|
||||
* Assigns attributes to an element
|
||||
*
|
||||
* @param {Element} element - Element to assign attributes to
|
||||
* @param {...Object} attributes - Attribute objects to assign
|
||||
* @returns {Element} The element with attributes assigned
|
||||
*/
|
||||
export function assign(element, ...attributes){
|
||||
if(!attributes.length) return element;
|
||||
assign_context.set(element, assignContext(element, this));
|
||||
|
||||
for(const [ key, value ] of Object.entries(Object.assign({}, ...attributes)))
|
||||
for(const [ key, value ] of Object.entries(oAssign({}, ...attributes)))
|
||||
assignAttribute.call(this, element, key, value);
|
||||
assign_context.delete(element);
|
||||
return element;
|
||||
}
|
||||
/**
|
||||
* Assigns a single attribute to an element
|
||||
*
|
||||
* @param {Element} element - Element to assign attribute to
|
||||
* @param {string} key - Attribute name
|
||||
* @param {any} value - Attribute value
|
||||
* @returns {any} Result of the attribute assignment
|
||||
*/
|
||||
export function assignAttribute(element, key, value){
|
||||
const { setRemoveAttr, s }= assignContext(element, this);
|
||||
const _this= this;
|
||||
@ -170,13 +274,28 @@ export function assignAttribute(element, key, value){
|
||||
}
|
||||
return isPropSetter(element, key) ? setDeleteAttr(element, key, value) : setRemoveAttr(key, value);
|
||||
}
|
||||
/**
|
||||
* Gets or creates assignment context for an element
|
||||
*
|
||||
* @param {Element} element - Element to get context for
|
||||
* @param {Object} _this - Context object
|
||||
* @returns {Object} Assignment context
|
||||
* @private
|
||||
*/
|
||||
function assignContext(element, _this){
|
||||
if(assign_context.has(element)) return assign_context.get(element);
|
||||
const is_svg= element instanceof env.S;
|
||||
const is_svg= isInstance(element, env.S);
|
||||
const setRemoveAttr= (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
|
||||
const s= signals(_this);
|
||||
return { setRemoveAttr, s };
|
||||
}
|
||||
/**
|
||||
* Applies a declarative classList object to an element
|
||||
*
|
||||
* @param {Element} element - Element to apply classes to
|
||||
* @param {Object} toggle - Object with class names as keys and boolean values
|
||||
* @returns {Element} The element with classes applied
|
||||
*/
|
||||
export function classListDeclarative(element, toggle){
|
||||
const s= signals(this);
|
||||
forEachEntries(s, "classList", element, toggle,
|
||||
@ -184,18 +303,45 @@ export function classListDeclarative(element, toggle){
|
||||
element.classList.toggle(class_name, val===-1 ? undefined : Boolean(val)) );
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic element attribute manipulation
|
||||
*
|
||||
* @param {Element} element - Element to manipulate
|
||||
* @param {string} op - Operation ("set" or "remove")
|
||||
* @param {string} key - Attribute name
|
||||
* @param {any} [value] - Attribute value
|
||||
* @returns {void}
|
||||
*/
|
||||
export function elementAttribute(element, op, key, value){
|
||||
if(element instanceof env.H)
|
||||
if(isInstance(element, env.H))
|
||||
return element[op+"Attribute"](key, value);
|
||||
return element[op+"AttributeNS"](null, key, value);
|
||||
}
|
||||
import { isUndef } from "./helpers.js";
|
||||
|
||||
//TODO: add cache? `(Map/Set)<el.tagName+key,isUndef>`
|
||||
/**
|
||||
* Checks if a property can be set on an element
|
||||
*
|
||||
* @param {Element} el - Element to check
|
||||
* @param {string} key - Property name
|
||||
* @returns {boolean} Whether the property can be set
|
||||
* @private
|
||||
*/
|
||||
function isPropSetter(el, key){
|
||||
if(!(key in el)) return false;
|
||||
const des= getPropDescriptor(el, key);
|
||||
return !isUndef(des.set);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a property descriptor from a prototype chain
|
||||
*
|
||||
* @param {Object} p - Prototype object
|
||||
* @param {string} key - Property name
|
||||
* @returns {PropertyDescriptor} Property descriptor
|
||||
* @private
|
||||
*/
|
||||
function getPropDescriptor(p, key){
|
||||
p= Object.getPrototypeOf(p);
|
||||
if(!p) return {};
|
||||
@ -224,9 +370,44 @@ function forEachEntries(s, target, element, obj, cb){
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or removes an attribute based on value
|
||||
*
|
||||
* @param {Element} obj - Element to modify
|
||||
* @param {string} prop - Property suffix ("Attribute")
|
||||
* @param {string} key - Attribute name
|
||||
* @param {any} val - Attribute value
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function setRemove(obj, prop, key, val){
|
||||
return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, val); }
|
||||
return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or removes a namespaced attribute based on value
|
||||
*
|
||||
* @param {Element} obj - Element to modify
|
||||
* @param {string} prop - Property suffix ("Attribute")
|
||||
* @param {string} key - Attribute name
|
||||
* @param {any} val - Attribute value
|
||||
* @param {string|null} [ns=null] - Namespace URI
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function setRemoveNS(obj, prop, key, val, ns= null){
|
||||
return obj[ (isUndef(val) ? "remove" : "set") + prop + "NS" ](ns, key, val); }
|
||||
return obj[ (isUndef(val) ? "remove" : "set") + prop + "NS" ](ns, key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or deletes a property based on value
|
||||
*
|
||||
* @param {Object} obj - Object to modify
|
||||
* @param {string} key - Property name
|
||||
* @param {any} val - Property value
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function setDelete(obj, key, val){
|
||||
Reflect.set(obj, key, val); if(!isUndef(val)) return; return Reflect.deleteProperty(obj, key); }
|
||||
Reflect.set(obj, key, val); if(!isUndef(val)) return; return Reflect.deleteProperty(obj, key);
|
||||
}
|
||||
|
@ -1,11 +1,27 @@
|
||||
import { enviroment as env, evc, evd } from './dom-common.js';
|
||||
import { isInstance } from "./helpers.js";
|
||||
|
||||
/**
|
||||
* Connection changes observer for tracking element connection/disconnection
|
||||
* Falls back to a dummy implementation if MutationObserver is not available
|
||||
*/
|
||||
export const c_ch_o= env.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
|
||||
get(){ return ()=> {}; }
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates an observer that tracks elements being connected to and disconnected from the DOM
|
||||
* @returns {Object} Observer with methods to register element listeners
|
||||
*/
|
||||
function connectionsChangesObserverConstructor(){
|
||||
const store= new Map();
|
||||
let is_observing= false;
|
||||
|
||||
/**
|
||||
* Creates a mutation observer callback
|
||||
* @param {Function} stop - Function to stop observation when no longer needed
|
||||
* @returns {Function} MutationObserver callback
|
||||
*/
|
||||
const observerListener= stop=> function(mutations){
|
||||
for(const mutation of mutations){
|
||||
if(mutation.type!=="childList") continue;
|
||||
@ -17,13 +33,26 @@ function connectionsChangesObserverConstructor(){
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
const observer= new env.M(observerListener(stop));
|
||||
|
||||
return {
|
||||
/**
|
||||
* Creates an observer for a specific element
|
||||
* @param {Element} element - Element to observe
|
||||
* @returns {Function} Cleanup function
|
||||
*/
|
||||
observe(element){
|
||||
const o= new env.M(observerListener(()=> {}));
|
||||
o.observe(element, { childList: true, subtree: true });
|
||||
return ()=> o.disconnect();
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a connection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for connection event
|
||||
*/
|
||||
onConnected(element, listener){
|
||||
start();
|
||||
const listeners= getElementStore(element);
|
||||
@ -31,6 +60,12 @@ function connectionsChangesObserverConstructor(){
|
||||
listeners.connected.add(listener);
|
||||
listeners.length_c+= 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregister a connection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offConnected(element, listener){
|
||||
if(!store.has(element)) return;
|
||||
const ls= store.get(element);
|
||||
@ -39,6 +74,12 @@ function connectionsChangesObserverConstructor(){
|
||||
ls.length_c-= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a disconnection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for disconnection event
|
||||
*/
|
||||
onDisconnected(element, listener){
|
||||
start();
|
||||
const listeners= getElementStore(element);
|
||||
@ -46,21 +87,38 @@ function connectionsChangesObserverConstructor(){
|
||||
listeners.disconnected.add(listener);
|
||||
listeners.length_d+= 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregister a disconnection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offDisconnected(element, listener){
|
||||
if(!store.has(element)) return;
|
||||
const ls= store.get(element);
|
||||
if(!ls.disconnected.has(listener)) return;
|
||||
ls.disconnected.delete(listener);
|
||||
ls.length_d-= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Cleanup element tracking when all listeners are removed
|
||||
* @param {Element} element - Element to potentially remove from tracking
|
||||
* @param {Object} ls - Element's listener store
|
||||
*/
|
||||
function cleanWhenOff(element, ls){
|
||||
if(ls.length_c || ls.length_d)
|
||||
return;
|
||||
store.delete(element);
|
||||
stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates a store for element listeners
|
||||
* @param {Element} element - Element to get store for
|
||||
* @returns {Object} Listener store for the element
|
||||
*/
|
||||
function getElementStore(element){
|
||||
if(store.has(element)) return store.get(element);
|
||||
const out= {
|
||||
@ -72,32 +130,58 @@ function connectionsChangesObserverConstructor(){
|
||||
store.set(element, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start observing DOM changes
|
||||
*/
|
||||
function start(){
|
||||
if(is_observing) return;
|
||||
is_observing= true;
|
||||
observer.observe(env.D.body, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop observing DOM changes when no longer needed
|
||||
*/
|
||||
function stop(){
|
||||
if(!is_observing || store.size) return;
|
||||
is_observing= false;
|
||||
observer.disconnect();
|
||||
}
|
||||
|
||||
//TODO: remount support?
|
||||
/**
|
||||
* Schedule a task during browser idle time
|
||||
* @returns {Promise<void>} Promise that resolves when browser is idle
|
||||
*/
|
||||
function requestIdle(){ return new Promise(function(resolve){
|
||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
}); }
|
||||
|
||||
/**
|
||||
* Collects child elements from the store that are contained by the given element
|
||||
* @param {Element} element - Parent element
|
||||
* @returns {Promise<Element[]>} Promise resolving to array of child elements
|
||||
*/
|
||||
async function collectChildren(element){
|
||||
if(store.size > 30)//TODO?: limit
|
||||
await requestIdle();
|
||||
const out= [];
|
||||
if(!(element instanceof Node)) return out;
|
||||
if(!isInstance(element, Node)) return out;
|
||||
for(const el of store.keys()){
|
||||
if(el===element || !(el instanceof Node)) continue;
|
||||
if(el===element || !isInstance(el, Node)) continue;
|
||||
if(element.contains(el))
|
||||
out.push(el);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process nodes added to the DOM
|
||||
* @param {NodeList} addedNodes - Nodes that were added
|
||||
* @param {boolean} is_root - Whether these are root-level additions
|
||||
* @returns {boolean} Whether any relevant elements were processed
|
||||
*/
|
||||
function observerAdded(addedNodes, is_root){
|
||||
let out= false;
|
||||
for(const element of addedNodes){
|
||||
@ -115,6 +199,13 @@ function connectionsChangesObserverConstructor(){
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process nodes removed from the DOM
|
||||
* @param {NodeList} removedNodes - Nodes that were removed
|
||||
* @param {boolean} is_root - Whether these are root-level removals
|
||||
* @returns {boolean} Whether any relevant elements were processed
|
||||
*/
|
||||
function observerRemoved(removedNodes, is_root){
|
||||
let out= false;
|
||||
for(const element of removedNodes){
|
||||
@ -128,6 +219,12 @@ function connectionsChangesObserverConstructor(){
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function to dispatch the disconnect event
|
||||
* @param {Element} element - Element that was removed
|
||||
* @returns {Function} Function to dispatch event after confirming disconnection
|
||||
*/
|
||||
function dispatchRemove(element){
|
||||
return ()=> {
|
||||
if(element.isConnected) return;
|
||||
|
@ -1,6 +1,15 @@
|
||||
export { registerReactivity } from './signals-common.js';
|
||||
export { registerReactivity } from './signals-lib/common.js';
|
||||
import { enviroment as env, keyLTE, evc, evd, eva } from './dom-common.js';
|
||||
import { oAssign, onAbort } from './helpers.js';
|
||||
|
||||
/**
|
||||
* Creates a function to dispatch events on elements
|
||||
*
|
||||
* @param {string} name - Event name
|
||||
* @param {Object} [options] - Event options
|
||||
* @param {Element|Function} [host] - Host element or function returning host element
|
||||
* @returns {Function} Function that dispatches the event
|
||||
*/
|
||||
export function dispatchEvent(name, options, host){
|
||||
if(!options) options= {};
|
||||
return function dispatch(element, ...d){
|
||||
@ -9,10 +18,19 @@ export function dispatchEvent(name, options, host){
|
||||
element= typeof host==="function"? host() : host;
|
||||
}
|
||||
//TODO: what about re-emmitting?
|
||||
const event= d.length ? new CustomEvent(name, Object.assign({ detail: d[0] }, options)) : new Event(name, options);
|
||||
const event= d.length ? new CustomEvent(name, oAssign({ detail: d[0] }, options)) : new Event(name, options);
|
||||
return element.dispatchEvent(event);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function to register event listeners on elements
|
||||
*
|
||||
* @param {string} event - Event name
|
||||
* @param {Function} listener - Event handler
|
||||
* @param {Object} [options] - Event listener options
|
||||
* @returns {Function} Function that registers the listener
|
||||
*/
|
||||
export function on(event, listener, options){
|
||||
return function registerElement(element){
|
||||
element.addEventListener(event, listener, options);
|
||||
@ -21,10 +39,23 @@ export function on(event, listener, options){
|
||||
}
|
||||
|
||||
import { c_ch_o } from "./events-observer.js";
|
||||
import { onAbort } from './helpers.js';
|
||||
const lifeOptions= obj=> Object.assign({}, typeof obj==="object" ? obj : null, { once: true });
|
||||
|
||||
/**
|
||||
* Prepares lifecycle event options with once:true default
|
||||
* @private
|
||||
*/
|
||||
const lifeOptions= obj=> oAssign({}, typeof obj==="object" ? obj : null, { once: true });
|
||||
|
||||
//TODO: cleanUp when event before abort?
|
||||
//TODO: docs (e.g.) https://nolanlawson.com/2024/01/13/web-component-gotcha-constructor-vs-connectedcallback/
|
||||
|
||||
/**
|
||||
* Creates a function to register connected lifecycle event listeners
|
||||
*
|
||||
* @param {Function} listener - Event handler
|
||||
* @param {Object} [options] - Event listener options
|
||||
* @returns {Function} Function that registers the connected listener
|
||||
*/
|
||||
on.connected= function(listener, options){
|
||||
options= lifeOptions(options);
|
||||
return function registerElement(element){
|
||||
@ -37,6 +68,14 @@ on.connected= function(listener, options){
|
||||
return element;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a function to register disconnected lifecycle event listeners
|
||||
*
|
||||
* @param {Function} listener - Event handler
|
||||
* @param {Object} [options] - Event listener options
|
||||
* @returns {Function} Function that registers the disconnected listener
|
||||
*/
|
||||
on.disconnected= function(listener, options){
|
||||
options= lifeOptions(options);
|
||||
return function registerElement(element){
|
||||
@ -48,7 +87,16 @@ on.disconnected= function(listener, options){
|
||||
return element;
|
||||
};
|
||||
};
|
||||
|
||||
/** Store for disconnect abort controllers */
|
||||
const store_abort= new WeakMap();
|
||||
|
||||
/**
|
||||
* Creates an AbortController that triggers when the element disconnects
|
||||
*
|
||||
* @param {Element|Function} host - Host element or function taking an element
|
||||
* @returns {AbortController} AbortController that aborts on disconnect
|
||||
*/
|
||||
on.disconnectedAsAbort= function(host){
|
||||
if(store_abort.has(host)) return store_abort.get(host);
|
||||
|
||||
@ -57,7 +105,17 @@ on.disconnectedAsAbort= function(host){
|
||||
host(on.disconnected(()=> a.abort()));
|
||||
return a;
|
||||
};
|
||||
|
||||
/** Store for elements with attribute observers */
|
||||
const els_attribute_store= new WeakSet();
|
||||
|
||||
/**
|
||||
* Creates a function to register attribute change event listeners
|
||||
*
|
||||
* @param {Function} listener - Event handler
|
||||
* @param {Object} [options] - Event listener options
|
||||
* @returns {Function} Function that registers the attribute change listener
|
||||
*/
|
||||
on.attributeChanged= function(listener, options){
|
||||
if(typeof options !== "object")
|
||||
options= {};
|
||||
|
@ -1,13 +1,43 @@
|
||||
/**
|
||||
* Safe method to check if an object has a specific property
|
||||
* @param {...any} a - Arguments to pass to Object.prototype.hasOwnProperty.call
|
||||
* @returns {boolean} Result of hasOwnProperty check
|
||||
*/
|
||||
export const hasOwn= (...a)=> Object.prototype.hasOwnProperty.call(...a);
|
||||
|
||||
/**
|
||||
* Checks if a value is undefined
|
||||
* @param {any} value - The value to check
|
||||
* @returns {boolean} True if the value is undefined
|
||||
*/
|
||||
export function isUndef(value){ return typeof value==="undefined"; }
|
||||
|
||||
/**
|
||||
* Enhanced typeof that handles null and objects better
|
||||
* @param {any} v - The value to check
|
||||
* @returns {string} Type as a string
|
||||
*/
|
||||
export function typeOf(v){
|
||||
const t= typeof v;
|
||||
if(t!=="object") return t;
|
||||
if(v===null) return "null";
|
||||
return Object.prototype.toString.call(v);
|
||||
}
|
||||
|
||||
export function isInstance(obj, cls){ return obj instanceof cls; }
|
||||
/** @type {typeof Object.prototype.isPrototypeOf.call} */
|
||||
export function isProtoFrom(obj, cls){ return Object.prototype.isPrototypeOf.call(cls, obj); }
|
||||
export function oCreate(proto= null, p= {}){ return Object.create(proto, p); }
|
||||
export function oAssign(...o){ return Object.assign(...o); }
|
||||
|
||||
/**
|
||||
* Handles AbortSignal registration and cleanup
|
||||
* @param {AbortSignal} signal - The AbortSignal to listen to
|
||||
* @param {Function} listener - The abort event listener
|
||||
* @returns {Function|undefined|boolean} Cleanup function or undefined if already aborted
|
||||
*/
|
||||
export function onAbort(signal, listener){
|
||||
if(!signal || !(signal instanceof AbortSignal))
|
||||
if(!signal || !isInstance(signal, AbortSignal))
|
||||
return true;
|
||||
if(signal.aborted)
|
||||
return;
|
||||
@ -16,6 +46,13 @@ export function onAbort(signal, listener){
|
||||
signal.removeEventListener("abort", listener);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes observed attributes for custom elements
|
||||
* @param {object} instance - The custom element instance
|
||||
* @param {Function} observedAttribute - Function to process each attribute
|
||||
* @returns {object} Object with processed attributes
|
||||
*/
|
||||
export function observedAttributes(instance, observedAttribute){
|
||||
const { observedAttributes= [] }= instance.constructor;
|
||||
return observedAttributes
|
||||
@ -24,4 +61,28 @@ export function observedAttributes(instance, observedAttribute){
|
||||
return out;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts kebab-case strings to camelCase
|
||||
* @param {string} name - The kebab-case string
|
||||
* @returns {string} The camelCase string
|
||||
*/
|
||||
function kebabToCamel(name){ return name.replace(/-./g, x=> x[1].toUpperCase()); }
|
||||
|
||||
/**
|
||||
* Error class for definition tracking
|
||||
* Shows the correct stack trace for debugging (signal) creation
|
||||
*/
|
||||
export class Defined extends Error{
|
||||
constructor(){
|
||||
super();
|
||||
const [ curr, ...rest ]= this.stack.split("\n");
|
||||
const curr_file= curr.slice(curr.indexOf("@"), curr.indexOf(".js:")+4);
|
||||
const curr_lib= curr_file.includes("src/helpers.js") ? "src/" : curr_file;
|
||||
this.stack= rest.find(l=> !l.includes(curr_lib)) || curr;
|
||||
}
|
||||
get compact(){
|
||||
const { stack }= this;
|
||||
return stack.slice(0, stack.indexOf("@")+1)+"…"+stack.slice(stack.lastIndexOf("/"));
|
||||
}
|
||||
}
|
||||
|
@ -1,292 +0,0 @@
|
||||
export const mark= "__dde_signal";
|
||||
import { hasOwn } from "./helpers.js";
|
||||
|
||||
export function isSignal(candidate){
|
||||
try{ return hasOwn(candidate, mark); }
|
||||
catch(e){ return false; }
|
||||
}
|
||||
/** @type {function[]} */
|
||||
const stack_watch= [];
|
||||
/**
|
||||
* ### `WeakMap<function, Set<ddeSignal<any, any>>>`
|
||||
* The `Set` is in the form of `[ source, ...depended signals (DSs) ]`.
|
||||
* When the DS is cleaned (`S.clear`) it is removed from DSs,
|
||||
* if remains only one (`source`) it is cleared too.
|
||||
* ### `WeakMap<object, function>`
|
||||
* This is used for revesed deps, the `function` is also key for `deps`.
|
||||
* @type {WeakMap<function|object,Set<ddeSignal<any, any>>|function>}
|
||||
* */
|
||||
const deps= new WeakMap();
|
||||
export function signal(value, actions){
|
||||
if(typeof value!=="function")
|
||||
return create(false, value, actions);
|
||||
if(isSignal(value)) return value;
|
||||
|
||||
const out= create(true);
|
||||
const contextReWatch= function(){
|
||||
const [ origin, ...deps_old ]= deps.get(contextReWatch);
|
||||
deps.set(contextReWatch, new Set([ origin ]));
|
||||
|
||||
stack_watch.push(contextReWatch);
|
||||
write(out, value());
|
||||
stack_watch.pop();
|
||||
|
||||
if(!deps_old.length) return;
|
||||
const deps_curr= deps.get(contextReWatch);
|
||||
for (const dep_signal of deps_old){
|
||||
if(deps_curr.has(dep_signal)) continue;
|
||||
removeSignalListener(dep_signal, contextReWatch);
|
||||
}
|
||||
};
|
||||
deps.set(out[mark], contextReWatch);
|
||||
deps.set(contextReWatch, new Set([ out ]));
|
||||
contextReWatch();
|
||||
return out;
|
||||
}
|
||||
export { signal as S };
|
||||
signal.action= function(s, name, ...a){
|
||||
const M= s[mark], { actions }= M;
|
||||
if(!actions || !(name in actions))
|
||||
throw new Error(`'${s}' has no action with name '${name}'!`);
|
||||
actions[name].apply(M, a);
|
||||
if(M.skip) return (delete M.skip);
|
||||
M.listeners.forEach(l=> l(M.value));
|
||||
};
|
||||
signal.on= function on(s, listener, options= {}){
|
||||
const { signal: as }= options;
|
||||
if(as && as.aborted) return;
|
||||
if(Array.isArray(s)) return s.forEach(s=> on(s, listener, options));
|
||||
addSignalListener(s, listener);
|
||||
if(as) as.addEventListener("abort", ()=> removeSignalListener(s, listener));
|
||||
//TODO: cleanup when signal removed
|
||||
};
|
||||
signal.symbols= {
|
||||
//signal: mark,
|
||||
onclear: Symbol.for("Signal.onclear")
|
||||
};
|
||||
signal.clear= function(...signals){
|
||||
for(const s of signals){
|
||||
const M= s[mark];
|
||||
if(!M) continue;
|
||||
delete s.toJSON;
|
||||
M.onclear.forEach(f=> f.call(M));
|
||||
clearListDeps(s, M);
|
||||
delete s[mark];
|
||||
}
|
||||
function clearListDeps(s, o){
|
||||
o.listeners.forEach(l=> {
|
||||
o.listeners.delete(l);
|
||||
if(!deps.has(l)) return;
|
||||
|
||||
const ls= deps.get(l);
|
||||
ls.delete(s);
|
||||
if(ls.size>1) return;
|
||||
|
||||
s.clear(...ls);
|
||||
deps.delete(l);
|
||||
});
|
||||
}
|
||||
};
|
||||
const key_reactive= "__dde_reactive";
|
||||
import { enviroment as env } from "./dom-common.js";
|
||||
import { el } from "./dom.js";
|
||||
import { scope } from "./dom.js";
|
||||
// TODO: third argument for handle `cache_tmp` in re-render
|
||||
signal.el= function(s, map){
|
||||
const mark_start= el.mark({ type: "reactive" }, true);
|
||||
const mark_end= mark_start.end;
|
||||
const out= env.D.createDocumentFragment();
|
||||
out.append(mark_start, mark_end);
|
||||
const { current }= scope;
|
||||
let cache= {};
|
||||
const reRenderReactiveElement= v=> {
|
||||
if(!mark_start.parentNode || !mark_end.parentNode) // === `isConnected` or wasn’t yet rendered
|
||||
return removeSignalListener(s, reRenderReactiveElement);
|
||||
const cache_tmp= cache; // will be reused in the useCache or removed in the while loop on the end
|
||||
cache= {};
|
||||
scope.push(current);
|
||||
let els= map(v, function useCache(key, fun){
|
||||
let value;
|
||||
if(hasOwn(cache_tmp, key)){
|
||||
value= cache_tmp[key];
|
||||
delete cache_tmp[key];
|
||||
} else
|
||||
value= fun();
|
||||
cache[key]= value;
|
||||
return value;
|
||||
});
|
||||
scope.pop();
|
||||
if(!Array.isArray(els))
|
||||
els= [ els ];
|
||||
const el_start_rm= document.createComment("");
|
||||
els.push(el_start_rm);
|
||||
mark_start.after(...els);
|
||||
let el_r;
|
||||
while(( el_r= el_start_rm.nextSibling ) && el_r !== mark_end)
|
||||
el_r.remove();
|
||||
el_start_rm.remove();
|
||||
if(mark_start.isConnected)
|
||||
requestCleanUpReactives(current.host());
|
||||
};
|
||||
addSignalListener(s, reRenderReactiveElement);
|
||||
removeSignalsFromElements(s, reRenderReactiveElement, mark_start, map);
|
||||
reRenderReactiveElement(s());
|
||||
return out;
|
||||
};
|
||||
function requestCleanUpReactives(host){
|
||||
if(!host || !host[key_reactive]) return;
|
||||
(requestIdleCallback || setTimeout)(function(){
|
||||
host[key_reactive]= host[key_reactive]
|
||||
.filter(([ s, el ])=> el.isConnected ? true : (removeSignalListener(...s), false));
|
||||
});
|
||||
}
|
||||
import { on } from "./events.js";
|
||||
import { observedAttributes } from "./helpers.js";
|
||||
const observedAttributeActions= {
|
||||
_set(value){ this.value= value; },
|
||||
};
|
||||
function observedAttribute(store){
|
||||
return function(instance, name){
|
||||
const varS= (...args)=> !args.length
|
||||
? read(varS)
|
||||
: instance.setAttribute(name, ...args);
|
||||
const out= toSignal(varS, instance.getAttribute(name), observedAttributeActions);
|
||||
store[name]= out;
|
||||
return out;
|
||||
};
|
||||
}
|
||||
const key_attributes= "__dde_attributes";
|
||||
signal.observedAttributes= function(element){
|
||||
const store= element[key_attributes]= {};
|
||||
const attrs= observedAttributes(element, observedAttribute(store));
|
||||
on.attributeChanged(function attributeChangeToSignal({ detail }){
|
||||
/*! This maps attributes to signals (`S.observedAttributes`).
|
||||
* Investigate `__dde_attributes` key of the element.*/
|
||||
const [ name, value ]= detail;
|
||||
const curr= this[key_attributes][name];
|
||||
if(curr) return signal.action(curr, "_set", value);
|
||||
})(element);
|
||||
on.disconnected(function(){
|
||||
/*! This removes all signals mapped to attributes (`S.observedAttributes`).
|
||||
* Investigate `__dde_attributes` key of the element.*/
|
||||
signal.clear(...Object.values(this[key_attributes]));
|
||||
})(element);
|
||||
return attrs;
|
||||
};
|
||||
|
||||
import { typeOf } from './helpers.js';
|
||||
export const signals_config= {
|
||||
isSignal,
|
||||
processReactiveAttribute(element, key, attrs, set){
|
||||
if(!isSignal(attrs)) return attrs;
|
||||
const l= attr=> {
|
||||
if(!element.isConnected)
|
||||
return removeSignalListener(attrs, l);
|
||||
set(key, attr);
|
||||
};
|
||||
addSignalListener(attrs, l);
|
||||
removeSignalsFromElements(attrs, l, element, key);
|
||||
return attrs();
|
||||
}
|
||||
};
|
||||
function removeSignalsFromElements(s, listener, ...notes){
|
||||
const { current }= scope;
|
||||
if(current.prevent) return;
|
||||
current.host(function(element){
|
||||
if(!element[key_reactive]){
|
||||
element[key_reactive]= [];
|
||||
on.disconnected(()=>
|
||||
/*!
|
||||
* Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, …?).
|
||||
* You can investigate the `__dde_reactive` key of the element.
|
||||
* */
|
||||
element[key_reactive].forEach(([ [ s, listener ] ])=>
|
||||
removeSignalListener(s, listener, s[mark] && s[mark].host && s[mark].host() === element))
|
||||
)(element);
|
||||
}
|
||||
element[key_reactive].push([ [ s, listener ], ...notes ]);
|
||||
});
|
||||
}
|
||||
|
||||
function create(is_readonly, value, actions){
|
||||
const varS= is_readonly
|
||||
? ()=> read(varS)
|
||||
: (...value)=> value.length ? write(varS, ...value) : read(varS);
|
||||
return toSignal(varS, value, actions, is_readonly);
|
||||
}
|
||||
const protoSigal= Object.assign(Object.create(null), {
|
||||
stopPropagation(){
|
||||
this.skip= true;
|
||||
}
|
||||
});
|
||||
class SignalDefined extends Error{
|
||||
constructor(){
|
||||
super();
|
||||
const [ curr, ...rest ]= this.stack.split("\n");
|
||||
const curr_file= curr.slice(curr.indexOf("@"), curr.indexOf(".js:")+4);
|
||||
this.stack= rest.find(l=> !l.includes(curr_file));
|
||||
}
|
||||
}
|
||||
function toSignal(s, value, actions, readonly= false){
|
||||
const onclear= [];
|
||||
if(typeOf(actions)!=="[object Object]")
|
||||
actions= {};
|
||||
const { onclear: ocs }= signal.symbols;
|
||||
if(actions[ocs]){
|
||||
onclear.push(actions[ocs]);
|
||||
delete actions[ocs];
|
||||
}
|
||||
const { host }= scope;
|
||||
Reflect.defineProperty(s, mark, {
|
||||
value: {
|
||||
value, actions, onclear, host,
|
||||
listeners: new Set(),
|
||||
defined: (new SignalDefined()).stack,
|
||||
readonly
|
||||
},
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
configurable: true
|
||||
});
|
||||
s.toJSON= ()=> s();
|
||||
s.valueOf= ()=> s[mark] && s[mark].value;
|
||||
Object.setPrototypeOf(s[mark], protoSigal);
|
||||
return s;
|
||||
}
|
||||
function currentContext(){
|
||||
return stack_watch[stack_watch.length - 1];
|
||||
}
|
||||
function read(s){
|
||||
if(!s[mark]) return;
|
||||
const { value, listeners }= s[mark];
|
||||
const context= currentContext();
|
||||
if(context) listeners.add(context);
|
||||
if(deps.has(context)) deps.get(context).add(s);
|
||||
return value;
|
||||
}
|
||||
function write(s, value, force){
|
||||
if(!s[mark]) return;
|
||||
const M= s[mark];
|
||||
if(!force && M.value===value) return;
|
||||
M.value= value;
|
||||
M.listeners.forEach(l=> l(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
function addSignalListener(s, listener){
|
||||
if(!s[mark]) return;
|
||||
return s[mark].listeners.add(listener);
|
||||
}
|
||||
function removeSignalListener(s, listener, clear_when_empty){
|
||||
const M= s[mark];
|
||||
if(!M) return;
|
||||
const out= M.listeners.delete(listener);
|
||||
if(clear_when_empty && !M.listeners.size){
|
||||
signal.clear(s);
|
||||
if(!deps.has(M)) return out;
|
||||
const c= deps.get(M);
|
||||
if(!deps.has(c)) return out;
|
||||
deps.get(c).forEach(sig=> removeSignalListener(sig, c, true));
|
||||
}
|
||||
return out;
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
export const signals_global= {
|
||||
isSignal(attributes){ return false; },
|
||||
processReactiveAttribute(obj, key, attr, set){ return attr; },
|
||||
};
|
||||
export function registerReactivity(def, global= true){
|
||||
if(global) return Object.assign(signals_global, def);
|
||||
Object.setPrototypeOf(def, signals_global);
|
||||
return def;
|
||||
}
|
||||
/** @param {unknown} _this @returns {typeof signals_global} */
|
||||
export function signals(_this){
|
||||
return signals_global.isPrototypeOf(_this) && _this!==signals_global ? _this : signals_global;
|
||||
}
|
@ -1,293 +0,0 @@
|
||||
export const mark= "__dde_signal";
|
||||
import { hasOwn } from "./helpers.js";
|
||||
|
||||
export function isSignal(candidate){
|
||||
try{ return hasOwn(candidate, mark); }
|
||||
catch(e){ return false; }
|
||||
}
|
||||
/** @type {function[]} */
|
||||
const stack_watch= [];
|
||||
/**
|
||||
* ### `WeakMap<function, Set<ddeSignal<any, any>>>`
|
||||
* The `Set` is in the form of `[ source, ...depended signals (DSs) ]`.
|
||||
* When the DS is cleaned (`S.clear`) it is removed from DSs,
|
||||
* if remains only one (`source`) it is cleared too.
|
||||
* ### `WeakMap<object, function>`
|
||||
* This is used for revesed deps, the `function` is also key for `deps`.
|
||||
* @type {WeakMap<function|object,Set<ddeSignal<any, any>>|function>}
|
||||
* */
|
||||
const deps= new WeakMap();
|
||||
export function signal(value, actions){
|
||||
if(typeof value!=="function")
|
||||
return create(false, value, actions);
|
||||
if(isSignal(value)) return value;
|
||||
|
||||
const out= create(true);
|
||||
const contextReWatch= function(){
|
||||
const [ origin, ...deps_old ]= deps.get(contextReWatch);
|
||||
deps.set(contextReWatch, new Set([ origin ]));
|
||||
|
||||
stack_watch.push(contextReWatch);
|
||||
write(out, value());
|
||||
stack_watch.pop();
|
||||
|
||||
if(!deps_old.length) return;
|
||||
const deps_curr= deps.get(contextReWatch);
|
||||
for (const dep_signal of deps_old){
|
||||
if(deps_curr.has(dep_signal)) continue;
|
||||
removeSignalListener(dep_signal, contextReWatch);
|
||||
}
|
||||
};
|
||||
deps.set(out[mark], contextReWatch);
|
||||
deps.set(contextReWatch, new Set([ out ]));
|
||||
contextReWatch();
|
||||
return out;
|
||||
}
|
||||
export { signal as S };
|
||||
signal.action= function(s, name, ...a){
|
||||
const M= s[mark], { actions }= M;
|
||||
if(!actions || !(name in actions))
|
||||
throw new Error(`'${s}' has no action with name '${name}'!`);
|
||||
actions[name].apply(M, a);
|
||||
if(M.skip) return (delete M.skip);
|
||||
M.listeners.forEach(l=> l(M.value));
|
||||
};
|
||||
signal.on= function on(s, listener, options= {}){
|
||||
const { signal: as }= options;
|
||||
if(as && as.aborted) return;
|
||||
if(Array.isArray(s)) return s.forEach(s=> on(s, listener, options));
|
||||
addSignalListener(s, listener);
|
||||
if(as) as.addEventListener("abort", ()=> removeSignalListener(s, listener));
|
||||
//TODO: cleanup when signal removed
|
||||
};
|
||||
signal.symbols= {
|
||||
//signal: mark,
|
||||
onclear: Symbol.for("Signal.onclear")
|
||||
};
|
||||
signal.clear= function(...signals){
|
||||
for(const s of signals){
|
||||
const M= s[mark];
|
||||
if(!M) continue;
|
||||
delete s.toJSON;
|
||||
M.onclear.forEach(f=> f.call(M));
|
||||
clearListDeps(s, M);
|
||||
delete s[mark];
|
||||
}
|
||||
function clearListDeps(s, o){
|
||||
o.listeners.forEach(l=> {
|
||||
o.listeners.delete(l);
|
||||
if(!deps.has(l)) return;
|
||||
|
||||
const ls= deps.get(l);
|
||||
ls.delete(s);
|
||||
if(ls.size>1) return;
|
||||
|
||||
s.clear(...ls);
|
||||
deps.delete(l);
|
||||
});
|
||||
}
|
||||
};
|
||||
const key_reactive= "__dde_reactive";
|
||||
import { enviroment as env } from "./dom-common.js";
|
||||
import { el } from "./dom.js";
|
||||
import { scope } from "./dom.js";
|
||||
// TODO: third argument for handle `cache_tmp` in re-render
|
||||
// TODO: clear cache on disconnect
|
||||
// TODO: extract cache to separate (exportable) function
|
||||
signal.el= function(s, map){
|
||||
const mark_start= el.mark({ type: "reactive" }, true);
|
||||
const mark_end= mark_start.end;
|
||||
const out= env.D.createDocumentFragment();
|
||||
out.append(mark_start, mark_end);
|
||||
const { current }= scope;
|
||||
let cache= {};
|
||||
const reRenderReactiveElement= v=> {
|
||||
if(!mark_start.parentNode || !mark_end.parentNode) // === `isConnected` or wasn’t yet rendered
|
||||
return removeSignalListener(s, reRenderReactiveElement);
|
||||
const cache_tmp= cache; // will be reused in the useCache or removed in the while loop on the end
|
||||
cache= {};
|
||||
scope.push(current);
|
||||
let els= map(v, function useCache(key, fun){
|
||||
let value;
|
||||
if(hasOwn(cache_tmp, key)){
|
||||
value= cache_tmp[key];
|
||||
delete cache_tmp[key];
|
||||
} else
|
||||
value= fun();
|
||||
cache[key]= value;
|
||||
return value;
|
||||
});
|
||||
scope.pop();
|
||||
if(!Array.isArray(els))
|
||||
els= [ els ];
|
||||
const el_start_rm= document.createComment("");
|
||||
els.push(el_start_rm);
|
||||
mark_start.after(...els);
|
||||
let el_r;
|
||||
while(( el_r= el_start_rm.nextSibling ) && el_r !== mark_end)
|
||||
el_r.remove();
|
||||
el_start_rm.remove();
|
||||
if(mark_start.isConnected)
|
||||
requestCleanUpReactives(current.host());
|
||||
};
|
||||
addSignalListener(s, reRenderReactiveElement);
|
||||
removeSignalsFromElements(s, reRenderReactiveElement, mark_start, map);
|
||||
reRenderReactiveElement(s());
|
||||
return out;
|
||||
};
|
||||
function requestCleanUpReactives(host){
|
||||
if(!host || !host[key_reactive]) return;
|
||||
(requestIdleCallback || setTimeout)(function(){
|
||||
host[key_reactive]= host[key_reactive]
|
||||
.filter(([ s, el ])=> el.isConnected ? true : (removeSignalListener(...s), false));
|
||||
});
|
||||
}
|
||||
import { on } from "./events.js";
|
||||
import { observedAttributes } from "./helpers.js";
|
||||
const observedAttributeActions= {
|
||||
_set(value){ this.value= value; },
|
||||
};
|
||||
function observedAttribute(store){
|
||||
return function(instance, name){
|
||||
const varS= (...args)=> !args.length
|
||||
? read(varS)
|
||||
: instance.setAttribute(name, ...args);
|
||||
const out= toSignal(varS, instance.getAttribute(name), observedAttributeActions);
|
||||
store[name]= out;
|
||||
return out;
|
||||
};
|
||||
}
|
||||
const key_attributes= "__dde_attributes";
|
||||
signal.observedAttributes= function(element){
|
||||
const store= element[key_attributes]= {};
|
||||
const attrs= observedAttributes(element, observedAttribute(store));
|
||||
on.attributeChanged(function attributeChangeToSignal({ detail }){
|
||||
/*! This maps attributes to signals (`S.observedAttributes`).
|
||||
* Investigate `__dde_attributes` key of the element.*/
|
||||
const [ name, value ]= detail;
|
||||
const curr= this[key_attributes][name];
|
||||
if(curr) return signal.action(curr, "_set", value);
|
||||
})(element);
|
||||
on.disconnected(function(){
|
||||
/*! This removes all signals mapped to attributes (`S.observedAttributes`).
|
||||
* Investigate `__dde_attributes` key of the element.*/
|
||||
signal.clear(...Object.values(this[key_attributes]));
|
||||
})(element);
|
||||
return attrs;
|
||||
};
|
||||
|
||||
import { typeOf } from './helpers.js';
|
||||
export const signals_config= {
|
||||
isSignal,
|
||||
processReactiveAttribute(element, key, attrs, set){
|
||||
if(!isSignal(attrs)) return attrs;
|
||||
const l= attr=> {
|
||||
if(!element.isConnected)
|
||||
return removeSignalListener(attrs, l);
|
||||
set(key, attr);
|
||||
};
|
||||
addSignalListener(attrs, l);
|
||||
removeSignalsFromElements(attrs, l, element, key);
|
||||
return attrs();
|
||||
}
|
||||
};
|
||||
function removeSignalsFromElements(s, listener, ...notes){
|
||||
const { current }= scope;
|
||||
current.host(function(element){
|
||||
if(element[key_reactive])
|
||||
return element[key_reactive].push([ [ s, listener ], ...notes ]);
|
||||
element[key_reactive]= [];
|
||||
if(current.prevent) return; // typically document.body, doenst need auto-remove as it should happen on page leave
|
||||
on.disconnected(()=>
|
||||
/*!
|
||||
* Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, …?).
|
||||
* You can investigate the `__dde_reactive` key of the element.
|
||||
* */
|
||||
element[key_reactive].forEach(([ [ s, listener ] ])=>
|
||||
removeSignalListener(s, listener, s[mark] && s[mark].host && s[mark].host() === element))
|
||||
)(element);
|
||||
});
|
||||
}
|
||||
|
||||
function create(is_readonly, value, actions){
|
||||
const varS= is_readonly
|
||||
? ()=> read(varS)
|
||||
: (...value)=> value.length ? write(varS, ...value) : read(varS);
|
||||
return toSignal(varS, value, actions, is_readonly);
|
||||
}
|
||||
const protoSigal= Object.assign(Object.create(null), {
|
||||
stopPropagation(){
|
||||
this.skip= true;
|
||||
}
|
||||
});
|
||||
class SignalDefined extends Error{
|
||||
constructor(){
|
||||
super();
|
||||
const [ curr, ...rest ]= this.stack.split("\n");
|
||||
const curr_file= curr.slice(curr.indexOf("@"), curr.indexOf(".js:")+4);
|
||||
this.stack= rest.find(l=> !l.includes(curr_file));
|
||||
}
|
||||
}
|
||||
function toSignal(s, value, actions, readonly= false){
|
||||
const onclear= [];
|
||||
if(typeOf(actions)!=="[object Object]")
|
||||
actions= {};
|
||||
const { onclear: ocs }= signal.symbols;
|
||||
if(actions[ocs]){
|
||||
onclear.push(actions[ocs]);
|
||||
delete actions[ocs];
|
||||
}
|
||||
const { host }= scope;
|
||||
Reflect.defineProperty(s, mark, {
|
||||
value: {
|
||||
value, actions, onclear, host,
|
||||
listeners: new Set(),
|
||||
defined: (new SignalDefined()).stack,
|
||||
readonly
|
||||
},
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
configurable: true
|
||||
});
|
||||
s.toJSON= ()=> s();
|
||||
s.valueOf= ()=> s[mark] && s[mark].value;
|
||||
Object.setPrototypeOf(s[mark], protoSigal);
|
||||
return s;
|
||||
}
|
||||
function currentContext(){
|
||||
return stack_watch[stack_watch.length - 1];
|
||||
}
|
||||
function read(s){
|
||||
if(!s[mark]) return;
|
||||
const { value, listeners }= s[mark];
|
||||
const context= currentContext();
|
||||
if(context) listeners.add(context);
|
||||
if(deps.has(context)) deps.get(context).add(s);
|
||||
return value;
|
||||
}
|
||||
function write(s, value, force){
|
||||
if(!s[mark]) return;
|
||||
const M= s[mark];
|
||||
if(!force && M.value===value) return;
|
||||
M.value= value;
|
||||
M.listeners.forEach(l=> l(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
function addSignalListener(s, listener){
|
||||
if(!s[mark]) return;
|
||||
return s[mark].listeners.add(listener);
|
||||
}
|
||||
function removeSignalListener(s, listener, clear_when_empty){
|
||||
const M= s[mark];
|
||||
if(!M) return;
|
||||
const out= M.listeners.delete(listener);
|
||||
if(clear_when_empty && !M.listeners.size){
|
||||
signal.clear(s);
|
||||
if(!deps.has(M)) return out;
|
||||
const c= deps.get(M);
|
||||
if(!deps.has(c)) return out;
|
||||
deps.get(c).forEach(sig=> removeSignalListener(sig, c, true));
|
||||
}
|
||||
return out;
|
||||
}
|
44
src/signals-lib/common.js
Normal file
44
src/signals-lib/common.js
Normal file
@ -0,0 +1,44 @@
|
||||
import { isProtoFrom, oAssign } from "../helpers.js";
|
||||
/**
|
||||
* Global signals object with default implementation
|
||||
* @type {Object}
|
||||
*/
|
||||
export const signals_global= {
|
||||
/**
|
||||
* Checks if a value is a signal
|
||||
* @param {any} attributes - Value to check
|
||||
* @returns {boolean} Whether the value is a signal
|
||||
*/
|
||||
isSignal(attributes){ return false; },
|
||||
|
||||
/**
|
||||
* Processes an attribute that might be reactive
|
||||
* @param {Element} obj - Element that owns the attribute
|
||||
* @param {string} key - Attribute name
|
||||
* @param {any} attr - Attribute value
|
||||
* @param {Function} set - Function to set the attribute
|
||||
* @returns {any} Processed attribute value
|
||||
*/
|
||||
processReactiveAttribute(obj, key, attr, set){ return attr; },
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a reactivity implementation
|
||||
* @param {Object} def - Reactivity implementation
|
||||
* @param {boolean} [global=true] - Whether to set globally or create a new implementation
|
||||
* @returns {Object} The registered reactivity implementation
|
||||
*/
|
||||
export function registerReactivity(def, global= true){
|
||||
if(global) return oAssign(signals_global, def);
|
||||
Object.setPrototypeOf(def, signals_global);
|
||||
return def;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the signals implementation from a context
|
||||
* @param {unknown} _this - Context to check for signals implementation
|
||||
* @returns {typeof signals_global} Signals implementation
|
||||
*/
|
||||
export function signals(_this){
|
||||
return isProtoFrom(_this, signals_global) && _this!==signals_global ? _this : signals_global;
|
||||
}
|
39
src/signals-lib/helpers.js
Normal file
39
src/signals-lib/helpers.js
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Symbol used to identify signals in objects
|
||||
* @type {string}
|
||||
*/
|
||||
export const mark= "__dde_signal";
|
||||
|
||||
/**
|
||||
* Batches signal updates to improve performance
|
||||
* @type {Function}
|
||||
*/
|
||||
export const queueSignalWrite= (()=> {
|
||||
let pendingSignals= new Set();
|
||||
let scheduled= false;
|
||||
|
||||
/**
|
||||
* Processes all pending signal updates
|
||||
* @private
|
||||
*/
|
||||
function flushSignals() {
|
||||
scheduled = false;
|
||||
const todo= pendingSignals;
|
||||
pendingSignals= new Set();
|
||||
for(const signal of todo){
|
||||
const M = signal[mark];
|
||||
if(M) M.listeners.forEach(l => l(M.value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a signal for update
|
||||
* @param {Object} s - Signal to queue
|
||||
*/
|
||||
return function(s){
|
||||
pendingSignals.add(s);
|
||||
if(scheduled) return;
|
||||
scheduled = true;
|
||||
queueMicrotask(flushSignals);
|
||||
};
|
||||
})();
|
@ -1,4 +1,4 @@
|
||||
import type { Action, Actions, signal as s, Signal, SymbolOnclear } from "../signals.d.ts";
|
||||
import type { Action, Actions, signal as s, Signal, SymbolOnclear } from "../../signals.js";
|
||||
export { Action, Actions, Signal, SymbolOnclear };
|
||||
export const S: s;
|
||||
export const signal: s;
|
487
src/signals-lib/signals-lib.js
Normal file
487
src/signals-lib/signals-lib.js
Normal file
@ -0,0 +1,487 @@
|
||||
import { queueSignalWrite, mark } from "./helpers.js";
|
||||
export { mark };
|
||||
import { hasOwn, Defined, oCreate, isProtoFrom, oAssign } from "../helpers.js";
|
||||
|
||||
const Signal = oCreate(null, {
|
||||
get: { value(){ return read(this); } },
|
||||
set: { value(...v){ return write(this, ...v); } },
|
||||
toJSON: { value(){ return read(this); } },
|
||||
valueOf: { value(){ return this[mark] && this[mark].value; } }
|
||||
});
|
||||
const SignalReadOnly= oCreate(Signal, {
|
||||
set: { value(){ return; } },
|
||||
});
|
||||
/**
|
||||
* Checks if a value is a signal
|
||||
*
|
||||
* @param {any} candidate - Value to check
|
||||
* @returns {boolean} True if the value is a signal
|
||||
*/
|
||||
export function isSignal(candidate){
|
||||
return isProtoFrom(candidate, Signal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stack for tracking nested signal computations
|
||||
* @type {function[]}
|
||||
*/
|
||||
const stack_watch= [];
|
||||
|
||||
/**
|
||||
* Dependencies tracking map for signals
|
||||
*
|
||||
* ### `WeakMap<function, Set<ddeSignal<any, any>>>`
|
||||
* The `Set` is in the form of `[ source, ...depended signals (DSs) ]`.
|
||||
* When the DS is cleaned (`S.clear`) it is removed from DSs,
|
||||
* if remains only one (`source`) it is cleared too.
|
||||
* ### `WeakMap<object, function>`
|
||||
* This is used for revesed deps, the `function` is also key for `deps`.
|
||||
* @type {WeakMap<function|object,Set<ddeSignal<any, any>>|function>}
|
||||
*/
|
||||
const deps= new WeakMap();
|
||||
/**
|
||||
* Creates a new signal or converts a function into a derived signal
|
||||
*
|
||||
* @param {any|function} value - Initial value or function that computes the value
|
||||
* @param {Object} [actions] - Custom actions for the signal
|
||||
* @returns {Object} Signal object with get() and set() methods
|
||||
*/
|
||||
export function signal(value, actions){
|
||||
if(typeof value!=="function")
|
||||
return create(false, value, actions);
|
||||
if(isSignal(value)) return value;
|
||||
|
||||
const out= create(true);
|
||||
|
||||
/**
|
||||
* Updates the derived signal when dependencies change
|
||||
* @private
|
||||
*/
|
||||
function contextReWatch(){
|
||||
const [ origin, ...deps_old ]= deps.get(contextReWatch);
|
||||
deps.set(contextReWatch, new Set([ origin ]));
|
||||
|
||||
stack_watch.push(contextReWatch);
|
||||
write(out, value());
|
||||
stack_watch.pop();
|
||||
|
||||
if(!deps_old.length) return;
|
||||
const deps_curr= deps.get(contextReWatch);
|
||||
for (const dep_signal of deps_old){
|
||||
if(deps_curr.has(dep_signal)) continue;
|
||||
removeSignalListener(dep_signal, contextReWatch);
|
||||
}
|
||||
}
|
||||
deps.set(out[mark], contextReWatch);
|
||||
deps.set(contextReWatch, new Set([ out ]));
|
||||
contextReWatch();
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Alias for signal */
|
||||
export { signal as S };
|
||||
/**
|
||||
* Calls a custom action on a signal
|
||||
*
|
||||
* @param {Object} s - Signal object to call action on
|
||||
* @param {string} name - Action name
|
||||
* @param {...any} a - Arguments to pass to the action
|
||||
*/
|
||||
signal.action= function(s, name, ...a){
|
||||
const M= s[mark];
|
||||
if(!M) return;
|
||||
const { actions }= M;
|
||||
if(!actions || !hasOwn(actions, name))
|
||||
throw new Error(`Action "${name}" not defined. See ${mark}.actions.`);
|
||||
actions[name].apply(M, a);
|
||||
if(M.skip) return (delete M.skip);
|
||||
queueSignalWrite(s);
|
||||
};
|
||||
|
||||
/**
|
||||
* Subscribes a listener to signal changes
|
||||
*
|
||||
* @param {Object|Object[]} s - Signal object or array of signal objects to subscribe to
|
||||
* @param {function} listener - Callback function receiving signal value
|
||||
* @param {Object} [options={}] - Subscription options
|
||||
* @param {AbortSignal} [options.signal] - Signal to abort subscription
|
||||
*/
|
||||
signal.on= function on(s, listener, options= {}){
|
||||
const { signal: as }= options;
|
||||
if(as && as.aborted) return;
|
||||
if(Array.isArray(s)) return s.forEach(s=> on(s, listener, options));
|
||||
addSignalListener(s, listener);
|
||||
if(as) as.addEventListener("abort", ()=> removeSignalListener(s, listener));
|
||||
};
|
||||
|
||||
/**
|
||||
* Symbol constants for signal internals
|
||||
*/
|
||||
signal.symbols= {
|
||||
//signal: mark,
|
||||
onclear: Symbol.for("Signal.onclear")
|
||||
};
|
||||
|
||||
/**
|
||||
* Cleans up signals and their dependencies
|
||||
*
|
||||
* @param {...Object} signals - Signal objects to clean up
|
||||
*/
|
||||
signal.clear= function(...signals){
|
||||
for(const s of signals){
|
||||
const M= s[mark];
|
||||
if(!M) continue;
|
||||
delete s.toJSON;
|
||||
M.onclear.forEach(f=> f.call(M));
|
||||
clearListDeps(s, M);
|
||||
delete s[mark];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up signal dependencies
|
||||
* @param {function} s - Signal being cleared
|
||||
* @param {Object} o - Signal metadata
|
||||
* @private
|
||||
*/
|
||||
function clearListDeps(s, o){
|
||||
o.listeners.forEach(l=> {
|
||||
o.listeners.delete(l);
|
||||
if(!deps.has(l)) return;
|
||||
|
||||
const ls= deps.get(l);
|
||||
ls.delete(s);
|
||||
if(ls.size>1) return;
|
||||
|
||||
s.clear(...ls);
|
||||
deps.delete(l);
|
||||
});
|
||||
}
|
||||
};
|
||||
/** Property key for tracking reactive elements */
|
||||
const key_reactive= "__dde_reactive";
|
||||
import { enviroment as env } from "../dom-common.js";
|
||||
import { el } from "../dom.js";
|
||||
import { scope } from "../dom.js";
|
||||
import { on } from "../events.js";
|
||||
|
||||
export function cache(store= oCreate()){
|
||||
return (key, fun)=> hasOwn(store, key) ? store[key] : (store[key]= fun());
|
||||
}
|
||||
/**
|
||||
* Creates a reactive DOM element that re-renders when signal changes
|
||||
*
|
||||
* @TODO Third argument for handle `cache_tmp` in re-render
|
||||
* @param {Object} s - Signal object to watch
|
||||
* @param {Function} map - Function mapping signal value to DOM elements
|
||||
* @returns {DocumentFragment} Fragment containing reactive elements
|
||||
*/
|
||||
signal.el= function(s, map){
|
||||
const mark_start= el.mark({ type: "reactive", source: new Defined().compact }, true);
|
||||
const mark_end= mark_start.end;
|
||||
const out= env.D.createDocumentFragment();
|
||||
out.append(mark_start, mark_end);
|
||||
const { current }= scope;
|
||||
let cache_shared= oCreate();
|
||||
const reRenderReactiveElement= v=> {
|
||||
if(!mark_start.parentNode || !mark_end.parentNode) // === `isConnected` or wasn’t yet rendered
|
||||
return removeSignalListener(s, reRenderReactiveElement);
|
||||
const memo= cache(cache_shared);
|
||||
cache_shared= oCreate();
|
||||
scope.push(current);
|
||||
let els= map(v, function useCache(key, fun){
|
||||
return (cache_shared[key]= memo(key, fun));
|
||||
});
|
||||
scope.pop();
|
||||
if(!Array.isArray(els))
|
||||
els= [ els ];
|
||||
const el_start_rm= document.createComment("");
|
||||
els.push(el_start_rm);
|
||||
mark_start.after(...els);
|
||||
let el_r;
|
||||
while(( el_r= el_start_rm.nextSibling ) && el_r !== mark_end)
|
||||
el_r.remove();
|
||||
el_start_rm.remove();
|
||||
if(mark_start.isConnected)
|
||||
requestCleanUpReactives(current.host());
|
||||
};
|
||||
addSignalListener(s, reRenderReactiveElement);
|
||||
removeSignalsFromElements(s, reRenderReactiveElement, mark_start, map);
|
||||
reRenderReactiveElement(s.get());
|
||||
current.host(on.disconnected(()=>
|
||||
/*! Clears cached elements for reactive element `S.el` */
|
||||
cache_shared= {}
|
||||
));
|
||||
return out;
|
||||
};
|
||||
/**
|
||||
* Cleans up reactive elements that are no longer connected
|
||||
*
|
||||
* @param {Element} host - Host element containing reactive elements
|
||||
* @private
|
||||
*/
|
||||
function requestCleanUpReactives(host){
|
||||
if(!host || !host[key_reactive]) return;
|
||||
(requestIdleCallback || setTimeout)(function(){
|
||||
host[key_reactive]= host[key_reactive]
|
||||
.filter(([ s, el ])=> el.isConnected ? true : (removeSignalListener(...s), false));
|
||||
});
|
||||
}
|
||||
import { observedAttributes } from "../helpers.js";
|
||||
|
||||
/**
|
||||
* Actions for observed attribute signals
|
||||
* @private
|
||||
*/
|
||||
const observedAttributeActions= {
|
||||
_set(value){ this.value= value; },
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a function that returns signals for element attributes
|
||||
*
|
||||
* @param {Object} store - Storage object for attribute signals
|
||||
* @returns {Function} Function creating attribute signals
|
||||
* @private
|
||||
*/
|
||||
function observedAttribute(store){
|
||||
return function(instance, name){
|
||||
const varS= oCreate(Signal, {
|
||||
set: { value(...v){ return instance.setAttribute(name, ...v); } }
|
||||
});
|
||||
const out= toSignal(varS, instance.getAttribute(name), observedAttributeActions);
|
||||
store[name]= out;
|
||||
return out;
|
||||
};
|
||||
}
|
||||
/** Property key for storing attribute signals */
|
||||
const key_attributes= "__dde_attributes";
|
||||
|
||||
/**
|
||||
* Creates signals for observed attributes in custom elements
|
||||
*
|
||||
* @param {Element} element - Custom element instance
|
||||
* @returns {Object} Object with attribute signals
|
||||
*/
|
||||
signal.observedAttributes= function(element){
|
||||
const store= element[key_attributes]= {};
|
||||
const attrs= observedAttributes(element, observedAttribute(store));
|
||||
on.attributeChanged(function attributeChangeToSignal({ detail }){
|
||||
/*! This maps attributes to signals (`S.observedAttributes`).
|
||||
Investigate `__dde_attributes` key of the element. */
|
||||
const [ name, value ]= detail;
|
||||
const curr= this[key_attributes][name];
|
||||
if(curr) return signal.action(curr, "_set", value);
|
||||
})(element);
|
||||
on.disconnected(function(){
|
||||
/*! This removes all signals mapped to attributes (`S.observedAttributes`).
|
||||
Investigate `__dde_attributes` key of the element. */
|
||||
signal.clear(...Object.values(this[key_attributes]));
|
||||
})(element);
|
||||
return attrs;
|
||||
};
|
||||
|
||||
import { typeOf } from '../helpers.js';
|
||||
|
||||
/**
|
||||
* Signal configuration for the library
|
||||
* Implements processReactiveAttribute to handle signal-based attributes
|
||||
*/
|
||||
export const signals_config= {
|
||||
isSignal,
|
||||
|
||||
/**
|
||||
* Processes attributes that might be signals
|
||||
*
|
||||
* @param {Element} element - Element with the attribute
|
||||
* @param {string} key - Attribute name
|
||||
* @param {any} attrs - Attribute value (possibly a signal)
|
||||
* @param {Function} set - Function to set attribute value
|
||||
* @returns {any} Processed attribute value
|
||||
*/
|
||||
processReactiveAttribute(element, key, attrs, set){
|
||||
if(!isSignal(attrs)) return attrs;
|
||||
const l= attr=> {
|
||||
if(!element.isConnected)
|
||||
return removeSignalListener(attrs, l);
|
||||
set(key, attr);
|
||||
};
|
||||
addSignalListener(attrs, l);
|
||||
removeSignalsFromElements(attrs, l, element, key);
|
||||
return attrs.get();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Registers signal listener for cleanup when element is removed
|
||||
*
|
||||
* @param {Object} s - Signal object to track
|
||||
* @param {Function} listener - Signal listener
|
||||
* @param {...any} notes - Additional context information
|
||||
* @private
|
||||
*/
|
||||
function removeSignalsFromElements(s, listener, ...notes){
|
||||
const { current }= scope;
|
||||
current.host(function(element){
|
||||
if(element[key_reactive])
|
||||
return element[key_reactive].push([ [ s, listener ], ...notes ]);
|
||||
element[key_reactive]= [];
|
||||
if(current.prevent) return; // typically document.body, doenst need auto-remove as it should happen on page leave
|
||||
on.disconnected(()=>
|
||||
/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, …?).
|
||||
You can investigate the `__dde_reactive` key of the element. */
|
||||
element[key_reactive].forEach(([ [ s, listener ] ])=>
|
||||
removeSignalListener(s, listener, s[mark] && s[mark].host && s[mark].host() === element))
|
||||
)(element);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registry for cleaning up signals when they are garbage collected
|
||||
* @type {FinalizationRegistry}
|
||||
*/
|
||||
const cleanUpRegistry = new FinalizationRegistry(function(s){
|
||||
signal.clear({ [mark]: s });
|
||||
});
|
||||
/**
|
||||
* Creates a new signal object
|
||||
*
|
||||
* @param {boolean} is_readonly - Whether the signal is readonly
|
||||
* @param {any} value - Initial signal value
|
||||
* @param {Object} actions - Custom actions for the signal
|
||||
* @returns {Object} Signal object with get() and set() methods
|
||||
* @private
|
||||
*/
|
||||
function create(is_readonly, value, actions){
|
||||
const varS = oCreate(is_readonly ? SignalReadOnly : Signal);
|
||||
const SI= toSignal(varS, value, actions, is_readonly);
|
||||
cleanUpRegistry.register(SI, SI[mark]);
|
||||
return SI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prototype for signal internal objects
|
||||
* @private
|
||||
*/
|
||||
const protoSigal= oAssign(oCreate(), {
|
||||
/**
|
||||
* Prevents signal propagation
|
||||
*/
|
||||
stopPropagation(){
|
||||
this.skip= true;
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Transforms an object into a signal
|
||||
*
|
||||
* @param {Object} s - Object to transform
|
||||
* @param {any} value - Initial value
|
||||
* @param {Object} actions - Custom actions
|
||||
* @param {boolean} [readonly=false] - Whether the signal is readonly
|
||||
* @returns {Object} Signal object with get() and set() methods
|
||||
* @private
|
||||
*/
|
||||
function toSignal(s, value, actions, readonly= false){
|
||||
const onclear= [];
|
||||
if(typeOf(actions)!=="[object Object]")
|
||||
actions= {};
|
||||
const { onclear: ocs }= signal.symbols;
|
||||
if(actions[ocs]){
|
||||
onclear.push(actions[ocs]);
|
||||
delete actions[ocs];
|
||||
}
|
||||
const { host }= scope;
|
||||
Reflect.defineProperty(s, mark, {
|
||||
value: oAssign(oCreate(protoSigal), {
|
||||
value, actions, onclear, host,
|
||||
listeners: new Set(),
|
||||
defined: new Defined().stack,
|
||||
readonly
|
||||
}),
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
configurable: true
|
||||
});
|
||||
return s;
|
||||
}
|
||||
/**
|
||||
* Gets the current computation context
|
||||
* @returns {function|undefined} Current context function
|
||||
* @private
|
||||
*/
|
||||
function currentContext(){
|
||||
return stack_watch[stack_watch.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a signal's value and tracks dependencies
|
||||
*
|
||||
* @param {Object} s - Signal object to read
|
||||
* @returns {any} Signal value
|
||||
* @private
|
||||
*/
|
||||
function read(s){
|
||||
if(!s[mark]) return;
|
||||
const { value, listeners }= s[mark];
|
||||
const context= currentContext();
|
||||
if(context) listeners.add(context);
|
||||
if(deps.has(context)) deps.get(context).add(s);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a new value to a signal
|
||||
*
|
||||
* @param {Object} s - Signal object to update
|
||||
* @param {any} value - New value
|
||||
* @param {boolean} [force=false] - Force update even if value is unchanged
|
||||
* @returns {any} The new value
|
||||
* @private
|
||||
*/
|
||||
function write(s, value, force){
|
||||
const M= s[mark];
|
||||
if(!M || (!force && M.value===value)) return;
|
||||
|
||||
M.value= value;
|
||||
queueSignalWrite(s);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to a signal
|
||||
*
|
||||
* @param {Object} s - Signal object to listen to
|
||||
* @param {Function} listener - Callback function
|
||||
* @returns {Set} Listener set
|
||||
* @private
|
||||
*/
|
||||
function addSignalListener(s, listener){
|
||||
if(!s[mark]) return;
|
||||
return s[mark].listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener from a signal
|
||||
*
|
||||
* @param {Object} s - Signal object to modify
|
||||
* @param {Function} listener - Listener to remove
|
||||
* @param {boolean} [clear_when_empty] - Whether to clear the signal when no listeners remain
|
||||
* @returns {boolean} Whether the listener was found and removed
|
||||
* @private
|
||||
*/
|
||||
function removeSignalListener(s, listener, clear_when_empty){
|
||||
const M= s[mark];
|
||||
if(!M) return;
|
||||
|
||||
const { listeners: L }= M;
|
||||
const out= L.delete(listener);
|
||||
if(!out || !clear_when_empty || L.size) return out;
|
||||
|
||||
signal.clear(s);
|
||||
const depList= deps.get(M);
|
||||
if(!depList) return out;
|
||||
|
||||
const depSource= deps.get(depList);
|
||||
if(!depSource) return out;
|
||||
|
||||
for(const sig of depSource) removeSignalListener(sig, depList, true);
|
||||
return out;
|
||||
}
|
Reference in New Issue
Block a user