1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2024-11-23 09:09:38 +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***
@ -10,11 +12,14 @@ document.body.append(
el("p", "See some syntax examples here:"),
el("ul").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(
"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(component, { textContent: "A component", className: "example" }, on("change", console.log))
@ -35,18 +40,22 @@ function component({ textContent, className }){
}
```
# 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
- 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)
- [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)
- 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)
- [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)
- [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)
## Why an another one?
This library falls somewhere between van/hyperscript and [solid-js](https://github.com/solidjs/solid) in terms of size, complexity,
and usability.
This library falls somewhere between van/hyperscript and [solid-js](https://github.com/solidjs/solid) in terms of size,
complexity, and usability.
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
@ -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/`…)
## 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)

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);
echoVariant(file_dts_out);
s.echo(bundleDTS(file_dts)).to(file_dts_out);
await toDDE(out, file_root);
}
$.exit(0);
@ -43,10 +43,13 @@ $.api("", true)
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");
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}\``,
"(()=> {",

View File

@ -1,5 +1,5 @@
#!/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("Preparing…");
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();
// 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' }) }
dom= new JSDOM(html, options);
const window= dom.window;
return {

View File

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

View File

@ -51,9 +51,9 @@ let is_registered= {};
/** @param {string} page_id */
function registerClientPart(page_id){
if(is_registered[page_id]) return;
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(
new URL("./code.js.js", import.meta.url),

View File

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

View File

@ -2,7 +2,7 @@ import {
customElementRender,
customElementWithDDE,
observedAttributes,
el, on, scope
el, on, scope
} from "deka-dom-el";
import { S } from "deka-dom-el/signals";
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 elMath= elNS("http://www.w3.org/1998/Math/MathML");
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
elMath("math") // see https://developer.mozilla.org/en-US/docs/Web/MathML and https://developer.mozilla.org/en-US/docs/Web/MathML/Global_attributes
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 // editorconfig-checker-disable-line
);
console.log(

View File

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

View File

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

View File

@ -4,25 +4,32 @@ import { mnemonicUl } from "../mnemonicUl.html.js";
export function mnemonic(){
return mnemonicUl().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("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("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("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("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("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("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(){
return mnemonicUl().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("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("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("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("code", "el(<...>, <...>, ...<addons>)"), " — see following page"
el("code", "el(<...>, <...>, ...<addons>)"),
" — see following page"
),
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(){
return mnemonicUl().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("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."
),
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("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(){
return mnemonicUl().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("code", "scope.host()"), " — get current component reference"
el("code", "scope.host()"),
" — get current component reference"
),
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("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("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("code", "S.clear(...<signals>)"), " — off and clear signals",
el("code", "S.clear(...<signals>)"),
" — off and clear signals",
),
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("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("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;
return el(simplePage, { info, pkg }).append(
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("p").append(t`
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 })}
or ${el("a", { textContent: t`MVC`, ...references.w_mvc })}.
`),
el(h3, t`Organization of the documentation`),
);
}

View File

@ -69,7 +69,7 @@ function metaFacebook({ name, description, homepage }){
function iconGitHub(){
const el= elNS("http://www.w3.org/2000/svg");
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",
"c -8.836,0 -16,7.163 -16,16",
"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(h3, t`Creating element(s) (with custom attributes)`),
el("p").append(...T`
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`
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
all available IDLs for example for paragraphs, see ${el("a", { textContent: "HTMLParagraphElement", ...references.mdn_p })}.
Moreover, the ${el("code", "assign")} provides a way to sets declaratively some convenient properties:
all available IDLs for example for paragraphs, see ${el("a", { textContent: "HTMLParagraphElement",
...references.mdn_p })}. Moreover, the ${el("code", "assign")} provides a way to sets declaratively
some convenient properties:
`),
el("ul").append(
el("li").append(...T`
@ -114,7 +115,7 @@ export function page({ pkg, info }){
${el("code", "classListDeclarative")}.
`),
el(example, { src: fileURL("./components/examples/elements/dekaAssign.js"), page_id }),
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(example, { src: fileURL("./components/examples/elements/nativeAppend.js"), page_id }),
@ -123,8 +124,8 @@ export function page({ pkg, info }){
parent element.
`),
el(example, { src: fileURL("./components/examples/elements/dekaAppend.js"), page_id }),
el(h3, t`Basic (state-less) components`),
el("p").append(...T`
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:
`),
el(example, { src: fileURL("./components/examples/elements/dekaElNS.js"), page_id }),
el(mnemonic)
);
}

View File

@ -29,7 +29,7 @@ const references= {
},
/** Custom Element lifecycle callbacks */
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 */
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
${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(h3, t`Events and listenners`),
el("p").append(...T`
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`
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("p").append(...T`
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
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`
But, in case of regular elemnets the ${el("a", references.mdn_mutation).append(el("code", "MutationObserver"), " | MDN")}
is internaly used to track these events. Therefore, there are some drawbacks:
But, in case of regular elemnets the ${el("a", references.mdn_mutation).append(el("code",
"MutationObserver"), " | MDN")} is internaly used to track these events. Therefore, there are some
drawbacks:
`),
el("ul").append(
el("li").append(...T`
@ -136,14 +139,15 @@ export function page({ pkg, info }){
native behaviour re-(dis)connecting element, use:
`),
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(h3, t`Final notes`),
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(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.
`),
el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }),
el(h3, t`Introducing signals`),
el("p").append(...T`
Lets re-introduce
@ -62,7 +62,7 @@ export function page({ pkg, info }){
`),
el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }),
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`Publishsubscribe pattern`, ...references.wiki_pubsub })} (compare for example
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`
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")}.
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")}.
`),
el("p").append(...T`
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("p").append(...T`The library therefore use ${el("em", t`scopes`)} to provide these functionalities.`),
el(h3, t`Scopes and hosts`),
el("p").append(...T`
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 */
mdn_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 */
mdn_custom_elements: {
@ -55,10 +55,10 @@ export function page({ pkg, info }){
return el(simplePage, { info, pkg }).append(
el("h2", t`Using web components in combinantion with DDE`),
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.
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.
`),
el(code, { src: fileURL("./components/examples/customElement/intro.js"), page_id }),
@ -82,8 +82,8 @@ export function page({ pkg, info }){
`),
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("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 }),

View File

@ -21,7 +21,8 @@ export function thirdParty(){
});
// Array.from((new URL(location)).searchParams.entries())
// .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, {
onread(data){
Array.from(data.entries())

View File

@ -15,6 +15,7 @@ export function fullNameComponent(){
on.disconnected(()=> console.log(fullNameComponent))
);
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:"),
@ -29,7 +30,7 @@ export function fullNameComponent(){
": ",
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("text", { x: 20, y: 35, textContent: "Text" }),
)

View File

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

View File

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

View File

@ -1,4 +1,9 @@
import { style, el, S } from './exports.js';
style.css`
:root{
color-scheme: dark light;
}
`;
document.head.append(style.element);
import { fullNameComponent } from './components/fullNameComponent.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 SupportedElement=
HTMLElementTagNameMap[keyof HTMLElementTagNameMap]
| SVGElementTagNameMap[keyof SVGElementTagNameMap]
| MathMLElementTagNameMap[keyof MathMLElementTagNameMap]
| CustomElementTagNameMap[keyof CustomElementTagNameMap]
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>= (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).
*/
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>>,
/**
@ -28,20 +31,32 @@ type AttrsModified= {
* Sets `aria-*` simiraly to `dataset`
* */
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>;
/**
* 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.
* @private
*/
type ElementAttributes<T extends SupportedElement>= Partial<{ [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
type ElementAttributes<T extends SupportedElement>= Partial<{
[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 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 textContent= string | ddeSignal<string>;
@ -68,7 +83,9 @@ export function el<
component: C,
attrs?: Parameters<C>[0] | textContent,
...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 function elNS(
@ -88,7 +105,9 @@ export function elNS(
EL extends ( TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement ),
>(
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>[]
)=> ddeMathMLElement
export function elNS(
@ -106,18 +125,27 @@ export function chainableAppend<EL extends SupportedElement>(el: EL): EL;
* */
type simulateSlotsMapper= (body: HTMLSlotElement, el: HTMLElement)=> void;
/** 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`.
* @param el Custom Element root 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):
(element: SupportedElement, data?: any)=> void;
export function dispatchEvent(name: keyof DocumentEventMap | string, options: EventInit | null, element: SupportedElement | (()=> 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} */
<
@ -135,7 +163,7 @@ interface On{
listener: (this: El, 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 */
/** 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 )
@ -143,7 +171,7 @@ interface On{
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 */
/** 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 )
@ -151,7 +179,7 @@ interface On{
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 */
/** 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 )
@ -162,7 +190,12 @@ interface 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}) */
export const scope: {
current: Scope,
@ -176,7 +209,7 @@ export const scope: {
* `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"]>,
@ -202,12 +235,12 @@ export function observedAttributes(custom_element: HTMLElement): Record<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;
@ -350,6 +383,7 @@ declare global{
}
}
// editorconfig-checker-disable
interface ddeHTMLAnchorElement extends HTMLAnchorElement{ append: ddeAppend<ddeHTMLAnchorElement>; }
interface ddeHTMLAreaElement extends HTMLAreaElement{ append: ddeAppend<ddeHTMLAreaElement>; }
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 ddeSVGUseElement extends SVGUseElement{ append: ddeAppend<ddeSVGUseElement>; }
interface ddeSVGViewElement extends SVGViewElement{ append: ddeAppend<ddeSVGViewElement>; }
// editorconfig-checker-enable

View File

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

19
package-lock.json generated
View File

@ -11,6 +11,7 @@
"devDependencies": {
"@size-limit/preset-small-lib": "~11.0",
"dts-bundler": "~0.1",
"editorconfig-checker": "^6.0.0",
"esbuild": "~0.24",
"jsdom": "~25.0",
"jshint": "~2.13",
@ -1424,6 +1425,24 @@
"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": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",

View File

@ -83,9 +83,7 @@
"modifyEsbuildConfig": {
"platform": "browser"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"scripts": {},
"keywords": [
"dom",
"javascript",
@ -95,6 +93,7 @@
"devDependencies": {
"@size-limit/preset-small-lib": "~11.0",
"dts-bundler": "~0.1",
"editorconfig-checker": "~6.0",
"esbuild": "~0.24",
"jsdom": "~25.0",
"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;
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
}
export const signal: 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.
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)
*/
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);

View File

@ -10,13 +10,13 @@ const scopes= [ {
export const scope= {
get current(){ return scopes[scopes.length-1]; },
get host(){ return this.current.host; },
preventDefault(){
const { current }= this;
current.prevent= true;
return current;
},
get state(){ return [ ...scopes ]; },
push(s= {}){ return scopes.push(Object.assign({}, this.current, { prevent: false }, s)); },
pushRoot(){ return scopes.push(scopes[0]); },
@ -25,9 +25,11 @@ export const scope= {
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; }
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;
export function createElement(tag, attributes, ...addons){
/* jshint maxcomplexity: 15 */
@ -40,7 +42,9 @@ export function createElement(tag, attributes, ...addons){
switch(true){
case typeof tag==="function": {
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);
const is_fragment= el instanceof env.F;
if(el.nodeName==="#comment") break;
@ -108,7 +112,9 @@ export function simulateSlots(element, root, mapper){
}
function simulateSlotReplace(slot, element, mapper){
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); }
}
/**
@ -141,7 +147,7 @@ const { setDeleteAttr }= env;
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)))
assignAttribute.call(this, element, key, value);
assign_context.delete(element);
@ -150,7 +156,7 @@ export function assign(element, ...attributes){
export function assignAttribute(element, key, value){
const { setRemoveAttr, s }= assignContext(element, this);
const _this= this;
value= s.processReactiveAttribute(element, key, value,
(key, value)=> assignAttribute.call(_this, element, key, value));
const [ k ]= key;
@ -216,7 +222,9 @@ function getPropDescriptor(p, key){
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){
if(typeof obj !== "object" || obj===null) return;
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 setRemove(obj, prop, key, val){ return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, attrArrToStr(val)); }
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); }
function setRemove(obj, prop, key, val){
return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, attrArrToStr(val)); }
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){
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= new WeakSet();
ls.length_c= 0;
@ -120,7 +120,7 @@ function connectionsChangesObserverConstructor(){
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));

View File

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

View File

@ -21,7 +21,7 @@ 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);
@ -77,11 +77,11 @@ signal.clear= function(...signals){
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);
});

View File

@ -21,7 +21,7 @@ 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);
@ -77,11 +77,11 @@ signal.clear= function(...signals){
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);
});

View File

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