mirror of
https://github.com/jaandrle/deka-dom-el
synced 2024-11-24 01:29:36 +01:00
💥 rename signals to observables
This commit is contained in:
parent
fc4037f3eb
commit
58b73dcbc7
@ -35,8 +35,8 @@ 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
|
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 — whats going on behind the scenes | by Ryan Hoffnan | ITNEXT](https://itnext.io/signals-whats-going-on-behind-the-scenes-ec858589ea63) or [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 | 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) or [Observer pattern - Wikipedia](https://en.wikipedia.org/wiki/Observer_pattern)).
|
||||||
|
|
||||||
## 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 JSX and virtual DOM.](https://github.com/jaandrle/dollar_dom_component)
|
||||||
@ -59,7 +59,7 @@ hopefully, help in integrating the library into existing projects.
|
|||||||
|
|
||||||
To balance these requirements, numerous compromises have been made. To summarize:
|
To balance these requirements, numerous compromises have been made. To summarize:
|
||||||
- [ ] Library size: 10–15kB minified (the original goal was a maximum of 10kB)
|
- [ ] Library size: 10–15kB minified (the original goal was a maximum of 10kB)
|
||||||
- [x] Optional use of *signals* with the ability to register *your own signals implementation*
|
- [x] Optional use of *observables* with the ability to register *your own signals/observables implementation*
|
||||||
- [x] *No build step required*
|
- [x] *No build step required*
|
||||||
- [x] Preference for a *declarative/functional* approach
|
- [x] Preference for a *declarative/functional* approach
|
||||||
- [x] Focus on zero/minimal dependencies
|
- [x] Focus on zero/minimal dependencies
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env -S npx nodejsscript
|
#!/usr/bin/env -S npx nodejsscript
|
||||||
import { bundle as bundleDTS } from "dts-bundler";
|
import { bundle as bundleDTS } from "dts-bundler";
|
||||||
const files= [ "index", "index-with-signals" ];
|
const files= [ "index", "index-with-observables" ];
|
||||||
const filesOut= (file, mark= "esm")=> "dist/"+file.replace("index", mark);
|
const filesOut= (file, mark= "esm")=> "dist/"+file.replace("index", mark);
|
||||||
const css= echo.css`
|
const css= echo.css`
|
||||||
.info{ color: gray; }
|
.info{ color: gray; }
|
||||||
|
27
dist/dde-with-observables.js
vendored
Normal file
27
dist/dde-with-observables.js
vendored
Normal file
File diff suppressed because one or more lines are too long
26
dist/dde-with-signals.js
vendored
26
dist/dde-with-signals.js
vendored
File diff suppressed because one or more lines are too long
4
dist/dde.js
vendored
4
dist/dde.js
vendored
File diff suppressed because one or more lines are too long
@ -153,10 +153,10 @@ export const scope: {
|
|||||||
preventDefault<T extends boolean>(prevent: T): T,
|
preventDefault<T extends boolean>(prevent: T): T,
|
||||||
/**
|
/**
|
||||||
* This represents reference to the current host element — `scope.host()`.
|
* This represents reference to the current host element — `scope.host()`.
|
||||||
* It can be also used to register Addon (function to be called when component is initized)
|
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||||
* — `scope.host(on.connected(console.log))`.
|
* — `scope.host(on.connected(console.log))`.
|
||||||
* */
|
* */
|
||||||
host: ddeElementAddon<any>,
|
host: (...addons: ddeElementAddon<any>[])=> 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. */
|
||||||
@ -444,19 +444,19 @@ declare global{
|
|||||||
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>; }
|
||||||
}
|
}
|
||||||
export type Signal<V, A>= (set?: V)=> V & A;
|
export type Observable<V, A>= (set?: V)=> V & A;
|
||||||
type Action<V>= (this: { value: V }, ...a: any[])=> typeof S._ | void;
|
type Action<V>= (this: { value: V }, ...a: any[])=> typeof observable._ | void;
|
||||||
type SymbolOnclear= Symbol;
|
type SymbolOnclear= Symbol;
|
||||||
type SymbolSignal= Symbol;
|
type SymbolObservable= Symbol;
|
||||||
type Actions<V>= Record<string, Action<V>>;
|
type Actions<V>= Record<string, Action<V>>;
|
||||||
interface S {
|
interface observable{
|
||||||
_: Symbol
|
_: Symbol
|
||||||
/**
|
/**
|
||||||
* Simple example:
|
* Simple example:
|
||||||
* ```js
|
* ```js
|
||||||
* const hello= S("Hello Signal");
|
* const hello= S("Hello Observable");
|
||||||
* ```
|
* ```
|
||||||
* …simple todo signal:
|
* …simple todo observable:
|
||||||
* ```js
|
* ```js
|
||||||
* const todos= S([], {
|
* const todos= S([], {
|
||||||
* add(v){ this.value.push(S(v)); },
|
* add(v){ this.value.push(S(v)); },
|
||||||
@ -464,46 +464,47 @@ interface S {
|
|||||||
* [S.symbols.onclear](){ S.clear(...this.value); },
|
* [S.symbols.onclear](){ S.clear(...this.value); },
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
* …computed signal:
|
* …computed observable:
|
||||||
* ```js
|
* ```js
|
||||||
* const name= S("Jan");
|
* const name= S("Jan");
|
||||||
* const surname= S("Andrle");
|
* const surname= S("Andrle");
|
||||||
* const fullname= S(()=> name()+" "+surname());
|
* const fullname= S(()=> name()+" "+surname());
|
||||||
* ```
|
* ```
|
||||||
* @param value Initial signal value. Or function computing value from other signals.
|
* @param value Initial observable value. Or function computing value from other observables.
|
||||||
* @param actions Use to define actions on the signal. Such as add item to the array.
|
* @param actions Use to define actions on the observable. Such as add item to the array.
|
||||||
* There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared
|
* There is also a reserved function `S.symbol.onclear` which is called when the observable is cleared
|
||||||
* by `S.clear`.
|
* by `S.clear`.
|
||||||
* */
|
* */
|
||||||
<V, A extends Actions<V>>(value: V, actions?: A): Signal<V, A>;
|
<V, A extends Actions<V>>(value: V, actions?: A): Observable<V, A>;
|
||||||
/**
|
/**
|
||||||
* Computations signal. This creates a signal which is computed from other signals.
|
* Computations observable. This creates a observable which is computed from other observables.
|
||||||
* */
|
* */
|
||||||
<V>(computation: ()=> V): Signal<V, {}>
|
<V>(computation: ()=> V): Observable<V, {}>
|
||||||
action<S extends Signal<any, Actions<any>>, A extends (S extends Signal<any, infer A> ? A : never), N extends keyof A>(
|
action<S extends Observable<any, Actions<any>>, A extends (S extends Observable<any, infer A> ? A : never), N extends keyof A>(
|
||||||
signal: S,
|
observable: S,
|
||||||
name: N,
|
name: N,
|
||||||
...params: A[N] extends (...args: infer P)=> any ? P : never
|
...params: A[N] extends (...args: infer P)=> any ? P : never
|
||||||
): void;
|
): void;
|
||||||
clear(...signals: Signal<any, any>[]): void;
|
clear(...observables: Observable<any, any>[]): void;
|
||||||
on<T>(signal: Signal<T, any>, onchange: (a: T)=> void, options?: AddEventListenerOptions): void;
|
on<T>(observable: Observable<T, any>, onchange: (a: T)=> void, options?: AddEventListenerOptions): void;
|
||||||
symbols: {
|
symbols: {
|
||||||
signal: SymbolSignal;
|
observable: SymbolObservable;
|
||||||
onclear: SymbolOnclear;
|
onclear: SymbolOnclear;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Reactive element, which is rendered based on the given signal.
|
* Reactive element, which is rendered based on the given observable.
|
||||||
* ```js
|
* ```js
|
||||||
* S.el(signal, value=> value ? el("b", "True") : el("i", "False"));
|
* S.el(observable, value=> value ? el("b", "True") : el("i", "False"));
|
||||||
* S.el(listS, list=> list.map(li=> el("li", li)));
|
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||||
* ```
|
* ```
|
||||||
* */
|
* */
|
||||||
el<S extends any>(signal: Signal<S, any>, el: (v: S)=> Element | Element[]): DocumentFragment;
|
el<S extends any>(observable: Observable<S, any>, el: (v: S)=> Element | Element[]): DocumentFragment;
|
||||||
|
|
||||||
attribute(name: string, initial?: string): Signal<string, {}>;
|
attribute(name: string, initial?: string): Observable<string, {}>;
|
||||||
}
|
}
|
||||||
export const S: S;
|
export const observable: observable;
|
||||||
|
export const O: observable;
|
||||||
declare global {
|
declare global {
|
||||||
type ddeSignal<T, A= {}>= Signal<T, A>;
|
type ddeObservable<T, A= {}>= Observable<T, A>;
|
||||||
type ddeActions<V>= Actions<V>
|
type ddeActions<V>= Actions<V>
|
||||||
}
|
}
|
4
dist/esm-with-observables.js
vendored
Normal file
4
dist/esm-with-observables.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
dist/esm-with-signals.js
vendored
4
dist/esm-with-signals.js
vendored
File diff suppressed because one or more lines are too long
4
dist/esm.d.ts
vendored
4
dist/esm.d.ts
vendored
@ -153,10 +153,10 @@ export const scope: {
|
|||||||
preventDefault<T extends boolean>(prevent: T): T,
|
preventDefault<T extends boolean>(prevent: T): T,
|
||||||
/**
|
/**
|
||||||
* This represents reference to the current host element — `scope.host()`.
|
* This represents reference to the current host element — `scope.host()`.
|
||||||
* It can be also used to register Addon (function to be called when component is initized)
|
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||||
* — `scope.host(on.connected(console.log))`.
|
* — `scope.host(on.connected(console.log))`.
|
||||||
* */
|
* */
|
||||||
host: ddeElementAddon<any>,
|
host: (...addons: ddeElementAddon<any>[])=> 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. */
|
||||||
|
2
dist/esm.js
vendored
2
dist/esm.js
vendored
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Introducing a library."><title>`deka-dom-el` — Introduction</title><!--<dde:mark type="component" name="metaAuthor" host="this" ssr/>--><meta name="author" content="Jan Andrle"><link type="text/plain" rel="author" href="https://jaandrle.github.io/humans.txt"><meta name="generator" content="deka-dom-el"><!--<dde:mark type="component" name="metaTwitter" host="this" ssr/>--><meta name="twitter:card" content="summary_large_image"><meta name="twitter:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="twitter:title" content="deka-dom-el"><meta name="twitter:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="twitter:creator" content="@jaandrle"><!--<dde:mark type="component" name="metaFacebook" host="this" ssr/>--><meta name="og:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="og:title" content="deka-dom-el"><meta name="og:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="og:creator" content="@jaandrle"><script src="https://flems.io/flems.html" type="text/javascript" charset="utf-8"></script><link rel="stylesheet" href="global.css"></head><body><!--<dde:mark type="component" name="page" host="this" ssr/>--><!--<dde:mark type="component" name="simplePage" host="this" ssr/>--><!--<dde:mark type="component" name="header" host="this" ssr/>--><header><h1>`deka-dom-el` — Introduction</h1><p>Introducing a library.</p></header><nav><a href="https://github.com/jaandrle/deka-dom-el"><svg class="icon" viewBox="0 0 32 32"><!--<dde:mark type="component" name="iconGitHub" host="parentElement" ssr/>--><path d="M 16,0.395c -8.836,0 -16,7.163 -16,16c 0,7.069 4.585,13.067 10.942,15.182c 0.8,0.148 1.094,-0.347 1.094,-0.77c 0,-0.381 -0.015,-1.642 -0.022,-2.979c -4.452,0.968 -5.391,-1.888 -5.391,-1.888c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341c -1.452,-0.993 0.11,-0.973 0.11,-0.973c 1.606,0.113 2.452,1.649 2.452,1.649c 1.427,2.446 3.743,1.739 4.656,1.33c 0.143,-1.034 0.558,-1.74 1.016,-2.14c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907c 0,-1.747 0.625,-3.174 1.649,-4.295c -0.166,-0.403 -0.714,-2.03 0.155,-4.234c 0,0 1.344,-0.43 4.401,1.64c 1.276,-0.355 2.645,-0.532 4.005,-0.539c 1.359,0.006 2.729,0.184 4.008,0.539c 3.054,-2.07 4.395,-1.64 4.395,-1.64c 0.871,2.204 0.323,3.831 0.157,4.234c 1.026,1.12 1.647,2.548 1.647,4.295c 0,6.145 -3.743,7.498 -7.306,7.895c 0.574,0.497 1.085,1.47 1.085,2.963c 0,2.141 -0.019,3.864 -0.019,4.391c 0,0.426 0.288,0.925 1.099,0.768c 6.354,-2.118 10.933,-8.113 10.933,-15.18c 0,-8.837 -7.164,-16 -16,-16Z"></path></svg>GitHub</a><a href="./" title="Introducing a library." class="current">1. Introduction</a><a href="p02-elements" title="Basic concepts of elements modifications and creations.">2. Elements</a><a href="p03-events" title="Using not only events in UI declaratively.">3. Events and Addons</a><a href="p04-signals" title="Handling reactivity in UI via signals.">4. Signals and reactivity</a></nav><main><p>The library tries to provide pure JavaScript tool(s) to create reactive interfaces.</p><p>We start with creating and modifying a static elements and end up with UI templates. <i>From <code>document.createElement</code> to <code>el</code>.</i> Then we go through the native events system and the way to include it declaratively in UI templates. <i>From <code>element.addEventListener</code> to <code>on</code>.</i></p><p>Next step is providing interactivity not only for our UI templates. We introduce signals (<code>S</code>) and how them incorporate to UI templates.</p><p>Now we will clarify how the signals are incorporated into our templates with regard to application performance. This is not the only reason the library uses <code>scope</code>s. We will look at how they work in components represented in JavaScript by functions.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-exquzbx4inc" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
|
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Introducing a library."><title>`deka-dom-el` — Introduction</title><!--<dde:mark type="component" name="metaAuthor" host="this" ssr/>--><meta name="author" content="Jan Andrle"><link type="text/plain" rel="author" href="https://jaandrle.github.io/humans.txt"><meta name="generator" content="deka-dom-el"><!--<dde:mark type="component" name="metaTwitter" host="this" ssr/>--><meta name="twitter:card" content="summary_large_image"><meta name="twitter:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="twitter:title" content="deka-dom-el"><meta name="twitter:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="twitter:creator" content="@jaandrle"><!--<dde:mark type="component" name="metaFacebook" host="this" ssr/>--><meta name="og:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="og:title" content="deka-dom-el"><meta name="og:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="og:creator" content="@jaandrle"><script src="https://flems.io/flems.html" type="text/javascript" charset="utf-8"></script><link rel="stylesheet" href="global.css"></head><body><!--<dde:mark type="component" name="page" host="this" ssr/>--><!--<dde:mark type="component" name="simplePage" host="this" ssr/>--><!--<dde:mark type="component" name="header" host="this" ssr/>--><header><h1>`deka-dom-el` — Introduction</h1><p>Introducing a library.</p></header><nav><a href="https://github.com/jaandrle/deka-dom-el"><svg class="icon" viewBox="0 0 32 32"><!--<dde:mark type="component" name="iconGitHub" host="parentElement" ssr/>--><path d="M 16,0.395c -8.836,0 -16,7.163 -16,16c 0,7.069 4.585,13.067 10.942,15.182c 0.8,0.148 1.094,-0.347 1.094,-0.77c 0,-0.381 -0.015,-1.642 -0.022,-2.979c -4.452,0.968 -5.391,-1.888 -5.391,-1.888c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341c -1.452,-0.993 0.11,-0.973 0.11,-0.973c 1.606,0.113 2.452,1.649 2.452,1.649c 1.427,2.446 3.743,1.739 4.656,1.33c 0.143,-1.034 0.558,-1.74 1.016,-2.14c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907c 0,-1.747 0.625,-3.174 1.649,-4.295c -0.166,-0.403 -0.714,-2.03 0.155,-4.234c 0,0 1.344,-0.43 4.401,1.64c 1.276,-0.355 2.645,-0.532 4.005,-0.539c 1.359,0.006 2.729,0.184 4.008,0.539c 3.054,-2.07 4.395,-1.64 4.395,-1.64c 0.871,2.204 0.323,3.831 0.157,4.234c 1.026,1.12 1.647,2.548 1.647,4.295c 0,6.145 -3.743,7.498 -7.306,7.895c 0.574,0.497 1.085,1.47 1.085,2.963c 0,2.141 -0.019,3.864 -0.019,4.391c 0,0.426 0.288,0.925 1.099,0.768c 6.354,-2.118 10.933,-8.113 10.933,-15.18c 0,-8.837 -7.164,-16 -16,-16Z"></path></svg>GitHub</a><a href="./" title="Introducing a library." class="current">1. Introduction</a><a href="p02-elements" title="Basic concepts of elements modifications and creations.">2. Elements</a><a href="p03-events" title="Using not only events in UI declaratively.">3. Events and Addons</a><a href="p04-observables" title="Handling reactivity in UI via observables.">4. Observables and reactivity</a></nav><main><p>The library tries to provide pure JavaScript tool(s) to create reactive interfaces.</p><p>We start with creating and modifying a static elements and end up with UI templates. <i>From <code>document.createElement</code> to <code>el</code>.</i> Then we go through the native events system and the way to include it declaratively in UI templates. <i>From <code>element.addEventListener</code> to <code>on</code>.</i></p><p>Next step is providing interactivity not only for our UI templates. We introduce observables (<code>O</code>) and how them incorporate to UI templates.</p><p>Now we will clarify how the observables are incorporated into our templates with regard to application performance. This is not the only reason the library uses <code>scope</code>s. We will look at how they work in components represented in JavaScript by functions.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-exquzbx4inc" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
||||||
import { S } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
|
import { S } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
||||||
const clicks= S(0);
|
const clicks= S(0);
|
||||||
document.body.append(
|
document.body.append(
|
||||||
el().append(
|
el().append(
|
||||||
@ -13,4 +13,4 @@ document.body.append(
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
</code></div><script>Flems(document.getElementById("code-example-1-exquzbx4inc"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nimport { S } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nconst clicks= S(0);\\ndocument.body.append(\\n\\tel().append(\\n\\t\\tel(\\\"p\\\", S(()=>\\n\\t\\t\\t\\\"Hello World \\\"+\\\"🎉\\\".repeat(clicks())\\n\\t\\t)),\\n\\t\\tel(\\\"button\\\", {\\n\\t\\t\\ttype: \\\"button\\\",\\n\\t\\t\\tonclick: ()=> clicks(clicks()+1),\\n\\t\\t\\ttextContent: \\\"Fire\\\"\\n\\t\\t})\\n\\t)\\n);\\n\"}],\"toolbar\":false}"));</script><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><!--<dde:mark type="component" name="pageLink" host="this" ssr/>--><a rel="next" href="p02-elements" title="Basic concepts of elements modifications and creations."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Elements</a></div></main></body></html>
|
</code></div><script>Flems(document.getElementById("code-example-1-exquzbx4inc"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nimport { S } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst clicks= S(0);\\ndocument.body.append(\\n\\tel().append(\\n\\t\\tel(\\\"p\\\", S(()=>\\n\\t\\t\\t\\\"Hello World \\\"+\\\"🎉\\\".repeat(clicks())\\n\\t\\t)),\\n\\t\\tel(\\\"button\\\", {\\n\\t\\t\\ttype: \\\"button\\\",\\n\\t\\t\\tonclick: ()=> clicks(clicks()+1),\\n\\t\\t\\ttextContent: \\\"Fire\\\"\\n\\t\\t})\\n\\t)\\n);\\n\"}],\"toolbar\":false}"));</script><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><!--<dde:mark type="component" name="pageLink" host="this" ssr/>--><a rel="next" href="p02-elements" title="Basic concepts of elements modifications and creations."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Elements</a></div></main></body></html>
|
@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Basic concepts of elements modifications and creations."><title>`deka-dom-el` — Elements</title><!--<dde:mark type="component" name="metaAuthor" host="this" ssr/>--><meta name="author" content="Jan Andrle"><link type="text/plain" rel="author" href="https://jaandrle.github.io/humans.txt"><meta name="generator" content="deka-dom-el"><!--<dde:mark type="component" name="metaTwitter" host="this" ssr/>--><meta name="twitter:card" content="summary_large_image"><meta name="twitter:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="twitter:title" content="deka-dom-el"><meta name="twitter:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="twitter:creator" content="@jaandrle"><!--<dde:mark type="component" name="metaFacebook" host="this" ssr/>--><meta name="og:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="og:title" content="deka-dom-el"><meta name="og:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="og:creator" content="@jaandrle"><script src="https://cdn.jsdelivr.net/npm/shiki" defer=""></script><script type="module" src="code.js.js"></script><script src="https://flems.io/flems.html" type="text/javascript" charset="utf-8"></script><link rel="stylesheet" href="global.css"></head><body><!--<dde:mark type="component" name="page" host="this" ssr/>--><!--<dde:mark type="component" name="simplePage" host="this" ssr/>--><!--<dde:mark type="component" name="header" host="this" ssr/>--><header><h1>`deka-dom-el` — Elements</h1><p>Basic concepts of elements modifications and creations.</p></header><nav><a href="https://github.com/jaandrle/deka-dom-el"><svg class="icon" viewBox="0 0 32 32"><!--<dde:mark type="component" name="iconGitHub" host="parentElement" ssr/>--><path d="M 16,0.395c -8.836,0 -16,7.163 -16,16c 0,7.069 4.585,13.067 10.942,15.182c 0.8,0.148 1.094,-0.347 1.094,-0.77c 0,-0.381 -0.015,-1.642 -0.022,-2.979c -4.452,0.968 -5.391,-1.888 -5.391,-1.888c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341c -1.452,-0.993 0.11,-0.973 0.11,-0.973c 1.606,0.113 2.452,1.649 2.452,1.649c 1.427,2.446 3.743,1.739 4.656,1.33c 0.143,-1.034 0.558,-1.74 1.016,-2.14c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907c 0,-1.747 0.625,-3.174 1.649,-4.295c -0.166,-0.403 -0.714,-2.03 0.155,-4.234c 0,0 1.344,-0.43 4.401,1.64c 1.276,-0.355 2.645,-0.532 4.005,-0.539c 1.359,0.006 2.729,0.184 4.008,0.539c 3.054,-2.07 4.395,-1.64 4.395,-1.64c 0.871,2.204 0.323,3.831 0.157,4.234c 1.026,1.12 1.647,2.548 1.647,4.295c 0,6.145 -3.743,7.498 -7.306,7.895c 0.574,0.497 1.085,1.47 1.085,2.963c 0,2.141 -0.019,3.864 -0.019,4.391c 0,0.426 0.288,0.925 1.099,0.768c 6.354,-2.118 10.933,-8.113 10.933,-15.18c 0,-8.837 -7.164,-16 -16,-16Z"></path></svg>GitHub</a><a href="./" title="Introducing a library.">1. Introduction</a><a href="p02-elements" title="Basic concepts of elements modifications and creations." class="current">2. Elements</a><a href="p03-events" title="Using not only events in UI declaratively.">3. Events and Addons</a><a href="p04-signals" title="Handling reactivity in UI via signals.">4. Signals and reactivity</a></nav><main><h2>Native JavaScript DOM elements creations</h2><p>Let’s go through all patterns we would like to use and what needs to be improved for better experience.</p><div class="code" data-js="todo"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">// when NPM
|
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Basic concepts of elements modifications and creations."><title>`deka-dom-el` — Elements</title><!--<dde:mark type="component" name="metaAuthor" host="this" ssr/>--><meta name="author" content="Jan Andrle"><link type="text/plain" rel="author" href="https://jaandrle.github.io/humans.txt"><meta name="generator" content="deka-dom-el"><!--<dde:mark type="component" name="metaTwitter" host="this" ssr/>--><meta name="twitter:card" content="summary_large_image"><meta name="twitter:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="twitter:title" content="deka-dom-el"><meta name="twitter:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="twitter:creator" content="@jaandrle"><!--<dde:mark type="component" name="metaFacebook" host="this" ssr/>--><meta name="og:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="og:title" content="deka-dom-el"><meta name="og:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="og:creator" content="@jaandrle"><script src="https://cdn.jsdelivr.net/npm/shiki" defer=""></script><script type="module" src="code.js.js"></script><script src="https://flems.io/flems.html" type="text/javascript" charset="utf-8"></script><link rel="stylesheet" href="global.css"></head><body><!--<dde:mark type="component" name="page" host="this" ssr/>--><!--<dde:mark type="component" name="simplePage" host="this" ssr/>--><!--<dde:mark type="component" name="header" host="this" ssr/>--><header><h1>`deka-dom-el` — Elements</h1><p>Basic concepts of elements modifications and creations.</p></header><nav><a href="https://github.com/jaandrle/deka-dom-el"><svg class="icon" viewBox="0 0 32 32"><!--<dde:mark type="component" name="iconGitHub" host="parentElement" ssr/>--><path d="M 16,0.395c -8.836,0 -16,7.163 -16,16c 0,7.069 4.585,13.067 10.942,15.182c 0.8,0.148 1.094,-0.347 1.094,-0.77c 0,-0.381 -0.015,-1.642 -0.022,-2.979c -4.452,0.968 -5.391,-1.888 -5.391,-1.888c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341c -1.452,-0.993 0.11,-0.973 0.11,-0.973c 1.606,0.113 2.452,1.649 2.452,1.649c 1.427,2.446 3.743,1.739 4.656,1.33c 0.143,-1.034 0.558,-1.74 1.016,-2.14c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907c 0,-1.747 0.625,-3.174 1.649,-4.295c -0.166,-0.403 -0.714,-2.03 0.155,-4.234c 0,0 1.344,-0.43 4.401,1.64c 1.276,-0.355 2.645,-0.532 4.005,-0.539c 1.359,0.006 2.729,0.184 4.008,0.539c 3.054,-2.07 4.395,-1.64 4.395,-1.64c 0.871,2.204 0.323,3.831 0.157,4.234c 1.026,1.12 1.647,2.548 1.647,4.295c 0,6.145 -3.743,7.498 -7.306,7.895c 0.574,0.497 1.085,1.47 1.085,2.963c 0,2.141 -0.019,3.864 -0.019,4.391c 0,0.426 0.288,0.925 1.099,0.768c 6.354,-2.118 10.933,-8.113 10.933,-15.18c 0,-8.837 -7.164,-16 -16,-16Z"></path></svg>GitHub</a><a href="./" title="Introducing a library.">1. Introduction</a><a href="p02-elements" title="Basic concepts of elements modifications and creations." class="current">2. Elements</a><a href="p03-events" title="Using not only events in UI declaratively.">3. Events and Addons</a><a href="p04-observables" title="Handling reactivity in UI via observables.">4. Observables and reactivity</a></nav><main><h2>Native JavaScript DOM elements creations</h2><p>Let’s go through all patterns we would like to use and what needs to be improved for better experience.</p><div class="code" data-js="todo"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">// when NPM
|
||||||
import { assign, el, elNS } from "deka-dom-el";
|
import { assign, el, elNS } from "deka-dom-el";
|
||||||
// https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm.js
|
// https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm.js
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ document.body.append(
|
|||||||
{ textContent: "Element’s text content.", style: "color: coral;" }
|
{ textContent: "Element’s text content.", style: "color: coral;" }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
</code></div><script>Flems(document.getElementById("code-example-1-nkz9lcdhykg"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"document.body.append(\\n\\tdocument.createElement(\\\"div\\\")\\n);\\nconsole.log(\\n\\t\\\"Emty div is generated inside <body>:\\\",\\n\\tdocument.body.innerHTML.includes(\\\"<div></div>\\\")\\n);\\n\\ndocument.body.append(\\n\\tObject.assign(\\n\\t\\tdocument.createElement(\\\"p\\\"),\\n\\t\\t{ textContent: \\\"Element’s text content.\\\", style: \\\"color: coral;\\\" }\\n\\t)\\n);\\n\"}],\"toolbar\":false}"));</script><p>To make this easier, you can use the <code>el</code> function. Internally in basic examples, it is wrapper around <code>assign(document.createElement(…), { … })</code>.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-13lyjukvr0yk" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, assign } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
|
</code></div><script>Flems(document.getElementById("code-example-1-nkz9lcdhykg"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"document.body.append(\\n\\tdocument.createElement(\\\"div\\\")\\n);\\nconsole.log(\\n\\t\\\"Emty div is generated inside <body>:\\\",\\n\\tdocument.body.innerHTML.includes(\\\"<div></div>\\\")\\n);\\n\\ndocument.body.append(\\n\\tObject.assign(\\n\\t\\tdocument.createElement(\\\"p\\\"),\\n\\t\\t{ textContent: \\\"Element’s text content.\\\", style: \\\"color: coral;\\\" }\\n\\t)\\n);\\n\"}],\"toolbar\":false}"));</script><p>To make this easier, you can use the <code>el</code> function. Internally in basic examples, it is wrapper around <code>assign(document.createElement(…), { … })</code>.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-13lyjukvr0yk" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, assign } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
||||||
const color= "lightcoral";
|
const color= "lightcoral";
|
||||||
document.body.append(
|
document.body.append(
|
||||||
el("p", { textContent: "Hello (first time)", style: { color } })
|
el("p", { textContent: "Hello (first time)", style: { color } })
|
||||||
@ -33,7 +33,7 @@ document.body.append(
|
|||||||
{ textContent: "Hello (second time)", style: { color } }
|
{ textContent: "Hello (second time)", style: { color } }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
</code></div><script>Flems(document.getElementById("code-example-1-13lyjukvr0yk"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, assign } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nconst color= \\\"lightcoral\\\";\\ndocument.body.append(\\n\\tel(\\\"p\\\", { textContent: \\\"Hello (first time)\\\", style: { color } })\\n);\\ndocument.body.append(\\n\\tassign(\\n\\t\\tdocument.createElement(\\\"p\\\"),\\n\\t\\t{ textContent: \\\"Hello (second time)\\\", style: { color } }\\n\\t)\\n);\\n\"}],\"toolbar\":false}"));</script><p>The <code>assign</code> function provides improved behaviour of <code>Object.assign()</code>. You can declaratively sets any IDL and attribute of the given element. Function prefers IDL and fallback to the <code>element.setAttribute</code> if there is no writable property in the element prototype.</p><p>You can study all JavaScript elements interfaces to the corresponding HTML elements. All HTML elements inherits from <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement">HTMLElement</a>. To see all available IDLs for example for paragraphs, see <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLParagraphElement">HTMLParagraphElement</a>. Moreover, the <code>assign</code> provides a way to sets declaratively some convenient properties:</p><ul><li>It is possible to sets <code>data-*</code>/<code>aria-*</code> attributes using object notation.</li><li>In opposite, it is also possible to sets <code>data-*</code>/<code>aria-*</code> attribute using camelCase notation.</li><li>You can use string or object as a value for <code>style</code> property.</li><li><code>className</code> (IDL – preffered)/<code>class</code> are ways to add CSS class to the element. You can use string (similarly to <code>class="…"</code> syntax in HTML) or array of strings. This is handy to concat conditional classes.</li><li>Use <code>classList</code> to toggle specific classes. This will be handy later when the reactivity via signals is beeing introduced.</li><li>The <code>assign</code> also accepts the <code>undefined</code> as a value for any property to remove it from the element declaratively. Also for some IDL the corresponding attribute is removed as it can be confusing. <em>For example, natievly the element’s <code>id</code> is removed by setting the IDL to an empty string.</em></li><li>You can use <code>=</code> or <code>.</code> to force processing given key as attribute/property of the element.</li></ul><p>For processing, the <code>assign</code> internally uses <code>assignAttribute</code> and <code>classListDeclarative</code>.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-iro9ncxa4bc" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { assign, assignAttribute, classListDeclarative } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
|
</code></div><script>Flems(document.getElementById("code-example-1-13lyjukvr0yk"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, assign } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst color= \\\"lightcoral\\\";\\ndocument.body.append(\\n\\tel(\\\"p\\\", { textContent: \\\"Hello (first time)\\\", style: { color } })\\n);\\ndocument.body.append(\\n\\tassign(\\n\\t\\tdocument.createElement(\\\"p\\\"),\\n\\t\\t{ textContent: \\\"Hello (second time)\\\", style: { color } }\\n\\t)\\n);\\n\"}],\"toolbar\":false}"));</script><p>The <code>assign</code> function provides improved behaviour of <code>Object.assign()</code>. You can declaratively sets any IDL and attribute of the given element. Function prefers IDL and fallback to the <code>element.setAttribute</code> if there is no writable property in the element prototype.</p><p>You can study all JavaScript elements interfaces to the corresponding HTML elements. All HTML elements inherits from <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement">HTMLElement</a>. To see all available IDLs for example for paragraphs, see <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLParagraphElement">HTMLParagraphElement</a>. Moreover, the <code>assign</code> provides a way to sets declaratively some convenient properties:</p><ul><li>It is possible to sets <code>data-*</code>/<code>aria-*</code> attributes using object notation.</li><li>In opposite, it is also possible to sets <code>data-*</code>/<code>aria-*</code> attribute using camelCase notation.</li><li>You can use string or object as a value for <code>style</code> property.</li><li><code>className</code> (IDL – preffered)/<code>class</code> are ways to add CSS class to the element. You can use string (similarly to <code>class="…"</code> syntax in HTML) or array of strings. This is handy to concat conditional classes.</li><li>Use <code>classList</code> to toggle specific classes. This will be handy later when the reactivity via observables is beeing introduced.</li><li>The <code>assign</code> also accepts the <code>undefined</code> as a value for any property to remove it from the element declaratively. Also for some IDL the corresponding attribute is removed as it can be confusing. <em>For example, natievly the element’s <code>id</code> is removed by setting the IDL to an empty string.</em></li><li>You can use <code>=</code> or <code>.</code> to force processing given key as attribute/property of the element.</li></ul><p>For processing, the <code>assign</code> internally uses <code>assignAttribute</code> and <code>classListDeclarative</code>.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-iro9ncxa4bc" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { assign, assignAttribute, classListDeclarative } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
||||||
const paragraph= document.createElement("p");
|
const paragraph= document.createElement("p");
|
||||||
|
|
||||||
assignAttribute(paragraph, "textContent", "Hello, world!");
|
assignAttribute(paragraph, "textContent", "Hello, world!");
|
||||||
@ -66,7 +66,7 @@ console.log("paragraph.something=", paragraph.something);
|
|||||||
document.body.append(
|
document.body.append(
|
||||||
paragraph
|
paragraph
|
||||||
);
|
);
|
||||||
</code></div><script>Flems(document.getElementById("code-example-1-iro9ncxa4bc"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { assign, assignAttribute, classListDeclarative } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nconst paragraph= document.createElement(\\\"p\\\");\\n\\nassignAttribute(paragraph, \\\"textContent\\\", \\\"Hello, world!\\\");\\n\\nassignAttribute(paragraph, \\\"style\\\", \\\"color: red; font-weight: bold;\\\");\\nassignAttribute(paragraph, \\\"style\\\", { color: \\\"navy\\\" });\\n\\nassignAttribute(paragraph, \\\"dataTest1\\\", \\\"v1\\\");\\nassignAttribute(paragraph, \\\"dataset\\\", { test2: \\\"v2\\\" });\\n\\nassign(paragraph, { //textContent and style see above\\n\\tariaLabel: \\\"v1\\\", //data* see above\\n\\tariaset: { role: \\\"none\\\" }, // dataset see above\\n\\t\\\"=onclick\\\": \\\"console.log(event)\\\",\\n\\tonmouseout: console.info,\\n\\t\\\".something\\\": \\\"something\\\",\\n\\tclassList: {} //see below\\n});\\n\\nclassListDeclarative(paragraph, {\\n\\tclassAdd: true,\\n\\tclassRemove: false,\\n\\tclassAdd1: 1,\\n\\tclassRemove1: 0,\\n\\tclassToggle: -1\\n});\\n\\nconsole.log(paragraph.outerHTML);\\nconsole.log(\\\"paragraph.something=\\\", paragraph.something);\\ndocument.body.append(\\n\\tparagraph\\n);\\n\"}],\"toolbar\":false}"));</script><h3 id="h-native-javascript-templating"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-native-javascript-templating" tabindex="-1">#</a> Native JavaScript templating</h3><p>By default, the native JS has no good way to define HTML template using DOM API:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-b6a1hbrxh7s" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">document.body.append(
|
</code></div><script>Flems(document.getElementById("code-example-1-iro9ncxa4bc"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { assign, assignAttribute, classListDeclarative } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst paragraph= document.createElement(\\\"p\\\");\\n\\nassignAttribute(paragraph, \\\"textContent\\\", \\\"Hello, world!\\\");\\n\\nassignAttribute(paragraph, \\\"style\\\", \\\"color: red; font-weight: bold;\\\");\\nassignAttribute(paragraph, \\\"style\\\", { color: \\\"navy\\\" });\\n\\nassignAttribute(paragraph, \\\"dataTest1\\\", \\\"v1\\\");\\nassignAttribute(paragraph, \\\"dataset\\\", { test2: \\\"v2\\\" });\\n\\nassign(paragraph, { //textContent and style see above\\n\\tariaLabel: \\\"v1\\\", //data* see above\\n\\tariaset: { role: \\\"none\\\" }, // dataset see above\\n\\t\\\"=onclick\\\": \\\"console.log(event)\\\",\\n\\tonmouseout: console.info,\\n\\t\\\".something\\\": \\\"something\\\",\\n\\tclassList: {} //see below\\n});\\n\\nclassListDeclarative(paragraph, {\\n\\tclassAdd: true,\\n\\tclassRemove: false,\\n\\tclassAdd1: 1,\\n\\tclassRemove1: 0,\\n\\tclassToggle: -1\\n});\\n\\nconsole.log(paragraph.outerHTML);\\nconsole.log(\\\"paragraph.something=\\\", paragraph.something);\\ndocument.body.append(\\n\\tparagraph\\n);\\n\"}],\"toolbar\":false}"));</script><h3 id="h-native-javascript-templating"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-native-javascript-templating" tabindex="-1">#</a> Native JavaScript templating</h3><p>By default, the native JS has no good way to define HTML template using DOM API:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-b6a1hbrxh7s" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">document.body.append(
|
||||||
document.createElement("div"),
|
document.createElement("div"),
|
||||||
document.createElement("span"),
|
document.createElement("span"),
|
||||||
document.createElement("main")
|
document.createElement("main")
|
||||||
@ -77,7 +77,7 @@ const template= document.createElement("main").append(
|
|||||||
document.createElement("span"),
|
document.createElement("span"),
|
||||||
);
|
);
|
||||||
console.log(typeof template==="undefined");
|
console.log(typeof template==="undefined");
|
||||||
</code></div><script>Flems(document.getElementById("code-example-1-b6a1hbrxh7s"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"document.body.append(\\n\\tdocument.createElement(\\\"div\\\"),\\n\\tdocument.createElement(\\\"span\\\"),\\n\\tdocument.createElement(\\\"main\\\")\\n);\\nconsole.log(document.body.innerHTML.includes(\\\"<div></div><span></span><main></main>\\\"));\\nconst template= document.createElement(\\\"main\\\").append(\\n\\tdocument.createElement(\\\"div\\\"),\\n\\tdocument.createElement(\\\"span\\\"),\\n);\\nconsole.log(typeof template===\\\"undefined\\\");\\n\"}],\"toolbar\":false}"));</script><p>This library therefore overwrites the <code>append</code> method of created elements to always return parent element.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-2-iro9ncxa4bc" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
|
</code></div><script>Flems(document.getElementById("code-example-1-b6a1hbrxh7s"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"document.body.append(\\n\\tdocument.createElement(\\\"div\\\"),\\n\\tdocument.createElement(\\\"span\\\"),\\n\\tdocument.createElement(\\\"main\\\")\\n);\\nconsole.log(document.body.innerHTML.includes(\\\"<div></div><span></span><main></main>\\\"));\\nconst template= document.createElement(\\\"main\\\").append(\\n\\tdocument.createElement(\\\"div\\\"),\\n\\tdocument.createElement(\\\"span\\\"),\\n);\\nconsole.log(typeof template===\\\"undefined\\\");\\n\"}],\"toolbar\":false}"));</script><p>This library therefore overwrites the <code>append</code> method of created elements to always return parent element.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-2-iro9ncxa4bc" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
||||||
document.head.append(
|
document.head.append(
|
||||||
el("style").append(
|
el("style").append(
|
||||||
"tr, td{ border: 1px solid red; padding: 1em; }",
|
"tr, td{ border: 1px solid red; padding: 1em; }",
|
||||||
@ -102,7 +102,7 @@ document.body.append(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
import { chainableAppend } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
|
import { chainableAppend } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
||||||
/** @param {keyof HTMLElementTagNameMap} tag */
|
/** @param {keyof HTMLElementTagNameMap} tag */
|
||||||
const createElement= tag=> chainableAppend(document.createElement(tag));
|
const createElement= tag=> chainableAppend(document.createElement(tag));
|
||||||
document.body.append(
|
document.body.append(
|
||||||
@ -112,7 +112,7 @@ document.body.append(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
</code></div><script>Flems(document.getElementById("code-example-2-iro9ncxa4bc"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\ndocument.head.append(\\n\\tel(\\\"style\\\").append(\\n\\t\\t\\\"tr, td{ border: 1px solid red; padding: 1em; }\\\",\\n\\t\\t\\\"table{ border-collapse: collapse; }\\\"\\n\\t)\\n);\\ndocument.body.append(\\n\\tel(\\\"p\\\", \\\"Example of a complex template. Using for example nesting lists:\\\"),\\n\\tel(\\\"ul\\\").append(\\n\\t\\tel(\\\"li\\\", \\\"List item 1\\\"),\\n\\t\\tel(\\\"li\\\").append(\\n\\t\\t\\tel(\\\"ul\\\").append(\\n\\t\\t\\t\\tel(\\\"li\\\", \\\"Nested list item 1\\\"),\\n\\t\\t\\t)\\n\\t\\t)\\n\\t),\\n\\tel(\\\"table\\\").append(\\n\\t\\tel(\\\"tr\\\").append(\\n\\t\\t\\tel(\\\"td\\\", \\\"Row 1 – Col 1\\\"),\\n\\t\\t\\tel(\\\"td\\\", \\\"Row 1 – Col 2\\\")\\n\\t\\t)\\n\\t)\\n);\\n\\nimport { chainableAppend } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\n/** @param {keyof HTMLElementTagNameMap} tag */\\nconst createElement= tag=> chainableAppend(document.createElement(tag));\\ndocument.body.append(\\n\\tcreateElement(\\\"p\\\").append(\\n\\t\\tcreateElement(\\\"em\\\").append(\\n\\t\\t\\t\\\"You can also use `chainableAppend`!\\\"\\n\\t\\t)\\n\\t)\\n);\\n\"}],\"toolbar\":false}"));</script><h3 id="h-basic-state-less-components"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-basic-state-less-components" tabindex="-1">#</a> Basic (state-less) components</h3><p>You can use functions for encapsulation (repeating) logic. The <code>el</code> accepts function as first argument. In that case, the function should return dom elements and the second argument for <code>el</code> is argument for given element.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-320zz4y072o" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
|
</code></div><script>Flems(document.getElementById("code-example-2-iro9ncxa4bc"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\ndocument.head.append(\\n\\tel(\\\"style\\\").append(\\n\\t\\t\\\"tr, td{ border: 1px solid red; padding: 1em; }\\\",\\n\\t\\t\\\"table{ border-collapse: collapse; }\\\"\\n\\t)\\n);\\ndocument.body.append(\\n\\tel(\\\"p\\\", \\\"Example of a complex template. Using for example nesting lists:\\\"),\\n\\tel(\\\"ul\\\").append(\\n\\t\\tel(\\\"li\\\", \\\"List item 1\\\"),\\n\\t\\tel(\\\"li\\\").append(\\n\\t\\t\\tel(\\\"ul\\\").append(\\n\\t\\t\\t\\tel(\\\"li\\\", \\\"Nested list item 1\\\"),\\n\\t\\t\\t)\\n\\t\\t)\\n\\t),\\n\\tel(\\\"table\\\").append(\\n\\t\\tel(\\\"tr\\\").append(\\n\\t\\t\\tel(\\\"td\\\", \\\"Row 1 – Col 1\\\"),\\n\\t\\t\\tel(\\\"td\\\", \\\"Row 1 – Col 2\\\")\\n\\t\\t)\\n\\t)\\n);\\n\\nimport { chainableAppend } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\n/** @param {keyof HTMLElementTagNameMap} tag */\\nconst createElement= tag=> chainableAppend(document.createElement(tag));\\ndocument.body.append(\\n\\tcreateElement(\\\"p\\\").append(\\n\\t\\tcreateElement(\\\"em\\\").append(\\n\\t\\t\\t\\\"You can also use `chainableAppend`!\\\"\\n\\t\\t)\\n\\t)\\n);\\n\"}],\"toolbar\":false}"));</script><h3 id="h-basic-state-less-components"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-basic-state-less-components" tabindex="-1">#</a> Basic (state-less) components</h3><p>You can use functions for encapsulation (repeating) logic. The <code>el</code> accepts function as first argument. In that case, the function should return dom elements and the second argument for <code>el</code> is argument for given element.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-320zz4y072o" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
||||||
document.head.append(
|
document.head.append(
|
||||||
el("style").append(
|
el("style").append(
|
||||||
".class1{ font-weight: bold; }",
|
".class1{ font-weight: bold; }",
|
||||||
@ -129,7 +129,7 @@ function component({ className, textContent }){
|
|||||||
el("p", textContent)
|
el("p", textContent)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
</code></div><script>Flems(document.getElementById("code-example-1-320zz4y072o"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\ndocument.head.append(\\n\\tel(\\\"style\\\").append(\\n\\t\\t\\\".class1{ font-weight: bold; }\\\",\\n\\t\\t\\\".class2{ color: purple; }\\\"\\n\\t)\\n);\\ndocument.body.append(\\n\\tel(component, { className: \\\"class2\\\", textContent: \\\"Hello World!\\\" }),\\n\\tcomponent({ className: \\\"class2\\\", textContent: \\\"Hello World!\\\" })\\n);\\n\\nfunction component({ className, textContent }){\\n\\treturn el(\\\"div\\\", { className: [ \\\"class1\\\", className ] }).append(\\n\\t\\tel(\\\"p\\\", textContent)\\n\\t);\\n}\\n\"}],\"toolbar\":false}"));</script><p>As you can see, in case of state-less/basic components there is no difference between calling component function directly or using <code>el</code> function.</p><p class="notice">It is nice to use similar naming convention as native DOM API. This allows us to use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment" title="Destructuring assignment | MDN">the destructuring assignment syntax</a> and keep track of the native API (things are best remembered through regular use).</p><h3 id="h-creating-non-html-elements"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-creating-non-html-elements" tabindex="-1">#</a> Creating non-HTML elements</h3><p>Similarly to the native DOM API (<a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS" title="MDN"><code>document.createElementNS</code></a>) for non-HTML elements we need to tell JavaScript which kind of the element to create. We can use the <code>elNS</code> function:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-bcjydb50gdc" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { elNS, assign } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
|
</code></div><script>Flems(document.getElementById("code-example-1-320zz4y072o"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\ndocument.head.append(\\n\\tel(\\\"style\\\").append(\\n\\t\\t\\\".class1{ font-weight: bold; }\\\",\\n\\t\\t\\\".class2{ color: purple; }\\\"\\n\\t)\\n);\\ndocument.body.append(\\n\\tel(component, { className: \\\"class2\\\", textContent: \\\"Hello World!\\\" }),\\n\\tcomponent({ className: \\\"class2\\\", textContent: \\\"Hello World!\\\" })\\n);\\n\\nfunction component({ className, textContent }){\\n\\treturn el(\\\"div\\\", { className: [ \\\"class1\\\", className ] }).append(\\n\\t\\tel(\\\"p\\\", textContent)\\n\\t);\\n}\\n\"}],\"toolbar\":false}"));</script><p>As you can see, in case of state-less/basic components there is no difference between calling component function directly or using <code>el</code> function.</p><p class="notice">It is nice to use similar naming convention as native DOM API. This allows us to use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment" title="Destructuring assignment | MDN">the destructuring assignment syntax</a> and keep track of the native API (things are best remembered through regular use).</p><h3 id="h-creating-non-html-elements"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-creating-non-html-elements" tabindex="-1">#</a> Creating non-HTML elements</h3><p>Similarly to the native DOM API (<a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS" title="MDN"><code>document.createElementNS</code></a>) for non-HTML elements we need to tell JavaScript which kind of the element to create. We can use the <code>elNS</code> function:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-bcjydb50gdc" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { elNS, assign } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
||||||
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(
|
||||||
@ -140,4 +140,4 @@ document.body.append(
|
|||||||
console.log(
|
console.log(
|
||||||
document.body.innerHTML.includes("<svg></svg><math></math>")
|
document.body.innerHTML.includes("<svg></svg><math></math>")
|
||||||
)
|
)
|
||||||
</code></div><script>Flems(document.getElementById("code-example-1-bcjydb50gdc"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { elNS, assign } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nconst elSVG= elNS(\\\"http://www.w3.org/2000/svg\\\");\\nconst elMath= elNS(\\\"http://www.w3.org/1998/Math/MathML\\\");\\ndocument.body.append(\\n\\telSVG(\\\"svg\\\"), // see https://developer.mozilla.org/en-US/docs/Web/SVG and https://developer.mozilla.org/en-US/docs/Web/API/SVGElement\\n\\telMath(\\\"math\\\") // see https://developer.mozilla.org/en-US/docs/Web/MathML and https://developer.mozilla.org/en-US/docs/Web/MathML/Global_attributes\\n);\\n\\nconsole.log(\\n\\tdocument.body.innerHTML.includes(\\\"<svg></svg><math></math>\\\")\\n)\\n\"}],\"toolbar\":false}"));</script><div class="notice"><!--<dde:mark type="component" name="mnemonicUl" host="parentElement" ssr/>--><h3 id="h-mnemonic"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-mnemonic" tabindex="-1">#</a> Mnemonic</h3><ul><li><code>assign(<element>, ...<idl-objects>): <element></code> — assign properties to the element</li><li><code>el(<tag-name>, <primitive>)[.append(...)]: <element-from-tag-name></code> — simple element containing only text</li><li><code>el(<tag-name>, <idl-object>)[.append(...)]: <element-from-tag-name></code> — element with more properties</li><li><code>el(<function>, <function-argument(s)>)[.append(...)]: <element-returned-by-function></code> — using component represented by function</li><li><code>el(<...>, <...>, ...<addons>)</code> — see following page</li><li><code>elNS(<namespace>)(<as-el-see-above>)[.append(...)]: <element-based-on-arguments></code> — typically SVG elements</li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="./" title="Introducing a library."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Introduction (previous)</a><a rel="next" href="p03-events" title="Using not only events in UI declaratively."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Events and Addons</a></div></main></body></html>
|
</code></div><script>Flems(document.getElementById("code-example-1-bcjydb50gdc"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { elNS, assign } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst elSVG= elNS(\\\"http://www.w3.org/2000/svg\\\");\\nconst elMath= elNS(\\\"http://www.w3.org/1998/Math/MathML\\\");\\ndocument.body.append(\\n\\telSVG(\\\"svg\\\"), // see https://developer.mozilla.org/en-US/docs/Web/SVG and https://developer.mozilla.org/en-US/docs/Web/API/SVGElement\\n\\telMath(\\\"math\\\") // see https://developer.mozilla.org/en-US/docs/Web/MathML and https://developer.mozilla.org/en-US/docs/Web/MathML/Global_attributes\\n);\\n\\nconsole.log(\\n\\tdocument.body.innerHTML.includes(\\\"<svg></svg><math></math>\\\")\\n)\\n\"}],\"toolbar\":false}"));</script><div class="notice"><!--<dde:mark type="component" name="mnemonicUl" host="parentElement" ssr/>--><h3 id="h-mnemonic"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-mnemonic" tabindex="-1">#</a> Mnemonic</h3><ul><li><code>assign(<element>, ...<idl-objects>): <element></code> — assign properties to the element</li><li><code>el(<tag-name>, <primitive>)[.append(...)]: <element-from-tag-name></code> — simple element containing only text</li><li><code>el(<tag-name>, <idl-object>)[.append(...)]: <element-from-tag-name></code> — element with more properties</li><li><code>el(<function>, <function-argument(s)>)[.append(...)]: <element-returned-by-function></code> — using component represented by function</li><li><code>el(<...>, <...>, ...<addons>)</code> — see following page</li><li><code>elNS(<namespace>)(<as-el-see-above>)[.append(...)]: <element-based-on-arguments></code> — typically SVG elements</li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="./" title="Introducing a library."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Introduction (previous)</a><a rel="next" href="p03-events" title="Using not only events in UI declaratively."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Events and Addons</a></div></main></body></html>
|
@ -1,11 +1,11 @@
|
|||||||
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Using not only events in UI declaratively."><title>`deka-dom-el` — Events and Addons</title><!--<dde:mark type="component" name="metaAuthor" host="this" ssr/>--><meta name="author" content="Jan Andrle"><link type="text/plain" rel="author" href="https://jaandrle.github.io/humans.txt"><meta name="generator" content="deka-dom-el"><!--<dde:mark type="component" name="metaTwitter" host="this" ssr/>--><meta name="twitter:card" content="summary_large_image"><meta name="twitter:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="twitter:title" content="deka-dom-el"><meta name="twitter:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="twitter:creator" content="@jaandrle"><!--<dde:mark type="component" name="metaFacebook" host="this" ssr/>--><meta name="og:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="og:title" content="deka-dom-el"><meta name="og:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="og:creator" content="@jaandrle"><script src="https://cdn.jsdelivr.net/npm/shiki" defer=""></script><script type="module" src="code.js.js"></script><script src="https://flems.io/flems.html" type="text/javascript" charset="utf-8"></script><link rel="stylesheet" href="global.css"></head><body><!--<dde:mark type="component" name="page" host="this" ssr/>--><!--<dde:mark type="component" name="simplePage" host="this" ssr/>--><!--<dde:mark type="component" name="header" host="this" ssr/>--><header><h1>`deka-dom-el` — Events and Addons</h1><p>Using not only events in UI declaratively.</p></header><nav><a href="https://github.com/jaandrle/deka-dom-el"><svg class="icon" viewBox="0 0 32 32"><!--<dde:mark type="component" name="iconGitHub" host="parentElement" ssr/>--><path d="M 16,0.395c -8.836,0 -16,7.163 -16,16c 0,7.069 4.585,13.067 10.942,15.182c 0.8,0.148 1.094,-0.347 1.094,-0.77c 0,-0.381 -0.015,-1.642 -0.022,-2.979c -4.452,0.968 -5.391,-1.888 -5.391,-1.888c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341c -1.452,-0.993 0.11,-0.973 0.11,-0.973c 1.606,0.113 2.452,1.649 2.452,1.649c 1.427,2.446 3.743,1.739 4.656,1.33c 0.143,-1.034 0.558,-1.74 1.016,-2.14c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907c 0,-1.747 0.625,-3.174 1.649,-4.295c -0.166,-0.403 -0.714,-2.03 0.155,-4.234c 0,0 1.344,-0.43 4.401,1.64c 1.276,-0.355 2.645,-0.532 4.005,-0.539c 1.359,0.006 2.729,0.184 4.008,0.539c 3.054,-2.07 4.395,-1.64 4.395,-1.64c 0.871,2.204 0.323,3.831 0.157,4.234c 1.026,1.12 1.647,2.548 1.647,4.295c 0,6.145 -3.743,7.498 -7.306,7.895c 0.574,0.497 1.085,1.47 1.085,2.963c 0,2.141 -0.019,3.864 -0.019,4.391c 0,0.426 0.288,0.925 1.099,0.768c 6.354,-2.118 10.933,-8.113 10.933,-15.18c 0,-8.837 -7.164,-16 -16,-16Z"></path></svg>GitHub</a><a href="./" title="Introducing a library.">1. Introduction</a><a href="p02-elements" title="Basic concepts of elements modifications and creations.">2. Elements</a><a href="p03-events" title="Using not only events in UI declaratively." class="current">3. Events and Addons</a><a href="p04-signals" title="Handling reactivity in UI via signals.">4. Signals and reactivity</a></nav><main><h2>Listenning to the native DOM events and other Addons</h2><p>We quickly introduce helper to listening to the native DOM events. And library syntax/pattern so-called <em>Addon</em> to incorporate not only this in UI templates declaratively.</p><div class="code" data-js="todo"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">// when NPM
|
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Using not only events in UI declaratively."><title>`deka-dom-el` — Events and Addons</title><!--<dde:mark type="component" name="metaAuthor" host="this" ssr/>--><meta name="author" content="Jan Andrle"><link type="text/plain" rel="author" href="https://jaandrle.github.io/humans.txt"><meta name="generator" content="deka-dom-el"><!--<dde:mark type="component" name="metaTwitter" host="this" ssr/>--><meta name="twitter:card" content="summary_large_image"><meta name="twitter:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="twitter:title" content="deka-dom-el"><meta name="twitter:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="twitter:creator" content="@jaandrle"><!--<dde:mark type="component" name="metaFacebook" host="this" ssr/>--><meta name="og:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="og:title" content="deka-dom-el"><meta name="og:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="og:creator" content="@jaandrle"><script src="https://cdn.jsdelivr.net/npm/shiki" defer=""></script><script type="module" src="code.js.js"></script><script src="https://flems.io/flems.html" type="text/javascript" charset="utf-8"></script><link rel="stylesheet" href="global.css"></head><body><!--<dde:mark type="component" name="page" host="this" ssr/>--><!--<dde:mark type="component" name="simplePage" host="this" ssr/>--><!--<dde:mark type="component" name="header" host="this" ssr/>--><header><h1>`deka-dom-el` — Events and Addons</h1><p>Using not only events in UI declaratively.</p></header><nav><a href="https://github.com/jaandrle/deka-dom-el"><svg class="icon" viewBox="0 0 32 32"><!--<dde:mark type="component" name="iconGitHub" host="parentElement" ssr/>--><path d="M 16,0.395c -8.836,0 -16,7.163 -16,16c 0,7.069 4.585,13.067 10.942,15.182c 0.8,0.148 1.094,-0.347 1.094,-0.77c 0,-0.381 -0.015,-1.642 -0.022,-2.979c -4.452,0.968 -5.391,-1.888 -5.391,-1.888c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341c -1.452,-0.993 0.11,-0.973 0.11,-0.973c 1.606,0.113 2.452,1.649 2.452,1.649c 1.427,2.446 3.743,1.739 4.656,1.33c 0.143,-1.034 0.558,-1.74 1.016,-2.14c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907c 0,-1.747 0.625,-3.174 1.649,-4.295c -0.166,-0.403 -0.714,-2.03 0.155,-4.234c 0,0 1.344,-0.43 4.401,1.64c 1.276,-0.355 2.645,-0.532 4.005,-0.539c 1.359,0.006 2.729,0.184 4.008,0.539c 3.054,-2.07 4.395,-1.64 4.395,-1.64c 0.871,2.204 0.323,3.831 0.157,4.234c 1.026,1.12 1.647,2.548 1.647,4.295c 0,6.145 -3.743,7.498 -7.306,7.895c 0.574,0.497 1.085,1.47 1.085,2.963c 0,2.141 -0.019,3.864 -0.019,4.391c 0,0.426 0.288,0.925 1.099,0.768c 6.354,-2.118 10.933,-8.113 10.933,-15.18c 0,-8.837 -7.164,-16 -16,-16Z"></path></svg>GitHub</a><a href="./" title="Introducing a library.">1. Introduction</a><a href="p02-elements" title="Basic concepts of elements modifications and creations.">2. Elements</a><a href="p03-events" title="Using not only events in UI declaratively." class="current">3. Events and Addons</a><a href="p04-observables" title="Handling reactivity in UI via observables.">4. Observables and reactivity</a></nav><main><h2>Listenning to the native DOM events and other Addons</h2><p>We quickly introduce helper to listening to the native DOM events. And library syntax/pattern so-called <em>Addon</em> to incorporate not only this in UI templates declaratively.</p><div class="code" data-js="todo"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">// when NPM
|
||||||
import { on, dispatchEvent } from "deka-dom-el";
|
import { on, dispatchEvent } from "deka-dom-el";
|
||||||
// https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm.js
|
// https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm.js
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {ddeElementAddon}
|
* @type {ddeElementAddon}
|
||||||
* */
|
* */
|
||||||
</code></div><h3 id="h-events-and-listenners"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-events-and-listenners" tabindex="-1">#</a> Events and listenners</h3><p>In JavaScript you can listen to the native DOM events of the given element by using <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener" title="addEventListener on MDN"><code>element.addEventListener(type, listener, options)</code></a>. The library provides an alternative (<code>on</code>) accepting the differen order of the arguments:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-1wmddlo83w68" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
|
</code></div><h3 id="h-events-and-listenners"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-events-and-listenners" tabindex="-1">#</a> Events and listenners</h3><p>In JavaScript you can listen to the native DOM events of the given element by using <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener" title="addEventListener on MDN"><code>element.addEventListener(type, listener, options)</code></a>. The library provides an alternative (<code>on</code>) accepting the differen order of the arguments:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-1wmddlo83w68" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
||||||
const log= mark=> console.log.bind(console, mark);
|
const log= mark=> console.log.bind(console, mark);
|
||||||
|
|
||||||
const button= el("button", "Test click");
|
const button= el("button", "Test click");
|
||||||
@ -15,7 +15,7 @@ on("click", log("`on`"), { once: true })(button);
|
|||||||
document.body.append(
|
document.body.append(
|
||||||
button
|
button
|
||||||
);
|
);
|
||||||
</code></div><script>Flems(document.getElementById("code-example-1-1wmddlo83w68"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nconst log= mark=> console.log.bind(console, mark);\\n\\nconst button= el(\\\"button\\\", \\\"Test click\\\");\\nbutton.addEventListener(\\\"click\\\", log(\\\"`addEventListener`\\\"), { once: true });\\non(\\\"click\\\", log(\\\"`on`\\\"), { once: true })(button);\\n\\ndocument.body.append(\\n\\tbutton\\n);\\n\"}],\"toolbar\":false}"));</script><p>…this is actually one of the two differences. The another one is that <code>on</code> accepts only object as the <code>options</code> (but it is still optional).</p><p class="notice">The other difference is that there is <strong>no</strong> <code>off</code> function. You can remove listener declaratively using <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#signal" title="part of addEventListener on MDN">AbortSignal</a>:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-8r8qappf8mo" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
|
</code></div><script>Flems(document.getElementById("code-example-1-1wmddlo83w68"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst log= mark=> console.log.bind(console, mark);\\n\\nconst button= el(\\\"button\\\", \\\"Test click\\\");\\nbutton.addEventListener(\\\"click\\\", log(\\\"`addEventListener`\\\"), { once: true });\\non(\\\"click\\\", log(\\\"`on`\\\"), { once: true })(button);\\n\\ndocument.body.append(\\n\\tbutton\\n);\\n\"}],\"toolbar\":false}"));</script><p>…this is actually one of the two differences. The another one is that <code>on</code> accepts only object as the <code>options</code> (but it is still optional).</p><p class="notice">The other difference is that there is <strong>no</strong> <code>off</code> function. You can remove listener declaratively using <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#signal" title="part of addEventListener on MDN">AbortSignal</a>:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-8r8qappf8mo" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
||||||
const log= mark=> console.log.bind(console, mark);
|
const log= mark=> console.log.bind(console, mark);
|
||||||
|
|
||||||
const abort_controller= new AbortController();
|
const abort_controller= new AbortController();
|
||||||
@ -28,7 +28,7 @@ on("click", log("`on`"), { signal })(button);
|
|||||||
document.body.append(
|
document.body.append(
|
||||||
button, " ", el("button", { textContent: "Off", onclick: ()=> abort_controller.abort() })
|
button, " ", el("button", { textContent: "Off", onclick: ()=> abort_controller.abort() })
|
||||||
);
|
);
|
||||||
</code></div><script>Flems(document.getElementById("code-example-1-8r8qappf8mo"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nconst log= mark=> console.log.bind(console, mark);\\n\\nconst abort_controller= new AbortController();\\nconst { signal }= abort_controller;\\n\\nconst button= el(\\\"button\\\", \\\"Test click\\\");\\nbutton.addEventListener(\\\"click\\\", log(\\\"`addEventListener`\\\"), { signal });\\non(\\\"click\\\", log(\\\"`on`\\\"), { signal })(button);\\n\\ndocument.body.append(\\n\\tbutton, \\\" \\\", el(\\\"button\\\", { textContent: \\\"Off\\\", onclick: ()=> abort_controller.abort() })\\n);\\n\"}],\"toolbar\":false}"));</script><div class="notice"><p>So, there are (typically) three ways to handle events. You can use:</p><ul><li><code>el("button", { textContent: "click me", "=onclick": "console.log(event)" })</code></li><li><code>el("button", { textContent: "click me", onclick: console.log })</code></li><li><code>el("button", { textContent: "click me" }, on("click", console.log))</code></li></ul><p>In the first example we force to use HTML attribute (it corresponds to <code><button onclick="console.log(event)">click me</button></code>). <em>Side note: this can be useful in case of SSR.</em> To study difference, you can read a nice summary here: <a href="https://gist.github.com/WebReflection/b404c36f46371e3b1173bf5492acc944">GIST @WebReflection/web_events.md</a>.</p></div><h3 id="h-addons"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-addons" tabindex="-1">#</a> Addons</h3><p>From practical point of view, <em>Addons</em> are just functions that accept any html element as their first parameter. You can see that the <code>on(…)</code> fullfills this requirement.</p><p>You can use Addons as ≥3rd argument of <code>el</code> function. This way is possible to extends you templates by additional (3rd party) functionalities. But for now mainly, you can add events listeners:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-6jtsnxfqzm4" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
|
</code></div><script>Flems(document.getElementById("code-example-1-8r8qappf8mo"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst log= mark=> console.log.bind(console, mark);\\n\\nconst abort_controller= new AbortController();\\nconst { signal }= abort_controller;\\n\\nconst button= el(\\\"button\\\", \\\"Test click\\\");\\nbutton.addEventListener(\\\"click\\\", log(\\\"`addEventListener`\\\"), { signal });\\non(\\\"click\\\", log(\\\"`on`\\\"), { signal })(button);\\n\\ndocument.body.append(\\n\\tbutton, \\\" \\\", el(\\\"button\\\", { textContent: \\\"Off\\\", onclick: ()=> abort_controller.abort() })\\n);\\n\"}],\"toolbar\":false}"));</script><div class="notice"><p>So, there are (typically) three ways to handle events. You can use:</p><ul><li><code>el("button", { textContent: "click me", "=onclick": "console.log(event)" })</code></li><li><code>el("button", { textContent: "click me", onclick: console.log })</code></li><li><code>el("button", { textContent: "click me" }, on("click", console.log))</code></li></ul><p>In the first example we force to use HTML attribute (it corresponds to <code><button onclick="console.log(event)">click me</button></code>). <em>Side note: this can be useful in case of SSR.</em> To study difference, you can read a nice summary here: <a href="https://gist.github.com/WebReflection/b404c36f46371e3b1173bf5492acc944">GIST @WebReflection/web_events.md</a>.</p></div><h3 id="h-addons"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-addons" tabindex="-1">#</a> Addons</h3><p>From practical point of view, <em>Addons</em> are just functions that accept any html element as their first parameter. You can see that the <code>on(…)</code> fullfills this requirement.</p><p>You can use Addons as ≥3rd argument of <code>el</code> function. This way is possible to extends you templates by additional (3rd party) functionalities. But for now mainly, you can add events listeners:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-6jtsnxfqzm4" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
||||||
const abort_controller= new AbortController();
|
const abort_controller= new AbortController();
|
||||||
const { signal }= abort_controller;
|
const { signal }= abort_controller;
|
||||||
/** @type {ddeElementAddon<HTMLButtonElement>} */
|
/** @type {ddeElementAddon<HTMLButtonElement>} */
|
||||||
@ -52,7 +52,7 @@ function update(event){
|
|||||||
"\n"
|
"\n"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
</code></div><script>Flems(document.getElementById("code-example-1-6jtsnxfqzm4"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nconst abort_controller= new AbortController();\\nconst { signal }= abort_controller;\\n/** @type {ddeElementAddon<HTMLButtonElement>} */\\nconst onclickOff= on(\\\"click\\\", ()=> abort_controller.abort(), { signal });\\n/** @type {(ref?: HTMLOutputElement)=> HTMLOutputElement | null} */\\nconst ref= (store=> ref=> ref ? (store= ref) : store)(null);\\n\\ndocument.body.append(\\n\\tel(\\\"button\\\", \\\"Test `on`\\\",\\n\\t\\ton(\\\"click\\\", console.log, { signal }),\\n\\t\\ton(\\\"click\\\", update, { signal }),\\n\\t\\ton(\\\"mouseup\\\", update, { signal })),\\n\\t\\\" \\\",\\n\\tel(\\\"button\\\", \\\"Off\\\", onclickOff),\\n\\tel(\\\"output\\\", { style: { display: \\\"block\\\", whiteSpace: \\\"pre\\\" } }, ref)\\n);\\n/** @param {MouseEvent} event @this {HTMLButtonElement} */\\nfunction update(event){\\n\\tref().append(\\n\\t\\tevent.type,\\n\\t\\t\\\"\\\\n\\\"\\n\\t);\\n}\\n\"}],\"toolbar\":false}"));</script><p>As the example shows, you can also provide types in JSDoc+TypeScript by using global type <code>ddeElementAddon</code>. Also notice, you can use Addons to get element reference.</p><h3 id="h-life-cycle-events"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-life-cycle-events" tabindex="-1">#</a> Life-cycle events</h3><p>Addons are called immediately when the element is created, even it is not connected to live DOM yet. Therefore, you can understand the Addon to be “oncreate” event.</p><p>The library provide three additional live-cycle events corresponding to how they are named in a case of custom elements: <code>on.connected</code>, <code>on.disconnected</code> and <code>on.attributeChanged</code>.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-35hjjp3e4js" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
|
</code></div><script>Flems(document.getElementById("code-example-1-6jtsnxfqzm4"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst abort_controller= new AbortController();\\nconst { signal }= abort_controller;\\n/** @type {ddeElementAddon<HTMLButtonElement>} */\\nconst onclickOff= on(\\\"click\\\", ()=> abort_controller.abort(), { signal });\\n/** @type {(ref?: HTMLOutputElement)=> HTMLOutputElement | null} */\\nconst ref= (store=> ref=> ref ? (store= ref) : store)(null);\\n\\ndocument.body.append(\\n\\tel(\\\"button\\\", \\\"Test `on`\\\",\\n\\t\\ton(\\\"click\\\", console.log, { signal }),\\n\\t\\ton(\\\"click\\\", update, { signal }),\\n\\t\\ton(\\\"mouseup\\\", update, { signal })),\\n\\t\\\" \\\",\\n\\tel(\\\"button\\\", \\\"Off\\\", onclickOff),\\n\\tel(\\\"output\\\", { style: { display: \\\"block\\\", whiteSpace: \\\"pre\\\" } }, ref)\\n);\\n/** @param {MouseEvent} event @this {HTMLButtonElement} */\\nfunction update(event){\\n\\tref().append(\\n\\t\\tevent.type,\\n\\t\\t\\\"\\\\n\\\"\\n\\t);\\n}\\n\"}],\"toolbar\":false}"));</script><p>As the example shows, you can also provide types in JSDoc+TypeScript by using global type <code>ddeElementAddon</code>. Also notice, you can use Addons to get element reference.</p><h3 id="h-life-cycle-events"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-life-cycle-events" tabindex="-1">#</a> Life-cycle events</h3><p>Addons are called immediately when the element is created, even it is not connected to live DOM yet. Therefore, you can understand the Addon to be “oncreate” event.</p><p>The library provide three additional live-cycle events corresponding to how they are named in a case of custom elements: <code>on.connected</code>, <code>on.disconnected</code> and <code>on.attributeChanged</code>.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-35hjjp3e4js" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
||||||
const paragraph= el("p", "See live-cycle events in console.",
|
const paragraph= el("p", "See live-cycle events in console.",
|
||||||
el=> log({ type: "dde:created", detail: el }),
|
el=> log({ type: "dde:created", detail: el }),
|
||||||
on.connected(log),
|
on.connected(log),
|
||||||
@ -70,7 +70,7 @@ document.body.append(
|
|||||||
function log({ type, detail }){
|
function log({ type, detail }){
|
||||||
console.log({ _this: this, type, detail });
|
console.log({ _this: this, type, detail });
|
||||||
}
|
}
|
||||||
</code></div><script>Flems(document.getElementById("code-example-1-35hjjp3e4js"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nconst paragraph= el(\\\"p\\\", \\\"See live-cycle events in console.\\\",\\n\\tel=> log({ type: \\\"dde:created\\\", detail: el }),\\n\\ton.connected(log),\\n\\ton.disconnected(log),\\n\\ton.attributeChanged(log));\\n\\ndocument.body.append(\\n\\tparagraph,\\n\\tel(\\\"button\\\", \\\"Update attribute\\\", on(\\\"click\\\", ()=> paragraph.setAttribute(\\\"test\\\", Math.random().toString()))),\\n\\t\\\" \\\",\\n\\tel(\\\"button\\\", \\\"Remove\\\", on(\\\"click\\\", ()=> paragraph.remove()))\\n);\\n\\n/** @param {Partial<CustomEvent>} event */\\nfunction log({ type, detail }){\\n\\tconsole.log({ _this: this, type, detail });\\n}\\n\"}],\"toolbar\":false}"));</script><p>For Custom elements, we will later introduce a way to replace <code>*Callback</code> syntax with <code>dde:*</code> events. The <code>on.*</code> functions then listen to the appropriate Custom Elements events (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks">Custom element lifecycle callbacks | MDN</a>).</p><p>But, in case of regular elemnets the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver"><code>MutationObserver</code> | MDN</a> is internaly used to track these events. Therefore, there are some drawbacks:</p><ul><li>To proper listener registration, you need to use <code>on.*</code> not `on("dde:*", …)`!</li><li>Use sparingly! Internally, library must loop of all registered events and fires event properly. <strong>It is good practice to use the fact that if an element is removed, its children are also removed!</strong> In this spirit, we will introduce later the <strong>host</strong> syntax to register clean up procedures when the component is removed from the app.</li></ul><h3 id="h-final-notes"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-final-notes" tabindex="-1">#</a> Final notes</h3><p>The library also provides a method to dispatch the events.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-2-b6a1hbrxh7s" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on, dispatchEvent } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
|
</code></div><script>Flems(document.getElementById("code-example-1-35hjjp3e4js"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst paragraph= el(\\\"p\\\", \\\"See live-cycle events in console.\\\",\\n\\tel=> log({ type: \\\"dde:created\\\", detail: el }),\\n\\ton.connected(log),\\n\\ton.disconnected(log),\\n\\ton.attributeChanged(log));\\n\\ndocument.body.append(\\n\\tparagraph,\\n\\tel(\\\"button\\\", \\\"Update attribute\\\", on(\\\"click\\\", ()=> paragraph.setAttribute(\\\"test\\\", Math.random().toString()))),\\n\\t\\\" \\\",\\n\\tel(\\\"button\\\", \\\"Remove\\\", on(\\\"click\\\", ()=> paragraph.remove()))\\n);\\n\\n/** @param {Partial<CustomEvent>} event */\\nfunction log({ type, detail }){\\n\\tconsole.log({ _this: this, type, detail });\\n}\\n\"}],\"toolbar\":false}"));</script><p>For Custom elements, we will later introduce a way to replace <code>*Callback</code> syntax with <code>dde:*</code> events. The <code>on.*</code> functions then listen to the appropriate Custom Elements events (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks">Custom element lifecycle callbacks | MDN</a>).</p><p>But, in case of regular elemnets the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver"><code>MutationObserver</code> | MDN</a> is internaly used to track these events. Therefore, there are some drawbacks:</p><ul><li>To proper listener registration, you need to use <code>on.*</code> not `on("dde:*", …)`!</li><li>Use sparingly! Internally, library must loop of all registered events and fires event properly. <strong>It is good practice to use the fact that if an element is removed, its children are also removed!</strong> In this spirit, we will introduce later the <strong>host</strong> syntax to register clean up procedures when the component is removed from the app.</li></ul><h3 id="h-final-notes"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-final-notes" tabindex="-1">#</a> Final notes</h3><p>The library also provides a method to dispatch the events.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-2-b6a1hbrxh7s" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on, dispatchEvent } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
||||||
document.body.append(
|
document.body.append(
|
||||||
el("p", "Listenning to `test` event.", on("test", console.log)).append(
|
el("p", "Listenning to `test` event.", on("test", console.log)).append(
|
||||||
el("br"),
|
el("br"),
|
||||||
@ -94,4 +94,4 @@ function dde(){
|
|||||||
function ddeOptions(){
|
function ddeOptions(){
|
||||||
dispatchEvent("test", { bubbles: true })(this, "hi");
|
dispatchEvent("test", { bubbles: true })(this, "hi");
|
||||||
}
|
}
|
||||||
</code></div><script>Flems(document.getElementById("code-example-2-b6a1hbrxh7s"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on, dispatchEvent } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\ndocument.body.append(\\n\\tel(\\\"p\\\", \\\"Listenning to `test` event.\\\", on(\\\"test\\\", console.log)).append(\\n\\t\\tel(\\\"br\\\"),\\n\\t\\tel(\\\"button\\\", \\\"native\\\", on(\\\"click\\\", native)),\\n\\t\\t\\\" \\\",\\n\\t\\tel(\\\"button\\\", \\\"dde\\\", on(\\\"click\\\", dde)),\\n\\t\\t\\\" \\\",\\n\\t\\tel(\\\"button\\\", \\\"dde with options\\\", on(\\\"click\\\", ddeOptions))\\n\\t)\\n);\\nfunction native(){\\n\\tthis.dispatchEvent(\\n\\t\\tnew CustomEvent(\\\"test\\\",\\n\\t\\t\\t{ bubbles: true, detail: \\\"hi\\\" }\\n\\t\\t)\\n\\t);\\n}\\nfunction dde(){\\n\\tdispatchEvent(\\\"test\\\")(this.parentElement, \\\"hi\\\");\\n}\\nfunction ddeOptions(){\\n\\tdispatchEvent(\\\"test\\\", { bubbles: true })(this, \\\"hi\\\");\\n}\\n\"}],\"toolbar\":false}"));</script><div class="notice"><!--<dde:mark type="component" name="mnemonicUl" host="parentElement" ssr/>--><h3 id="h-mnemonic"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-mnemonic" tabindex="-1">#</a> Mnemonic</h3><ul><li><code>on(<event>, <listener>[, <options>])(<element>)</code> — just <code><element>.addEventListener(<event>, <listener>[, <options>])</code></li><li><code>on.<live-cycle>(<event>, <listener>[, <options>])(<element>)</code> — corresponds to custom elemnets callbacks <code><live-cycle>Callback(...){...}</code>. To connect to custom element see following page, else it is simulated by MutationObserver.</li><li><code>dispatchEvent(<event>[, <options>])(element)</code> — just <code><element>.dispatchEvent(new Event(<event>[, <options>]))</code></li><li><code>dispatchEvent(<event>[, <options>])(element, detail)</code> — just <code><element>.dispatchEvent(new CustomEvent(<event>, { detail, ...<options> }))</code></li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="p02-elements" title="Basic concepts of elements modifications and creations."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Elements (previous)</a><a rel="next" href="p04-signals" title="Handling reactivity in UI via signals."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Signals and reactivity</a></div></main></body></html>
|
</code></div><script>Flems(document.getElementById("code-example-2-b6a1hbrxh7s"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on, dispatchEvent } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\ndocument.body.append(\\n\\tel(\\\"p\\\", \\\"Listenning to `test` event.\\\", on(\\\"test\\\", console.log)).append(\\n\\t\\tel(\\\"br\\\"),\\n\\t\\tel(\\\"button\\\", \\\"native\\\", on(\\\"click\\\", native)),\\n\\t\\t\\\" \\\",\\n\\t\\tel(\\\"button\\\", \\\"dde\\\", on(\\\"click\\\", dde)),\\n\\t\\t\\\" \\\",\\n\\t\\tel(\\\"button\\\", \\\"dde with options\\\", on(\\\"click\\\", ddeOptions))\\n\\t)\\n);\\nfunction native(){\\n\\tthis.dispatchEvent(\\n\\t\\tnew CustomEvent(\\\"test\\\",\\n\\t\\t\\t{ bubbles: true, detail: \\\"hi\\\" }\\n\\t\\t)\\n\\t);\\n}\\nfunction dde(){\\n\\tdispatchEvent(\\\"test\\\")(this.parentElement, \\\"hi\\\");\\n}\\nfunction ddeOptions(){\\n\\tdispatchEvent(\\\"test\\\", { bubbles: true })(this, \\\"hi\\\");\\n}\\n\"}],\"toolbar\":false}"));</script><div class="notice"><!--<dde:mark type="component" name="mnemonicUl" host="parentElement" ssr/>--><h3 id="h-mnemonic"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-mnemonic" tabindex="-1">#</a> Mnemonic</h3><ul><li><code>on(<event>, <listener>[, <options>])(<element>)</code> — just <code><element>.addEventListener(<event>, <listener>[, <options>])</code></li><li><code>on.<live-cycle>(<event>, <listener>[, <options>])(<element>)</code> — corresponds to custom elemnets callbacks <code><live-cycle>Callback(...){...}</code>. To connect to custom element see following page, else it is simulated by MutationObserver.</li><li><code>dispatchEvent(<event>[, <options>])(element)</code> — just <code><element>.dispatchEvent(new Event(<event>[, <options>]))</code></li><li><code>dispatchEvent(<event>[, <options>])(element, detail)</code> — just <code><element>.dispatchEvent(new CustomEvent(<event>, { detail, ...<options> }))</code></li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="p02-elements" title="Basic concepts of elements modifications and creations."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Elements (previous)</a><a rel="next" href="p04-observables" title="Handling reactivity in UI via observables."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Observables and reactivity</a></div></main></body></html>
|
35
docs/p04-observables.html
Normal file
35
docs/p04-observables.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Handling reactivity in UI via observables."><title>`deka-dom-el` — Observables and reactivity</title><!--<dde:mark type="component" name="metaAuthor" host="this" ssr/>--><meta name="author" content="Jan Andrle"><link type="text/plain" rel="author" href="https://jaandrle.github.io/humans.txt"><meta name="generator" content="deka-dom-el"><!--<dde:mark type="component" name="metaTwitter" host="this" ssr/>--><meta name="twitter:card" content="summary_large_image"><meta name="twitter:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="twitter:title" content="deka-dom-el"><meta name="twitter:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="twitter:creator" content="@jaandrle"><!--<dde:mark type="component" name="metaFacebook" host="this" ssr/>--><meta name="og:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="og:title" content="deka-dom-el"><meta name="og:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="og:creator" content="@jaandrle"><script src="https://cdn.jsdelivr.net/npm/shiki" defer=""></script><script type="module" src="code.js.js"></script><script src="https://flems.io/flems.html" type="text/javascript" charset="utf-8"></script><link rel="stylesheet" href="global.css"></head><body><!--<dde:mark type="component" name="page" host="this" ssr/>--><!--<dde:mark type="component" name="simplePage" host="this" ssr/>--><!--<dde:mark type="component" name="header" host="this" ssr/>--><header><h1>`deka-dom-el` — Observables and reactivity</h1><p>Handling reactivity in UI via observables.</p></header><nav><a href="https://github.com/jaandrle/deka-dom-el"><svg class="icon" viewBox="0 0 32 32"><!--<dde:mark type="component" name="iconGitHub" host="parentElement" ssr/>--><path d="M 16,0.395c -8.836,0 -16,7.163 -16,16c 0,7.069 4.585,13.067 10.942,15.182c 0.8,0.148 1.094,-0.347 1.094,-0.77c 0,-0.381 -0.015,-1.642 -0.022,-2.979c -4.452,0.968 -5.391,-1.888 -5.391,-1.888c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341c -1.452,-0.993 0.11,-0.973 0.11,-0.973c 1.606,0.113 2.452,1.649 2.452,1.649c 1.427,2.446 3.743,1.739 4.656,1.33c 0.143,-1.034 0.558,-1.74 1.016,-2.14c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907c 0,-1.747 0.625,-3.174 1.649,-4.295c -0.166,-0.403 -0.714,-2.03 0.155,-4.234c 0,0 1.344,-0.43 4.401,1.64c 1.276,-0.355 2.645,-0.532 4.005,-0.539c 1.359,0.006 2.729,0.184 4.008,0.539c 3.054,-2.07 4.395,-1.64 4.395,-1.64c 0.871,2.204 0.323,3.831 0.157,4.234c 1.026,1.12 1.647,2.548 1.647,4.295c 0,6.145 -3.743,7.498 -7.306,7.895c 0.574,0.497 1.085,1.47 1.085,2.963c 0,2.141 -0.019,3.864 -0.019,4.391c 0,0.426 0.288,0.925 1.099,0.768c 6.354,-2.118 10.933,-8.113 10.933,-15.18c 0,-8.837 -7.164,-16 -16,-16Z"></path></svg>GitHub</a><a href="./" title="Introducing a library.">1. Introduction</a><a href="p02-elements" title="Basic concepts of elements modifications and creations.">2. Elements</a><a href="p03-events" title="Using not only events in UI declaratively.">3. Events and Addons</a><a href="p04-observables" title="Handling reactivity in UI via observables." class="current">4. Observables and reactivity</a></nav><main><h2>Using observables to manage reactivity</h2><p>How a program responds to variable data or user interactions is one of the fundamental problems of programming. If we desire to solve the issue in a declarative manner, observables may be a viable approach.</p><div class="code" data-js="todo"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">// when NPM
|
||||||
|
import { O } from "deka-dom-el/observables";
|
||||||
|
// https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {ddeObservable}
|
||||||
|
* */
|
||||||
|
/**
|
||||||
|
* @type {ddeActions}
|
||||||
|
* */
|
||||||
|
</code></div><h3 id="h-introducing-observables"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-introducing-observables" tabindex="-1">#</a> Introducing observables</h3><p>Using observables, we split program logic into the three parts. Firstly (α), we create a variable (constant) representing reactive value. Somewhere later, we can register (β) a logic reacting to the observable value changes. Similarly, in a remaining part (γ), we can update the observable value.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-v1fw44pkzuo" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { O } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
||||||
|
// α — `observable` represents a reactive value
|
||||||
|
const observable= O(0);
|
||||||
|
// β — just reacts on observable changes
|
||||||
|
O.on(observable, console.log);
|
||||||
|
// γ — just updates the value
|
||||||
|
observable(observable()+1);
|
||||||
|
setInterval(()=> observable(observable()+1), 5000);
|
||||||
|
</code></div><script>Flems(document.getElementById("code-example-1-v1fw44pkzuo"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\n// α — `observable` represents a reactive value\\nconst observable= O(0);\\n// β — just reacts on observable changes\\nO.on(observable, console.log);\\n// γ — just updates the value\\nobservable(observable()+1);\\nsetInterval(()=> observable(observable()+1), 5000);\\n\"}],\"toolbar\":false}"));</script><p>All this is just an example of <a href="https://en.wikipedia.org/wiki/Event-driven_programming" title="Wikipedia: Event-driven programming">Event-driven programming</a> and <a href="https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern" title="Wikipedia: Publish–subscribe pattern">Publish–subscribe pattern</a> (compare for example with <a href="https://www.npmjs.com/package/fpubsub" title="NPM package: fpubsub">fpubsub library</a>). All three parts can be in some manner independent and still connected to the same reactive entity.</p><p>Observables are implemented in the library as functions. To see current value of observable, just call it without any arguments <code>console.log(observable())</code>. To update the observable value, pass any argument <code>observable('a new value')</code>. For listenning the observable value changes, use <code>O.on(observable, console.log)</code>.</p><p>Similarly to the <code>on</code> function to register DOM events listener. You can use <code>AbortController</code>/<code>AbortSignal</code> to <em>off</em>/stop listenning. For representing “live” piece of code computation pattern:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-1ti9ynadhw5c" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { O } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
||||||
|
const observable= O(0);
|
||||||
|
// computation pattern
|
||||||
|
const double= O(()=> 2*observable());
|
||||||
|
|
||||||
|
const ac= new AbortController();
|
||||||
|
O.on(observable, v=> console.log("observable", v), { signal: ac.signal });
|
||||||
|
O.on(double, v=> console.log("double", v), { signal: ac.signal });
|
||||||
|
|
||||||
|
observable(observable()+1);
|
||||||
|
const interval= 5000;
|
||||||
|
const id= setInterval(()=> observable(observable()+1), interval);
|
||||||
|
ac.signal.addEventListener("abort",
|
||||||
|
()=> setTimeout(()=> clearInterval(id), 2*interval));
|
||||||
|
|
||||||
|
setTimeout(()=> ac.abort(), 3*interval)
|
||||||
|
</code></div><script>Flems(document.getElementById("code-example-1-1ti9ynadhw5c"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst observable= O(0);\\n// computation pattern\\nconst double= O(()=> 2*observable());\\n\\nconst ac= new AbortController();\\nO.on(observable, v=> console.log(\\\"observable\\\", v), { signal: ac.signal });\\nO.on(double, v=> console.log(\\\"double\\\", v), { signal: ac.signal });\\n\\nobservable(observable()+1);\\nconst interval= 5000;\\nconst id= setInterval(()=> observable(observable()+1), interval);\\nac.signal.addEventListener(\\\"abort\\\",\\n\\t()=> setTimeout(()=> clearInterval(id), 2*interval));\\n\\nsetTimeout(()=> ac.abort(), 3*interval)\\n\"}],\"toolbar\":false}"));</script><div class="notice"><!--<dde:mark type="component" name="mnemonicUl" host="parentElement" ssr/>--><h3 id="h-mnemonic"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-mnemonic" tabindex="-1">#</a> Mnemonic</h3><ul><li><code>O(<value>)</code> — observable: reactive value</li><li><code>O(()=> <computation>)</code> — observable: reactive value dependent on calculation using other observables</li><li><code>O.on(<observable>, <listener>[, <options>])</code> — listen to the observable value changes</li><li><code>O.clear(...<observables>)</code> — off and clear observables</li><li><code>O(<value>, <actions>)</code> — observable: pattern to create complex reactive objects/arrays</li><li><code>O.action(<observable>, <action-name>, ...<action-arguments>)</code> — invoke an action for given observable</li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="p03-events" title="Using not only events in UI declaratively."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Events and Addons (previous)</a><!--<dde:mark type="component" name="pageLink" host="this" ssr/>--></div></main></body></html>
|
@ -26,7 +26,7 @@ import { relative } from "node:path";
|
|||||||
export function example({ src, language= "js", page_id }){
|
export function example({ src, language= "js", page_id }){
|
||||||
registerClientPart(page_id);
|
registerClientPart(page_id);
|
||||||
const content= s.cat(src).toString()
|
const content= s.cat(src).toString()
|
||||||
.replaceAll(/ from "deka-dom-el(\/signals)?";/g, ' from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";');
|
.replaceAll(/ from "deka-dom-el(\/observables)?";/g, ' from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";');
|
||||||
const id= "code-example-"+generateCodeId(src);
|
const id= "code-example-"+generateCodeId(src);
|
||||||
return el().append(
|
return el().append(
|
||||||
el(code, { id, content, language, className: example.name }),
|
el(code, { id, content, language, className: example.name }),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { el } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
import { S } from "deka-dom-el/signals";
|
import { S } from "deka-dom-el/observables";
|
||||||
const clicks= S(0);
|
const clicks= S(0);
|
||||||
document.body.append(
|
document.body.append(
|
||||||
el().append(
|
el().append(
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
import { O } from "deka-dom-el/observables";
|
||||||
|
const observable= O(0);
|
||||||
|
// computation pattern
|
||||||
|
const double= O(()=> 2*observable());
|
||||||
|
|
||||||
|
const ac= new AbortController();
|
||||||
|
O.on(observable, v=> console.log("observable", v), { signal: ac.signal });
|
||||||
|
O.on(double, v=> console.log("double", v), { signal: ac.signal });
|
||||||
|
|
||||||
|
observable(observable()+1);
|
||||||
|
const interval= 5000;
|
||||||
|
const id= setInterval(()=> observable(observable()+1), interval);
|
||||||
|
ac.signal.addEventListener("abort",
|
||||||
|
()=> setTimeout(()=> clearInterval(id), 2*interval));
|
||||||
|
|
||||||
|
setTimeout(()=> ac.abort(), 3*interval)
|
@ -1,9 +1,9 @@
|
|||||||
// when NPM
|
// when NPM
|
||||||
import { S } from "deka-dom-el/signals";
|
import { O } from "deka-dom-el/observables";
|
||||||
// https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js
|
// https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {ddeSignal}
|
* @type {ddeObservable}
|
||||||
* */
|
* */
|
||||||
/**
|
/**
|
||||||
* @type {ddeActions}
|
* @type {ddeActions}
|
8
docs_src/components/examples/observables/observables.js
Normal file
8
docs_src/components/examples/observables/observables.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { O } from "deka-dom-el/observables";
|
||||||
|
// α — `observable` represents a reactive value
|
||||||
|
const observable= O(0);
|
||||||
|
// β — just reacts on observable changes
|
||||||
|
O.on(observable, console.log);
|
||||||
|
// γ — just updates the value
|
||||||
|
observable(observable()+1);
|
||||||
|
setInterval(()=> observable(observable()+1), 5000);
|
@ -1,16 +0,0 @@
|
|||||||
import { S } from "deka-dom-el/signals";
|
|
||||||
const signal= S(0);
|
|
||||||
// computation pattern
|
|
||||||
const double= S(()=> 2*signal());
|
|
||||||
|
|
||||||
const ac= new AbortController();
|
|
||||||
S.on(signal, v=> console.log("signal", v), { signal: ac.signal });
|
|
||||||
S.on(double, v=> console.log("double", v), { signal: ac.signal });
|
|
||||||
|
|
||||||
signal(signal()+1);
|
|
||||||
const interval= 5000;
|
|
||||||
const id= setInterval(()=> signal(signal()+1), interval);
|
|
||||||
ac.signal.addEventListener("abort",
|
|
||||||
()=> setTimeout(()=> clearInterval(id), 2*interval));
|
|
||||||
|
|
||||||
setTimeout(()=> ac.abort(), 3*interval)
|
|
@ -1,8 +0,0 @@
|
|||||||
import { S } from "deka-dom-el/signals";
|
|
||||||
// α — `signal` represents a reactive value
|
|
||||||
const signal= S(0);
|
|
||||||
// β — just reacts on signal changes
|
|
||||||
S.on(signal, console.log);
|
|
||||||
// γ — just updates the value
|
|
||||||
signal(signal()+1);
|
|
||||||
setInterval(()=> signal(signal()+1), 5000);
|
|
@ -23,10 +23,10 @@ export function page({ pkg, info }){
|
|||||||
el("p").append(
|
el("p").append(
|
||||||
"Next step is providing interactivity not only for our UI templates.",
|
"Next step is providing interactivity not only for our UI templates.",
|
||||||
" ",
|
" ",
|
||||||
"We introduce signals (", el("code", "S"), ") and how them incorporate to UI templates.",
|
"We introduce observables (", el("code", "O"), ") and how them incorporate to UI templates.",
|
||||||
),
|
),
|
||||||
el("p").append(
|
el("p").append(
|
||||||
"Now we will clarify how the signals are incorporated into our templates with regard ",
|
"Now we will clarify how the observables are incorporated into our templates with regard ",
|
||||||
"to application performance. This is not the only reason the library uses ",
|
"to application performance. This is not the only reason the library uses ",
|
||||||
el("code", "scope"), "s. We will look at how they work in components represented ",
|
el("code", "scope"), "s. We will look at how they work in components represented ",
|
||||||
"in JavaScript by functions."
|
"in JavaScript by functions."
|
||||||
|
@ -64,7 +64,7 @@ export function page({ pkg, info }){
|
|||||||
"This is handy to concat conditional classes."
|
"This is handy to concat conditional classes."
|
||||||
),
|
),
|
||||||
el("li").append(
|
el("li").append(
|
||||||
"Use ", el("code", "classList"), " to toggle specific classes. This will be handy later when the reactivity via signals is beeing introduced.",
|
"Use ", el("code", "classList"), " to toggle specific classes. This will be handy later when the reactivity via observables is beeing introduced.",
|
||||||
),
|
),
|
||||||
el("li").append(
|
el("li").append(
|
||||||
"The ", el("code", "assign"), " also accepts the ", el("code", "undefined"), " as a value for any property to remove it from the element declaratively. ",
|
"The ", el("code", "assign"), " also accepts the ", el("code", "undefined"), " as a value for any property to remove it from the element declaratively. ",
|
||||||
|
@ -12,24 +12,24 @@ const fileURL= url=> new URL(url, import.meta.url);
|
|||||||
export function page({ pkg, info }){
|
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("h2", "Using signals to manage reactivity"),
|
el("h2", "Using observables to manage reactivity"),
|
||||||
el("p").append(
|
el("p").append(
|
||||||
"How a program responds to variable data or user",
|
"How a program responds to variable data or user",
|
||||||
" interactions is one of the fundamental problems of programming.",
|
" interactions is one of the fundamental problems of programming.",
|
||||||
" If we desire to solve the issue in a declarative manner,",
|
" If we desire to solve the issue in a declarative manner,",
|
||||||
" signals may be a viable approach.",
|
" observables may be a viable approach.",
|
||||||
),
|
),
|
||||||
el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/observables/intro.js"), page_id }),
|
||||||
|
|
||||||
el(h3, "Introducing signals"),
|
el(h3, "Introducing observables"),
|
||||||
el("p").append(
|
el("p").append(
|
||||||
"Using signals, we split program logic into the three parts.",
|
"Using observables, we split program logic into the three parts.",
|
||||||
" Firstly (α), we create a variable (constant) representing reactive",
|
" Firstly (α), we create a variable (constant) representing reactive",
|
||||||
" value. Somewhere later, we can register (β) a logic reacting",
|
" value. Somewhere later, we can register (β) a logic reacting",
|
||||||
" to the signal value changes. Similarly, in a remaining part (γ), we",
|
" to the observable value changes. Similarly, in a remaining part (γ), we",
|
||||||
" can update the signal value."
|
" can update the observable value."
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/observables/observables.js"), page_id }),
|
||||||
el("p").append(
|
el("p").append(
|
||||||
"All this is just an example of ",
|
"All this is just an example of ",
|
||||||
el("a", { textContent: "Event-driven programming", href: "https://en.wikipedia.org/wiki/Event-driven_programming", title: "Wikipedia: Event-driven programming" }),
|
el("a", { textContent: "Event-driven programming", href: "https://en.wikipedia.org/wiki/Event-driven_programming", title: "Wikipedia: Event-driven programming" }),
|
||||||
@ -40,35 +40,35 @@ export function page({ pkg, info }){
|
|||||||
" to the same reactive entity."
|
" to the same reactive entity."
|
||||||
),
|
),
|
||||||
el("p").append(
|
el("p").append(
|
||||||
"Signals are implemented in the library as functions. To see current value",
|
"Observables are implemented in the library as functions. To see current value",
|
||||||
" of signal, just call it without any arguments ", el("code", "console.log(signal())"), ".",
|
" of observable, just call it without any arguments ", el("code", "console.log(observable())"), ".",
|
||||||
" To update the signal value, pass any argument ", el("code", "signal('a new value')"), ".",
|
" To update the observable value, pass any argument ", el("code", "observable('a new value')"), ".",
|
||||||
" For listenning the signal value changes, use ", el("code", "S.on(signal, console.log)"), "."
|
" For listenning the observable value changes, use ", el("code", "O.on(observable, console.log)"), "."
|
||||||
),
|
),
|
||||||
el("p").append(
|
el("p").append(
|
||||||
"Similarly to the ", el("code", "on"), " function to register DOM events listener.",
|
"Similarly to the ", el("code", "on"), " function to register DOM events listener.",
|
||||||
" You can use ", el("code", "AbortController"), "/", el("code", "AbortSignal"), " to",
|
" You can use ", el("code", "AbortController"), "/", el("code", "AbortSignal"), " to",
|
||||||
" ", el("em", "off"), "/stop listenning. For representing “live” piece of code computation pattern:"
|
" ", el("em", "off"), "/stop listenning. For representing “live” piece of code computation pattern:"
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/signals/computations-abort.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/observables/computations-abort.js"), page_id }),
|
||||||
el(mnemonicUl).append(
|
el(mnemonicUl).append(
|
||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "S(<value>)"), " — signal: reactive value",
|
el("code", "O(<value>)"), " — observable: reactive value",
|
||||||
),
|
),
|
||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "S(()=> <computation>)"), " — signal: reactive value dependent on calculation using other signals",
|
el("code", "O(()=> <computation>)"), " — observable: reactive value dependent on calculation using other observables",
|
||||||
),
|
),
|
||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "S.on(<signal>, <listener>[, <options>])"), " — listen to the signal value changes",
|
el("code", "O.on(<observable>, <listener>[, <options>])"), " — listen to the observable value changes",
|
||||||
),
|
),
|
||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "S.clear(...<signals>)"), " — off and clear signals",
|
el("code", "O.clear(...<observables>)"), " — off and clear observables",
|
||||||
),
|
),
|
||||||
el("li").append(
|
el("li").append(
|
||||||
el("code", "S(<value>, <actions>)"), " — signal: pattern to create complex reactive objects/arrays",
|
el("code", "O(<value>, <actions>)"), " — observable: 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", "O.action(<observable>, <action-name>, ...<action-arguments>)"), " — invoke an action for given observable"
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
);
|
);
|
@ -6,7 +6,7 @@ export const pages= [
|
|||||||
{ id: "index", href: "./", title: "Introduction", description: "Introducing a library." },
|
{ id: "index", href: "./", title: "Introduction", description: "Introducing a library." },
|
||||||
{ id: "p02-elements", href: "p02-elements", title: "Elements", description: "Basic concepts of elements modifications and creations." },
|
{ id: "p02-elements", href: "p02-elements", title: "Elements", description: "Basic concepts of elements modifications and creations." },
|
||||||
{ id: "p03-events", href: "p03-events", title: "Events and Addons", description: "Using not only events in UI declaratively." },
|
{ id: "p03-events", href: "p03-events", title: "Events and Addons", description: "Using not only events in UI declaratively." },
|
||||||
{ id: "p04-signals", href: "p04-signals", title: "Signals and reactivity", description: "Handling reactivity in UI via signals." },
|
{ id: "p04-observables", href: "p04-observables", title: "Observables and reactivity", description: "Handling reactivity in UI via observables." },
|
||||||
];
|
];
|
||||||
/**
|
/**
|
||||||
* @typedef registerClientFile
|
* @typedef registerClientFile
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { style, el, elNS, on, S, scope } from '../exports.js';
|
import { style, el, elNS, on, O, scope } from '../exports.js';
|
||||||
const className= style.host(fullNameComponent).css`
|
const className= style.host(fullNameComponent).css`
|
||||||
:host form{
|
:host form{
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -7,8 +7,8 @@ const className= style.host(fullNameComponent).css`
|
|||||||
`;
|
`;
|
||||||
export function fullNameComponent(){
|
export function fullNameComponent(){
|
||||||
const labels= [ "Name", "Surname" ];
|
const labels= [ "Name", "Surname" ];
|
||||||
const name= labels.map(_=> S(""));
|
const name= labels.map(_=> O(""));
|
||||||
const full_name= S(()=>
|
const full_name= O(()=>
|
||||||
name.map(l=> l()).filter(Boolean).join(" ") || "-");
|
name.map(l=> l()).filter(Boolean).join(" ") || "-");
|
||||||
scope.host(
|
scope.host(
|
||||||
on.connected(()=> console.log(fullNameComponent)),
|
on.connected(()=> console.log(fullNameComponent)),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { style, el, dispatchEvent, on, S, scope } from '../exports.js';
|
import { style, el, dispatchEvent, on, O, scope } from '../exports.js';
|
||||||
const className= style.host(todosComponent).css`
|
const className= style.host(todosComponent).css`
|
||||||
:host{
|
:host{
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -16,23 +16,23 @@ const className= style.host(todosComponent).css`
|
|||||||
`;
|
`;
|
||||||
/** @param {{ todos: string[] }} */
|
/** @param {{ todos: string[] }} */
|
||||||
export function todosComponent({ todos= [ "Task A" ] }= {}){
|
export function todosComponent({ todos= [ "Task A" ] }= {}){
|
||||||
const todosS= S(todos.map(t=> S(t)), {
|
const todosS= O(todos.map(t=> O(t)), {
|
||||||
add(v){ this.value.push(S(v)); },
|
add(v){ this.value.push(O(v)); },
|
||||||
remove(i){ S.clear(this.value.splice(i, 1)[0]); }
|
remove(i){ O.clear(this.value.splice(i, 1)[0]); }
|
||||||
});
|
});
|
||||||
|
|
||||||
const name= "todoName";
|
const name= "todoName";
|
||||||
const onsubmitAdd= on("submit", event=> {
|
const onsubmitAdd= on("submit", event=> {
|
||||||
const el= event.target.elements[name];
|
const el= event.target.elements[name];
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
S.action(todosS, "add", el.value);
|
O.action(todosS, "add", el.value);
|
||||||
el.value= "";
|
el.value= "";
|
||||||
});
|
});
|
||||||
const onremove= on("remove", event=>
|
const onremove= on("remove", event=>
|
||||||
S.action(todosS, "remove", event.detail));
|
O.action(todosS, "remove", event.detail));
|
||||||
|
|
||||||
const ul_todos= el("ul").append(
|
const ul_todos= el("ul").append(
|
||||||
S.el(todosS, ts=> ts
|
O.el(todosS, ts=> ts
|
||||||
.map((textContent, value)=>
|
.map((textContent, value)=>
|
||||||
el(todoComponent, { textContent, value, className }, onremove))
|
el(todoComponent, { textContent, value, className }, onremove))
|
||||||
));
|
));
|
||||||
@ -40,7 +40,7 @@ export function todosComponent({ todos= [ "Task A" ] }= {}){
|
|||||||
el("div").append(
|
el("div").append(
|
||||||
el("h2", "Todos:"),
|
el("h2", "Todos:"),
|
||||||
el("h3", "List of todos:"),
|
el("h3", "List of todos:"),
|
||||||
S.el(todosS, ts=> !ts.length
|
O.el(todosS, ts=> !ts.length
|
||||||
? el("p", "No todos yet")
|
? el("p", "No todos yet")
|
||||||
: ul_todos),
|
: ul_todos),
|
||||||
el("p", "Click to the text to edit it.")
|
el("p", "Click to the text to edit it.")
|
||||||
@ -54,7 +54,7 @@ export function todosComponent({ todos= [ "Task A" ] }= {}){
|
|||||||
),
|
),
|
||||||
el("div").append(
|
el("div").append(
|
||||||
el("h3", "Output (JSON):"),
|
el("h3", "Output (JSON):"),
|
||||||
el("output", S(()=> JSON.stringify(todosS, null, "\t")))
|
el("output", O(()=> JSON.stringify(todosS, null, "\t")))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -69,13 +69,13 @@ function todoComponent({ textContent, value }){
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
dispatchEvent("remove")(host(), value);
|
dispatchEvent("remove")(host(), value);
|
||||||
});
|
});
|
||||||
const is_editable= S(false);
|
const is_editable= O(false);
|
||||||
const onedited= on("change", ev=> {
|
const onedited= on("change", ev=> {
|
||||||
textContent(ev.target.value);
|
textContent(ev.target.value);
|
||||||
is_editable(false);
|
is_editable(false);
|
||||||
});
|
});
|
||||||
return el("li").append(
|
return el("li").append(
|
||||||
S.el(is_editable, is=> is
|
O.el(is_editable, is=> is
|
||||||
? el("input", { value: textContent(), type: "text" }, onedited)
|
? el("input", { value: textContent(), type: "text" }, onedited)
|
||||||
: el("span", { textContent, onclick: is_editable.bind(null, true) }),
|
: el("span", { textContent, onclick: is_editable.bind(null, true) }),
|
||||||
),
|
),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { el, on, scope } from "../../index.js";
|
import { el, on, scope } from "../../index.js";
|
||||||
import { S } from "../../signals.js";
|
import { O } from "../../observables.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compatible with `npx -y web-component-analyzer examples/components/webComponent.js`
|
* Compatible with `npx -y web-component-analyzer examples/components/webComponent.js`
|
||||||
@ -24,8 +24,8 @@ export class CustomHTMLTestElement extends HTMLElement{
|
|||||||
on.attributeChanged(e=> console.log(e)),
|
on.attributeChanged(e=> console.log(e)),
|
||||||
on.disconnected(()=> console.log(CustomHTMLTestElement))
|
on.disconnected(()=> console.log(CustomHTMLTestElement))
|
||||||
);
|
);
|
||||||
const name= S.attribute("name");
|
const name= O.attribute("name");
|
||||||
const preName= S.attribute("pre-name");
|
const preName= O.attribute("pre-name");
|
||||||
|
|
||||||
console.log({ name, test, preName});
|
console.log({ name, test, preName});
|
||||||
return el("p").append(
|
return el("p").append(
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import * as dde_dom from "../index.js";
|
import * as dde_dom from "../index.js";
|
||||||
export * from "../index.js";
|
export * from "../index.js";
|
||||||
import * as dde_s from "../signals.js";
|
import * as dde_s from "../observables.js";
|
||||||
export * from "../signals.js";
|
export * from "../observables.js";
|
||||||
Object.assign(globalThis, dde_dom, dde_s);
|
Object.assign(globalThis, dde_dom, dde_s);
|
||||||
//import * as dde_dom from "../dist/esm-with-signals.js";
|
//import * as dde_dom from "../dist/esm-with-observables.js";
|
||||||
//export * from "../dist/esm-with-signals.js";
|
//export * from "../dist/esm-with-observables.js";
|
||||||
//Object.assign(globalThis, dde_dom);
|
//Object.assign(globalThis, dde_dom);
|
||||||
export const style= createStyle();
|
export const style= createStyle();
|
||||||
|
|
||||||
|
2
index-with-observables.d.ts
vendored
Normal file
2
index-with-observables.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./index";
|
||||||
|
export * from "./observables";
|
2
index-with-observables.js
Normal file
2
index-with-observables.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./index.js";
|
||||||
|
export * from "./observables.js";
|
2
index-with-signals.d.ts
vendored
2
index-with-signals.d.ts
vendored
@ -1,2 +0,0 @@
|
|||||||
export * from "./index";
|
|
||||||
export * from "./signals";
|
|
@ -1,2 +0,0 @@
|
|||||||
export * from "./index.js";
|
|
||||||
export * from "./signals.js";
|
|
4
index.d.ts
vendored
4
index.d.ts
vendored
@ -152,10 +152,10 @@ export const scope: {
|
|||||||
preventDefault<T extends boolean>(prevent: T): T,
|
preventDefault<T extends boolean>(prevent: T): T,
|
||||||
/**
|
/**
|
||||||
* This represents reference to the current host element — `scope.host()`.
|
* This represents reference to the current host element — `scope.host()`.
|
||||||
* It can be also used to register Addon (function to be called when component is initized)
|
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||||
* — `scope.host(on.connected(console.log))`.
|
* — `scope.host(on.connected(console.log))`.
|
||||||
* */
|
* */
|
||||||
host: ddeElementAddon<any>,
|
host: (...addons: ddeElementAddon<any>[])=> 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. */
|
||||||
|
64
observables.d.ts
vendored
Normal file
64
observables.d.ts
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
export type Observable<V, A>= (set?: V)=> V & A;
|
||||||
|
type Action<V>= (this: { value: V }, ...a: any[])=> typeof observable._ | void;
|
||||||
|
type SymbolOnclear= Symbol;
|
||||||
|
type SymbolObservable= Symbol;
|
||||||
|
type Actions<V>= Record<string, Action<V>>;
|
||||||
|
interface observable{
|
||||||
|
_: Symbol
|
||||||
|
/**
|
||||||
|
* Simple example:
|
||||||
|
* ```js
|
||||||
|
* const hello= S("Hello Observable");
|
||||||
|
* ```
|
||||||
|
* …simple todo observable:
|
||||||
|
* ```js
|
||||||
|
* const todos= S([], {
|
||||||
|
* add(v){ this.value.push(S(v)); },
|
||||||
|
* remove(i){ this.value.splice(i, 1); },
|
||||||
|
* [S.symbols.onclear](){ S.clear(...this.value); },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* …computed observable:
|
||||||
|
* ```js
|
||||||
|
* const name= S("Jan");
|
||||||
|
* const surname= S("Andrle");
|
||||||
|
* const fullname= S(()=> name()+" "+surname());
|
||||||
|
* ```
|
||||||
|
* @param value Initial observable value. Or function computing value from other observables.
|
||||||
|
* @param actions Use to define actions on the observable. Such as add item to the array.
|
||||||
|
* There is also a reserved function `S.symbol.onclear` which is called when the observable is cleared
|
||||||
|
* by `S.clear`.
|
||||||
|
* */
|
||||||
|
<V, A extends Actions<V>>(value: V, actions?: A): Observable<V, A>;
|
||||||
|
/**
|
||||||
|
* Computations observable. This creates a observable which is computed from other observables.
|
||||||
|
* */
|
||||||
|
<V>(computation: ()=> V): Observable<V, {}>
|
||||||
|
action<S extends Observable<any, Actions<any>>, A extends (S extends Observable<any, infer A> ? A : never), N extends keyof A>(
|
||||||
|
observable: S,
|
||||||
|
name: N,
|
||||||
|
...params: A[N] extends (...args: infer P)=> any ? P : never
|
||||||
|
): void;
|
||||||
|
clear(...observables: Observable<any, any>[]): void;
|
||||||
|
on<T>(observable: Observable<T, any>, onchange: (a: T)=> void, options?: AddEventListenerOptions): void;
|
||||||
|
symbols: {
|
||||||
|
observable: SymbolObservable;
|
||||||
|
onclear: SymbolOnclear;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Reactive element, which is rendered based on the given observable.
|
||||||
|
* ```js
|
||||||
|
* S.el(observable, value=> value ? el("b", "True") : el("i", "False"));
|
||||||
|
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
el<S extends any>(observable: Observable<S, any>, el: (v: S)=> Element | Element[]): DocumentFragment;
|
||||||
|
|
||||||
|
attribute(name: string, initial?: string): Observable<string, {}>;
|
||||||
|
}
|
||||||
|
export const observable: observable;
|
||||||
|
export const O: observable;
|
||||||
|
declare global {
|
||||||
|
type ddeObservable<T, A= {}>= Observable<T, A>;
|
||||||
|
type ddeActions<V>= Actions<V>
|
||||||
|
}
|
4
observables.js
Normal file
4
observables.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export { observable, O, isObservable } from "./src/observables-lib.js";
|
||||||
|
import { observables_config } from "./src/observables-lib.js";
|
||||||
|
import { registerReactivity } from "./src/observables-common.js";
|
||||||
|
registerReactivity(observables_config);
|
14
package.json
14
package.json
@ -24,13 +24,13 @@
|
|||||||
"import": "./jsdom.js",
|
"import": "./jsdom.js",
|
||||||
"types": "./jsdom.d.ts"
|
"types": "./jsdom.d.ts"
|
||||||
},
|
},
|
||||||
"./signals": {
|
"./observables": {
|
||||||
"import": "./signals.js",
|
"import": "./observables.js",
|
||||||
"types": "./signals.d.ts"
|
"types": "./observables.d.ts"
|
||||||
},
|
},
|
||||||
"./src/signals-lib": {
|
"./src/observables-lib": {
|
||||||
"import": "./src/signals-lib.js",
|
"import": "./src/observables-lib.js",
|
||||||
"types": "./src/signals.d.ts"
|
"types": "./src/observables.d.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
@ -62,7 +62,7 @@
|
|||||||
"gzip": false
|
"gzip": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "./signals.js",
|
"path": "./observables.js",
|
||||||
"limit": "11.5 kB",
|
"limit": "11.5 kB",
|
||||||
"gzip": false
|
"gzip": false
|
||||||
},
|
},
|
||||||
|
63
signals.d.ts
vendored
63
signals.d.ts
vendored
@ -1,63 +0,0 @@
|
|||||||
export type Signal<V, A>= (set?: V)=> V & A;
|
|
||||||
type Action<V>= (this: { value: V }, ...a: any[])=> typeof S._ | void;
|
|
||||||
type SymbolOnclear= Symbol;
|
|
||||||
type SymbolSignal= Symbol;
|
|
||||||
type Actions<V>= Record<string, Action<V>>;
|
|
||||||
interface S {
|
|
||||||
_: Symbol
|
|
||||||
/**
|
|
||||||
* Simple example:
|
|
||||||
* ```js
|
|
||||||
* const hello= S("Hello Signal");
|
|
||||||
* ```
|
|
||||||
* …simple todo signal:
|
|
||||||
* ```js
|
|
||||||
* const todos= S([], {
|
|
||||||
* add(v){ this.value.push(S(v)); },
|
|
||||||
* remove(i){ this.value.splice(i, 1); },
|
|
||||||
* [S.symbols.onclear](){ S.clear(...this.value); },
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
* …computed signal:
|
|
||||||
* ```js
|
|
||||||
* const name= S("Jan");
|
|
||||||
* const surname= S("Andrle");
|
|
||||||
* const fullname= S(()=> name()+" "+surname());
|
|
||||||
* ```
|
|
||||||
* @param value Initial signal value. Or function computing value from other signals.
|
|
||||||
* @param actions Use to define actions on the signal. Such as add item to the array.
|
|
||||||
* There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared
|
|
||||||
* by `S.clear`.
|
|
||||||
* */
|
|
||||||
<V, A extends Actions<V>>(value: V, actions?: A): Signal<V, A>;
|
|
||||||
/**
|
|
||||||
* Computations signal. This creates a signal which is computed from other signals.
|
|
||||||
* */
|
|
||||||
<V>(computation: ()=> V): Signal<V, {}>
|
|
||||||
action<S extends Signal<any, Actions<any>>, A extends (S extends Signal<any, infer A> ? A : never), N extends keyof A>(
|
|
||||||
signal: S,
|
|
||||||
name: N,
|
|
||||||
...params: A[N] extends (...args: infer P)=> any ? P : never
|
|
||||||
): void;
|
|
||||||
clear(...signals: Signal<any, any>[]): void;
|
|
||||||
on<T>(signal: Signal<T, any>, onchange: (a: T)=> void, options?: AddEventListenerOptions): void;
|
|
||||||
symbols: {
|
|
||||||
signal: SymbolSignal;
|
|
||||||
onclear: SymbolOnclear;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Reactive element, which is rendered based on the given signal.
|
|
||||||
* ```js
|
|
||||||
* S.el(signal, value=> value ? el("b", "True") : el("i", "False"));
|
|
||||||
* S.el(listS, list=> list.map(li=> el("li", li)));
|
|
||||||
* ```
|
|
||||||
* */
|
|
||||||
el<S extends any>(signal: Signal<S, any>, el: (v: S)=> Element | Element[]): DocumentFragment;
|
|
||||||
|
|
||||||
attribute(name: string, initial?: string): Signal<string, {}>;
|
|
||||||
}
|
|
||||||
export const S: S;
|
|
||||||
declare global {
|
|
||||||
type ddeSignal<T, A= {}>= Signal<T, A>;
|
|
||||||
type ddeActions<V>= Actions<V>
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
export { S, isSignal } from "./src/signals-lib.js";
|
|
||||||
import { signals_config } from "./src/signals-lib.js";
|
|
||||||
import { registerReactivity } from "./src/signals-common.js";
|
|
||||||
registerReactivity(signals_config);
|
|
10
src/dom.js
10
src/dom.js
@ -1,4 +1,4 @@
|
|||||||
import { signals } from "./signals-common.js";
|
import { observables } from "./observables-common.js";
|
||||||
import { enviroment } from './dom-common.js';
|
import { enviroment } from './dom-common.js';
|
||||||
|
|
||||||
/** @type {{ scope: object, prevent: boolean, host: function }[]} */
|
/** @type {{ scope: object, prevent: boolean, host: function }[]} */
|
||||||
@ -32,11 +32,11 @@ export function chainableAppend(el){ if(el.append===append) return el; el.append
|
|||||||
let namespace;
|
let namespace;
|
||||||
export function createElement(tag, attributes, ...addons){
|
export function createElement(tag, attributes, ...addons){
|
||||||
/* jshint maxcomplexity: 15 */
|
/* jshint maxcomplexity: 15 */
|
||||||
const s= signals(this);
|
const s= observables(this);
|
||||||
let scoped= 0;
|
let scoped= 0;
|
||||||
let el, el_host;
|
let el, el_host;
|
||||||
//TODO Array.isArray(tag) ⇒ set key (cache els)
|
//TODO Array.isArray(tag) ⇒ set key (cache els)
|
||||||
if(Object(attributes)!==attributes || s.isSignal(attributes))
|
if(Object(attributes)!==attributes || s.isObservable(attributes))
|
||||||
attributes= { textContent: attributes };
|
attributes= { textContent: attributes };
|
||||||
switch(true){
|
switch(true){
|
||||||
case typeof tag==="function": {
|
case typeof tag==="function": {
|
||||||
@ -168,11 +168,11 @@ function assignContext(element, _this){
|
|||||||
if(assign_context.has(element)) return assign_context.get(element);
|
if(assign_context.has(element)) return assign_context.get(element);
|
||||||
const is_svg= element instanceof SVGElement;
|
const is_svg= element instanceof SVGElement;
|
||||||
const setRemoveAttr= (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
|
const setRemoveAttr= (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
|
||||||
const s= signals(_this);
|
const s= observables(_this);
|
||||||
return { setRemoveAttr, s };
|
return { setRemoveAttr, s };
|
||||||
}
|
}
|
||||||
export function classListDeclarative(element, toggle){
|
export function classListDeclarative(element, toggle){
|
||||||
const s= signals(this);
|
const s= observables(this);
|
||||||
forEachEntries(s, toggle,
|
forEachEntries(s, toggle,
|
||||||
(class_name, val)=>
|
(class_name, val)=>
|
||||||
element.classList.toggle(class_name, val===-1 ? undefined : Boolean(val)));
|
element.classList.toggle(class_name, val===-1 ? undefined : Boolean(val)));
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export { registerReactivity } from './signals-common.js';
|
export { registerReactivity } from './observables-common.js';
|
||||||
|
|
||||||
export function dispatchEvent(name, options, host){
|
export function dispatchEvent(name, options, host){
|
||||||
if(!options) options= {};
|
if(!options) options= {};
|
||||||
|
13
src/observables-common.js
Normal file
13
src/observables-common.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export const observables_global= {
|
||||||
|
isObservable(attributes){ return false; },
|
||||||
|
processReactiveAttribute(obj, key, attr, set){ return attr; },
|
||||||
|
};
|
||||||
|
export function registerReactivity(def, global= true){
|
||||||
|
if(global) return Object.assign(observables_global, def);
|
||||||
|
Object.setPrototypeOf(def, observables_global);
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
/** @param {unknown} _this @returns {typeof observables_global} */
|
||||||
|
export function observables(_this){
|
||||||
|
return observables_global.isPrototypeOf(_this) && _this!==observables_global ? _this : observables_global;
|
||||||
|
}
|
@ -1,25 +1,25 @@
|
|||||||
export const mark= Symbol.for("Signal");
|
export const mark= Symbol.for("observable");
|
||||||
|
|
||||||
export function isSignal(candidate){
|
export function isObservable(candidate){
|
||||||
try{ return Reflect.has(candidate, mark); }
|
try{ return Reflect.has(candidate, mark); }
|
||||||
catch(e){ return false; }
|
catch(e){ return false; }
|
||||||
}
|
}
|
||||||
/** @type {function[]} */
|
/** @type {function[]} */
|
||||||
const stack_watch= [];
|
const stack_watch= [];
|
||||||
/**
|
/**
|
||||||
* ### `WeakMap<function, Set<ddeSignal<any, any>>>`
|
* ### `WeakMap<function, Set<ddeObservable<any, any>>>`
|
||||||
* The `Set` is in the form of `[ source, ...depended signals (DSs) ]`.
|
* The `Set` is in the form of `[ source, ...depended observables (DSs) ]`.
|
||||||
* When the DS is cleaned (`S.clear`) it is removed from DSs,
|
* When the DS is cleaned (`S.clear`) it is removed from DSs,
|
||||||
* if remains only one (`source`) it is cleared too.
|
* if remains only one (`source`) it is cleared too.
|
||||||
* ### `WeakMap<object, function>`
|
* ### `WeakMap<object, function>`
|
||||||
* This is used for revesed deps, the `function` is also key for `deps`.
|
* This is used for revesed deps, the `function` is also key for `deps`.
|
||||||
* @type {WeakMap<function|object,Set<ddeSignal<any, any>>|function>}
|
* @type {WeakMap<function|object,Set<ddeObservable<any, any>>|function>}
|
||||||
* */
|
* */
|
||||||
const deps= new WeakMap();
|
const deps= new WeakMap();
|
||||||
export function S(value, actions){
|
export function observable(value, actions){
|
||||||
if(typeof value!=="function")
|
if(typeof value!=="function")
|
||||||
return create(value, actions);
|
return create(value, actions);
|
||||||
if(isSignal(value)) return value;
|
if(isObservable(value)) return value;
|
||||||
|
|
||||||
const out= create();
|
const out= create();
|
||||||
const contextReWatch= function(){
|
const contextReWatch= function(){
|
||||||
@ -32,9 +32,9 @@ export function S(value, actions){
|
|||||||
|
|
||||||
if(!deps_old.length) return;
|
if(!deps_old.length) return;
|
||||||
const deps_curr= deps.get(contextReWatch);
|
const deps_curr= deps.get(contextReWatch);
|
||||||
for (const dep_signal of deps_old){
|
for (const dep_observable of deps_old){
|
||||||
if(deps_curr.has(dep_signal)) continue;
|
if(deps_curr.has(dep_observable)) continue;
|
||||||
removeSignalListener(dep_signal, contextReWatch);
|
removeObservableListener(dep_observable, contextReWatch);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
deps.set(out[mark], contextReWatch);
|
deps.set(out[mark], contextReWatch);
|
||||||
@ -42,44 +42,45 @@ export function S(value, actions){
|
|||||||
contextReWatch();
|
contextReWatch();
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
S.action= function(signal, name, ...a){
|
export { observable as O };
|
||||||
const s= signal[mark], { actions }= s;
|
observable.action= function(observable, name, ...a){
|
||||||
|
const s= observable[mark], { actions }= s;
|
||||||
if(!actions || !Reflect.has(actions, name))
|
if(!actions || !Reflect.has(actions, name))
|
||||||
throw new Error(`'${signal}' has no action with name '${name}'!`);
|
throw new Error(`'${observable}' has no action with name '${name}'!`);
|
||||||
actions[name].apply(s, a);
|
actions[name].apply(s, a);
|
||||||
if(s.skip) return Reflect.deleteProperty(s, "skip");
|
if(s.skip) return Reflect.deleteProperty(s, "skip");
|
||||||
s.listeners.forEach(l=> l(s.value));
|
s.listeners.forEach(l=> l(s.value));
|
||||||
};
|
};
|
||||||
S.on= function on(signals, listener, options= {}){
|
observable.on= function on(observables, listener, options= {}){
|
||||||
const { signal: as }= options;
|
const { observable: as }= options;
|
||||||
if(as && as.aborted) return;
|
if(as && as.aborted) return;
|
||||||
if(Array.isArray(signals)) return signals.forEach(s=> on(s, listener, options));
|
if(Array.isArray(observables)) return observables.forEach(s=> on(s, listener, options));
|
||||||
addSignalListener(signals, listener);
|
addObservableListener(observables, listener);
|
||||||
if(as) as.addEventListener("abort", ()=> removeSignalListener(signals, listener));
|
if(as) as.addEventListener("abort", ()=> removeObservableListener(observables, listener));
|
||||||
//TODO cleanup when signal removed
|
//TODO cleanup when observable removed
|
||||||
};
|
};
|
||||||
S.symbols= {
|
observable.symbols= {
|
||||||
signal: mark,
|
observable: mark,
|
||||||
onclear: Symbol.for("Signal.onclear")
|
onclear: Symbol.for("Observable.onclear")
|
||||||
};
|
};
|
||||||
S.clear= function(...signals){
|
observable.clear= function(...observables){
|
||||||
for(const signal of signals){
|
for(const observable of observables){
|
||||||
Reflect.deleteProperty(signal, "toJSON");
|
Reflect.deleteProperty(observable, "toJSON");
|
||||||
const s= signal[mark];
|
const s= observable[mark];
|
||||||
s.onclear.forEach(f=> f.call(s));
|
s.onclear.forEach(f=> f.call(s));
|
||||||
clearListDeps(signal, s);
|
clearListDeps(observable, s);
|
||||||
Reflect.deleteProperty(signal, mark);
|
Reflect.deleteProperty(observable, mark);
|
||||||
}
|
}
|
||||||
function clearListDeps(signal, s){
|
function clearListDeps(observable, s){
|
||||||
s.listeners.forEach(l=> {
|
s.listeners.forEach(l=> {
|
||||||
s.listeners.delete(l);
|
s.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(signal);
|
ls.delete(observable);
|
||||||
if(ls.size>1) return;
|
if(ls.size>1) return;
|
||||||
|
|
||||||
S.clear(...ls);
|
observable.clear(...ls);
|
||||||
deps.delete(l);
|
deps.delete(l);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -87,7 +88,7 @@ S.clear= function(...signals){
|
|||||||
const key_reactive= "__dde_reactive";
|
const key_reactive= "__dde_reactive";
|
||||||
import { el, elementAttribute } from "./dom.js";
|
import { el, elementAttribute } from "./dom.js";
|
||||||
import { scope } from "./dom.js";
|
import { scope } from "./dom.js";
|
||||||
S.el= function(signal, map){
|
observable.el= function(observable, map){
|
||||||
const mark_start= el.mark({ type: "reactive" }, false);
|
const mark_start= el.mark({ type: "reactive" }, false);
|
||||||
const mark_end= mark_start.end;
|
const mark_end= mark_start.end;
|
||||||
const out= document.createDocumentFragment();
|
const out= document.createDocumentFragment();
|
||||||
@ -95,7 +96,7 @@ S.el= function(signal, map){
|
|||||||
const { current }= scope;
|
const { current }= scope;
|
||||||
const reRenderReactiveElement= v=> {
|
const reRenderReactiveElement= v=> {
|
||||||
if(!mark_start.parentNode || !mark_end.parentNode)
|
if(!mark_start.parentNode || !mark_end.parentNode)
|
||||||
return removeSignalListener(signal, reRenderReactiveElement);
|
return removeObservableListener(observable, reRenderReactiveElement);
|
||||||
scope.push(current);
|
scope.push(current);
|
||||||
let els= map(v);
|
let els= map(v);
|
||||||
scope.pop();
|
scope.pop();
|
||||||
@ -106,16 +107,16 @@ S.el= function(signal, map){
|
|||||||
el_r.remove();
|
el_r.remove();
|
||||||
mark_start.after(...els);
|
mark_start.after(...els);
|
||||||
};
|
};
|
||||||
addSignalListener(signal, reRenderReactiveElement);
|
addObservableListener(observable, reRenderReactiveElement);
|
||||||
removeSignalsFromElements(signal, reRenderReactiveElement, mark_start, map);
|
removeObservablesFromElements(observable, reRenderReactiveElement, mark_start, map);
|
||||||
reRenderReactiveElement(signal());
|
reRenderReactiveElement(observable());
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
import { on } from "./events.js";
|
import { on } from "./events.js";
|
||||||
const key_attributes= "__dde_attributes";
|
const key_attributes= "__dde_attributes";
|
||||||
S.attribute= function(name, initial= null){
|
observable.attribute= function(name, initial= null){
|
||||||
//TODO host=element & reuse existing
|
//TODO host=element & reuse existing
|
||||||
const out= S(initial);
|
const out= observable(initial);
|
||||||
let element;
|
let element;
|
||||||
scope.host(el=> {
|
scope.host(el=> {
|
||||||
element= el;
|
element= el;
|
||||||
@ -127,17 +128,17 @@ S.attribute= function(name, initial= null){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
element[key_attributes]= { [name]: out };
|
element[key_attributes]= { [name]: out };
|
||||||
on.attributeChanged(function attributeChangeToSignal({ detail }){
|
on.attributeChanged(function attributeChangeToObservable({ detail }){
|
||||||
/*! This maps attributes to signals (`S.attribute`).
|
/*! This maps attributes to observables (`S.attribute`).
|
||||||
* Investigate `__dde_attributes` key of the element.*/
|
* Investigate `__dde_attributes` key of the element.*/
|
||||||
const [ name, value ]= detail;
|
const [ name, value ]= detail;
|
||||||
const curr= element[key_attributes][name];
|
const curr= element[key_attributes][name];
|
||||||
if(curr) return curr(value);
|
if(curr) return curr(value);
|
||||||
})(element);
|
})(element);
|
||||||
on.disconnected(function(){
|
on.disconnected(function(){
|
||||||
/*! This removes all signals mapped to attributes (`S.attribute`).
|
/*! This removes all observables mapped to attributes (`S.attribute`).
|
||||||
* Investigate `__dde_attributes` key of the element.*/
|
* Investigate `__dde_attributes` key of the element.*/
|
||||||
S.clear(...Object.values(element[key_attributes]));
|
observable.clear(...Object.values(element[key_attributes]));
|
||||||
})(element);
|
})(element);
|
||||||
});
|
});
|
||||||
return new Proxy(out, {
|
return new Proxy(out, {
|
||||||
@ -150,17 +151,17 @@ S.attribute= function(name, initial= null){
|
|||||||
};
|
};
|
||||||
|
|
||||||
import { typeOf } from './helpers.js';
|
import { typeOf } from './helpers.js';
|
||||||
export const signals_config= {
|
export const observables_config= {
|
||||||
isSignal,
|
isObservable,
|
||||||
processReactiveAttribute(element, key, attrs, set){
|
processReactiveAttribute(element, key, attrs, set){
|
||||||
if(!isSignal(attrs)) return attrs;
|
if(!isObservable(attrs)) return attrs;
|
||||||
const l= attr=> set(key, attr);
|
const l= attr=> set(key, attr);
|
||||||
addSignalListener(attrs, l);
|
addObservableListener(attrs, l);
|
||||||
removeSignalsFromElements(attrs, l, element, key);
|
removeObservablesFromElements(attrs, l, element, key);
|
||||||
return attrs();
|
return attrs();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
function removeSignalsFromElements(signal, listener, ...notes){
|
function removeObservablesFromElements(observable, listener, ...notes){
|
||||||
const { current }= scope;
|
const { current }= scope;
|
||||||
if(current.prevent) return;
|
if(current.prevent) return;
|
||||||
current.host(function(element){
|
current.host(function(element){
|
||||||
@ -168,28 +169,28 @@ function removeSignalsFromElements(signal, listener, ...notes){
|
|||||||
element[key_reactive]= [];
|
element[key_reactive]= [];
|
||||||
on.disconnected(()=>
|
on.disconnected(()=>
|
||||||
/*!
|
/*!
|
||||||
* Clears all signals listeners added in the current scope/host (`S.el`, `assign`, …?).
|
* Clears all Observables listeners added in the current scope/host (`S.el`, `assign`, …?).
|
||||||
* You can investigate the `__dde_reactive` key of the element.
|
* You can investigate the `__dde_reactive` key of the element.
|
||||||
* */
|
* */
|
||||||
element[key_reactive].forEach(([ [ signal, listener ] ])=>
|
element[key_reactive].forEach(([ [ observable, listener ] ])=>
|
||||||
removeSignalListener(signal, listener, signal[mark]?.host() === element))
|
removeObservableListener(observable, listener, observable[mark]?.host() === element))
|
||||||
)(element);
|
)(element);
|
||||||
}
|
}
|
||||||
element[key_reactive].push([ [ signal, listener ], ...notes ]);
|
element[key_reactive].push([ [ observable, listener ], ...notes ]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function create(value, actions){
|
function create(value, actions){
|
||||||
const signal= (...value)=>
|
const observable= (...value)=>
|
||||||
value.length ? write(signal, ...value) : read(signal);
|
value.length ? write(observable, ...value) : read(observable);
|
||||||
return toSignal(signal, value, actions);
|
return toObservable(observable, value, actions);
|
||||||
}
|
}
|
||||||
const protoSigal= Object.assign(Object.create(null), {
|
const protoSigal= Object.assign(Object.create(null), {
|
||||||
stopPropagation(){
|
stopPropagation(){
|
||||||
this.skip= true;
|
this.skip= true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
class SignalDefined extends Error{
|
class ObservableDefined extends Error{
|
||||||
constructor(){
|
constructor(){
|
||||||
super();
|
super();
|
||||||
const [ curr, ...rest ]= this.stack.split("\n");
|
const [ curr, ...rest ]= this.stack.split("\n");
|
||||||
@ -197,64 +198,64 @@ class SignalDefined extends Error{
|
|||||||
this.stack= rest.find(l=> !l.includes(curr_file));
|
this.stack= rest.find(l=> !l.includes(curr_file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function toSignal(signal, value, actions){
|
function toObservable(observable, value, actions){
|
||||||
const onclear= [];
|
const onclear= [];
|
||||||
if(typeOf(actions)!=="[object Object]")
|
if(typeOf(actions)!=="[object Object]")
|
||||||
actions= {};
|
actions= {};
|
||||||
const { onclear: ocs }= S.symbols;
|
const { onclear: ocs }= observable.symbols;
|
||||||
if(actions[ocs]){
|
if(actions[ocs]){
|
||||||
onclear.push(actions[ocs]);
|
onclear.push(actions[ocs]);
|
||||||
Reflect.deleteProperty(actions, ocs);
|
Reflect.deleteProperty(actions, ocs);
|
||||||
}
|
}
|
||||||
const { host }= scope;
|
const { host }= scope;
|
||||||
Reflect.defineProperty(signal, mark, {
|
Reflect.defineProperty(observable, mark, {
|
||||||
value: {
|
value: {
|
||||||
value, actions, onclear, host,
|
value, actions, onclear, host,
|
||||||
listeners: new Set(),
|
listeners: new Set(),
|
||||||
defined: new SignalDefined()
|
defined: new ObservableDefined()
|
||||||
},
|
},
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
writable: false,
|
writable: false,
|
||||||
configurable: true
|
configurable: true
|
||||||
});
|
});
|
||||||
signal.toJSON= ()=> signal();
|
observable.toJSON= ()=> observable();
|
||||||
Object.setPrototypeOf(signal[mark], protoSigal);
|
Object.setPrototypeOf(observable[mark], protoSigal);
|
||||||
return signal;
|
return observable;
|
||||||
}
|
}
|
||||||
function currentContext(){
|
function currentContext(){
|
||||||
return stack_watch[stack_watch.length - 1];
|
return stack_watch[stack_watch.length - 1];
|
||||||
}
|
}
|
||||||
function read(signal){
|
function read(observable){
|
||||||
if(!signal[mark]) return;
|
if(!observable[mark]) return;
|
||||||
const { value, listeners }= signal[mark];
|
const { value, listeners }= observable[mark];
|
||||||
const context= currentContext();
|
const context= currentContext();
|
||||||
if(context) listeners.add(context);
|
if(context) listeners.add(context);
|
||||||
if(deps.has(context)) deps.get(context).add(signal);
|
if(deps.has(context)) deps.get(context).add(observable);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
function write(signal, value, force){
|
function write(observable, value, force){
|
||||||
if(!signal[mark]) return;
|
if(!observable[mark]) return;
|
||||||
const s= signal[mark];
|
const s= observable[mark];
|
||||||
if(!force && s.value===value) return;
|
if(!force && s.value===value) return;
|
||||||
s.value= value;
|
s.value= value;
|
||||||
s.listeners.forEach(l=> l(value));
|
s.listeners.forEach(l=> l(value));
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addSignalListener(signal, listener){
|
function addObservableListener(observable, listener){
|
||||||
if(!signal[mark]) return;
|
if(!observable[mark]) return;
|
||||||
return signal[mark].listeners.add(listener);
|
return observable[mark].listeners.add(listener);
|
||||||
}
|
}
|
||||||
function removeSignalListener(signal, listener, clear_when_empty){
|
function removeObservableListener(observable, listener, clear_when_empty){
|
||||||
const s= signal[mark];
|
const s= observable[mark];
|
||||||
if(!s) return;
|
if(!s) return;
|
||||||
const out= s.listeners.delete(listener);
|
const out= s.listeners.delete(listener);
|
||||||
if(clear_when_empty && !s.listeners.size){
|
if(clear_when_empty && !s.listeners.size){
|
||||||
S.clear(signal);
|
observable.clear(observable);
|
||||||
if(!deps.has(s)) return out;
|
if(!deps.has(s)) return out;
|
||||||
const c= deps.get(s);
|
const c= deps.get(s);
|
||||||
if(!deps.has(c)) return out;
|
if(!deps.has(c)) return out;
|
||||||
deps.get(c).forEach(sig=> removeSignalListener(sig, c, true));
|
deps.get(c).forEach(sig=> removeObservableListener(sig, c, true));
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
@ -1,13 +0,0 @@
|
|||||||
export const signals_global= {
|
|
||||||
isSignal(attributes){ return false; },
|
|
||||||
processReactiveAttribute(obj, key, attr, set){ return attr; },
|
|
||||||
};
|
|
||||||
export function registerReactivity(def, global= true){
|
|
||||||
if(global) return Object.assign(signals_global, def);
|
|
||||||
Object.setPrototypeOf(def, signals_global);
|
|
||||||
return def;
|
|
||||||
}
|
|
||||||
/** @param {unknown} _this @returns {typeof signals_global} */
|
|
||||||
export function signals(_this){
|
|
||||||
return signals_global.isPrototypeOf(_this) && _this!==signals_global ? _this : signals_global;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user