1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2025-04-01 19:55:53 +02:00

Compare commits

...

14 Commits

Author SHA1 Message Date
17e40fdd9c 🔤 2025-03-05 19:29:03 +01:00
05413cb2bb 🔤 2025-03-05 18:59:37 +01:00
5a6f011823 🔤 ui/ux 2025-03-05 18:40:09 +01:00
49243b978a 🐛 🔤 types 3ps 2025-03-05 18:00:04 +01:00
02f7b3fd67 🔤 logo 2025-03-05 16:54:09 +01:00
7078ec68c1 🐛 wrong file(s) in git 2025-03-05 16:24:47 +01:00
41d7728d18 🐛 fixes completitions for el with components 2025-03-05 16:24:06 +01:00
8f0879196f 🔤 intro 2025-03-05 15:29:59 +01:00
9ed6de2f8a 🔤 elements 2025-03-05 14:47:29 +01:00
2a3b6dc5cd 🔤 events 2025-03-05 14:39:28 +01:00
1c5f0dab5e dispatch 2025-03-05 14:39:19 +01:00
e1f2b32736 🔤UI 2025-03-05 14:08:21 +01:00
e2df9705d1 🔤 updates texts 2025-03-05 11:53:32 +01:00
209fa49dee 🔤 UI 2025-03-05 10:14:09 +01:00
56 changed files with 1458 additions and 917 deletions

166
README.md
View File

