mirror of
				https://github.com/jaandrle/deka-dom-el
				synced 2025-11-03 22:59:16 +01:00 
			
		
		
		
	🔤 updates texts
This commit is contained in:
		@@ -195,7 +195,7 @@ export function code({ id, src, content, language= "js", className= host.slice(1
 | 
			
		||||
		dataJS= "todo";
 | 
			
		||||
	}
 | 
			
		||||
	return el("div", { id, className, dataJS }).append(
 | 
			
		||||
		el("code", { className: "language-"+language, textContent: content })
 | 
			
		||||
		el("code", { className: "language-"+language, textContent: content.trim() })
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
let is_registered= {};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,35 +1,26 @@
 | 
			
		||||
/* PSEUDO-CODE!!! */
 | 
			
		||||
import { el } from "deka-dom-el";
 | 
			
		||||
import { S } from "deka-dom-el/signals";
 | 
			
		||||
function component(){
 | 
			
		||||
	/* prepare changeable data */
 | 
			
		||||
	const dataA= S("data");
 | 
			
		||||
	const dataB= S("data");
 | 
			
		||||
	/* define data flow (can be asynchronous) */
 | 
			
		||||
	fetchAPI().then(data_new=> dataA(data_new));
 | 
			
		||||
	setTimeout(()=> dataB("DATA"));
 | 
			
		||||
	/* declarative UI */
 | 
			
		||||
	return el().append(
 | 
			
		||||
		el("h1", {
 | 
			
		||||
			textContent: "Example",
 | 
			
		||||
			/* declarative attribute(s) */
 | 
			
		||||
			classList: { declarative: dataB }
 | 
			
		||||
		}),
 | 
			
		||||
		el("ul").append(
 | 
			
		||||
			/* declarative element(s) */
 | 
			
		||||
			S.el(dataA, data=> data.map(d=> el("li", d)))
 | 
			
		||||
		),
 | 
			
		||||
		el("ul").append(
 | 
			
		||||
			/* declarative component(s) */
 | 
			
		||||
			S.el(dataA, data=> data.map(d=> el(subcomponent, d)))
 | 
			
		||||
		)
 | 
			
		||||
function Counter() {
 | 
			
		||||
	// Define state
 | 
			
		||||
	const count = S(0);
 | 
			
		||||
 | 
			
		||||
	// Define behavior
 | 
			
		||||
	const increment = () => count.set(count.get() + 1);
 | 
			
		||||
 | 
			
		||||
	// Define data flow
 | 
			
		||||
	setTimeout(increment, 1000);
 | 
			
		||||
	// or fetchAPI().then(increment);
 | 
			
		||||
 | 
			
		||||
	// Declarative UI (how to render data/`count`)
 | 
			
		||||
	// …automatically updates when changes
 | 
			
		||||
	return el("div").append(
 | 
			
		||||
		// declarative element(s)
 | 
			
		||||
		el("p", S(() => "Count: " + count.get())),
 | 
			
		||||
		el("button", {
 | 
			
		||||
			onclick: increment,
 | 
			
		||||
			textContent: "Increment",
 | 
			
		||||
			// declarative attribute(s)
 | 
			
		||||
			disabled: S(() => count.get() >= 10)
 | 
			
		||||
		})
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
function subcomponent({ id }){
 | 
			
		||||
	/* prepare changeable data */
 | 
			
		||||
	const textContent= S("…");
 | 
			
		||||
	/* define data flow (can be asynchronous) */
 | 
			
		||||
	fetchAPI(id).then(text=> textContent(text));
 | 
			
		||||
	/* declarative UI */
 | 
			
		||||
	return el("li", { textContent, dataId: id });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,31 +1,25 @@
 | 
			
		||||
/* PSEUDO-CODE!!! */
 | 
			
		||||
import { el, on, scope } from "deka-dom-el";
 | 
			
		||||
function component(){
 | 
			
		||||
	const { host }= scope;
 | 
			
		||||
	const ul= el("ul");
 | 
			
		||||
	const ac= new AbortController();
 | 
			
		||||
	fetchAPI({ signal: ac.signal }).then(data=> {
 | 
			
		||||
		data.forEach(d=> ul.append(el("li", d)));
 | 
			
		||||
	});
 | 
			
		||||
	host(
 | 
			
		||||
		/* element was remove before data fetched */
 | 
			
		||||
		on.disconnected(()=> ac.abort())
 | 
			
		||||
import { el, scope } from "deka-dom-el";
 | 
			
		||||
function Counter() {
 | 
			
		||||
	const { host } = scope;
 | 
			
		||||
 | 
			
		||||
	let count = 0;
 | 
			
		||||
	const counterText = el("p", "Count: 0");
 | 
			
		||||
 | 
			
		||||
	// Manually update DOM element
 | 
			
		||||
	const increment = () => {
 | 
			
		||||
		count++;
 | 
			
		||||
		counterText.textContent = "Count: " + count;
 | 
			
		||||
		host().querySelector("button").disabled = count >= 10;
 | 
			
		||||
	};
 | 
			
		||||
	setTimeout(increment, 1000);
 | 
			
		||||
	// or fetchAPI().then(increment);
 | 
			
		||||
 | 
			
		||||
	return el("div").append(
 | 
			
		||||
		counterText,
 | 
			
		||||
		el("button", {
 | 
			
		||||
			onclick: increment,
 | 
			
		||||
			textContent: "Increment"
 | 
			
		||||
		})
 | 
			
		||||
	);
 | 
			
		||||
	return ul;
 | 
			
		||||
	/**
 | 
			
		||||
	 * NEVER EVER!!
 | 
			
		||||
	 * let data;
 | 
			
		||||
	 * fetchAPI().then(d=> data= O(d));
 | 
			
		||||
	 *
 | 
			
		||||
	 * OR NEVER EVER!!
 | 
			
		||||
	 * const ul= el("ul");
 | 
			
		||||
	 * fetchAPI().then(d=> {
 | 
			
		||||
	 *	const data= O("data");
 | 
			
		||||
	 *	ul.append(el("li", data));
 | 
			
		||||
	 * });
 | 
			
		||||
	 *
 | 
			
		||||
	 * // THE HOST IS PROBABLY DIFFERENT THAN
 | 
			
		||||
	 * // YOU EXPECT AND OBSERVABLES MAY BE
 | 
			
		||||
	 * // UNEXPECTEDLY REMOVED!!!
 | 
			
		||||
	 * */
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										34
									
								
								docs/components/examples/scopes/mixed.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								docs/components/examples/scopes/mixed.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
/* PSEUDO-CODE!!! */
 | 
			
		||||
import { el, scope } from "deka-dom-el";
 | 
			
		||||
import { S } from "deka-dom-el/signals";
 | 
			
		||||
function Counter() {
 | 
			
		||||
	const { host } = scope;
 | 
			
		||||
 | 
			
		||||
	let count = S(0);
 | 
			
		||||
	const counterText = el("p", "Count: 0");
 | 
			
		||||
	S.on(count, c=> counterText.textContent= "Count: " + c);
 | 
			
		||||
 | 
			
		||||
	// Manually update DOM element
 | 
			
		||||
	const increment = () => {
 | 
			
		||||
		count.set(count.get() + 1);
 | 
			
		||||
		// NEVER EVER
 | 
			
		||||
		// count = S(count.get() + 1);
 | 
			
		||||
		host().querySelector("button").disabled = count.get() >= 10;
 | 
			
		||||
	};
 | 
			
		||||
	// NEVER EVER
 | 
			
		||||
	// setTimeout(()=> {
 | 
			
		||||
	//	const wrong= S(0);
 | 
			
		||||
	//  // THE HOST IS PROBABLY DIFFERENT THAN
 | 
			
		||||
	//  // YOU EXPECT AND OBSERVABLES MAY BE
 | 
			
		||||
	//  // UNEXPECTEDLY REMOVED!!!
 | 
			
		||||
	//	counterText.textContent= "Count: " + wrong.get();
 | 
			
		||||
	// }, 1000);
 | 
			
		||||
 | 
			
		||||
	return el("div").append(
 | 
			
		||||
		counterText,
 | 
			
		||||
		el("button", {
 | 
			
		||||
			onclick: increment,
 | 
			
		||||
			textContent: "Increment"
 | 
			
		||||
		})
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
import { S } from "deka-dom-el/signals";
 | 
			
		||||
// α — `signal` represents a reactive value
 | 
			
		||||
// A — `signal` represents a reactive value
 | 
			
		||||
const signal= S(0);
 | 
			
		||||
// β — just reacts on signal changes
 | 
			
		||||
// B — just reacts on signal changes
 | 
			
		||||
S.on(signal, console.log);
 | 
			
		||||
// γ — just updates the value
 | 
			
		||||
// C — just updates the value
 | 
			
		||||
const update= ()=> signal.set(signal.get()+1);
 | 
			
		||||
 | 
			
		||||
update();
 | 
			
		||||
const interval= 5*1000;
 | 
			
		||||
setTimeout(clearInterval, 10*interval,
 | 
			
		||||
	setInterval(update, interval));
 | 
			
		||||
	setInterval(update, interval));
 | 
			
		||||
 
 | 
			
		||||
@@ -208,6 +208,12 @@ pre code {
 | 
			
		||||
	background-color: transparent;
 | 
			
		||||
	padding: 0;
 | 
			
		||||
}
 | 
			
		||||
.illustration:not(:has( .comparison)) pre {
 | 
			
		||||
	background: none;
 | 
			
		||||
	border-style: dashed !important;
 | 
			
		||||
	width: fit-content;
 | 
			
		||||
	padding: 1em 2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Layout */
 | 
			
		||||
@media (min-width: 768px) {
 | 
			
		||||
 
 | 
			
		||||
@@ -55,8 +55,7 @@ export function page({ pkg, info }){
 | 
			
		||||
				el("li", t`Automatic UI updates when data changes`),
 | 
			
		||||
				el("li", t`Clean separation between data, logic, and UI`),
 | 
			
		||||
				el("li", t`Small runtime with minimal overhead`),
 | 
			
		||||
				el("li", t`Works seamlessly with DDE's DOM creation`),
 | 
			
		||||
				el("li", t`No dependencies or framework lock-in`)
 | 
			
		||||
				el("li").append(...T`${el("strong", "In future")} no dependencies or framework lock-in`)
 | 
			
		||||
			)
 | 
			
		||||
		),
 | 
			
		||||
		el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }),
 | 
			
		||||
@@ -68,17 +67,17 @@ export function page({ pkg, info }){
 | 
			
		||||
		`),
 | 
			
		||||
		el("div", { class: "signal-diagram" }).append(
 | 
			
		||||
			el("div", { class: "signal-part" }).append(
 | 
			
		||||
				el("h4", t`α: Create Signal`),
 | 
			
		||||
				el("h4", t`A: Create Signal`),
 | 
			
		||||
				el(code, { content: "const count = S(0);", page_id }),
 | 
			
		||||
				el("p", t`Define a reactive value that can be observed and changed`)
 | 
			
		||||
			),
 | 
			
		||||
			el("div", { class: "signal-part" }).append(
 | 
			
		||||
				el("h4", t`β: React to Changes`),
 | 
			
		||||
				el("h4", t`B: React to Changes`),
 | 
			
		||||
				el(code, { content: "S.on(count, value => updateUI(value));", page_id }),
 | 
			
		||||
				el("p", t`Subscribe to signal changes with callbacks or effects`)
 | 
			
		||||
			),
 | 
			
		||||
			el("div", { class: "signal-part" }).append(
 | 
			
		||||
				el("h4", t`γ: Update Signal`),
 | 
			
		||||
				el("h4", t`C: Update Signal`),
 | 
			
		||||
				el(code, { content: "count.set(count.get() + 1);", page_id }),
 | 
			
		||||
				el("p", t`Modify the signal value, which automatically triggers updates`)
 | 
			
		||||
			)
 | 
			
		||||
@@ -90,7 +89,8 @@ export function page({ pkg, info }){
 | 
			
		||||
				Signals implement the ${el("a", { textContent: t`Publish–subscribe pattern`, ...references.wiki_pubsub })},
 | 
			
		||||
				a form of ${el("a", { textContent: t`Event-driven programming`, ...references.wiki_event_driven })}.
 | 
			
		||||
				This architecture allows different parts of your application to stay synchronized through a shared signal,
 | 
			
		||||
				without direct dependencies on each other.
 | 
			
		||||
				without direct dependencies on each other. Compare for example with ${el("a", { textContent:
 | 
			
		||||
				t`fpubsub library`, ...references.fpubsub })}.
 | 
			
		||||
			`)
 | 
			
		||||
		),
 | 
			
		||||
 | 
			
		||||
@@ -110,7 +110,8 @@ export function page({ pkg, info }){
 | 
			
		||||
				el("dd", t`S.on(signal, callback) → runs callback whenever signal changes`),
 | 
			
		||||
 | 
			
		||||
				el("dt", t`Unsubscribing`),
 | 
			
		||||
				el("dd", t`S.on(signal, callback, { signal: abortController.signal })`)
 | 
			
		||||
				el("dd").append(...T`S.on(signal, callback, { signal: abortController.signal }) → Similarly to the
 | 
			
		||||
					${el("code", "on")} function to register DOM events listener.`)
 | 
			
		||||
			)
 | 
			
		||||
		),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
@@ -139,15 +140,6 @@ export function page({ pkg, info }){
 | 
			
		||||
		el("div", { class: "illustration" }).append(
 | 
			
		||||
			el("h4", t`Actions vs. Direct Mutation`),
 | 
			
		||||
			el("div", { class: "comparison" }).append(
 | 
			
		||||
				el("div", { class: "bad-practice" }).append(
 | 
			
		||||
					el("h5", t`❌ Without Actions`),
 | 
			
		||||
					el(code, { content: `
 | 
			
		||||
const todos = S([]);
 | 
			
		||||
// Directly mutating the array
 | 
			
		||||
const items = todos.get();
 | 
			
		||||
items.push("New todo");
 | 
			
		||||
// This WON'T trigger updates!`, page_id }))
 | 
			
		||||
				),
 | 
			
		||||
				el("div", { class: "good-practice" }).append(
 | 
			
		||||
					el("h5", t`✅ With Actions`),
 | 
			
		||||
					el(code, { content: `const todos = S([], {
 | 
			
		||||
@@ -159,8 +151,25 @@ items.push("New todo");
 | 
			
		||||
 | 
			
		||||
// Use the action
 | 
			
		||||
S.action(todos, "add", "New todo");`, page_id })
 | 
			
		||||
				)
 | 
			
		||||
				),
 | 
			
		||||
				el("div", { class: "bad-practice" }).append(
 | 
			
		||||
					el("h5", t`❌ Without Actions`),
 | 
			
		||||
					el(code, { content: `
 | 
			
		||||
const todos = S([]);
 | 
			
		||||
// Directly mutating the array
 | 
			
		||||
const items = todos.get();
 | 
			
		||||
items.push("New todo");
 | 
			
		||||
// This WON'T trigger updates!`, page_id }))
 | 
			
		||||
				),
 | 
			
		||||
		),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			In some way, you can compare it with ${el("a", { textContent: "useReducer", ...references.mdn_use_reducer })}
 | 
			
		||||
			hook from React. So, the ${el("code", "S(<data>, <actions>)")} pattern creates a store “machine”. We can
 | 
			
		||||
			then invoke (dispatch) registered action by calling ${el("code", "S.action(<signal>, <name>, ...<args>)")}
 | 
			
		||||
			after the action call the signal calls all its listeners. This can be stopped by calling
 | 
			
		||||
			${el("code", "this.stopPropagation()")} in the method representing the given action. As it can be seen in
 | 
			
		||||
			examples, the “store” value is available also in the function for given action (${el("code", "this.value")}).
 | 
			
		||||
		`),
 | 
			
		||||
		el(example, { src: fileURL("./components/examples/signals/actions-demo.js"), page_id }),
 | 
			
		||||
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
@@ -182,9 +191,7 @@ S.action(todos, "add", "New todo");`, page_id })
 | 
			
		||||
				${el("strong", "Special Action Methods")}: Signal actions can implement special lifecycle hooks:
 | 
			
		||||
			`),
 | 
			
		||||
			el("ul").append(
 | 
			
		||||
				el("li", t`[S.symbols.onclear]() - Called when the signal is cleared`),
 | 
			
		||||
				el("li", t`[S.symbols.onget]() - Called when the signal value is read`),
 | 
			
		||||
				el("li", t`[S.symbols.onset]() - Called after the signal value is changed`)
 | 
			
		||||
				el("li", t`[S.symbols.onclear]() - Called when the signal is cleared. Use it to clean up resources.`),
 | 
			
		||||
			)
 | 
			
		||||
		),
 | 
			
		||||
 | 
			
		||||
@@ -229,12 +236,12 @@ S.action(items, "push", "Dragonfruit"); // List updates automatically`, page_id
 | 
			
		||||
			)
 | 
			
		||||
		),
 | 
			
		||||
 | 
			
		||||
		el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			The ${el("code", "assign")} and ${el("code", "el")} functions detect signals automatically and handle binding.
 | 
			
		||||
			You can use special properties like ${el("code", "dataset")}, ${el("code", "ariaset")}, and
 | 
			
		||||
			${el("code", "classList")} for fine-grained control over specific attribute types.
 | 
			
		||||
		`),
 | 
			
		||||
		el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }),
 | 
			
		||||
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			${el("code", "S.el()")} is especially powerful for conditional rendering and lists:
 | 
			
		||||
 
 | 
			
		||||
@@ -30,53 +30,44 @@ export function page({ pkg, info }){
 | 
			
		||||
	return el(simplePage, { info, pkg }).append(
 | 
			
		||||
		el("h2", t`Building Maintainable UIs with Scopes and Components`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			Scopes provide a structured way to organize your UI code into reusable components that properly
 | 
			
		||||
			manage their lifecycle, handle cleanup, and maintain clear boundaries between different parts of your application.
 | 
			
		||||
			For state-less components we can use functions as UI components (see “Elements” page). But in real life,
 | 
			
		||||
			we may need to handle the component live-cycle and provide JavaScript the way to properly use
 | 
			
		||||
			the ${el("a", { textContent: t`Garbage collection`, ...references.garbage_collection })}.
 | 
			
		||||
		`),
 | 
			
		||||
		el("div", { className: "callout" }).append(
 | 
			
		||||
			el("h4", t`Why Use Scopes?`),
 | 
			
		||||
			el("ul").append(
 | 
			
		||||
				el("li", t`Automatic resource cleanup when components are removed from DOM`),
 | 
			
		||||
				el("li", t`Clear component boundaries with explicit host elements`),
 | 
			
		||||
				el("li", t`Simplified event handling with proper "this" binding`),
 | 
			
		||||
				el("li", t`Seamless integration with signals for reactive components`),
 | 
			
		||||
				el("li", t`Better memory management with ${el("a", { textContent: t`GC`, ...references.garbage_collection })}`)
 | 
			
		||||
			)
 | 
			
		||||
		),
 | 
			
		||||
		el(code, { src: fileURL("./components/examples/scopes/intro.js"), page_id }),
 | 
			
		||||
		el("p").append(...T`The library therefore use ${el("em", t`scopes`)} to provide these functionalities.`),
 | 
			
		||||
 | 
			
		||||
		el(h3, t`Understanding Host Elements and Scopes`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			The ${el("strong", "host")} is the name for the element representing the component. This is typically
 | 
			
		||||
			element returned by function. To get reference, you can use ${el("code", "scope.host()")} to applly addons
 | 
			
		||||
			just use ${el("code", "scope.host(...<addons>)")}.
 | 
			
		||||
		`),
 | 
			
		||||
		el("div", { className: "illustration" }).append(
 | 
			
		||||
			el("h4", t`Component Anatomy`),
 | 
			
		||||
			el("pre").append(el("code", `
 | 
			
		||||
┌─────────────────────────────────┐
 | 
			
		||||
│ // 1. Component scope created   │
 | 
			
		||||
│ el(MyComponent);                │
 | 
			
		||||
│                                 │
 | 
			
		||||
│ function MyComponent() {        │
 | 
			
		||||
│   // 2. access the host element │
 | 
			
		||||
│   const { host } = scope;       │
 | 
			
		||||
│                                 │
 | 
			
		||||
│   // 3. Add behavior to host    │
 | 
			
		||||
│   host(                         │
 | 
			
		||||
│     on.click(handleClick)       │
 | 
			
		||||
│   );                            │
 | 
			
		||||
│                                 │
 | 
			
		||||
│   // 4. Return the host element │
 | 
			
		||||
│   return el("div", {           │
 | 
			
		||||
│     className: "my-component"   │
 | 
			
		||||
│   }).append(                    │
 | 
			
		||||
│     el("h2", "Title"),          │
 | 
			
		||||
│     el("p", "Content")          │
 | 
			
		||||
│   );                            │
 | 
			
		||||
│ }                               │
 | 
			
		||||
└─────────────────────────────────┘
 | 
			
		||||
			`))
 | 
			
		||||
// 1. Component scope created
 | 
			
		||||
el(MyComponent);
 | 
			
		||||
 | 
			
		||||
function MyComponent() {
 | 
			
		||||
  // 2. access the host element
 | 
			
		||||
  const { host } = scope;
 | 
			
		||||
 | 
			
		||||
  // 3. Add behavior to host
 | 
			
		||||
  host(
 | 
			
		||||
    on.click(handleClick)
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // 4. Return the host element
 | 
			
		||||
  return el("div", {
 | 
			
		||||
    className: "my-component"
 | 
			
		||||
  }).append(
 | 
			
		||||
    el("h2", "Title"),
 | 
			
		||||
    el("p", "Content")
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
			`.trim()))
 | 
			
		||||
		),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			The ${el("strong", "host element")} is the root element of your component - typically the element returned
 | 
			
		||||
			by your component function. It serves as the identity of your component in the DOM.
 | 
			
		||||
		`),
 | 
			
		||||
		el("div", { className: "function-table" }).append(
 | 
			
		||||
			el("h4", t`scope.host()`),
 | 
			
		||||
			el("dl").append(
 | 
			
		||||
@@ -91,34 +82,24 @@ export function page({ pkg, info }){
 | 
			
		||||
 | 
			
		||||
		el("div", { className: "tip" }).append(
 | 
			
		||||
			el("p").append(...T`
 | 
			
		||||
				${el("strong", "Best Practice:")} Always capture the host reference at the beginning of your component function
 | 
			
		||||
				using ${el("code", "const { host } = scope")} to avoid scope-related issues, especially with asynchronous code.
 | 
			
		||||
			`)
 | 
			
		||||
		),
 | 
			
		||||
 | 
			
		||||
		el(h3, t`Class-Based Components`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			While functional components are the primary pattern in DDE, you can also create class-based components
 | 
			
		||||
			for more structured organization of component logic.
 | 
			
		||||
		`),
 | 
			
		||||
		el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }),
 | 
			
		||||
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			This pattern can be useful when:
 | 
			
		||||
		`),
 | 
			
		||||
		el("ul").append(
 | 
			
		||||
			el("li", t`You have complex component logic that benefits from object-oriented organization`),
 | 
			
		||||
			el("li", t`You need private methods and properties for your component`),
 | 
			
		||||
			el("li", t`You're transitioning from another class-based component system`)
 | 
			
		||||
		),
 | 
			
		||||
		el("div", { className: "tip" }).append(
 | 
			
		||||
				${el("strong", "Best Practice:")} Always capture the host reference at the beginning of your component
 | 
			
		||||
				function using ${el("code", "const { host } = scope")} to avoid scope-related issues, especially with
 | 
			
		||||
				asynchronous code.
 | 
			
		||||
			`),
 | 
			
		||||
			el("p").append(...T`
 | 
			
		||||
				${el("strong", "Note:")} Even with class-based components, follow the best practice of storing the host reference
 | 
			
		||||
				early in your component code. This ensures proper access to the host throughout the component's lifecycle.
 | 
			
		||||
				If you are interested in the implementation details, see Class-Based Components section.
 | 
			
		||||
			`)
 | 
			
		||||
		),
 | 
			
		||||
		el(code, { src: fileURL("./components/examples/scopes/good-practise.js"), page_id }),
 | 
			
		||||
 | 
			
		||||
		el(h3, t`Class-Based Components`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			While functional components are the primary pattern in DDE, you can also create class-based components.
 | 
			
		||||
			For this, we implement function ${el("code", "elClass")} and use it to demonstrate implementation details
 | 
			
		||||
			for better understanding of the scope logic.
 | 
			
		||||
		`),
 | 
			
		||||
		el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }),
 | 
			
		||||
 | 
			
		||||
		el(h3, t`Automatic Cleanup with Scopes`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			One of the most powerful features of scopes is automatic cleanup when components are removed from the DOM.
 | 
			
		||||
@@ -149,77 +130,38 @@ export function page({ pkg, info }){
 | 
			
		||||
 | 
			
		||||
		el(h3, t`Declarative vs Imperative Components`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			Scopes work best with a declarative approach to UI building, especially when combined
 | 
			
		||||
			with ${el("a", { textContent: "signals", ...references.signals })} for state management.
 | 
			
		||||
			The library DOM API and signals works best when used declaratively. It means, you split your app logic
 | 
			
		||||
			into three parts as it was itroduced in ${el("a", { textContent: "Signals", ...references.signals })}.
 | 
			
		||||
		`),
 | 
			
		||||
		el("div", { className: "note" }).append(
 | 
			
		||||
			el("p").append(...T`
 | 
			
		||||
				Strictly speaking, the imperative way of using the library is not prohibited. Just be careful (rather avoid)
 | 
			
		||||
				mixing declarative approach (using signals) and imperative manipulation of elements.
 | 
			
		||||
			`)
 | 
			
		||||
		),
 | 
			
		||||
		el("div", { className: "tabs" }).append(
 | 
			
		||||
			el("div", { className: "tab", "data-tab": "declarative" }).append(
 | 
			
		||||
				el("h4", t`✅ Declarative Approach`),
 | 
			
		||||
				el("p", t`Define what your UI should look like based on state:`),
 | 
			
		||||
				el("pre").append(el("code", `function Counter() {
 | 
			
		||||
	const { host } = scope;
 | 
			
		||||
 | 
			
		||||
	// Define state
 | 
			
		||||
	const count = S(0);
 | 
			
		||||
 | 
			
		||||
	// Define behavior
 | 
			
		||||
	const increment = () => count.set(count.get() + 1);
 | 
			
		||||
 | 
			
		||||
	// UI automatically updates when count changes
 | 
			
		||||
	return el("div").append(
 | 
			
		||||
		el("p", S(() => "Count: " + count.get())),
 | 
			
		||||
		el("button", {
 | 
			
		||||
			onclick: increment,
 | 
			
		||||
			textContent: "Increment"
 | 
			
		||||
		})
 | 
			
		||||
	);
 | 
			
		||||
}`))
 | 
			
		||||
				el(code, { src: fileURL("./components/examples/scopes/declarative.js"), page_id }),
 | 
			
		||||
			),
 | 
			
		||||
			el("div", { className: "tab", "data-tab": "imperative" }).append(
 | 
			
		||||
				el("h4", t`⚠️ Imperative Approach`),
 | 
			
		||||
				el("p", t`Manually update the DOM in response to events:`),
 | 
			
		||||
				el("pre").append(el("code", `function Counter() {
 | 
			
		||||
	const { host } = scope;
 | 
			
		||||
 | 
			
		||||
	let count = 0;
 | 
			
		||||
	const counterText = el("p", "Count: 0");
 | 
			
		||||
 | 
			
		||||
	// Manually update DOM element
 | 
			
		||||
	const increment = () => {
 | 
			
		||||
		count++;
 | 
			
		||||
		counterText.textContent = "Count: " + count;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	return el("div").append(
 | 
			
		||||
		counterText,
 | 
			
		||||
		el("button", {
 | 
			
		||||
			onclick: increment,
 | 
			
		||||
			textContent: "Increment"
 | 
			
		||||
		})
 | 
			
		||||
	);
 | 
			
		||||
}`))
 | 
			
		||||
			)
 | 
			
		||||
				el(code, { src: fileURL("./components/examples/scopes/imperative.js"), page_id }),
 | 
			
		||||
			),
 | 
			
		||||
			el("div", { className: "tab", "data-tab": "imperative" }).append(
 | 
			
		||||
				el("h4", t`❌ Mixed Approach`),
 | 
			
		||||
				el("p", t`Just AVOID:`),
 | 
			
		||||
				el(code, { src: fileURL("./components/examples/scopes/mixed.js"), page_id }),
 | 
			
		||||
			),
 | 
			
		||||
		),
 | 
			
		||||
 | 
			
		||||
		el(code, { src: fileURL("./components/examples/scopes/declarative.js"), page_id }),
 | 
			
		||||
 | 
			
		||||
		el("div", { className: "note" }).append(
 | 
			
		||||
			el("p").append(...T`
 | 
			
		||||
				While DDE supports both declarative and imperative approaches, the declarative style is recommended
 | 
			
		||||
				as it leads to more maintainable code with fewer opportunities for bugs. Signals handle the complexity
 | 
			
		||||
				of keeping your UI in sync with your data.
 | 
			
		||||
			`)
 | 
			
		||||
		),
 | 
			
		||||
		el(code, { src: fileURL("./components/examples/scopes/imperative.js"), page_id }),
 | 
			
		||||
 | 
			
		||||
		el(h3, t`Best Practices for Scopes and Components`),
 | 
			
		||||
		el("ol").append(
 | 
			
		||||
			el("li").append(...T`
 | 
			
		||||
				${el("strong", "Capture host early:")} Use ${el("code", "const { host } = scope")} at component start
 | 
			
		||||
			`),
 | 
			
		||||
			el("li").append(...T`
 | 
			
		||||
				${el("strong", "Return a single root element:")} Components should have one host element that contains all others
 | 
			
		||||
			`),
 | 
			
		||||
			el("li").append(...T`
 | 
			
		||||
				${el("strong", "Prefer declarative patterns:")} Use signals to drive UI updates rather than manual DOM manipulation
 | 
			
		||||
			`),
 | 
			
		||||
 
 | 
			
		||||
@@ -201,22 +201,16 @@ export function page({ pkg, info }){
 | 
			
		||||
		el("div", { className: "illustration" }).append(
 | 
			
		||||
			el("h4", t`Shadow DOM Encapsulation`),
 | 
			
		||||
			el("pre").append(el("code", `
 | 
			
		||||
┌─────────────────────────────────┐
 | 
			
		||||
│ <my-custom-element>             │
 | 
			
		||||
│                                 │
 | 
			
		||||
│  ┌─────────────────────────┐    │
 | 
			
		||||
│  │ #shadow-root           │    │
 | 
			
		||||
│  │                         │    │
 | 
			
		||||
│  │  Created with DDE:      │    │
 | 
			
		||||
│  │  ┌─────────────────┐    │    │
 | 
			
		||||
│  │  │ <div>           │    │    │
 | 
			
		||||
│  │  │   <h2>Title</h2> │    │    │
 | 
			
		||||
│  │  │   <p>Content</p> │    │    │
 | 
			
		||||
│  │  └─────────────────┘    │    │
 | 
			
		||||
│  │                         │    │
 | 
			
		||||
│  └─────────────────────────┘    │
 | 
			
		||||
│                                 │
 | 
			
		||||
└─────────────────────────────────┘
 | 
			
		||||
<my-custom-element>
 | 
			
		||||
 | 
			
		||||
  ┌─────────────────────────┐
 | 
			
		||||
    #shadow-root
 | 
			
		||||
 | 
			
		||||
     Created with DDE:
 | 
			
		||||
     ┌──────────────────┐
 | 
			
		||||
       <div>
 | 
			
		||||
         <h2>Title</h2>
 | 
			
		||||
         <p>Content</p>
 | 
			
		||||
			`))
 | 
			
		||||
		),
 | 
			
		||||
		el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js"), page_id }),
 | 
			
		||||
@@ -229,9 +223,10 @@ export function page({ pkg, info }){
 | 
			
		||||
 | 
			
		||||
		el(h3, t`Working with Slots`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			Slots allow users of your component to insert content inside it. When using DDE, you can simulate the
 | 
			
		||||
			slot mechanism with the ${el("code", "simulateSlots")} function:
 | 
			
		||||
			Besides the encapsulation, the Shadow DOM allows for using the ${el("a", references.mdn_shadow_dom_slot).append(
 | 
			
		||||
			el("strong", t`<slot>`), t` element(s)`)}. You can simulate this feature using ${el("code", "simulateSlots")}:
 | 
			
		||||
		`),
 | 
			
		||||
		el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js"), page_id }),
 | 
			
		||||
		el("div", { className: "function-table" }).append(
 | 
			
		||||
			el("h4", t`simulateSlots`),
 | 
			
		||||
			el("dl").append(
 | 
			
		||||
@@ -241,15 +236,6 @@ export function page({ pkg, info }){
 | 
			
		||||
				el("dd", t`A mapping object of slot names to DOM elements`)
 | 
			
		||||
			)
 | 
			
		||||
		),
 | 
			
		||||
		el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js"), page_id }),
 | 
			
		||||
 | 
			
		||||
		el("div", { className: "tip" }).append(
 | 
			
		||||
			el("p").append(...T`
 | 
			
		||||
				${el("strong", "When to use simulateSlots:")} This approach is useful when you need to distribute
 | 
			
		||||
				content from the light DOM into specific locations in the shadow DOM, particularly in environments
 | 
			
		||||
				where native slots might not be fully supported.
 | 
			
		||||
			`)
 | 
			
		||||
		),
 | 
			
		||||
 | 
			
		||||
		el(h3, t`Best Practices for Web Components with DDE`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user