1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2025-07-29 07:00:16 +02:00

dde and docs improvements (#27)

*  🎉

*  wip

* 🔤

*  wip

*  wip

*  Refatc signals to .get/.set syntax #26

* 🐛 Better types for on*

* 🔤

* 🔤

* 🐛 coumputed signal

* 🔤  Docs UI/UX

*  🔤 UI enhancements

*  (bs) (un)min

* 🔤 adds debugging

* 🔤 ssr

* 🔤

*  bs/lint

* 🔤

* 🔤 UI

* 🔤 updates texts

* 🔤UI

*  dispatch

* 🔤 events

* 🔤 elements

* 🔤 intro

* 🐛 fixes completitions for el with components

* 🐛 wrong file(s) in git

* 🔤 logo

* 🐛 🔤 types 3ps

* 🔤 ui/ux

* 🔤

* 🔤

* 🔤 scopes

* 🔤

* 🔤 ui/ux

* 🔤

*  issignal

* 🔤 improvemens

*  irelands

* 🔤 UI/UX/wording

* 🐛 npx-hint

[Scrollable region must have keyboard access | Axe Rules | Deque University | Deque Systems](https://dequeuniversity.com/rules/axe/4.10/scrollable-region-focusable?application=axeAPI)

* 🔤 logos

*  better? dts builds

* Update README.md
This commit is contained in:
2025-03-07 14:40:45 +01:00
committed by GitHub
parent dba4e93b88
commit 4366027658
110 changed files with 16261 additions and 4741 deletions

View File

@@ -1,7 +1,8 @@
import { T, t } from "./utils/index.js";
export const info= {
title: t`Signals and reactivity`,
description: t`Handling reactivity in UI via signals.`,
title: t`Signals and Reactivity`,
fullTitle: t`Building Reactive UIs with Signals`,
description: t`Managing reactive UI state with signals.`,
};
import { el } from "deka-dom-el";
@@ -43,53 +44,124 @@ const references= {
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("h2", t`Using signals to manage reactivity`),
el("p").append(...T`
How a program responds to variable data or user interactions is one of the fundamental problems of
programming. If we desire to solve the issue in a declarative manner, signals may be a viable approach.
Signals provide a simple yet powerful way to create reactive applications with DDE. They handle the
fundamental challenge of keeping your UI in sync with changing data in a declarative, efficient way.
`),
el("div", { class: "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").append(...T`${el("strong", "In future")} no dependencies or framework lock-in`)
)
),
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`
Lets re-introduce
${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.
Signals organize your code into three distinct parts, following the
${el("a", { textContent: t`3PS principle`, href: "./#h-3ps" })}:
`),
el("div", { class: "signal-diagram" }).append(
el("div", { class: "signal-part" }).append(
el("h4", t`PART 1: 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`PART 2: 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`PART 3: 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("div", { class: "note" }).append(
el("p").append(...T`
Signals implement the ${el("a", { textContent: t`Publishsubscribe 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. Compare for example with ${el("a", { textContent:
t`fpubsub library`, ...references.fpubsub })}.
`)
),
el(h3, t`Signal Essentials: Core API`),
el("div", { class: "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").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`
All this is just an example of
${el("a", { textContent: t`Event-driven programming`, ...references.wiki_event_driven })} and
${el("a", { textContent: t`Publishsubscribe pattern`, ...references.wiki_pubsub })} (compare for example
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.
Signals can be created with any type of value, but they work best with
${el("a", { textContent: t`primitive types`, ...references.mdn_primitive })} like strings, numbers, and booleans.
For complex data types like objects and arrays, you'll want to use Actions (covered below).
`),
el(h3, t`Derived Signals: Computed Values`),
el("p").append(...T`
Signals are implemented in the library as functions. To see current value of signal, just call it without
any arguments ${el("code", "console.log(signal())")}. To update the signal value, pass any argument
${el("code", `signal('${t`a new value`}')`)}. For listenning the signal value changes, use
${el("code", "S.on(signal, console.log)")}.
Computed values (also called derived signals) automatically update when their dependencies change.
Create them by passing a function to S():
`),
el(example, { src: fileURL("./components/examples/signals/derived.js"), page_id }),
el("p").append(...T`
Similarly to the ${el("code", "on")} function to register DOM events listener. You can use
${el("code", "AbortController")}/${el("code", "AbortSignal")} to ${el("em", "off")}/stop listenning. In
example, you also found the way for representing “live” piece of code computation pattern (derived signal):
Derived signals are read-only - you can't call .set() on them. Their value is always computed
from their dependencies. They're perfect for transforming or combining data from other signals.
`),
el(example, { src: fileURL("./components/examples/signals/computations-abort.js"), page_id }),
el(h3, t`Signals and actions`),
el(h3, t`Signal Actions: For Complex State`),
el("p").append(...T`
${el("code", `S(/* ${t`primitive`} */)`)} allows you to declare simple reactive variables, typically, around
${el("em", t`immutable`)} ${el("a", { textContent: t`primitive types`, ...references.mdn_primitive })}.
However, it may also be necessary to use reactive arrays, objects, or other complex reactive structures.
When working with objects, arrays, or other complex data structures, Signal Actions provide
a structured way to modify state while maintaining reactivity.
`),
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("div", { class: "illustration" }).append(
el("h4", t`Actions vs. Direct Mutation`),
el("div", { class: "comparison" }).append(
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("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
@@ -98,31 +170,123 @@ export function page({ pkg, info }){
${el("code", "this.stopPropagation()")} in the method representing the given action. As it can be seen in
examples, the “store” value is available also in the function for given action (${el("code", "this.value")}).
`),
el(example, { src: fileURL("./components/examples/signals/actions-demo.js"), page_id }),
el(h3, t`Reactive DOM attributes and elements`),
el("p", t`There are on basic level two distinc situation to mirror dynamic value into the DOM/UI`),
el("ol").append(
el("li", t`to change some attribute(s) of existing element(s)`),
el("li", t`to generate elements itself dynamically this covers conditions and loops`)
el("p").append(...T`
Actions provide these benefits:
`),
el("ul").append(
el("li", t`Encapsulate state change logic in named methods`),
el("li", t`Guarantee notifications when state changes`),
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: "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. Use it to clean up resources.`),
)
),
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("div", { class: "tabs" }).append(
el("div", { class: "tab", "data-tab": "attributes" }).append(
el("h4", t`Reactive Attributes`),
el("p", t`Bind signal values directly to element attributes, properties, or styles:`),
el(code, { content: `// Create a signal
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("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`
To derived attribute based on value of signal variable just use the signal as a value of the attribute
(${el("code", "assign(element, { attribute: S('value') })")}). ${el("code", "assign")}/${el("code", "el")}
provides ways to glue reactive attributes/classes more granularly into the DOM. Just use dedicated build-in
attributes ${el("code", "dataset")}, ${el("code", "ariaset")} and ${el("code", "classList")}.
`),
el("p").append(...T`
For computation, you can use the “derived signal” (see above) like
${el("code", "assign(element, { textContent: S(()=> 'Hello '+WorldSignal()) })")}. 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("code", "S.el()")} is especially powerful for conditional rendering and lists:
`),
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: "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)
);
}