@ -2,94 +2,114 @@
| [source code on GitHub](https://github.com/jaandrle/deka-dom-el)
| [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el)
<p align="center">
<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180">
</p>
# Deka DOM Elements
***Vanilla for flavouring — a full-fledged feast for large projects***
*…use simple DOM API by default and library tools and logic when you need them*
```javascript
// 🌟 Reactive component with clear separation of concerns
document.body.append(
el(HelloWorldComponent, { initial: "🚀" })
el(EmojiCounter, { initial: "🚀" })
);
/** @typedef {"🎉" | "🚀"} Emoji */
/** @param {{ initial: Emoji }} attrs */
function HelloWorldComponent({ initial }){
const clicks= S(0);
const emoji= S(initial);
/** @param {HTMLOptionElement} el */
const isSelected= el=> (el.selected= el.value===initial);
// @ts-expect-error 2339: The <select> has only two options with {@link Emoji}
const onChange= on("change", event=> emoji.set(event.target.value));
return el().append(
el("p", {
textContent: S(() => `Hello World ${emoji.get().repeat(clicks.get())}`),
className: "example",
ariaLive: "polite", //OR ariaset: { live: "polite" },
dataset: { example: "Example" }, //OR dataExample: "Example",
}),
el("button",
{ textContent: "Fire", type: "button" },
on("click", ()=> clicks.set(clicks.get() + 1)),
on("keyup", ()=> clicks.set(clicks.get() - 2)),
),
el("select", null, onChange).append(
el(OptionComponent, "🎉", isSelected),//OR { textContent: "🎉" }
el(OptionComponent, "🚀", isSelected),//OR { textContent: "🚀" }
)
);
function EmojiCounter({ initial }) {
// ✨ State - Define reactive data
const count = S(0);
const emoji = S(initial);
/** @param {HTMLOptionElement} el */
const isSelected= el=> (el.selected= el.value===initial);
// 🔄 View - UI updates automatically when signals change
return el().append(
el("p", {
className: "output",
textContent: S(() =>
`Hello World ${emoji.get().repeat(clicks.get())}`),
}),
// 🎮 Controls - Update state on events
el("button", { textContent: "Add Emoji" },
on("click", () => count.set(count.get() + 1))
),
el("select", null, on("change", e => emoji.set(e.target.value)))
.append(
el(Option, "🎉", isSelected),
el(Option, "🚀", isSelected),
el(Option, "💖", isSelected),
)
);
}
function OptionComponent({ textContent }){
return el("option", { value: textContent, textContent })
function Option({ textContent }){
return Ol("option", { value: textContent, textContent });
}
```
# Deka DOM Elements
Creating reactive elements, components and Web components using [IDL](https://developer.mozilla.org/en-US/docs/
Glossary/IDL)/JavaScript DOM API and [**signals/observables**](#signals).
## Inspiration and suggested alternatives
- my previous library (mostly used internaly): [jaandrle/dollar_dom_component: Functional DOM components without
JSX and virtual DOM.](https://github.com/jaandrle/dollar_dom_component)
- [vanjs-org/van: 🍦 VanJS: World's smallest reactive UI framework. Incredibly Powerful, Insanely Small -
Everyone can build a useful UI app in an hour.](https://github.com/vanjs-org/van)
- [hyperhype/hyperscript: Create HyperText with JavaScript.](https://github.com/hyperhype/hyperscript)
- [adamhaile/S: S.js - Simple, Clean, Fast Reactive Programming in Javascript](https://github.com/adamhaile/S)
([adamhaile/surplus: High performance JSX web views for S.js applications](https://github.com/adamhaile/surplus))
- [potch/signals: a small reactive signals library](https://github.com/potch/signals)
Creating reactive elements, components, and Web Components using the native
[IDL](https://developer.mozilla.org/en-US/docs/Glossary/IDL)/JavaScript DOM API enhanced with
[**signals/observables**](#understanding-signals).
## Why an another one?
This library falls somewhere between van/hyperscript and [solid-js](https://github.com/solidjs/solid) in terms of size,
complexity, and usability.
## Features at a Glance
Another goal is to proceed in the best spirit of functional programming. This involves starting with
pure JavaScript (DOM API) and gradually adding auxiliary functions, ranging from “minor” improvements
to the capability of writing complete declarative reactive UI templates.
- ✅ **No build step required** — use directly in browsers or Node.js
- ☑️ **Lightweight** — ~10-15kB minified (original goal 10kB) with zero/minimal dependencies
- ✅ **Declarative & functional approach** for clean, maintainable code
- ✅ **Optional signals** with support for custom reactive implementations
- ✅ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom)
- 🔄 **TypeScript support** (work in progress)
- 🔄 **Enhanced Web Components** support (work in progress)
As a result, any “internal” function (`assign`, `classListDeclarative`, `on`, `dispatchEvent`, …, `S`, …)
can be used independently, although they are primarily designed for use in combination. This can also,
hopefully, help in integrating the library into existing projects.
## Why Another Library?
To balance these requirements, numerous compromises have been made. To summarize:
- [ ] Library size: 1015kB minified (the original goal was a maximum of 10kB)
- [x] Optional use of *signals* with the ability to register *your own signals/observables implementation*
- [x] *No build step required*
- [x] Preference for a *declarative/functional* approach
- [x] Focus on zero/minimal dependencies
- [ ] Support for/enhancement of custom elements (web components)
- [x] Support for SSR ([jsdom](https://github.com/jsdom/jsdom))
- [ ] WIP providing types
This library bridges the gap between minimal solutions like van/hyperscript and more comprehensive frameworks like [solid-js](https://github.com/solidjs/solid), offering a balanced trade-off between size, complexity, and usability.
## First steps
- [**Guide**](https://jaandrle.github.io/deka-dom-el)
- Documentation
- Installation
- npm
- [dist/](dist/) (`https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/`…)
Following functional programming principles, Deka DOM Elements starts with pure JavaScript (DOM API) and gradually adds auxiliary functions. These range from minor improvements to advanced features for building complete declarative reactive UI templates.
## Signals
- [Signals — whats going on behind the scenes \| by Ryan Hoffnan \|
ITNEXT](https://itnext.io/signals-whats-going-on-behind-the-scenes-ec858589ea63)
- [The Evolution of Signals in JavaScript - DEV
Community](https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob)
- there is also [tc39/proposal-signals:
A proposal to add signals to JavaScript.](https://github.com/tc39/proposal-signals)
- [Observer pattern - Wikipedia](https://en.wikipedia.org/wiki/Observer_pattern)
A key advantage: any internal function (`assign`, `classListDeclarative`, `on`, `dispatchEvent`, `S`, etc.) can be used independently while also working seamlessly together. This modular approach makes it easier to integrate the library into existing projects.
## Getting Started
### Installation
#### npm
```bash
# TBD
# npm install deka-dom-el
```
#### Direct Script
```html
<script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/dde-with-signals.min.js"></script>
<script type="module">
const { el, S } = dde;
</script>
```
### Documentation
- [**Interactive Guide**](https://jaandrle.github.io/deka-dom-el): WIP
- [Examples](./examples/): TBD/WIP
## Understanding Signals
Signals are the reactive backbone of Deka DOM Elements:
- [Signals — what's going on behind the scenes](https://itnext.io/signals-whats-going-on-behind-the-scenes-ec858589ea63)
- [The Evolution of Signals in JavaScript](https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob)
- [TC39 Signals Proposal](https://github.com/tc39/proposal-signals) (future standard)
- [Observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) (underlying concept)
## Inspiration and Alternatives
- [vanjs-org/van](https://github.com/vanjs-org/van) - World's smallest reactive UI framework
- [adamhaile/S](https://github.com/adamhaile/S) - Simple, clean, fast reactive programming
- [hyperhype/hyperscript](https://github.com/hyperhype/hyperscript) - Create HyperText with JavaScript
- [potch/signals](https://github.com/potch/signals) - A small reactive signals library
- [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) - Functional DOM components without JSX/virtual DOM

View File

@ -14,6 +14,10 @@ if(s.test("-d", path_target.root)){
echo("Creating directory…");
s.mkdir("-p", path_target.root);
}
// Create assets directory in target
echo("Creating assets directory…");
s.mkdir("-p", path_target.root+"assets");
echo("Collecting list of pages…");
const pages= s.ls($.xdg.main`../docs/*.html.js`).map(addPage);
for(const { id, info } of pages){
@ -33,6 +37,13 @@ for(const { id, info } of pages){
s.echo(serverDOM.serialize()).to(path_target.root+id+".html");
}
s.echo(styles.content).to(path_target.css+styles.name);
// Copy assets
echo("Copying assets…");
if(s.test("-d", "docs/assets")) {
s.cp("-r", "docs/assets/*", path_target.assets);
}
dispatchEvent("onssrend");
echo("Done");

View File

@ -177,6 +177,11 @@ var scope = {
pop() {
if (scopes.length === 1) return;
return scopes.pop();
},
isolate(fn) {
this.push({ prevent: true });
fn();
this.pop();
}
};
function append(...els) {
@ -594,6 +599,10 @@ function observedAttributes2(instance) {
// src/events.js
function dispatchEvent(name, options, host) {
if (typeof options === "function") {
host = options;
options = null;
}
if (!options) options = {};
return function dispatch(element, ...d) {
if (host) {

File diff suppressed because one or more lines are too long

9
dist/dde.js vendored
View File

@ -154,6 +154,11 @@ var scope = {
pop() {
if (scopes.length === 1) return;
return scopes.pop();
},
isolate(fn) {
this.push({ prevent: true });
fn();
this.pop();
}
};
function append(...els) {
@ -571,6 +576,10 @@ function observedAttributes2(instance) {
// src/events.js
function dispatchEvent(name, options, host) {
if (typeof options === "function") {
host = options;
options = null;
}
if (!options) options = {};
return function dispatch(element, ...d) {
if (host) {

10
dist/dde.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -54,7 +54,8 @@ type IsReadonly<T, K extends keyof T> =
type ElementAttributes<T extends SupportedElement>= Partial<{
[K in keyof _fromElsInterfaces<T>]:
_fromElsInterfaces<T>[K] extends ((...p: any[])=> any)
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=> ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=>
ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
: (IsReadonly<_fromElsInterfaces<T>, K> extends false
? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
: ddeStringable)
@ -71,6 +72,26 @@ export function assignAttribute<El extends SupportedElement, ATT extends keyof E
): ElementAttributes<El>[ATT]
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
export function el<
A extends ddeComponentAttributes,
EL extends SupportedElement | ddeDocumentFragment
>(
component: (attr: A)=> EL,
attrs?: NoInfer<A>,
...addons: ddeElementAddon<EL>[]
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? EL
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
export function el<
A extends { textContent: ddeStringable },
EL extends SupportedElement | ddeDocumentFragment
>(
component: (attr: A)=> EL,
attrs?: NoInfer<A>["textContent"],
...addons: ddeElementAddon<EL>[]
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? EL
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
export function el<
TAG extends keyof ExtendedHTMLElementTagNameMap,
>(
@ -88,16 +109,6 @@ export function el(
attrs?: ElementAttributes<HTMLElement> | ddeStringable,
...addons: ddeElementAddon<HTMLElement>[]
): ddeHTMLElement
export function el<
C extends (attr: ddeComponentAttributes)=> SupportedElement | ddeDocumentFragment
>(
component: C,
attrs?: Parameters<C>[0] | ddeStringable,
...addons: ddeElementAddon<ReturnType<C>>[]
): ReturnType<C> extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? ReturnType<C>
: ( ReturnType<C> extends ddeDocumentFragment ? ReturnType<C> : ddeHTMLElement )
export { el as createElement }
export function elNS(
@ -146,6 +157,8 @@ export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
body: EL,
): EL
export function dispatchEvent(name: keyof DocumentEventMap | string, element: SupportedElement):
(data?: any)=> void;
export function dispatchEvent(name: keyof DocumentEventMap | string, options?: EventInit):
(element: SupportedElement, data?: any)=> void;
export function dispatchEvent(
@ -219,11 +232,13 @@ export const scope: {
state: Scope[],
/** Adds new child scope. All attributes are inherited by default. */
push(scope: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
/** Adds root scope as a child of the current scope. */
pushRoot(): ReturnType<Array<Scope>["push"]>,
/** Removes last/current child scope. */
pop(): ReturnType<Array<Scope>["pop"]>,
/** Runs function in a new (isolated) scope */
isolate(fn: Function): void,
};
export function customElementRender<

View File

@ -54,7 +54,8 @@ type IsReadonly<T, K extends keyof T> =
type ElementAttributes<T extends SupportedElement>= Partial<{
[K in keyof _fromElsInterfaces<T>]:
_fromElsInterfaces<T>[K] extends ((...p: any[])=> any)
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=> ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=>
ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
: (IsReadonly<_fromElsInterfaces<T>, K> extends false
? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
: ddeStringable)
@ -71,6 +72,26 @@ export function assignAttribute<El extends SupportedElement, ATT extends keyof E
): ElementAttributes<El>[ATT]
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
export function el<
A extends ddeComponentAttributes,
EL extends SupportedElement | ddeDocumentFragment
>(
component: (attr: A)=> EL,
attrs?: NoInfer<A>,
...addons: ddeElementAddon<EL>[]
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? EL
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
export function el<
A extends { textContent: ddeStringable },
EL extends SupportedElement | ddeDocumentFragment
>(
component: (attr: A)=> EL,
attrs?: NoInfer<A>["textContent"],
...addons: ddeElementAddon<EL>[]
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? EL
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
export function el<
TAG extends keyof ExtendedHTMLElementTagNameMap,
>(
@ -88,16 +109,6 @@ export function el(
attrs?: ElementAttributes<HTMLElement> | ddeStringable,
...addons: ddeElementAddon<HTMLElement>[]
): ddeHTMLElement
export function el<
C extends (attr: ddeComponentAttributes)=> SupportedElement | ddeDocumentFragment
>(
component: C,
attrs?: Parameters<C>[0] | ddeStringable,
...addons: ddeElementAddon<ReturnType<C>>[]
): ReturnType<C> extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? ReturnType<C>
: ( ReturnType<C> extends ddeDocumentFragment ? ReturnType<C> : ddeHTMLElement )
export { el as createElement }
export function elNS(
@ -146,6 +157,8 @@ export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
body: EL,
): EL
export function dispatchEvent(name: keyof DocumentEventMap | string, element: SupportedElement):
(data?: any)=> void;
export function dispatchEvent(name: keyof DocumentEventMap | string, options?: EventInit):
(element: SupportedElement, data?: any)=> void;
export function dispatchEvent(
@ -219,11 +232,13 @@ export const scope: {
state: Scope[],
/** Adds new child scope. All attributes are inherited by default. */
push(scope: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
/** Adds root scope as a child of the current scope. */
pushRoot(): ReturnType<Array<Scope>["push"]>,
/** Removes last/current child scope. */
pop(): ReturnType<Array<Scope>["pop"]>,
/** Runs function in a new (isolated) scope */
isolate(fn: Function): void,
};
export function customElementRender<

View File

@ -175,6 +175,11 @@ var scope = {
pop() {
if (scopes.length === 1) return;
return scopes.pop();
},
isolate(fn) {
this.push({ prevent: true });
fn();
this.pop();
}
};
function append(...els) {
@ -592,6 +597,10 @@ function observedAttributes2(instance) {
// src/events.js
function dispatchEvent(name, options, host) {
if (typeof options === "function") {
host = options;
options = null;
}
if (!options) options = {};
return function dispatch(element, ...d) {
if (host) {

File diff suppressed because one or more lines are too long

39
dist/esm.d.min.ts vendored
View File

@ -54,7 +54,8 @@ type IsReadonly<T, K extends keyof T> =
type ElementAttributes<T extends SupportedElement>= Partial<{
[K in keyof _fromElsInterfaces<T>]:
_fromElsInterfaces<T>[K] extends ((...p: any[])=> any)
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=> ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=>
ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
: (IsReadonly<_fromElsInterfaces<T>, K> extends false
? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
: ddeStringable)
@ -71,6 +72,26 @@ export function assignAttribute<El extends SupportedElement, ATT extends keyof E
): ElementAttributes<El>[ATT]
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
export function el<
A extends ddeComponentAttributes,
EL extends SupportedElement | ddeDocumentFragment
>(
component: (attr: A)=> EL,
attrs?: NoInfer<A>,
...addons: ddeElementAddon<EL>[]
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? EL
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
export function el<
A extends { textContent: ddeStringable },
EL extends SupportedElement | ddeDocumentFragment
>(
component: (attr: A)=> EL,
attrs?: NoInfer<A>["textContent"],
...addons: ddeElementAddon<EL>[]
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? EL
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
export function el<
TAG extends keyof ExtendedHTMLElementTagNameMap,
>(
@ -88,16 +109,6 @@ export function el(
attrs?: ElementAttributes<HTMLElement> | ddeStringable,
...addons: ddeElementAddon<HTMLElement>[]
): ddeHTMLElement
export function el<
C extends (attr: ddeComponentAttributes)=> SupportedElement | ddeDocumentFragment
>(
component: C,
attrs?: Parameters<C>[0] | ddeStringable,
...addons: ddeElementAddon<ReturnType<C>>[]
): ReturnType<C> extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? ReturnType<C>
: ( ReturnType<C> extends ddeDocumentFragment ? ReturnType<C> : ddeHTMLElement )
export { el as createElement }
export function elNS(
@ -146,6 +157,8 @@ export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
body: EL,
): EL
export function dispatchEvent(name: keyof DocumentEventMap | string, element: SupportedElement):
(data?: any)=> void;
export function dispatchEvent(name: keyof DocumentEventMap | string, options?: EventInit):
(element: SupportedElement, data?: any)=> void;
export function dispatchEvent(
@ -219,11 +232,13 @@ export const scope: {
state: Scope[],
/** Adds new child scope. All attributes are inherited by default. */
push(scope: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
/** Adds root scope as a child of the current scope. */
pushRoot(): ReturnType<Array<Scope>["push"]>,
/** Removes last/current child scope. */
pop(): ReturnType<Array<Scope>["pop"]>,
/** Runs function in a new (isolated) scope */
isolate(fn: Function): void,
};
export function customElementRender<

39
dist/esm.d.ts vendored
View File

@ -54,7 +54,8 @@ type IsReadonly<T, K extends keyof T> =
type ElementAttributes<T extends SupportedElement>= Partial<{
[K in keyof _fromElsInterfaces<T>]:
_fromElsInterfaces<T>[K] extends ((...p: any[])=> any)
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=> ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=>
ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
: (IsReadonly<_fromElsInterfaces<T>, K> extends false
? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
: ddeStringable)
@ -71,6 +72,26 @@ export function assignAttribute<El extends SupportedElement, ATT extends keyof E
): ElementAttributes<El>[ATT]
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
export function el<
A extends ddeComponentAttributes,
EL extends SupportedElement | ddeDocumentFragment
>(
component: (attr: A)=> EL,
attrs?: NoInfer<A>,
...addons: ddeElementAddon<EL>[]
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? EL
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
export function el<
A extends { textContent: ddeStringable },
EL extends SupportedElement | ddeDocumentFragment
>(
component: (attr: A)=> EL,
attrs?: NoInfer<A>["textContent"],
...addons: ddeElementAddon<EL>[]
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? EL
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
export function el<
TAG extends keyof ExtendedHTMLElementTagNameMap,
>(
@ -88,16 +109,6 @@ export function el(
attrs?: ElementAttributes<HTMLElement> | ddeStringable,
...addons: ddeElementAddon<HTMLElement>[]
): ddeHTMLElement
export function el<
C extends (attr: ddeComponentAttributes)=> SupportedElement | ddeDocumentFragment
>(
component: C,
attrs?: Parameters<C>[0] | ddeStringable,
...addons: ddeElementAddon<ReturnType<C>>[]
): ReturnType<C> extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? ReturnType<C>
: ( ReturnType<C> extends ddeDocumentFragment ? ReturnType<C> : ddeHTMLElement )
export { el as createElement }
export function elNS(
@ -146,6 +157,8 @@ export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
body: EL,
): EL
export function dispatchEvent(name: keyof DocumentEventMap | string, element: SupportedElement):
(data?: any)=> void;
export function dispatchEvent(name: keyof DocumentEventMap | string, options?: EventInit):
(element: SupportedElement, data?: any)=> void;
export function dispatchEvent(
@ -219,11 +232,13 @@ export const scope: {
state: Scope[],
/** Adds new child scope. All attributes are inherited by default. */
push(scope: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
/** Adds root scope as a child of the current scope. */
pushRoot(): ReturnType<Array<Scope>["push"]>,
/** Removes last/current child scope. */
pop(): ReturnType<Array<Scope>["pop"]>,
/** Runs function in a new (isolated) scope */
isolate(fn: Function): void,
};
export function customElementRender<

9
dist/esm.js vendored
View File

@ -152,6 +152,11 @@ var scope = {
pop() {
if (scopes.length === 1) return;
return scopes.pop();
},
isolate(fn) {
this.push({ prevent: true });
fn();
this.pop();
}
};
function append(...els) {
@ -569,6 +574,10 @@ function observedAttributes2(instance) {
// src/events.js
function dispatchEvent(name, options, host) {
if (typeof options === "function") {
host = options;
options = null;
}
if (!options) options = {};
return function dispatch(element, ...d) {
if (host) {

2
dist/esm.min.js vendored

File diff suppressed because one or more lines are too long

28
docs/assets/favicon.svg Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#333333" />
<stop offset="100%" stop-color="#222222" />
</linearGradient>
<linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#e32c2c" />
<stop offset="100%" stop-color="#ff5252" />
</linearGradient>
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="0.5" stdDeviation="0.5" flood-color="#000" flood-opacity="0.3"/>
</filter>
</defs>
<!-- Square background with rounded corners -->
<rect x="2" y="2" width="28" height="28" rx="4" ry="4" fill="url(#bgGradient)" />
<!-- Subtle code brackets as background element -->
<g opacity="0.15" fill="#fff">
<path d="M10,7.5 L6.25,16 L10,24.5" stroke="#fff" stroke-width="1" fill="none"/>
<path d="M22,7.5 L25.75,16 L22,24.5" stroke="#fff" stroke-width="1" fill="none"/>
</g>
<!-- lowercase dde letters -->
<text x="16" y="21" text-anchor="middle" font-family="'Fira Code', 'JetBrains Mono', 'Source Code Pro', 'SF Mono', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace" font-size="14" font-weight="bold" fill="url(#textGradient)" filter="url(#shadow)">dde</text>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

33
docs/assets/logo.svg Normal file
View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256" height="256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<!-- Gradients and effects -->
<defs>
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#333333" />
<stop offset="100%" stop-color="#222222" />
</linearGradient>
<linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#e32c2c" />
<stop offset="100%" stop-color="#ff5252" />
</linearGradient>
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="#000" flood-opacity="0.3"/>
</filter>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
</filter>
</defs>
<!-- Square background with rounded corners -->
<rect x="18" y="18" width="220" height="220" rx="20" ry="20" fill="url(#bgGradient)" />
<!-- Subtle code brackets as background element -->
<g opacity="0.15" fill="#fff" filter="url(#glow)">
<path d="M80,60 L50,128 L80,196" stroke="#fff" stroke-width="8" fill="none"/>
<path d="M176,60 L206,128 L176,196" stroke="#fff" stroke-width="8" fill="none"/>
</g>
<!-- lowercase dde letters with shadow effect -->
<text x="128" y="154" text-anchor="middle" font-family="'Fira Code', 'JetBrains Mono', 'Source Code Pro', 'SF Mono', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace" font-size="100" font-weight="bold" fill="url(#textGradient)" filter="url(#shadow)">dde</text>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -31,7 +31,7 @@ ${host} {
overflow: auto;
border-radius: var(--border-radius);
font-family: var(--font-mono);
font-size: 0.8rem;
font-size: .85rem;
line-height: 1.5;
position: relative;
margin-block: 1rem;
@ -195,7 +195,7 @@ export function code({ id, src, content, language= "js", className= host.slice(1
dataJS= "todo";
}
return el("div", { id, className, dataJS }).append(
el("code", { className: "language-"+language, textContent: content })
el("code", { className: "language-"+language, textContent: content.trim() })
);
}
let is_registered= {};

View File

@ -3,9 +3,8 @@ const host= "."+example.name;
styles.css`
${host} {
grid-column: full-main;
width: 100%;
max-width: calc(9/5 * var(--body-max-width));
height: calc(3/5 * var(--body-max-width));
width: calc(100% - .75em);
height: calc(4/6 * var(--body-max-width));
margin: 2rem auto;
border-radius: var(--border-radius);
box-shadow: var(--shadow);
@ -19,14 +18,13 @@ ${host} .runtime {
.CodeMirror {
height: 100% !important;
font-family: var(--font-mono) !important;
font-size: 0.8rem !important;
font-size: .85rem !important;
line-height: 1.5 !important;
}
/* Dark mode styles for CodeMirror */
.CodeMirror, .CodeMirror-gutters {
background: var(--code-bg) !important;
border: 1px solid var(--border) !important;
color: var(--text) !important;
}

View File

@ -0,0 +1,14 @@
import { el } from "deka-dom-el";
// Create element with properties
const button = el("button", {
textContent: "Click me",
className: "primary",
disabled: true
});
// Shorter and more expressive
// than the native approach
// Add to DOM
document.body.append(button);

View File

@ -0,0 +1,11 @@
import { el } from "deka-dom-el";
// Chainable, natural nesting
// append() returns parent element
// making chains easy and intuitive
document.body.append(
el("div").append(
el("h1", "Title"),
el("p", "Paragraph")
)
);

View File

@ -0,0 +1,19 @@
// Create element with properties
const button = document.createElement('button');
button.textContent = "Click me";
button.className = "primary";
button.disabled = true;
// Or using Object.assign()
const button2 = Object.assign(
document.createElement('button'),
{
textContent: "Click me",
className: "primary",
disabled: true
}
);
// Add to DOM
document.body.appendChild(button);
document.body.appendChild(button2);

View File

@ -0,0 +1,15 @@
// Verbose, needs temp variables
const div = document.createElement('div');
const h1 = document.createElement('h1');
h1.textContent = 'Title';
div.appendChild(h1);
const p = document.createElement('p');
p.textContent = 'Paragraph';
div.appendChild(p);
// appendChild doesn't return parent
// so chaining is not possible
// Add to DOM
document.body.appendChild(div);

View File

@ -0,0 +1,8 @@
import { el, on } from "deka-dom-el";
// Third approach - append with on addon
el("button", {
textContent: "click me"
}).append(
on("click", (e) => console.log("Clicked!", e))
);

View File

@ -0,0 +1,7 @@
import { el } from "deka-dom-el";
// Using events with HTML attribute style
el("button", {
textContent: "click me",
"=onclick": "console.log(event)"
});

View File

@ -0,0 +1,8 @@
import { el, on } from "deka-dom-el";
// Using events as addons - chainable approach
el("button", {
textContent: "click me",
},
on("click", (e) => console.log("Clicked!", e))
);

View File

@ -0,0 +1,2 @@
// Standard DOM event listener approach
element.addEventListener('click', callback, options);

View File

@ -0,0 +1,7 @@
import { el } from "deka-dom-el";
// Using events with property assignment
el("button", {
textContent: "click me",
onclick: console.log
});

View File

@ -0,0 +1,14 @@
// pseudocode
// Mixed concerns make code hard to maintain
const button = document.querySelector('button');
let count = 0;
button.addEventListener('click', () => {
count++;
document.querySelector('p').textContent =
'Clicked ' + count + ' times';
if (count > 10) {
button.disabled = true;
}
});

View File

@ -1,6 +1,14 @@
// pseudo code!
const onchage=
event=>
console.log("Reacting to the:", event); // A
input.addEventListener("change", onchange); // B
input.dispatchEvent(new Event("change")); // C
// pseudocode
// 1. Create state
const count = S(0);
// 2. React to state changes
S.on(count, value => {
updateUI(value);
if (value > 10) disableButton();
});
// 3. Update state on events
button.addEventListener('click', () => {
count.set(count.get() + 1);
});

View File

@ -1,19 +1,30 @@
import { el } from "deka-dom-el";
import { el, on } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
const threePS= ({ emoji= "🚀" })=> {
const clicks= S(0); // A
return el().append(
el("p", S(()=>
"Hello World "+emoji.repeat(clicks.get()) // B
)),
el("button", {
type: "button",
onclick: ()=> clicks.set(clicks.get()+1), // C
textContent: "Fire",
})
);
};
document.body.append(
el(threePS, { emoji: "🎉" }),
);
// A HelloWorld component using the 3PS pattern
function HelloWorld({ emoji = "🚀" }) {
// PART 1: Create reactive state
const clicks = S(0);
return el().append(
// PART 2: Bind state to UI elements
el("p", {
className: "greeting",
// This paragraph automatically updates when clicks changes
textContent: S(() => `Hello World ${emoji.repeat(clicks.get())}`)
}),
// PART 3: Update state in response to events
el("button", {
type: "button",
textContent: "Add emoji",
// When clicked, update the state
onclick: () => clicks.set(clicks.get() + 1)
})
);
}
// Use the component in your app
document.body.append(
el(HelloWorld, { emoji: "🎉" })
);

View File

@ -0,0 +1,14 @@
import { scope } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
function customSignalLogic() {
// Create an isolated scope for a specific operation
scope.push(); // Start new scope
// These signals are in the new scope
const isolatedCount = S(0);
const isolatedDerived = S(() => isolatedCount.get() * 2);
// Clean up by returning to previous scope
scope.pop();
}

View File

@ -1,35 +1,26 @@
/* PSEUDO-CODE!!! */
import { el } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
function component(){
/* prepare changeable data */
const dataA= S("data");
const dataB= S("data");
/* define data flow (can be asynchronous) */
fetchAPI().then(data_new=> dataA(data_new));
setTimeout(()=> dataB("DATA"));
/* declarative UI */
return el().append(
el("h1", {
textContent: "Example",
/* declarative attribute(s) */
classList: { declarative: dataB }
}),
el("ul").append(
/* declarative element(s) */
S.el(dataA, data=> data.map(d=> el("li", d)))
),
el("ul").append(
/* declarative component(s) */
S.el(dataA, data=> data.map(d=> el(subcomponent, d)))
)
function Counter() {
// Define state
const count = S(0);
// Define behavior
const increment = () => count.set(count.get() + 1);
// Define data flow
setTimeout(increment, 1000);
// or fetchAPI().then(increment);
// Declarative UI (how to render data/`count`)
// …automatically updates when changes
return el("div").append(
// declarative element(s)
el("p", S(() => "Count: " + count.get())),
el("button", {
onclick: increment,
textContent: "Increment",
// declarative attribute(s)
disabled: S(() => count.get() >= 10)
})
);
}
function subcomponent({ id }){
/* prepare changeable data */
const textContent= S("…");
/* define data flow (can be asynchronous) */
fetchAPI(id).then(text=> textContent(text));
/* declarative UI */
return el("li", { textContent, dataId: id });
}

View File

@ -1,31 +1,25 @@
/* PSEUDO-CODE!!! */
import { el, on, scope } from "deka-dom-el";
function component(){
const { host }= scope;
const ul= el("ul");
const ac= new AbortController();
fetchAPI({ signal: ac.signal }).then(data=> {
data.forEach(d=> ul.append(el("li", d)));
});
host(
/* element was remove before data fetched */
on.disconnected(()=> ac.abort())
import { el, scope } from "deka-dom-el";
function Counter() {
const { host } = scope;
let count = 0;
const counterText = el("p", "Count: 0");
// Manually update DOM element
const increment = () => {
count++;
counterText.textContent = "Count: " + count;
host().querySelector("button").disabled = count >= 10;
};
setTimeout(increment, 1000);
// or fetchAPI().then(increment);
return el("div").append(
counterText,
el("button", {
onclick: increment,
textContent: "Increment"
})
);
return ul;
/**
* NEVER EVER!!
* let data;
* fetchAPI().then(d=> data= O(d));
*
* OR NEVER EVER!!
* const ul= el("ul");
* fetchAPI().then(d=> {
* const data= O("data");
* ul.append(el("li", data));
* });
*
* // THE HOST IS PROBABLY DIFFERENT THAN
* // YOU EXPECT AND OBSERVABLES MAY BE
* // UNEXPECTEDLY REMOVED!!!
* */
}

View File

@ -0,0 +1,38 @@
/* PSEUDO-CODE!!! */
import { el, scope } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
function Counter() {
const { host } = scope;
let count = S(0);
const counterText = el("p", "Count: 0");
S.on(count, c=> counterText.textContent= "Count: " + c);
// Manually update DOM element
const increment = () => {
count.set(count.get() + 1);
// NEVER EVER
// count = S(count.get() + 1);
// THE HOST IS PROBABLY DIFFERENT THAN
// YOU EXPECT AND SIGNAL MAY BE
// UNEXPECTEDLY REMOVED!!!
host().querySelector("button").disabled = count.get() >= 10;
};
setTimeout(()=> {
// ok, BUT consider extract to separate function
// see section below for more info
scope.push();
const ok= S(0);
scope.pop();
S.on(ok, console.log);
setInterval(()=> ok.set(ok.get() + 1), 100);
}, 100);
return el("div").append(
counterText,
el("button", {
onclick: increment,
textContent: "Increment"
})
);
}

View File

@ -0,0 +1,45 @@
import { el, scope } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
function CounterWithIsolatedTimer() {
const { host } = scope;
// Main component state
const count = S(0);
// Create a timer in an isolated scope
scope.isolate(() => {
// These subscriptions won't be tied to the component lifecycle
// They would continue to run even if the component was removed
const timer = S(0);
// Not recommended for real applications!
// Just demonstrating scope isolation
setInterval(() => {
timer.set(timer.get() + 1);
console.log(`Timer: ${timer.get()}`);
}, 1000);
});
// Normal component functionality within main scope
function increment() {
count.set(count.get() + 1);
}
return el("div").append(
el("p").append(
"Count: ",
el("#text", S(() => count.get()))
),
el("button", {
textContent: "Increment",
onclick: increment
}),
el("p", "An isolated timer runs in console")
);
}
// Usage
document.body.append(
el(CounterWithIsolatedTimer)
);

View File

@ -1,12 +1,12 @@
import { S } from "deka-dom-el/signals";
// α — `signal` represents a reactive value
// PART 1 — `signal` represents a reactive value
const signal= S(0);
// β — just reacts on signal changes
// PART 2 — just reacts on signal changes
S.on(signal, console.log);
// γ — just updates the value
// PART 3 — just updates the value
const update= ()=> signal.set(signal.get()+1);
update();
const interval= 5*1000;
setTimeout(clearInterval, 10*interval,
setInterval(update, interval));
setInterval(update, interval));

View File

@ -17,8 +17,14 @@ export function mnemonic(){
" — just ", el("code", "<element>.dispatchEvent(new Event(<event>[, <options>]))")
),
el("li").append(
el("code", "dispatchEvent(<event>[, <options>])(element, detail)"),
" — just ", el("code", "<element>.dispatchEvent(new CustomEvent(<event>, { detail, ...<options> }))")
el("code", "dispatchEvent(<event>, <element>)([<detail>])"),
" — just ", el("code", "<element>.dispatchEvent(new Event(<event>))"), " or ",
el("code", "<element>.dispatchEvent(new CustomEvent(<event>, { detail: <detail> }))")
),
el("li").append(
el("code", "dispatchEvent(<event>[, <options>])(<element>[, <detail>])"),
" — just ", el("code", "<element>.dispatchEvent(new Event(<event>[, <options>] ))"), " or ",
el("code", "<element>.dispatchEvent(new CustomEvent(<event>, { detail: <detail> }))")
),
);
}

View File

@ -1,14 +1,14 @@
import { styles } from "./ssr.js";
styles.css`
:root {
--primary: #b71c1c;
--primary-light: #f05545;
--primary-dark: #7f0000;
--primary-rgb: 183, 28, 28;
--secondary: #700037;
--secondary-light: #ae1357;
--secondary-dark: #4a0027;
--secondary-rgb: 112, 0, 55;
--primary: hsl(0, 74%, 42%);
--primary-light: hsl(5, 87%, 61%);
--primary-dark: hsl(0, 100%, 25%);
--primary-hs: 0, 74%;
--secondary: hsl(330, 100%, 22%);
--secondary-light: hsl(339, 80%, 38%);
--secondary-dark: hsl(328, 100%, 15%);
--secondary-hs: 330, 100%;
--font-main: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
@ -20,50 +20,49 @@ styles.css`
--header-height: 4rem;
--border-radius: 0.375rem;
--bg: #ffffff;
--bg-sidebar: #fff5f5;
--text: #1a1313;
--text-light: #555050;
--code-bg: #f9f2f2;
--code-text: #9a0000;
--border: #d8c0c0;
--selection: rgba(183, 28, 28, 0.15);
--marked: #b71c1c;
--bg: hsl(0, 0%, 100%);
--bg-sidebar: hsl(0, 100%, 98%);
--text: hsl(0, 16%, 15%);
--text-light: hsl(0, 4%, 33%);
--code-bg: hsl(0, 39%, 97%);
--code-text: hsl(0, 100%, 30%);
--border: hsl(0, 32%, 80%);
--selection: hsl(var(--primary-hs), 90%);
--marked: var(--primary);
--accent: var(--secondary);
--shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
--shadow: 0 2px 6px hsla(0, 0%, 0%, 0.15);
--shadow-sm: 0 2px 4px hsla(0, 0%, 0%, 0.15);
--link-color: #9a0000;
--link-hover: #7f0000;
--button-text: #ffffff;
--link-color: hsl(0, 100%, 30%);
--link-hover: hsl(0, 100%, 25%);
--button-text: hsl(0, 0%, 100%);
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #121212;
--bg-sidebar: #1a1212;
--text: #ffffff;
--text-light: #cccccc;
--code-bg: #2c2020;
--code-text: #ff9e80;
--border: #4d3939;
--selection: rgba(255, 99, 71, 0.25);
--primary: #b74141;
--primary-light: #ff867f;
--primary-dark: #c62828;
--secondary: #f02b47;
--secondary-light: #ff6090;
--secondary-dark: #b0003a;
--bg: hsl(0, 0%, 7%);
--bg-sidebar: hsl(0, 9%, 9%);
--text: hsl(0, 0%, 100%);
--text-light: hsl(0, 0%, 80%);
--code-bg: hsl(0, 18%, 15%);
--code-text: hsl(20, 100%, 75%);
--border: hsl(0, 14%, 27%);
--selection: hsla(9, 100%, 64%, 0.25);
--primary: hsl(0, 48%, 49%);
--primary-light: hsl(5, 100%, 75%);
--primary-dark: hsl(0, 67%, 47%);
--secondary: hsl(350, 87%, 55%);
--secondary-light: hsl(341, 100%, 69%);
--secondary-dark: hsl(340, 100%, 35%);
--accent: var(--secondary);
--link-color: #ff5252;
--link-hover: #ff867f;
--button-text: #ffffff;
--link-color: hsl(0, 100%, 66%);
--link-hover: hsl(5, 100%, 75%);
--button-text: hsl(0, 0%, 100%);
--nav-current-bg: #aa2222;
--nav-current-text: #ffffff;
--primary-rgb: 255, 82, 82;
--secondary-rgb: 233, 30, 99;
--primary-hs: 0, 48%;
--secondary-hs: 350, 87%;
}
}
@ -78,7 +77,7 @@ html {
/* Accessibility improvements */
:focus {
outline: 3px solid rgba(63, 81, 181, 0.5);
outline: 3px solid hsl(231, 48%, 70%);
outline-offset: 2px;
}
@ -87,7 +86,7 @@ html {
}
:focus-visible {
outline: 3px solid rgba(63, 81, 181, 0.5);
outline: 3px solid hsl(231, 48%, 70%);
outline-offset: 2px;
}
@ -127,7 +126,7 @@ body {
background-color: var(--bg);
color: var(--text);
line-height: 1.6;
font-size: 1rem;
font-size: 1.05rem;
display: grid;
grid-template-columns: 100%;
grid-template-areas:
@ -209,6 +208,12 @@ pre code {
background-color: transparent;
padding: 0;
}
.illustration:not(:has( .comparison)) pre {
background: none;
border-style: dashed !important;
width: fit-content;
padding: 1em 2em;
}
/* Layout */
@media (min-width: 768px) {
@ -224,7 +229,7 @@ pre code {
/* Main content */
body > main {
grid-area: content;
padding: 2rem;
padding-block: 2rem;
max-width: 100%;
overflow-x: hidden;
display: grid;
@ -242,83 +247,54 @@ body > main > *, body > main slot > * {
}
/* Page title with ID anchor for skip link */
body > main .page-title {
h2 {
margin-top: 0;
border-bottom: 1px solid var(--border);
padding-bottom: 0.75rem;
margin-bottom: 1.5rem;
color: var(--primary);
position: relative;
}
/* Section headings with better visual hierarchy */
body > main h2, body > main h3 {
body > main h3, body > main h4 {
scroll-margin-top: calc(var(--header-height) + 1rem);
position: relative;
}
body > main h3 {
border-left: 3px solid var(--primary);
position: relative;
left: -1.5rem;
padding-inline-start: 1em;
}
/* Make clickable heading links for better navigation */
.heading-anchor {
position: absolute;
color: var(--text-light);
left: -2rem;
text-decoration: none;
font-weight: normal;
opacity: 0;
transition: opacity 0.2s;
}
h2:hover .heading-anchor,
h3:hover .heading-anchor {
opacity: 0.8;
opacity: .4;
transition: opacity .2s;
&:hover { opacity: .8; }
}
@media (max-width: 767px) {
body > main {
padding: 1.5rem 1rem;
}
body > main h2, body > main h3 {
left: 1rem;
width: calc(100% - 1rem);
}
.heading-anchor {
opacity: 0.4;
/* Boxes */
.comparison {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
margin: 1.5rem 0;
}
@media (min-width: 768px) {
.comparison {
grid-template-columns: 1fr 1fr;
}
}
/* Example boxes */
.example {
.good-practice, .bad-practice {
border: 1px solid var(--border);
border-radius: var(--border-radius);
margin: 2rem 0;
overflow: hidden;
box-shadow: var(--shadow-sm);
transition: box-shadow 0.2s;
}
.example:hover {
box-shadow: var(--shadow);
}
.example-header {
background-color: var(--bg-sidebar);
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border);
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
.example-content {
padding: 1.25rem;
border-top: 4px solid var(--practice-color);
padding: 0 1rem;
}
.good-practice {
--practice-color: hsl(145, 63%, 49%); }
.bad-practice {
--practice-color: hsl(340, 82%, 52%); }
/* Icon styling */
.icon {
@ -332,52 +308,116 @@ h3:hover .heading-anchor {
}
/* Information blocks */
.note, .tip, .warning {
.note, .tip, .warning, .callout, .troubleshooting {
padding: 1rem 1.25rem;
margin: 1.5rem 0;
border-radius: var(--border-radius);
position: relative;
font-size: 0.95rem;
line-height: 1.5;
}
.note {
background-color: rgba(63, 81, 181, 0.08);
border-left: 4px solid var(--primary);
background-color: var(--bg-sidebar);
border-radius: 0 var(--border-radius) var(--border-radius) 0;
}
--block-color: var(--primary);
border-left: 4px solid var(--block-color);
h4 { margin-top: 0; }
}
.tip {
background-color: rgba(46, 204, 113, 0.08);
border-left: 4px solid #2ecc71;
border-radius: 0 var(--border-radius) var(--border-radius) 0;
}
--block-color: hsl(145, 63%, 49%); }
.warning {
background-color: rgba(241, 196, 15, 0.08);
border-left: 4px solid #f1c40f;
border-radius: 0 var(--border-radius) var(--border-radius) 0;
}
--block-color: hsl(48, 89%, 50%); }
.note::before, .tip::before, .warning::before {
.callout {
--block-color: hsl(231, 48%, 70%); }
.troubleshooting {
--block-color: hsl(340, 82%, 52%); }
.note::before, .tip::before, .warning::before, .callout::before, .troubleshooting::before {
font-weight: 600;
display: block;
margin-bottom: 0.5rem;
color: var(--block-color);
}
.note::before {
content: "Note";
color: var(--primary);
}
.tip::before {
content: "Tip";
color: #2ecc71;
}
content: "Note"; }
.warning::before {
content: "Warning";
color: #f1c40f;
content: "Warning"; }
/* Function table styling */
.function-table {
border: 1px solid var(--border);
border-radius: var(--border-radius);
padding: 1rem;
margin: 1.5rem 0;
background-color: var(--bg-sidebar);
}
.function-table h4 {
margin-top: 0;
color: var(--primary);
font-family: var(--font-mono);
border-bottom: 1px solid var(--border);
padding-bottom: 0.5rem;
margin-bottom: 1rem;
}
.function-table dl {
margin: 0;
}
.function-table dt {
font-weight: 600;
margin-top: 0.75rem;
}
.function-table dd {
margin-left: 1rem;
margin-bottom: 0.75rem;
white-space: pre-wrap;
word-wrap: break-word;
}
/* Tabs styling */
.tabs {
border: 1px solid var(--border);
border-radius: var(--border-radius);
margin: 1.5rem 0;
overflow: hidden;
}
.tab {
padding: 1rem;
border-top: 1px solid var(--border);
}
.tab:first-child {
border-top: none;
}
.tab h4 {
margin-top: 0;
margin-bottom: 0.75rem;
font-size: 1.1rem;
}
@media (min-width: 768px) {
.tabs {
display: flex;
flex-wrap: wrap;
}
.tab {
flex: 1;
min-width: 50%;
border-top: none;
border-left: 1px solid var(--border);
}
.tab:first-child {
border-left: none;
}
}
/* Prev/Next buttons */

View File

@ -2,7 +2,8 @@ import { t, T } from "./utils/index.js";
export const info= {
href: "./",
title: t`Introduction`,
description: t`Introducing a library.`,
fullTitle: t`Vanilla for flavouring — a full-fledged feast for large projects`,
description: t`A lightweight, reactive DOM library for creating dynamic UIs with a declarative syntax`,
};
import { el } from "deka-dom-el";
@ -26,37 +27,85 @@ const references= {
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p", t`The library tries to provide pure JavaScript tool(s) to create reactive interfaces using …`),
el(h3, t`Event-driven programming (3 parts separation ≡ 3PS)`),
el("p").append(t`
Let's introduce the basic principle on which the library is built. We'll use the JavaScript listener as
a starting point.
`),
el(code, { src: fileURL("./components/examples/introducing/3ps.js"), page_id }),
el("p").append(...T`
As we can see, in the code at location A we define ${el("em", t`how to react`)} when the function
is called with any event as an argument. At that moment, we ${el("em", t`dont care who/why/how`)}
the function was called. Similarly, at point B, we reference to a function to be called on the event
${el("em", t`without caring`)} what the function will do at that time. Finally, at point C, we tell
the application that a change has occurred, in the input, and we ${el("em", t`don't care if/how someone`)}
is listening for the event.
Welcome to Deka DOM Elements (DDE) a lightweight library for building dynamic UIs with a
declarative syntax that stays close to the native DOM API. DDE gives you powerful reactive
tools without the complexity and overhead of larger frameworks.
`),
el("div", { className: "callout" }).append(
el("h4", t`What Makes DDE Special`),
el("ul").append(
el("li", t`No build step required — use directly in the browser`),
el("li", t`Lightweight core (~10-15kB minified) with zero dependencies`),
el("li", t`Natural DOM API — work with real DOM nodes, not abstractions`),
el("li", t`Built-in reactivity with powerful signals system`),
el("li", t`Clean code organization with the 3PS pattern`)
)
),
el(example, { src: fileURL("./components/examples/introducing/helloWorld.js"), page_id }),
el(h3, { textContent: t`The 3PS Pattern: A Better Way to Build UIs`, id: "h-3ps" }),
el("p").append(...T`
The library introduces a new type of variable/constant called ${el("em", t`signal`)} allowing us to
to use introduced 3PS pattern in our applications. As you can see it in the example above.
At the heart of DDE is the 3PS (3-Part Separation) pattern. This simple yet powerful approach helps you
organize your UI code into three distinct areas, making your applications more maintainable and easier to reason about.
`),
el("div", { className: "illustration" }).append(
el("div", { className: "tabs" }).append(
el("div", { className: "tab" }).append(
el("h5", t`Traditional DOM Manipulation`),
el(code, { src: fileURL("./components/examples/introducing/3ps-before.js"), page_id }),
),
el("div", { className: "tab" }).append(
el("h5", t`DDE's 3PS Pattern`),
el(code, { src: fileURL("./components/examples/introducing/3ps.js"), page_id }),
)
)
),
el("p").append(...T`
Also please notice that there is very similar 3PS pattern used for separate creation of UI and
business logic.
The 3PS pattern separates your code into three clear parts:
`),
el("ol").append(
el("li").append(...T`
${el("strong", "Create State")}: Define your application's reactive data using signals
`),
el("li").append(...T`
${el("strong", "Bind to Elements")}: Define how UI elements react to state changes
`),
el("li").append(...T`
${el("strong", "Update State")}: Modify state in response to user events or other triggers
`)
),
el("p").append(...T`
The 3PS is very simplified definition of the pattern. There are more deep/academic definitions more precisely
describe usage in specific situations, see for example ${el("a", { textContent: t`MVVM`, ...references.w_mvv })}
or ${el("a", { textContent: t`MVC`, ...references.w_mvc })}.
By separating these concerns, your code becomes more modular, testable, and easier to maintain. This approach
shares principles with more formal patterns like ${el("a", { textContent: "MVVM", ...references.w_mvv })} and
${el("a", { textContent: "MVC", ...references.w_mvc })}, but with less overhead and complexity.
`),
el(h3, t`Organization of the documentation`),
el("div", { className: "note" }).append(
el("p").append(...T`
The 3PS pattern becomes especially powerful when combined with components, allowing you to create
reusable pieces of UI with encapsulated state and behavior. You'll learn more about this in the
following sections.
`)
),
el(h3, t`How to Use This Documentation`),
el("p").append(...T`
This guide will take you through DDE's features step by step:
`),
el("ol").append(
el("li").append(...T`${el("strong", "Elements")} — Creating and manipulating DOM elements`),
el("li").append(...T`${el("strong", "Events")} — Handling user interactions and lifecycle events`),
el("li").append(...T`${el("strong", "Signals")} — Adding reactivity to your UI`),
el("li").append(...T`${el("strong", "Scopes")} — Managing component lifecycles`),
el("li").append(...T`${el("strong", "Custom Elements")} — Building web components`),
el("li").append(...T`${el("strong", "Debugging")} — Tools to help you build and fix your apps`),
el("li").append(...T`${el("strong", "SSR")} — Server-side rendering with DDE`)
),
el("p").append(...T`
Each section builds on the previous ones, so we recommend following them in order.
Let's get started with the basics of creating elements!
`),
);
}

View File

@ -37,7 +37,7 @@ ${host} h1 {
${host} .version-badge {
font-size: 0.75rem;
background-color: rgba(150, 150, 150, 0.2);
background-color: hsla(0, 0%, 59%, 0.2);
padding: 0.25rem 0.5rem;
border-radius: var(--border-radius);
}
@ -49,22 +49,11 @@ ${host} p {
margin: 0;
}
${host} .github-link {
${host_nav} .github-link {
display: flex;
align-items: center;
gap: 0.5rem;
color: white;
font-size: 0.875rem;
padding: 0.375rem 0.75rem;
border-radius: var(--border-radius);
background-color: rgba(0, 0, 0, 0.2);
text-decoration: none;
transition: background-color 0.2s;
}
${host} .github-link:hover {
background-color: rgba(0, 0, 0, 0.3);
text-decoration: none;
font-size: 0.9rem;
}
/* Navigation */
@ -85,46 +74,27 @@ ${host_nav} a {
padding: 0.625rem 0.75rem;
border-radius: var(--border-radius);
color: var(--text);
text-decoration: none;
transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
transition: background-color 0.1s ease, color 0.1s ease, transform 0.1s ease, box-shadow 0.1s ease;
line-height: 1.2;
}
${host_nav} a:hover {
background-color: rgba(var(--primary-rgb), 0.08); /* Using CSS variables for better theming */
text-decoration: none;
transform: translateY(-1px);
color: var(--primary);
}
${host_nav} a.current,
${host_nav} a[aria-current=page] {
background-color: var(--nav-current-bg, var(--primary-dark));
color: var(--nav-current-text, white);
background-color: hsl(var(--primary-hs), 40%);
color: whitesmoke;
font-weight: 600;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25);
box-shadow: var(--shadow);
text-decoration: none;
}
${host_nav} a.current:hover,
${host_nav} a[aria-current=page]:hover {
background-color: var(--primary);
color: white;
${host_nav} a:hover {
background-color: hsl(var(--primary-hs), 45%);
color: whitesmoke;
transform: translateY(-1px);
text-decoration: none;
}
${host_nav} a .nav-number {
display: inline-block;
width: 1.5rem;
text-align: right;
margin-right: 0.5rem;
opacity: 0.7;
}
${host_nav} a:first-child {
display: flex;
align-items: center;
font-weight: 600;
margin-bottom: 0.5rem;
color: rgb(from currentColor r g b / .75);
}
/* Mobile navigation */
@ -172,24 +142,17 @@ export function header({ info: { href, title, description }, pkg }){
head({ title: pageTitle, description, pkg })
);
// Add theme color meta tag
document.head.append(
el("meta", { name: "theme-color", content: "#3f51b5" })
);
return el().append(
// Header section with accessibility support
el("header", { role: "banner", className: header.name }).append(
el("div", { className: "header-title" }).append(
el("a", {
href: pkg.homepage,
className: "github-link",
"aria-label": "View on GitHub",
target: "_blank",
rel: "noopener noreferrer"
}).append(
el(iconGitHub),
),
el("img", {
src: "assets/logo.svg",
alt: "DDE Logo",
width: "32",
height: "32",
style: "margin-right: 0.5rem;"
}),
el("h1").append(
el("a", { href: pages[0].href, textContent: pkg.name, title: "Go to documentation homepage" }),
),
@ -203,15 +166,25 @@ export function header({ info: { href, title, description }, pkg }){
),
// Navigation between pages
nav({ href })
nav({ href, pkg })
);
}
function nav({ href }){
function nav({ href, pkg }){
return el("nav", {
role: "navigation",
"aria-label": "Main navigation",
className: nav.name
}).append(
el("a", {
href: pkg.homepage,
className: "github-link",
"aria-label": "View on GitHub",
target: "_blank",
rel: "noopener noreferrer",
}).append(
el(iconGitHub),
"GitHub"
),
...pages.map((p, i) => {
const isIndex = p.href === "index";
const isCurrent = p.href === href;
@ -220,16 +193,15 @@ function nav({ href }){
href: isIndex ? "./" : p.href,
title: p.description || `Go to ${p.title}`,
"aria-current": isCurrent ? "page" : null,
classList: { current: isCurrent }
}).append(
el("span", {
className: "nav-number",
"aria-hidden": "true",
textContent: `${i+1}.`
textContent: `${i+1}. `
}),
p.title
);
})
}),
);
}
function head({ title, description, pkg }){
@ -237,6 +209,7 @@ function head({ title, description, pkg }){
el("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
el("meta", { name: "description", content: description }),
el("meta", { name: "theme-color", content: "#b71c1c" }),
el("link", { rel: "icon", href: "assets/favicon.svg", type: "image/svg+xml" }),
el("title", title),
el(metaAuthor),
el(metaTwitter, pkg),
@ -257,7 +230,7 @@ function metaTwitter({ name, description, homepage }){
el("meta", { name: "twitter:url", content: homepage }),
el("meta", { name: "twitter:title", content: name }),
el("meta", { name: "twitter:description", content: description }),
//el("meta", { name: "twitter:image", content: "" }),
el("meta", { name: "twitter:image", content: homepage + "/assets/logo.svg" }),
el("meta", { name: "twitter:creator", content: "@jaandrle" }),
);
}
@ -266,7 +239,7 @@ function metaFacebook({ name, description, homepage }){
el("meta", { name: "og:url", content: homepage }),
el("meta", { name: "og:title", content: name }),
el("meta", { name: "og:description", content: description }),
//el("meta", { name: "og:image", content: "" }),
el("meta", { name: "og:image", content: homepage + "/assets/logo.svg" }),
el("meta", { name: "og:creator", content: "@jaandrle" }),
);
}

View File

@ -19,8 +19,7 @@ export function simplePage({ pkg, info }){
// Main content area
el("main", { id: "main-content", role: "main" }).append(
// Page title as an h1
el("h1", { className: "page-title", textContent: info.title }),
el("h2", { textContent: info.fullTitle || info.title }),
// Main content from child elements
el("slot"),

View File

@ -1,7 +1,8 @@
import { T, t } from "./utils/index.js";
export const info= {
title: t`Elements`,
description: t`Basic concepts of elements modifications and creations.`,
fullTitle: t`Declarative DOM Element Creation`,
description: t`Building user interfaces with declarative DOM element creation.`,
};
import { el } from "deka-dom-el";
@ -48,107 +49,173 @@ const references= {
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("h2", t`Native JavaScript DOM elements creations`),
el("p", t`
Lets go through all patterns we would like to use and what needs to be improved for better experience.
el("p").append(...T`
Building user interfaces in JavaScript often involves creating and manipulating DOM elements.
DDE provides a simple yet powerful approach to element creation that is declarative, chainable,
and maintains a clean syntax close to HTML structure.
`),
el("div", { class: "callout" }).append(
el("h4", t`DDE Elements: Key Benefits`),
el("ul").append(
el("li", t`Declarative element creation with intuitive property assignment`),
el("li", t`Chainable methods for natural DOM tree construction`),
el("li", t`Simplified component patterns for code reuse`),
el("li", t`Normalized property/attribute handling across browsers`),
el("li", t`Smart element return values for cleaner code flow`)
)
),
el(code, { src: fileURL("./components/examples/elements/intro.js"), page_id }),
el(h3, t`Creating element(s) (with custom attributes)`),
el(h3, t`Creating Elements: Native vs DDE`),
el("p").append(...T`
You can create a native DOM element by using the ${el("a", references.mdn_create).append(
el("code", "document.createElement()") )}. It is also possible to provide a some attribute(s) declaratively
using ${el("code", "Object.assign()")}. More precisely, this way you can sets some
${el("a", references.mdn_idl).append( el("abbr", { textContent: "IDL", title: "Interface Description Language" }))}
also known as a JavaScript property.
`),
el(example, { src: fileURL("./components/examples/elements/nativeCreateElement.js"), page_id }),
el("p").append(...T`
To make this easier, you can use the ${el("code", "el")} function. Internally in basic examples,
it is wrapper around ${el("code", "assign(document.createElement(…), { … })")}.
In standard JavaScript, you create DOM elements using the
${el("a", references.mdn_create).append(el("code", "document.createElement()"))} method
and then set properties individually or with Object.assign():
`),
el("div", { class: "illustration" }).append(
el("div", { class: "comparison" }).append(
el("div").append(
el("h5", t`Native DOM API`),
el(code, { src: fileURL("./components/examples/elements/native-dom-create.js"), page_id })
),
el("div").append(
el("h5", t`DDE Approach`),
el(code, { src: fileURL("./components/examples/elements/dde-dom-create.js"), page_id })
)
)
),
el(example, { src: fileURL("./components/examples/elements/dekaCreateElement.js"), page_id }),
el("p").append(...T`
The ${el("code", "assign")} function provides improved behaviour of ${el("code", "Object.assign()")}.
You can declaratively sets any IDL and attribute of the given element. Function prefers IDL and fallback
to the ${el("code", "element.setAttribute")} if there is no writable property in the element prototype.
The ${el("code", "el")} function provides a simple wrapper around ${el("code", "document.createElement")}
with enhanced property assignment.
`),
el(h3, t`Advanced Property Assignment`),
el("p").append(...T`
You can study all JavaScript elements interfaces to the corresponding HTML elements. All HTML elements
inherits from ${el("a", { textContent: "HTMLElement", ...references.mdn_el })}. To see
all available IDLs for example for paragraphs, see ${el("a", { textContent: "HTMLParagraphElement",
...references.mdn_p })}. Moreover, the ${el("code", "assign")} provides a way to sets declaratively
some convenient properties:
The ${el("code", "assign")} function is the heart of DDE's element property handling. It provides
intelligent assignment of both properties (IDL) and attributes:
`),
el("ul").append(
el("li").append(...T`
It is possible to sets ${el("code", "data-*")}/${el("code", "aria-*")} attributes using object notation.
`),
el("li").append(...T`
In opposite, it is also possible to sets ${el("code", "data-*")}/${el("code", "aria-*")} attribute
using camelCase notation.
`),
el("li").append(...T`You can use string or object as a value for ${el("code", "style")} property.`),
el("li").append(...T`
${el("code", "className")} (IDL preffered)/${el("code", "class")} are ways to add CSS classes
to the element. You can use string (similarly to ${el("code", "class=\"…\"")} syntax in HTML).
`),
el("li").append(...T`
Use ${el("code", "classList")} to toggle specific classes. This will be handy later when
the reactivity via signals is beeing introduced.
`),
el("li").append(...T`
The ${el("code", "assign")} also accepts the ${el("code", "undefined")} 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. ${el("em").append(...T`For example, natievly the elements ${el("code", "id")}
is removed by setting the IDL to an empty string.`)}
`),
el("li").append(...T`
You can use ${el("code", "=")} or ${el("code", ".")} to force processing given key as attribute/property
of the element.
`)
el("div", { class: "function-table" }).append(
el("dl").append(
el("dt", t`Property vs Attribute Priority`),
el("dd", t`Prefers IDL properties, falls back to setAttribute() when no writable property exists`),
el("dt", t`Data and ARIA Attributes`),
el("dd", t`Both dataset.* and data-* syntaxes supported (same for ARIA)`),
el("dt", t`Style Handling`),
el("dd", t`Accepts string or object notation for style property`),
el("dt", t`Class Management`),
el("dd", t`Works with className, class, or classList object for toggling classes`),
el("dt", t`Force Modes`),
el("dd", t`Use = prefix to force attribute mode, . prefix to force property mode`),
el("dt", t`Attribute Removal`),
el("dd", t`Pass undefined to remove a property or attribute`)
)
),
el("p").append(...T`
For processing, the ${el("code", "assign")} internally uses ${el("code", "assignAttribute")} and
${el("code", "classListDeclarative")}.
`),
el(example, { src: fileURL("./components/examples/elements/dekaAssign.js"), page_id }),
el(h3, t`Native JavaScript templating`),
el("p", t`By default, the native JS has no good way to define HTML template using DOM API:`),
el(example, { src: fileURL("./components/examples/elements/nativeAppend.js"), page_id }),
el("div", { class: "note" }).append(
el("p").append(...T`
You can explore standard HTML element properties in the MDN documentation for
${el("a", { textContent: "HTMLElement", ...references.mdn_el })} (base class)
and specific element interfaces like ${el("a", { textContent: "HTMLParagraphElement", ...references.mdn_p })}.
`)
),
el(h3, t`Building DOM Trees with Chainable Methods`),
el("p").append(...T`
This library therefore overwrites the ${el("code", "append")} method of created elements to always return
parent element.
One of the most powerful features of DDE is its approach to building element trees.
Unlike the native DOM API which doesn't return the parent after appendChild(), DDE's
append() always returns the parent element:
`),
el("div", { class: "illustration" }).append(
el("div", { class: "comparison" }).append(
el("div", { class: "bad-practice" }).append(
el("h5", t`❌ Native DOM API`),
el(code, { src: fileURL("./components/examples/elements/native-dom-tree.js"), page_id })
),
el("div", { class: "good-practice" }).append(
el("h5", t`✅ DDE Approach`),
el(code, { src: fileURL("./components/examples/elements/dde-dom-tree.js"), page_id })
)
)
),
el(example, { src: fileURL("./components/examples/elements/dekaAppend.js"), page_id }),
el(h3, t`Basic (state-less) components`),
el("p").append(...T`
You can use functions for encapsulation (repeating) logic. The ${el("code", "el")} accepts function
as first argument. In that case, the function should return dom elements and the second argument for
${el("code", "el")} is argument for given element.
This chainable approach results in code that more closely mirrors the structure of your HTML,
making it easier to understand and maintain.
`),
el(h3, t`Building Reusable Components`),
el("p").append(...T`
DDE makes it simple to create reusable element components through regular JavaScript functions.
The ${el("code", "el()")} function accepts a component function as its first argument:
`),
el(example, { src: fileURL("./components/examples/elements/dekaBasicComponent.js"), page_id }),
el("p").append(...T`
As you can see, in case of state-less/basic components there is no difference between calling component
function directly or using ${el("code", "el")} function.
`),
el("p", { className: "notice" }).append(...T`
It is nice to use similar naming convention as native DOM API. This allows us to use
${el("a", { textContent: t`the destructuring assignment syntax`, ...references.mdn_destruct })} and keep
track of the native API (things are best remembered through regular use).
Component functions receive props as their argument and return element(s). This pattern
encourages code reuse and better organization of your UI code.
`),
el("div", { class: "tip" }).append(
el("p").append(...T`
It's helpful to use naming conventions similar to native DOM elements for your components.
This allows you to use ${el("a", { textContent: "destructuring assignment", ...references.mdn_destruct })}
and keeps your code consistent with the DOM API.
`)
),
el(h3, t`Creating non-HTML elements`),
el(h3, t`Working with SVG and Other Namespaces`),
el("p").append(...T`
Similarly to the native DOM API (${el("a", references.mdn_ns).append(el("code", "document.createElementNS"))})
for non-HTML elements we need to tell JavaScript which kind of the element to create. We can use
the ${el("code", "elNS")} function:
For non-HTML elements like SVG, MathML, or custom namespaces, DDE provides the ${el("code", "elNS")} function
which corresponds to the native ${el("a", references.mdn_ns).append(el("code", "document.createElementNS"))}:
`),
el(example, { src: fileURL("./components/examples/elements/dekaElNS.js"), page_id }),
el("p").append(...T`
This function returns a namespace-specific element creator, allowing you to work with any element type
using the same consistent interface.
`),
el(h3, t`Best Practices`),
el("ol").append(
el("li").append(...T`
${el("strong", "Prefer composition over complexity")}: Create small component functions that can be
combined rather than large, complex templates
`),
el("li").append(...T`
${el("strong", "Use meaningful component names")}: Name your component functions after the elements or
patterns they create
`),
el("li").append(...T`
${el("strong", "Destructure for better readability")}: Use ${el("code", "const { div, p, button } = el")}
to create element-specific functions
`),
el("li").append(...T`
${el("strong", "Be consistent with property usage")}: Prefer using the same pattern (property vs attribute)
throughout your code
`)
),
el("div", { class: "troubleshooting" }).append(
el("h4", t`Common Element Creation Pitfalls`),
el("dl").append(
el("dt", t`Elements not showing up in DOM`),
el("dd", t`Remember to append elements to the document or a parent that's already in the document`),
el("dt", t`Properties not being applied correctly`),
el("dd", t`Check if you're mixing up property (IDL) names with attribute names (e.g., className vs class)`),
el("dt", t`Event listeners not working`),
el("dd", t`Ensure you're using the correct event binding approach (see Events section)`),
el("dt", t`SVG elements not rendering correctly`),
el("dd", t`Make sure you're using elNS with the correct SVG namespace for SVG elements`)
)
),
el(mnemonic)
);

View File

@ -1,7 +1,8 @@
import { T, t } from "./utils/index.js";
export const info= {
title: t`Events and Addons`,
description: t`Using not only events in UI declaratively.`,
fullTitle: t`Declarative Event Handling and Addons`,
description: t`Using events and addons for declarative UI interactions.`,
};
import { el } from "deka-dom-el";
@ -15,7 +16,7 @@ const fileURL= url=> new URL(url, import.meta.url);
const references= {
/** element.addEventListener() */
mdn_listen: {
title: t`MDN documentation page for elemetn.addEventListener`,
title: t`MDN documentation page for element.addEventListener`,
href: "https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener",
},
/** AbortSignal+element.addEventListener */
@ -45,108 +46,196 @@ const references= {
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("h2", t`Listenning to the native DOM events and other Addons`),
el("p").append(...T`
We quickly introduce helper to listening to the native DOM events. And library syntax/pattern so-called
${el("em", t`Addon`)} to incorporate not only this in UI templates declaratively.
Events are at the core of interactive web applications. DDE provides a clean, declarative approach to
handling DOM events and extends this pattern with a powerful Addon system to incorporate additional
functionalities into your UI templates.
`),
el("div", { className: "callout" }).append(
el("h4", t`Why DDE's Event System and Addons Matters`),
el("ul").append(
el("li", t`Integrate event handling directly in element declarations`),
el("li", t`Leverage lifecycle events for better component design`),
el("li", t`Clean up listeners automatically with abort signals`),
el("li", t`Extend elements with custom behaviors using Addons`),
el("li", t`Maintain clean, readable code with consistent patterns`)
)
),
el(code, { src: fileURL("./components/examples/events/intro.js"), page_id }),
el(h3, t`Events and listenners`),
el(h3, t`Events and Listeners: Two Approaches`),
el("p").append(...T`
In JavaScript you can listen to the native DOM events of the given element by using
${el("a", references.mdn_listen).append( el("code", "element.addEventListener(type, listener, options)") )}.
The library provides an alternative (${el("code", "on")}) accepting the differen order of the arguments:
In JavaScript you can listen to native DOM events using
${el("a", references.mdn_listen).append(el("code", "element.addEventListener(type, listener, options)"))}.
DDE provides an alternative approach with arguments ordered differently to better fit its declarative style:
`),
el("div", { className: "illustration" }).append(
el("div", { className: "tabs" }).append(
el("div", { className: "tab" }).append(
el("h5", t`Native DOM API`),
el(code, { content: `element.addEventListener('click', callback, options);`, page_id })
),
el("div", { className: "tab" }).append(
el("h5", t`DDE Approach`),
el(code, { content: `on('click', callback, options)(element);`, page_id })
)
)
),
el(example, { src: fileURL("./components/examples/events/compare.js"), page_id }),
el("p").append(...T`
this is actually one of the two differences. The another one is that ${el("code", "on")} accepts only
object as the ${el("code", "options")} (but it is still optional).
The main benefit of DDE's approach is that it works as an Addon, making it easy to integrate
directly into element declarations.
`),
el("p", { className: "notice" }).append(...T`
The other difference is that there is ${el("strong", "no")} ${el("code", "off")} function. You can remove
listener declaratively using ${el("a", { textContent: "AbortSignal", ...references.mdn_abortListener })}:
`),
el(example, { src: fileURL("./components/examples/events/abortSignal.js"), page_id }),
el("div", { className: "notice" }).append(
el("p", t`So, there are (typically) three ways to handle events. You can use:`),
el("ul").append(
el("li").append( el("code", `el("button", { textContent: "click me", "=onclick": "console.log(event)" })`)),
el("li").append( el("code", `el("button", { textContent: "click me", onclick: console.log })`)),
el("li").append( el("code", `el("button", { textContent: "click me" }, on("click", console.log))`))
),
el(h3, t`Removing Event Listeners`),
el("div", { className: "note" }).append(
el("p").append(...T`
In the first example we force to use HTML attribute (it corresponds to
${el("code", `<button onclick="console.log(event)">click me</button>`)}). ${el("em", t`Side note:
this can be useful in case of SSR.`)} To study difference, you can read a nice summary here:
${el("a", { textContent: "GIST @WebReflection/web_events.md", ...references.web_events })}.
Unlike the native addEventListener/removeEventListener pattern, DDE uses the ${el("a", {
textContent: "AbortSignal", ...references.mdn_abortListener })} for declarative approach for removal:
`)
),
el(example, { src: fileURL("./components/examples/events/abortSignal.js"), page_id }),
el(h3, t`Addons`),
el(h3, t`Three Ways to Handle Events`),
el("div", { className: "tabs" }).append(
el("div", { className: "tab", "data-tab": "html-attr" }).append(
el("h4", t`HTML Attribute Style`),
el(code, { src: fileURL("./components/examples/events/attribute-event.js"), page_id }),
el("p").append(...T`
Forces usage as an HTML attribute. Corresponds to
${el("code", `<button onclick="console.log(event)">click me</button>`)}. This can be particularly
useful for SSR scenarios.
`)
),
el("div", { className: "tab", "data-tab": "property" }).append(
el("h4", t`Property Assignment`),
el(code, { src: fileURL("./components/examples/events/property-event.js"), page_id }),
el("p", t`Assigns the event handler directly to the element's property.`)
),
el("div", { className: "tab", "data-tab": "addon" }).append(
el("h4", t`Addon Approach`),
el(code, { src: fileURL("./components/examples/events/chain-event.js"), page_id }),
el("p", t`Uses the addon pattern, see above.`)
)
),
el("p").append(...T`
From practical point of view, ${el("em", t`Addons`)} are just functions that accept any HTML element as
their first parameter. You can see that the ${el("code", "on(…)")} fullfills this requirement.
For a deeper comparison of these approaches, see
${el("a", { textContent: "WebReflection's detailed analysis", ...references.web_events })}.
`),
el(h3, t`Understanding Addons`),
el("p").append(...T`
You can use Addons as 3rd argument of ${el("code", "el")} function. This way is possible to extends your
templates by additional (3rd party) functionalities. But for now mainly, you can add events listeners:
Addons are a powerful pattern in DDE that extends beyond just event handling.
An Addon is any function that accepts an HTML element as its first parameter.
`),
el("div", { className: "callout" }).append(
el("h4", t`What Can Addons Do?`),
el("ul").append(
el("li", t`Add event listeners to elements`),
el("li", t`Set up lifecycle behaviors`),
el("li", t`Integrate third-party libraries`),
el("li", t`Create reusable element behaviors`),
el("li", t`Capture element references`)
)
),
el("p").append(...T`
You can use Addons as 3rd argument of the ${el("code", "el")} function, making it possible to
extend your templates with additional functionality:
`),
el(example, { src: fileURL("./components/examples/events/templateWithListeners.js"), page_id }),
el("p").append(...T`
As the example shows, you can also provide types in JSDoc+TypeScript by using global type
${el("code", "ddeElementAddon")}. Also notice, you can use Addons to get element reference.
As the example shows, you can provide types in JSDoc+TypeScript using the global type
${el("code", "ddeElementAddon")}. Notice how Addons can also be used to get element references.
`),
el(h3, t`Life-cycle events`),
el(h3, t`Lifecycle Events`),
el("p").append(...T`
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.
Addons are called immediately when an element is created, even before it's connected to the live DOM.
You can think of an Addon as an "oncreate" event handler.
`),
el("p").append(...T`
The library provide three additional live-cycle events corresponding to how they are named in a case of
custom elements: ${el("code", "on.connected")}, ${el("code", "on.disconnected")} and ${el("code",
"on.attributeChanged")}.
DDE provides three additional lifecycle events that correspond to custom element lifecycle callbacks:
`),
el(example, { src: fileURL("./components/examples/events/live-cycle.js"), page_id }),
el("p").append(...T`
For Custom elements, we will later introduce a way to replace ${el("code", "*Callback")} syntax with
${el("code", "dde:*")} events. The ${el("code", "on.*")} functions then listen to the appropriate
Custom Elements events (see ${el("a", { textContent: t`Custom element lifecycle callbacks | MDN`,
...references.mdn_customElement })}).
`),
el("p").append(...T`
But, in case of regular elemnets the ${el("a", references.mdn_mutation).append(el("code",
"MutationObserver"), " | MDN")} is internaly used to track these events. Therefore, there are some
drawbacks:
`),
el("ul").append(
el("li").append(...T`
To proper listener registration, you need to use ${el("code", "on.*")} not \`on("dde:*", …)\`!
`),
el("li").append(...T`
Use sparingly! Internally, library must loop of all registered events and fires event properly.
${el("strong", t`It is good practice to use the fact that if an element is removed, its children are
also removed!`)} In this spirit, we will introduce later the ${el("strong", t`host`)} syntax to
register, clean up procedures when the component is removed from the app.
`),
el("div", { className: "function-table" }).append(
el("dl").append(
el("dt", t`on.connected(callback)`),
el("dd", t`Fires when the element is added to the DOM`),
el("dt", t`on.disconnected(callback)`),
el("dd", t`Fires when the element is removed from the DOM`),
el("dt", t`on.attributeChanged(callback, attributeName)`),
el("dd", t`Fires when the specified attribute changes`)
)
),
el("p").append(...T`
To provide intuitive behaviour, similar also to how the life-cycle events works in other
frameworks/libraries, deka-dom-el library ensures that ${el("code", "on.connected")} and
${el("code", "on.disconnected")} are called only once and only when the element, is (dis)connected to
live DOM. The solution is inspired by ${el("a", { textContent: "Vue", ...references.vue_fix })}. For using
native behaviour re-(dis)connecting element, use:
`),
el("ul").append(
el("li").append(...T`custom ${el("code", "MutationObserver")} or logic in (dis)${el("code",
"connectedCallback")} or`),
el("li").append(...T`re-add ${el("code", "on.connected")} or ${el("code", "on.disconnected")} listeners.`)
el(example, { src: fileURL("./components/examples/events/live-cycle.js"), page_id }),
el("div", { className: "note" }).append(
el("p").append(...T`
For regular elements (non-custom elements), DDE uses
${el("a", references.mdn_mutation).append(el("code", "MutationObserver"), " | MDN")}
internally to track lifecycle events.
`)
),
el(h3, t`Final notes`),
el("p", t`The library also provides a method to dispatch the events.`),
el("div", { className: "warning" }).append(
el("ul").append(
el("li").append(...T`
Always use ${el("code", "on.*")} functions, not ${el("code", "on('dde:*', ...)")}, for proper registration
`),
el("li").append(...T`
Use lifecycle events sparingly, as they require internal tracking
`),
el("li").append(...T`
Leverage parent-child relationships: when a parent is removed, all children are also removed
`),
el("li").append(...T`
see section later in documentation regarding hosts elements
`),
el("li").append(...T`
DDE ensures that connected/disconnected events fire only once for better predictability
`)
)
),
el(h3, t`Dispatching Custom Events`),
el(example, { src: fileURL("./components/examples/events/compareDispatch.js"), page_id }),
el("p").append(...T`
This makes it easy to implement component communication through events,
following standard web platform patterns. The curried approach allows for easy reuse
of event dispatchers throughout your application.
`),
el(h3, t`Best Practices`),
el("ol").append(
el("li").append(...T`
${el("strong", "Clean up listeners")}: Use AbortSignal to prevent memory leaks
`),
el("li").append(...T`
${el("strong", "Leverage lifecycle events")}: For component setup and teardown
`),
el("li").append(...T`
${el("strong", "Delegate when possible")}: Add listeners to container elements when handling many similar elements
`),
el("li").append(...T`
${el("strong", "Maintain consistency")}: Choose one event binding approach and stick with it
`)
),
el("div", { className: "troubleshooting" }).append(
el("h4", t`Common Event Pitfalls`),
el("dl").append(
el("dt", t`Event listeners not working`),
el("dd", t`Ensure element is in the DOM before expecting events to fire`),
el("dt", t`Memory leaks`),
el("dd", t`Use AbortController to clean up listeners when elements are removed`),
el("dt", t`Lifecycle events firing unexpectedly`),
el("dd", t`Remember that on.connected and on.disconnected events only fire once per connection state`)
)
),
el(mnemonic)
);

View File

@ -1,6 +1,7 @@
import { T, t } from "./utils/index.js";
export const info= {
title: t`Signals and Reactivity`,
fullTitle: t`Building Reactive UIs with Signals`,
description: t`Managing reactive UI state with signals.`,
};
@ -43,20 +44,18 @@ const references= {
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("h2", t`Building Reactive UIs with Signals`),
el("p").append(...T`
Signals provide a simple yet powerful way to create reactive applications with DDE. They handle the
fundamental challenge of keeping your UI in sync with changing data in a declarative, efficient way.
`),
el("div", { class: "dde-callout" }).append(
el("div", { class: "callout" }).append(
el("h4", t`What Makes Signals Special?`),
el("ul").append(
el("li", t`Fine-grained reactivity without complex state management`),
el("li", t`Automatic UI updates when data changes`),
el("li", t`Clean separation between data, logic, and UI`),
el("li", t`Small runtime with minimal overhead`),
el("li", t`Works seamlessly with DDE's DOM creation`),
el("li", t`No dependencies or framework lock-in`)
el("li").append(...T`${el("strong", "In future")} no dependencies or framework lock-in`)
)
),
el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }),
@ -64,38 +63,39 @@ export function page({ pkg, info }){
el(h3, t`The 3-Part Structure of Signals`),
el("p").append(...T`
Signals organize your code into three distinct parts, following the
${el("a", { textContent: t`3PS principle`, href: "./#h-event-driven-programming--parts-separation--ps" })}:
${el("a", { textContent: t`3PS principle`, href: "./#h-3ps" })}:
`),
el("div", { class: "dde-signal-diagram" }).append(
el("div", { class: "signal-diagram" }).append(
el("div", { class: "signal-part" }).append(
el("h4", t`α: Create Signal`),
el("h4", t`PART 1: Create Signal`),
el(code, { content: "const count = S(0);", page_id }),
el("p", t`Define a reactive value that can be observed and changed`)
),
el("div", { class: "signal-part" }).append(
el("h4", t`β: React to Changes`),
el("h4", t`PART 2: React to Changes`),
el(code, { content: "S.on(count, value => updateUI(value));", page_id }),
el("p", t`Subscribe to signal changes with callbacks or effects`)
),
el("div", { class: "signal-part" }).append(
el("h4", t`γ: Update Signal`),
el("h4", t`PART 3: Update Signal`),
el(code, { content: "count.set(count.get() + 1);", page_id }),
el("p", t`Modify the signal value, which automatically triggers updates`)
)
),
el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }),
el("div", { class: "dde-note" }).append(
el("div", { class: "note" }).append(
el("p").append(...T`
Signals implement the ${el("a", { textContent: t`Publishsubscribe pattern`, ...references.wiki_pubsub })},
a form of ${el("a", { textContent: t`Event-driven programming`, ...references.wiki_event_driven })}.
This architecture allows different parts of your application to stay synchronized through a shared signal,
without direct dependencies on each other.
without direct dependencies on each other. Compare for example with ${el("a", { textContent:
t`fpubsub library`, ...references.fpubsub })}.
`)
),
el(h3, t`Signal Essentials: Core API`),
el("div", { class: "dde-function-table" }).append(
el("div", { class: "function-table" }).append(
el("dl").append(
el("dt", t`Creating a Signal`),
el("dd", t`S(initialValue) → creates a signal with the given value`),
@ -110,7 +110,8 @@ export function page({ pkg, info }){
el("dd", t`S.on(signal, callback) → runs callback whenever signal changes`),
el("dt", t`Unsubscribing`),
el("dd", t`S.on(signal, callback, { signal: abortController.signal })`)
el("dd").append(...T`S.on(signal, callback, { signal: abortController.signal }) → Similarly to the
${el("code", "on")} function to register DOM events listener.`)
)
),
el("p").append(...T`
@ -136,18 +137,9 @@ export function page({ pkg, info }){
When working with objects, arrays, or other complex data structures, Signal Actions provide
a structured way to modify state while maintaining reactivity.
`),
el("div", { class: "dde-illustration" }).append(
el("div", { class: "illustration" }).append(
el("h4", t`Actions vs. Direct Mutation`),
el("div", { class: "comparison" }).append(
el("div", { class: "bad-practice" }).append(
el("h5", t`❌ Without Actions`),
el(code, { content: `
const todos = S([]);
// Directly mutating the array
const items = todos.get();
items.push("New todo");
// This WON'T trigger updates!`, page_id }))
),
el("div", { class: "good-practice" }).append(
el("h5", t`✅ With Actions`),
el(code, { content: `const todos = S([], {
@ -159,8 +151,25 @@ items.push("New todo");
// Use the action
S.action(todos, "add", "New todo");`, page_id })
)
),
el("div", { class: "bad-practice" }).append(
el("h5", t`❌ Without Actions`),
el(code, { content: `
const todos = S([]);
// Directly mutating the array
const items = todos.get();
items.push("New todo");
// This WON'T trigger updates!`, page_id }))
),
),
el("p").append(...T`
In some way, you can compare it with ${el("a", { textContent: "useReducer", ...references.mdn_use_reducer })}
hook from React. So, the ${el("code", "S(<data>, <actions>)")} pattern creates a store machine. We can
then invoke (dispatch) registered action by calling ${el("code", "S.action(<signal>, <name>, ...<args>)")}
after the action call the signal calls all its listeners. This can be stopped by calling
${el("code", "this.stopPropagation()")} in the method representing the given action. As it can be seen in
examples, the store value is available also in the function for given action (${el("code", "this.value")}).
`),
el(example, { src: fileURL("./components/examples/signals/actions-demo.js"), page_id }),
el("p").append(...T`
@ -177,14 +186,12 @@ S.action(todos, "add", "New todo");`, page_id })
`),
el(example, { src: fileURL("./components/examples/signals/actions-todos.js"), page_id }),
el("div", { class: "dde-tip" }).append(
el("div", { class: "tip" }).append(
el("p").append(...T`
${el("strong", "Special Action Methods")}: Signal actions can implement special lifecycle hooks:
`),
el("ul").append(
el("li", t`[S.symbols.onclear]() - Called when the signal is cleared`),
el("li", t`[S.symbols.onget]() - Called when the signal value is read`),
el("li", t`[S.symbols.onset]() - Called after the signal value is changed`)
el("li", t`[S.symbols.onclear]() - Called when the signal is cleared. Use it to clean up resources.`),
)
),
@ -193,7 +200,7 @@ S.action(todos, "add", "New todo");`, page_id })
Signals really shine when connected to your UI. DDE provides several ways to bind signals to DOM elements:
`),
el("div", { class: "dde-tabs" }).append(
el("div", { class: "tabs" }).append(
el("div", { class: "tab", "data-tab": "attributes" }).append(
el("h4", t`Reactive Attributes`),
el("p", t`Bind signal values directly to element attributes, properties, or styles:`),
@ -229,12 +236,12 @@ S.action(items, "push", "Dragonfruit"); // List updates automatically`, page_id
)
),
el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }),
el("p").append(...T`
The ${el("code", "assign")} and ${el("code", "el")} functions detect signals automatically and handle binding.
You can use special properties like ${el("code", "dataset")}, ${el("code", "ariaset")}, and
${el("code", "classList")} for fine-grained control over specific attribute types.
`),
el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }),
el("p").append(...T`
${el("code", "S.el()")} is especially powerful for conditional rendering and lists:
@ -263,7 +270,7 @@ S.action(items, "push", "Dragonfruit"); // List updates automatically`, page_id
`)
),
el("div", { class: "dde-troubleshooting" }).append(
el("div", { class: "troubleshooting" }).append(
el("h4", t`Common Signal Pitfalls`),
el("dl").append(
el("dt", t`UI not updating when array/object changes`),

View File

@ -1,6 +1,7 @@
import { T, t } from "./utils/index.js";
export const info= {
title: t`Scopes and Components`,
fullTitle: t`Building Maintainable UIs with Scopes and Components`,
description: t`Organizing UI into reusable, manageable components`,
};
@ -28,56 +29,46 @@ const references= {
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("h2", t`Building Maintainable UIs with Scopes and Components`),
el("p").append(...T`
Scopes provide a structured way to organize your UI code into reusable components that properly
manage their lifecycle, handle cleanup, and maintain clear boundaries between different parts of your application.
For state-less components we can use functions as UI components (see Elements page). But in real life,
we may need to handle the component live-cycle and provide JavaScript the way to properly use
the ${el("a", { textContent: t`Garbage collection`, ...references.garbage_collection })}.
`),
el("div", { class: "dde-callout" }).append(
el("h4", t`Why Use Scopes?`),
el("ul").append(
el("li", t`Automatic resource cleanup when components are removed from DOM`),
el("li", t`Clear component boundaries with explicit host elements`),
el("li", t`Simplified event handling with proper "this" binding`),
el("li", t`Seamless integration with signals for reactive components`),
el("li", t`Better memory management with ${el("a", { textContent: t`GC`, ...references.garbage_collection })}`)
)
),
el(code, { src: fileURL("./components/examples/scopes/intro.js"), page_id }),
el("p").append(...T`The library therefore use ${el("em", t`scopes`)} to provide these functionalities.`),
el(h3, t`Understanding Host Elements and Scopes`),
el("div", { class: "dde-illustration" }).append(
el("p").append(...T`
The ${el("strong", "host")} is the name for the element representing the component. This is typically
element returned by function. To get reference, you can use ${el("code", "scope.host()")} to applly addons
just use ${el("code", "scope.host(...<addons>)")}.
`),
el("div", { className: "illustration" }).append(
el("h4", t`Component Anatomy`),
el("pre").append(el("code", `
// 1. Component scope created │
el(MyComponent);
function MyComponent() {
// 2. access the host element │
const { host } = scope;
// 3. Add behavior to host │
host(
on.click(handleClick)
);
// 4. Return the host element │
return el("div", {
className: "my-component"
}).append(
el("h2", "Title"),
el("p", "Content")
);
}
`))
// 1. Component scope created
el(MyComponent);
function MyComponent() {
// 2. access the host element
const { host } = scope;
// 3. Add behavior to host
host(
on.click(handleClick)
);
// 4. Return the host element
return el("div", {
className: "my-component"
}).append(
el("h2", "Title"),
el("p", "Content")
);
}
`.trim()))
),
el("p").append(...T`
The ${el("strong", "host element")} is the root element of your component - typically the element returned
by your component function. It serves as the identity of your component in the DOM.
`),
el("div", { class: "dde-function-table" }).append(
el("div", { className: "function-table" }).append(
el("h4", t`scope.host()`),
el("dl").append(
el("dt", t`When called with no arguments`),
@ -89,42 +80,32 @@ export function page({ pkg, info }){
),
el(example, { src: fileURL("./components/examples/scopes/scopes-and-hosts.js"), page_id }),
el("div", { class: "dde-tip" }).append(
el("div", { className: "tip" }).append(
el("p").append(...T`
${el("strong", "Best Practice:")} Always capture the host reference at the beginning of your component function
using ${el("code", "const { host } = scope")} to avoid scope-related issues, especially with asynchronous code.
`)
),
el(h3, t`Class-Based Components`),
el("p").append(...T`
While functional components are the primary pattern in DDE, you can also create class-based components
for more structured organization of component logic.
`),
el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }),
el("p").append(...T`
This pattern can be useful when:
`),
el("ul").append(
el("li", t`You have complex component logic that benefits from object-oriented organization`),
el("li", t`You need private methods and properties for your component`),
el("li", t`You're transitioning from another class-based component system`)
),
el("div", { class: "dde-tip" }).append(
${el("strong", "Best Practice:")} Always capture the host reference at the beginning of your component
function using ${el("code", "const { host } = scope")} to avoid scope-related issues, especially with
asynchronous code.
`),
el("p").append(...T`
${el("strong", "Note:")} Even with class-based components, follow the best practice of storing the host reference
early in your component code. This ensures proper access to the host throughout the component's lifecycle.
If you are interested in the implementation details, see Class-Based Components section.
`)
),
el(code, { src: fileURL("./components/examples/scopes/good-practise.js"), page_id }),
el(h3, t`Class-Based Components`),
el("p").append(...T`
While functional components are the primary pattern in DDE, you can also create class-based components.
For this, we implement function ${el("code", "elClass")} and use it to demonstrate implementation details
for better understanding of the scope logic.
`),
el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }),
el(h3, t`Automatic Cleanup with Scopes`),
el("p").append(...T`
One of the most powerful features of scopes is automatic cleanup when components are removed from the DOM.
This prevents memory leaks and ensures resources are properly released.
`),
el("div", { class: "dde-illustration" }).append(
el("div", { className: "illustration" }).append(
el("h4", t`Lifecycle Flow`),
el("pre").append(el("code", `
1. Component created scope established
@ -139,7 +120,7 @@ export function page({ pkg, info }){
),
el(example, { src: fileURL("./components/examples/scopes/cleaning.js"), page_id }),
el("div", { class: "dde-note" }).append(
el("div", { className: "note" }).append(
el("p").append(...T`
In this example, when you click "Remove", the component is removed from the DOM, and all its associated
resources are automatically cleaned up, including the signal subscription that updates the text content.
@ -149,77 +130,82 @@ export function page({ pkg, info }){
el(h3, t`Declarative vs Imperative Components`),
el("p").append(...T`
Scopes work best with a declarative approach to UI building, especially when combined
with ${el("a", { textContent: "signals", ...references.signals })} for state management.
The library DOM API and signals works best when used declaratively. It means, you split your app logic
into three parts as it was itroduced in ${el("a", { textContent: "Signals", ...references.signals })}.
`),
el("div", { class: "dde-tabs" }).append(
el("div", { class: "tab", "data-tab": "declarative" }).append(
el("h4", t`✅ Declarative Approach`),
el("p", t`Define what your UI should look like based on state:`),
el("pre").append(el("code", `function Counter() {
const { host } = scope;
// Define state
const count = S(0);
// Define behavior
const increment = () => count.set(count.get() + 1);
// UI automatically updates when count changes
return el("div").append(
el("p", S(() => "Count: " + count.get())),
el("button", {
onclick: increment,
textContent: "Increment"
})
);
}`))
),
el("div", { class: "tab", "data-tab": "imperative" }).append(
el("h4", t`⚠️ Imperative Approach`),
el("p", t`Manually update the DOM in response to events:`),
el("pre").append(el("code", `function Counter() {
const { host } = scope;
let count = 0;
const counterText = el("p", "Count: 0");
// Manually update DOM element
const increment = () => {
count++;
counterText.textContent = "Count: " + count;
};
return el("div").append(
counterText,
el("button", {
onclick: increment,
textContent: "Increment"
})
);
}`))
)
),
el(code, { src: fileURL("./components/examples/scopes/declarative.js"), page_id }),
el("div", { class: "dde-note" }).append(
el("div", { className: "note" }).append(
el("p").append(...T`
While DDE supports both declarative and imperative approaches, the declarative style is recommended
as it leads to more maintainable code with fewer opportunities for bugs. Signals handle the complexity
of keeping your UI in sync with your data.
Strictly speaking, the imperative way of using the library is not prohibited. Just be careful (rather avoid)
mixing declarative approach (using signals) and imperative manipulation of elements.
`)
),
el("div", { className: "tabs" }).append(
el("div", { className: "tab", "data-tab": "declarative" }).append(
el("h4", t`✅ Declarative Approach`),
el("p", t`Define what your UI should look like based on state:`),
el(code, { src: fileURL("./components/examples/scopes/declarative.js"), page_id }),
),
el("div", { className: "tab", "data-tab": "imperative" }).append(
el("h4", t`⚠️ Imperative Approach`),
el("p", t`Manually update the DOM in response to events:`),
el(code, { src: fileURL("./components/examples/scopes/imperative.js"), page_id }),
),
el("div", { className: "tab", "data-tab": "mixed" }).append(
el("h4", t`❌ Mixed Approach`),
el("p", t`Just AVOID:`),
el(code, { src: fileURL("./components/examples/scopes/mixed.js"), page_id }),
),
),
el(h3, t`Advanced: Custom Scoping Control`),
el("p").append(...T`
In more complex applications, you may need finer control over scopes. DDE provides
manual scope control mechanisms through ${el("code", "scope.push()")} and ${el("code", "scope.pop()")}.
`),
el("div", { className: "function-table" }).append(
el("h4", t`Manual Scope Control API`),
el("dl").append(
el("dt", t`scope.current`),
el("dd", t`Returns the currently active scope object.`),
el("dt", t`scope.isolate(callback)`),
el("dd", t`Executes the callback function within a temporary scope, then automatically restores the previous scope.
Safer than manual push/pop for most use cases.`),
el("dt", t`scope.push()`),
el("dd", t`Creates a new scope and makes it the current active scope. All signals and subscriptions
created after this call will be associated with this new scope.`),
el("dt", t`scope.pop()`),
el("dd", t`Restores the previous scope that was active before the matching push() call.`),
)
),
el("p").append(...T`
Custom scoping is particularly useful for:
`),
el("ul").append(
el("li", t`Isolating signal dependencies in async operations`),
el("li", t`Creating detached reactive logic that shouldn't be tied to a component's lifecycle`),
el("li", t`Building utilities that work with signals but need scope isolation`)
),
el(example, { src: fileURL("./components/examples/scopes/custom-scope.js"), page_id }),
el(example, { src: fileURL("./components/examples/scopes/with-scope.js"), page_id }),
el("div", { className: "warning" }).append(
el("p").append(...T`
${el("strong", "Be careful with manual scope control!")} Always ensure you have matching push() and pop() calls,
preferably in the same function. Unbalanced scope management can lead to memory leaks or unexpected behavior.
`),
el("p").append(...T`
For most use cases, prefer using the automatic scope management provided by components.
Manual scope control should be considered an advanced feature.
`)
),
el(code, { src: fileURL("./components/examples/scopes/imperative.js"), page_id }),
el(h3, t`Best Practices for Scopes and Components`),
el("ol").append(
el("li").append(...T`
${el("strong", "Capture host early:")} Use ${el("code", "const { host } = scope")} at component start
`),
el("li").append(...T`
${el("strong", "Return a single root element:")} Components should have one host element that contains all others
`),
el("li").append(...T`
${el("strong", "Prefer declarative patterns:")} Use signals to drive UI updates rather than manual DOM manipulation
`),
@ -231,7 +217,7 @@ export function page({ pkg, info }){
`)
),
el("div", { class: "dde-troubleshooting" }).append(
el("div", { className: "troubleshooting" }).append(
el("h4", t`Common Scope Pitfalls`),
el("dl").append(
el("dt", t`Losing host reference in async code`),

View File

@ -1,6 +1,7 @@
import { T, t } from "./utils/index.js";
export const info= {
title: t`Web Components`,
fullTitle: t`Using Web Components with DDE: Better Together`,
description: t`Using custom elements in combinantion with DDE`,
};
@ -53,13 +54,12 @@ const references= {
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("h2", t`Using Web Components with DDE: Better Together`),
el("p").append(...T`
DDE pairs powerfully with ${el("a", references.mdn_web_components).append(el("strong", t`Web Components`))}
to create reusable, encapsulated custom elements with all the benefits of DDE's declarative DOM
construction and reactivity system.
`),
el("div", { class: "dde-callout" }).append(
el("div", { className: "callout" }).append(
el("h4", t`Why Combine DDE with Web Components?`),
el("ul").append(
el("li", t`Declarative DOM creation within your components`),
@ -92,7 +92,7 @@ export function page({ pkg, info }){
`),
el(code, { src: fileURL("./components/examples/customElement/native-basic.js"), page_id }),
el("div", { class: "dde-note" }).append(
el("div", { className: "note" }).append(
el("p").append(...T`
For complete information on Web Components, see the
${el("a", references.mdn_custom_elements).append(el("strong", t`MDN documentation`))}.
@ -107,7 +107,7 @@ export function page({ pkg, info }){
Custom Elements. This is done with ${el("code", "customElementWithDDE")}, which makes your Custom Element
compatible with DDE's event handling.
`),
el("div", { class: "dde-function-table" }).append(
el("div", { className: "function-table" }).append(
el("h4", t`customElementWithDDE`),
el("dl").append(
el("dt", t`Purpose`),
@ -120,7 +120,7 @@ export function page({ pkg, info }){
),
el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js"), page_id }),
el("div", { class: "dde-tip" }).append(
el("div", { className: "tip" }).append(
el("p").append(...T`
${el("strong", "Key Point:")} The ${el("code", "customElementWithDDE")} function adds event dispatching
to your Custom Element lifecycle methods, making them work seamlessly with DDE's event system.
@ -132,7 +132,7 @@ export function page({ pkg, info }){
The next step is to use DDE's component rendering within your Custom Element. This is done with
${el("code", "customElementRender")}, which connects your DDE component function to the Custom Element.
`),
el("div", { class: "dde-function-table" }).append(
el("div", { className: "function-table" }).append(
el("h4", t`customElementRender`),
el("dl").append(
el("dt", t`Purpose`),
@ -151,7 +151,7 @@ export function page({ pkg, info }){
),
el(example, { src: fileURL("./components/examples/customElement/dde.js"), page_id }),
el("div", { class: "dde-note" }).append(
el("div", { className: "note" }).append(
el("p").append(...T`
In this example, we're using Shadow DOM (${el("code", "this.attachShadow()")}) for encapsulation,
but you can also render directly to the element with ${el("code", "customElementRender(this, ...)")}.
@ -163,7 +163,7 @@ export function page({ pkg, info }){
One of the most powerful features of integrating DDE with Web Components is connecting HTML attributes
to DDE's reactive signals system. This creates truly reactive custom elements.
`),
el("div", { class: "dde-tip" }).append(
el("div", { className: "tip" }).append(
el("p").append(...T`
${el("strong", "Two Ways to Handle Attributes:")}
`),
@ -182,15 +182,15 @@ export function page({ pkg, info }){
`),
el(example, { src: fileURL("./components/examples/customElement/observedAttributes.js"), page_id }),
el("div", { class: "dde-callout" }).append(
el("div", { className: "callout" }).append(
el("h4", t`How S.observedAttributes Works`),
el("p").append(...T`
1. Takes each attribute listed in static observedAttributes
2. Creates a DDE signal for each one
3. Automatically updates these signals when attributes change
4. Passes the signals to your component function
5. Your component reacts to changes through signal subscriptions
`)
el("ol").append(
el("li", t`Takes each attribute listed in static observedAttributes`),
el("li", t`Creates a DDE signal for each one`),
el("li", t`Automatically updates these signals when attributes change`),
el("li", t`Passes the signals to your component function`),
el("li", t`Your component reacts to changes through signal subscriptions`)
)
),
el(h3, t`Working with Shadow DOM`),
@ -198,25 +198,19 @@ export function page({ pkg, info }){
Shadow DOM provides encapsulation for your component's styles and markup. When using DDE with Shadow DOM,
you get the best of both worlds: encapsulation plus declarative DOM creation.
`),
el("div", { class: "dde-illustration" }).append(
el("div", { className: "illustration" }).append(
el("h4", t`Shadow DOM Encapsulation`),
el("pre").append(el("code", `
<my-custom-element>
#shadow-root
Created with DDE:
<div>
<h2>Title</h2>
<p>Content</p>
<my-custom-element>
#shadow-root
Created with DDE:
<div>
<h2>Title</h2>
<p>Content</p>
`))
),
el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js"), page_id }),
@ -229,10 +223,11 @@ export function page({ pkg, info }){
el(h3, t`Working with Slots`),
el("p").append(...T`
Slots allow users of your component to insert content inside it. When using DDE, you can simulate the
slot mechanism with the ${el("code", "simulateSlots")} function:
Besides the encapsulation, the Shadow DOM allows for using the ${el("a", references.mdn_shadow_dom_slot).append(
el("strong", t`<slot>`), t` element(s)`)}. You can simulate this feature using ${el("code", "simulateSlots")}:
`),
el("div", { class: "dde-function-table" }).append(
el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js"), page_id }),
el("div", { className: "function-table" }).append(
el("h4", t`simulateSlots`),
el("dl").append(
el("dt", t`Purpose`),
@ -241,15 +236,6 @@ export function page({ pkg, info }){
el("dd", t`A mapping object of slot names to DOM elements`)
)
),
el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js"), page_id }),
el("div", { class: "dde-tip" }).append(
el("p").append(...T`
${el("strong", "When to use simulateSlots:")} This approach is useful when you need to distribute
content from the light DOM into specific locations in the shadow DOM, particularly in environments
where native slots might not be fully supported.
`)
),
el(h3, t`Best Practices for Web Components with DDE`),
el("p").append(...T`
@ -273,7 +259,7 @@ export function page({ pkg, info }){
`)
),
el("div", { class: "dde-troubleshooting" }).append(
el("div", { className: "troubleshooting" }).append(
el("h4", t`Common Issues`),
el("dl").append(
el("dt", t`Events not firing properly`),

View File

@ -1,6 +1,7 @@
import { T, t } from "./utils/index.js";
export const info= {
title: t`Debugging`,
fullTitle: t`Debugging applications with deka-dom-el`,
description: t`Techniques for debugging applications using deka-dom-el, especially signals.`,
};
@ -16,7 +17,6 @@ const fileURL= url=> new URL(url, import.meta.url);
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("h2", t`Debugging applications with deka-dom-el`),
el("p").append(...T`
Debugging is an essential part of application development. This guide provides techniques
and best practices for debugging applications built with deka-dom-el, with a focus on signals.

View File

@ -1,102 +0,0 @@
import { T, t } from "./utils/index.js";
export const info= {
title: t`Debugging`,
description: t`Techniques for debugging applications using deka-dom-el, especially signals.`,
};
import { el } from "deka-dom-el";
import { simplePage } from "./layout/simplePage.html.js";
import { example } from "./components/example.html.js";
import { h3 } from "./components/pageUtils.html.js";
import { code } from "./components/code.html.js";
/** @param {string} url */
const fileURL= url=> new URL(url, import.meta.url);
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("h2", t`Debugging applications with deka-dom-el`),
el("p").append(...T`
Debugging is an essential part of application development. This guide provides techniques
and best practices for debugging applications built with deka-dom-el, with a focus on signals.
`),
el(h3, t`Debugging signals`),
el("p").append(...T`
Signals are reactive primitives that update the UI when their values change. When debugging signals,
you need to track their values, understand their dependencies, and identify why updates are or aren't happening.
`),
el("h4", t`Inspecting signal values`),
el("p").append(...T`
The simplest way to debug a signal is to log its current value by calling the get method:
`),
el("pre").append(
el("code", "const signal = S(0);\nconsole.log('Current value:', signal.get());")
),
el("p").append(...T`
You can also monitor signal changes by adding a listener:
`),
el("pre").append(
el("code", "// Log every time the signal changes\nS.on(signal, value => console.log('Signal changed:', value));")
),
el("h4", t`Debugging derived signals`),
el("p").append(...T`
With derived signals (created with S(() => computation)), debugging is a bit more complex
because the value depends on other signals. To understand why a derived signal isn't updating correctly:
`),
el("ol").append(
el("li", t`Check that all dependency signals are updating correctly`),
el("li", t`Add logging inside the computation function to see when it runs`),
el("li", t`Verify that the computation function actually accesses the signal values with .get()`)
),
el(example, { src: fileURL("./components/examples/debugging/consoleLog.js"), page_id }),
el(h3, t`Common signal debugging issues`),
el("h4", t`Signal updates not triggering UI changes`),
el("p").append(...T`
If signal updates aren't reflected in the UI, check:
`),
el("ul").append(
el("li", t`That you're using signal.set() to update the value, not modifying objects/arrays directly`),
el("li", t`For mutable objects, ensure you're using actions or making proper copies before updating`),
el("li", t`That the signal is actually connected to the DOM element (check your S.el or attribute binding code)`)
),
el(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }),
el("h4", t`Memory leaks with signal listeners`),
el("p").append(...T`
Signal listeners can cause memory leaks if not properly cleaned up. Always use AbortSignal
to cancel listeners.
`),
el("h4", t`Performance issues with frequently updating signals`),
el("p").append(...T`
If you notice performance issues with signals that update very frequently:
`),
el("ul").append(
el("li", t`Consider debouncing or throttling signal updates`),
el("li", t`Make sure derived signals don't perform expensive calculations unnecessarily`),
el("li", t`Keep signal computations focused and minimal`)
),
el(code, { src: fileURL("./components/examples/debugging/debouncing.js"), page_id }),
el(h3, t`Browser DevTools tips for deka-dom-el`),
el("p").append(...T`
When debugging in the browser, here are some helpful techniques:
`),
el("ul").append(
el("li").append(...T`
Use the Elements panel to inspect the DOM structure created by deka-dom-el
`),
el("li").append(...T`
Set breakpoints in your signal handlers and actions
`),
el("li").append(...T`
Use performance profiling to identify bottlenecks in signal updates
`),
),
);
}

View File

@ -1,6 +1,7 @@
import { T, t } from "./utils/index.js";
export const info= {
title: t`Server-Side Rendering (SSR)`,
fullTitle: t`Server-Side Rendering with deka-dom-el`,
description: t`Using deka-dom-el for server-side rendering with jsdom to generate static HTML.`,
};
@ -15,7 +16,6 @@ const fileURL= url=> new URL(url, import.meta.url);
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("h2", t`Server-Side Rendering with deka-dom-el`),
el("p").append(...T`
deka-dom-el isn't limited to browser environments. Thanks to its flexible architecture,
it can be used for server-side rendering (SSR) to generate static HTML files.

View File

@ -1,7 +1,8 @@
export { t } from "./utils/index.js";
export const path_target= {
root: "dist/docs/",
css: "dist/docs/"
css: "dist/docs/",
assets: "dist/docs/assets/"
};
/**
* This variable will be filled with the list of pages during the build process (see `bs/docs.js`).

1
docs/types.d.ts vendored
View File

@ -4,6 +4,7 @@ export type Info= {
id: string,
href: string,
title: string,
fullTitle: string,
description: string,
}
export type Pages=Info[];

View File

@ -45,7 +45,7 @@ export function todosComponent({ todos= [ "Task A" ] }= {}){
: el("ul").append(
...Array.from(ts).map(([ value, textContent ])=>
memo(value, ()=> el(todoComponent, { textContent, value, className }, onremove)))
)
),
),
el("p", "Click to the text to edit it.")
),
@ -54,7 +54,7 @@ export function todosComponent({ todos= [ "Task A" ] }= {}){
el("label", "New todo: ").append(
el("input", { name, type: "text", required: true }),
),
el("button", "+")
el("button", "+"),
),
el("div").append(
el("h3", "Output (JSON):"),

36
index.d.ts vendored
View File

@ -72,6 +72,26 @@ export function assignAttribute<El extends SupportedElement, ATT extends keyof E
): ElementAttributes<El>[ATT]
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
export function el<
A extends ddeComponentAttributes,
EL extends SupportedElement | ddeDocumentFragment
>(
component: (attr: A)=> EL,
attrs?: NoInfer<A>,
...addons: ddeElementAddon<EL>[]
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? EL
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
export function el<
A extends { textContent: ddeStringable },
EL extends SupportedElement | ddeDocumentFragment
>(
component: (attr: A)=> EL,
attrs?: NoInfer<A>["textContent"],
...addons: ddeElementAddon<EL>[]
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? EL
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
export function el<
TAG extends keyof ExtendedHTMLElementTagNameMap,
>(
@ -89,16 +109,6 @@ export function el(
attrs?: ElementAttributes<HTMLElement> | ddeStringable,
...addons: ddeElementAddon<HTMLElement>[]
): ddeHTMLElement
export function el<
C extends (attr: ddeComponentAttributes)=> SupportedElement | ddeDocumentFragment
>(
component: C,
attrs?: Parameters<C>[0] | ddeStringable,
...addons: ddeElementAddon<ReturnType<C>>[]
): ReturnType<C> extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? ReturnType<C>
: ( ReturnType<C> extends ddeDocumentFragment ? ReturnType<C> : ddeHTMLElement )
export { el as createElement }
export function elNS(
@ -147,6 +157,8 @@ export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
body: EL,
): EL
export function dispatchEvent(name: keyof DocumentEventMap | string, element: SupportedElement):
(data?: any)=> void;
export function dispatchEvent(name: keyof DocumentEventMap | string, options?: EventInit):
(element: SupportedElement, data?: any)=> void;
export function dispatchEvent(
@ -220,11 +232,13 @@ export const scope: {
state: Scope[],
/** Adds new child scope. All attributes are inherited by default. */
push(scope: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
/** Adds root scope as a child of the current scope. */
pushRoot(): ReturnType<Array<Scope>["push"]>,
/** Removes last/current child scope. */
pop(): ReturnType<Array<Scope>["pop"]>,
/** Runs function in a new (isolated) scope */
isolate(fn: Function): void,
};
export function customElementRender<

26
nohup.out Normal file
View File

@ -0,0 +1,26 @@
Markserv boot: starting Markserv...
(node:170089) [DEP0128] DeprecationWarning: Invalid 'main' field in '/home/jaandrle/.npm/_npx/13a70f167aa91a98/node_modules/implant/package.json' of 'implant'. Please either fix that or report it to the module author
(Use `node --trace-deprecation ...` to show where the warning was created)
(node:170089) [DEP0128] DeprecationWarning: Invalid 'main' field in '/home/jaandrle/.npm/_npx/13a70f167aa91a98/node_modules/balanced-pairs/package.json' of 'balanced-pairs'. Please either fix that or report it to the module author
(node:170089) [DEP0128] DeprecationWarning: Invalid 'main' field in '/home/jaandrle/.npm/_npx/13a70f167aa91a98/node_modules/super-split/package.json' of 'super-split'. Please either fix that or report it to the module author
Markserv address: http://localhost:8642
Markserv path: /home/jaandrle/Vzdálené/GitHub/deka-dom-el
Markserv livereload: communicating on port: 35729
Markserv process: your pid is: 170089
Markserv stop: press [Ctrl + C] or type "sudo kill -9 170089"
GitHub Contribute on Github - github.com/markserv
Markserv upgrade: checking for upgrade...
Markserv upgrade: no upgrade available
Markserv dir: /home/jaandrle/Vzdálené/GitHub/deka-dom-el/
Markserv markdown: /home/jaandrle/Vzdálené/GitHub/deka-dom-el/README.md
Markserv file: /home/jaandrle/Vzdálené/GitHub/deka-dom-el/docs/assets/logo.svg
Markserv markdown: /home/jaandrle/Vzdálené/GitHub/deka-dom-el/README.md
Markserv file: /home/jaandrle/Vzdálené/GitHub/deka-dom-el/docs/assets/logo.svg
Markserv markdown: /home/jaandrle/Vzdálené/GitHub/deka-dom-el/README.md
Markserv file: /home/jaandrle/Vzdálené/GitHub/deka-dom-el/docs/assets/logo.svg
Markserv markdown: /home/jaandrle/Vzdálené/GitHub/deka-dom-el/README.md
Markserv file: /home/jaandrle/Vzdálené/GitHub/deka-dom-el/docs/assets/logo.svg
Markserv markdown: /home/jaandrle/Vzdálené/GitHub/deka-dom-el/README.md
Markserv file: /home/jaandrle/Vzdálené/GitHub/deka-dom-el/docs/assets/logo.svg
Markserv markdown: /home/jaandrle/Vzdálené/GitHub/deka-dom-el/README.md
Markserv file: /home/jaandrle/Vzdálené/GitHub/deka-dom-el/docs/assets/logo.svg

View File

@ -72,6 +72,12 @@ export const scope= {
if(scopes.length===1) return;
return scopes.pop();
},
isolate(fn){
this.push({ prevent: true });
fn();
this.pop();
}
};
/**
* Chainable append function for elements

View File

@ -11,6 +11,7 @@ import { oAssign, onAbort } from './helpers.js';
* @returns {Function} Function that dispatches the event
*/
export function dispatchEvent(name, options, host){
if(typeof options==="function"){ host= options; options= null; }
if(!options) options= {};
return function dispatch(element, ...d){
if(host){