1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2025-03-31 11:35:52 +02:00

Compare commits

...

5 Commits

Author SHA1 Message Date
a1d2c69049
🔤 md enhancements 2025-03-19 14:29:48 +01:00
7472cea880
🐛 logo alignemt (due to gh) 2025-03-19 14:07:39 +01:00
7664932041
🔤 Better build and improve texting 2025-03-19 13:57:16 +01:00
ad255e3e19
🐛 ensures only one disconncetd listener
…for cleanup
2025-03-19 11:39:45 +01:00
74ed180919
Signal ← SignalReadonly 2025-03-19 10:02:33 +01:00
34 changed files with 246 additions and 255 deletions

View File

@ -75,11 +75,14 @@ where everyone feels comfortable participating.
…see [BS folder](./bs/README.md) for more info.
## Categorizing [![git3moji](https://img.shields.io/badge/git3moji%E2%80%93v1-%E2%9A%A1%EF%B8%8F%F0%9F%90%9B%F0%9F%93%BA%F0%9F%91%AE%F0%9F%94%A4-fffad8.svg?style=flat-square)](https://robinpokorny.github.io/git3moji/) <!-- editorconfig-checker-disable-line -->
We use [git3moji](https://git3moji.netlify.app/) for commit messages, issue titles, pull request titles and in other
areas. To make categorizing quick and consistent.
## Commit Guidelines
We use
[![git3moji](https://img.shields.io/badge/git3moji%E2%80%93v1-%E2%9A%A1%EF%B8%8F%F0%9F%90%9B%F0%9F%93%BA%F0%9F%91%AE%F0%9F%94%A4-fffad8.svg?style=flat-square)](https://robinpokorny.github.io/git3moji/) <!-- editorconfig-checker-disable-line -->
for commit messages. This helps keep the commit history clear and consistent.
We use [git3moji](https://git3moji.netlify.app/) for commit messages. This helps keep the commit history clear and
consistent.
```
:emoji: Short summary of the change

View File

@ -1,18 +1,11 @@
**Alpha**
| [source code on GitHub](https://github.com/jaandrle/deka-dom-el)
| [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el)
| [npm package](https://www.npmjs.com/package/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 (dd\<el\> or DDE)
| [Docs](https://jaandrle.github.io/deka-dom-el "Official documentation and guide site")
| [NPM](https://www.npmjs.com/package/deka-dom-el "Official NPM package page")
| [GitHub](https://github.com/jaandrle/deka-dom-el "Official GitHub repository")
([*Gitea*](https://gitea.jaandrle.cz/jaandrle/deka-dom-el "GitHub repository mirror on my own Gitea instance"))
***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(
@ -20,25 +13,23 @@ document.body.append(
);
function EmojiCounter({ initial }) {
// ✨ State - Define reactive data
// ✨ - Define reactive data
const count = S(0);
const emoji = S(initial);
const textContent = S(() => `Hello World ${emoji.get().repeat(count.get())}`);
// 🔄 View - UI updates automatically when signals change
// 🔄 - UI updates automatically when signals change
return el().append(
el("p", {
className: "output",
textContent: S(() =>
`Hello World ${emoji.get().repeat(count.get())}`),
}),
el("p", { textContent, className: "output" }),
// 🎮 Controls - Update state on events
// 🎮 - Update state on events
el("button", { textContent: "Add Emoji" },
on("click", () => count.set(count.get() + 1))
on("click", () => count.set(count.get() + 1)),
),
el("select", null, on.defer(el=> el.value= initial),
on("change", e => emoji.set(e.target.value))
el("select", null,
on.defer(el=> el.value= initial),
on("change", e => emoji.set(e.target.value)),
).append(
el(Option, "🎉"),
el(Option, "🚀"),
@ -50,6 +41,13 @@ function Option({ textContent }){
return el("option", { value: textContent, textContent });
}
```
*…use simple DOM API by default and library tools and logic when you need them*
<p align="center">
<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180">
</p>
# Deka DOM Elements (dd\<el\> or DDE)
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
@ -61,19 +59,21 @@ Creating reactive elements, components, and Web Components using the native
- ☑️ **Lightweight** — ~10-15kB minified (original goal 10kB) with **zero**/minimal dependencies
- ✅ **Declarative & functional approach** for clean, maintainable code
- ✅ **Signals and events** for reactive UI
- ✅ **Auto-releasing resources** for memory management but nice development experience
- ✅ **Memoization for performance** — optimize rendering with intelligent caching
- **Optional build-in signals** with support for custom reactive implementations (#39)
- **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom)
- ☑️ **Optional build-in signals** with support for custom reactive implementations (#39)
- ☑️ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom)
- ✅ **TypeScript support**
- ☑️ **Support for debugging with browser DevTools** without extensions
- **Support for debugging with browser DevTools** without extensions
- ☑️ **Enhanced Web Components** support
## Getting Started
### Documentation
### Quick Links
- [**Documentation and Guide**](https://jaandrle.github.io/deka-dom-el)
- [**Examples**](https://jaandrle.github.io/deka-dom-el/p15-examples.html)
- [**Changelog**](https://github.com/jaandrle/deka-dom-el/releases)
### Installation
@ -138,9 +138,9 @@ get started, coding standards, commit guidelines, and the pull request process.
interfaces or HTML code.
- [pota](https://pota.quack.uy/) — small and pluggable Reactive Web Renderer. It's compiler-less, includes an html
function, and a optimized babel preset in case you fancy JSX.
- [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) —
Functional DOM components without JSX/virtual DOM
- [TarekRaafat/eleva](https://github.com/TarekRaafat/eleva) — A minimalist, lightweight, pure vanilla JavaScript
frontend runtime framework.
- [didi/mpx](https://github.com/didi/mpx) — Mpx一款具有优秀开发体验和深度性能优化的增强型跨端小程序框架
- [mxjp/rvx](https://github.com/mxjp/rvx) — A signal based frontend framework
- [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) —
Functional DOM components without JSX/virtual DOM (my old library)

View File

@ -2,7 +2,7 @@
/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */// editorconfig-checker-disable-line
echo("Building static documentation files…");
echo("Preparing…");
import { path_target, pages as pages_registered, styles, dispatchEvent, t } from "../docs/ssr.js";
import { path_target, pages as pages_registered, styles, currentPageId, dispatchEvent, t } from "../docs/ssr.js";
import { createHTMl } from "./docs/jsdom.js";
import { register, queue } from "../jsdom.js";
const pkg= s.cat("package.json").xargs(JSON.parse);
@ -28,6 +28,7 @@ for(const { id, info } of pages){
);
const { el }= await register(serverDOM.dom);
const { page }= await import(`../docs/${id}.html.js`);
currentPageId(id)
serverDOM.document.body.append(
el(page, { pkg, info }),
);

View File

@ -696,13 +696,10 @@ var queueSignalWrite = /* @__PURE__ */ (() => {
})();
// src/signals-lib/signals-lib.js
var Signal = oCreate(null, {
var SignalReadOnly = oCreate(null, {
get: { value() {
return read(this);
} },
set: { value(...v) {
return write(this, ...v);
} },
toJSON: { value() {
return read(this);
} },
@ -710,9 +707,9 @@ var Signal = oCreate(null, {
return this[mark] && this[mark].value;
} }
});
var SignalReadOnly = oCreate(Signal, {
set: { value() {
return;
var Signal = oCreate(SignalReadOnly, {
set: { value(...v) {
return write(this, ...v);
} }
});
function isSignal(candidate) {
@ -889,9 +886,10 @@ var signals_config = {
function removeSignalsFromElements(s, listener, ...notes) {
const { current } = scope;
current.host(function(element) {
if (!element[key_reactive]) element[key_reactive] = [];
const is_first = !element[key_reactive];
if (is_first) element[key_reactive] = [];
element[key_reactive].push([[s, listener], ...notes]);
if (current.prevent) return;
if (!is_first || current.prevent) return;
on.disconnected(
() => (
/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, ?).
@ -906,7 +904,7 @@ var cleanUpRegistry = new FinalizationRegistry(function(s) {
});
function create(is_readonly, value, actions) {
const varS = oCreate(is_readonly ? SignalReadOnly : Signal);
const SI = toSignal(varS, value, actions, is_readonly);
const SI = toSignal(varS, value, actions);
cleanUpRegistry.register(SI, SI[mark]);
return SI;
}
@ -918,7 +916,7 @@ var protoSigal = oAssign(oCreate(), {
this.skip = true;
}
});
function toSignal(s, value, actions, readonly = false) {
function toSignal(s, value, actions) {
const onclear = [];
if (typeOf(actions) !== "[object Object]")
actions = {};
@ -934,8 +932,7 @@ function toSignal(s, value, actions, readonly = false) {
actions,
onclear,
host,
listeners: /* @__PURE__ */ new Set(),
readonly
listeners: /* @__PURE__ */ new Set()
}),
enumerable: false,
writable: false,

File diff suppressed because one or more lines are too long

View File

@ -741,13 +741,10 @@ var DDE = (() => {
})();
// src/signals-lib/signals-lib.js
var Signal = oCreate(null, {
var SignalReadOnly = oCreate(null, {
get: { value() {
return read(this);
} },
set: { value(...v) {
return write(this, ...v);
} },
toJSON: { value() {
return read(this);
} },
@ -755,9 +752,9 @@ var DDE = (() => {
return this[mark] && this[mark].value;
} }
});
var SignalReadOnly = oCreate(Signal, {
set: { value() {
return;
var Signal = oCreate(SignalReadOnly, {
set: { value(...v) {
return write(this, ...v);
} }
});
function isSignal(candidate) {
@ -934,9 +931,10 @@ var DDE = (() => {
function removeSignalsFromElements(s, listener, ...notes) {
const { current } = scope;
current.host(function(element) {
if (!element[key_reactive]) element[key_reactive] = [];
const is_first = !element[key_reactive];
if (is_first) element[key_reactive] = [];
element[key_reactive].push([[s, listener], ...notes]);
if (current.prevent) return;
if (!is_first || current.prevent) return;
on.disconnected(
() => (
/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, ?).
@ -951,7 +949,7 @@ var DDE = (() => {
});
function create(is_readonly, value, actions) {
const varS = oCreate(is_readonly ? SignalReadOnly : Signal);
const SI = toSignal(varS, value, actions, is_readonly);
const SI = toSignal(varS, value, actions);
cleanUpRegistry.register(SI, SI[mark]);
return SI;
}
@ -963,7 +961,7 @@ var DDE = (() => {
this.skip = true;
}
});
function toSignal(s, value, actions, readonly = false) {
function toSignal(s, value, actions) {
const onclear = [];
if (typeOf(actions) !== "[object Object]")
actions = {};
@ -979,8 +977,7 @@ var DDE = (() => {
actions,
onclear,
host,
listeners: /* @__PURE__ */ new Set(),
readonly
listeners: /* @__PURE__ */ new Set()
}),
enumerable: false,
writable: false,

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
import { registerClientFile, styles } from "../ssr.js";
import { page_id, registerClientFile, styles } from "../ssr.js";
const host= "."+code.name;
styles.css`
/* Code block styling */
@ -177,6 +177,9 @@ ${host}:hover .copy-button {
}
`;
import { el } from "deka-dom-el";
/**
* @typedef {"js"|"ts"|"html"|"css"|"shell"|"-"} Language
* */
/**
* Prints code to the page and registers flems to make it interactive.
* @param {object} attrs
@ -184,15 +187,17 @@ import { el } from "deka-dom-el";
* @param {string} [attrs.className]
* @param {URL} [attrs.src] Example code file path
* @param {string} [attrs.content] Example code
* @param {"js"|"ts"|"html"|"css"|"shell"} [attrs.language="js"] Language of the code
* @param {string} [attrs.page_id] ID of the page, if setted it registers shiki
* @param {Language} [attrs.language="-s"] Language of the code
* */
export function code({ id, src, content, language= "js", className= host.slice(1), page_id }){
if(src) content= s.cat(src);
export function code({ id, src, content, language= "-", className= host.slice(1) }){
if(src){
content= s.cat(src);
if(language=== "-") language= /** @type {Language} */(src.pathname.split(".").pop());
}
content= normalizeIndentation(content);
let dataJS;
if(page_id){
registerClientPart(page_id);
if(language!== "-"){
registerClientPart();
dataJS= "todo";
}
return el("div", { id, className, dataJS, tabIndex: 0 }).append(
@ -204,8 +209,7 @@ export function pre({ content }){
return el("pre").append(el("code", content.trim()));
}
let is_registered= {};
/** @param {string} page_id */
function registerClientPart(page_id){
function registerClientPart(){
if(is_registered[page_id]) return;
// Add Shiki with a more reliable loading method

View File

@ -1,4 +1,4 @@
import { styles } from "../ssr.js";
import { page_id, styles } from "../ssr.js";
styles.css`
#html-to-dde-converter {
@ -149,12 +149,11 @@ import { ireland } from "./ireland.html.js";
import { el } from "deka-dom-el";
const fileURL= url=> new URL(url, import.meta.url);
export function converter({ page_id }){
export function converter(){
registerClientPart(page_id);
return el(ireland, {
src: fileURL("./converter.js.js"),
exportName: "converter",
page_id,
});
}

View File

@ -1,4 +1,4 @@
import { styles } from "../ssr.js";
import { page_id, styles } from "../ssr.js";
const host= "."+example.name;
styles.css`
${host} {
@ -119,9 +119,8 @@ import { relative } from "node:path";
* @param {URL} attrs.src Example code file path
* @param {"js"|"ts"|"html"|"css"} [attrs.language="js"] Language of the code
* @param {"normal"|"big"} [attrs.variant="normal"] Size of the example
* @param {string} attrs.page_id ID of the page
* */
export function example({ src, language= "js", variant= "normal", page_id }){
export function example({ src, language= "js", variant= "normal" }){
registerClientPart(page_id);
const content= s.cat(src).toString()
.replaceAll(/ from "deka-dom-el(\/signals)?";/g, ' from "./esm-with-signals.js";');

View File

@ -303,6 +303,7 @@ document.body.append(
padding: 1rem;
margin-bottom: 1.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
overflow: auto;
}
.loading-spinner {

View File

@ -483,7 +483,7 @@ document.body.append(
.task-board {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
}

View File

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

View File

@ -6,6 +6,6 @@ import { el } from "deka-dom-el";
document.body.append(
el("div").append(
el("h1", "Title"),
el("p", "Paragraph")
)
el("p", "Paragraph"),
),
);

View File

@ -1,4 +1,4 @@
import { styles } from "../ssr.js";
import { styles, page_id } from "../ssr.js";
styles.css`
#library-url-form {
@ -74,7 +74,7 @@ styles.css`
import { el } from "deka-dom-el";
import { ireland } from "./ireland.html.js";
export function getLibraryUrl({ page_id }){
export function getLibraryUrl(){
return el(ireland, {
src: new URL("./getLibraryUrl.js.js", import.meta.url),
exportName: "getLibraryUrl",

View File

@ -43,7 +43,6 @@ const componentsRegistry = new Map();
* @param {object} attrs
* @param {URL} attrs.src - Path to the file containing the component
* @param {string} [attrs.exportName="default"] - Name of the export to use
* @param {string} attrs.page_id - ID of the current page
* @param {object} [attrs.props={}] - Props to pass to the component
*/
export function ireland({ src, exportName = "default", props = {} }) {

View File

@ -12,6 +12,10 @@ export function mnemonic(){
" — corresponds to custom elemnets callbacks ", el("code", "<live-cycle>Callback(...){...}"),
". To connect to custom element see following page, else it is simulated by MutationObserver."
),
el("li").append(
el("code", "on.defer(<identity>=> <identity>)(<identity>)"),
" — calls callback later",
),
el("li").append(
el("code", "dispatchEvent(<event>[, <options>])(element)"),
" — just ", el("code", "<element>.dispatchEvent(new Event(<event>[, <options>]))")

View File

@ -3,8 +3,8 @@ import { t, T } from "./utils/index.js";
export const info= {
href: "./",
title: t`Introduction`,
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`,
fullTitle: t`Vanilla for flavouring — a full-fledged feast for large projects`,
description: t`Reactive DOM library for creating dynamic UIs with a declarative syntax`,
};
import { el } from "deka-dom-el";
@ -31,10 +31,9 @@ const references= {
};
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(T`
Welcome to Deka DOM Elements (dd<el> or DDE) a lightweight library for building dynamic UIs with
Welcome to Deka DOM Elements (dd<el> or DDE) a library for building dynamic UIs with
a declarative syntax that stays close to the native DOM API. dd<el> gives you powerful reactive tools
without the complexity and overhead of larger frameworks.
`),
@ -44,11 +43,11 @@ export function page({ pkg, info }){
el("li", t`No build step required — use directly in the browser`),
el("li", t`Lightweight core (~1015kB minified) without unnecessary dependencies (0 at now 😇)`),
el("li", t`Natural DOM API — work with real DOM nodes, not abstractions`),
el("li", t`Built-in reactivity with simplified but powerful signals system`),
el("li", t`Built-in (but optional) reactivity with simplified but 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(example, { src: fileURL("./components/examples/introducing/helloWorld.js") }),
el(h3, { textContent: t`The 3PS Pattern: Simplified architecture pattern`, id: "h-3ps" }),
el("p").append(T`
@ -60,11 +59,11 @@ export function page({ pkg, info }){
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(code, { src: fileURL("./components/examples/introducing/3ps-before.js") }),
),
el("div", { className: "tab" }).append(
el("h5", t`dd<el>'s 3PS Pattern`),
el(code, { src: fileURL("./components/examples/introducing/3ps.js"), page_id }),
el(code, { src: fileURL("./components/examples/introducing/3ps.js") }),
)
)
),
@ -105,11 +104,11 @@ export function page({ pkg, info }){
el(h3, t`Getting Started`),
el("p").append(T`
There are multiple ways to include dd<el> in your project. You can use npm for a full development setup,
or directly include it from a CDN for quick prototyping.
There are multiple ways to include dd<el> in your project. You can use npm for a full development setup,
or directly include it from a CDN for quick prototyping.
`),
el("h4", "npm installation"),
el(code, { content: "npm install deka-dom-el --save", language: "shell", page_id }),
el(code, { content: "npm install deka-dom-el --save", language: "shell" }),
el("p").append(T`
see ${el("a", { textContent: "package page", ...references.npm, target: "_blank" })}.
`),
@ -118,7 +117,7 @@ export function page({ pkg, info }){
el("p").append(T`
Use the interactive selector below to choose your preferred format:
`),
el(getLibraryUrl, { page_id }),
el(getLibraryUrl),
el("div", { className: "note" }).append(
el("p").append(T`
Based on your selection, you can use dd<el> in your project like this:
@ -127,10 +126,10 @@ export function page({ pkg, info }){
// ESM format (modern JavaScript with import/export)
import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.min.js";
// Or with IIFE format (creates a global DDE object)
// Or with IIFE format (creates a global DDE object)
// <script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/iife-with-signals.min.js"></script>
const { el, on } = DDE;
`, language: "js", page_id }),
`, language: "js" }),
),
el(h3, t`How to Use This Documentation`),
@ -154,7 +153,7 @@ export function page({ pkg, info }){
Integrating third-party functionalities`),
el("li").append(T`${el("a", { href: "p09-optimization.html" })
.append(el("strong", "Performance Optimization"))} Techniques for optimizing your applications`),
el("li").append(T`${el("a", { href: "p10-todomvc.html" }).append(el("strong", "TodoMVC"))} — A real-world
el("li").append(T`${el("a", { href: "p10-todomvc.html" }).append(el("strong", "TodoMVC"))} — A real-world
application implementation`),
el("li").append(T`${el("a", { href: "p11-ssr.html" }).append(el("strong", "SSR"))} — Server-side
rendering with dd<el>`),

View File

@ -47,12 +47,11 @@ const references= {
};
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(T`
Building user interfaces in JavaScript often involves creating and manipulating DOM elements.
dd<el> provides a simple yet powerful approach to element creation that is declarative, chainable,
and maintains a clean syntax close to HTML structure.
dd<el> provides a simple yet powerful approach to element creation that is declarative, chainable,
and maintains a clean syntax close to HTML structure.
`),
el("div", { className: "callout" }).append(
el("h4", t`dd<el> Elements: Key Benefits`),
@ -65,7 +64,7 @@ export function page({ pkg, info }){
)
),
el(code, { src: fileURL("./components/examples/elements/intro.js"), page_id }),
el(code, { src: fileURL("./components/examples/elements/intro.js") }),
el(h3, t`Creating Elements: Native vs dd<el>`),
el("p").append(T`
@ -77,11 +76,11 @@ export function page({ pkg, info }){
el("div", { className: "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(code, { src: fileURL("./components/examples/elements/native-dom-create.js") })
),
el("div").append(
el("h5", t`dd<el> Approach`),
el(code, { src: fileURL("./components/examples/elements/dde-dom-create.js"), page_id })
el(code, { src: fileURL("./components/examples/elements/dde-dom-create.js") })
)
)
),
@ -89,7 +88,7 @@ export function page({ pkg, info }){
The ${el("code", "el")} function provides a simple wrapper around ${el("code", "document.createElement")}
with enhanced property assignment.
`),
el(example, { src: fileURL("./components/examples/elements/dekaCreateElement.js"), page_id }),
el(example, { src: fileURL("./components/examples/elements/dekaCreateElement.js") }),
el(h3, t`Advanced Property Assignment`),
el("p").append(T`
@ -122,7 +121,7 @@ export function page({ pkg, info }){
el("dd").append(T`Pass ${el("code", "undefined")} to remove a property or attribute`)
)
),
el(example, { src: fileURL("./components/examples/elements/dekaAssign.js"), page_id }),
el(example, { src: fileURL("./components/examples/elements/dekaAssign.js") }),
el("div", { className: "note" }).append(
el("p").append(T`
@ -142,11 +141,11 @@ export function page({ pkg, info }){
el("div", { className: "comparison" }).append(
el("div", { className: "bad-practice" }).append(
el("h5", t`❌ Native DOM API`),
el(code, { src: fileURL("./components/examples/elements/native-dom-tree.js"), page_id })
el(code, { src: fileURL("./components/examples/elements/native-dom-tree.js") })
),
el("div", { className: "good-practice" }).append(
el("h5", t`✅ dd<el> Approach`),
el(code, { src: fileURL("./components/examples/elements/dde-dom-tree.js"), page_id })
el(code, { src: fileURL("./components/examples/elements/dde-dom-tree.js") })
)
)
),
@ -154,14 +153,14 @@ export function page({ pkg, info }){
This chainable pattern is much cleaner and easier to follow, especially for deeply nested elements.
It also makes it simple to add multiple children to a parent element in a single fluent expression.
`),
el(example, { src: fileURL("./components/examples/elements/dekaAppend.js"), page_id }),
el(example, { src: fileURL("./components/examples/elements/dekaAppend.js") }),
el(h3, t`Using Components to Build UI Fragments`),
el("p").append(T`
The ${el("code", "el")} function is overloaded to support both tag names and function components.
This lets you refactor complex UI trees into reusable pieces:
`),
el(example, { src: fileURL("./components/examples/elements/dekaBasicComponent.js"), page_id }),
el(example, { src: fileURL("./components/examples/elements/dekaBasicComponent.js") }),
el("p").append(T`
Component functions receive the properties object as their first argument, just like regular elements.
This makes it easy to pass data down to components and create reusable UI fragments.
@ -185,9 +184,9 @@ export function page({ pkg, info }){
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(example, { src: fileURL("./components/examples/elements/dekaElNS.js") }),
el("p").append(T`
This function returns a namespace-specific element creator, allowing you to work with any element type
This function returns a namespace-specific element creator, allowing you to work with any element type
using the same consistent interface.
`),

View File

@ -39,7 +39,6 @@ const references= {
};
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(T`
Events are at the core of interactive web applications. dd<el> provides a clean, declarative approach to
@ -57,7 +56,7 @@ export function page({ pkg, info }){
)
),
el(code, { src: fileURL("./components/examples/events/intro.js"), page_id }),
el(code, { src: fileURL("./components/examples/events/intro.js") }),
el(h3, t`Events and Listeners: Two Approaches`),
el("p").append(T`
@ -70,11 +69,11 @@ export function page({ pkg, info }){
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(code, { content: `element.addEventListener("click", callback, options);`, language: "js" })
),
el("div", { className: "tab" }).append(
el("h5", t`dd<el> Approach`),
el(code, { content: `on("click", callback, options)(element);`, page_id })
el(code, { content: `on("click", callback, options)(element);`, language: "js" })
)
)
),
@ -82,7 +81,7 @@ export function page({ pkg, info }){
The main benefit of dd<el>s approach is that it works as an Addon (see below), making it easy to integrate
directly into element declarations.
`),
el(example, { src: fileURL("./components/examples/events/compare.js"), page_id }),
el(example, { src: fileURL("./components/examples/events/compare.js") }),
el(h3, t`Removing Event Listeners`),
el("div", { className: "note" }).append(
@ -91,7 +90,7 @@ export function page({ pkg, info }){
${el("a", { textContent: "AbortSignal", ...references.mdn_abortListener })} for declarative removal:
`)
),
el(example, { src: fileURL("./components/examples/events/abortSignal.js"), page_id }),
el(example, { src: fileURL("./components/examples/events/abortSignal.js") }),
el("p").append(T`
This is the same for signals (see next section) and works well with scopes and library extendability (
see scopes and extensions section mainly ${el("code", "scope.signal")}).
@ -101,26 +100,26 @@ export function page({ pkg, info }){
el("div", { className: "tabs" }).append(
el("div", { className: "tab", dataTab: "html-attr" }).append(
el("h4", t`HTML Attribute Style`),
el(code, { src: fileURL("./components/examples/events/attribute-event.js"), page_id }),
el(code, { src: fileURL("./components/examples/events/attribute-event.js") }),
el("p").append(T`
Forces usage as an HTML attribute. Corresponds to
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", dataTab: "property" }).append(
el("h4", t`Property Assignment`),
el(code, { src: fileURL("./components/examples/events/property-event.js"), page_id }),
el(code, { src: fileURL("./components/examples/events/property-event.js") }),
el("p", t`Assigns the event handler directly to the elements property.`)
),
el("div", { className: "tab", dataTab: "addon" }).append(
el("h4", t`Addon Approach`),
el(code, { src: fileURL("./components/examples/events/chain-event.js"), page_id }),
el(code, { src: fileURL("./components/examples/events/chain-event.js") }),
el("p", t`Uses the addon pattern (so adds the event listener to the element), see above.`)
)
),
el("p").append(T`
For a deeper comparison of these approaches, see
For a deeper comparison of these approaches, see
${el("a", { textContent: "WebReflections detailed analysis", ...references.web_events })}.
`),
@ -143,7 +142,7 @@ export function page({ pkg, info }){
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(example, { src: fileURL("./components/examples/events/templateWithListeners.js") }),
el("p").append(T`
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.
@ -167,7 +166,7 @@ export function page({ pkg, info }){
el("dd", t`Fires when the element is removed from the DOM`),
)
),
el(example, { src: fileURL("./components/examples/events/live-cycle.js"), page_id }),
el(example, { src: fileURL("./components/examples/events/live-cycle.js") }),
el("div", { className: "note" }).append(
el("p").append(T`
@ -217,8 +216,8 @@ export function page({ pkg, info }){
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(example, { src: fileURL("./components/examples/events/compareDispatch.js"), page_id }),
el(code, { src: fileURL("./components/examples/events/dispatch.js"), page_id }),
el(example, { src: fileURL("./components/examples/events/compareDispatch.js") }),
el(code, { src: fileURL("./components/examples/events/dispatch.js") }),
el(h3, t`Best Practices`),
el("ol").append(
@ -251,6 +250,6 @@ export function page({ pkg, info }){
)
),
el(mnemonic)
el(mnemonic),
);
}

View File

@ -42,7 +42,6 @@ const references= {
};
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(T`
Signals provide a simple yet powerful way to create reactive applications with dd<el>. They handle the
@ -58,7 +57,7 @@ export function page({ pkg, info }){
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 }),
el(code, { src: fileURL("./components/examples/signals/intro.js") }),
el(h3, t`The 3-Part Structure of Signals`),
el("p").append(T`
@ -68,21 +67,21 @@ export function page({ pkg, info }){
el("div", { className: "signal-diagram" }).append(
el("div", { className: "signal-part" }).append(
el("h4", t`PART 1: Create Signal`),
el(code, { content: "const count = S(0);", page_id }),
el(code, { content: "const count = S(0);", language: "js" }),
el("p", t`Define a reactive value that can be observed and changed`)
),
el("div", { className: "signal-part" }).append(
el("h4", t`PART 2: React to Changes`),
el(code, { content: "S.on(count, value => updateUI(value));", page_id }),
el(code, { content: "S.on(count, value => updateUI(value));", language: "js" }),
el("p", t`Subscribe to signal changes with callbacks or effects`)
),
el("div", { className: "signal-part" }).append(
el("h4", t`PART 3: Update Signal`),
el(code, { content: "count.set(count.get() + 1);", page_id }),
el(code, { content: "count.set(count.get() + 1);", language: "js" }),
el("p", t`Modify the signal value, which automatically triggers updates`)
)
),
el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }),
el(example, { src: fileURL("./components/examples/signals/signals.js") }),
el("div", { className: "note" }).append(
el("p").append(T`
@ -125,12 +124,12 @@ export function page({ pkg, info }){
Computed values (also called derived signals) automatically update when their dependencies change.
Create them by passing ${el("strong", "a function")} to ${el("code", "S()")}:
`),
el(example, { src: fileURL("./components/examples/signals/derived.js"), page_id }),
el(example, { src: fileURL("./components/examples/signals/derived.js") }),
el("p").append(T`
Derived signals are read-only - you cant call ${el("code", ".set()")} on them. Their value is always
computed from their dependencies. Theyre perfect for transforming or combining data from other signals.
`),
el(example, { src: fileURL("./components/examples/signals/computations-abort.js"), page_id }),
el(example, { src: fileURL("./components/examples/signals/computations-abort.js") }),
el(h3, t`Signal Actions: For Complex State`),
el("p").append(T`
@ -151,7 +150,7 @@ export function page({ pkg, info }){
});
// Use the action
S.action(todos, "add", "New todo");
`, page_id })
`, language: "js" })
),
el("div", { className: "bad-practice" }).append(
el("h5", t`❌ Without Actions`),
@ -161,7 +160,7 @@ export function page({ pkg, info }){
const items = todos.get();
items.push("New todo");
// This WONT trigger updates!
`, page_id }))
`, language: "js" }))
),
),
el("p").append(T`
@ -172,7 +171,7 @@ export function page({ pkg, info }){
${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(example, { src: fileURL("./components/examples/signals/actions-demo.js") }),
el("p").append(T`
Actions provide these benefits:
@ -186,7 +185,7 @@ export function page({ pkg, info }){
el("p").append(T`
Heres a more complete example of a todo list using signal actions:
`),
el(example, { src: fileURL("./components/examples/signals/actions-todos.js"), page_id }),
el(example, { src: fileURL("./components/examples/signals/actions-todos.js") }),
el("div", { className: "tip" }).append(
el("p").append(T`
@ -223,7 +222,7 @@ export function page({ pkg, info }){
// Later:
color.set("red"); // UI updates automatically
`, page_id }),
`, language: "js" }),
),
el("div", { className: "tab", dataTab: "elements" }).append(
el("h4", t`Reactive Elements`),
@ -241,7 +240,7 @@ export function page({ pkg, info }){
// Later:
S.action(items, "push", "Dragonfruit"); // List updates automatically
`, page_id }),
`, language: "js" }),
)
),
@ -250,12 +249,12 @@ export function page({ pkg, info }){
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(example, { src: fileURL("./components/examples/signals/dom-attrs.js") }),
el("p").append(T`
${el("code", "S.el()")} is especially powerful for conditional rendering and lists:
`),
el(example, { src: fileURL("./components/examples/signals/dom-el.js"), page_id }),
el(example, { src: fileURL("./components/examples/signals/dom-el.js") }),
el(h3, t`Best Practices for Signals`),
el("p").append(T`
@ -289,7 +288,7 @@ export function page({ pkg, info }){
el("h4", t`We can process form events without signals`),
el("p", t`This can be used when the form data doesnt need to be reactive and we just waiting for
results.`),
el(code, { page_id, content: `
el(code, { content: `
const onFormSubmit = on("submit", e => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
@ -301,13 +300,13 @@ export function page({ pkg, info }){
return el("form", null, onFormSubmit).append(
// …
);
` })
`, language: "js" })
),
el("div", { className: "tab", dataTab: "variables" }).append(
el("h4", t`We can use variables without signals`),
el("p", t`We use this when we dontt need to reflect changes in the elsewhere (UI).`),
el(code, { page_id, content: `
el(code, { content: `
let canSubmit = false;
const onFormSubmit = on("submit", e => {
@ -318,14 +317,14 @@ export function page({ pkg, info }){
const onAllowSubmit = on("click", e => {
canSubmit = true;
});
`}),
`, language: "js" }),
),
el("div", { className: "tab", dataTab: "state" }).append(
el("h4", t`Using signals`),
el("p", t`We use this when we need to reflect changes for example in the UI (e.g. enable/disable
buttons).`),
el(code, { page_id, content: `
el(code, { content: `
const canSubmit = S(false);
const onFormSubmit = on("submit", e => {
@ -341,7 +340,7 @@ export function page({ pkg, info }){
el("button", { textContent: "Allow Submit", type: "button" }, onAllowSubmit),
el("button", { disabled: S(()=> !canSubmit), textContent: "Submit" })
);
`}),
`, language: "js" }),
),
),

View File

@ -27,14 +27,13 @@ const references= {
};
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(T`
For state-less components we can use functions as UI components (see Elements page). But in real life,
we may need to handle the components life-cycle and provide JavaScript the way to properly use
the ${el("a", { textContent: t`Garbage collection`, ...references.garbage_collection })}.
`),
el(code, { src: fileURL("./components/examples/scopes/intro.js"), page_id }),
el(code, { src: fileURL("./components/examples/scopes/intro.js") }),
el("p").append(T`The library therefore uses ${el("em", t`scopes`)} to provide these functionalities.`),
el(h3, t`Understanding Host Elements and Scopes`),
@ -83,7 +82,7 @@ export function page({ pkg, info }){
el("dd", t`Applies the addons to the host element (and returns the host element)`)
)
),
el(example, { src: fileURL("./components/examples/scopes/scopes-and-hosts.js"), page_id }),
el(example, { src: fileURL("./components/examples/scopes/scopes-and-hosts.js") }),
el("div", { className: "tip" }).append(
el("p").append(T`
@ -95,7 +94,7 @@ export function page({ pkg, info }){
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(code, { src: fileURL("./components/examples/scopes/good-practise.js") }),
el(h3, t`Class-Based Components`),
el("p").append(T`
@ -103,7 +102,7 @@ export function page({ pkg, info }){
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(example, { src: fileURL("./components/examples/scopes/class-component.js") }),
el(h3, t`Automatic Cleanup with Scopes`),
el("p").append(T`
@ -123,7 +122,7 @@ export function page({ pkg, info }){
- Custom cleanup code (dd<el> and user)
` })
),
el(example, { src: fileURL("./components/examples/scopes/cleaning.js"), page_id }),
el(example, { src: fileURL("./components/examples/scopes/cleaning.js") }),
el("div", { className: "note" }).append(
el("p").append(T`
@ -149,17 +148,17 @@ export function page({ pkg, info }){
el("div", { className: "tab", dataTab: "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(code, { src: fileURL("./components/examples/scopes/declarative.js") })
),
el("div", { className: "tab", dataTab: "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(code, { src: fileURL("./components/examples/scopes/imperative.js") })
),
el("div", { className: "tab", dataTab: "mixed" }).append(
el("h4", t`❌ Mixed Approach`),
el("p", t`This approach should be avoided:`),
el(code, { src: fileURL("./components/examples/scopes/mixed.js"), page_id })
el(code, { src: fileURL("./components/examples/scopes/mixed.js") })
)
),

View File

@ -57,7 +57,6 @@ const references= {
};
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(T`
dd<el> pairs powerfully with ${el("a", references.mdn_web_components).append(el("strong", t`Web
@ -73,7 +72,7 @@ export function page({ pkg, info }){
el("li", t`Clean component lifecycle management`),
),
),
el(code, { src: fileURL("./components/examples/customElement/intro.js"), page_id }),
el(code, { src: fileURL("./components/examples/customElement/intro.js") }),
el(h3, t`Getting Started: Web Components Basics`),
el("p").append(T`
@ -95,7 +94,7 @@ export function page({ pkg, info }){
el("p").append(T`
Lets start with a basic Custom Element example without dd<el> to establish the foundation:
`),
el(code, { src: fileURL("./components/examples/customElement/native-basic.js"), page_id }),
el(code, { src: fileURL("./components/examples/customElement/native-basic.js") }),
el("div", { className: "note" }).append(
el("p").append(T`
@ -124,7 +123,7 @@ export function page({ pkg, info }){
el("dd", t`Allows using on.connected(), on.disconnected() or S.observedAttributes().`)
)
),
el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js"), page_id }),
el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js") }),
el("div", { className: "tip" }).append(
el("p").append(T`
@ -156,7 +155,7 @@ export function page({ pkg, info }){
el("dd", t`The rendered DOM tree`)
)
),
el(example, { src: fileURL("./components/examples/customElement/dde.js"), page_id }),
el(example, { src: fileURL("./components/examples/customElement/dde.js") }),
el("div", { className: "note" }).append(
el("p").append(T`
@ -188,7 +187,7 @@ export function page({ pkg, info }){
Using the ${el("code", "S.observedAttributes")} creates a reactive connection between your elements
attributes and its internal rendering. When attributes change, your component automatically updates!
`),
el(example, { src: fileURL("./components/examples/customElement/observedAttributes.js"), page_id }),
el(example, { src: fileURL("./components/examples/customElement/observedAttributes.js") }),
el("div", { className: "callout" }).append(
el("h4", t`How S.observedAttributes Works`),
@ -221,7 +220,7 @@ export function page({ pkg, info }){
<p>Content</p>
` })
),
el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js"), page_id }),
el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js") }),
el("p").append(T`
For more information on Shadow DOM, see
@ -234,7 +233,7 @@ export function page({ pkg, info }){
Besides the encapsulation, the Shadow DOM allows for using the ${el("a", references.mdn_shadow_dom_slot).append(
el("strong", t`<slot>`), t` element(s)`)}. You can simulate this feature using ${el("code", "simulateSlots")}:
`),
el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js"), page_id }),
el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js") }),
el("div", { className: "function-table" }).append(
el("h4", t`simulateSlots`),
el("dl").append(

View File

@ -15,7 +15,6 @@ 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("p").append(T`
Debugging is an essential part of application development. This guide provides techniques
@ -36,7 +35,7 @@ export function page({ pkg, info }){
el(code, { content: `
const signal = S(0);
console.log('Current value:', signal.valueOf());
`, page_id }),
`, language: "js" }),
el("div", { className: "warning" }).append(
el("p").append(T`
${el("code", "signal.get")} is OK, but in some situations may lead to unexpected results:
@ -49,7 +48,7 @@ export function page({ pkg, info }){
// but typically this is fine ↓
return signal.get() + 1;
});
` })
`, language: "js" })
),
el("p").append(T`
You can also monitor signal changes by adding a listener:
@ -57,7 +56,7 @@ export function page({ pkg, info }){
el(code, { content: `
// Log every time the signal changes
S.on(signal, value => console.log('Signal changed:', value));
`, page_id }),
`, language: "js" }),
el("h4", t`Debugging derived signals`),
el("p").append(T`
@ -69,7 +68,7 @@ export function page({ pkg, info }){
el("li", t`Add logging/debugger 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(example, { src: fileURL("./components/examples/debugging/consoleLog.js") }),
el("h4", t`Examining signal via DevTools`),
el("p").append(T`
@ -77,11 +76,11 @@ export function page({ pkg, info }){
signal objects. It contains the following information:
`),
el("ul").append(
// TODO: value?
el("li", t`listeners: A Set of functions called when the signal value changes`),
el("li", t`actions: Custom actions that can be performed on the signal`),
el("li", t`onclear: Functions to run when the signal is cleared`),
el("li", t`host: Reference to the host element/scope in which the signal was created`),
el("li", t`readonly: Boolean flag indicating if the signal is read-only`)
),
el("p").append(T`
to determine the current value of the signal, call ${el("code", "signal.valueOf()")}. Dont hesitate to
@ -121,7 +120,7 @@ export function page({ pkg, info }){
"S.el(S(()=> count.get() % 2), odd=> …)")}).
`),
),
el(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }),
el(code, { src: fileURL("./components/examples/debugging/mutations.js") }),
el("h4", t`Memory leaks with signal listeners`),
el("p").append(T`
@ -137,7 +136,7 @@ export function page({ pkg, info }){
el("li", t`Make sure derived signals dont 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(code, { src: fileURL("./components/examples/debugging/debouncing.js") }),
el(h3, t`Browser DevTools tips for components and reactivity`),
el("p").append(T`
@ -150,7 +149,7 @@ export function page({ pkg, info }){
that are automatically updated when signal values change. These elements are wrapped in special
comment nodes for debugging (to be true they are also used internally, so please do not edit them by hand):
`),
el(code, { src: fileURL("./components/examples/debugging/dom-reactive-mark.html"), page_id }),
el(code, { src: fileURL("./components/examples/debugging/dom-reactive-mark.html") }),
el("p").append(T`
This is particularly useful when debugging why a reactive section isnt updating as expected.
You can inspect the elements between the comment nodes to see their current state and the
@ -187,7 +186,7 @@ export function page({ pkg, info }){
so you can see the element and property that changes in the console right away. These properties make it
easier to understand the reactive structure of your application when inspecting elements.
`),
el(example, { src: fileURL("./components/examples/signals/debugging-dom.js"), page_id }),
el(example, { src: fileURL("./components/examples/signals/debugging-dom.js") }),
el("p", { className: "note" }).append(T`
${el("code", "<element>.__dde_reactive")} - An array property on DOM elements that tracks signal-to-element

View File

@ -14,7 +14,6 @@ const fileURL= url=> new URL(url, import.meta.url);
/** @param {import("./types.js").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(T`
dd<el> is designed with extensibility in mind. This page covers how to separate
@ -49,7 +48,7 @@ export function page({ pkg, info }){
// Using an addon
el("div", { id: "example" }, myAddon({ option: "value" }));
`, page_id }),
`, language: "js" }),
el(h3, t`Resource Cleanup with Abort Signals`),
el("p").append(T`
@ -83,7 +82,7 @@ export function page({ pkg, info }){
const { signal }= scope;
return el("div", null, externalLibraryAddon({ option: "value" }, signal));
}
`, page_id }),
`, language: "js" }),
el(h3, t`Building Library-Independent Extensions`),
el("p").append(T`
@ -104,7 +103,7 @@ export function page({ pkg, info }){
});
};
}
`, page_id })
`, language: "js" })
),
el("div", { className: "tab" }).append(
el("h5", t`⚠️ Library-Dependent`),
@ -118,7 +117,7 @@ export function page({ pkg, info }){
})(element);
};
}
`, page_id })
`, language: "js" })
)
)
),
@ -177,7 +176,7 @@ export function page({ pkg, info }){
textContent: "All"
})
);
`, page_id }),
`, language: "js" }),
el("div", { className: "callout" }).append(
el("h4", t`Benefits of Signal Factories`),
@ -217,7 +216,7 @@ export function page({ pkg, info }){
const counter = createEnhancedSignal(0);
el("button", { textContent: "Increment", onclick: () => counter.increment() });
el("div", S.text\`Count: \${counter}\`);
`, page_id }),
`, language: "js" }),
el("div", { className: "tip" }).append(
el("p").append(T`
@ -259,7 +258,7 @@ export function page({ pkg, info }){
// Update signal value
count.set(5); // Logs: 5
console.log(doubled.get()); // 10
`, page_id }),
`, language: "js" }),
el("p").append(T`
The independent signals API includes all core functionality (${el("code", "S()")}, ${el("code", "S.on()")},
${el("code", "S.action()")}).

View File

@ -43,7 +43,6 @@ const references= {
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(T`
As your applications grow, performance becomes increasingly important. dd<el> provides several
@ -60,7 +59,7 @@ export function page({ pkg, info }){
el("li", t`Simple debugging for performance bottlenecks`)
)
),
el(code, { src: fileURL("./components/examples/optimization/intro.js"), page_id }),
el(code, { src: fileURL("./components/examples/optimization/intro.js") }),
el(h3, t`Memoization with memo: Native vs dd<el>`),
el("p").append(T`
@ -84,7 +83,7 @@ export function page({ pkg, info }){
))
);
}
`, page_id })
`, language: "js" })
),
el("div").append(
el("h5", t`With dd<el>'s memo`),
@ -102,7 +101,7 @@ export function page({ pkg, info }){
)))
);
}
`, page_id })
`, language: "js" })
)
)
),
@ -134,7 +133,7 @@ export function page({ pkg, info }){
memo(todo.id, () =>
el(TodoItem, todo)
))))
`, page_id }),
`, language: "js" }),
el("p").append(T`
The ${el("code", "memo")} function in this context:
@ -146,7 +145,7 @@ export function page({ pkg, info }){
el("li", t`Only calls the generator function when rendering an item with a new key`)
),
el(example, { src: fileURL("./components/examples/optimization/memo.js"), page_id }),
el(example, { src: fileURL("./components/examples/optimization/memo.js") }),
el(h3, t`Creating Memoization Scopes`),
el("p").append(T`
@ -171,7 +170,7 @@ export function page({ pkg, info }){
const container = el("div").append(
...items.map(item => renderItem(item))
);
`, page_id }),
`, language: "js" }),
el("p").append(T`
The scope function accepts options to customize its behavior:
@ -188,7 +187,7 @@ export function page({ pkg, info }){
// Clear cache when signal is aborted
signal: controller.signal
});
`, page_id }),
`, language: "js" }),
el("p").append(T`
You can use custom memo scope as function in (e. g. ${el("code", "S.el(signal, renderList)")}) and as
(Abort) signal use ${el("code", "scope.signal")}.
@ -317,7 +316,7 @@ export function page({ pkg, info }){
// On subsequent renders, the cached fragment is empty!
container.append(memoizedFragment); // Nothing gets appended
`, page_id }),
`, language: "js" }),
el("p").append(T`
This happens because a DocumentFragment is emptied when it's appended to the DOM. When the fragment
@ -338,7 +337,7 @@ export function page({ pkg, info }){
S.el(itemsSignal, items => items.map(item => el("div", item)))
)
);
`, page_id })
`, language: "js" })
),
el("p").append(T`

View File

@ -43,7 +43,6 @@ const references= {
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(T`
${el("a", references.todomvc).append("TodoMVC")} is a project that helps developers compare different
@ -69,7 +68,7 @@ export function page({ pkg, info }){
challenges in a clean, maintainable way.
`),
el(example, { src: fileURL("./components/examples/reallife/todomvc.js"), variant: "big", page_id }),
el(example, { src: fileURL("./components/examples/reallife/todomvc.js"), variant: "big" }),
el(h3, t`Application Architecture Overview`),
el("p").append(T`
@ -118,7 +117,7 @@ export function page({ pkg, info }){
});
});
const todosRemainingS = S(()=> todosS.get().filter(todo => !todo.completed).length);
`, page_id }),
`, language: "js" }),
el("p").append(T`
The ${el("code", "todosSignal")} function creates a custom signal with actions for manipulating the todos:
@ -207,7 +206,7 @@ export function page({ pkg, info }){
});
return out;
}
`, page_id }),
`, language: "js" }),
el("div", { className: "note" }).append(
el("p").append(T`
@ -241,7 +240,7 @@ export function page({ pkg, info }){
memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit)))
)
)
`, page_id }),
`, language: "js" }),
el("p").append(T`
The derived signal automatically recalculates whenever either the todos list or the current filter changes,
@ -263,7 +262,7 @@ export function page({ pkg, info }){
type: "checkbox"
}, onToggleAll),
el("label", { htmlFor: "toggle-all", title: "Mark all as complete" }),
`, page_id }),
`, language: "js" }),
el("p").append(T`
The "toggle all" checkbox allows users to mark all todos as completed or active. When the checkbox
@ -300,7 +299,7 @@ export function page({ pkg, info }){
// Component content...
);
}
`, page_id }),
`, language: "js" }),
el("p").append(T`
The TodoItem component maintains its own local UI state with signals, providing immediate
@ -320,7 +319,7 @@ export function page({ pkg, info }){
el("li", {
classList: { completed: isCompleted, editing: isEditing }
})
`, page_id }),
`, language: "js" }),
el("div", { className: "tip" }).append(
el("p").append(T`
@ -341,7 +340,7 @@ export function page({ pkg, info }){
memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit)))
)
)
`, page_id }),
`, language: "js" }),
el("p").append(T`
This approach ensures that:
@ -369,7 +368,7 @@ export function page({ pkg, info }){
// …
)
))
`, page_id }),
`, language: "js" }),
el("p").append(T`
We memoize the UI section and uses derived signal for the classList. Re-rendering this part is therefore
@ -400,7 +399,7 @@ export function page({ pkg, info }){
S.action(todosS, "delete", /** @type {{ detail: Todo["id"] }} */(ev).detail));
const onEdit = on("todo:edit", ev =>
S.action(todosS, "edit", /** @type {{ detail: Partial<Todo> & { id: Todo["id"] } }} */(ev).detail));
`, page_id }),
`, language: "js" }),
el("h4", t`2. The TodoItem Component with Scopes and Local State`),
el("p").append(T`
@ -443,7 +442,7 @@ export function page({ pkg, info }){
// Component implementation...
}
`, page_id }),
`, language: "js" }),
el("div", { className: "tip" }).append(
el("p").append(T`
@ -464,7 +463,7 @@ export function page({ pkg, info }){
}).append(
// Component content...
);
`, page_id }),
`, language: "js" }),
el("p").append(T`
Benefits of using ${el("code", "classList")}:
@ -503,7 +502,7 @@ export function page({ pkg, info }){
value: title,
"data-id": id
}, onBlurEdit, onKeyDown, addFocus)
`, page_id }),
`, language: "js" }),
el("p").append(T`
This approach offers several advantages:
@ -536,7 +535,7 @@ export function page({ pkg, info }){
// Main content with toggle all and todo list
)
)
`, page_id }),
`, language: "js" }),
el("h4", t`Conditional Edit Form`),
el(code, { content: `
@ -550,7 +549,7 @@ export function page({ pkg, info }){
}, onBlurEdit, onKeyDown, addFocus)
)
)
`, page_id }),
`, language: "js" }),
el("h4", t`Conditional Clear Completed Button`),
el(code, { content: `
@ -561,7 +560,7 @@ export function page({ pkg, info }){
{ textContent: "Clear completed", className: "clear-completed" },
onClearCompleted)
)
`, page_id }),
`, language: "js" }),
el("div", { className: "note" }).append(
el("p").append(T`
@ -607,7 +606,7 @@ export function page({ pkg, info }){
if (event.key !== "Escape") return;
isEditing.set(false);
});
`, page_id }),
`, language: "js" }),
el("div", { className: "tip" }).append(
el("p").append(T`

View File

@ -14,7 +14,6 @@ const fileURL= url=> new URL(url, import.meta.url);
/** @param {import("./types.js").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("div", { className: "warning" }).append(
el("p").append(T`
@ -45,7 +44,7 @@ export function page({ pkg, info }){
than jsdom
`),
),
el(code, { src: fileURL("./components/examples/ssr/intro.js"), page_id }),
el(code, { src: fileURL("./components/examples/ssr/intro.js") }),
el(h3, t`Why Server-Side Rendering?`),
el("p").append(T`
@ -71,27 +70,27 @@ export function page({ pkg, info }){
el("li", t`Provides a promise queue system for managing async operations during rendering`),
el("li", t`Handles DOM property/attribute mapping differences between browsers and jsdom`)
),
el(code, { src: fileURL("./components/examples/ssr/start.js"), page_id }),
el(code, { src: fileURL("./components/examples/ssr/start.js") }),
el(h3, t`Basic SSR Example`),
el("p").append(T`
Heres a simple example of how to use dd<el> for server-side rendering in a Node.js script:
`),
el(code, { src: fileURL("./components/examples/ssr/basic-example.js"), page_id }),
el(code, { src: fileURL("./components/examples/ssr/basic-example.js") }),
el(h3, t`Building a Static Site Generator`),
el("p").append(T`
You can build a complete static site generator with dd<el>. In fact, this documentation site
is built using dd<el> for server-side rendering! Heres how the documentation build process works:
`),
el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js"), page_id }),
el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js") }),
el(h3, t`Working with Async Content in SSR`),
el("p").append(T`
The jsdom export includes a queue system to handle asynchronous operations during rendering.
This is crucial for components that fetch data or perform other async tasks.
`),
el(code, { src: fileURL("./components/examples/ssr/async-data.js"), page_id }),
el(code, { src: fileURL("./components/examples/ssr/async-data.js") }),
el(h3, t`Working with Dynamic Imports for SSR`),
el("p").append(T`
@ -119,7 +118,7 @@ export function page({ pkg, info }){
el("p").append(T`
Follow this pattern when creating server-side rendered pages:
`),
el(code, { src: fileURL("./components/examples/ssr/pages.js"), page_id }),
el(code, { src: fileURL("./components/examples/ssr/pages.js") }),
el(h3, t`SSR Considerations and Limitations`),
el("p").append(T`
@ -141,7 +140,7 @@ export function page({ pkg, info }){
This documentation site itself is built using dd<el>s SSR capabilities.
The build process collects all page components, renders them with jsdom, and outputs static HTML files.
`),
el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js"), page_id }),
el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js") }),
el("p").append(T`
The resulting static files can be deployed to any static hosting service,

View File

@ -15,7 +15,6 @@ const fileURL= url=> new URL(url, import.meta.url);
/** @param {import("./types.js").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("div", { className: "warning" }).append(
el("p").append(T`
@ -66,7 +65,7 @@ export function page({ pkg, info }){
src: fileURL("./components/examples/path/to/component.js"),
exportName: "NamedExport", // optional, defaults to "default",
})
`, page_id }),
`, language: "js" }),
el("p").append(T`
During the build process (${el("code", "bs/docs.js")}), the following happens:
@ -118,7 +117,7 @@ export function page({ pkg, info }){
// Final build step - trigger SSR end event
dispatchEvent("onssrend");
`, page_id }),
`, language: "js" }),
el("h4", t`File Registration`),
el(code, { content: `
// From docs/ssr.js - File registration system
@ -144,7 +143,7 @@ export function page({ pkg, info }){
head[head instanceof HTMLScriptElement ? "src" : "href"] = file_name;
document.head.append(head);
}
`, page_id }),
`, language: "js" }),
el("h4", t`Server-Side Rendering`),
el(code, { content: `
// From docs/components/ireland.html.js - Server-side component implementation
@ -225,7 +224,7 @@ export function page({ pkg, info }){
\`.trim())
);
}
`, page_id }),
`, language: "js" }),
el("h4", t`Client-Side Hydration`),
el(code, { content: `
// From docs/components/ireland.js.js - Client-side hydration
@ -249,7 +248,7 @@ export function page({ pkg, info }){
});
});
}
`, page_id }),
`, language: "js" }),
el(h3, t`Live Example`),
el("p").append(T`
@ -258,14 +257,10 @@ export function page({ pkg, info }){
rendered with the Ireland component system:
`),
el(code, {
src: fileURL("./components/examples/ireland-test/counter.js"),
page_id
}),
el(code, { src: fileURL("./components/examples/ireland-test/counter.js") }),
el(ireland, {
src: fileURL("./components/examples/ireland-test/counter.js"),
exportName: "CounterStandard",
page_id
}),
el("p").append(T`

View File

@ -40,7 +40,6 @@ const references= {
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(T`
This reference guide provides a comprehensive summary of dd<el>s key concepts, best practices,
@ -166,7 +165,7 @@ export function page({ pkg, info }){
className: S(() => countS.get() > 10 ? 'warning' : '')
})
);
`, page_id }),
`, language: "js" }),
el(h3, t`Key Concepts Reference`),

View File

@ -13,7 +13,6 @@ import { converter } from "./components/converter.html.js";
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(T`
Transitioning from HTML to dd<el> is simple with our interactive converter. This tool helps you quickly
@ -28,7 +27,7 @@ export function page({ pkg, info }){
),
// The actual converter component
el(converter, { page_id }),
el(converter),
el(h3, t`Next Steps`),
el("p").append(T`

View File

@ -14,7 +14,6 @@ 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("p").append(T`
Real-world application examples showcasing how to build complete, production-ready interfaces with dd<el>:
@ -25,14 +24,14 @@ export function page({ pkg, info }){
third-party charting library, data fetching and state management, responsive layout design, and multiple
interactive components working together.
`),
el(example, { src: fileURL("./components/examples/case-studies/data-dashboard.js"), variant: "big", page_id }),
el(example, { src: fileURL("./components/examples/case-studies/data-dashboard.js"), variant: "big" }),
el(h3, t`Interactive Form`),
el("p").append(T`
Complete form with real-time validation, conditional rendering, and responsive design. Form handling with
real-time validation, reactive UI updates, complex form state management, and clean separation of concerns.
`),
el(example, { src: fileURL("./components/examples/case-studies/interactive-form.js"), variant: "big", page_id }),
el(example, { src: fileURL("./components/examples/case-studies/interactive-form.js"), variant: "big" }),
el(h3, t`Interactive Image Gallery`),
@ -40,7 +39,7 @@ export function page({ pkg, info }){
Responsive image gallery with lightbox, keyboard navigation, and filtering. Dynamic loading of content,
lightbox functionality, animation handling, and keyboard and gesture navigation support.
`),
el(example, { src: fileURL("./components/examples/case-studies/image-gallery.js"), variant: "big", page_id }),
el(example, { src: fileURL("./components/examples/case-studies/image-gallery.js"), variant: "big" }),
el(h3, t`Task Manager`),
@ -49,7 +48,7 @@ export function page({ pkg, info }){
with signals, drag and drop functionality, local storage persistence, and responsive design for different
devices.
`),
el(example, { src: fileURL("./components/examples/case-studies/task-manager.js"), variant: "big", page_id }),
el(example, { src: fileURL("./components/examples/case-studies/task-manager.js"), variant: "big" }),
el(h3, t`TodoMVC`),

View File

@ -1,4 +1,8 @@
export { t } from "./utils/index.js";
/** @type {string} */
export let page_id;
/** @param {string} id */
export function currentPageId(id){ page_id= id; }
export const path_target= {
root: "dist/docs/",
css: "dist/docs/",

View File

@ -2,14 +2,13 @@ import { queueSignalWrite, mark } from "./helpers.js";
export { mark };
import { hasOwn, oCreate, oAssign, requestIdle } from "../helpers.js";
const Signal = oCreate(null, {
const SignalReadOnly= oCreate(null, {
get: { value(){ return read(this); } },
set: { value(...v){ return write(this, ...v); } },
toJSON: { value(){ return read(this); } },
valueOf: { value(){ return this[mark] && this[mark].value; } }
});
const SignalReadOnly= oCreate(Signal, {
set: { value(){ return; } },
const Signal = oCreate(SignalReadOnly, {
set: { value(...v){ return write(this, ...v); } },
});
/**
* Checks if a value is a signal
@ -314,9 +313,14 @@ export const signals_config= {
function removeSignalsFromElements(s, listener, ...notes){
const { current }= scope;
current.host(function(element){
if(!element[key_reactive]) element[key_reactive]= [];
const is_first= !element[key_reactive];
if(is_first) element[key_reactive]= [];
element[key_reactive].push([ [ s, listener ], ...notes ]);
if(current.prevent) return; // typically document.body, doenst need auto-remove as it should happen on page leave
if(
!is_first
// typically document.body, doenst need auto-remove as it should happen on page leave
|| current.prevent
) return;
on.disconnected(()=>
/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, ?).
You can investigate the `__dde_reactive` key of the element. */
@ -344,7 +348,7 @@ const cleanUpRegistry = new FinalizationRegistry(function(s){
*/
function create(is_readonly, value, actions){
const varS = oCreate(is_readonly ? SignalReadOnly : Signal);
const SI= toSignal(varS, value, actions, is_readonly);
const SI= toSignal(varS, value, actions);
cleanUpRegistry.register(SI, SI[mark]);
return SI;
}
@ -367,11 +371,10 @@ const protoSigal= oAssign(oCreate(), {
* @param {Object} s - Object to transform
* @param {any} value - Initial value
* @param {Object} actions - Custom actions
* @param {boolean} [readonly=false] - Whether the signal is readonly
* @returns {Object} Signal object with get() and set() methods
* @private
*/
function toSignal(s, value, actions, readonly= false){
function toSignal(s, value, actions){
const onclear= [];
if(typeOf(actions)!=="[object Object]")
actions= {};
@ -385,7 +388,6 @@ function toSignal(s, value, actions, readonly= false){
value: oAssign(oCreate(protoSigal), {
value, actions, onclear, host,
listeners: new Set(),
readonly
}),
enumerable: false,
writable: false,