mirror of
https://github.com/jaandrle/deka-dom-el
synced 2025-03-31 11:35:52 +02:00
Compare commits
5 Commits
a459c0d786
...
a1d2c69049
Author | SHA1 | Date | |
---|---|---|---|
a1d2c69049 | |||
7472cea880 | |||
7664932041 | |||
ad255e3e19 | |||
74ed180919 |
@ -75,11 +75,14 @@ where everyone feels comfortable participating.
|
||||
|
||||
…see [BS folder](./bs/README.md) for more info.
|
||||
|
||||
## Categorizing [](https://robinpokorny.github.io/git3moji/) <!-- editorconfig-checker-disable-line -->
|
||||
We use [git3moji](https://git3moji.netlify.app/) for commit messages, issue titles, pull request titles and in other
|
||||
areas. To make categorizing quick and consistent.
|
||||
|
||||
## Commit Guidelines
|
||||
|
||||
We use
|
||||
[](https://robinpokorny.github.io/git3moji/) <!-- editorconfig-checker-disable-line -->
|
||||
for commit messages. This helps keep the commit history clear and consistent.
|
||||
We use [git3moji](https://git3moji.netlify.app/) for commit messages. This helps keep the commit history clear and
|
||||
consistent.
|
||||
|
||||
```
|
||||
:emoji: Short summary of the change
|
||||
|
56
README.md
56
README.md
@ -1,18 +1,11 @@
|
||||
**Alpha**
|
||||
| [source code on GitHub](https://github.com/jaandrle/deka-dom-el)
|
||||
| [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el)
|
||||
| [npm package](https://www.npmjs.com/package/deka-dom-el)
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180">
|
||||
</p>
|
||||
|
||||
# Deka DOM Elements (dd\<el\> or DDE)
|
||||
| [Docs](https://jaandrle.github.io/deka-dom-el "Official documentation and guide site")
|
||||
| [NPM](https://www.npmjs.com/package/deka-dom-el "Official NPM package page")
|
||||
| [GitHub](https://github.com/jaandrle/deka-dom-el "Official GitHub repository")
|
||||
([*Gitea*](https://gitea.jaandrle.cz/jaandrle/deka-dom-el "GitHub repository mirror on my own Gitea instance"))
|
||||
|
||||
***Vanilla for flavouring — a full-fledged feast for large projects***
|
||||
|
||||
*…use simple DOM API by default and library tools and logic when you need them*
|
||||
|
||||
```javascript
|
||||
// 🌟 Reactive component with clear separation of concerns
|
||||
document.body.append(
|
||||
@ -20,25 +13,23 @@ document.body.append(
|
||||
);
|
||||
|
||||
function EmojiCounter({ initial }) {
|
||||
// ✨ State - Define reactive data
|
||||
// ✨ - Define reactive data
|
||||
const count = S(0);
|
||||
const emoji = S(initial);
|
||||
const textContent = S(() => `Hello World ${emoji.get().repeat(count.get())}`);
|
||||
|
||||
// 🔄 View - UI updates automatically when signals change
|
||||
// 🔄 - UI updates automatically when signals change
|
||||
return el().append(
|
||||
el("p", {
|
||||
className: "output",
|
||||
textContent: S(() =>
|
||||
`Hello World ${emoji.get().repeat(count.get())}`),
|
||||
}),
|
||||
el("p", { textContent, className: "output" }),
|
||||
|
||||
// 🎮 Controls - Update state on events
|
||||
// 🎮 - Update state on events
|
||||
el("button", { textContent: "Add Emoji" },
|
||||
on("click", () => count.set(count.get() + 1))
|
||||
on("click", () => count.set(count.get() + 1)),
|
||||
),
|
||||
|
||||
el("select", null, on.defer(el=> el.value= initial),
|
||||
on("change", e => emoji.set(e.target.value))
|
||||
el("select", null,
|
||||
on.defer(el=> el.value= initial),
|
||||
on("change", e => emoji.set(e.target.value)),
|
||||
).append(
|
||||
el(Option, "🎉"),
|
||||
el(Option, "🚀"),
|
||||
@ -50,6 +41,13 @@ function Option({ textContent }){
|
||||
return el("option", { value: textContent, textContent });
|
||||
}
|
||||
```
|
||||
*…use simple DOM API by default and library tools and logic when you need them*
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180">
|
||||
</p>
|
||||
|
||||
# Deka DOM Elements (dd\<el\> or DDE)
|
||||
|
||||
Creating reactive elements, components, and Web Components using the native
|
||||
[IDL](https://developer.mozilla.org/en-US/docs/Glossary/IDL)/JavaScript DOM API enhanced with
|
||||
@ -61,19 +59,21 @@ Creating reactive elements, components, and Web Components using the native
|
||||
- ☑️ **Lightweight** — ~10-15kB minified (original goal 10kB) with **zero**/minimal dependencies
|
||||
- ✅ **Declarative & functional approach** for clean, maintainable code
|
||||
- ✅ **Signals and events** for reactive UI
|
||||
- ✅ **Auto-releasing resources** for memory management but nice development experience
|
||||
- ✅ **Memoization for performance** — optimize rendering with intelligent caching
|
||||
- ✅ **Optional build-in signals** with support for custom reactive implementations (#39)
|
||||
- ✅ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom)
|
||||
- ☑️ **Optional build-in signals** with support for custom reactive implementations (#39)
|
||||
- ☑️ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom)
|
||||
- ✅ **TypeScript support**
|
||||
- ☑️ **Support for debugging with browser DevTools** without extensions
|
||||
- ✅ **Support for debugging with browser DevTools** without extensions
|
||||
- ☑️ **Enhanced Web Components** support
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Documentation
|
||||
### Quick Links
|
||||
|
||||
- [**Documentation and Guide**](https://jaandrle.github.io/deka-dom-el)
|
||||
- [**Examples**](https://jaandrle.github.io/deka-dom-el/p15-examples.html)
|
||||
- [**Changelog**](https://github.com/jaandrle/deka-dom-el/releases)
|
||||
|
||||
### Installation
|
||||
|
||||
@ -138,9 +138,9 @@ get started, coding standards, commit guidelines, and the pull request process.
|
||||
interfaces or HTML code.
|
||||
- [pota](https://pota.quack.uy/) — small and pluggable Reactive Web Renderer. It's compiler-less, includes an html
|
||||
function, and a optimized babel preset in case you fancy JSX.
|
||||
- [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) —
|
||||
Functional DOM components without JSX/virtual DOM
|
||||
- [TarekRaafat/eleva](https://github.com/TarekRaafat/eleva) — A minimalist, lightweight, pure vanilla JavaScript
|
||||
frontend runtime framework.
|
||||
- [didi/mpx](https://github.com/didi/mpx) — Mpx,一款具有优秀开发体验和深度性能优化的增强型跨端小程序框架
|
||||
- [mxjp/rvx](https://github.com/mxjp/rvx) — A signal based frontend framework
|
||||
- [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) —
|
||||
Functional DOM components without JSX/virtual DOM (my old library)
|
||||
|
@ -2,7 +2,7 @@
|
||||
/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */// editorconfig-checker-disable-line
|
||||
echo("Building static documentation files…");
|
||||
echo("Preparing…");
|
||||
import { path_target, pages as pages_registered, styles, dispatchEvent, t } from "../docs/ssr.js";
|
||||
import { path_target, pages as pages_registered, styles, currentPageId, dispatchEvent, t } from "../docs/ssr.js";
|
||||
import { createHTMl } from "./docs/jsdom.js";
|
||||
import { register, queue } from "../jsdom.js";
|
||||
const pkg= s.cat("package.json").xargs(JSON.parse);
|
||||
@ -28,6 +28,7 @@ for(const { id, info } of pages){
|
||||
);
|
||||
const { el }= await register(serverDOM.dom);
|
||||
const { page }= await import(`../docs/${id}.html.js`);
|
||||
currentPageId(id)
|
||||
serverDOM.document.body.append(
|
||||
el(page, { pkg, info }),
|
||||
);
|
||||
|
23
dist/esm-with-signals.js
vendored
23
dist/esm-with-signals.js
vendored
@ -696,13 +696,10 @@ var queueSignalWrite = /* @__PURE__ */ (() => {
|
||||
})();
|
||||
|
||||
// src/signals-lib/signals-lib.js
|
||||
var Signal = oCreate(null, {
|
||||
var SignalReadOnly = oCreate(null, {
|
||||
get: { value() {
|
||||
return read(this);
|
||||
} },
|
||||
set: { value(...v) {
|
||||
return write(this, ...v);
|
||||
} },
|
||||
toJSON: { value() {
|
||||
return read(this);
|
||||
} },
|
||||
@ -710,9 +707,9 @@ var Signal = oCreate(null, {
|
||||
return this[mark] && this[mark].value;
|
||||
} }
|
||||
});
|
||||
var SignalReadOnly = oCreate(Signal, {
|
||||
set: { value() {
|
||||
return;
|
||||
var Signal = oCreate(SignalReadOnly, {
|
||||
set: { value(...v) {
|
||||
return write(this, ...v);
|
||||
} }
|
||||
});
|
||||
function isSignal(candidate) {
|
||||
@ -889,9 +886,10 @@ var signals_config = {
|
||||
function removeSignalsFromElements(s, listener, ...notes) {
|
||||
const { current } = scope;
|
||||
current.host(function(element) {
|
||||
if (!element[key_reactive]) element[key_reactive] = [];
|
||||
const is_first = !element[key_reactive];
|
||||
if (is_first) element[key_reactive] = [];
|
||||
element[key_reactive].push([[s, listener], ...notes]);
|
||||
if (current.prevent) return;
|
||||
if (!is_first || current.prevent) return;
|
||||
on.disconnected(
|
||||
() => (
|
||||
/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, …?).
|
||||
@ -906,7 +904,7 @@ var cleanUpRegistry = new FinalizationRegistry(function(s) {
|
||||
});
|
||||
function create(is_readonly, value, actions) {
|
||||
const varS = oCreate(is_readonly ? SignalReadOnly : Signal);
|
||||
const SI = toSignal(varS, value, actions, is_readonly);
|
||||
const SI = toSignal(varS, value, actions);
|
||||
cleanUpRegistry.register(SI, SI[mark]);
|
||||
return SI;
|
||||
}
|
||||
@ -918,7 +916,7 @@ var protoSigal = oAssign(oCreate(), {
|
||||
this.skip = true;
|
||||
}
|
||||
});
|
||||
function toSignal(s, value, actions, readonly = false) {
|
||||
function toSignal(s, value, actions) {
|
||||
const onclear = [];
|
||||
if (typeOf(actions) !== "[object Object]")
|
||||
actions = {};
|
||||
@ -934,8 +932,7 @@ function toSignal(s, value, actions, readonly = false) {
|
||||
actions,
|
||||
onclear,
|
||||
host,
|
||||
listeners: /* @__PURE__ */ new Set(),
|
||||
readonly
|
||||
listeners: /* @__PURE__ */ new Set()
|
||||
}),
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
|
6
dist/esm-with-signals.min.js
vendored
6
dist/esm-with-signals.min.js
vendored
File diff suppressed because one or more lines are too long
23
dist/iife-with-signals.js
vendored
23
dist/iife-with-signals.js
vendored
@ -741,13 +741,10 @@ var DDE = (() => {
|
||||
})();
|
||||
|
||||
// src/signals-lib/signals-lib.js
|
||||
var Signal = oCreate(null, {
|
||||
var SignalReadOnly = oCreate(null, {
|
||||
get: { value() {
|
||||
return read(this);
|
||||
} },
|
||||
set: { value(...v) {
|
||||
return write(this, ...v);
|
||||
} },
|
||||
toJSON: { value() {
|
||||
return read(this);
|
||||
} },
|
||||
@ -755,9 +752,9 @@ var DDE = (() => {
|
||||
return this[mark] && this[mark].value;
|
||||
} }
|
||||
});
|
||||
var SignalReadOnly = oCreate(Signal, {
|
||||
set: { value() {
|
||||
return;
|
||||
var Signal = oCreate(SignalReadOnly, {
|
||||
set: { value(...v) {
|
||||
return write(this, ...v);
|
||||
} }
|
||||
});
|
||||
function isSignal(candidate) {
|
||||
@ -934,9 +931,10 @@ var DDE = (() => {
|
||||
function removeSignalsFromElements(s, listener, ...notes) {
|
||||
const { current } = scope;
|
||||
current.host(function(element) {
|
||||
if (!element[key_reactive]) element[key_reactive] = [];
|
||||
const is_first = !element[key_reactive];
|
||||
if (is_first) element[key_reactive] = [];
|
||||
element[key_reactive].push([[s, listener], ...notes]);
|
||||
if (current.prevent) return;
|
||||
if (!is_first || current.prevent) return;
|
||||
on.disconnected(
|
||||
() => (
|
||||
/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, …?).
|
||||
@ -951,7 +949,7 @@ var DDE = (() => {
|
||||
});
|
||||
function create(is_readonly, value, actions) {
|
||||
const varS = oCreate(is_readonly ? SignalReadOnly : Signal);
|
||||
const SI = toSignal(varS, value, actions, is_readonly);
|
||||
const SI = toSignal(varS, value, actions);
|
||||
cleanUpRegistry.register(SI, SI[mark]);
|
||||
return SI;
|
||||
}
|
||||
@ -963,7 +961,7 @@ var DDE = (() => {
|
||||
this.skip = true;
|
||||
}
|
||||
});
|
||||
function toSignal(s, value, actions, readonly = false) {
|
||||
function toSignal(s, value, actions) {
|
||||
const onclear = [];
|
||||
if (typeOf(actions) !== "[object Object]")
|
||||
actions = {};
|
||||
@ -979,8 +977,7 @@ var DDE = (() => {
|
||||
actions,
|
||||
onclear,
|
||||
host,
|
||||
listeners: /* @__PURE__ */ new Set(),
|
||||
readonly
|
||||
listeners: /* @__PURE__ */ new Set()
|
||||
}),
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
|
6
dist/iife-with-signals.min.js
vendored
6
dist/iife-with-signals.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,4 +1,4 @@
|
||||
import { registerClientFile, styles } from "../ssr.js";
|
||||
import { page_id, registerClientFile, styles } from "../ssr.js";
|
||||
const host= "."+code.name;
|
||||
styles.css`
|
||||
/* Code block styling */
|
||||
@ -177,6 +177,9 @@ ${host}:hover .copy-button {
|
||||
}
|
||||
`;
|
||||
import { el } from "deka-dom-el";
|
||||
/**
|
||||
* @typedef {"js"|"ts"|"html"|"css"|"shell"|"-"} Language
|
||||
* */
|
||||
/**
|
||||
* Prints code to the page and registers flems to make it interactive.
|
||||
* @param {object} attrs
|
||||
@ -184,15 +187,17 @@ import { el } from "deka-dom-el";
|
||||
* @param {string} [attrs.className]
|
||||
* @param {URL} [attrs.src] Example code file path
|
||||
* @param {string} [attrs.content] Example code
|
||||
* @param {"js"|"ts"|"html"|"css"|"shell"} [attrs.language="js"] Language of the code
|
||||
* @param {string} [attrs.page_id] ID of the page, if setted it registers shiki
|
||||
* @param {Language} [attrs.language="-s"] Language of the code
|
||||
* */
|
||||
export function code({ id, src, content, language= "js", className= host.slice(1), page_id }){
|
||||
if(src) content= s.cat(src);
|
||||
export function code({ id, src, content, language= "-", className= host.slice(1) }){
|
||||
if(src){
|
||||
content= s.cat(src);
|
||||
if(language=== "-") language= /** @type {Language} */(src.pathname.split(".").pop());
|
||||
}
|
||||
content= normalizeIndentation(content);
|
||||
let dataJS;
|
||||
if(page_id){
|
||||
registerClientPart(page_id);
|
||||
if(language!== "-"){
|
||||
registerClientPart();
|
||||
dataJS= "todo";
|
||||
}
|
||||
return el("div", { id, className, dataJS, tabIndex: 0 }).append(
|
||||
@ -204,8 +209,7 @@ export function pre({ content }){
|
||||
return el("pre").append(el("code", content.trim()));
|
||||
}
|
||||
let is_registered= {};
|
||||
/** @param {string} page_id */
|
||||
function registerClientPart(page_id){
|
||||
function registerClientPart(){
|
||||
if(is_registered[page_id]) return;
|
||||
|
||||
// Add Shiki with a more reliable loading method
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { styles } from "../ssr.js";
|
||||
import { page_id, styles } from "../ssr.js";
|
||||
|
||||
styles.css`
|
||||
#html-to-dde-converter {
|
||||
@ -149,12 +149,11 @@ import { ireland } from "./ireland.html.js";
|
||||
import { el } from "deka-dom-el";
|
||||
const fileURL= url=> new URL(url, import.meta.url);
|
||||
|
||||
export function converter({ page_id }){
|
||||
export function converter(){
|
||||
registerClientPart(page_id);
|
||||
return el(ireland, {
|
||||
src: fileURL("./converter.js.js"),
|
||||
exportName: "converter",
|
||||
page_id,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { styles } from "../ssr.js";
|
||||
import { page_id, styles } from "../ssr.js";
|
||||
const host= "."+example.name;
|
||||
styles.css`
|
||||
${host} {
|
||||
@ -119,9 +119,8 @@ import { relative } from "node:path";
|
||||
* @param {URL} attrs.src Example code file path
|
||||
* @param {"js"|"ts"|"html"|"css"} [attrs.language="js"] Language of the code
|
||||
* @param {"normal"|"big"} [attrs.variant="normal"] Size of the example
|
||||
* @param {string} attrs.page_id ID of the page
|
||||
* */
|
||||
export function example({ src, language= "js", variant= "normal", page_id }){
|
||||
export function example({ src, language= "js", variant= "normal" }){
|
||||
registerClientPart(page_id);
|
||||
const content= s.cat(src).toString()
|
||||
.replaceAll(/ from "deka-dom-el(\/signals)?";/g, ' from "./esm-with-signals.js";');
|
||||
|
@ -303,6 +303,7 @@ document.body.append(
|
||||
padding: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
|
@ -483,7 +483,7 @@ document.body.append(
|
||||
|
||||
.task-board {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
|
@ -4,11 +4,11 @@ import { el } from "deka-dom-el";
|
||||
const button = el("button", {
|
||||
textContent: "Click me",
|
||||
className: "primary",
|
||||
disabled: true
|
||||
disabled: true,
|
||||
});
|
||||
|
||||
// Shorter and more expressive
|
||||
// than the native approach
|
||||
|
||||
// Add to DOM
|
||||
document.body.append(button);
|
||||
document.body.append(button);
|
||||
|
@ -6,6 +6,6 @@ import { el } from "deka-dom-el";
|
||||
document.body.append(
|
||||
el("div").append(
|
||||
el("h1", "Title"),
|
||||
el("p", "Paragraph")
|
||||
)
|
||||
el("p", "Paragraph"),
|
||||
),
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { styles } from "../ssr.js";
|
||||
import { styles, page_id } from "../ssr.js";
|
||||
|
||||
styles.css`
|
||||
#library-url-form {
|
||||
@ -74,7 +74,7 @@ styles.css`
|
||||
import { el } from "deka-dom-el";
|
||||
import { ireland } from "./ireland.html.js";
|
||||
|
||||
export function getLibraryUrl({ page_id }){
|
||||
export function getLibraryUrl(){
|
||||
return el(ireland, {
|
||||
src: new URL("./getLibraryUrl.js.js", import.meta.url),
|
||||
exportName: "getLibraryUrl",
|
||||
|
@ -43,7 +43,6 @@ const componentsRegistry = new Map();
|
||||
* @param {object} attrs
|
||||
* @param {URL} attrs.src - Path to the file containing the component
|
||||
* @param {string} [attrs.exportName="default"] - Name of the export to use
|
||||
* @param {string} attrs.page_id - ID of the current page
|
||||
* @param {object} [attrs.props={}] - Props to pass to the component
|
||||
*/
|
||||
export function ireland({ src, exportName = "default", props = {} }) {
|
||||
|
@ -12,6 +12,10 @@ export function mnemonic(){
|
||||
" — corresponds to custom elemnets callbacks ", el("code", "<live-cycle>Callback(...){...}"),
|
||||
". To connect to custom element see following page, else it is simulated by MutationObserver."
|
||||
),
|
||||
el("li").append(
|
||||
el("code", "on.defer(<identity>=> <identity>)(<identity>)"),
|
||||
" — calls callback later",
|
||||
),
|
||||
el("li").append(
|
||||
el("code", "dispatchEvent(<event>[, <options>])(element)"),
|
||||
" — just ", el("code", "<element>.dispatchEvent(new Event(<event>[, <options>]))")
|
||||
|
@ -3,8 +3,8 @@ import { t, T } from "./utils/index.js";
|
||||
export const info= {
|
||||
href: "./",
|
||||
title: t`Introduction`,
|
||||
fullTitle: t`Vanilla for flavouring — a full-fledged feast for large projects`,
|
||||
description: t`A lightweight, reactive DOM library for creating dynamic UIs with a declarative syntax`,
|
||||
fullTitle: t`Vanilla for flavouring — a full-fledged feast for large projects`,
|
||||
description: t`Reactive DOM library for creating dynamic UIs with a declarative syntax`,
|
||||
};
|
||||
|
||||
import { el } from "deka-dom-el";
|
||||
@ -31,10 +31,9 @@ const references= {
|
||||
};
|
||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("p").append(T`
|
||||
Welcome to Deka DOM Elements (dd<el> or DDE) — a lightweight library for building dynamic UIs with
|
||||
Welcome to Deka DOM Elements (dd<el> or DDE) — a library for building dynamic UIs with
|
||||
a declarative syntax that stays close to the native DOM API. dd<el> gives you powerful reactive tools
|
||||
without the complexity and overhead of larger frameworks.
|
||||
`),
|
||||
@ -44,11 +43,11 @@ export function page({ pkg, info }){
|
||||
el("li", t`No build step required — use directly in the browser`),
|
||||
el("li", t`Lightweight core (~10–15kB minified) without unnecessary dependencies (0 at now 😇)`),
|
||||
el("li", t`Natural DOM API — work with real DOM nodes, not abstractions`),
|
||||
el("li", t`Built-in reactivity with simplified but powerful signals system`),
|
||||
el("li", t`Built-in (but optional) reactivity with simplified but powerful signals system`),
|
||||
el("li", t`Clean code organization with the 3PS pattern`)
|
||||
)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/introducing/helloWorld.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/introducing/helloWorld.js") }),
|
||||
|
||||
el(h3, { textContent: t`The 3PS Pattern: Simplified architecture pattern`, id: "h-3ps" }),
|
||||
el("p").append(T`
|
||||
@ -60,11 +59,11 @@ export function page({ pkg, info }){
|
||||
el("div", { className: "tabs" }).append(
|
||||
el("div", { className: "tab" }).append(
|
||||
el("h5", t`Traditional DOM Manipulation`),
|
||||
el(code, { src: fileURL("./components/examples/introducing/3ps-before.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/introducing/3ps-before.js") }),
|
||||
),
|
||||
el("div", { className: "tab" }).append(
|
||||
el("h5", t`dd<el>'s 3PS Pattern`),
|
||||
el(code, { src: fileURL("./components/examples/introducing/3ps.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/introducing/3ps.js") }),
|
||||
)
|
||||
)
|
||||
),
|
||||
@ -105,11 +104,11 @@ export function page({ pkg, info }){
|
||||
|
||||
el(h3, t`Getting Started`),
|
||||
el("p").append(T`
|
||||
There are multiple ways to include dd<el> in your project. You can use npm for a full development setup,
|
||||
or directly include it from a CDN for quick prototyping.
|
||||
There are multiple ways to include dd<el> in your project. You can use npm for a full development setup,
|
||||
or directly include it from a CDN for quick prototyping.
|
||||
`),
|
||||
el("h4", "npm installation"),
|
||||
el(code, { content: "npm install deka-dom-el --save", language: "shell", page_id }),
|
||||
el(code, { content: "npm install deka-dom-el --save", language: "shell" }),
|
||||
el("p").append(T`
|
||||
…see ${el("a", { textContent: "package page", ...references.npm, target: "_blank" })}.
|
||||
`),
|
||||
@ -118,7 +117,7 @@ export function page({ pkg, info }){
|
||||
el("p").append(T`
|
||||
Use the interactive selector below to choose your preferred format:
|
||||
`),
|
||||
el(getLibraryUrl, { page_id }),
|
||||
el(getLibraryUrl),
|
||||
el("div", { className: "note" }).append(
|
||||
el("p").append(T`
|
||||
Based on your selection, you can use dd<el> in your project like this:
|
||||
@ -127,10 +126,10 @@ export function page({ pkg, info }){
|
||||
// ESM format (modern JavaScript with import/export)
|
||||
import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.min.js";
|
||||
|
||||
// Or with IIFE format (creates a global DDE object)
|
||||
// Or with IIFE format (creates a global DDE object)
|
||||
// <script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/iife-with-signals.min.js"></script>
|
||||
const { el, on } = DDE;
|
||||
`, language: "js", page_id }),
|
||||
`, language: "js" }),
|
||||
),
|
||||
|
||||
el(h3, t`How to Use This Documentation`),
|
||||
@ -154,7 +153,7 @@ export function page({ pkg, info }){
|
||||
Integrating third-party functionalities`),
|
||||
el("li").append(T`${el("a", { href: "p09-optimization.html" })
|
||||
.append(el("strong", "Performance Optimization"))} — Techniques for optimizing your applications`),
|
||||
el("li").append(T`${el("a", { href: "p10-todomvc.html" }).append(el("strong", "TodoMVC"))} — A real-world
|
||||
el("li").append(T`${el("a", { href: "p10-todomvc.html" }).append(el("strong", "TodoMVC"))} — A real-world
|
||||
application implementation`),
|
||||
el("li").append(T`${el("a", { href: "p11-ssr.html" }).append(el("strong", "SSR"))} — Server-side
|
||||
rendering with dd<el>`),
|
||||
|
@ -47,12 +47,11 @@ const references= {
|
||||
};
|
||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("p").append(T`
|
||||
Building user interfaces in JavaScript often involves creating and manipulating DOM elements.
|
||||
dd<el> provides a simple yet powerful approach to element creation that is declarative, chainable,
|
||||
and maintains a clean syntax close to HTML structure.
|
||||
dd<el> provides a simple yet powerful approach to element creation that is declarative, chainable,
|
||||
and maintains a clean syntax close to HTML structure.
|
||||
`),
|
||||
el("div", { className: "callout" }).append(
|
||||
el("h4", t`dd<el> Elements: Key Benefits`),
|
||||
@ -65,7 +64,7 @@ export function page({ pkg, info }){
|
||||
)
|
||||
),
|
||||
|
||||
el(code, { src: fileURL("./components/examples/elements/intro.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/elements/intro.js") }),
|
||||
|
||||
el(h3, t`Creating Elements: Native vs dd<el>`),
|
||||
el("p").append(T`
|
||||
@ -77,11 +76,11 @@ export function page({ pkg, info }){
|
||||
el("div", { className: "comparison" }).append(
|
||||
el("div").append(
|
||||
el("h5", t`Native DOM API`),
|
||||
el(code, { src: fileURL("./components/examples/elements/native-dom-create.js"), page_id })
|
||||
el(code, { src: fileURL("./components/examples/elements/native-dom-create.js") })
|
||||
),
|
||||
el("div").append(
|
||||
el("h5", t`dd<el> Approach`),
|
||||
el(code, { src: fileURL("./components/examples/elements/dde-dom-create.js"), page_id })
|
||||
el(code, { src: fileURL("./components/examples/elements/dde-dom-create.js") })
|
||||
)
|
||||
)
|
||||
),
|
||||
@ -89,7 +88,7 @@ export function page({ pkg, info }){
|
||||
The ${el("code", "el")} function provides a simple wrapper around ${el("code", "document.createElement")}
|
||||
with enhanced property assignment.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/elements/dekaCreateElement.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/elements/dekaCreateElement.js") }),
|
||||
|
||||
el(h3, t`Advanced Property Assignment`),
|
||||
el("p").append(T`
|
||||
@ -122,7 +121,7 @@ export function page({ pkg, info }){
|
||||
el("dd").append(T`Pass ${el("code", "undefined")} to remove a property or attribute`)
|
||||
)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/elements/dekaAssign.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/elements/dekaAssign.js") }),
|
||||
|
||||
el("div", { className: "note" }).append(
|
||||
el("p").append(T`
|
||||
@ -142,11 +141,11 @@ export function page({ pkg, info }){
|
||||
el("div", { className: "comparison" }).append(
|
||||
el("div", { className: "bad-practice" }).append(
|
||||
el("h5", t`❌ Native DOM API`),
|
||||
el(code, { src: fileURL("./components/examples/elements/native-dom-tree.js"), page_id })
|
||||
el(code, { src: fileURL("./components/examples/elements/native-dom-tree.js") })
|
||||
),
|
||||
el("div", { className: "good-practice" }).append(
|
||||
el("h5", t`✅ dd<el> Approach`),
|
||||
el(code, { src: fileURL("./components/examples/elements/dde-dom-tree.js"), page_id })
|
||||
el(code, { src: fileURL("./components/examples/elements/dde-dom-tree.js") })
|
||||
)
|
||||
)
|
||||
),
|
||||
@ -154,14 +153,14 @@ export function page({ pkg, info }){
|
||||
This chainable pattern is much cleaner and easier to follow, especially for deeply nested elements.
|
||||
It also makes it simple to add multiple children to a parent element in a single fluent expression.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/elements/dekaAppend.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/elements/dekaAppend.js") }),
|
||||
|
||||
el(h3, t`Using Components to Build UI Fragments`),
|
||||
el("p").append(T`
|
||||
The ${el("code", "el")} function is overloaded to support both tag names and function components.
|
||||
This lets you refactor complex UI trees into reusable pieces:
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/elements/dekaBasicComponent.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/elements/dekaBasicComponent.js") }),
|
||||
el("p").append(T`
|
||||
Component functions receive the properties object as their first argument, just like regular elements.
|
||||
This makes it easy to pass data down to components and create reusable UI fragments.
|
||||
@ -185,9 +184,9 @@ export function page({ pkg, info }){
|
||||
function which corresponds to the native ${el("a", references.mdn_ns).append(el("code",
|
||||
"document.createElementNS"))}:
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/elements/dekaElNS.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/elements/dekaElNS.js") }),
|
||||
el("p").append(T`
|
||||
This function returns a namespace-specific element creator, allowing you to work with any element type
|
||||
This function returns a namespace-specific element creator, allowing you to work with any element type
|
||||
using the same consistent interface.
|
||||
`),
|
||||
|
||||
|
@ -39,7 +39,6 @@ const references= {
|
||||
};
|
||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("p").append(T`
|
||||
Events are at the core of interactive web applications. dd<el> provides a clean, declarative approach to
|
||||
@ -57,7 +56,7 @@ export function page({ pkg, info }){
|
||||
)
|
||||
),
|
||||
|
||||
el(code, { src: fileURL("./components/examples/events/intro.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/events/intro.js") }),
|
||||
|
||||
el(h3, t`Events and Listeners: Two Approaches`),
|
||||
el("p").append(T`
|
||||
@ -70,11 +69,11 @@ export function page({ pkg, info }){
|
||||
el("div", { className: "tabs" }).append(
|
||||
el("div", { className: "tab" }).append(
|
||||
el("h5", t`Native DOM API`),
|
||||
el(code, { content: `element.addEventListener("click", callback, options);`, page_id })
|
||||
el(code, { content: `element.addEventListener("click", callback, options);`, language: "js" })
|
||||
),
|
||||
el("div", { className: "tab" }).append(
|
||||
el("h5", t`dd<el> Approach`),
|
||||
el(code, { content: `on("click", callback, options)(element);`, page_id })
|
||||
el(code, { content: `on("click", callback, options)(element);`, language: "js" })
|
||||
)
|
||||
)
|
||||
),
|
||||
@ -82,7 +81,7 @@ export function page({ pkg, info }){
|
||||
The main benefit of dd<el>’s approach is that it works as an Addon (see below), making it easy to integrate
|
||||
directly into element declarations.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/events/compare.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/events/compare.js") }),
|
||||
|
||||
el(h3, t`Removing Event Listeners`),
|
||||
el("div", { className: "note" }).append(
|
||||
@ -91,7 +90,7 @@ export function page({ pkg, info }){
|
||||
${el("a", { textContent: "AbortSignal", ...references.mdn_abortListener })} for declarative removal:
|
||||
`)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/events/abortSignal.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/events/abortSignal.js") }),
|
||||
el("p").append(T`
|
||||
This is the same for signals (see next section) and works well with scopes and library extendability (
|
||||
see scopes and extensions section — mainly ${el("code", "scope.signal")}).
|
||||
@ -101,26 +100,26 @@ export function page({ pkg, info }){
|
||||
el("div", { className: "tabs" }).append(
|
||||
el("div", { className: "tab", dataTab: "html-attr" }).append(
|
||||
el("h4", t`HTML Attribute Style`),
|
||||
el(code, { src: fileURL("./components/examples/events/attribute-event.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/events/attribute-event.js") }),
|
||||
el("p").append(T`
|
||||
Forces usage as an HTML attribute. Corresponds to
|
||||
Forces usage as an HTML attribute. Corresponds to
|
||||
${el("code", `<button onclick="console.log(event)">click me</button>`)}. This can be particularly
|
||||
useful for SSR scenarios.
|
||||
`)
|
||||
),
|
||||
el("div", { className: "tab", dataTab: "property" }).append(
|
||||
el("h4", t`Property Assignment`),
|
||||
el(code, { src: fileURL("./components/examples/events/property-event.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/events/property-event.js") }),
|
||||
el("p", t`Assigns the event handler directly to the element’s property.`)
|
||||
),
|
||||
el("div", { className: "tab", dataTab: "addon" }).append(
|
||||
el("h4", t`Addon Approach`),
|
||||
el(code, { src: fileURL("./components/examples/events/chain-event.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/events/chain-event.js") }),
|
||||
el("p", t`Uses the addon pattern (so adds the event listener to the element), see above.`)
|
||||
)
|
||||
),
|
||||
el("p").append(T`
|
||||
For a deeper comparison of these approaches, see
|
||||
For a deeper comparison of these approaches, see
|
||||
${el("a", { textContent: "WebReflection’s detailed analysis", ...references.web_events })}.
|
||||
`),
|
||||
|
||||
@ -143,7 +142,7 @@ export function page({ pkg, info }){
|
||||
You can use Addons as ≥3rd argument of the ${el("code", "el")} function, making it possible to
|
||||
extend your templates with additional functionality:
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/events/templateWithListeners.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/events/templateWithListeners.js") }),
|
||||
el("p").append(T`
|
||||
As the example shows, you can provide types in JSDoc+TypeScript using the global type
|
||||
${el("code", "ddeElementAddon")}. Notice how Addons can also be used to get element references.
|
||||
@ -167,7 +166,7 @@ export function page({ pkg, info }){
|
||||
el("dd", t`Fires when the element is removed from the DOM`),
|
||||
)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/events/live-cycle.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/events/live-cycle.js") }),
|
||||
|
||||
el("div", { className: "note" }).append(
|
||||
el("p").append(T`
|
||||
@ -217,8 +216,8 @@ export function page({ pkg, info }){
|
||||
This makes it easy to implement component communication through events, following standard web platform
|
||||
patterns. The curried approach allows for easy reuse of event dispatchers throughout your application.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/events/compareDispatch.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/events/dispatch.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/events/compareDispatch.js") }),
|
||||
el(code, { src: fileURL("./components/examples/events/dispatch.js") }),
|
||||
|
||||
el(h3, t`Best Practices`),
|
||||
el("ol").append(
|
||||
@ -251,6 +250,6 @@ export function page({ pkg, info }){
|
||||
)
|
||||
),
|
||||
|
||||
el(mnemonic)
|
||||
el(mnemonic),
|
||||
);
|
||||
}
|
||||
|
@ -42,7 +42,6 @@ const references= {
|
||||
};
|
||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("p").append(T`
|
||||
Signals provide a simple yet powerful way to create reactive applications with dd<el>. They handle the
|
||||
@ -58,7 +57,7 @@ export function page({ pkg, info }){
|
||||
el("li").append(T`${el("strong", "In future")} no dependencies or framework lock-in`)
|
||||
)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/signals/intro.js") }),
|
||||
|
||||
el(h3, t`The 3-Part Structure of Signals`),
|
||||
el("p").append(T`
|
||||
@ -68,21 +67,21 @@ export function page({ pkg, info }){
|
||||
el("div", { className: "signal-diagram" }).append(
|
||||
el("div", { className: "signal-part" }).append(
|
||||
el("h4", t`PART 1: Create Signal`),
|
||||
el(code, { content: "const count = S(0);", page_id }),
|
||||
el(code, { content: "const count = S(0);", language: "js" }),
|
||||
el("p", t`Define a reactive value that can be observed and changed`)
|
||||
),
|
||||
el("div", { className: "signal-part" }).append(
|
||||
el("h4", t`PART 2: React to Changes`),
|
||||
el(code, { content: "S.on(count, value => updateUI(value));", page_id }),
|
||||
el(code, { content: "S.on(count, value => updateUI(value));", language: "js" }),
|
||||
el("p", t`Subscribe to signal changes with callbacks or effects`)
|
||||
),
|
||||
el("div", { className: "signal-part" }).append(
|
||||
el("h4", t`PART 3: Update Signal`),
|
||||
el(code, { content: "count.set(count.get() + 1);", page_id }),
|
||||
el(code, { content: "count.set(count.get() + 1);", language: "js" }),
|
||||
el("p", t`Modify the signal value, which automatically triggers updates`)
|
||||
)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/signals/signals.js") }),
|
||||
|
||||
el("div", { className: "note" }).append(
|
||||
el("p").append(T`
|
||||
@ -125,12 +124,12 @@ export function page({ pkg, info }){
|
||||
Computed values (also called derived signals) automatically update when their dependencies change.
|
||||
Create them by passing ${el("strong", "a function")} to ${el("code", "S()")}:
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/signals/derived.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/signals/derived.js") }),
|
||||
el("p").append(T`
|
||||
Derived signals are read-only - you can’t call ${el("code", ".set()")} on them. Their value is always
|
||||
computed from their dependencies. They’re perfect for transforming or combining data from other signals.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/signals/computations-abort.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/signals/computations-abort.js") }),
|
||||
|
||||
el(h3, t`Signal Actions: For Complex State`),
|
||||
el("p").append(T`
|
||||
@ -151,7 +150,7 @@ export function page({ pkg, info }){
|
||||
});
|
||||
// Use the action
|
||||
S.action(todos, "add", "New todo");
|
||||
`, page_id })
|
||||
`, language: "js" })
|
||||
),
|
||||
el("div", { className: "bad-practice" }).append(
|
||||
el("h5", t`❌ Without Actions`),
|
||||
@ -161,7 +160,7 @@ export function page({ pkg, info }){
|
||||
const items = todos.get();
|
||||
items.push("New todo");
|
||||
// This WON’T trigger updates!
|
||||
`, page_id }))
|
||||
`, language: "js" }))
|
||||
),
|
||||
),
|
||||
el("p").append(T`
|
||||
@ -172,7 +171,7 @@ export function page({ pkg, info }){
|
||||
${el("code", "this.stopPropagation()")} in the method representing the given action. As it can be seen in
|
||||
examples, the “store” value is available also in the function for given action (${el("code", "this.value")}).
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/signals/actions-demo.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/signals/actions-demo.js") }),
|
||||
|
||||
el("p").append(T`
|
||||
Actions provide these benefits:
|
||||
@ -186,7 +185,7 @@ export function page({ pkg, info }){
|
||||
el("p").append(T`
|
||||
Here’s a more complete example of a todo list using signal actions:
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/signals/actions-todos.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/signals/actions-todos.js") }),
|
||||
|
||||
el("div", { className: "tip" }).append(
|
||||
el("p").append(T`
|
||||
@ -223,7 +222,7 @@ export function page({ pkg, info }){
|
||||
|
||||
// Later:
|
||||
color.set("red"); // UI updates automatically
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
),
|
||||
el("div", { className: "tab", dataTab: "elements" }).append(
|
||||
el("h4", t`Reactive Elements`),
|
||||
@ -241,7 +240,7 @@ export function page({ pkg, info }){
|
||||
|
||||
// Later:
|
||||
S.action(items, "push", "Dragonfruit"); // List updates automatically
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
)
|
||||
),
|
||||
|
||||
@ -250,12 +249,12 @@ export function page({ pkg, info }){
|
||||
You can use special properties like ${el("code", "dataset")}, ${el("code", "ariaset")}, and
|
||||
${el("code", "classList")} for fine-grained control over specific attribute types.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/signals/dom-attrs.js") }),
|
||||
|
||||
el("p").append(T`
|
||||
${el("code", "S.el()")} is especially powerful for conditional rendering and lists:
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/signals/dom-el.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/signals/dom-el.js") }),
|
||||
|
||||
el(h3, t`Best Practices for Signals`),
|
||||
el("p").append(T`
|
||||
@ -289,7 +288,7 @@ export function page({ pkg, info }){
|
||||
el("h4", t`We can process form events without signals`),
|
||||
el("p", t`This can be used when the form data doesn’t need to be reactive and we just waiting for
|
||||
results.`),
|
||||
el(code, { page_id, content: `
|
||||
el(code, { content: `
|
||||
const onFormSubmit = on("submit", e => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
@ -301,13 +300,13 @@ export function page({ pkg, info }){
|
||||
return el("form", null, onFormSubmit).append(
|
||||
// …
|
||||
);
|
||||
` })
|
||||
`, language: "js" })
|
||||
),
|
||||
|
||||
el("div", { className: "tab", dataTab: "variables" }).append(
|
||||
el("h4", t`We can use variables without signals`),
|
||||
el("p", t`We use this when we dont’t need to reflect changes in the elsewhere (UI).`),
|
||||
el(code, { page_id, content: `
|
||||
el(code, { content: `
|
||||
let canSubmit = false;
|
||||
|
||||
const onFormSubmit = on("submit", e => {
|
||||
@ -318,14 +317,14 @@ export function page({ pkg, info }){
|
||||
const onAllowSubmit = on("click", e => {
|
||||
canSubmit = true;
|
||||
});
|
||||
`}),
|
||||
`, language: "js" }),
|
||||
),
|
||||
|
||||
el("div", { className: "tab", dataTab: "state" }).append(
|
||||
el("h4", t`Using signals`),
|
||||
el("p", t`We use this when we need to reflect changes for example in the UI (e.g. enable/disable
|
||||
buttons).`),
|
||||
el(code, { page_id, content: `
|
||||
el(code, { content: `
|
||||
const canSubmit = S(false);
|
||||
|
||||
const onFormSubmit = on("submit", e => {
|
||||
@ -341,7 +340,7 @@ export function page({ pkg, info }){
|
||||
el("button", { textContent: "Allow Submit", type: "button" }, onAllowSubmit),
|
||||
el("button", { disabled: S(()=> !canSubmit), textContent: "Submit" })
|
||||
);
|
||||
`}),
|
||||
`, language: "js" }),
|
||||
),
|
||||
),
|
||||
|
||||
|
@ -27,14 +27,13 @@ const references= {
|
||||
};
|
||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("p").append(T`
|
||||
For state-less components we can use functions as UI components (see “Elements” page). But in real life,
|
||||
we may need to handle the component’s life-cycle and provide JavaScript the way to properly use
|
||||
the ${el("a", { textContent: t`Garbage collection`, ...references.garbage_collection })}.
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/scopes/intro.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/scopes/intro.js") }),
|
||||
el("p").append(T`The library therefore uses ${el("em", t`scopes`)} to provide these functionalities.`),
|
||||
|
||||
el(h3, t`Understanding Host Elements and Scopes`),
|
||||
@ -83,7 +82,7 @@ export function page({ pkg, info }){
|
||||
el("dd", t`Applies the addons to the host element (and returns the host element)`)
|
||||
)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/scopes/scopes-and-hosts.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/scopes/scopes-and-hosts.js") }),
|
||||
|
||||
el("div", { className: "tip" }).append(
|
||||
el("p").append(T`
|
||||
@ -95,7 +94,7 @@ export function page({ pkg, info }){
|
||||
If you are interested in the implementation details, see Class-Based Components section.
|
||||
`)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/scopes/good-practise.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/scopes/good-practise.js") }),
|
||||
|
||||
el(h3, t`Class-Based Components`),
|
||||
el("p").append(T`
|
||||
@ -103,7 +102,7 @@ export function page({ pkg, info }){
|
||||
For this, we implement function ${el("code", "elClass")} and use it to demonstrate implementation details
|
||||
for better understanding of the scope logic.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/scopes/class-component.js") }),
|
||||
|
||||
el(h3, t`Automatic Cleanup with Scopes`),
|
||||
el("p").append(T`
|
||||
@ -123,7 +122,7 @@ export function page({ pkg, info }){
|
||||
- Custom cleanup code (dd<el> and user)
|
||||
` })
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/scopes/cleaning.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/scopes/cleaning.js") }),
|
||||
|
||||
el("div", { className: "note" }).append(
|
||||
el("p").append(T`
|
||||
@ -149,17 +148,17 @@ export function page({ pkg, info }){
|
||||
el("div", { className: "tab", dataTab: "declarative" }).append(
|
||||
el("h4", t`✅ Declarative Approach`),
|
||||
el("p", t`Define what your UI should look like based on state:`),
|
||||
el(code, { src: fileURL("./components/examples/scopes/declarative.js"), page_id })
|
||||
el(code, { src: fileURL("./components/examples/scopes/declarative.js") })
|
||||
),
|
||||
el("div", { className: "tab", dataTab: "imperative" }).append(
|
||||
el("h4", t`⚠️ Imperative Approach`),
|
||||
el("p", t`Manually update the DOM in response to events:`),
|
||||
el(code, { src: fileURL("./components/examples/scopes/imperative.js"), page_id })
|
||||
el(code, { src: fileURL("./components/examples/scopes/imperative.js") })
|
||||
),
|
||||
el("div", { className: "tab", dataTab: "mixed" }).append(
|
||||
el("h4", t`❌ Mixed Approach`),
|
||||
el("p", t`This approach should be avoided:`),
|
||||
el(code, { src: fileURL("./components/examples/scopes/mixed.js"), page_id })
|
||||
el(code, { src: fileURL("./components/examples/scopes/mixed.js") })
|
||||
)
|
||||
),
|
||||
|
||||
|
@ -57,7 +57,6 @@ const references= {
|
||||
};
|
||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("p").append(T`
|
||||
dd<el> pairs powerfully with ${el("a", references.mdn_web_components).append(el("strong", t`Web
|
||||
@ -73,7 +72,7 @@ export function page({ pkg, info }){
|
||||
el("li", t`Clean component lifecycle management`),
|
||||
),
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/customElement/intro.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/customElement/intro.js") }),
|
||||
|
||||
el(h3, t`Getting Started: Web Components Basics`),
|
||||
el("p").append(T`
|
||||
@ -95,7 +94,7 @@ export function page({ pkg, info }){
|
||||
el("p").append(T`
|
||||
Let’s start with a basic Custom Element example without dd<el> to establish the foundation:
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/customElement/native-basic.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/customElement/native-basic.js") }),
|
||||
|
||||
el("div", { className: "note" }).append(
|
||||
el("p").append(T`
|
||||
@ -124,7 +123,7 @@ export function page({ pkg, info }){
|
||||
el("dd", t`Allows using on.connected(), on.disconnected() or S.observedAttributes().`)
|
||||
)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js") }),
|
||||
|
||||
el("div", { className: "tip" }).append(
|
||||
el("p").append(T`
|
||||
@ -156,7 +155,7 @@ export function page({ pkg, info }){
|
||||
el("dd", t`The rendered DOM tree`)
|
||||
)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/customElement/dde.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/customElement/dde.js") }),
|
||||
|
||||
el("div", { className: "note" }).append(
|
||||
el("p").append(T`
|
||||
@ -188,7 +187,7 @@ export function page({ pkg, info }){
|
||||
Using the ${el("code", "S.observedAttributes")} creates a reactive connection between your element’s
|
||||
attributes and its internal rendering. When attributes change, your component automatically updates!
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/customElement/observedAttributes.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/customElement/observedAttributes.js") }),
|
||||
|
||||
el("div", { className: "callout" }).append(
|
||||
el("h4", t`How S.observedAttributes Works`),
|
||||
@ -221,7 +220,7 @@ export function page({ pkg, info }){
|
||||
<p>Content</p>
|
||||
` })
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js") }),
|
||||
|
||||
el("p").append(T`
|
||||
For more information on Shadow DOM, see
|
||||
@ -234,7 +233,7 @@ export function page({ pkg, info }){
|
||||
Besides the encapsulation, the Shadow DOM allows for using the ${el("a", references.mdn_shadow_dom_slot).append(
|
||||
el("strong", t`<slot>`), t` element(s)`)}. You can simulate this feature using ${el("code", "simulateSlots")}:
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js") }),
|
||||
el("div", { className: "function-table" }).append(
|
||||
el("h4", t`simulateSlots`),
|
||||
el("dl").append(
|
||||
|
@ -15,7 +15,6 @@ const fileURL= url=> new URL(url, import.meta.url);
|
||||
|
||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("p").append(T`
|
||||
Debugging is an essential part of application development. This guide provides techniques
|
||||
@ -36,7 +35,7 @@ export function page({ pkg, info }){
|
||||
el(code, { content: `
|
||||
const signal = S(0);
|
||||
console.log('Current value:', signal.valueOf());
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
el("div", { className: "warning" }).append(
|
||||
el("p").append(T`
|
||||
${el("code", "signal.get")} is OK, but in some situations may lead to unexpected results:
|
||||
@ -49,7 +48,7 @@ export function page({ pkg, info }){
|
||||
// but typically this is fine ↓
|
||||
return signal.get() + 1;
|
||||
});
|
||||
` })
|
||||
`, language: "js" })
|
||||
),
|
||||
el("p").append(T`
|
||||
You can also monitor signal changes by adding a listener:
|
||||
@ -57,7 +56,7 @@ export function page({ pkg, info }){
|
||||
el(code, { content: `
|
||||
// Log every time the signal changes
|
||||
S.on(signal, value => console.log('Signal changed:', value));
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("h4", t`Debugging derived signals`),
|
||||
el("p").append(T`
|
||||
@ -69,7 +68,7 @@ export function page({ pkg, info }){
|
||||
el("li", t`Add logging/debugger inside the computation function to see when it runs`),
|
||||
el("li", t`Verify that the computation function actually accesses the signal values with .get()`)
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/debugging/consoleLog.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/debugging/consoleLog.js") }),
|
||||
|
||||
el("h4", t`Examining signal via DevTools`),
|
||||
el("p").append(T`
|
||||
@ -77,11 +76,11 @@ export function page({ pkg, info }){
|
||||
signal objects. It contains the following information:
|
||||
`),
|
||||
el("ul").append(
|
||||
// TODO: value?
|
||||
el("li", t`listeners: A Set of functions called when the signal value changes`),
|
||||
el("li", t`actions: Custom actions that can be performed on the signal`),
|
||||
el("li", t`onclear: Functions to run when the signal is cleared`),
|
||||
el("li", t`host: Reference to the host element/scope in which the signal was created`),
|
||||
el("li", t`readonly: Boolean flag indicating if the signal is read-only`)
|
||||
),
|
||||
el("p").append(T`
|
||||
…to determine the current value of the signal, call ${el("code", "signal.valueOf()")}. Don’t hesitate to
|
||||
@ -121,7 +120,7 @@ export function page({ pkg, info }){
|
||||
"S.el(S(()=> count.get() % 2), odd=> …)")}).
|
||||
`),
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/debugging/mutations.js") }),
|
||||
|
||||
el("h4", t`Memory leaks with signal listeners`),
|
||||
el("p").append(T`
|
||||
@ -137,7 +136,7 @@ export function page({ pkg, info }){
|
||||
el("li", t`Make sure derived signals don’t perform expensive calculations unnecessarily`),
|
||||
el("li", t`Keep signal computations focused and minimal`)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/debugging/debouncing.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/debugging/debouncing.js") }),
|
||||
|
||||
el(h3, t`Browser DevTools tips for components and reactivity`),
|
||||
el("p").append(T`
|
||||
@ -150,7 +149,7 @@ export function page({ pkg, info }){
|
||||
that are automatically updated when signal values change. These elements are wrapped in special
|
||||
comment nodes for debugging (to be true they are also used internally, so please do not edit them by hand):
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/debugging/dom-reactive-mark.html"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/debugging/dom-reactive-mark.html") }),
|
||||
el("p").append(T`
|
||||
This is particularly useful when debugging why a reactive section isn’t updating as expected.
|
||||
You can inspect the elements between the comment nodes to see their current state and the
|
||||
@ -187,7 +186,7 @@ export function page({ pkg, info }){
|
||||
so you can see the element and property that changes in the console right away. These properties make it
|
||||
easier to understand the reactive structure of your application when inspecting elements.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/signals/debugging-dom.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/signals/debugging-dom.js") }),
|
||||
|
||||
el("p", { className: "note" }).append(T`
|
||||
${el("code", "<element>.__dde_reactive")} - An array property on DOM elements that tracks signal-to-element
|
||||
|
@ -14,7 +14,6 @@ const fileURL= url=> new URL(url, import.meta.url);
|
||||
|
||||
/** @param {import("./types.js").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("p").append(T`
|
||||
dd<el> is designed with extensibility in mind. This page covers how to separate
|
||||
@ -49,7 +48,7 @@ export function page({ pkg, info }){
|
||||
|
||||
// Using an addon
|
||||
el("div", { id: "example" }, myAddon({ option: "value" }));
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el(h3, t`Resource Cleanup with Abort Signals`),
|
||||
el("p").append(T`
|
||||
@ -83,7 +82,7 @@ export function page({ pkg, info }){
|
||||
const { signal }= scope;
|
||||
return el("div", null, externalLibraryAddon({ option: "value" }, signal));
|
||||
}
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el(h3, t`Building Library-Independent Extensions`),
|
||||
el("p").append(T`
|
||||
@ -104,7 +103,7 @@ export function page({ pkg, info }){
|
||||
});
|
||||
};
|
||||
}
|
||||
`, page_id })
|
||||
`, language: "js" })
|
||||
),
|
||||
el("div", { className: "tab" }).append(
|
||||
el("h5", t`⚠️ Library-Dependent`),
|
||||
@ -118,7 +117,7 @@ export function page({ pkg, info }){
|
||||
})(element);
|
||||
};
|
||||
}
|
||||
`, page_id })
|
||||
`, language: "js" })
|
||||
)
|
||||
)
|
||||
),
|
||||
@ -177,7 +176,7 @@ export function page({ pkg, info }){
|
||||
textContent: "All"
|
||||
})
|
||||
);
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("div", { className: "callout" }).append(
|
||||
el("h4", t`Benefits of Signal Factories`),
|
||||
@ -217,7 +216,7 @@ export function page({ pkg, info }){
|
||||
const counter = createEnhancedSignal(0);
|
||||
el("button", { textContent: "Increment", onclick: () => counter.increment() });
|
||||
el("div", S.text\`Count: \${counter}\`);
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("div", { className: "tip" }).append(
|
||||
el("p").append(T`
|
||||
@ -259,7 +258,7 @@ export function page({ pkg, info }){
|
||||
// Update signal value
|
||||
count.set(5); // Logs: 5
|
||||
console.log(doubled.get()); // 10
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
el("p").append(T`
|
||||
The independent signals API includes all core functionality (${el("code", "S()")}, ${el("code", "S.on()")},
|
||||
${el("code", "S.action()")}).
|
||||
|
@ -43,7 +43,6 @@ const references= {
|
||||
|
||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("p").append(T`
|
||||
As your applications grow, performance becomes increasingly important. dd<el> provides several
|
||||
@ -60,7 +59,7 @@ export function page({ pkg, info }){
|
||||
el("li", t`Simple debugging for performance bottlenecks`)
|
||||
)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/optimization/intro.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/optimization/intro.js") }),
|
||||
|
||||
el(h3, t`Memoization with memo: Native vs dd<el>`),
|
||||
el("p").append(T`
|
||||
@ -84,7 +83,7 @@ export function page({ pkg, info }){
|
||||
))
|
||||
);
|
||||
}
|
||||
`, page_id })
|
||||
`, language: "js" })
|
||||
),
|
||||
el("div").append(
|
||||
el("h5", t`With dd<el>'s memo`),
|
||||
@ -102,7 +101,7 @@ export function page({ pkg, info }){
|
||||
)))
|
||||
);
|
||||
}
|
||||
`, page_id })
|
||||
`, language: "js" })
|
||||
)
|
||||
)
|
||||
),
|
||||
@ -134,7 +133,7 @@ export function page({ pkg, info }){
|
||||
memo(todo.id, () =>
|
||||
el(TodoItem, todo)
|
||||
))))
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("p").append(T`
|
||||
The ${el("code", "memo")} function in this context:
|
||||
@ -146,7 +145,7 @@ export function page({ pkg, info }){
|
||||
el("li", t`Only calls the generator function when rendering an item with a new key`)
|
||||
),
|
||||
|
||||
el(example, { src: fileURL("./components/examples/optimization/memo.js"), page_id }),
|
||||
el(example, { src: fileURL("./components/examples/optimization/memo.js") }),
|
||||
|
||||
el(h3, t`Creating Memoization Scopes`),
|
||||
el("p").append(T`
|
||||
@ -171,7 +170,7 @@ export function page({ pkg, info }){
|
||||
const container = el("div").append(
|
||||
...items.map(item => renderItem(item))
|
||||
);
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("p").append(T`
|
||||
The scope function accepts options to customize its behavior:
|
||||
@ -188,7 +187,7 @@ export function page({ pkg, info }){
|
||||
// Clear cache when signal is aborted
|
||||
signal: controller.signal
|
||||
});
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
el("p").append(T`
|
||||
You can use custom memo scope as function in (e. g. ${el("code", "S.el(signal, renderList)")}) and as
|
||||
(Abort) signal use ${el("code", "scope.signal")}.
|
||||
@ -317,7 +316,7 @@ export function page({ pkg, info }){
|
||||
|
||||
// On subsequent renders, the cached fragment is empty!
|
||||
container.append(memoizedFragment); // Nothing gets appended
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("p").append(T`
|
||||
This happens because a DocumentFragment is emptied when it's appended to the DOM. When the fragment
|
||||
@ -338,7 +337,7 @@ export function page({ pkg, info }){
|
||||
S.el(itemsSignal, items => items.map(item => el("div", item)))
|
||||
)
|
||||
);
|
||||
`, page_id })
|
||||
`, language: "js" })
|
||||
),
|
||||
|
||||
el("p").append(T`
|
||||
|
@ -43,7 +43,6 @@ const references= {
|
||||
|
||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("p").append(T`
|
||||
${el("a", references.todomvc).append("TodoMVC")} is a project that helps developers compare different
|
||||
@ -69,7 +68,7 @@ export function page({ pkg, info }){
|
||||
challenges in a clean, maintainable way.
|
||||
`),
|
||||
|
||||
el(example, { src: fileURL("./components/examples/reallife/todomvc.js"), variant: "big", page_id }),
|
||||
el(example, { src: fileURL("./components/examples/reallife/todomvc.js"), variant: "big" }),
|
||||
|
||||
el(h3, t`Application Architecture Overview`),
|
||||
el("p").append(T`
|
||||
@ -118,7 +117,7 @@ export function page({ pkg, info }){
|
||||
});
|
||||
});
|
||||
const todosRemainingS = S(()=> todosS.get().filter(todo => !todo.completed).length);
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("p").append(T`
|
||||
The ${el("code", "todosSignal")} function creates a custom signal with actions for manipulating the todos:
|
||||
@ -207,7 +206,7 @@ export function page({ pkg, info }){
|
||||
});
|
||||
return out;
|
||||
}
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("div", { className: "note" }).append(
|
||||
el("p").append(T`
|
||||
@ -241,7 +240,7 @@ export function page({ pkg, info }){
|
||||
memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit)))
|
||||
)
|
||||
)
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("p").append(T`
|
||||
The derived signal automatically recalculates whenever either the todos list or the current filter changes,
|
||||
@ -263,7 +262,7 @@ export function page({ pkg, info }){
|
||||
type: "checkbox"
|
||||
}, onToggleAll),
|
||||
el("label", { htmlFor: "toggle-all", title: "Mark all as complete" }),
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("p").append(T`
|
||||
The "toggle all" checkbox allows users to mark all todos as completed or active. When the checkbox
|
||||
@ -300,7 +299,7 @@ export function page({ pkg, info }){
|
||||
// Component content...
|
||||
);
|
||||
}
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("p").append(T`
|
||||
The TodoItem component maintains its own local UI state with signals, providing immediate
|
||||
@ -320,7 +319,7 @@ export function page({ pkg, info }){
|
||||
el("li", {
|
||||
classList: { completed: isCompleted, editing: isEditing }
|
||||
})
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("div", { className: "tip" }).append(
|
||||
el("p").append(T`
|
||||
@ -341,7 +340,7 @@ export function page({ pkg, info }){
|
||||
memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit)))
|
||||
)
|
||||
)
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("p").append(T`
|
||||
This approach ensures that:
|
||||
@ -369,7 +368,7 @@ export function page({ pkg, info }){
|
||||
// …
|
||||
)
|
||||
))
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("p").append(T`
|
||||
We memoize the UI section and uses derived signal for the classList. Re-rendering this part is therefore
|
||||
@ -400,7 +399,7 @@ export function page({ pkg, info }){
|
||||
S.action(todosS, "delete", /** @type {{ detail: Todo["id"] }} */(ev).detail));
|
||||
const onEdit = on("todo:edit", ev =>
|
||||
S.action(todosS, "edit", /** @type {{ detail: Partial<Todo> & { id: Todo["id"] } }} */(ev).detail));
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("h4", t`2. The TodoItem Component with Scopes and Local State`),
|
||||
el("p").append(T`
|
||||
@ -443,7 +442,7 @@ export function page({ pkg, info }){
|
||||
|
||||
// Component implementation...
|
||||
}
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("div", { className: "tip" }).append(
|
||||
el("p").append(T`
|
||||
@ -464,7 +463,7 @@ export function page({ pkg, info }){
|
||||
}).append(
|
||||
// Component content...
|
||||
);
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("p").append(T`
|
||||
Benefits of using ${el("code", "classList")}:
|
||||
@ -503,7 +502,7 @@ export function page({ pkg, info }){
|
||||
value: title,
|
||||
"data-id": id
|
||||
}, onBlurEdit, onKeyDown, addFocus)
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("p").append(T`
|
||||
This approach offers several advantages:
|
||||
@ -536,7 +535,7 @@ export function page({ pkg, info }){
|
||||
// Main content with toggle all and todo list
|
||||
)
|
||||
)
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("h4", t`Conditional Edit Form`),
|
||||
el(code, { content: `
|
||||
@ -550,7 +549,7 @@ export function page({ pkg, info }){
|
||||
}, onBlurEdit, onKeyDown, addFocus)
|
||||
)
|
||||
)
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("h4", t`Conditional Clear Completed Button`),
|
||||
el(code, { content: `
|
||||
@ -561,7 +560,7 @@ export function page({ pkg, info }){
|
||||
{ textContent: "Clear completed", className: "clear-completed" },
|
||||
onClearCompleted)
|
||||
)
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("div", { className: "note" }).append(
|
||||
el("p").append(T`
|
||||
@ -607,7 +606,7 @@ export function page({ pkg, info }){
|
||||
if (event.key !== "Escape") return;
|
||||
isEditing.set(false);
|
||||
});
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("div", { className: "tip" }).append(
|
||||
el("p").append(T`
|
||||
|
@ -14,7 +14,6 @@ const fileURL= url=> new URL(url, import.meta.url);
|
||||
|
||||
/** @param {import("./types.js").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("div", { className: "warning" }).append(
|
||||
el("p").append(T`
|
||||
@ -45,7 +44,7 @@ export function page({ pkg, info }){
|
||||
than jsdom
|
||||
`),
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/ssr/intro.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/ssr/intro.js") }),
|
||||
|
||||
el(h3, t`Why Server-Side Rendering?`),
|
||||
el("p").append(T`
|
||||
@ -71,27 +70,27 @@ export function page({ pkg, info }){
|
||||
el("li", t`Provides a promise queue system for managing async operations during rendering`),
|
||||
el("li", t`Handles DOM property/attribute mapping differences between browsers and jsdom`)
|
||||
),
|
||||
el(code, { src: fileURL("./components/examples/ssr/start.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/ssr/start.js") }),
|
||||
|
||||
el(h3, t`Basic SSR Example`),
|
||||
el("p").append(T`
|
||||
Here’s a simple example of how to use dd<el> for server-side rendering in a Node.js script:
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/ssr/basic-example.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/ssr/basic-example.js") }),
|
||||
|
||||
el(h3, t`Building a Static Site Generator`),
|
||||
el("p").append(T`
|
||||
You can build a complete static site generator with dd<el>. In fact, this documentation site
|
||||
is built using dd<el> for server-side rendering! Here’s how the documentation build process works:
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js") }),
|
||||
|
||||
el(h3, t`Working with Async Content in SSR`),
|
||||
el("p").append(T`
|
||||
The jsdom export includes a queue system to handle asynchronous operations during rendering.
|
||||
This is crucial for components that fetch data or perform other async tasks.
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/ssr/async-data.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/ssr/async-data.js") }),
|
||||
|
||||
el(h3, t`Working with Dynamic Imports for SSR`),
|
||||
el("p").append(T`
|
||||
@ -119,7 +118,7 @@ export function page({ pkg, info }){
|
||||
el("p").append(T`
|
||||
Follow this pattern when creating server-side rendered pages:
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/ssr/pages.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/ssr/pages.js") }),
|
||||
|
||||
el(h3, t`SSR Considerations and Limitations`),
|
||||
el("p").append(T`
|
||||
@ -141,7 +140,7 @@ export function page({ pkg, info }){
|
||||
This documentation site itself is built using dd<el>’s SSR capabilities.
|
||||
The build process collects all page components, renders them with jsdom, and outputs static HTML files.
|
||||
`),
|
||||
el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js"), page_id }),
|
||||
el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js") }),
|
||||
|
||||
el("p").append(T`
|
||||
The resulting static files can be deployed to any static hosting service,
|
||||
|
@ -15,7 +15,6 @@ const fileURL= url=> new URL(url, import.meta.url);
|
||||
|
||||
/** @param {import("./types.js").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("div", { className: "warning" }).append(
|
||||
el("p").append(T`
|
||||
@ -66,7 +65,7 @@ export function page({ pkg, info }){
|
||||
src: fileURL("./components/examples/path/to/component.js"),
|
||||
exportName: "NamedExport", // optional, defaults to "default",
|
||||
})
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el("p").append(T`
|
||||
During the build process (${el("code", "bs/docs.js")}), the following happens:
|
||||
@ -118,7 +117,7 @@ export function page({ pkg, info }){
|
||||
|
||||
// Final build step - trigger SSR end event
|
||||
dispatchEvent("onssrend");
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
el("h4", t`File Registration`),
|
||||
el(code, { content: `
|
||||
// From docs/ssr.js - File registration system
|
||||
@ -144,7 +143,7 @@ export function page({ pkg, info }){
|
||||
head[head instanceof HTMLScriptElement ? "src" : "href"] = file_name;
|
||||
document.head.append(head);
|
||||
}
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
el("h4", t`Server-Side Rendering`),
|
||||
el(code, { content: `
|
||||
// From docs/components/ireland.html.js - Server-side component implementation
|
||||
@ -225,7 +224,7 @@ export function page({ pkg, info }){
|
||||
\`.trim())
|
||||
);
|
||||
}
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
el("h4", t`Client-Side Hydration`),
|
||||
el(code, { content: `
|
||||
// From docs/components/ireland.js.js - Client-side hydration
|
||||
@ -249,7 +248,7 @@ export function page({ pkg, info }){
|
||||
});
|
||||
});
|
||||
}
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el(h3, t`Live Example`),
|
||||
el("p").append(T`
|
||||
@ -258,14 +257,10 @@ export function page({ pkg, info }){
|
||||
rendered with the Ireland component system:
|
||||
`),
|
||||
|
||||
el(code, {
|
||||
src: fileURL("./components/examples/ireland-test/counter.js"),
|
||||
page_id
|
||||
}),
|
||||
el(code, { src: fileURL("./components/examples/ireland-test/counter.js") }),
|
||||
el(ireland, {
|
||||
src: fileURL("./components/examples/ireland-test/counter.js"),
|
||||
exportName: "CounterStandard",
|
||||
page_id
|
||||
}),
|
||||
|
||||
el("p").append(T`
|
||||
|
@ -40,7 +40,6 @@ const references= {
|
||||
|
||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("p").append(T`
|
||||
This reference guide provides a comprehensive summary of dd<el>’s key concepts, best practices,
|
||||
@ -166,7 +165,7 @@ export function page({ pkg, info }){
|
||||
className: S(() => countS.get() > 10 ? 'warning' : '')
|
||||
})
|
||||
);
|
||||
`, page_id }),
|
||||
`, language: "js" }),
|
||||
|
||||
el(h3, t`Key Concepts Reference`),
|
||||
|
||||
|
@ -13,7 +13,6 @@ import { converter } from "./components/converter.html.js";
|
||||
|
||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("p").append(T`
|
||||
Transitioning from HTML to dd<el> is simple with our interactive converter. This tool helps you quickly
|
||||
@ -28,7 +27,7 @@ export function page({ pkg, info }){
|
||||
),
|
||||
|
||||
// The actual converter component
|
||||
el(converter, { page_id }),
|
||||
el(converter),
|
||||
|
||||
el(h3, t`Next Steps`),
|
||||
el("p").append(T`
|
||||
|
@ -14,7 +14,6 @@ const fileURL= url=> new URL(url, import.meta.url);
|
||||
|
||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||
export function page({ pkg, info }){
|
||||
const page_id= info.id;
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("p").append(T`
|
||||
Real-world application examples showcasing how to build complete, production-ready interfaces with dd<el>:
|
||||
@ -25,14 +24,14 @@ export function page({ pkg, info }){
|
||||
third-party charting library, data fetching and state management, responsive layout design, and multiple
|
||||
interactive components working together.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/case-studies/data-dashboard.js"), variant: "big", page_id }),
|
||||
el(example, { src: fileURL("./components/examples/case-studies/data-dashboard.js"), variant: "big" }),
|
||||
|
||||
el(h3, t`Interactive Form`),
|
||||
el("p").append(T`
|
||||
Complete form with real-time validation, conditional rendering, and responsive design. Form handling with
|
||||
real-time validation, reactive UI updates, complex form state management, and clean separation of concerns.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/case-studies/interactive-form.js"), variant: "big", page_id }),
|
||||
el(example, { src: fileURL("./components/examples/case-studies/interactive-form.js"), variant: "big" }),
|
||||
|
||||
|
||||
el(h3, t`Interactive Image Gallery`),
|
||||
@ -40,7 +39,7 @@ export function page({ pkg, info }){
|
||||
Responsive image gallery with lightbox, keyboard navigation, and filtering. Dynamic loading of content,
|
||||
lightbox functionality, animation handling, and keyboard and gesture navigation support.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/case-studies/image-gallery.js"), variant: "big", page_id }),
|
||||
el(example, { src: fileURL("./components/examples/case-studies/image-gallery.js"), variant: "big" }),
|
||||
|
||||
|
||||
el(h3, t`Task Manager`),
|
||||
@ -49,7 +48,7 @@ export function page({ pkg, info }){
|
||||
with signals, drag and drop functionality, local storage persistence, and responsive design for different
|
||||
devices.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/case-studies/task-manager.js"), variant: "big", page_id }),
|
||||
el(example, { src: fileURL("./components/examples/case-studies/task-manager.js"), variant: "big" }),
|
||||
|
||||
|
||||
el(h3, t`TodoMVC`),
|
||||
|
@ -1,4 +1,8 @@
|
||||
export { t } from "./utils/index.js";
|
||||
/** @type {string} */
|
||||
export let page_id;
|
||||
/** @param {string} id */
|
||||
export function currentPageId(id){ page_id= id; }
|
||||
export const path_target= {
|
||||
root: "dist/docs/",
|
||||
css: "dist/docs/",
|
||||
|
@ -2,14 +2,13 @@ import { queueSignalWrite, mark } from "./helpers.js";
|
||||
export { mark };
|
||||
import { hasOwn, oCreate, oAssign, requestIdle } from "../helpers.js";
|
||||
|
||||
const Signal = oCreate(null, {
|
||||
const SignalReadOnly= oCreate(null, {
|
||||
get: { value(){ return read(this); } },
|
||||
set: { value(...v){ return write(this, ...v); } },
|
||||
toJSON: { value(){ return read(this); } },
|
||||
valueOf: { value(){ return this[mark] && this[mark].value; } }
|
||||
});
|
||||
const SignalReadOnly= oCreate(Signal, {
|
||||
set: { value(){ return; } },
|
||||
const Signal = oCreate(SignalReadOnly, {
|
||||
set: { value(...v){ return write(this, ...v); } },
|
||||
});
|
||||
/**
|
||||
* Checks if a value is a signal
|
||||
@ -314,9 +313,14 @@ export const signals_config= {
|
||||
function removeSignalsFromElements(s, listener, ...notes){
|
||||
const { current }= scope;
|
||||
current.host(function(element){
|
||||
if(!element[key_reactive]) element[key_reactive]= [];
|
||||
const is_first= !element[key_reactive];
|
||||
if(is_first) element[key_reactive]= [];
|
||||
element[key_reactive].push([ [ s, listener ], ...notes ]);
|
||||
if(current.prevent) return; // typically document.body, doenst need auto-remove as it should happen on page leave
|
||||
if(
|
||||
!is_first
|
||||
// typically document.body, doenst need auto-remove as it should happen on page leave
|
||||
|| current.prevent
|
||||
) return;
|
||||
on.disconnected(()=>
|
||||
/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, …?).
|
||||
You can investigate the `__dde_reactive` key of the element. */
|
||||
@ -344,7 +348,7 @@ const cleanUpRegistry = new FinalizationRegistry(function(s){
|
||||
*/
|
||||
function create(is_readonly, value, actions){
|
||||
const varS = oCreate(is_readonly ? SignalReadOnly : Signal);
|
||||
const SI= toSignal(varS, value, actions, is_readonly);
|
||||
const SI= toSignal(varS, value, actions);
|
||||
cleanUpRegistry.register(SI, SI[mark]);
|
||||
return SI;
|
||||
}
|
||||
@ -367,11 +371,10 @@ const protoSigal= oAssign(oCreate(), {
|
||||
* @param {Object} s - Object to transform
|
||||
* @param {any} value - Initial value
|
||||
* @param {Object} actions - Custom actions
|
||||
* @param {boolean} [readonly=false] - Whether the signal is readonly
|
||||
* @returns {Object} Signal object with get() and set() methods
|
||||
* @private
|
||||
*/
|
||||
function toSignal(s, value, actions, readonly= false){
|
||||
function toSignal(s, value, actions){
|
||||
const onclear= [];
|
||||
if(typeOf(actions)!=="[object Object]")
|
||||
actions= {};
|
||||
@ -385,7 +388,6 @@ function toSignal(s, value, actions, readonly= false){
|
||||
value: oAssign(oCreate(protoSigal), {
|
||||
value, actions, onclear, host,
|
||||
listeners: new Set(),
|
||||
readonly
|
||||
}),
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
|
Loading…
x
Reference in New Issue
Block a user