1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2024-11-23 17:19:37 +01:00
This commit is contained in:
Jan Andrle 2024-11-22 16:26:42 +01:00
parent b50f8449aa
commit 576b33921b
Signed by: jaandrle
GPG Key ID: B3A25AED155AFFAB
41 changed files with 300 additions and 158 deletions

View File

@ -1,4 +1,6 @@
**WIP** (the experimentation phase) | [source code on GitHub](https://github.com/jaandrle/deka-dom-el) | [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el) **WIP** (the experimentation phase)
| [source code on GitHub](https://github.com/jaandrle/deka-dom-el)
| [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el)
***Vanilla for flavouring — a full-fledged feast for large projects*** ***Vanilla for flavouring — a full-fledged feast for large projects***
@ -10,11 +12,14 @@ document.body.append(
el("p", "See some syntax examples here:"), el("p", "See some syntax examples here:"),
el("ul").append( el("ul").append(
el("li").append( el("li").append(
el("a", { textContent: "Link to the library repo", title: "Deka DOM El — GitHub", href: "https://github.com/jaandrle/deka-dom-el" }) el("a", { textContent: "Link to the library repo",
title: "Deka DOM El — GitHub",
href: "https://github.com/jaandrle/deka-dom-el" })
), ),
el("li").append( el("li").append(
"Use extended Vanilla JavaScript DOM/IDL API: ", "Use extended Vanilla JavaScript DOM/IDL API: ",
el("span", { textContent: "» this is a span with class=cN and data-a=A, data-b=B «", className: "cN", dataset: { a: "A", b: "B" } }) el("span", { textContent: "» this is a span with class=cN and data-a=A, data-b=B «",
className: "cN", dataset: { a: "A", b: "B" } })
), ),
el("li").append( el("li").append(
el(component, { textContent: "A component", className: "example" }, on("change", console.log)) el(component, { textContent: "A component", className: "example" }, on("change", console.log))
@ -35,18 +40,22 @@ function component({ textContent, className }){
} }
``` ```
# Deka DOM Elements # Deka DOM Elements
Creating reactive elements, components and Web components using [IDL](https://developer.mozilla.org/en-US/docs/Glossary/IDL)/JavaScript DOM API and [**signals/observables**](#signals). Creating reactive elements, components and Web components using [IDL](https://developer.mozilla.org/en-US/docs/
Glossary/IDL)/JavaScript DOM API and [**signals/observables**](#signals).
## Inspiration and suggested alternatives ## Inspiration and suggested alternatives
- my previous library (mostly used internaly): [jaandrle/dollar_dom_component: Functional DOM components without JSX and virtual DOM.](https://github.com/jaandrle/dollar_dom_component) - my previous library (mostly used internaly): [jaandrle/dollar_dom_component: Functional DOM components without
- [vanjs-org/van: 🍦 VanJS: World's smallest reactive UI framework. Incredibly Powerful, Insanely Small - Everyone can build a useful UI app in an hour.](https://github.com/vanjs-org/van) JSX and virtual DOM.](https://github.com/jaandrle/dollar_dom_component)
- [vanjs-org/van: 🍦 VanJS: World's smallest reactive UI framework. Incredibly Powerful, Insanely Small -
Everyone can build a useful UI app in an hour.](https://github.com/vanjs-org/van)
- [hyperhype/hyperscript: Create HyperText with JavaScript.](https://github.com/hyperhype/hyperscript) - [hyperhype/hyperscript: Create HyperText with JavaScript.](https://github.com/hyperhype/hyperscript)
- [adamhaile/S: S.js - Simple, Clean, Fast Reactive Programming in Javascript](https://github.com/adamhaile/S) ([adamhaile/surplus: High performance JSX web views for S.js applications](https://github.com/adamhaile/surplus)) - [adamhaile/S: S.js - Simple, Clean, Fast Reactive Programming in Javascript](https://github.com/adamhaile/S)
([adamhaile/surplus: High performance JSX web views for S.js applications](https://github.com/adamhaile/surplus))
- [potch/signals: a small reactive signals library](https://github.com/potch/signals) - [potch/signals: a small reactive signals library](https://github.com/potch/signals)
## Why an another one? ## Why an another one?
This library falls somewhere between van/hyperscript and [solid-js](https://github.com/solidjs/solid) in terms of size, complexity, This library falls somewhere between van/hyperscript and [solid-js](https://github.com/solidjs/solid) in terms of size,
and usability. complexity, and usability.
Another goal is to proceed in the best spirit of functional programming. This involves starting with Another goal is to proceed in the best spirit of functional programming. This involves starting with
pure JavaScript (DOM API) and gradually adding auxiliary functions, ranging from “minor” improvements pure JavaScript (DOM API) and gradually adding auxiliary functions, ranging from “minor” improvements
@ -74,7 +83,10 @@ To balance these requirements, numerous compromises have been made. To summarize
- [dist/](dist/) (`https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/`…) - [dist/](dist/) (`https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/`…)
## Signals ## Signals
- [Signals — whats going on behind the scenes | by Ryan Hoffnan | ITNEXT](https://itnext.io/signals-whats-going-on-behind-the-scenes-ec858589ea63) - [Signals — whats going on behind the scenes | by Ryan Hoffnan | ITNEXT](https://itnext.io/
- [The Evolution of Signals in JavaScript - DEV Community](https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob) signals-whats-going-on-behind-the-scenes-ec858589ea63)
- there is also [tc39/proposal-signals: A proposal to add signals to JavaScript.](https://github.com/tc39/proposal-signals) - [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) - [Observer pattern - Wikipedia](https://en.wikipedia.org/wiki/Observer_pattern)

View File

@ -1 +1,16 @@
[jaandrle/bs: The simplest possible build system using executables](https://github.com/jaandrle/bs/) ## bs: Build system based on executables
This project uses [jaandrle/bs: The simplest possible build system using executable/bash scripts](
https://github.com/jaandrle/bs).
#### bs/build.js [--minify|--help]
Generates alternative versions of the project (other than native ESM code).
Also generates typescript definitions.
#### bs/docs.js
Generates documentation, from `docs/`. Uses “SSR” technique, using deka-dom-el itself.
#### bs/lint.sh
Lints size of the project, jshint. See configs:
- `package.json`: key `size-limit`
- `package.json`: key `jshintConfig`

View File

@ -34,7 +34,7 @@ $.api("", true)
const file_dts_out= filesOut(file_dts); const file_dts_out= filesOut(file_dts);
echoVariant(file_dts_out); echoVariant(file_dts_out);
s.echo(bundleDTS(file_dts)).to(file_dts_out); s.echo(bundleDTS(file_dts)).to(file_dts_out);
await toDDE(out, file_root); await toDDE(out, file_root);
} }
$.exit(0); $.exit(0);
@ -43,10 +43,13 @@ $.api("", true)
const name= "dde"; const name= "dde";
const out= filesOut(file_root+".js", name); const out= filesOut(file_root+".js", name);
echoVariant(`${out} (${file} → globalThis.${name})`) echoVariant(`${out} (${file} → globalThis.${name})`)
let content= s.cat(file).toString().split(/export ?{/); let content= s.cat(file).toString().split(/export ?{/);
content.splice(1, 0, `\nglobalThis.${name}= {`); 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"); content[2]= content[2]
.replace(/,(?!\n)/g, ",\n")
.replace(/(?<!\n)}/, "\n}")
.replace(/^(\t*)(.*) as ([^,\n]*)(,?)$/mg, "$1$3: $2$4");
s.echo([ s.echo([
`//deka-dom-el library is available via global namespace \`${name}\``, `//deka-dom-el library is available via global namespace \`${name}\``,
"(()=> {", "(()=> {",

View File

@ -1,5 +1,5 @@
#!/usr/bin/env -S npx nodejsscript #!/usr/bin/env -S npx nodejsscript
/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */ /* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */// editorconfig-checker-disable-line
echo("Building static documentation files…"); echo("Building static documentation files…");
echo("Preparing…"); echo("Preparing…");
import { path_target, pages as pages_registered, styles, dispatchEvent, t } from "../docs/ssr.js"; import { path_target, pages as pages_registered, styles, dispatchEvent, t } from "../docs/ssr.js";

View File

@ -10,7 +10,7 @@ export function createHTMl(html, options= {}){
if(dom) cleanHTML(); if(dom) cleanHTML();
// set a default url if we don't get one - otherwise things explode when we copy localstorage keys // set a default url if we don't get one - otherwise things explode when we copy localstorage keys
if (!('url' in options)) { Object.assign(options, { url: 'http://localhost:3000' }) } if (!('url' in options)) { Object.assign(options, { url: 'http://localhost:3000' }) }
dom= new JSDOM(html, options); dom= new JSDOM(html, options);
const window= dom.window; const window= dom.window;
return { return {

View File

@ -1,3 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -eou pipefail
npx editorconfig-checker -format gcc
npx size-limit npx size-limit
npx jshint index.js src npx jshint index.js src

View File

@ -51,9 +51,9 @@ let is_registered= {};
/** @param {string} page_id */ /** @param {string} page_id */
function registerClientPart(page_id){ function registerClientPart(page_id){
if(is_registered[page_id]) return; if(is_registered[page_id]) return;
document.head.append( document.head.append(
el("script", { src: "https://cdn.jsdelivr.net/npm/shiki@0.9", defer: true }), el("script", { src: "https://cdn.jsdelivr.net/npm/shiki@0.9", defer: true }),
); );
registerClientFile( registerClientFile(
new URL("./code.js.js", import.meta.url), new URL("./code.js.js", import.meta.url),

View File

@ -47,7 +47,7 @@ let is_registered= {};
/** @param {string} page_id */ /** @param {string} page_id */
function registerClientPart(page_id){ function registerClientPart(page_id){
if(is_registered[page_id]) return; if(is_registered[page_id]) return;
document.head.append( document.head.append(
el("script", { src: "https://flems.io/flems.html", type: "text/javascript", charset: "utf-8" }), el("script", { src: "https://flems.io/flems.html", type: "text/javascript", charset: "utf-8" }),
); );
@ -64,5 +64,5 @@ function generateCodeId(src){
.replace(/000+/g, ""); .replace(/000+/g, "");
const count= 1 + ( store_prev.get(candidate) || 0 ); const count= 1 + ( store_prev.get(candidate) || 0 );
store_prev.set(candidate, count); store_prev.set(candidate, count);
return count.toString()+"-"+candidate; return count.toString()+"-"+candidate;
} }

View File

@ -2,7 +2,7 @@ import {
customElementRender, customElementRender,
customElementWithDDE, customElementWithDDE,
observedAttributes, observedAttributes,
el, on, scope el, on, scope
} from "deka-dom-el"; } from "deka-dom-el";
import { S } from "deka-dom-el/signals"; import { S } from "deka-dom-el/signals";
export class HTMLCustomElement extends HTMLElement{ export class HTMLCustomElement extends HTMLElement{

View File

@ -2,8 +2,8 @@ import { elNS, assign } from "deka-dom-el";
const elSVG= elNS("http://www.w3.org/2000/svg"); const elSVG= elNS("http://www.w3.org/2000/svg");
const elMath= elNS("http://www.w3.org/1998/Math/MathML"); const elMath= elNS("http://www.w3.org/1998/Math/MathML");
document.body.append( document.body.append(
elSVG("svg"), // see https://developer.mozilla.org/en-US/docs/Web/SVG and https://developer.mozilla.org/en-US/docs/Web/API/SVGElement elSVG("svg"), // see https://developer.mozilla.org/en-US/docs/Web/SVG and https://developer.mozilla.org/en-US/docs/Web/API/SVGElement // editorconfig-checker-disable-line
elMath("math") // see https://developer.mozilla.org/en-US/docs/Web/MathML and https://developer.mozilla.org/en-US/docs/Web/MathML/Global_attributes elMath("math") // see https://developer.mozilla.org/en-US/docs/Web/MathML and https://developer.mozilla.org/en-US/docs/Web/MathML/Global_attributes // editorconfig-checker-disable-line
); );
console.log( console.log(

View File

@ -36,7 +36,7 @@ function elClass(_class, attributes, ...addons){
}); });
element.prepend(el_mark); element.prepend(el_mark);
if(is_fragment) element_host= el_mark; if(is_fragment) element_host= el_mark;
chainableAppend(element); chainableAppend(element);
addons.forEach(c=> c(element_host)); addons.forEach(c=> c(element_host));
scope.pop(); scope.pop();

View File

@ -23,7 +23,7 @@ function component(){
* const data= O("data"); * const data= O("data");
* ul.append(el("li", data)); * ul.append(el("li", data));
* }); * });
* *
* // THE HOST IS PROBABLY DIFFERENT THAN * // THE HOST IS PROBABLY DIFFERENT THAN
* // YOU EXPECT AND OBSERVABLES MAY BE * // YOU EXPECT AND OBSERVABLES MAY BE
* // UNEXPECTEDLY REMOVED!!! * // UNEXPECTEDLY REMOVED!!!

View File

@ -4,25 +4,32 @@ import { mnemonicUl } from "../mnemonicUl.html.js";
export function mnemonic(){ export function mnemonic(){
return mnemonicUl().append( return mnemonicUl().append(
el("li").append( el("li").append(
el("code", "customElementRender(<custom-element>, <connect-target>, <render-function>[, <properties>])"), " — use function to render DOM structure for given <custom-element>", el("code", "customElementRender(<custom-element>, <connect-target>, <render-function>[, <properties>])"),
" — use function to render DOM structure for given <custom-element>",
), ),
el("li").append( el("li").append(
el("code", "customElementWithDDE(<custom-element>)"), " — register <custom-element> to DDE library, see also `lifecyclesToEvents`, can be also used as decorator", el("code", "customElementWithDDE(<custom-element>)"),
" — register <custom-element> to DDE library, see also `lifecyclesToEvents`, can be also used as decorator",
), ),
el("li").append( el("li").append(
el("code", "observedAttributes(<custom-element>)"), " — returns record of observed attributes (keys uses camelCase)", el("code", "observedAttributes(<custom-element>)"),
" — returns record of observed attributes (keys uses camelCase)",
), ),
el("li").append( el("li").append(
el("code", "S.observedAttributes(<custom-element>)"), " — returns record of observed attributes (keys uses camelCase and values are signals)", el("code", "S.observedAttributes(<custom-element>)"),
" — returns record of observed attributes (keys uses camelCase and values are signals)",
), ),
el("li").append( el("li").append(
el("code", "lifecyclesToEvents(<class-declaration>)"), " — convert lifecycle methods to events, can be also used as decorator", el("code", "lifecyclesToEvents(<class-declaration>)"),
" — convert lifecycle methods to events, can be also used as decorator",
), ),
el("li").append( el("li").append(
el("code", "simulateSlots(<class-instance>, <body>[, <mapper>])"), " — simulate slots for Custom Elements without shadow DOM", el("code", "simulateSlots(<class-instance>, <body>[, <mapper>])"),
" — simulate slots for Custom Elements without shadow DOM",
), ),
el("li").append( el("li").append(
el("code", "simulateSlots(<dde-component>[, <mapper>])"), " — simulate slots for “dde”/functional components", el("code", "simulateSlots(<dde-component>[, <mapper>])"),
" — simulate slots for “dde”/functional components",
), ),
); );
} }

View File

@ -4,22 +4,28 @@ import { mnemonicUl } from "../mnemonicUl.html.js";
export function mnemonic(){ export function mnemonic(){
return mnemonicUl().append( return mnemonicUl().append(
el("li").append( el("li").append(
el("code", "assign(<element>, ...<idl-objects>): <element>"), " — assign properties to the element", el("code", "assign(<element>, ...<idl-objects>): <element>"),
" — assign properties to the element",
), ),
el("li").append( el("li").append(
el("code", "el(<tag-name>, <primitive>)[.append(...)]: <element-from-tag-name>"), " — simple element containing only text", el("code", "el(<tag-name>, <primitive>)[.append(...)]: <element-from-tag-name>"),
" — simple element containing only text",
), ),
el("li").append( el("li").append(
el("code", "el(<tag-name>, <idl-object>)[.append(...)]: <element-from-tag-name>"), " — element with more properties", el("code", "el(<tag-name>, <idl-object>)[.append(...)]: <element-from-tag-name>"),
" — element with more properties",
), ),
el("li").append( el("li").append(
el("code", "el(<function>, <function-argument(s)>)[.append(...)]: <element-returned-by-function>"), " — using component represented by function", el("code", "el(<function>, <function-argument(s)>)[.append(...)]: <element-returned-by-function>"),
" — using component represented by function",
), ),
el("li").append( el("li").append(
el("code", "el(<...>, <...>, ...<addons>)"), " — see following page" el("code", "el(<...>, <...>, ...<addons>)"),
" — see following page"
), ),
el("li").append( el("li").append(
el("code", "elNS(<namespace>)(<as-el-see-above>)[.append(...)]: <element-based-on-arguments>"), " — typically SVG elements", el("code", "elNS(<namespace>)(<as-el-see-above>)[.append(...)]: <element-based-on-arguments>"),
" — typically SVG elements",
) )
); );
} }

View File

@ -4,17 +4,21 @@ import { mnemonicUl } from "../mnemonicUl.html.js";
export function mnemonic(){ export function mnemonic(){
return mnemonicUl().append( return mnemonicUl().append(
el("li").append( el("li").append(
el("code", "on(<event>, <listener>[, <options>])(<element>)"), " — just ", el("code", "<element>.addEventListener(<event>, <listener>[, <options>])") el("code", "on(<event>, <listener>[, <options>])(<element>)"),
" — just ", el("code", "<element>.addEventListener(<event>, <listener>[, <options>])")
), ),
el("li").append( el("li").append(
el("code", "on.<live-cycle>(<event>, <listener>[, <options>])(<element>)"), " — corresponds to custom elemnets callbacks ", el("code", "<live-cycle>Callback(...){...}"), el("code", "on.<live-cycle>(<event>, <listener>[, <options>])(<element>)"),
" — corresponds to custom elemnets callbacks ", el("code", "<live-cycle>Callback(...){...}"),
". To connect to custom element see following page, else it is simulated by MutationObserver." ". To connect to custom element see following page, else it is simulated by MutationObserver."
), ),
el("li").append( el("li").append(
el("code", "dispatchEvent(<event>[, <options>])(element)"), " — just ", el("code", "<element>.dispatchEvent(new Event(<event>[, <options>]))") el("code", "dispatchEvent(<event>[, <options>])(element)"),
" — just ", el("code", "<element>.dispatchEvent(new Event(<event>[, <options>]))")
), ),
el("li").append( el("li").append(
el("code", "dispatchEvent(<event>[, <options>])(element, detail)"), " — just ", el("code", "<element>.dispatchEvent(new CustomEvent(<event>, { detail, ...<options> }))") el("code", "dispatchEvent(<event>[, <options>])(element, detail)"),
" — just ", el("code", "<element>.dispatchEvent(new CustomEvent(<event>, { detail, ...<options> }))")
), ),
); );
} }

View File

@ -4,13 +4,16 @@ import { mnemonicUl } from "../mnemonicUl.html.js";
export function mnemonic(){ export function mnemonic(){
return mnemonicUl().append( return mnemonicUl().append(
el("li").append( el("li").append(
el("code", "el(<function>, <function-argument(s)>)[.append(...)]: <element-returned-by-function>"), " — using component represented by function", el("code", "el(<function>, <function-argument(s)>)[.append(...)]: <element-returned-by-function>"),
" — using component represented by function",
), ),
el("li").append( el("li").append(
el("code", "scope.host()"), " — get current component reference" el("code", "scope.host()"),
" — get current component reference"
), ),
el("li").append( el("li").append(
el("code", "scope.host(...<addons>)"), " — use addons to current component", el("code", "scope.host(...<addons>)"),
" — use addons to current component",
) )
); );
} }

View File

@ -7,22 +7,28 @@ export function mnemonic(){
el("code", "S(<value>)"), " — signal: reactive value", el("code", "S(<value>)"), " — signal: reactive value",
), ),
el("li").append( el("li").append(
el("code", "S(()=> <computation>)"), " — read-only signal: reactive value dependent on calculation using other signals", el("code", "S(()=> <computation>)"),
" — read-only signal: reactive value dependent on calculation using other signals",
), ),
el("li").append( el("li").append(
el("code", "S.on(<signal>, <listener>[, <options>])"), " — listen to the signal value changes", el("code", "S.on(<signal>, <listener>[, <options>])"),
" — listen to the signal value changes",
), ),
el("li").append( el("li").append(
el("code", "S.clear(...<signals>)"), " — off and clear signals", el("code", "S.clear(...<signals>)"),
" — off and clear signals",
), ),
el("li").append( el("li").append(
el("code", "S(<value>, <actions>)"), " — signal: pattern to create complex reactive objects/arrays", el("code", "S(<value>, <actions>)"),
" — signal: pattern to create complex reactive objects/arrays",
), ),
el("li").append( el("li").append(
el("code", "S.action(<signal>, <action-name>, ...<action-arguments>)"), " — invoke an action for given signal" el("code", "S.action(<signal>, <action-name>, ...<action-arguments>)"),
" — invoke an action for given signal"
), ),
el("li").append( el("li").append(
el("code", "S.el(<signal>, <function-returning-dom>)"), " — render partial dom structure (template) based on the current signal value", el("code", "S.el(<signal>, <function-returning-dom>)"),
" — render partial dom structure (template) based on the current signal value",
) )
); );
} }

View File

@ -27,7 +27,7 @@ export function page({ pkg, info }){
const page_id= info.id; const page_id= info.id;
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("p", t`The library tries to provide pure JavaScript tool(s) to create reactive interfaces using …`), el("p", t`The library tries to provide pure JavaScript tool(s) to create reactive interfaces using …`),
el(h3, t`Event-driven programming (3 parts separation ≡ 3PS)`), el(h3, t`Event-driven programming (3 parts separation ≡ 3PS)`),
el("p").append(t` el("p").append(t`
Let's introduce the basic principle on which the library is built. We'll use the JavaScript listener as Let's introduce the basic principle on which the library is built. We'll use the JavaScript listener as
@ -56,7 +56,7 @@ export function page({ pkg, info }){
describe usage in specific situations, see for example ${el("a", { textContent: t`MVVM`, ...references.w_mvv })} describe usage in specific situations, see for example ${el("a", { textContent: t`MVVM`, ...references.w_mvv })}
or ${el("a", { textContent: t`MVC`, ...references.w_mvc })}. or ${el("a", { textContent: t`MVC`, ...references.w_mvc })}.
`), `),
el(h3, t`Organization of the documentation`), el(h3, t`Organization of the documentation`),
); );
} }

View File

@ -69,7 +69,7 @@ function metaFacebook({ name, description, homepage }){
function iconGitHub(){ function iconGitHub(){
const el= elNS("http://www.w3.org/2000/svg"); const el= elNS("http://www.w3.org/2000/svg");
return el("svg", { className: "icon", viewBox: "0 0 32 32" }).append( return el("svg", { className: "icon", viewBox: "0 0 32 32" }).append(
el("path", { d: [ //see https://svg-path-visualizer.netlify.app/#M16%200.395c-8.836%200-16%207.163-16%2016%200%207.069%204.585%2013.067%2010.942%2015.182%200.8%200.148%201.094-0.347%201.094-0.77%200-0.381-0.015-1.642-0.022-2.979-4.452%200.968-5.391-1.888-5.391-1.888-0.728-1.849-1.776-2.341-1.776-2.341-1.452-0.993%200.11-0.973%200.11-0.973%201.606%200.113%202.452%201.649%202.452%201.649%201.427%202.446%203.743%201.739%204.656%201.33%200.143-1.034%200.558-1.74%201.016-2.14-3.554-0.404-7.29-1.777-7.29-7.907%200-1.747%200.625-3.174%201.649-4.295-0.166-0.403-0.714-2.030%200.155-4.234%200%200%201.344-0.43%204.401%201.64%201.276-0.355%202.645-0.532%204.005-0.539%201.359%200.006%202.729%200.184%204.008%200.539%203.054-2.070%204.395-1.64%204.395-1.64%200.871%202.204%200.323%203.831%200.157%204.234%201.026%201.12%201.647%202.548%201.647%204.295%200%206.145-3.743%207.498-7.306%207.895%200.574%200.497%201.085%201.47%201.085%202.963%200%202.141-0.019%203.864-0.019%204.391%200%200.426%200.288%200.925%201.099%200.768%206.354-2.118%2010.933-8.113%2010.933-15.18%200-8.837-7.164-16-16-16z el("path", { d: [ //see https://svg-path-visualizer.netlify.app/#M16%200.395c-8.836%200-16%207.163-16%2016%200%207.069%204.585%2013.067%2010.942%2015.182%200.8%200.148%201.094-0.347%201.094-0.77%200-0.381-0.015-1.642-0.022-2.979-4.452%200.968-5.391-1.888-5.391-1.888-0.728-1.849-1.776-2.341-1.776-2.341-1.452-0.993%200.11-0.973%200.11-0.973%201.606%200.113%202.452%201.649%202.452%201.649%201.427%202.446%203.743%201.739%204.656%201.33%200.143-1.034%200.558-1.74%201.016-2.14-3.554-0.404-7.29-1.777-7.29-7.907%200-1.747%200.625-3.174%201.649-4.295-0.166-0.403-0.714-2.030%200.155-4.234%200%200%201.344-0.43%204.401%201.64%201.276-0.355%202.645-0.532%204.005-0.539%201.359%200.006%202.729%200.184%204.008%200.539%203.054-2.070%204.395-1.64%204.395-1.64%200.871%202.204%200.323%203.831%200.157%204.234%201.026%201.12%201.647%202.548%201.647%204.295%200%206.145-3.743%207.498-7.306%207.895%200.574%200.497%201.085%201.47%201.085%202.963%200%202.141-0.019%203.864-0.019%204.391%200%200.426%200.288%200.925%201.099%200.768%206.354-2.118%2010.933-8.113%2010.933-15.18%200-8.837-7.164-16-16-16z // editorconfig-checker-disable-line
"M 16,0.395", "M 16,0.395",
"c -8.836,0 -16,7.163 -16,16", "c -8.836,0 -16,7.163 -16,16",
"c 0,7.069 4.585,13.067 10.942,15.182", "c 0,7.069 4.585,13.067 10.942,15.182",

View File

@ -54,7 +54,7 @@ export function page({ pkg, info }){
`), `),
el(code, { src: fileURL("./components/examples/elements/intro.js"), page_id }), el(code, { src: fileURL("./components/examples/elements/intro.js"), page_id }),
el(h3, t`Creating element(s) (with custom attributes)`), el(h3, t`Creating element(s) (with custom attributes)`),
el("p").append(...T` el("p").append(...T`
You can create a native DOM element by using the ${el("a", references.mdn_create).append( You can create a native DOM element by using the ${el("a", references.mdn_create).append(
@ -77,8 +77,9 @@ export function page({ pkg, info }){
el("p").append(...T` el("p").append(...T`
You can study all JavaScript elements interfaces to the corresponding HTML elements. All HTML elements You can study all JavaScript elements interfaces to the corresponding HTML elements. All HTML elements
inherits from ${el("a", { textContent: "HTMLElement", ...references.mdn_el })}. To see inherits from ${el("a", { textContent: "HTMLElement", ...references.mdn_el })}. To see
all available IDLs for example for paragraphs, see ${el("a", { textContent: "HTMLParagraphElement", ...references.mdn_p })}. all available IDLs for example for paragraphs, see ${el("a", { textContent: "HTMLParagraphElement",
Moreover, the ${el("code", "assign")} provides a way to sets declaratively some convenient properties: ...references.mdn_p })}. Moreover, the ${el("code", "assign")} provides a way to sets declaratively
some convenient properties:
`), `),
el("ul").append( el("ul").append(
el("li").append(...T` el("li").append(...T`
@ -114,7 +115,7 @@ export function page({ pkg, info }){
${el("code", "classListDeclarative")}. ${el("code", "classListDeclarative")}.
`), `),
el(example, { src: fileURL("./components/examples/elements/dekaAssign.js"), page_id }), el(example, { src: fileURL("./components/examples/elements/dekaAssign.js"), page_id }),
el(h3, t`Native JavaScript templating`), el(h3, t`Native JavaScript templating`),
el("p", t`By default, the native JS has no good way to define HTML template using DOM API:`), el("p", t`By default, the native JS has no good way to define HTML template using DOM API:`),
el(example, { src: fileURL("./components/examples/elements/nativeAppend.js"), page_id }), el(example, { src: fileURL("./components/examples/elements/nativeAppend.js"), page_id }),
@ -123,8 +124,8 @@ export function page({ pkg, info }){
parent element. parent element.
`), `),
el(example, { src: fileURL("./components/examples/elements/dekaAppend.js"), page_id }), el(example, { src: fileURL("./components/examples/elements/dekaAppend.js"), page_id }),
el(h3, t`Basic (state-less) components`), el(h3, t`Basic (state-less) components`),
el("p").append(...T` el("p").append(...T`
You can use functions for encapsulation (repeating) logic. The ${el("code", "el")} accepts function You can use functions for encapsulation (repeating) logic. The ${el("code", "el")} accepts function
@ -149,7 +150,7 @@ export function page({ pkg, info }){
the ${el("code", "elNS")} function: the ${el("code", "elNS")} function:
`), `),
el(example, { src: fileURL("./components/examples/elements/dekaElNS.js"), page_id }), el(example, { src: fileURL("./components/examples/elements/dekaElNS.js"), page_id }),
el(mnemonic) el(mnemonic)
); );
} }

View File

@ -29,7 +29,7 @@ const references= {
}, },
/** Custom Element lifecycle callbacks */ /** Custom Element lifecycle callbacks */
mdn_customElement: { mdn_customElement: {
href: "https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks" href: "https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks" // editorconfig-checker-disable-line
}, },
/** MutationObserver */ /** MutationObserver */
mdn_mutation: { mdn_mutation: {
@ -50,9 +50,9 @@ export function page({ pkg, info }){
We quickly introduce helper to listening to the native DOM events. And library syntax/pattern so-called We quickly introduce helper to listening to the native DOM events. And library syntax/pattern so-called
${el("em", t`Addon`)} to incorporate not only this in UI templates declaratively. ${el("em", t`Addon`)} to incorporate not only this in UI templates declaratively.
`), `),
el(code, { src: fileURL("./components/examples/events/intro.js"), page_id }), el(code, { src: fileURL("./components/examples/events/intro.js"), page_id }),
el(h3, t`Events and listenners`), el(h3, t`Events and listenners`),
el("p").append(...T` el("p").append(...T`
In JavaScript you can listen to the native DOM events of the given element by using In JavaScript you can listen to the native DOM events of the given element by using
@ -105,17 +105,20 @@ export function page({ pkg, info }){
`), `),
el("p").append(...T` el("p").append(...T`
The library provide three additional live-cycle events corresponding to how they are named in a case of The library provide three additional live-cycle events corresponding to how they are named in a case of
custom elements: ${el("code", "on.connected")}, ${el("code", "on.disconnected")} and ${el("code", "on.attributeChanged")}. custom elements: ${el("code", "on.connected")}, ${el("code", "on.disconnected")} and ${el("code",
"on.attributeChanged")}.
`), `),
el(example, { src: fileURL("./components/examples/events/live-cycle.js"), page_id }), el(example, { src: fileURL("./components/examples/events/live-cycle.js"), page_id }),
el("p").append(...T` el("p").append(...T`
For Custom elements, we will later introduce a way to replace ${el("code", "*Callback")} syntax with For Custom elements, we will later introduce a way to replace ${el("code", "*Callback")} syntax with
${el("code", "dde:*")} events. The ${el("code", "on.*")} functions then listen to the appropriate ${el("code", "dde:*")} events. The ${el("code", "on.*")} functions then listen to the appropriate
Custom Elements events (see ${el("a", { textContent: t`Custom element lifecycle callbacks | MDN`, ...references.mdn_customElement })}). Custom Elements events (see ${el("a", { textContent: t`Custom element lifecycle callbacks | MDN`,
...references.mdn_customElement })}).
`), `),
el("p").append(...T` el("p").append(...T`
But, in case of regular elemnets the ${el("a", references.mdn_mutation).append(el("code", "MutationObserver"), " | MDN")} But, in case of regular elemnets the ${el("a", references.mdn_mutation).append(el("code",
is internaly used to track these events. Therefore, there are some drawbacks: "MutationObserver"), " | MDN")} is internaly used to track these events. Therefore, there are some
drawbacks:
`), `),
el("ul").append( el("ul").append(
el("li").append(...T` el("li").append(...T`
@ -136,14 +139,15 @@ export function page({ pkg, info }){
native behaviour re-(dis)connecting element, use: native behaviour re-(dis)connecting element, use:
`), `),
el("ul").append( el("ul").append(
el("li").append(...T`custom ${el("code", "MutationObserver")} or logic in (dis)${el("code", "connectedCallback")} or…`), el("li").append(...T`custom ${el("code", "MutationObserver")} or logic in (dis)${el("code",
"connectedCallback")} or`),
el("li").append(...T`re-add ${el("code", "on.connected")} or ${el("code", "on.disconnected")} listeners.`) el("li").append(...T`re-add ${el("code", "on.connected")} or ${el("code", "on.disconnected")} listeners.`)
), ),
el(h3, t`Final notes`), el(h3, t`Final notes`),
el("p", t`The library also provides a method to dispatch the events.`), el("p", t`The library also provides a method to dispatch the events.`),
el(example, { src: fileURL("./components/examples/events/compareDispatch.js"), page_id }), el(example, { src: fileURL("./components/examples/events/compareDispatch.js"), page_id }),
el(mnemonic) el(mnemonic)
); );
} }

View File

@ -49,7 +49,7 @@ export function page({ pkg, info }){
programming. If we desire to solve the issue in a declarative manner, signals may be a viable approach. programming. If we desire to solve the issue in a declarative manner, signals may be a viable approach.
`), `),
el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }), el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }),
el(h3, t`Introducing signals`), el(h3, t`Introducing signals`),
el("p").append(...T` el("p").append(...T`
Lets re-introduce Lets re-introduce
@ -62,7 +62,7 @@ export function page({ pkg, info }){
`), `),
el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }), el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }),
el("p").append(...T` el("p").append(...T`
All this is just an example of All this is just an example of
${el("a", { textContent: t`Event-driven programming`, ...references.wiki_event_driven })} and ${el("a", { textContent: t`Event-driven programming`, ...references.wiki_event_driven })} and
${el("a", { textContent: t`Publishsubscribe pattern`, ...references.wiki_pubsub })} (compare for example ${el("a", { textContent: t`Publishsubscribe pattern`, ...references.wiki_pubsub })} (compare for example
with ${el("a", { textContent: t`fpubsub library`, ...references.fpubsub })}). All three parts can be in with ${el("a", { textContent: t`fpubsub library`, ...references.fpubsub })}). All three parts can be in
@ -109,8 +109,8 @@ export function page({ pkg, info }){
el("p").append(...T` el("p").append(...T`
To derived attribute based on value of signal variable just use the signal as a value of the attribute 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")} (${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 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")}. attributes ${el("code", "dataset")}, ${el("code", "ariaset")} and ${el("code", "classList")}.
`), `),
el("p").append(...T` el("p").append(...T`
For computation, you can use the derived signal (see above) like For computation, you can use the derived signal (see above) like

View File

@ -36,7 +36,7 @@ export function page({ pkg, info }){
`), `),
el(code, { src: fileURL("./components/examples/scopes/intro.js"), page_id }), 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("p").append(...T`The library therefore use ${el("em", t`scopes`)} to provide these functionalities.`),
el(h3, t`Scopes and hosts`), el(h3, t`Scopes and hosts`),
el("p").append(...T` el("p").append(...T`
The ${el("strong", "host")} is the name for the element representing the component. This is typically The ${el("strong", "host")} is the name for the element representing the component. This is typically

View File

@ -21,7 +21,7 @@ const references= {
/** observedAttributes on MDN */ /** observedAttributes on MDN */
mdn_observedAttributes: { mdn_observedAttributes: {
title: t`MDN documentation page for observedAttributes`, title: t`MDN documentation page for observedAttributes`,
href: "https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#responding_to_attribute_changes", href: "https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#responding_to_attribute_changes", // editorconfig-checker-disable-line
}, },
/** Custom Elements on MDN */ /** Custom Elements on MDN */
mdn_custom_elements: { mdn_custom_elements: {
@ -55,10 +55,10 @@ export function page({ pkg, info }){
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("h2", t`Using web components in combinantion with DDE`), el("h2", t`Using web components in combinantion with DDE`),
el("p").append(...T` el("p").append(...T`
The DDE library allows for use within ${el("a", references.mdn_web_components).append( el("strong", t`Web Components`) )} The DDE library allows for use within ${el("a", references.mdn_web_components).append( el("strong",
for dom-tree generation. However, in order to be able to use signals (possibly mapping to registered t`Web Components`) )} for dom-tree generation. However, in order to be able to use signals (possibly
${el("a", references.mdn_observedAttributes).append( el("code", "observedAttributes") )}) and additional mapping to registered ${el("a", references.mdn_observedAttributes).append( el("code", "observedAttributes")
functionality is (unfortunately) required to use helpers provided by the library. )}) and additional functionality is (unfortunately) required to use helpers provided by the library.
`), `),
el(code, { src: fileURL("./components/examples/customElement/intro.js"), page_id }), el(code, { src: fileURL("./components/examples/customElement/intro.js"), page_id }),
@ -82,8 +82,8 @@ export function page({ pkg, info }){
`), `),
el("p").append(...T` el("p").append(...T`
Also see the Life Cycle Events sections, very similarly we would like to use 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` })}. ${el("a", { textContent: t`DDE events`, href: "./p03-events.html", title: t`See events part of the library
To do it, the library provides function ${el("code", "customElementWithDDE")} documentation` })}. To do it, the library provides function ${el("code", "customElementWithDDE")}
`), `),
el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js"), page_id }), el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js"), page_id }),

View File

@ -21,7 +21,8 @@ export function thirdParty(){
}); });
// Array.from((new URL(location)).searchParams.entries()) // Array.from((new URL(location)).searchParams.entries())
// .forEach(([ key, value ])=> S.action(store, "set", key, value)); // .forEach(([ key, value ])=> S.action(store, "set", key, value));
// S.on(store, data=> history.replaceState("", "", "?"+(new URLSearchParams(JSON.parse(JSON.stringify(data)))).toString())); // S.on(store, data=> history.replaceState("", "",
// "?"+(new URLSearchParams(JSON.parse(JSON.stringify(data)))).toString()));
useStore(store_adapter, { useStore(store_adapter, {
onread(data){ onread(data){
Array.from(data.entries()) Array.from(data.entries())

View File

@ -15,6 +15,7 @@ export function fullNameComponent(){
on.disconnected(()=> console.log(fullNameComponent)) on.disconnected(()=> console.log(fullNameComponent))
); );
const style= { height: "80px", display: "block", fill: "currentColor" };
const elSVG= elNS("http://www.w3.org/2000/svg"); const elSVG= elNS("http://www.w3.org/2000/svg");
return el("div", { className }).append( return el("div", { className }).append(
el("h2", "Simple form:"), el("h2", "Simple form:"),
@ -29,7 +30,7 @@ export function fullNameComponent(){
": ", ": ",
el("#text", full_name) el("#text", full_name)
), ),
elSVG("svg", { viewBox: "0 0 240 80", style: { height: "80px", display: "block" } }).append( elSVG("svg", { viewBox: "0 0 240 80", style }).append(
//elSVG("style", { }) //elSVG("style", { })
elSVG("text", { x: 20, y: 35, textContent: "Text" }), elSVG("text", { x: 20, y: 35, textContent: "Text" }),
) )

View File

@ -17,22 +17,25 @@ const className= style.host(todosComponent).css`
/** @param {{ todos: string[] }} */ /** @param {{ todos: string[] }} */
export function todosComponent({ todos= [ "Task A" ] }= {}){ export function todosComponent({ todos= [ "Task A" ] }= {}){
let key= 0; let key= 0;
const todosO= S(new Map(), { const todosO= S( /** @type {Map<number, ddeSignal<string>>} */ (new Map()), {
/** @param {string} v */
add(v){ this.value.set(key++, S(v)); }, add(v){ this.value.set(key++, S(v)); },
/** @param {number} key */
remove(key){ S.clear(this.value.get(key)); this.value.delete(key); } remove(key){ S.clear(this.value.get(key)); this.value.delete(key); }
}); });
todos.forEach(text=> S.action(todosO, "add", text)); todos.forEach(text=> S.action(todosO, "add", text));
const name= "todoName"; const name= "todoName";
const onsubmitAdd= on("submit", event=> { const onsubmitAdd= on("submit", event=> {
const el= event.target.elements[name]; const el_form= /** @type {HTMLFormElement} */ (event.target);
const el= /** @type {HTMLInputElement} */ (el_form.elements[name]);
event.preventDefault(); event.preventDefault();
S.action(todosO, "add", el.value); S.action(todosO, "add", el.value);
el.value= ""; el.value= "";
}); });
const onremove= on("remove", event=> const onremove= on("remove", /** @param {CustomEvent<number>} event */
S.action(todosO, "remove", event.detail)); event=> S.action(todosO, "remove", event.detail));
return el("div", { className }).append( return el("div", { className }).append(
el("div").append( el("div").append(
el("h2", "Todos:"), el("h2", "Todos:"),
@ -60,19 +63,24 @@ export function todosComponent({ todos= [ "Task A" ] }= {}){
) )
} }
/** /**
* @param {{ textContent: ddeSignal<string>, value: ddeSignal<number> }} attrs
* @dispatchs {number} remove * @dispatchs {number} remove
* */ * */
function todoComponent({ textContent, value }){ function todoComponent({ textContent, value }){
const { host }= scope; const { host }= scope;
const dispatchRemove= /** @type {(data: number) => void} */
(dispatchEvent("remove", null, host));
const onclick= on("click", event=> { const onclick= on("click", event=> {
const value= Number(event.target.value); const el= /** @type {HTMLButtonElement} */ (event.target);
const value= Number(el.value);
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
dispatchEvent("remove")(host(), value); dispatchRemove(value);
}); });
const is_editable= S(false); const is_editable= S(false);
const onedited= on("change", ev=> { const onedited= on("change", ev=> {
textContent(ev.target.value); const el= /** @type {HTMLInputElement} */ (ev.target);
textContent(el.value);
is_editable(false); is_editable(false);
}); });
return el("li").append( return el("li").append(

View File

@ -38,7 +38,7 @@ export class CustomHTMLTestElement extends HTMLElement{
); );
} }
test= "A"; test= "A";
get name(){ return this.getAttribute("name"); } get name(){ return this.getAttribute("name"); }
set name(value){ this.setAttribute("name", value); } set name(value){ this.setAttribute("name", value); }
/** @attr pre-name */ /** @attr pre-name */

View File

@ -1,4 +1,9 @@
import { style, el, S } from './exports.js'; import { style, el, S } from './exports.js';
style.css`
:root{
color-scheme: dark light;
}
`;
document.head.append(style.element); document.head.append(style.element);
import { fullNameComponent } from './components/fullNameComponent.js'; import { fullNameComponent } from './components/fullNameComponent.js';
import { todosComponent } from './components/todosComponent.js'; import { todosComponent } from './components/todosComponent.js';

83
index.d.ts vendored
View File

@ -2,10 +2,10 @@ declare global{ /* ddeSignal */ }
type CustomElementTagNameMap= { '#text': Text, '#comment': Comment } type CustomElementTagNameMap= { '#text': Text, '#comment': Comment }
type SupportedElement= type SupportedElement=
HTMLElementTagNameMap[keyof HTMLElementTagNameMap] HTMLElementTagNameMap[keyof HTMLElementTagNameMap]
| SVGElementTagNameMap[keyof SVGElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap]
| MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap]
| CustomElementTagNameMap[keyof CustomElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap]
declare global { declare global {
type ddeComponentAttributes= Record<any, any> | undefined; type ddeComponentAttributes= Record<any, any> | undefined;
type ddeElementAddon<El extends SupportedElement | DocumentFragment>= (element: El)=> El | void; type ddeElementAddon<El extends SupportedElement | DocumentFragment>= (element: El)=> El | void;
@ -15,9 +15,12 @@ type AttrsModified= {
/** /**
* Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API). * Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API).
*/ */
style: string | Partial<CSSStyleDeclaration> | ddeSignal<string> | Partial<{ [K in keyof CSSStyleDeclaration]: ddeSignal<CSSStyleDeclaration[K]> }> style: string | Partial<CSSStyleDeclaration> | ddeSignal<string>
| 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. * 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>>, classList: Record<string,-1|0|1|boolean|ddeSignal<-1|0|1|boolean>>,
/** /**
@ -28,20 +31,32 @@ type AttrsModified= {
* Sets `aria-*` simiraly to `dataset` * Sets `aria-*` simiraly to `dataset`
* */ * */
ariaset: Record<string,string|ddeSignal<string>>, ariaset: Record<string,string|ddeSignal<string>>,
} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, string|ddeSignal<string>> & Record<`.${string}`, any> } & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, string|ddeSignal<string>>
& Record<`.${string}`, any>
type _fromElsInterfaces<EL extends SupportedElement>= Omit<EL, keyof AttrsModified>; type _fromElsInterfaces<EL extends SupportedElement>= Omit<EL, keyof AttrsModified>;
/** /**
* Just element attributtes * 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)). * 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. * There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives.
* @private * @private
*/ */
type ElementAttributes<T extends SupportedElement>= Partial<{ [K in keyof _fromElsInterfaces<T>]: _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]> } & AttrsModified> & Record<string, any>; type ElementAttributes<T extends SupportedElement>= Partial<{
export function classListDeclarative<El extends SupportedElement>(element: El, classList: AttrsModified["classList"]): El [K in keyof _fromElsInterfaces<T>]: _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
} & 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 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] 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; type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
type textContent= string | ddeSignal<string>; type textContent= string | ddeSignal<string>;
@ -68,7 +83,9 @@ export function el<
component: C, component: C,
attrs?: Parameters<C>[0] | textContent, attrs?: Parameters<C>[0] | textContent,
...addons: ddeElementAddon<ReturnType<C>>[] ...addons: ddeElementAddon<ReturnType<C>>[]
): ReturnType<C> extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? ReturnType<C> : ( ReturnType<C> extends ddeDocumentFragment ? ReturnType<C> : ddeHTMLElement ) ): ReturnType<C> extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? ReturnType<C>
: ( ReturnType<C> extends ddeDocumentFragment ? ReturnType<C> : ddeHTMLElement )
export { el as createElement } export { el as createElement }
export function elNS( export function elNS(
@ -88,7 +105,9 @@ export function elNS(
EL extends ( TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement ), EL extends ( TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement ),
>( >(
tag_name: TAG, tag_name: TAG,
attrs?: string | textContent | Partial<{ [key in keyof EL]: EL[key] | ddeSignal<EL[key]> | string | number | boolean }>, attrs?: string | textContent | Partial<{
[key in keyof EL]: EL[key] | ddeSignal<EL[key]> | string | number | boolean
}>,
...addons: ddeElementAddon<EL>[] ...addons: ddeElementAddon<EL>[]
)=> ddeMathMLElement )=> ddeMathMLElement
export function elNS( export function elNS(
@ -106,18 +125,27 @@ export function chainableAppend<EL extends SupportedElement>(el: EL): EL;
* */ * */
type simulateSlotsMapper= (body: HTMLSlotElement, el: HTMLElement)=> void; type simulateSlotsMapper= (body: HTMLSlotElement, el: HTMLElement)=> void;
/** Simulate slots for ddeComponents */ /** Simulate slots for ddeComponents */
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(root: EL, mapper?: simulateSlotsMapper): EL export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
root: EL,
mapper?: simulateSlotsMapper
): EL
/** /**
* Simulate slots in Custom Elements without using `shadowRoot`. * Simulate slots in Custom Elements without using `shadowRoot`.
* @param el Custom Element root element * @param el Custom Element root element
* @param body Body of the custom element * @param body Body of the custom element
* */ * */
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(el: HTMLElement, body: EL, mapper?: simulateSlotsMapper): EL export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
el: HTMLElement,
body: EL, mapper?: simulateSlotsMapper
): EL
export function dispatchEvent(name: keyof DocumentEventMap | string, options?: EventInit): export function dispatchEvent(name: keyof DocumentEventMap | string, options?: EventInit):
(element: SupportedElement, data?: any)=> void; (element: SupportedElement, data?: any)=> void;
export function dispatchEvent(name: keyof DocumentEventMap | string, options: EventInit | null, element: SupportedElement | (()=> SupportedElement)): export function dispatchEvent(
(data?: any)=> void; name: keyof DocumentEventMap | string,
options: EventInit | null,
element: SupportedElement | (()=> SupportedElement)
): (data?: any)=> void;
interface On{ interface On{
/** Listens to the DOM event. See {@link Document.addEventListener} */ /** Listens to the DOM event. See {@link Document.addEventListener} */
< <
@ -135,7 +163,7 @@ interface On{
listener: (this: El, ev: Event | CustomEvent ) => any, listener: (this: El, ev: Event | CustomEvent ) => any,
options?: AddEventListenerOptions options?: AddEventListenerOptions
) : EE; ) : 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 */ /** 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< connected<
EE extends ddeElementAddon<SupportedElement>, EE extends ddeElementAddon<SupportedElement>,
El extends ( EE extends ddeElementAddon<infer El> ? El : never ) El extends ( EE extends ddeElementAddon<infer El> ? El : never )
@ -143,7 +171,7 @@ interface On{
listener: (this: El, event: CustomEvent<El>) => any, listener: (this: El, event: CustomEvent<El>) => any,
options?: AddEventListenerOptions options?: AddEventListenerOptions
) : EE; ) : 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 */ /** 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< disconnected<
EE extends ddeElementAddon<SupportedElement>, EE extends ddeElementAddon<SupportedElement>,
El extends ( EE extends ddeElementAddon<infer El> ? El : never ) El extends ( EE extends ddeElementAddon<infer El> ? El : never )
@ -151,7 +179,7 @@ interface On{
listener: (this: El, event: CustomEvent<void>) => any, listener: (this: El, event: CustomEvent<void>) => any,
options?: AddEventListenerOptions options?: AddEventListenerOptions
) : EE; ) : 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 */ /** 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< attributeChanged<
EE extends ddeElementAddon<SupportedElement>, EE extends ddeElementAddon<SupportedElement>,
El extends ( EE extends ddeElementAddon<infer El> ? El : never ) El extends ( EE extends ddeElementAddon<infer El> ? El : never )
@ -162,7 +190,12 @@ interface On{
} }
export const on: On; export const on: On;
type Scope= { scope: Node | Function | Object, host: ddeElementAddon<any>, custom_element: false | HTMLElement, prevent: boolean } 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}) */ /** Current scope created last time the `el(Function)` was invoke. (Or {@link scope.push}) */
export const scope: { export const scope: {
current: Scope, current: Scope,
@ -176,7 +209,7 @@ export const scope: {
* `scope.host(on.connected(console.log))`. * `scope.host(on.connected(console.log))`.
* */ * */
host: (...addons: ddeElementAddon<SupportedElement>[])=> HTMLElement, host: (...addons: ddeElementAddon<SupportedElement>[])=> HTMLElement,
state: Scope[], state: Scope[],
/** Adds new child scope. All attributes are inherited by default. */ /** Adds new child scope. All attributes are inherited by default. */
push(scope: Partial<Scope>): ReturnType<Array<Scope>["push"]>, push(scope: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
@ -202,12 +235,12 @@ export function observedAttributes(custom_element: HTMLElement): Record<string,
/* TypeScript MEH */ /* TypeScript MEH */
declare global{ declare global{
type ddeAppend<el>= (...nodes: (Node | string)[])=> el; type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; } interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; }
interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; } interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; }
interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; } interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; }
interface ddeMathMLElement extends MathMLElement{ append: ddeAppend<ddeMathMLElement>; } interface ddeMathMLElement extends MathMLElement{ append: ddeAppend<ddeMathMLElement>; }
interface ddeHTMLElementTagNameMap { interface ddeHTMLElementTagNameMap {
"a": ddeHTMLAnchorElement; "a": ddeHTMLAnchorElement;
"area": ddeHTMLAreaElement; "area": ddeHTMLAreaElement;
@ -350,6 +383,7 @@ declare global{
} }
} }
// editorconfig-checker-disable
interface ddeHTMLAnchorElement extends HTMLAnchorElement{ append: ddeAppend<ddeHTMLAnchorElement>; } interface ddeHTMLAnchorElement extends HTMLAnchorElement{ append: ddeAppend<ddeHTMLAnchorElement>; }
interface ddeHTMLAreaElement extends HTMLAreaElement{ append: ddeAppend<ddeHTMLAreaElement>; } interface ddeHTMLAreaElement extends HTMLAreaElement{ append: ddeAppend<ddeHTMLAreaElement>; }
interface ddeHTMLAudioElement extends HTMLAudioElement{ append: ddeAppend<ddeHTMLAudioElement>; } interface ddeHTMLAudioElement extends HTMLAudioElement{ append: ddeAppend<ddeHTMLAudioElement>; }
@ -477,3 +511,4 @@ interface ddeSVGTitleElement extends SVGTitleElement{ append: ddeAppend<ddeSVGTi
interface ddeSVGTSpanElement extends SVGTSpanElement{ append: ddeAppend<ddeSVGTSpanElement>; } interface ddeSVGTSpanElement extends SVGTSpanElement{ append: ddeAppend<ddeSVGTSpanElement>; }
interface ddeSVGUseElement extends SVGUseElement{ append: ddeAppend<ddeSVGUseElement>; } interface ddeSVGUseElement extends SVGUseElement{ append: ddeAppend<ddeSVGUseElement>; }
interface ddeSVGViewElement extends SVGViewElement{ append: ddeAppend<ddeSVGViewElement>; } interface ddeSVGViewElement extends SVGViewElement{ append: ddeAppend<ddeSVGViewElement>; }
// editorconfig-checker-enable

View File

@ -29,7 +29,7 @@ export function register(dom){
export function unregister(){ export function unregister(){
if(!dom_last) if(!dom_last)
return false; return false;
Object.assign(env, env_bk); Object.assign(env, env_bk);
env_bk= {}; env_bk= {};
dom_last= undefined; dom_last= undefined;

19
package-lock.json generated
View File

@ -11,6 +11,7 @@
"devDependencies": { "devDependencies": {
"@size-limit/preset-small-lib": "~11.0", "@size-limit/preset-small-lib": "~11.0",
"dts-bundler": "~0.1", "dts-bundler": "~0.1",
"editorconfig-checker": "^6.0.0",
"esbuild": "~0.24", "esbuild": "~0.24",
"jsdom": "~25.0", "jsdom": "~25.0",
"jshint": "~2.13", "jshint": "~2.13",
@ -1424,6 +1425,24 @@
"typescript": "^2.4.0" "typescript": "^2.4.0"
} }
}, },
"node_modules/editorconfig-checker": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/editorconfig-checker/-/editorconfig-checker-6.0.0.tgz",
"integrity": "sha512-uyTOwLJzR/k7ugiu7ITjCzkLKBhXeirQZ8hGlUkt1u/hq2Qu1E8EslgFZDN+lxZoQc97eiI87sUFgVILK4P+YQ==",
"dev": true,
"license": "MIT",
"bin": {
"ec": "dist/index.js",
"editorconfig-checker": "dist/index.js"
},
"engines": {
"node": ">=20.11.0"
},
"funding": {
"type": "buymeacoffee",
"url": "https://www.buymeacoffee.com/mstruebing"
}
},
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",

View File

@ -83,9 +83,7 @@
"modifyEsbuildConfig": { "modifyEsbuildConfig": {
"platform": "browser" "platform": "browser"
}, },
"scripts": { "scripts": {},
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [ "keywords": [
"dom", "dom",
"javascript", "javascript",
@ -95,6 +93,7 @@
"devDependencies": { "devDependencies": {
"@size-limit/preset-small-lib": "~11.0", "@size-limit/preset-small-lib": "~11.0",
"dts-bundler": "~0.1", "dts-bundler": "~0.1",
"editorconfig-checker": "~6.0",
"esbuild": "~0.24", "esbuild": "~0.24",
"jsdom": "~25.0", "jsdom": "~25.0",
"jshint": "~2.13", "jshint": "~2.13",

2
signals.d.ts vendored
View File

@ -55,7 +55,7 @@ interface signal{
* */ * */
el<S extends any>(signal: Signal<S, any>, el: (v: S)=> Element | Element[] | DocumentFragment): DocumentFragment; el<S extends any>(signal: Signal<S, any>, el: (v: S)=> Element | Element[] | DocumentFragment): DocumentFragment;
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>; observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
} }
export const signal: signal; export const signal: signal;
export const S: signal; export const S: signal;

View File

@ -13,11 +13,11 @@ function setDeleteAttr(obj, prop, val){
For some native attrs you can unset only to set empty string. For some native attrs you can unset only to set empty string.
This can be confusing as it is seen in inspector `<… id=""`. This can be confusing as it is seen in inspector `<… id=""`.
Options: Options:
1. Leave it, as it is native behaviour 1. Leave it, as it is native behaviour
2. Sets as empty string and removes the corresponding attribute when also has empty string 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 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) 4. Point 2. with checks for coincidence (e.g. use special string)
*/ */
Reflect.set(obj, prop, val); Reflect.set(obj, prop, val);
if(!isUndef(val)) return; if(!isUndef(val)) return;
Reflect.deleteProperty(obj, prop); Reflect.deleteProperty(obj, prop);

View File

@ -10,13 +10,13 @@ const scopes= [ {
export const scope= { export const scope= {
get current(){ return scopes[scopes.length-1]; }, get current(){ return scopes[scopes.length-1]; },
get host(){ return this.current.host; }, get host(){ return this.current.host; },
preventDefault(){ preventDefault(){
const { current }= this; const { current }= this;
current.prevent= true; current.prevent= true;
return current; return current;
}, },
get state(){ return [ ...scopes ]; }, get state(){ return [ ...scopes ]; },
push(s= {}){ return scopes.push(Object.assign({}, this.current, { prevent: false }, s)); }, push(s= {}){ return scopes.push(Object.assign({}, this.current, { prevent: false }, s)); },
pushRoot(){ return scopes.push(scopes[0]); }, pushRoot(){ return scopes.push(scopes[0]); },
@ -25,9 +25,11 @@ export const scope= {
return scopes.pop(); return scopes.pop();
}, },
}; };
// following chainableAppend implementation is OK as the ElementPrototype.append description already is { writable: true, enumerable: true, configurable: true } // following chainableAppend implementation is OK as the ElementPrototype.append description already is { writable: true, enumerable: true, configurable: true } // editorconfig-checker-disable-line
function append(...els){ this.appendOriginal(...els); return this; } function append(...els){ this.appendOriginal(...els); return this; }
export function chainableAppend(el){ if(el.append===append) return el; el.appendOriginal= el.append; el.append= append; return el; } export function chainableAppend(el){
if(el.append===append) return el; el.appendOriginal= el.append; el.append= append; return el;
}
let namespace; let namespace;
export function createElement(tag, attributes, ...addons){ export function createElement(tag, attributes, ...addons){
/* jshint maxcomplexity: 15 */ /* jshint maxcomplexity: 15 */
@ -40,7 +42,9 @@ export function createElement(tag, attributes, ...addons){
switch(true){ switch(true){
case typeof tag==="function": { case typeof tag==="function": {
scoped= 1; scoped= 1;
scope.push({ scope: tag, host: (...c)=> c.length ? (scoped===1 ? addons.unshift(...c) : c.forEach(c=> c(el_host)), undefined) : el_host }); const host= (...c)=> !c.length ? el_host :
(scoped===1 ? addons.unshift(...c) : c.forEach(c=> c(el_host)), undefined);
scope.push({ scope: tag, host });
el= tag(attributes || undefined); el= tag(attributes || undefined);
const is_fragment= el instanceof env.F; const is_fragment= el instanceof env.F;
if(el.nodeName==="#comment") break; if(el.nodeName==="#comment") break;
@ -108,7 +112,9 @@ export function simulateSlots(element, root, mapper){
} }
function simulateSlotReplace(slot, element, mapper){ function simulateSlotReplace(slot, element, mapper){
if(mapper) mapper(slot, element); if(mapper) mapper(slot, element);
try{ slot.replaceWith(assign(element, { className: [ element.className, slot.className ], dataset: { ...slot.dataset } })); } try{ slot.replaceWith(assign(element, {
className: [ element.className, slot.className ],
dataset: { ...slot.dataset } })); }
catch(_){ slot.replaceWith(element); } catch(_){ slot.replaceWith(element); }
} }
/** /**
@ -141,7 +147,7 @@ const { setDeleteAttr }= env;
export function assign(element, ...attributes){ export function assign(element, ...attributes){
if(!attributes.length) return element; if(!attributes.length) return element;
assign_context.set(element, assignContext(element, this)); assign_context.set(element, assignContext(element, this));
for(const [ key, value ] of Object.entries(Object.assign({}, ...attributes))) for(const [ key, value ] of Object.entries(Object.assign({}, ...attributes)))
assignAttribute.call(this, element, key, value); assignAttribute.call(this, element, key, value);
assign_context.delete(element); assign_context.delete(element);
@ -150,7 +156,7 @@ export function assign(element, ...attributes){
export function assignAttribute(element, key, value){ export function assignAttribute(element, key, value){
const { setRemoveAttr, s }= assignContext(element, this); const { setRemoveAttr, s }= assignContext(element, this);
const _this= this; const _this= this;
value= s.processReactiveAttribute(element, key, value, value= s.processReactiveAttribute(element, key, value,
(key, value)=> assignAttribute.call(_this, element, key, value)); (key, value)=> assignAttribute.call(_this, element, key, value));
const [ k ]= key; const [ k ]= key;
@ -216,7 +222,9 @@ function getPropDescriptor(p, key){
return des; return des;
} }
/** @template {Record<any, any>} T @param {object} s @param {T} obj @param {(param: [ keyof T, T[keyof T] ])=> void} cb */ /**
* @template {Record<any, any>} T @param {object} s @param {T} obj @param {(param: [ keyof T, T[keyof T] ])=> void} cb
* */
function forEachEntries(s, obj, cb){ function forEachEntries(s, obj, cb){
if(typeof obj !== "object" || obj===null) return; if(typeof obj !== "object" || obj===null) return;
return Object.entries(obj).forEach(function process([ key, val ]){ return Object.entries(obj).forEach(function process([ key, val ]){
@ -227,6 +235,9 @@ function forEachEntries(s, obj, cb){
} }
function attrArrToStr(attr){ return Array.isArray(attr) ? attr.filter(Boolean).join(" ") : attr; } function attrArrToStr(attr){ return Array.isArray(attr) ? attr.filter(Boolean).join(" ") : attr; }
function setRemove(obj, prop, key, val){ return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, attrArrToStr(val)); } function setRemove(obj, prop, key, val){
function setRemoveNS(obj, prop, key, val, ns= null){ return obj[ (isUndef(val) ? "remove" : "set") + prop + "NS" ](ns, key, attrArrToStr(val)); } return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, attrArrToStr(val)); }
function setDelete(obj, key, val){ Reflect.set(obj, key, val); if(!isUndef(val)) return; return Reflect.deleteProperty(obj, key); } function setRemoveNS(obj, prop, key, val, ns= null){
return obj[ (isUndef(val) ? "remove" : "set") + prop + "NS" ](ns, key, attrArrToStr(val)); }
function setDelete(obj, key, val){
Reflect.set(obj, key, val); if(!isUndef(val)) return; return Reflect.deleteProperty(obj, key); }

View File

@ -103,10 +103,10 @@ function connectionsChangesObserverConstructor(){
for(const element of addedNodes){ for(const element of addedNodes){
if(is_root) collectChildren(element).then(observerAdded); if(is_root) collectChildren(element).then(observerAdded);
if(!store.has(element)) continue; if(!store.has(element)) continue;
const ls= store.get(element); const ls= store.get(element);
if(!ls.length_c) continue; if(!ls.length_c) continue;
element.dispatchEvent(new Event(evc)); element.dispatchEvent(new Event(evc));
ls.connected= new WeakSet(); ls.connected= new WeakSet();
ls.length_c= 0; ls.length_c= 0;
@ -120,7 +120,7 @@ function connectionsChangesObserverConstructor(){
for(const element of removedNodes){ for(const element of removedNodes){
if(is_root) collectChildren(element).then(observerRemoved); if(is_root) collectChildren(element).then(observerRemoved);
if(!store.has(element)) continue; if(!store.has(element)) continue;
const ls= store.get(element); const ls= store.get(element);
if(!ls.length_d) continue; if(!ls.length_d) continue;
(globalThis.queueMicrotask || setTimeout)(dispatchRemove(element)); (globalThis.queueMicrotask || setTimeout)(dispatchRemove(element));

View File

@ -64,9 +64,9 @@ on.attributeChanged= function(listener, options){
element.addEventListener(eva, listener, options); element.addEventListener(eva, listener, options);
if(element[keyLTE] || els_attribute_store.has(element)) if(element[keyLTE] || els_attribute_store.has(element))
return element; return element;
if(!env.M) return element; if(!env.M) return element;
const observer= new env.M(function(mutations){ const observer= new env.M(function(mutations){
for(const { attributeName, target } of mutations) for(const { attributeName, target } of mutations)
target.dispatchEvent( target.dispatchEvent(
@ -77,4 +77,4 @@ on.attributeChanged= function(listener, options){
//TODO: clean up when element disconnected //TODO: clean up when element disconnected
return element; return element;
}; };
}; };

View File

@ -21,7 +21,7 @@ export function signal(value, actions){
if(typeof value!=="function") if(typeof value!=="function")
return create(false, value, actions); return create(false, value, actions);
if(isSignal(value)) return value; if(isSignal(value)) return value;
const out= create(true); const out= create(true);
const contextReWatch= function(){ const contextReWatch= function(){
const [ origin, ...deps_old ]= deps.get(contextReWatch); const [ origin, ...deps_old ]= deps.get(contextReWatch);
@ -77,11 +77,11 @@ signal.clear= function(...signals){
o.listeners.forEach(l=> { o.listeners.forEach(l=> {
o.listeners.delete(l); o.listeners.delete(l);
if(!deps.has(l)) return; if(!deps.has(l)) return;
const ls= deps.get(l); const ls= deps.get(l);
ls.delete(s); ls.delete(s);
if(ls.size>1) return; if(ls.size>1) return;
s.clear(...ls); s.clear(...ls);
deps.delete(l); deps.delete(l);
}); });

View File

@ -21,7 +21,7 @@ export function signal(value, actions){
if(typeof value!=="function") if(typeof value!=="function")
return create(false, value, actions); return create(false, value, actions);
if(isSignal(value)) return value; if(isSignal(value)) return value;
const out= create(true); const out= create(true);
const contextReWatch= function(){ const contextReWatch= function(){
const [ origin, ...deps_old ]= deps.get(contextReWatch); const [ origin, ...deps_old ]= deps.get(contextReWatch);
@ -77,11 +77,11 @@ signal.clear= function(...signals){
o.listeners.forEach(l=> { o.listeners.forEach(l=> {
o.listeners.delete(l); o.listeners.delete(l);
if(!deps.has(l)) return; if(!deps.has(l)) return;
const ls= deps.get(l); const ls= deps.get(l);
ls.delete(s); ls.delete(s);
if(ls.size>1) return; if(ls.size>1) return;
s.clear(...ls); s.clear(...ls);
deps.delete(l); deps.delete(l);
}); });

View File

@ -1,7 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"emitDeclarationOnly": true, "emitDeclarationOnly": true,
"declaration": true, "declaration": true,
"declarationDir": "dist" "declarationDir": "dist"
} }
} }