1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2025-04-02 04:05: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. …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 ## Commit Guidelines
We use We use [git3moji](https://git3moji.netlify.app/) for commit messages. This helps keep the commit history clear and
[![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 --> consistent.
for commit messages. This helps keep the commit history clear and consistent.
``` ```
:emoji: Short summary of the change :emoji: Short summary of the change

View File

@ -1,18 +1,11 @@
**Alpha** **Alpha**
| [source code on GitHub](https://github.com/jaandrle/deka-dom-el) | [Docs](https://jaandrle.github.io/deka-dom-el "Official documentation and guide site")
| [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el) | [NPM](https://www.npmjs.com/package/deka-dom-el "Official NPM package page")
| [npm package](https://www.npmjs.com/package/deka-dom-el) | [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"))
<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)
***Vanilla for flavouring — a full-fledged feast for large projects*** ***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 ```javascript
// 🌟 Reactive component with clear separation of concerns // 🌟 Reactive component with clear separation of concerns
document.body.append( document.body.append(
@ -20,25 +13,23 @@ document.body.append(
); );
function EmojiCounter({ initial }) { function EmojiCounter({ initial }) {
// ✨ State - Define reactive data // ✨ - Define reactive data
const count = S(0); const count = S(0);
const emoji = S(initial); 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( return el().append(
el("p", { el("p", { textContent, className: "output" }),
className: "output",
textContent: S(() =>
`Hello World ${emoji.get().repeat(count.get())}`),
}),
// 🎮 Controls - Update state on events // 🎮 - Update state on events
el("button", { textContent: "Add Emoji" }, 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), el("select", null,
on("change", e => emoji.set(e.target.value)) on.defer(el=> el.value= initial),
on("change", e => emoji.set(e.target.value)),
).append( ).append(
el(Option, "🎉"), el(Option, "🎉"),
el(Option, "🚀"), el(Option, "🚀"),
@ -50,6 +41,13 @@ function Option({ textContent }){
return el("option", { value: textContent, 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 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 [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 - ☑️ **Lightweight** — ~10-15kB minified (original goal 10kB) with **zero**/minimal dependencies
- ✅ **Declarative & functional approach** for clean, maintainable code - ✅ **Declarative & functional approach** for clean, maintainable code
- ✅ **Signals and events** for reactive UI - ✅ **Signals and events** for reactive UI
- ✅ **Auto-releasing resources** for memory management but nice development experience
- ✅ **Memoization for performance** — optimize rendering with intelligent caching - ✅ **Memoization for performance** — optimize rendering with intelligent caching
- **Optional build-in signals** with support for custom reactive implementations (#39) - ☑️ **Optional build-in signals** with support for custom reactive implementations (#39)
- **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom) - ☑️ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom)
- ✅ **TypeScript support** - ✅ **TypeScript support**
- ☑️ **Support for debugging with browser DevTools** without extensions - **Support for debugging with browser DevTools** without extensions
- ☑️ **Enhanced Web Components** support - ☑️ **Enhanced Web Components** support
## Getting Started ## Getting Started
### Documentation ### Quick Links
- [**Documentation and Guide**](https://jaandrle.github.io/deka-dom-el) - [**Documentation and Guide**](https://jaandrle.github.io/deka-dom-el)
- [**Examples**](https://jaandrle.github.io/deka-dom-el/p15-examples.html) - [**Examples**](https://jaandrle.github.io/deka-dom-el/p15-examples.html)
- [**Changelog**](https://github.com/jaandrle/deka-dom-el/releases)
### Installation ### Installation
@ -138,9 +138,9 @@ get started, coding standards, commit guidelines, and the pull request process.
interfaces or HTML code. interfaces or HTML code.
- [pota](https://pota.quack.uy/) — small and pluggable Reactive Web Renderer. It's compiler-less, includes an html - [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. 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 - [TarekRaafat/eleva](https://github.com/TarekRaafat/eleva) — A minimalist, lightweight, pure vanilla JavaScript
frontend runtime framework. frontend runtime framework.
- [didi/mpx](https://github.com/didi/mpx) — Mpx一款具有优秀开发体验和深度性能优化的增强型跨端小程序框架 - [didi/mpx](https://github.com/didi/mpx) — Mpx一款具有优秀开发体验和深度性能优化的增强型跨端小程序框架
- [mxjp/rvx](https://github.com/mxjp/rvx) — A signal based frontend framework - [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 /* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */// editorconfig-checker-disable-line
echo("Building static documentation files…"); echo("Building static documentation files…");
echo("Preparing…"); echo("Preparing…");
import { path_target, pages as pages_registered, styles, dispatchEvent, t } from "../docs/ssr.js"; import { path_target, pages as pages_registered, styles, currentPageId, dispatchEvent, t } from "../docs/ssr.js";
import { createHTMl } from "./docs/jsdom.js"; import { createHTMl } from "./docs/jsdom.js";
import { register, queue } from "../jsdom.js"; import { register, queue } from "../jsdom.js";
const pkg= s.cat("package.json").xargs(JSON.parse); 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 { el }= await register(serverDOM.dom);
const { page }= await import(`../docs/${id}.html.js`); const { page }= await import(`../docs/${id}.html.js`);
currentPageId(id)
serverDOM.document.body.append( serverDOM.document.body.append(
el(page, { pkg, info }), el(page, { pkg, info }),
); );

View File

@ -696,13 +696,10 @@ var queueSignalWrite = /* @__PURE__ */ (() => {
})(); })();
// src/signals-lib/signals-lib.js // src/signals-lib/signals-lib.js
var Signal = oCreate(null, { var SignalReadOnly = oCreate(null, {
get: { value() { get: { value() {
return read(this); return read(this);
} }, } },
set: { value(...v) {
return write(this, ...v);
} },
toJSON: { value() { toJSON: { value() {
return read(this); return read(this);
} }, } },
@ -710,9 +707,9 @@ var Signal = oCreate(null, {
return this[mark] && this[mark].value; return this[mark] && this[mark].value;
} } } }
}); });
var SignalReadOnly = oCreate(Signal, { var Signal = oCreate(SignalReadOnly, {
set: { value() { set: { value(...v) {
return; return write(this, ...v);
} } } }
}); });
function isSignal(candidate) { function isSignal(candidate) {
@ -889,9 +886,10 @@ var signals_config = {
function removeSignalsFromElements(s, listener, ...notes) { function removeSignalsFromElements(s, listener, ...notes) {
const { current } = scope; const { current } = scope;
current.host(function(element) { 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]); element[key_reactive].push([[s, listener], ...notes]);
if (current.prevent) return; if (!is_first || current.prevent) return;
on.disconnected( on.disconnected(
() => ( () => (
/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, ?). /*! 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) { function create(is_readonly, value, actions) {
const varS = oCreate(is_readonly ? SignalReadOnly : Signal); 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]); cleanUpRegistry.register(SI, SI[mark]);
return SI; return SI;
} }
@ -918,7 +916,7 @@ var protoSigal = oAssign(oCreate(), {
this.skip = true; this.skip = true;
} }
}); });
function toSignal(s, value, actions, readonly = false) { function toSignal(s, value, actions) {
const onclear = []; const onclear = [];
if (typeOf(actions) !== "[object Object]") if (typeOf(actions) !== "[object Object]")
actions = {}; actions = {};
@ -934,8 +932,7 @@ function toSignal(s, value, actions, readonly = false) {
actions, actions,
onclear, onclear,
host, host,
listeners: /* @__PURE__ */ new Set(), listeners: /* @__PURE__ */ new Set()
readonly
}), }),
enumerable: false, enumerable: false,
writable: 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 // src/signals-lib/signals-lib.js
var Signal = oCreate(null, { var SignalReadOnly = oCreate(null, {
get: { value() { get: { value() {
return read(this); return read(this);
} }, } },
set: { value(...v) {
return write(this, ...v);
} },
toJSON: { value() { toJSON: { value() {
return read(this); return read(this);
} }, } },
@ -755,9 +752,9 @@ var DDE = (() => {
return this[mark] && this[mark].value; return this[mark] && this[mark].value;
} } } }
}); });
var SignalReadOnly = oCreate(Signal, { var Signal = oCreate(SignalReadOnly, {
set: { value() { set: { value(...v) {
return; return write(this, ...v);
} } } }
}); });
function isSignal(candidate) { function isSignal(candidate) {
@ -934,9 +931,10 @@ var DDE = (() => {
function removeSignalsFromElements(s, listener, ...notes) { function removeSignalsFromElements(s, listener, ...notes) {
const { current } = scope; const { current } = scope;
current.host(function(element) { 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]); element[key_reactive].push([[s, listener], ...notes]);
if (current.prevent) return; if (!is_first || current.prevent) return;
on.disconnected( on.disconnected(
() => ( () => (
/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, ?). /*! 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) { function create(is_readonly, value, actions) {
const varS = oCreate(is_readonly ? SignalReadOnly : Signal); 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]); cleanUpRegistry.register(SI, SI[mark]);
return SI; return SI;
} }
@ -963,7 +961,7 @@ var DDE = (() => {
this.skip = true; this.skip = true;
} }
}); });
function toSignal(s, value, actions, readonly = false) { function toSignal(s, value, actions) {
const onclear = []; const onclear = [];
if (typeOf(actions) !== "[object Object]") if (typeOf(actions) !== "[object Object]")
actions = {}; actions = {};
@ -979,8 +977,7 @@ var DDE = (() => {
actions, actions,
onclear, onclear,
host, host,
listeners: /* @__PURE__ */ new Set(), listeners: /* @__PURE__ */ new Set()
readonly
}), }),
enumerable: false, enumerable: false,
writable: 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; const host= "."+code.name;
styles.css` styles.css`
/* Code block styling */ /* Code block styling */
@ -177,6 +177,9 @@ ${host}:hover .copy-button {
} }
`; `;
import { el } from "deka-dom-el"; 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. * Prints code to the page and registers flems to make it interactive.
* @param {object} attrs * @param {object} attrs
@ -184,15 +187,17 @@ import { el } from "deka-dom-el";
* @param {string} [attrs.className] * @param {string} [attrs.className]
* @param {URL} [attrs.src] Example code file path * @param {URL} [attrs.src] Example code file path
* @param {string} [attrs.content] Example code * @param {string} [attrs.content] Example code
* @param {"js"|"ts"|"html"|"css"|"shell"} [attrs.language="js"] Language of the code * @param {Language} [attrs.language="-s"] Language of the code
* @param {string} [attrs.page_id] ID of the page, if setted it registers shiki
* */ * */
export function code({ id, src, content, language= "js", className= host.slice(1), page_id }){ export function code({ id, src, content, language= "-", className= host.slice(1) }){
if(src) content= s.cat(src); if(src){
content= s.cat(src);
if(language=== "-") language= /** @type {Language} */(src.pathname.split(".").pop());
}
content= normalizeIndentation(content); content= normalizeIndentation(content);
let dataJS; let dataJS;
if(page_id){ if(language!== "-"){
registerClientPart(page_id); registerClientPart();
dataJS= "todo"; dataJS= "todo";
} }
return el("div", { id, className, dataJS, tabIndex: 0 }).append( 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())); return el("pre").append(el("code", content.trim()));
} }
let is_registered= {}; let is_registered= {};
/** @param {string} page_id */ function registerClientPart(){
function registerClientPart(page_id){
if(is_registered[page_id]) return; if(is_registered[page_id]) return;
// Add Shiki with a more reliable loading method // 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` styles.css`
#html-to-dde-converter { #html-to-dde-converter {
@ -149,12 +149,11 @@ import { ireland } from "./ireland.html.js";
import { el } from "deka-dom-el"; import { el } from "deka-dom-el";
const fileURL= url=> new URL(url, import.meta.url); const fileURL= url=> new URL(url, import.meta.url);
export function converter({ page_id }){ export function converter(){
registerClientPart(page_id); registerClientPart(page_id);
return el(ireland, { return el(ireland, {
src: fileURL("./converter.js.js"), src: fileURL("./converter.js.js"),
exportName: "converter", 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; const host= "."+example.name;
styles.css` styles.css`
${host} { ${host} {
@ -119,9 +119,8 @@ import { relative } from "node:path";
* @param {URL} attrs.src Example code file path * @param {URL} attrs.src Example code file path
* @param {"js"|"ts"|"html"|"css"} [attrs.language="js"] Language of the code * @param {"js"|"ts"|"html"|"css"} [attrs.language="js"] Language of the code
* @param {"normal"|"big"} [attrs.variant="normal"] Size of the example * @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); registerClientPart(page_id);
const content= s.cat(src).toString() const content= s.cat(src).toString()
.replaceAll(/ from "deka-dom-el(\/signals)?";/g, ' from "./esm-with-signals.js";'); .replaceAll(/ from "deka-dom-el(\/signals)?";/g, ' from "./esm-with-signals.js";');

View File

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

View File

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

View File

@ -4,11 +4,11 @@ import { el } from "deka-dom-el";
const button = el("button", { const button = el("button", {
textContent: "Click me", textContent: "Click me",
className: "primary", className: "primary",
disabled: true disabled: true,
}); });
// Shorter and more expressive // Shorter and more expressive
// than the native approach // than the native approach
// Add to DOM // 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( document.body.append(
el("div").append( el("div").append(
el("h1", "Title"), 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` styles.css`
#library-url-form { #library-url-form {
@ -74,7 +74,7 @@ styles.css`
import { el } from "deka-dom-el"; import { el } from "deka-dom-el";
import { ireland } from "./ireland.html.js"; import { ireland } from "./ireland.html.js";
export function getLibraryUrl({ page_id }){ export function getLibraryUrl(){
return el(ireland, { return el(ireland, {
src: new URL("./getLibraryUrl.js.js", import.meta.url), src: new URL("./getLibraryUrl.js.js", import.meta.url),
exportName: "getLibraryUrl", exportName: "getLibraryUrl",

View File

@ -43,7 +43,6 @@ const componentsRegistry = new Map();
* @param {object} attrs * @param {object} attrs
* @param {URL} attrs.src - Path to the file containing the component * @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.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 * @param {object} [attrs.props={}] - Props to pass to the component
*/ */
export function ireland({ src, exportName = "default", props = {} }) { 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(...){...}"), " — corresponds to custom elemnets callbacks ", el("code", "<live-cycle>Callback(...){...}"),
". To connect to custom element see following page, else it is simulated by MutationObserver." ". To connect to custom element see following page, else it is simulated by MutationObserver."
), ),
el("li").append(
el("code", "on.defer(<identity>=> <identity>)(<identity>)"),
" — calls callback later",
),
el("li").append( el("li").append(
el("code", "dispatchEvent(<event>[, <options>])(element)"), el("code", "dispatchEvent(<event>[, <options>])(element)"),
" — just ", el("code", "<element>.dispatchEvent(new Event(<event>[, <options>]))") " — 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= { export const info= {
href: "./", href: "./",
title: t`Introduction`, title: t`Introduction`,
fullTitle: t`Vanilla for flavouring — a full-fledged feast for large projects`, 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`, description: t`Reactive DOM library for creating dynamic UIs with a declarative syntax`,
}; };
import { el } from "deka-dom-el"; import { el } from "deka-dom-el";
@ -31,10 +31,9 @@ const references= {
}; };
/** @param {import("./types.d.ts").PageAttrs} attrs */ /** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){ export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("p").append(T` 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 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. 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`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`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`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("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(h3, { textContent: t`The 3PS Pattern: Simplified architecture pattern`, id: "h-3ps" }),
el("p").append(T` el("p").append(T`
@ -60,11 +59,11 @@ export function page({ pkg, info }){
el("div", { className: "tabs" }).append( el("div", { className: "tabs" }).append(
el("div", { className: "tab" }).append( el("div", { className: "tab" }).append(
el("h5", t`Traditional DOM Manipulation`), 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("div", { className: "tab" }).append(
el("h5", t`dd<el>'s 3PS Pattern`), 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(h3, t`Getting Started`),
el("p").append(T` el("p").append(T`
There are multiple ways to include dd<el> in your project. You can use npm for a full development setup, 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. or directly include it from a CDN for quick prototyping.
`), `),
el("h4", "npm installation"), 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` el("p").append(T`
see ${el("a", { textContent: "package page", ...references.npm, target: "_blank" })}. see ${el("a", { textContent: "package page", ...references.npm, target: "_blank" })}.
`), `),
@ -118,7 +117,7 @@ export function page({ pkg, info }){
el("p").append(T` el("p").append(T`
Use the interactive selector below to choose your preferred format: Use the interactive selector below to choose your preferred format:
`), `),
el(getLibraryUrl, { page_id }), el(getLibraryUrl),
el("div", { className: "note" }).append( el("div", { className: "note" }).append(
el("p").append(T` el("p").append(T`
Based on your selection, you can use dd<el> in your project like this: 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) // 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"; 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> // <script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/iife-with-signals.min.js"></script>
const { el, on } = DDE; const { el, on } = DDE;
`, language: "js", page_id }), `, language: "js" }),
), ),
el(h3, t`How to Use This Documentation`), el(h3, t`How to Use This Documentation`),
@ -154,7 +153,7 @@ export function page({ pkg, info }){
Integrating third-party functionalities`), Integrating third-party functionalities`),
el("li").append(T`${el("a", { href: "p09-optimization.html" }) el("li").append(T`${el("a", { href: "p09-optimization.html" })
.append(el("strong", "Performance Optimization"))} Techniques for optimizing your applications`), .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`), application implementation`),
el("li").append(T`${el("a", { href: "p11-ssr.html" }).append(el("strong", "SSR"))} — Server-side el("li").append(T`${el("a", { href: "p11-ssr.html" }).append(el("strong", "SSR"))} — Server-side
rendering with dd<el>`), rendering with dd<el>`),

View File

@ -47,12 +47,11 @@ const references= {
}; };
/** @param {import("./types.d.ts").PageAttrs} attrs */ /** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){ export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("p").append(T` el("p").append(T`
Building user interfaces in JavaScript often involves creating and manipulating DOM elements. 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, dd<el> provides a simple yet powerful approach to element creation that is declarative, chainable,
and maintains a clean syntax close to HTML structure. and maintains a clean syntax close to HTML structure.
`), `),
el("div", { className: "callout" }).append( el("div", { className: "callout" }).append(
el("h4", t`dd<el> Elements: Key Benefits`), 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(h3, t`Creating Elements: Native vs dd<el>`),
el("p").append(T` el("p").append(T`
@ -77,11 +76,11 @@ export function page({ pkg, info }){
el("div", { className: "comparison" }).append( el("div", { className: "comparison" }).append(
el("div").append( el("div").append(
el("h5", t`Native DOM API`), 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("div").append(
el("h5", t`dd<el> Approach`), 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")} The ${el("code", "el")} function provides a simple wrapper around ${el("code", "document.createElement")}
with enhanced property assignment. 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(h3, t`Advanced Property Assignment`),
el("p").append(T` 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("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("div", { className: "note" }).append(
el("p").append(T` el("p").append(T`
@ -142,11 +141,11 @@ export function page({ pkg, info }){
el("div", { className: "comparison" }).append( el("div", { className: "comparison" }).append(
el("div", { className: "bad-practice" }).append( el("div", { className: "bad-practice" }).append(
el("h5", t`❌ Native DOM API`), 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("div", { className: "good-practice" }).append(
el("h5", t`✅ dd<el> Approach`), 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. 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. 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(h3, t`Using Components to Build UI Fragments`),
el("p").append(T` el("p").append(T`
The ${el("code", "el")} function is overloaded to support both tag names and function components. 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: 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` el("p").append(T`
Component functions receive the properties object as their first argument, just like regular elements. 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. 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", function which corresponds to the native ${el("a", references.mdn_ns).append(el("code",
"document.createElementNS"))}: "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` 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. using the same consistent interface.
`), `),

View File

@ -39,7 +39,6 @@ const references= {
}; };
/** @param {import("./types.d.ts").PageAttrs} attrs */ /** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){ export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("p").append(T` el("p").append(T`
Events are at the core of interactive web applications. dd<el> provides a clean, declarative approach to 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(h3, t`Events and Listeners: Two Approaches`),
el("p").append(T` el("p").append(T`
@ -70,11 +69,11 @@ export function page({ pkg, info }){
el("div", { className: "tabs" }).append( el("div", { className: "tabs" }).append(
el("div", { className: "tab" }).append( el("div", { className: "tab" }).append(
el("h5", t`Native DOM API`), 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("div", { className: "tab" }).append(
el("h5", t`dd<el> Approach`), 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 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. 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(h3, t`Removing Event Listeners`),
el("div", { className: "note" }).append( 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("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` el("p").append(T`
This is the same for signals (see next section) and works well with scopes and library extendability ( 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")}). 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: "tabs" }).append(
el("div", { className: "tab", dataTab: "html-attr" }).append( el("div", { className: "tab", dataTab: "html-attr" }).append(
el("h4", t`HTML Attribute Style`), 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` 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 ${el("code", `<button onclick="console.log(event)">click me</button>`)}. This can be particularly
useful for SSR scenarios. useful for SSR scenarios.
`) `)
), ),
el("div", { className: "tab", dataTab: "property" }).append( el("div", { className: "tab", dataTab: "property" }).append(
el("h4", t`Property Assignment`), 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("p", t`Assigns the event handler directly to the elements property.`)
), ),
el("div", { className: "tab", dataTab: "addon" }).append( el("div", { className: "tab", dataTab: "addon" }).append(
el("h4", t`Addon Approach`), 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", t`Uses the addon pattern (so adds the event listener to the element), see above.`)
) )
), ),
el("p").append(T` 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 })}. ${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 You can use Addons as 3rd argument of the ${el("code", "el")} function, making it possible to
extend your templates with additional functionality: 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` el("p").append(T`
As the example shows, you can provide types in JSDoc+TypeScript using the global type 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("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("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("div", { className: "note" }).append(
el("p").append(T` 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 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. 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(example, { src: fileURL("./components/examples/events/compareDispatch.js") }),
el(code, { src: fileURL("./components/examples/events/dispatch.js"), page_id }), el(code, { src: fileURL("./components/examples/events/dispatch.js") }),
el(h3, t`Best Practices`), el(h3, t`Best Practices`),
el("ol").append( 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 */ /** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){ export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("p").append(T` el("p").append(T`
Signals provide a simple yet powerful way to create reactive applications with dd<el>. They handle the 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("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(h3, t`The 3-Part Structure of Signals`),
el("p").append(T` el("p").append(T`
@ -68,21 +67,21 @@ export function page({ pkg, info }){
el("div", { className: "signal-diagram" }).append( el("div", { className: "signal-diagram" }).append(
el("div", { className: "signal-part" }).append( el("div", { className: "signal-part" }).append(
el("h4", t`PART 1: Create Signal`), 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("p", t`Define a reactive value that can be observed and changed`)
), ),
el("div", { className: "signal-part" }).append( el("div", { className: "signal-part" }).append(
el("h4", t`PART 2: React to Changes`), 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("p", t`Subscribe to signal changes with callbacks or effects`)
), ),
el("div", { className: "signal-part" }).append( el("div", { className: "signal-part" }).append(
el("h4", t`PART 3: Update Signal`), 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("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("div", { className: "note" }).append(
el("p").append(T` 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. Computed values (also called derived signals) automatically update when their dependencies change.
Create them by passing ${el("strong", "a function")} to ${el("code", "S()")}: 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` el("p").append(T`
Derived signals are read-only - you cant call ${el("code", ".set()")} on them. Their value is always 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. 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(h3, t`Signal Actions: For Complex State`),
el("p").append(T` el("p").append(T`
@ -151,7 +150,7 @@ export function page({ pkg, info }){
}); });
// Use the action // Use the action
S.action(todos, "add", "New todo"); S.action(todos, "add", "New todo");
`, page_id }) `, language: "js" })
), ),
el("div", { className: "bad-practice" }).append( el("div", { className: "bad-practice" }).append(
el("h5", t`❌ Without Actions`), el("h5", t`❌ Without Actions`),
@ -161,7 +160,7 @@ export function page({ pkg, info }){
const items = todos.get(); const items = todos.get();
items.push("New todo"); items.push("New todo");
// This WONT trigger updates! // This WONT trigger updates!
`, page_id })) `, language: "js" }))
), ),
), ),
el("p").append(T` 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 ${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")}). 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` el("p").append(T`
Actions provide these benefits: Actions provide these benefits:
@ -186,7 +185,7 @@ export function page({ pkg, info }){
el("p").append(T` el("p").append(T`
Heres a more complete example of a todo list using signal actions: 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("div", { className: "tip" }).append(
el("p").append(T` el("p").append(T`
@ -223,7 +222,7 @@ export function page({ pkg, info }){
// Later: // Later:
color.set("red"); // UI updates automatically color.set("red"); // UI updates automatically
`, page_id }), `, language: "js" }),
), ),
el("div", { className: "tab", dataTab: "elements" }).append( el("div", { className: "tab", dataTab: "elements" }).append(
el("h4", t`Reactive Elements`), el("h4", t`Reactive Elements`),
@ -241,7 +240,7 @@ export function page({ pkg, info }){
// Later: // Later:
S.action(items, "push", "Dragonfruit"); // List updates automatically 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 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("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("p").append(T`
${el("code", "S.el()")} is especially powerful for conditional rendering and lists: ${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(h3, t`Best Practices for Signals`),
el("p").append(T` el("p").append(T`
@ -289,7 +288,7 @@ export function page({ pkg, info }){
el("h4", t`We can process form events without signals`), 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 el("p", t`This can be used when the form data doesnt need to be reactive and we just waiting for
results.`), results.`),
el(code, { page_id, content: ` el(code, { content: `
const onFormSubmit = on("submit", e => { const onFormSubmit = on("submit", e => {
e.preventDefault(); e.preventDefault();
const formData = new FormData(e.currentTarget); const formData = new FormData(e.currentTarget);
@ -301,13 +300,13 @@ export function page({ pkg, info }){
return el("form", null, onFormSubmit).append( return el("form", null, onFormSubmit).append(
// … // …
); );
` }) `, language: "js" })
), ),
el("div", { className: "tab", dataTab: "variables" }).append( el("div", { className: "tab", dataTab: "variables" }).append(
el("h4", t`We can use variables without signals`), 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("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; let canSubmit = false;
const onFormSubmit = on("submit", e => { const onFormSubmit = on("submit", e => {
@ -318,14 +317,14 @@ export function page({ pkg, info }){
const onAllowSubmit = on("click", e => { const onAllowSubmit = on("click", e => {
canSubmit = true; canSubmit = true;
}); });
`}), `, language: "js" }),
), ),
el("div", { className: "tab", dataTab: "state" }).append( el("div", { className: "tab", dataTab: "state" }).append(
el("h4", t`Using signals`), 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 el("p", t`We use this when we need to reflect changes for example in the UI (e.g. enable/disable
buttons).`), buttons).`),
el(code, { page_id, content: ` el(code, { content: `
const canSubmit = S(false); const canSubmit = S(false);
const onFormSubmit = on("submit", e => { const onFormSubmit = on("submit", e => {
@ -341,7 +340,7 @@ export function page({ pkg, info }){
el("button", { textContent: "Allow Submit", type: "button" }, onAllowSubmit), el("button", { textContent: "Allow Submit", type: "button" }, onAllowSubmit),
el("button", { disabled: S(()=> !canSubmit), textContent: "Submit" }) el("button", { disabled: S(()=> !canSubmit), textContent: "Submit" })
); );
`}), `, language: "js" }),
), ),
), ),

View File

@ -27,14 +27,13 @@ const references= {
}; };
/** @param {import("./types.d.ts").PageAttrs} attrs */ /** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){ export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("p").append(T` el("p").append(T`
For state-less components we can use functions as UI components (see Elements page). But in real life, 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 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 })}. 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("p").append(T`The library therefore uses ${el("em", t`scopes`)} to provide these functionalities.`),
el(h3, t`Understanding Host Elements and Scopes`), 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("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("div", { className: "tip" }).append(
el("p").append(T` 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. 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(h3, t`Class-Based Components`),
el("p").append(T` 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 this, we implement function ${el("code", "elClass")} and use it to demonstrate implementation details
for better understanding of the scope logic. 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(h3, t`Automatic Cleanup with Scopes`),
el("p").append(T` el("p").append(T`
@ -123,7 +122,7 @@ export function page({ pkg, info }){
- Custom cleanup code (dd<el> and user) - 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("div", { className: "note" }).append(
el("p").append(T` el("p").append(T`
@ -149,17 +148,17 @@ export function page({ pkg, info }){
el("div", { className: "tab", dataTab: "declarative" }).append( el("div", { className: "tab", dataTab: "declarative" }).append(
el("h4", t`✅ Declarative Approach`), el("h4", t`✅ Declarative Approach`),
el("p", t`Define what your UI should look like based on state:`), 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("div", { className: "tab", dataTab: "imperative" }).append(
el("h4", t`⚠️ Imperative Approach`), el("h4", t`⚠️ Imperative Approach`),
el("p", t`Manually update the DOM in response to events:`), 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("div", { className: "tab", dataTab: "mixed" }).append(
el("h4", t`❌ Mixed Approach`), el("h4", t`❌ Mixed Approach`),
el("p", t`This approach should be avoided:`), 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 */ /** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){ export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("p").append(T` el("p").append(T`
dd<el> pairs powerfully with ${el("a", references.mdn_web_components).append(el("strong", t`Web 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("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(h3, t`Getting Started: Web Components Basics`),
el("p").append(T` el("p").append(T`
@ -95,7 +94,7 @@ export function page({ pkg, info }){
el("p").append(T` el("p").append(T`
Lets start with a basic Custom Element example without dd<el> to establish the foundation: 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("div", { className: "note" }).append(
el("p").append(T` 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("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("div", { className: "tip" }).append(
el("p").append(T` el("p").append(T`
@ -156,7 +155,7 @@ export function page({ pkg, info }){
el("dd", t`The rendered DOM tree`) 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("div", { className: "note" }).append(
el("p").append(T` 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 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! 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("div", { className: "callout" }).append(
el("h4", t`How S.observedAttributes Works`), el("h4", t`How S.observedAttributes Works`),
@ -221,7 +220,7 @@ export function page({ pkg, info }){
<p>Content</p> <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` el("p").append(T`
For more information on Shadow DOM, see 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( 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("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("div", { className: "function-table" }).append(
el("h4", t`simulateSlots`), el("h4", t`simulateSlots`),
el("dl").append( 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 */ /** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){ export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("p").append(T` el("p").append(T`
Debugging is an essential part of application development. This guide provides techniques Debugging is an essential part of application development. This guide provides techniques
@ -36,7 +35,7 @@ export function page({ pkg, info }){
el(code, { content: ` el(code, { content: `
const signal = S(0); const signal = S(0);
console.log('Current value:', signal.valueOf()); console.log('Current value:', signal.valueOf());
`, page_id }), `, language: "js" }),
el("div", { className: "warning" }).append( el("div", { className: "warning" }).append(
el("p").append(T` el("p").append(T`
${el("code", "signal.get")} is OK, but in some situations may lead to unexpected results: ${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 ↓ // but typically this is fine ↓
return signal.get() + 1; return signal.get() + 1;
}); });
` }) `, language: "js" })
), ),
el("p").append(T` el("p").append(T`
You can also monitor signal changes by adding a listener: You can also monitor signal changes by adding a listener:
@ -57,7 +56,7 @@ export function page({ pkg, info }){
el(code, { content: ` el(code, { content: `
// Log every time the signal changes // Log every time the signal changes
S.on(signal, value => console.log('Signal changed:', value)); S.on(signal, value => console.log('Signal changed:', value));
`, page_id }), `, language: "js" }),
el("h4", t`Debugging derived signals`), el("h4", t`Debugging derived signals`),
el("p").append(T` 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`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("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("h4", t`Examining signal via DevTools`),
el("p").append(T` el("p").append(T`
@ -77,11 +76,11 @@ export function page({ pkg, info }){
signal objects. It contains the following information: signal objects. It contains the following information:
`), `),
el("ul").append( el("ul").append(
// TODO: value?
el("li", t`listeners: A Set of functions called when the signal value changes`), 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`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`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`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` el("p").append(T`
to determine the current value of the signal, call ${el("code", "signal.valueOf()")}. Dont hesitate to 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=> …)")}). "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("h4", t`Memory leaks with signal listeners`),
el("p").append(T` 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`Make sure derived signals dont perform expensive calculations unnecessarily`),
el("li", t`Keep signal computations focused and minimal`) 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(h3, t`Browser DevTools tips for components and reactivity`),
el("p").append(T` 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 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): 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` el("p").append(T`
This is particularly useful when debugging why a reactive section isnt updating as expected. 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 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 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. 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("p", { className: "note" }).append(T`
${el("code", "<element>.__dde_reactive")} - An array property on DOM elements that tracks signal-to-element ${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 */ /** @param {import("./types.js").PageAttrs} attrs */
export function page({ pkg, info }){ export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("p").append(T` el("p").append(T`
dd<el> is designed with extensibility in mind. This page covers how to separate 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 // Using an addon
el("div", { id: "example" }, myAddon({ option: "value" })); el("div", { id: "example" }, myAddon({ option: "value" }));
`, page_id }), `, language: "js" }),
el(h3, t`Resource Cleanup with Abort Signals`), el(h3, t`Resource Cleanup with Abort Signals`),
el("p").append(T` el("p").append(T`
@ -83,7 +82,7 @@ export function page({ pkg, info }){
const { signal }= scope; const { signal }= scope;
return el("div", null, externalLibraryAddon({ option: "value" }, signal)); return el("div", null, externalLibraryAddon({ option: "value" }, signal));
} }
`, page_id }), `, language: "js" }),
el(h3, t`Building Library-Independent Extensions`), el(h3, t`Building Library-Independent Extensions`),
el("p").append(T` el("p").append(T`
@ -104,7 +103,7 @@ export function page({ pkg, info }){
}); });
}; };
} }
`, page_id }) `, language: "js" })
), ),
el("div", { className: "tab" }).append( el("div", { className: "tab" }).append(
el("h5", t`⚠️ Library-Dependent`), el("h5", t`⚠️ Library-Dependent`),
@ -118,7 +117,7 @@ export function page({ pkg, info }){
})(element); })(element);
}; };
} }
`, page_id }) `, language: "js" })
) )
) )
), ),
@ -177,7 +176,7 @@ export function page({ pkg, info }){
textContent: "All" textContent: "All"
}) })
); );
`, page_id }), `, language: "js" }),
el("div", { className: "callout" }).append( el("div", { className: "callout" }).append(
el("h4", t`Benefits of Signal Factories`), el("h4", t`Benefits of Signal Factories`),
@ -217,7 +216,7 @@ export function page({ pkg, info }){
const counter = createEnhancedSignal(0); const counter = createEnhancedSignal(0);
el("button", { textContent: "Increment", onclick: () => counter.increment() }); el("button", { textContent: "Increment", onclick: () => counter.increment() });
el("div", S.text\`Count: \${counter}\`); el("div", S.text\`Count: \${counter}\`);
`, page_id }), `, language: "js" }),
el("div", { className: "tip" }).append( el("div", { className: "tip" }).append(
el("p").append(T` el("p").append(T`
@ -259,7 +258,7 @@ export function page({ pkg, info }){
// Update signal value // Update signal value
count.set(5); // Logs: 5 count.set(5); // Logs: 5
console.log(doubled.get()); // 10 console.log(doubled.get()); // 10
`, page_id }), `, language: "js" }),
el("p").append(T` el("p").append(T`
The independent signals API includes all core functionality (${el("code", "S()")}, ${el("code", "S.on()")}, The independent signals API includes all core functionality (${el("code", "S()")}, ${el("code", "S.on()")},
${el("code", "S.action()")}). ${el("code", "S.action()")}).

View File

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

View File

@ -43,7 +43,6 @@ const references= {
/** @param {import("./types.d.ts").PageAttrs} attrs */ /** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){ export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("p").append(T` el("p").append(T`
${el("a", references.todomvc).append("TodoMVC")} is a project that helps developers compare different ${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. 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(h3, t`Application Architecture Overview`),
el("p").append(T` el("p").append(T`
@ -118,7 +117,7 @@ export function page({ pkg, info }){
}); });
}); });
const todosRemainingS = S(()=> todosS.get().filter(todo => !todo.completed).length); const todosRemainingS = S(()=> todosS.get().filter(todo => !todo.completed).length);
`, page_id }), `, language: "js" }),
el("p").append(T` el("p").append(T`
The ${el("code", "todosSignal")} function creates a custom signal with actions for manipulating the todos: 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; return out;
} }
`, page_id }), `, language: "js" }),
el("div", { className: "note" }).append( el("div", { className: "note" }).append(
el("p").append(T` el("p").append(T`
@ -241,7 +240,7 @@ export function page({ pkg, info }){
memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit)))
) )
) )
`, page_id }), `, language: "js" }),
el("p").append(T` el("p").append(T`
The derived signal automatically recalculates whenever either the todos list or the current filter changes, 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" type: "checkbox"
}, onToggleAll), }, onToggleAll),
el("label", { htmlFor: "toggle-all", title: "Mark all as complete" }), el("label", { htmlFor: "toggle-all", title: "Mark all as complete" }),
`, page_id }), `, language: "js" }),
el("p").append(T` el("p").append(T`
The "toggle all" checkbox allows users to mark all todos as completed or active. When the checkbox 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... // Component content...
); );
} }
`, page_id }), `, language: "js" }),
el("p").append(T` el("p").append(T`
The TodoItem component maintains its own local UI state with signals, providing immediate The TodoItem component maintains its own local UI state with signals, providing immediate
@ -320,7 +319,7 @@ export function page({ pkg, info }){
el("li", { el("li", {
classList: { completed: isCompleted, editing: isEditing } classList: { completed: isCompleted, editing: isEditing }
}) })
`, page_id }), `, language: "js" }),
el("div", { className: "tip" }).append( el("div", { className: "tip" }).append(
el("p").append(T` el("p").append(T`
@ -341,7 +340,7 @@ export function page({ pkg, info }){
memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit)))
) )
) )
`, page_id }), `, language: "js" }),
el("p").append(T` el("p").append(T`
This approach ensures that: This approach ensures that:
@ -369,7 +368,7 @@ export function page({ pkg, info }){
// … // …
) )
)) ))
`, page_id }), `, language: "js" }),
el("p").append(T` el("p").append(T`
We memoize the UI section and uses derived signal for the classList. Re-rendering this part is therefore 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)); S.action(todosS, "delete", /** @type {{ detail: Todo["id"] }} */(ev).detail));
const onEdit = on("todo:edit", ev => const onEdit = on("todo:edit", ev =>
S.action(todosS, "edit", /** @type {{ detail: Partial<Todo> & { id: Todo["id"] } }} */(ev).detail)); 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("h4", t`2. The TodoItem Component with Scopes and Local State`),
el("p").append(T` el("p").append(T`
@ -443,7 +442,7 @@ export function page({ pkg, info }){
// Component implementation... // Component implementation...
} }
`, page_id }), `, language: "js" }),
el("div", { className: "tip" }).append( el("div", { className: "tip" }).append(
el("p").append(T` el("p").append(T`
@ -464,7 +463,7 @@ export function page({ pkg, info }){
}).append( }).append(
// Component content... // Component content...
); );
`, page_id }), `, language: "js" }),
el("p").append(T` el("p").append(T`
Benefits of using ${el("code", "classList")}: Benefits of using ${el("code", "classList")}:
@ -503,7 +502,7 @@ export function page({ pkg, info }){
value: title, value: title,
"data-id": id "data-id": id
}, onBlurEdit, onKeyDown, addFocus) }, onBlurEdit, onKeyDown, addFocus)
`, page_id }), `, language: "js" }),
el("p").append(T` el("p").append(T`
This approach offers several advantages: This approach offers several advantages:
@ -536,7 +535,7 @@ export function page({ pkg, info }){
// Main content with toggle all and todo list // Main content with toggle all and todo list
) )
) )
`, page_id }), `, language: "js" }),
el("h4", t`Conditional Edit Form`), el("h4", t`Conditional Edit Form`),
el(code, { content: ` el(code, { content: `
@ -550,7 +549,7 @@ export function page({ pkg, info }){
}, onBlurEdit, onKeyDown, addFocus) }, onBlurEdit, onKeyDown, addFocus)
) )
) )
`, page_id }), `, language: "js" }),
el("h4", t`Conditional Clear Completed Button`), el("h4", t`Conditional Clear Completed Button`),
el(code, { content: ` el(code, { content: `
@ -561,7 +560,7 @@ export function page({ pkg, info }){
{ textContent: "Clear completed", className: "clear-completed" }, { textContent: "Clear completed", className: "clear-completed" },
onClearCompleted) onClearCompleted)
) )
`, page_id }), `, language: "js" }),
el("div", { className: "note" }).append( el("div", { className: "note" }).append(
el("p").append(T` el("p").append(T`
@ -607,7 +606,7 @@ export function page({ pkg, info }){
if (event.key !== "Escape") return; if (event.key !== "Escape") return;
isEditing.set(false); isEditing.set(false);
}); });
`, page_id }), `, language: "js" }),
el("div", { className: "tip" }).append( el("div", { className: "tip" }).append(
el("p").append(T` 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 */ /** @param {import("./types.js").PageAttrs} attrs */
export function page({ pkg, info }){ export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("div", { className: "warning" }).append( el("div", { className: "warning" }).append(
el("p").append(T` el("p").append(T`
@ -45,7 +44,7 @@ export function page({ pkg, info }){
than jsdom 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(h3, t`Why Server-Side Rendering?`),
el("p").append(T` 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`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("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(h3, t`Basic SSR Example`),
el("p").append(T` el("p").append(T`
Heres a simple example of how to use dd<el> for server-side rendering in a Node.js script: 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(h3, t`Building a Static Site Generator`),
el("p").append(T` el("p").append(T`
You can build a complete static site generator with dd<el>. In fact, this documentation site 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: 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(h3, t`Working with Async Content in SSR`),
el("p").append(T` el("p").append(T`
The jsdom export includes a queue system to handle asynchronous operations during rendering. 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. 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(h3, t`Working with Dynamic Imports for SSR`),
el("p").append(T` el("p").append(T`
@ -119,7 +118,7 @@ export function page({ pkg, info }){
el("p").append(T` el("p").append(T`
Follow this pattern when creating server-side rendered pages: 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(h3, t`SSR Considerations and Limitations`),
el("p").append(T` 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. 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. 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` el("p").append(T`
The resulting static files can be deployed to any static hosting service, 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 */ /** @param {import("./types.js").PageAttrs} attrs */
export function page({ pkg, info }){ export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("div", { className: "warning" }).append( el("div", { className: "warning" }).append(
el("p").append(T` el("p").append(T`
@ -66,7 +65,7 @@ export function page({ pkg, info }){
src: fileURL("./components/examples/path/to/component.js"), src: fileURL("./components/examples/path/to/component.js"),
exportName: "NamedExport", // optional, defaults to "default", exportName: "NamedExport", // optional, defaults to "default",
}) })
`, page_id }), `, language: "js" }),
el("p").append(T` el("p").append(T`
During the build process (${el("code", "bs/docs.js")}), the following happens: 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 // Final build step - trigger SSR end event
dispatchEvent("onssrend"); dispatchEvent("onssrend");
`, page_id }), `, language: "js" }),
el("h4", t`File Registration`), el("h4", t`File Registration`),
el(code, { content: ` el(code, { content: `
// From docs/ssr.js - File registration system // From docs/ssr.js - File registration system
@ -144,7 +143,7 @@ export function page({ pkg, info }){
head[head instanceof HTMLScriptElement ? "src" : "href"] = file_name; head[head instanceof HTMLScriptElement ? "src" : "href"] = file_name;
document.head.append(head); document.head.append(head);
} }
`, page_id }), `, language: "js" }),
el("h4", t`Server-Side Rendering`), el("h4", t`Server-Side Rendering`),
el(code, { content: ` el(code, { content: `
// From docs/components/ireland.html.js - Server-side component implementation // From docs/components/ireland.html.js - Server-side component implementation
@ -225,7 +224,7 @@ export function page({ pkg, info }){
\`.trim()) \`.trim())
); );
} }
`, page_id }), `, language: "js" }),
el("h4", t`Client-Side Hydration`), el("h4", t`Client-Side Hydration`),
el(code, { content: ` el(code, { content: `
// From docs/components/ireland.js.js - Client-side hydration // 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(h3, t`Live Example`),
el("p").append(T` el("p").append(T`
@ -258,14 +257,10 @@ export function page({ pkg, info }){
rendered with the Ireland component system: rendered with the Ireland component system:
`), `),
el(code, { el(code, { src: fileURL("./components/examples/ireland-test/counter.js") }),
src: fileURL("./components/examples/ireland-test/counter.js"),
page_id
}),
el(ireland, { el(ireland, {
src: fileURL("./components/examples/ireland-test/counter.js"), src: fileURL("./components/examples/ireland-test/counter.js"),
exportName: "CounterStandard", exportName: "CounterStandard",
page_id
}), }),
el("p").append(T` el("p").append(T`

View File

@ -40,7 +40,6 @@ const references= {
/** @param {import("./types.d.ts").PageAttrs} attrs */ /** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){ export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("p").append(T` el("p").append(T`
This reference guide provides a comprehensive summary of dd<el>s key concepts, best practices, 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' : '') className: S(() => countS.get() > 10 ? 'warning' : '')
}) })
); );
`, page_id }), `, language: "js" }),
el(h3, t`Key Concepts Reference`), 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 */ /** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){ export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("p").append(T` el("p").append(T`
Transitioning from HTML to dd<el> is simple with our interactive converter. This tool helps you quickly 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 // The actual converter component
el(converter, { page_id }), el(converter),
el(h3, t`Next Steps`), el(h3, t`Next Steps`),
el("p").append(T` 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 */ /** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){ export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("p").append(T` el("p").append(T`
Real-world application examples showcasing how to build complete, production-ready interfaces with dd<el>: 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 third-party charting library, data fetching and state management, responsive layout design, and multiple
interactive components working together. 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(h3, t`Interactive Form`),
el("p").append(T` el("p").append(T`
Complete form with real-time validation, conditional rendering, and responsive design. Form handling with 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. 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`), 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, Responsive image gallery with lightbox, keyboard navigation, and filtering. Dynamic loading of content,
lightbox functionality, animation handling, and keyboard and gesture navigation support. 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`), 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 with signals, drag and drop functionality, local storage persistence, and responsive design for different
devices. 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`), el(h3, t`TodoMVC`),

View File

@ -1,4 +1,8 @@
export { t } from "./utils/index.js"; 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= { export const path_target= {
root: "dist/docs/", root: "dist/docs/",
css: "dist/docs/", css: "dist/docs/",

View File

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