diff --git a/docs/assets/devtools.png b/docs/assets/devtools.png new file mode 100644 index 0000000..6a2f824 Binary files /dev/null and b/docs/assets/devtools.png differ diff --git a/docs/components/examples/debugging/debouncing.js b/docs/components/examples/debugging/debouncing.js index ccbb618..17ed6c0 100644 --- a/docs/components/examples/debugging/debouncing.js +++ b/docs/components/examples/debugging/debouncing.js @@ -1,5 +1,6 @@ import { S } from "deka-dom-el/signals"; -// Debouncing signal updates + +// ===== Approach 1: Traditional debouncing with utility function ===== function debounce(func, wait) { let timeout; return (...args)=> { @@ -8,8 +9,59 @@ function debounce(func, wait) { }; } -const inputSignal= S(""); -const debouncedSet= debounce(value => inputSignal.set(value), 300); +const inputSignal = S(""); +const debouncedSet = debounce(value => inputSignal.set(value), 300); // In your input handler -inputElement.addEventListener("input", e=> debouncedSet(e.target.value)); +inputElement.addEventListener("input", e => debouncedSet(e.target.value)); + +// ===== Approach 2: Signal debouncing utility ===== +/** + * Creates a debounced signal that only updates after delay + * @param {any} initialValue Initial signal value + * @param {number} delay Debounce delay in ms + */ +function createDebouncedSignal(initialValue, delay = 300) { + // Create two signals: one for immediate updates, one for debounced values + const immediateSignal = S(initialValue); + const debouncedSignal = S(initialValue); + + // Keep track of the timeout + let timeout = null; + + // Set up a listener on the immediate signal + S.on(immediateSignal, value => { + // Clear any existing timeout + if (timeout) clearTimeout(timeout); + + // Set a new timeout to update the debounced signal + timeout = setTimeout(() => { + debouncedSignal.set(value); + }, delay); + }); + + // Return an object with both signals and a setter function + return { + // The raw signal that updates immediately + raw: immediateSignal, + // The debounced signal that only updates after delay + debounced: debouncedSignal, + // Setter function to update the immediate signal + set: value => immediateSignal.set(value) + }; +} + +// Usage example +const searchInput = createDebouncedSignal("", 300); + +// Log immediate changes for demonstration +S.on(searchInput.raw, value => console.log("Input changed to:", value)); + +// Only perform expensive operations on the debounced value +S.on(searchInput.debounced, value => { + console.log("Performing search with:", value); + // Expensive operation would go here +}); + +// In your input handler +searchElement.addEventListener("input", e => searchInput.set(e.target.value)); \ No newline at end of file diff --git a/docs/components/examples/events/live-cycle.js b/docs/components/examples/events/live-cycle.js index e24a9c8..7692f1e 100644 --- a/docs/components/examples/events/live-cycle.js +++ b/docs/components/examples/events/live-cycle.js @@ -7,8 +7,6 @@ const paragraph= el("p", "See lifecycle events in console.", document.body.append( paragraph, - el("button", "Update attribute", on("click", ()=> paragraph.setAttribute("test", Math.random().toString()))), - " ", el("button", "Remove", on("click", ()=> paragraph.remove())) ); diff --git a/docs/components/examples/signals/actions-todos.js b/docs/components/examples/signals/actions-todos.js index 9a15932..a69f824 100644 --- a/docs/components/examples/signals/actions-todos.js +++ b/docs/components/examples/signals/actions-todos.js @@ -7,7 +7,7 @@ const todos= S([], { const removed= this.value.pop(); if(removed) S.clear(removed); }, - [S.symbols.onclear](){ // this covers `O.clear(todos)` + [S.symbols.onclear](){ // this covers `S.clear(todos)` S.clear(...this.value); } }); diff --git a/docs/components/mnemonic/signals-init.js b/docs/components/mnemonic/signals-init.js index 2e29529..6e4429e 100644 --- a/docs/components/mnemonic/signals-init.js +++ b/docs/components/mnemonic/signals-init.js @@ -14,10 +14,6 @@ export function mnemonic(){ el("code", "S.on(, [, ])"), " — listen to the signal value changes", ), - el("li").append( - el("code", "S.clear(...)"), - " — off and clear signals", - ), el("li").append( el("code", "S(, )"), " — signal: pattern to create complex reactive objects/arrays", @@ -29,6 +25,11 @@ export function mnemonic(){ el("li").append( el("code", "S.el(, )"), " — render partial dom structure (template) based on the current signal value", - ) + ), + el("li").append( + el("code", "S.clear(...)"), + " — off and clear signals (most of the time it is not needed as reactive ", + "attributes and elements are cleared automatically)", + ), ); } diff --git a/docs/global.css.js b/docs/global.css.js index eca1342..eb81fcc 100644 --- a/docs/global.css.js +++ b/docs/global.css.js @@ -193,6 +193,20 @@ pre code { background-color: transparent; padding: 0; } +figure { + width: 100%; + text-align: center; + color: var(--text-light); + border: 1px dashed var(--border); + border-radius: var(--border-radius); + + img { + object-fit: contain; + border-radius: var(--border-radius); + box-shadow: var(--shadow); + max-width: 100%; + } +} /* Layout */ body { diff --git a/docs/index.html.js b/docs/index.html.js index b4bed67..605a7c9 100644 --- a/docs/index.html.js +++ b/docs/index.html.js @@ -33,7 +33,7 @@ export function page({ pkg, info }){ without the complexity and overhead of larger frameworks. `), el("div", { className: "callout" }).append( - el("h4", t`What Makes dd Special`), + el("h4", t`Key Benefits of dd`), el("ul").append( el("li", t`No build step required — use directly in the browser`), el("li", t`Lightweight core (~10–15kB minified) without unnecessary dependencies (0 at now 😇)`), @@ -44,7 +44,7 @@ export function page({ pkg, info }){ ), el(example, { src: fileURL("./components/examples/introducing/helloWorld.js"), page_id }), - el(h3, { textContent: t`The 3PS Pattern: A Better Way to Build UIs`, id: "h-3ps" }), + el(h3, { textContent: t`The 3PS Pattern: Simplified architecture pattern`, id: "h-3ps" }), el("p").append(T` At the heart of dd is the 3PS (3-Part Separation) pattern. This simple yet powerful approach helps you organize your UI code into three distinct areas, making your applications more maintainable and easier @@ -70,7 +70,8 @@ export function page({ pkg, info }){ ${el("strong", "Create State")}: Define your application’s reactive data using signals `), el("li").append(T` - ${el("strong", "Bind to Elements")}: Define how UI elements react to state changes + ${el("strong", "React to Changes")}: Define how UI elements and other parts of your app react to state + changes `), el("li").append(T` ${el("strong", "Update State")}: Modify state in response to user events or other triggers @@ -79,9 +80,9 @@ export function page({ pkg, info }){ el("p").append(T` By separating these concerns, your code becomes more modular, testable, and easier to maintain. This - approach shares principles with more formal patterns like ${el("a", { textContent: "MVVM", - ...references.w_mvv })} and ${el("a", { textContent: "MVC", ...references.w_mvc })}, but with less - overhead and complexity. + approach ${el("strong", "is not")} something new and/or special to dd. It’s based on ${el("a", { + textContent: "MVC", ...references.w_mvc })} (${el("a", { textContent: "MVVM", ...references.w_mvv })}), + but is there presented in simpler form. `), el("div", { className: "note" }).append( @@ -89,6 +90,10 @@ export function page({ pkg, info }){ The 3PS pattern becomes especially powerful when combined with components, allowing you to create reusable pieces of UI with encapsulated state and behavior. You’ll learn more about this in the following sections. + `), + el("p").append(T` + The 3PS pattern isn’t required to use with dd but it is good practice to follow it or some similar + software architecture. `) ), diff --git a/docs/p02-elements.html.js b/docs/p02-elements.html.js index abcecd7..bda75bf 100644 --- a/docs/p02-elements.html.js +++ b/docs/p02-elements.html.js @@ -104,14 +104,14 @@ export function page({ pkg, info }){ el("dd", t`Prefers IDL properties, falls back to setAttribute() when no writable property exists`), el("dt", t`Data and ARIA Attributes`), - el("dd").append(T`Both ${el("code", "dataset")}.* and ${el("code", "data-")}* syntaxes supported - (same for ${el("em", "ARIA")})`), + el("dd").append(T`Both ${el("code", "dataset.keyName")} and ${el("code", "dataKeyName")} syntaxes are + supported (same for ${el("code", "aria")}/${el("code", "ariaset")})`), el("dt", t`Style Handling`), el("dd").append(T`Accepts string or object notation for ${el("code", "style")} property`), el("dt", t`Class Management`), - el("dd").append(T`Works with ${el("code", "className")}, ${el("code", "class")}, or ${el("code", + el("dd").append(T`Works with ${el("code", "className")} (${el("code", "class")}) and ${el("code", "classList")} object for toggling classes`), el("dt", t`Force Modes`), diff --git a/docs/p03-events.html.js b/docs/p03-events.html.js index 6dff824..8f8db51 100644 --- a/docs/p03-events.html.js +++ b/docs/p03-events.html.js @@ -87,14 +87,14 @@ export function page({ pkg, info }){ el(h3, t`Removing Event Listeners`), el("div", { className: "note" }).append( el("p").append(T` - Unlike the native addEventListener/removeEventListener pattern, dd uses the ${el("a", { - textContent: "AbortSignal", ...references.mdn_abortListener })} for declarative approach for removal: + Unlike the native addEventListener/removeEventListener pattern, dd uses ${el("strong", "only")} + ${el("a", { textContent: "AbortSignal", ...references.mdn_abortListener })} for declarative removal: `) ), el(example, { src: fileURL("./components/examples/events/abortSignal.js"), page_id }), el("p").append(T` This is the same for signals (see next section) and works well with scopes and library extendability ( - see scopes and extensions section). + see scopes and extensions section — mainly ${el("code", "scope.signal")}). `), el(h3, t`Three Ways to Handle Events`), @@ -152,7 +152,7 @@ export function page({ pkg, info }){ el(h3, t`Lifecycle Events`), el("p").append(T` Addons are called immediately when an element is created, even before it’s connected to the live DOM. - You can think of an Addon as an "oncreate" event handler. + You can think of an Addon as an “oncreate” event handler. `), el("p").append(T` dd provides two additional lifecycle events that correspond to ${el("a", { textContent: diff --git a/docs/p04-signals.html.js b/docs/p04-signals.html.js index 9c7df1a..f1f1849 100644 --- a/docs/p04-signals.html.js +++ b/docs/p04-signals.html.js @@ -286,12 +286,13 @@ export function page({ pkg, info }){ el("dt", t`UI not updating when array/object changes`), el("dd", t`Use signal actions instead of direct mutation`), + el("dt", t`UI not updating`), + el("dd").append(T`Ensure you passing the (correct) signal not its value (${el("code", "signal")} vs + ${el("code", "signal.get()")})`), + 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`) ) diff --git a/docs/p05-scopes.html.js b/docs/p05-scopes.html.js index c112d38..81b6df9 100644 --- a/docs/p05-scopes.html.js +++ b/docs/p05-scopes.html.js @@ -68,7 +68,7 @@ export function page({ pkg, info }){ className: "my-component" }).append( el("h2", "Title"), - el("p", "Content") + el("p", "Content"), ); } ` }) diff --git a/docs/p07-debugging.html.js b/docs/p07-debugging.html.js index be3a14f..dd430a9 100644 --- a/docs/p07-debugging.html.js +++ b/docs/p07-debugging.html.js @@ -25,19 +25,32 @@ export function page({ pkg, info }){ el(h3, t`Debugging signals`), el("p").append(T` Signals are reactive primitives that update the UI when their values change. When debugging signals, - you need to track their values, understand their dependencies, and identify why updates are or aren't happening. + you need to track their values, understand their dependencies, and identify why updates are or aren’t + happening. `), el("h4", t`Inspecting signal values`), el("p").append(T` - The simplest way to debug a signal is to log its current value by calling the get method: + The simplest way to debug a signal is to log its current value by calling the get or valueOf method: `), el(code, { content: ` const signal = S(0); - console.log('Current value:', signal.get()); - // without triggering updates console.log('Current value:', signal.valueOf()); `, page_id }), + el("div", { className: "warning" }).append( + el("p").append(T` + ${el("code", "signal.get")} is OK, but in some situations may lead to unexpected results: + `), + el(code, { content: ` + const signal = S(0); + const derived = S(()=> { + console.log('Current value:', signal.get()); + // ↑ in rare cases this will register unwanted dependency + // but typically this is fine ↓ + return signal.get() + 1; + }); + ` }) + ), el("p").append(T` You can also monitor signal changes by adding a listener: `), @@ -58,83 +71,7 @@ export function page({ pkg, info }){ ), el(example, { src: fileURL("./components/examples/debugging/consoleLog.js"), page_id }), - el(h3, t`Common signal debugging issues`), - el("h4", t`Signal updates not triggering UI changes`), - el("p", t`If signal updates aren’t reflected in the UI, check:`), - el("ul").append( - el("li", t`That you’re using signal.set() to update the value, not modifying objects/arrays directly`), - el("li", t`For mutable objects, ensure you’re using actions or making proper copies before updating`), - el("li", t`That the signal is actually connected to the DOM element (check your S.el or attribute binding code)`) - ), - el(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }), - - el("h4", t`Memory leaks with signal listeners`), - el("p").append(T` - Signal listeners can cause memory leaks if not properly cleaned up. Always use AbortSignal - to cancel listeners. - `), - - el("h4", t`Performance issues with frequently updating signals`), - el("p", t`If you notice performance issues with signals that update very frequently:`), - el("ul").append( - el("li", t`Consider debouncing or throttling signal updates`), - el("li", t`Make sure derived signals don’t perform expensive calculations unnecessarily`), - el("li", t`Keep signal computations focused and minimal`) - ), - el(code, { src: fileURL("./components/examples/debugging/debouncing.js"), page_id }), - - el(h3, t`Browser DevTools tips for dd`), - el("p").append(T` - When debugging in the browser, dd provides several helpful DevTools-friendly features: - `), - - el("h4", t`Identifying components in the DOM`), - el("p").append(T` - dd marks components in the DOM with special comment nodes to help you identify component boundaries. - Components created with ${el("code", "el(ComponentFunction)")} are marked with comment nodes - ${el("code", ``)} and - includes: - `), - el("ul").append( - el("li", t`type - Identifies the type of marker ("component", "reactive", or "later")`), - el("li", t`name - The name of the component function`), - el("li", t`host - Indicates whether the host is "this" (for DocumentFragments) or "parentElement"`), - ), - - el("h4", t`Finding reactive elements in the DOM`), - el("p").append(T` - When using ${el("code", "S.el()")}, dd creates reactive elements in the DOM - that are automatically updated when signal values change. These elements are wrapped in special - comment nodes for debugging (to be true they are also used internally, so please do not edit them by hand): - `), - el(code, { src: fileURL("./components/examples/debugging/dom-reactive-mark.html"), page_id }), - el("p").append(T` - This is particularly useful when debugging why a reactive section isn’t updating as expected. - You can inspect the elements between the comment nodes to see their current state and the - signal connections through \`__dde_reactive\` of the host element. - `), - - el("h4", t`DOM inspection properties`), - el("p").append(T` - Elements created with the dd library have special properties to aid in debugging: - `), - el("p").append(T` - ${el("code", ".__dde_reactive")} - An array property on DOM elements that tracks signal-to-element - relationships. This allows you to quickly identify which elements are reactive and what signals they’re - bound to. Each entry in the array contains: - `), - el("ul").append( - el("li", t`A pair of signal and listener function: [signal, listener]`), - el("li", t`Additional context information about the element or attribute`), - el("li", t`Automatically managed by signal.el(), signal.observedAttributes(), and processReactiveAttribute()`) - ), - el("p").append(T` - These properties make it easier to understand the reactive structure of your application when inspecting - elements. - `), - el(example, { src: fileURL("./components/examples/signals/debugging-dom.js"), page_id }), - - el("h4", t`Examining signal connections`), + el("h4", t`Examining signal via DevTools`), el("p").append(T` ${el("code", ".__dde_signal")} - A Symbol property used to identify and store the internal state of signal objects. It contains the following information: @@ -148,22 +85,8 @@ export function page({ pkg, info }){ el("li", t`readonly: Boolean flag indicating if the signal is read-only`) ), el("p").append(T` - …to determine the current value of the signal, call ${el("code", "signal.valueOf()")}. - `), - el("p").append(T` - You can inspect (host) element relationships and bindings with signals in the DevTools console using - ${el("code", "$0.__dde_reactive")} (for currently selected element). In the console you will see a list of - ${el("code", `[ [ signal, listener ], element, property ]`)}, where: - `), - el("ul").append( - el("li", t`signal — the signal triggering the changes`), - el("li", t`listener — the listener function (this is an internal function for dd)`), - el("li", t`element — the DOM element that is bound to the signal`), - el("li", t`property — the attribute or property name which is changing based on the signal`), - ), - el("p").append(T` - …the structure of \`__dde_reactive\` utilizes the browser’s behavior of packing the first field, - so you can see the element and property that changes in the console right away. + …to determine the current value of the signal, call ${el("code", "signal.valueOf()")}. Don’t hesitate to + use the debugger to inspect the signal object. `), el("h4", t`Debugging with breakpoints`), @@ -185,5 +108,110 @@ export function page({ pkg, info }){ `) ), + el(h3, t`Common signal debugging issues`), + el("h4", t`Signal updates not triggering UI changes`), + el("p", t`If signal updates aren’t reflected in the UI, check:`), + el("ul").append( + el("li", t`That you’re using signal.set() to update the value, not modifying objects/arrays directly`), + el("li", t`For mutable objects, ensure you’re using actions or making proper copies before updating`), + el("li", t`That the signal is actually connected to the DOM element (check your S.el or attribute binding code)`) + ), + el(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }), + + el("h4", t`Memory leaks with signal listeners`), + el("p").append(T` + Signal listeners can cause memory leaks if not properly cleaned up. Always use AbortSignal + to cancel listeners when they are used ouside the dd knowledge (el, assign, S.el, … auto cleanup + unnecessarily signals automatically). + `), + + el("h4", t`Performance issues with frequently updating signals`), + el("p", t`If you notice performance issues with signals that update very frequently:`), + el("ul").append( + el("li", t`Consider debouncing or throttling signal updates`), + el("li", t`Make sure derived signals don’t perform expensive calculations unnecessarily`), + el("li", t`Keep signal computations focused and minimal`) + ), + el(code, { src: fileURL("./components/examples/debugging/debouncing.js"), page_id }), + + el(h3, t`Browser DevTools tips for components and reactivity`), + el("p").append(T` + When debugging in the browser, dd provides several helpful DevTools-friendly features: + `), + + el("h4", t`Finding reactive elements in the DOM`), + el("p").append(T` + When using ${el("code", "S.el()")}, dd creates reactive elements in the DOM + that are automatically updated when signal values change. These elements are wrapped in special + comment nodes for debugging (to be true they are also used internally, so please do not edit them by hand): + `), + el(code, { src: fileURL("./components/examples/debugging/dom-reactive-mark.html"), page_id }), + el("p").append(T` + This is particularly useful when debugging why a reactive section isn’t updating as expected. + You can inspect the elements between the comment nodes to see their current state and the + signal connections through \`__dde_reactive\` of the host element. + `), + + el("h4", t`Identifying components in the DOM`), + el("p").append(T` + dd marks components in the DOM with special comment nodes to help you identify component boundaries. + Components created with ${el("code", "el(MyComponent)")} are marked with comment nodes + ${el("code", ``)} and + includes: + `), + el("ul").append( + el("li", t`type - Identifies the type of marker ("component", "reactive", …)`), + el("li", t`name - The name of the component function`), + el("li", t`host - Indicates whether the host is "this" (for DocumentFragments) or "parentElement"`), + ), + + el("h4", t`Identifying reactive elements in the DOM`), + el("p").append(T` + You can inspect (host) element relationships and bindings with signals in the DevTools console using + ${el("code", "$0.__dde_reactive")} (for currently selected element). In the console you will see a list of + ${el("code", `[ [ signal, listener ], element, property ]`)}, where: + `), + el("ul").append( + el("li", t`signal — the signal triggering the changes`), + el("li", t`listener — the listener function (this is an internal function for dd)`), + el("li", t`element — the DOM element that is bound to the signal`), + el("li", t`property — the attribute or property name which is changing based on the signal`), + ), + el("p").append(T` + …the structure of \`__dde_reactive\` utilizes the browser’s behavior of packing the first field, + so you can see the element and property that changes in the console right away. These properties make it + easier to understand the reactive structure of your application when inspecting elements. + `), + el(example, { src: fileURL("./components/examples/signals/debugging-dom.js"), page_id }), + + el("p", { className: "note" }).append(T` + ${el("code", ".__dde_reactive")} - An array property on DOM elements that tracks signal-to-element + relationships. This allows you to quickly identify which elements are reactive and what signals they’re + bound to. Each entry in the array contains: + `), + + el("h4", t`Inspecting events and listeners in DevTools`), + el("p").append(T` + Modern browser DevTools provide built-in tools for inspecting event listeners attached to DOM elements. + For example, in Firefox and Chrome, you can: + `), + el("ol").append( + el("li", t`Select an element in the Elements/Inspector panel`), + el("li", t`Look for the "Event Listeners" tab or section`), + el("li", t`See all event listeners attached to the element, including those added by dd`) + ), + el("p").append(T` + Additionally, dd provides special markers in the DOM that help identify debug information. + Look for comments with ${el("code", "dde:mark")}, ${el("code", "dde:disconnected")} and ${el("code", + "__dde_reactive")} which indicate components, reactive regions, and other internal relationships: + `), + el("figure").append( + el("img", { + src: "./assets/devtools.png", + alt: "Screenshot of DevTools showing usage of “event” button to inspect event listeners", + }), + el("figcaption", t`Firefox DevTools showing dd debugging information with event listeners and reactive + markers`) + ), ); } diff --git a/docs/p08-extensions.html.js b/docs/p08-extensions.html.js index 9f0f47f..d37df79 100644 --- a/docs/p08-extensions.html.js +++ b/docs/p08-extensions.html.js @@ -91,7 +91,6 @@ export function page({ pkg, info }){ This approach enables better interoperability and future-proofing. `), el("div", { className: "illustration" }).append( - el("h4", t`Library-Independent vs. Library-Dependent Extension`), el("div", { className: "tabs" }).append( el("div", { className: "tab" }).append( el("h5", t`✅ Library-Independent`), diff --git a/docs/p09-optimization.html.js b/docs/p09-optimization.html.js index b019296..3bd5c58 100644 --- a/docs/p09-optimization.html.js +++ b/docs/p09-optimization.html.js @@ -189,6 +189,10 @@ export function page({ pkg, info }){ signal: controller.signal }); `, page_id }), + el("p").append(T` + You can use custom memo scope as function in (e. g. ${el("code", "S.el(signal, renderList)")}) and as + (Abort) signal use ${el("code", "scope.signal")}. + `), el("div", { className: "function-table" }).append( el("dl").append( @@ -210,10 +214,8 @@ export function page({ pkg, info }){ Signals are efficient, but unnecessary updates can impact performance: `), el("ul").append( - el("li", t`Avoid setting signal values that haven't actually changed`), el("li", t`For frequently updating values (like scroll position), consider debouncing`), el("li", t`Keep signal computations small and focused`), - el("li", t`Use derived signals to compute values only when dependencies change`) ), el("h4", t`Optimizing List Rendering`), @@ -250,32 +252,44 @@ export function page({ pkg, info }){ el("h4", t`Choosing the Right Optimization Approach`), el("p").append(T` - While memo is powerful, it's not always the best solution: + While ${el("code", "memo")} is powerful, different scenarios call for different optimization techniques: `), - el("table").append( - el("thead").append( - el("tr").append( - el("th", "Approach"), - el("th", "When to use") - ) - ), - el("tbody").append( - el("tr").append( - el("td", "memo"), - el("td", "Lists with stable items that infrequently change position") - ), - el("tr").append( - el("td", "Signal computations"), - el("td", "Derived values that depend on other signals") - ), - el("tr").append( - el("td", "Debouncing"), - el("td", "High-frequency events like scroll or resize") - ), - el("tr").append( - el("td", "Stateful components"), - el("td", "Complex components with internal state") - ) + el("div", { className: "function-table" }).append( + el("dl").append( + el("dt", t`memo`), + el("dd").append(T` + Best for list rendering where items rarely change or only their properties update. + ${el("code", "todos.map(todo => memo(todo.id, () => el(TodoItem, todo)))")} + Use when you need to cache and reuse DOM elements to avoid recreating them on every render. + `), + + el("dt", t`Signal computations`), + el("dd").append(T` + Ideal for derived values that depend on other signals and need to auto-update. + ${el("code", "const totalPrice = S(() => items.get().reduce((t, i) => t + i.price, 0))")} + Use when calculated values need to stay in sync with changing source data. + `), + + el("dt", t`Debouncing/Throttling`), + el("dd").append(T` + Essential for high-frequency events (scroll, resize) or rapidly changing input values. + ${el("code", "debounce(e => searchQuery.set(e.target.value), 300)")} + Use to limit the rate at which expensive operations execute when triggered by fast events. + `), + + el("dt", t`memo.scope`), + el("dd").append(T` + Useful for using memoization inside any function: ${el("code", + "const renderList = memo.scope(items => items.map(...))")}. Use to create isolated memoization + contexts that can be cleared or managed independently. + `), + + el("dt", t`Stateful components`), + el("dd").append(T` + For complex UI components with internal state management. + ${el("code", "el(ComplexComponent, { initialState, onChange })")} + Use when a component needs to encapsulate and manage its own state and lifecycle. + `) ) ),