mirror of
				https://github.com/jaandrle/deka-dom-el
				synced 2025-11-04 07:09:15 +01:00 
			
		
		
		
	🔤
This commit is contained in:
		
							
								
								
									
										13
									
								
								docs/components/examples/signals/derived.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								docs/components/examples/signals/derived.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					import { S } from "deka-dom-el/signals";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Create base signals
 | 
				
			||||||
 | 
					const firstName = S("John");
 | 
				
			||||||
 | 
					const lastName = S("Doe");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Create a derived signal
 | 
				
			||||||
 | 
					const fullName = S(() => firstName.get() + " " + lastName.get());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// The fullName signal updates automatically when either dependency changes
 | 
				
			||||||
 | 
					S.on(fullName, name => console.log("Name changed to:", name));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					firstName.set("Jane"); // logs: "Name changed to: Jane Doe"
 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { T, t } from "./utils/index.js";
 | 
					import { T, t } from "./utils/index.js";
 | 
				
			||||||
export const info= {
 | 
					export const info= {
 | 
				
			||||||
	title: t`Signals and reactivity`,
 | 
						title: t`Signals and Reactivity`,
 | 
				
			||||||
	description: t`Managing reactive UI state with signals.`,
 | 
						description: t`Managing reactive UI state with signals.`,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -43,87 +43,243 @@ const references= {
 | 
				
			|||||||
export function page({ pkg, info }){
 | 
					export function page({ pkg, info }){
 | 
				
			||||||
	const page_id= info.id;
 | 
						const page_id= info.id;
 | 
				
			||||||
	return el(simplePage, { info, pkg }).append(
 | 
						return el(simplePage, { info, pkg }).append(
 | 
				
			||||||
		el("h2", t`Using signals to manage reactivity`),
 | 
							el("h2", t`Building Reactive UIs with Signals`),
 | 
				
			||||||
		el("p").append(...T`
 | 
							el("p").append(...T`
 | 
				
			||||||
			How a program responds to variable data or user interactions is one of the fundamental problems of
 | 
								Signals provide a simple yet powerful way to create reactive applications with DDE. They handle the
 | 
				
			||||||
			programming. If we desire to solve the issue in a declarative manner, signals may be a viable approach.
 | 
								fundamental challenge of keeping your UI in sync with changing data in a declarative, efficient way.
 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
 | 
							el("div", { class: "dde-callout" }).append(
 | 
				
			||||||
 | 
								el("h4", t`What Makes Signals Special?`),
 | 
				
			||||||
 | 
								el("ul").append(
 | 
				
			||||||
 | 
									el("li", t`Fine-grained reactivity without complex state management`),
 | 
				
			||||||
 | 
									el("li", t`Automatic UI updates when data changes`),
 | 
				
			||||||
 | 
									el("li", t`Clean separation between data, logic, and UI`),
 | 
				
			||||||
 | 
									el("li", t`Small runtime with minimal overhead`),
 | 
				
			||||||
 | 
									el("li", t`Works seamlessly with DDE's DOM creation`),
 | 
				
			||||||
 | 
									el("li", t`No dependencies or framework lock-in`)
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
		el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }),
 | 
							el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		el(h3, t`Introducing signals`),
 | 
							el(h3, t`The 3-Part Structure of Signals`),
 | 
				
			||||||
		el("p").append(...T`
 | 
							el("p").append(...T`
 | 
				
			||||||
			Let’s re-introduce
 | 
								Signals organize your code into three distinct parts, following the
 | 
				
			||||||
			${el("a", { textContent: t`3PS principle`, href: "./#h-event-driven-programming--parts-separation--ps" })}.
 | 
								${el("a", { textContent: t`3PS principle`, href: "./#h-event-driven-programming--parts-separation--ps" })}:
 | 
				
			||||||
		`),
 | 
					 | 
				
			||||||
		el("p").append(...T`
 | 
					 | 
				
			||||||
			Using signals, we split program logic into the three parts. Firstly (α), we create a variable (constant)
 | 
					 | 
				
			||||||
			representing reactive value. Somewhere later, we can register (β) a logic reacting to the signal value
 | 
					 | 
				
			||||||
			changes. Similarly, in a remaining part (γ), we can update the signal value.
 | 
					 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
 | 
							el("div", { class: "dde-signal-diagram" }).append(
 | 
				
			||||||
 | 
								el("div", { class: "signal-part" }).append(
 | 
				
			||||||
 | 
									el("h4", t`α: 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(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(code, { content: "count.set(count.get() + 1);", page_id }),
 | 
				
			||||||
 | 
									el("p", t`Modify the signal value, which automatically triggers updates`)
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
		el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }),
 | 
							el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el("div", { class: "dde-note" }).append(
 | 
				
			||||||
 | 
								el("p").append(...T`
 | 
				
			||||||
 | 
									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.
 | 
				
			||||||
 | 
								`)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el(h3, t`Signal Essentials: Core API`),
 | 
				
			||||||
 | 
							el("div", { class: "dde-function-table" }).append(
 | 
				
			||||||
 | 
								el("dl").append(
 | 
				
			||||||
 | 
									el("dt", t`Creating a Signal`),
 | 
				
			||||||
 | 
									el("dd", t`S(initialValue) → creates a signal with the given value`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									el("dt", t`Reading a Signal`),
 | 
				
			||||||
 | 
									el("dd", t`signal.get() → returns the current value`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									el("dt", t`Writing to a Signal`),
 | 
				
			||||||
 | 
									el("dd", t`signal.set(newValue) → updates the value and notifies subscribers`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									el("dt", t`Subscribing to Changes`),
 | 
				
			||||||
 | 
									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("p").append(...T`
 | 
							el("p").append(...T`
 | 
				
			||||||
			All this is just an example of
 | 
								Signals can be created with any type of value, but they work best with
 | 
				
			||||||
			${el("a", { textContent: t`Event-driven programming`, ...references.wiki_event_driven })} and
 | 
								${el("a", { textContent: t`primitive types`, ...references.mdn_primitive })} like strings, numbers, and booleans.
 | 
				
			||||||
			${el("a", { textContent: t`Publish–subscribe pattern`, ...references.wiki_pubsub })} (compare for example
 | 
								For complex data types like objects and arrays, you'll want to use Actions (covered below).
 | 
				
			||||||
			with ${el("a", { textContent: t`fpubsub library`, ...references.fpubsub })}). All three parts can be in
 | 
					 | 
				
			||||||
			some manner independent and still connected to the same reactive entity.
 | 
					 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el(h3, t`Derived Signals: Computed Values`),
 | 
				
			||||||
		el("p").append(...T`
 | 
							el("p").append(...T`
 | 
				
			||||||
			Signals are implemented in the library as objects with methods. To see current value of signal,
 | 
								Computed values (also called derived signals) automatically update when their dependencies change.
 | 
				
			||||||
			call the get method ${el("code", "console.log(signal.get())")}. To update the signal value, use the set method
 | 
								Create them by passing a function to S():
 | 
				
			||||||
			${el("code", `signal.set('${t`a new value`}')`)}. For listenning the signal value changes, use
 | 
					 | 
				
			||||||
			${el("code", "S.on(signal, console.log)")}.
 | 
					 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
 | 
							el(example, { src: fileURL("./components/examples/signals/derived.js"), page_id }),
 | 
				
			||||||
		el("p").append(...T`
 | 
							el("p").append(...T`
 | 
				
			||||||
			Similarly to the ${el("code", "on")} function to register DOM events listener. You can use
 | 
								Derived signals are read-only - you can't call .set() on them. Their value is always computed
 | 
				
			||||||
			${el("code", "AbortController")}/${el("code", "AbortSignal")} to ${el("em", "off")}/stop listenning. In
 | 
								from their dependencies. They're perfect for transforming or combining data from other signals.
 | 
				
			||||||
			example, you also found the way for representing “live” piece of code computation pattern (derived signal):
 | 
					 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
		el(example, { src: fileURL("./components/examples/signals/computations-abort.js"), page_id }),
 | 
							el(example, { src: fileURL("./components/examples/signals/computations-abort.js"), page_id }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		el(h3, t`Signals and actions`),
 | 
							el(h3, t`Signal Actions: For Complex State`),
 | 
				
			||||||
		el("p").append(...T`
 | 
							el("p").append(...T`
 | 
				
			||||||
			${el("code", `S(/* ${t`primitive`} */)`)} allows you to declare simple reactive variables, typically, around
 | 
								When working with objects, arrays, or other complex data structures, Signal Actions provide
 | 
				
			||||||
			${el("em", t`immutable`)} ${el("a", { textContent: t`primitive types`, ...references.mdn_primitive })}.
 | 
								a structured way to modify state while maintaining reactivity.
 | 
				
			||||||
			However, it may also be necessary to use reactive arrays, objects, or other complex reactive structures.
 | 
					 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
 | 
							el("div", { class: "dde-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([], {
 | 
				
			||||||
 | 
						add(text) {
 | 
				
			||||||
 | 
							this.value.push(text);
 | 
				
			||||||
 | 
							// Subscribers notified automatically
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Use the action
 | 
				
			||||||
 | 
					S.action(todos, "add", "New todo");`, page_id })
 | 
				
			||||||
 | 
									)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
		el(example, { src: fileURL("./components/examples/signals/actions-demo.js"), page_id }),
 | 
							el(example, { src: fileURL("./components/examples/signals/actions-demo.js"), page_id }),
 | 
				
			||||||
		el("p", t`…but typical user-case is object/array (maps, sets and other mutable objects):`),
 | 
					
 | 
				
			||||||
		el(example, { src: fileURL("./components/examples/signals/actions-todos.js"), page_id }),
 | 
					 | 
				
			||||||
		el("p").append(...T`
 | 
							el("p").append(...T`
 | 
				
			||||||
			In some way, you can compare it with ${el("a", { textContent: "useReducer", ...references.mdn_use_reducer })}
 | 
								Actions provide these benefits:
 | 
				
			||||||
			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>)")}
 | 
							el("ul").append(
 | 
				
			||||||
			after the action call the signal calls all its listeners. This can be stopped by calling
 | 
								el("li", t`Encapsulate state change logic in named methods`),
 | 
				
			||||||
			${el("code", "this.stopPropagation()")} in the method representing the given action. As it can be seen in
 | 
								el("li", t`Guarantee notifications when state changes`),
 | 
				
			||||||
			examples, the “store” value is available also in the function for given action (${el("code", "this.value")}).
 | 
								el("li", t`Prevent accidental direct mutations`),
 | 
				
			||||||
 | 
								el("li", t`Act similar to reducers in other state management libraries`)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
							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("div", { class: "dde-tip" }).append(
 | 
				
			||||||
 | 
								el("p").append(...T`
 | 
				
			||||||
 | 
									${el("strong", "Special Action Methods")}: Signal actions can implement special lifecycle hooks:
 | 
				
			||||||
 | 
								`),
 | 
				
			||||||
 | 
								el("ul").append(
 | 
				
			||||||
 | 
									el("li", t`[S.symbols.onclear]() - Called when the signal is cleared`),
 | 
				
			||||||
 | 
									el("li", t`[S.symbols.onget]() - Called when the signal value is read`),
 | 
				
			||||||
 | 
									el("li", t`[S.symbols.onset]() - Called after the signal value is changed`)
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el(h3, t`Connecting Signals to the DOM`),
 | 
				
			||||||
 | 
							el("p").append(...T`
 | 
				
			||||||
 | 
								Signals really shine when connected to your UI. DDE provides several ways to bind signals to DOM elements:
 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		el(h3, t`Reactive DOM attributes and elements`),
 | 
							el("div", { class: "dde-tabs" }).append(
 | 
				
			||||||
		el("p", t`There are two fundamental ways to make your DOM reactive with signals:`),
 | 
								el("div", { class: "tab", "data-tab": "attributes" }).append(
 | 
				
			||||||
		el("ol").append(
 | 
									el("h4", t`Reactive Attributes`),
 | 
				
			||||||
			el("li", t`Reactive attributes: Update properties, attributes, and styles of existing elements`),
 | 
									el("p", t`Bind signal values directly to element attributes, properties, or styles:`),
 | 
				
			||||||
			el("li").append(...T`Reactive elements: Dynamically create or update DOM elements based on data changes
 | 
									el(code, { content: `// Create a signal
 | 
				
			||||||
				(for conditions and loops)`)
 | 
					const color = S("blue");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Bind it to an element's style
 | 
				
			||||||
 | 
					el("div", {
 | 
				
			||||||
 | 
						style: {
 | 
				
			||||||
 | 
							color, // Updates when signal changes
 | 
				
			||||||
 | 
							fontWeight: S(() => color.get() === "red" ? "bold" : "normal")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}, "This text changes color");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Later:
 | 
				
			||||||
 | 
					color.set("red"); // UI updates automatically`, page_id })
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
								el("div", { class: "tab", "data-tab": "elements" }).append(
 | 
				
			||||||
 | 
									el("h4", t`Reactive Elements`),
 | 
				
			||||||
 | 
									el("p", t`Dynamically create or update elements based on signal values:`),
 | 
				
			||||||
 | 
									el(code, { content: `// Create an array signal
 | 
				
			||||||
 | 
					const items = S(["Apple", "Banana", "Cherry"]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Create a dynamic list that updates when items change
 | 
				
			||||||
 | 
					el("ul").append(
 | 
				
			||||||
 | 
						S.el(items, items =>
 | 
				
			||||||
 | 
							items.map(item => el("li", item))
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Later:
 | 
				
			||||||
 | 
					S.action(items, "push", "Dragonfruit"); // List updates automatically`, page_id })
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
		),
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }),
 | 
							el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }),
 | 
				
			||||||
		el("p").append(...T`
 | 
							el("p").append(...T`
 | 
				
			||||||
			To derived attribute based on value of signal variable just use the signal as a value of the attribute
 | 
								The ${el("code", "assign")} and ${el("code", "el")} functions detect signals automatically and handle binding.
 | 
				
			||||||
			(${el("code", "assign(element, { attribute: S('value') })")}). ${el("code", "assign")}/${el("code", "el")}
 | 
								You can use special properties like ${el("code", "dataset")}, ${el("code", "ariaset")}, and
 | 
				
			||||||
			provides ways to glue reactive attributes/classes more granularly into the DOM. Just use dedicated build-in
 | 
								${el("code", "classList")} for fine-grained control over specific attribute types.
 | 
				
			||||||
			attributes ${el("code", "dataset")}, ${el("code", "ariaset")} and ${el("code", "classList")}.
 | 
					 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		el("p").append(...T`
 | 
							el("p").append(...T`
 | 
				
			||||||
			For computation, you can use the “derived signal” (see above) like
 | 
								${el("code", "S.el()")} is especially powerful for conditional rendering and lists:
 | 
				
			||||||
			${el("code", "assign(element, { textContent: S(()=> 'Hello '+WorldSignal.get()) })")}. This is read-only signal
 | 
					 | 
				
			||||||
			its value is computed based on given function and updated when any signal used in the function changes.
 | 
					 | 
				
			||||||
		`),
 | 
					 | 
				
			||||||
		el("p").append(...T`
 | 
					 | 
				
			||||||
			To represent part of the template filled dynamically based on the signal value use
 | 
					 | 
				
			||||||
			${el("code", "S.el(signal, DOMgenerator)")}. This was already used in the todo example above or see:
 | 
					 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
		el(example, { src: fileURL("./components/examples/signals/dom-el.js"), page_id }),
 | 
							el(example, { src: fileURL("./components/examples/signals/dom-el.js"), page_id }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el(h3, t`Best Practices for Signals`),
 | 
				
			||||||
 | 
							el("p").append(...T`
 | 
				
			||||||
 | 
								Follow these guidelines to get the most out of signals:
 | 
				
			||||||
 | 
							`),
 | 
				
			||||||
 | 
							el("ol").append(
 | 
				
			||||||
 | 
								el("li").append(...T`
 | 
				
			||||||
 | 
									${el("strong", "Keep signals small and focused")}: Use many small signals rather than a few large ones
 | 
				
			||||||
 | 
								`),
 | 
				
			||||||
 | 
								el("li").append(...T`
 | 
				
			||||||
 | 
									${el("strong", "Use derived signals for computations")}: Don't recompute values in multiple places
 | 
				
			||||||
 | 
								`),
 | 
				
			||||||
 | 
								el("li").append(...T`
 | 
				
			||||||
 | 
									${el("strong", "Clean up signal subscriptions")}: Use AbortController or scope.host() to prevent memory leaks
 | 
				
			||||||
 | 
								`),
 | 
				
			||||||
 | 
								el("li").append(...T`
 | 
				
			||||||
 | 
									${el("strong", "Use actions for complex state")}: Don't directly mutate objects or arrays in signals
 | 
				
			||||||
 | 
								`),
 | 
				
			||||||
 | 
								el("li").append(...T`
 | 
				
			||||||
 | 
									${el("strong", "Avoid infinite loops")}: Be careful when one signal updates another in a subscription
 | 
				
			||||||
 | 
								`)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el("div", { class: "dde-troubleshooting" }).append(
 | 
				
			||||||
 | 
								el("h4", t`Common Signal Pitfalls`),
 | 
				
			||||||
 | 
								el("dl").append(
 | 
				
			||||||
 | 
									el("dt", t`UI not updating when array/object changes`),
 | 
				
			||||||
 | 
									el("dd", t`Use signal actions instead of direct mutation`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									el("dt", t`Infinite update loops`),
 | 
				
			||||||
 | 
									el("dd", t`Check for circular dependencies between signals`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									el("dt", t`Memory leaks`),
 | 
				
			||||||
 | 
									el("dd", t`Use AbortController or scope.host() to clean up subscriptions`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									el("dt", t`Multiple elements updating unnecessarily`),
 | 
				
			||||||
 | 
									el("dd", t`Split large signals into smaller, more focused ones`)
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		el(mnemonic)
 | 
							el(mnemonic)
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { T, t } from "./utils/index.js";
 | 
					import { T, t } from "./utils/index.js";
 | 
				
			||||||
export const info= {
 | 
					export const info= {
 | 
				
			||||||
	title: t`Scopes and components`,
 | 
						title: t`Scopes and Components`,
 | 
				
			||||||
	description: t`Organizing UI into components`,
 | 
						description: t`Organizing UI into reusable, manageable components`,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { el } from "deka-dom-el";
 | 
					import { el } from "deka-dom-el";
 | 
				
			||||||
@@ -28,60 +28,226 @@ const references= {
 | 
				
			|||||||
export function page({ pkg, info }){
 | 
					export function page({ pkg, info }){
 | 
				
			||||||
	const page_id= info.id;
 | 
						const page_id= info.id;
 | 
				
			||||||
	return el(simplePage, { info, pkg }).append(
 | 
						return el(simplePage, { info, pkg }).append(
 | 
				
			||||||
		el("h2", t`Using functions as UI components`),
 | 
							el("h2", t`Building Maintainable UIs with Scopes and Components`),
 | 
				
			||||||
		el("p").append(...T`
 | 
							el("p").append(...T`
 | 
				
			||||||
			For state-less components we can use functions as UI components (see “Elements” page). But in real life,
 | 
								Scopes provide a structured way to organize your UI code into reusable components that properly
 | 
				
			||||||
			we may need to handle the component live-cycle and provide JavaScript the way to properly use
 | 
								manage their lifecycle, handle cleanup, and maintain clear boundaries between different parts of your application.
 | 
				
			||||||
			the ${el("a", { textContent: t`Garbage collection`, ...references.garbage_collection })}.
 | 
					 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
 | 
							el("div", { class: "dde-callout" }).append(
 | 
				
			||||||
 | 
								el("h4", t`Why Use Scopes?`),
 | 
				
			||||||
 | 
								el("ul").append(
 | 
				
			||||||
 | 
									el("li", t`Automatic resource cleanup when components are removed from DOM`),
 | 
				
			||||||
 | 
									el("li", t`Clear component boundaries with explicit host elements`),
 | 
				
			||||||
 | 
									el("li", t`Simplified event handling with proper "this" binding`),
 | 
				
			||||||
 | 
									el("li", t`Seamless integration with signals for reactive components`),
 | 
				
			||||||
 | 
									el("li", t`Better memory management with ${el("a", { textContent: t`GC`, ...references.garbage_collection })}`)
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
		el(code, { src: fileURL("./components/examples/scopes/intro.js"), page_id }),
 | 
							el(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`Scopes and hosts`),
 | 
							el(h3, t`Understanding Host Elements and Scopes`),
 | 
				
			||||||
 | 
							el("div", { class: "dde-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")          │
 | 
				
			||||||
 | 
					│   );                            │
 | 
				
			||||||
 | 
					│ }                               │
 | 
				
			||||||
 | 
					└─────────────────────────────────┘
 | 
				
			||||||
 | 
								`))
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
		el("p").append(...T`
 | 
							el("p").append(...T`
 | 
				
			||||||
			The ${el("strong", "host")} is the name for the element representing the component. This is typically
 | 
								The ${el("strong", "host element")} is the root element of your component - typically the element returned
 | 
				
			||||||
			element returned by function. To get reference, you can use ${el("code", "scope.host()")} to applly addons
 | 
								by your component function. It serves as the identity of your component in the DOM.
 | 
				
			||||||
			just use ${el("code", "scope.host(...<addons>)")}.
 | 
					 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
 | 
							el("div", { class: "dde-function-table" }).append(
 | 
				
			||||||
 | 
								el("h4", t`scope.host()`),
 | 
				
			||||||
 | 
								el("dl").append(
 | 
				
			||||||
 | 
									el("dt", t`When called with no arguments`),
 | 
				
			||||||
 | 
									el("dd", t`Returns a reference to the host element (the root element of your component)`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									el("dt", t`When called with addons/callbacks`),
 | 
				
			||||||
 | 
									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"), page_id }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el("div", { class: "dde-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`
 | 
							el("p").append(...T`
 | 
				
			||||||
			To better understanding we implement function ${el("code", "elClass")} helping to create component as
 | 
								While functional components are the primary pattern in DDE, you can also create class-based components
 | 
				
			||||||
			class instances.
 | 
								for more structured organization of component logic.
 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
		el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }),
 | 
							el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		el("p").append(...T`
 | 
							el("p").append(...T`
 | 
				
			||||||
			As you can see, the ${el("code", "scope.host()")} is stored temporarily and synchronously. Therefore, at
 | 
								This pattern can be useful when:
 | 
				
			||||||
			least in the beginning of using library, it is the good practise to store ${el("code", "host")} in the root
 | 
					 | 
				
			||||||
			of your component. As it may be changed, typically when there is asynchronous code in the component.
 | 
					 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
 | 
							el("ul").append(
 | 
				
			||||||
 | 
								el("li", t`You have complex component logic that benefits from object-oriented organization`),
 | 
				
			||||||
 | 
								el("li", t`You need private methods and properties for your component`),
 | 
				
			||||||
 | 
								el("li", t`You're transitioning from another class-based component system`)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
							el("div", { class: "dde-tip" }).append(
 | 
				
			||||||
 | 
								el("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.
 | 
				
			||||||
 | 
								`)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
		el(code, { src: fileURL("./components/examples/scopes/good-practise.js"), page_id }),
 | 
							el(code, { src: fileURL("./components/examples/scopes/good-practise.js"), page_id }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		el(h3, t`Scopes, signals and cleaning magic`),
 | 
							el(h3, t`Automatic Cleanup with Scopes`),
 | 
				
			||||||
		el("p").append(...T`
 | 
							el("p").append(...T`
 | 
				
			||||||
			The ${el("code", "host")} is internally used to register the cleaning procedure, when the component
 | 
								One of the most powerful features of scopes is automatic cleanup when components are removed from the DOM.
 | 
				
			||||||
			(${el("code", "host")} element) is removed from the DOM.
 | 
								This prevents memory leaks and ensures resources are properly released.
 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
 | 
							el("div", { class: "dde-illustration" }).append(
 | 
				
			||||||
 | 
								el("h4", t`Lifecycle Flow`),
 | 
				
			||||||
 | 
								el("pre").append(el("code", `
 | 
				
			||||||
 | 
					1. Component created → scope established
 | 
				
			||||||
 | 
					2. Component added to DOM → connected event
 | 
				
			||||||
 | 
					3. Component interactions happen
 | 
				
			||||||
 | 
					4. Component removed from DOM → disconnected event
 | 
				
			||||||
 | 
					5. Automatic cleanup of:
 | 
				
			||||||
 | 
						- Event listeners
 | 
				
			||||||
 | 
						- Signal subscriptions
 | 
				
			||||||
 | 
						- Custom cleanup code
 | 
				
			||||||
 | 
								`))
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
		el(example, { src: fileURL("./components/examples/scopes/cleaning.js"), page_id }),
 | 
							el(example, { src: fileURL("./components/examples/scopes/cleaning.js"), page_id }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el("div", { class: "dde-note" }).append(
 | 
				
			||||||
 | 
								el("p").append(...T`
 | 
				
			||||||
 | 
									In this example, when you click "Remove", the component is removed from the DOM, and all its associated
 | 
				
			||||||
 | 
									resources are automatically cleaned up, including the signal subscription that updates the text content.
 | 
				
			||||||
 | 
									This happens because the library internally registers a disconnected event handler on the host element.
 | 
				
			||||||
 | 
								`)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el(h3, t`Declarative vs Imperative Components`),
 | 
				
			||||||
		el("p").append(...T`
 | 
							el("p").append(...T`
 | 
				
			||||||
			The text content of the paragraph is changing when the value of the signal ${el("code", "textContent")}
 | 
								Scopes work best with a declarative approach to UI building, especially when combined
 | 
				
			||||||
			is changed. Internally, there is association between ${el("code", "textContent")} and the paragraph,
 | 
								with ${el("a", { textContent: "signals", ...references.signals })} for state management.
 | 
				
			||||||
			similar to using ${el("code", `S.on(textContent, /* ${t`update the paragraph`} */)`)}.
 | 
					 | 
				
			||||||
		`),
 | 
					 | 
				
			||||||
		el("p").append(...T`
 | 
					 | 
				
			||||||
			This listener must be removed when the component is removed from the DOM. To do it, the library assign
 | 
					 | 
				
			||||||
			internally ${el("code", `on.disconnected(/* ${t`remove the listener`} */)(host())`)} to the host element.
 | 
					 | 
				
			||||||
		`),
 | 
					 | 
				
			||||||
		el("p", { className: "notice" }).append(...T`
 | 
					 | 
				
			||||||
			The library DOM API and signals works ideally when used declaratively. It means, you split your app logic
 | 
					 | 
				
			||||||
			into three parts as it was itroduced in ${el("a", { textContent: "Signals", ...references.signals })}.
 | 
					 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
 | 
							el("div", { class: "dde-tabs" }).append(
 | 
				
			||||||
 | 
								el("div", { class: "tab", "data-tab": "declarative" }).append(
 | 
				
			||||||
 | 
									el("h4", t`✅ Declarative Approach`),
 | 
				
			||||||
 | 
									el("p", t`Define what your UI should look like based on state:`),
 | 
				
			||||||
 | 
									el("pre").append(el("code", `function Counter() {
 | 
				
			||||||
 | 
						const { host } = scope;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Define state
 | 
				
			||||||
 | 
						const count = S(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Define behavior
 | 
				
			||||||
 | 
						const increment = () => count.set(count.get() + 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// UI automatically updates when count changes
 | 
				
			||||||
 | 
						return el("div").append(
 | 
				
			||||||
 | 
							el("p", S(() => "Count: " + count.get())),
 | 
				
			||||||
 | 
							el("button", {
 | 
				
			||||||
 | 
								onclick: increment,
 | 
				
			||||||
 | 
								textContent: "Increment"
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}`))
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
								el("div", { class: "tab", "data-tab": "imperative" }).append(
 | 
				
			||||||
 | 
									el("h4", t`⚠️ Imperative Approach`),
 | 
				
			||||||
 | 
									el("p", t`Manually update the DOM in response to events:`),
 | 
				
			||||||
 | 
									el("pre").append(el("code", `function Counter() {
 | 
				
			||||||
 | 
						const { host } = scope;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let count = 0;
 | 
				
			||||||
 | 
						const counterText = el("p", "Count: 0");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Manually update DOM element
 | 
				
			||||||
 | 
						const increment = () => {
 | 
				
			||||||
 | 
							count++;
 | 
				
			||||||
 | 
							counterText.textContent = "Count: " + count;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return el("div").append(
 | 
				
			||||||
 | 
							counterText,
 | 
				
			||||||
 | 
							el("button", {
 | 
				
			||||||
 | 
								onclick: increment,
 | 
				
			||||||
 | 
								textContent: "Increment"
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}`))
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		el(code, { src: fileURL("./components/examples/scopes/declarative.js"), page_id }),
 | 
							el(code, { src: fileURL("./components/examples/scopes/declarative.js"), page_id }),
 | 
				
			||||||
		el("p").append(...T`
 | 
					
 | 
				
			||||||
			Strictly speaking, the imperative way of using the library is not prohibited. Just be careful (rather avoid)
 | 
							el("div", { class: "dde-note" }).append(
 | 
				
			||||||
			mixing declarative approach (using signals) and imperative manipulation of elements.
 | 
								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(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
 | 
				
			||||||
 | 
								`),
 | 
				
			||||||
 | 
								el("li").append(...T`
 | 
				
			||||||
 | 
									${el("strong", "Keep components focused:")} Each component should do one thing well
 | 
				
			||||||
 | 
								`),
 | 
				
			||||||
 | 
								el("li").append(...T`
 | 
				
			||||||
 | 
									${el("strong", "Add explicit cleanup:")} For resources not managed by DDE, use ${el("code", "on.disconnected")}
 | 
				
			||||||
 | 
								`)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el("div", { class: "dde-troubleshooting" }).append(
 | 
				
			||||||
 | 
								el("h4", t`Common Scope Pitfalls`),
 | 
				
			||||||
 | 
								el("dl").append(
 | 
				
			||||||
 | 
									el("dt", t`Losing host reference in async code`),
 | 
				
			||||||
 | 
									el("dd", t`Store host reference early with const { host } = scope`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									el("dt", t`Memory leaks from custom resources`),
 | 
				
			||||||
 | 
									el("dd", t`Use host(on.disconnected(cleanup)) for manual resource cleanup`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									el("dt", t`Event handlers with incorrect 'this'`),
 | 
				
			||||||
 | 
									el("dd", t`Use arrow functions or .bind() to preserve context`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									el("dt", t`Mixing declarative and imperative styles`),
 | 
				
			||||||
 | 
									el("dd", t`Choose one approach and be consistent throughout a component`)
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		el(mnemonic)
 | 
							el(mnemonic)
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -53,90 +53,236 @@ const references= {
 | 
				
			|||||||
export function page({ pkg, info }){
 | 
					export function page({ pkg, info }){
 | 
				
			||||||
	const page_id= info.id;
 | 
						const page_id= info.id;
 | 
				
			||||||
	return el(simplePage, { info, pkg }).append(
 | 
						return el(simplePage, { info, pkg }).append(
 | 
				
			||||||
		el("h2", t`Using web components in combinantion with DDE`),
 | 
							el("h2", t`Using Web Components with DDE: Better Together`),
 | 
				
			||||||
		el("p").append(...T`
 | 
							el("p").append(...T`
 | 
				
			||||||
			The DDE library allows for use within ${el("a", references.mdn_web_components).append( el("strong",
 | 
								DDE pairs powerfully with ${el("a", references.mdn_web_components).append(el("strong", t`Web Components`))}
 | 
				
			||||||
			t`Web Components`) )} for dom-tree generation. However, in order to be able to use signals (possibly
 | 
								to create reusable, encapsulated custom elements with all the benefits of DDE's declarative DOM
 | 
				
			||||||
			mapping to registered ${el("a", references.mdn_observedAttributes).append( el("code", "observedAttributes")
 | 
								construction and reactivity system.
 | 
				
			||||||
			)}) and additional functionality is (unfortunately) required to use helpers provided by the library.
 | 
					 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
 | 
							el("div", { class: "dde-callout" }).append(
 | 
				
			||||||
 | 
								el("h4", t`Why Combine DDE with Web Components?`),
 | 
				
			||||||
 | 
								el("ul").append(
 | 
				
			||||||
 | 
									el("li", t`Declarative DOM creation within your components`),
 | 
				
			||||||
 | 
									el("li", t`Reactive attribute updates through signals`),
 | 
				
			||||||
 | 
									el("li", t`Simplified event handling with the same events API`),
 | 
				
			||||||
 | 
									el("li", t`Clean component lifecycle management`),
 | 
				
			||||||
 | 
									el("li", t`Improved code organization with scopes`)
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
		el(code, { src: fileURL("./components/examples/customElement/intro.js"), page_id }),
 | 
							el(code, { src: fileURL("./components/examples/customElement/intro.js"), page_id }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		el(h3, t`Custom Elements Introduction`),
 | 
							el(h3, t`Getting Started: Web Components Basics`),
 | 
				
			||||||
		el("p").append(...T`
 | 
							el("p").append(...T`
 | 
				
			||||||
			Web Components, specifically Custom Elements, are a set of web platform APIs that allow you to create
 | 
								Web Components are a set of standard browser APIs that let you create custom HTML elements with
 | 
				
			||||||
			new HTML tags with custom functionality encapsulated within them. This allows for the creation of reusable
 | 
								encapsulated functionality. They consist of three main technologies:
 | 
				
			||||||
			components that can be used across web applications.
 | 
					 | 
				
			||||||
		`),
 | 
					 | 
				
			||||||
		el("p").append(...T`
 | 
					 | 
				
			||||||
			To start with, let’s see how to use native Custom Elements. As starting point please read
 | 
					 | 
				
			||||||
			${el("a", references.mdn_custom_elements).append( el("strong", t`Using Custom Elements`), t` on MDN` )}.
 | 
					 | 
				
			||||||
			To sum up and for mnemonic see following code overview:
 | 
					 | 
				
			||||||
		`),
 | 
					 | 
				
			||||||
		el(code, { src: fileURL("./components/examples/customElement/native-basic.js"), page_id }),
 | 
					 | 
				
			||||||
		el("p").append(...T`
 | 
					 | 
				
			||||||
			For more advanced use of Custom Elements, the summary ${el("a", references.custom_elements_tips)
 | 
					 | 
				
			||||||
			.append( el("strong", t`Handy Custom Elements Patterns`) )} may be useful. Especially pay attention to
 | 
					 | 
				
			||||||
			linking HTML attributes and defining setters/getters, this is very helpful to use in combination with
 | 
					 | 
				
			||||||
			the library (${el("code", `el(HTMLCustomElement.tagName, { customAttribute: "${t`new-value`}" });`)}).
 | 
					 | 
				
			||||||
		`),
 | 
					 | 
				
			||||||
		el("p").append(...T`
 | 
					 | 
				
			||||||
			Also see the Life Cycle Events sections, very similarly we would like to use
 | 
					 | 
				
			||||||
			${el("a", { textContent: t`DDE events`, href: "./p03-events.html", title: t`See events part of the library
 | 
					 | 
				
			||||||
			documentation` })}. To do it, the library provides function ${el("code", "customElementWithDDE")}…
 | 
					 | 
				
			||||||
		`),
 | 
					 | 
				
			||||||
		el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js"), page_id }),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		el("h3", t`Custom Elements with DDE`),
 | 
					 | 
				
			||||||
		el("p").append(...T`
 | 
					 | 
				
			||||||
			The ${el("code", "customElementWithDDE")} function is only (small) part of the inregration of the library.
 | 
					 | 
				
			||||||
			More important for coexistence is render component function as a body of the Custom Element. For that, you
 | 
					 | 
				
			||||||
			can use ${el("code", "customElementRender")} with arguments instance reference, target for connection,
 | 
					 | 
				
			||||||
			render function and optional properties (will be passed to the render function) see later…
 | 
					 | 
				
			||||||
		`),
 | 
					 | 
				
			||||||
		el(example, { src: fileURL("./components/examples/customElement/dde.js"), page_id }),
 | 
					 | 
				
			||||||
		el("p").append(...T`
 | 
					 | 
				
			||||||
			…as you can see, you can use components created based on the documentation previously introduced. To unlock
 | 
					 | 
				
			||||||
			full potential, use with combination ${el("code", "customElementWithDDE")} (allows to use livecycle events)
 | 
					 | 
				
			||||||
			and ${el("code", "observedAttributes")} (converts attributes to render function arguments —
 | 
					 | 
				
			||||||
			${el("em", "default")}) or ${el("code", "S.observedAttributes")} (converts attributes to signals).
 | 
					 | 
				
			||||||
		`),
 | 
					 | 
				
			||||||
		el(example, { src: fileURL("./components/examples/customElement/observedAttributes.js"), page_id }),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		el(h3, t`Shadow DOM`),
 | 
					 | 
				
			||||||
		el("p").append(...T`
 | 
					 | 
				
			||||||
			Shadow DOM is a web platform feature that allows for the encapsulation of a component’s internal DOM tree
 | 
					 | 
				
			||||||
			from the rest of the document. This means that styles and scripts applied to the document will not affect
 | 
					 | 
				
			||||||
			the component’s internal DOM, and vice versa.
 | 
					 | 
				
			||||||
		`),
 | 
					 | 
				
			||||||
		el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js"), page_id }),
 | 
					 | 
				
			||||||
		el("p").append(...T`
 | 
					 | 
				
			||||||
			Regarding to ${el("code", "this.attachShadow({ mode: 'open' })")} see quick overview
 | 
					 | 
				
			||||||
			${el("a", { textContent: t`Using Shadow DOM`, ...references.mdn_shadow_dom_depth })}. An another source of
 | 
					 | 
				
			||||||
			information can be ${el("a", { textContent: t`Shadow DOM in Depth`, ...references.shadow_dom_depth })}.
 | 
					 | 
				
			||||||
		`),
 | 
					 | 
				
			||||||
		el("p").append(...T`
 | 
					 | 
				
			||||||
			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("p").append(...T`
 | 
					 | 
				
			||||||
			To sum up:
 | 
					 | 
				
			||||||
		`),
 | 
							`),
 | 
				
			||||||
		el("ul").append(
 | 
							el("ul").append(
 | 
				
			||||||
			el("li").append(...T`
 | 
								el("li").append(...T`
 | 
				
			||||||
				The use of shadow DOM to encapsulate the internal structure of the custom element, which affects how
 | 
									${el("strong", "Custom Elements:")} Create your own HTML tags with JS-defined behavior
 | 
				
			||||||
				the custom element can be styled and modified using JavaScript and CSS.
 | 
					 | 
				
			||||||
			`),
 | 
								`),
 | 
				
			||||||
			el("li").append(...T`
 | 
								el("li").append(...T`
 | 
				
			||||||
				The ability to access and modify the internal structure of the custom element using JavaScript, which
 | 
									${el("strong", "Shadow DOM:")} Encapsulate styles and markup within a component
 | 
				
			||||||
				is affected by the use of shadow DOM and the mode of the shadow DOM.
 | 
					 | 
				
			||||||
			`),
 | 
								`),
 | 
				
			||||||
			el("li").append(...T`
 | 
								el("li").append(...T`
 | 
				
			||||||
				The use of slots to allow for the insertion of content from the parent document into the custom
 | 
									${el("strong", "HTML Templates:")} Define reusable markup structures
 | 
				
			||||||
				element, which is affected by the use of shadow DOM and the mode of the shadow DOM.
 | 
								`)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
							el("p").append(...T`
 | 
				
			||||||
 | 
								Let's start with a basic Custom Element example without DDE to establish the foundation:
 | 
				
			||||||
 | 
							`),
 | 
				
			||||||
 | 
							el(code, { src: fileURL("./components/examples/customElement/native-basic.js"), page_id }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el("div", { class: "dde-note" }).append(
 | 
				
			||||||
 | 
								el("p").append(...T`
 | 
				
			||||||
 | 
									For complete information on Web Components, see the
 | 
				
			||||||
 | 
									${el("a", references.mdn_custom_elements).append(el("strong", t`MDN documentation`))}.
 | 
				
			||||||
 | 
									Also, ${el("a", references.custom_elements_tips).append(el("strong", t`Handy Custom Elements Patterns`))}
 | 
				
			||||||
 | 
									provides useful techniques for connecting attributes with properties.
 | 
				
			||||||
 | 
								`)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el(h3, t`DDE Integration: Step 1 - Event Handling`),
 | 
				
			||||||
 | 
							el("p").append(...T`
 | 
				
			||||||
 | 
								The first step in integrating DDE with Web Components is enabling DDE's event system to work with your
 | 
				
			||||||
 | 
								Custom Elements. This is done with ${el("code", "customElementWithDDE")}, which makes your Custom Element
 | 
				
			||||||
 | 
								compatible with DDE's event handling.
 | 
				
			||||||
 | 
							`),
 | 
				
			||||||
 | 
							el("div", { class: "dde-function-table" }).append(
 | 
				
			||||||
 | 
								el("h4", t`customElementWithDDE`),
 | 
				
			||||||
 | 
								el("dl").append(
 | 
				
			||||||
 | 
									el("dt", t`Purpose`),
 | 
				
			||||||
 | 
									el("dd", t`Enables DDE's event system to work with your Custom Element`),
 | 
				
			||||||
 | 
									el("dt", t`Usage`),
 | 
				
			||||||
 | 
									el("dd", t`customElementWithDDE(YourElementClass)`),
 | 
				
			||||||
 | 
									el("dt", t`Benefits`),
 | 
				
			||||||
 | 
									el("dd", t`Allows using on.connected(), on.disconnected(), etc. with your element`)
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
							el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js"), page_id }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el("div", { class: "dde-tip" }).append(
 | 
				
			||||||
 | 
								el("p").append(...T`
 | 
				
			||||||
 | 
									${el("strong", "Key Point:")} The ${el("code", "customElementWithDDE")} function adds event dispatching
 | 
				
			||||||
 | 
									to your Custom Element lifecycle methods, making them work seamlessly with DDE's event system.
 | 
				
			||||||
 | 
								`)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el(h3, t`DDE Integration: Step 2 - Rendering Components`),
 | 
				
			||||||
 | 
							el("p").append(...T`
 | 
				
			||||||
 | 
								The next step is to use DDE's component rendering within your Custom Element. This is done with
 | 
				
			||||||
 | 
								${el("code", "customElementRender")}, which connects your DDE component function to the Custom Element.
 | 
				
			||||||
 | 
							`),
 | 
				
			||||||
 | 
							el("div", { class: "dde-function-table" }).append(
 | 
				
			||||||
 | 
								el("h4", t`customElementRender`),
 | 
				
			||||||
 | 
								el("dl").append(
 | 
				
			||||||
 | 
									el("dt", t`Purpose`),
 | 
				
			||||||
 | 
									el("dd", t`Connects a DDE component function to a Custom Element`),
 | 
				
			||||||
 | 
									el("dt", t`Parameters`),
 | 
				
			||||||
 | 
									el("dd").append(
 | 
				
			||||||
 | 
										el("ol").append(
 | 
				
			||||||
 | 
											el("li", t`Target (usually this or this.shadowRoot)`),
 | 
				
			||||||
 | 
											el("li", t`Component function that returns a DOM tree`),
 | 
				
			||||||
 | 
											el("li", t`Optional: Attributes transformer function (default or S.observedAttributes)`)
 | 
				
			||||||
 | 
										)
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									el("dt", t`Returns`),
 | 
				
			||||||
 | 
									el("dd", t`The rendered DOM tree`)
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
							el(example, { src: fileURL("./components/examples/customElement/dde.js"), page_id }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el("div", { class: "dde-note" }).append(
 | 
				
			||||||
 | 
								el("p").append(...T`
 | 
				
			||||||
 | 
									In this example, we're using Shadow DOM (${el("code", "this.attachShadow()")}) for encapsulation,
 | 
				
			||||||
 | 
									but you can also render directly to the element with ${el("code", "customElementRender(this, ...)")}.
 | 
				
			||||||
 | 
								`)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el(h3, t`Reactive Web Components with Signals`),
 | 
				
			||||||
 | 
							el("p").append(...T`
 | 
				
			||||||
 | 
								One of the most powerful features of integrating DDE with Web Components is connecting HTML attributes
 | 
				
			||||||
 | 
								to DDE's reactive signals system. This creates truly reactive custom elements.
 | 
				
			||||||
 | 
							`),
 | 
				
			||||||
 | 
							el("div", { class: "dde-tip" }).append(
 | 
				
			||||||
 | 
								el("p").append(...T`
 | 
				
			||||||
 | 
									${el("strong", "Two Ways to Handle Attributes:")}
 | 
				
			||||||
			`),
 | 
								`),
 | 
				
			||||||
 | 
								el("ol").append(
 | 
				
			||||||
 | 
									el("li").append(...T`
 | 
				
			||||||
 | 
										${el("code", "observedAttributes")} - Passes attributes as regular values (static)
 | 
				
			||||||
 | 
									`),
 | 
				
			||||||
 | 
									el("li").append(...T`
 | 
				
			||||||
 | 
										${el("code", "S.observedAttributes")} - Transforms attributes into signals (reactive)
 | 
				
			||||||
 | 
									`)
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
							el("p").append(...T`
 | 
				
			||||||
 | 
								Using ${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("div", { class: "dde-callout" }).append(
 | 
				
			||||||
 | 
								el("h4", t`How S.observedAttributes Works`),
 | 
				
			||||||
 | 
								el("p").append(...T`
 | 
				
			||||||
 | 
									1. Takes each attribute listed in static observedAttributes
 | 
				
			||||||
 | 
									2. Creates a DDE signal for each one
 | 
				
			||||||
 | 
									3. Automatically updates these signals when attributes change
 | 
				
			||||||
 | 
									4. Passes the signals to your component function
 | 
				
			||||||
 | 
									5. Your component reacts to changes through signal subscriptions
 | 
				
			||||||
 | 
								`)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el(h3, t`Working with Shadow DOM`),
 | 
				
			||||||
 | 
							el("p").append(...T`
 | 
				
			||||||
 | 
								Shadow DOM provides encapsulation for your component's styles and markup. When using DDE with Shadow DOM,
 | 
				
			||||||
 | 
								you get the best of both worlds: encapsulation plus declarative DOM creation.
 | 
				
			||||||
 | 
							`),
 | 
				
			||||||
 | 
							el("div", { class: "dde-illustration" }).append(
 | 
				
			||||||
 | 
								el("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> │    │    │
 | 
				
			||||||
 | 
					│  │  └─────────────────┘    │    │
 | 
				
			||||||
 | 
					│  │                         │    │
 | 
				
			||||||
 | 
					│  └─────────────────────────┘    │
 | 
				
			||||||
 | 
					│                                 │
 | 
				
			||||||
 | 
					└─────────────────────────────────┘
 | 
				
			||||||
 | 
								`))
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
							el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js"), page_id }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el("p").append(...T`
 | 
				
			||||||
 | 
								For more information on Shadow DOM, see
 | 
				
			||||||
 | 
								${el("a", { textContent: t`Using Shadow DOM`, ...references.mdn_shadow_dom_depth })}, or the comprehensive
 | 
				
			||||||
 | 
								${el("a", { textContent: t`Shadow DOM in Depth`, ...references.shadow_dom_depth })}.
 | 
				
			||||||
 | 
							`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							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:
 | 
				
			||||||
 | 
							`),
 | 
				
			||||||
 | 
							el("div", { class: "dde-function-table" }).append(
 | 
				
			||||||
 | 
								el("h4", t`simulateSlots`),
 | 
				
			||||||
 | 
								el("dl").append(
 | 
				
			||||||
 | 
									el("dt", t`Purpose`),
 | 
				
			||||||
 | 
									el("dd", t`Provides slot functionality when you cannot/do not want to use shadow DOM`),
 | 
				
			||||||
 | 
									el("dt", t`Parameters`),
 | 
				
			||||||
 | 
									el("dd", t`A mapping object of slot names to DOM elements`)
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
							el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js"), page_id }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el("div", { class: "dde-tip" }).append(
 | 
				
			||||||
 | 
								el("p").append(...T`
 | 
				
			||||||
 | 
									${el("strong", "When to use simulateSlots:")} This approach is useful when you need to distribute
 | 
				
			||||||
 | 
									content from the light DOM into specific locations in the shadow DOM, particularly in environments
 | 
				
			||||||
 | 
									where native slots might not be fully supported.
 | 
				
			||||||
 | 
								`)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el(h3, t`Best Practices for Web Components with DDE`),
 | 
				
			||||||
 | 
							el("p").append(...T`
 | 
				
			||||||
 | 
								When combining DDE with Web Components, follow these recommendations:
 | 
				
			||||||
 | 
							`),
 | 
				
			||||||
 | 
							el("ol").append(
 | 
				
			||||||
 | 
								el("li").append(...T`
 | 
				
			||||||
 | 
									${el("strong", "Always use customElementWithDDE")} to enable event integration
 | 
				
			||||||
 | 
								`),
 | 
				
			||||||
 | 
								el("li").append(...T`
 | 
				
			||||||
 | 
									${el("strong", "Prefer S.observedAttributes")} for reactive attribute connections
 | 
				
			||||||
 | 
								`),
 | 
				
			||||||
 | 
								el("li").append(...T`
 | 
				
			||||||
 | 
									${el("strong", "Create reusable component functions")} that your custom elements render
 | 
				
			||||||
 | 
								`),
 | 
				
			||||||
 | 
								el("li").append(...T`
 | 
				
			||||||
 | 
									${el("strong", "Use scope.host()")} to clean up event listeners and subscriptions
 | 
				
			||||||
 | 
								`),
 | 
				
			||||||
 | 
								el("li").append(...T`
 | 
				
			||||||
 | 
									${el("strong", "Add setters and getters")} for better property access to your element
 | 
				
			||||||
 | 
								`)
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							el("div", { class: "dde-troubleshooting" }).append(
 | 
				
			||||||
 | 
								el("h4", t`Common Issues`),
 | 
				
			||||||
 | 
								el("dl").append(
 | 
				
			||||||
 | 
									el("dt", t`Events not firing properly`),
 | 
				
			||||||
 | 
									el("dd", t`Make sure you called customElementWithDDE before defining the element`),
 | 
				
			||||||
 | 
									el("dt", t`Attributes not updating`),
 | 
				
			||||||
 | 
									el("dd", t`Check that you've properly listed them in static observedAttributes`),
 | 
				
			||||||
 | 
									el("dt", t`Component not rendering`),
 | 
				
			||||||
 | 
									el("dd", t`Verify customElementRender is called in connectedCallback, not constructor`)
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
		),
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		el(mnemonic)
 | 
							el(mnemonic)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user