1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2025-07-01 04:12:14 +02:00

🔤 updates texts

This commit is contained in:
2025-03-05 11:53:32 +01:00
parent 209fa49dee
commit e2df9705d1
9 changed files with 189 additions and 229 deletions

View File

@ -195,7 +195,7 @@ export function code({ id, src, content, language= "js", className= host.slice(1
dataJS= "todo"; dataJS= "todo";
} }
return el("div", { id, className, dataJS }).append( return el("div", { id, className, dataJS }).append(
el("code", { className: "language-"+language, textContent: content }) el("code", { className: "language-"+language, textContent: content.trim() })
); );
} }
let is_registered= {}; let is_registered= {};

View File

@ -1,35 +1,26 @@
/* PSEUDO-CODE!!! */
import { el } from "deka-dom-el"; import { el } from "deka-dom-el";
import { S } from "deka-dom-el/signals"; import { S } from "deka-dom-el/signals";
function component(){ function Counter() {
/* prepare changeable data */ // Define state
const dataA= S("data"); const count = S(0);
const dataB= S("data");
/* define data flow (can be asynchronous) */ // Define behavior
fetchAPI().then(data_new=> dataA(data_new)); const increment = () => count.set(count.get() + 1);
setTimeout(()=> dataB("DATA"));
/* declarative UI */ // Define data flow
return el().append( setTimeout(increment, 1000);
el("h1", { // or fetchAPI().then(increment);
textContent: "Example",
/* declarative attribute(s) */ // Declarative UI (how to render data/`count`)
classList: { declarative: dataB } // …automatically updates when changes
}), return el("div").append(
el("ul").append( // declarative element(s)
/* declarative element(s) */ el("p", S(() => "Count: " + count.get())),
S.el(dataA, data=> data.map(d=> el("li", d))) el("button", {
), onclick: increment,
el("ul").append( textContent: "Increment",
/* declarative component(s) */ // declarative attribute(s)
S.el(dataA, data=> data.map(d=> el(subcomponent, d))) disabled: S(() => count.get() >= 10)
) })
); );
} }
function subcomponent({ id }){
/* prepare changeable data */
const textContent= S("…");
/* define data flow (can be asynchronous) */
fetchAPI(id).then(text=> textContent(text));
/* declarative UI */
return el("li", { textContent, dataId: id });
}

View File

@ -1,31 +1,25 @@
/* PSEUDO-CODE!!! */ /* PSEUDO-CODE!!! */
import { el, on, scope } from "deka-dom-el"; import { el, scope } from "deka-dom-el";
function component(){ function Counter() {
const { host }= scope; const { host } = scope;
const ul= el("ul");
const ac= new AbortController(); let count = 0;
fetchAPI({ signal: ac.signal }).then(data=> { const counterText = el("p", "Count: 0");
data.forEach(d=> ul.append(el("li", d)));
}); // Manually update DOM element
host( const increment = () => {
/* element was remove before data fetched */ count++;
on.disconnected(()=> ac.abort()) counterText.textContent = "Count: " + count;
host().querySelector("button").disabled = count >= 10;
};
setTimeout(increment, 1000);
// or fetchAPI().then(increment);
return el("div").append(
counterText,
el("button", {
onclick: increment,
textContent: "Increment"
})
); );
return ul;
/**
* NEVER EVER!!
* let data;
* fetchAPI().then(d=> data= O(d));
*
* OR NEVER EVER!!
* const ul= el("ul");
* fetchAPI().then(d=> {
* const data= O("data");
* ul.append(el("li", data));
* });
*
* // THE HOST IS PROBABLY DIFFERENT THAN
* // YOU EXPECT AND OBSERVABLES MAY BE
* // UNEXPECTEDLY REMOVED!!!
* */
} }

View File

@ -0,0 +1,34 @@
/* PSEUDO-CODE!!! */
import { el, scope } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
function Counter() {
const { host } = scope;
let count = S(0);
const counterText = el("p", "Count: 0");
S.on(count, c=> counterText.textContent= "Count: " + c);
// Manually update DOM element
const increment = () => {
count.set(count.get() + 1);
// NEVER EVER
// count = S(count.get() + 1);
host().querySelector("button").disabled = count.get() >= 10;
};
// NEVER EVER
// setTimeout(()=> {
// const wrong= S(0);
// // THE HOST IS PROBABLY DIFFERENT THAN
// // YOU EXPECT AND OBSERVABLES MAY BE
// // UNEXPECTEDLY REMOVED!!!
// counterText.textContent= "Count: " + wrong.get();
// }, 1000);
return el("div").append(
counterText,
el("button", {
onclick: increment,
textContent: "Increment"
})
);
}

View File

@ -1,12 +1,12 @@
import { S } from "deka-dom-el/signals"; import { S } from "deka-dom-el/signals";
// α — `signal` represents a reactive value // A — `signal` represents a reactive value
const signal= S(0); const signal= S(0);
// β — just reacts on signal changes // B — just reacts on signal changes
S.on(signal, console.log); S.on(signal, console.log);
// γ — just updates the value // C — just updates the value
const update= ()=> signal.set(signal.get()+1); const update= ()=> signal.set(signal.get()+1);
update(); update();
const interval= 5*1000; const interval= 5*1000;
setTimeout(clearInterval, 10*interval, setTimeout(clearInterval, 10*interval,
setInterval(update, interval)); setInterval(update, interval));

View File

@ -208,6 +208,12 @@ pre code {
background-color: transparent; background-color: transparent;
padding: 0; padding: 0;
} }
.illustration:not(:has( .comparison)) pre {
background: none;
border-style: dashed !important;
width: fit-content;
padding: 1em 2em;
}
/* Layout */ /* Layout */
@media (min-width: 768px) { @media (min-width: 768px) {

View File

@ -55,8 +55,7 @@ export function page({ pkg, info }){
el("li", t`Automatic UI updates when data changes`), el("li", t`Automatic UI updates when data changes`),
el("li", t`Clean separation between data, logic, and UI`), el("li", t`Clean separation between data, logic, and UI`),
el("li", t`Small runtime with minimal overhead`), el("li", t`Small runtime with minimal overhead`),
el("li", t`Works seamlessly with DDE's DOM creation`), el("li").append(...T`${el("strong", "In future")} no dependencies or framework lock-in`)
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 }),
@ -68,17 +67,17 @@ export function page({ pkg, info }){
`), `),
el("div", { class: "signal-diagram" }).append( el("div", { class: "signal-diagram" }).append(
el("div", { class: "signal-part" }).append( el("div", { class: "signal-part" }).append(
el("h4", t`α: Create Signal`), el("h4", t`A: Create Signal`),
el(code, { content: "const count = S(0);", page_id }), el(code, { content: "const count = S(0);", page_id }),
el("p", t`Define a reactive value that can be observed and changed`) el("p", t`Define a reactive value that can be observed and changed`)
), ),
el("div", { class: "signal-part" }).append( el("div", { class: "signal-part" }).append(
el("h4", t`β: React to Changes`), el("h4", t`B: React to Changes`),
el(code, { content: "S.on(count, value => updateUI(value));", page_id }), el(code, { content: "S.on(count, value => updateUI(value));", page_id }),
el("p", t`Subscribe to signal changes with callbacks or effects`) el("p", t`Subscribe to signal changes with callbacks or effects`)
), ),
el("div", { class: "signal-part" }).append( el("div", { class: "signal-part" }).append(
el("h4", t`γ: Update Signal`), el("h4", t`C: Update Signal`),
el(code, { content: "count.set(count.get() + 1);", page_id }), el(code, { content: "count.set(count.get() + 1);", page_id }),
el("p", t`Modify the signal value, which automatically triggers updates`) el("p", t`Modify the signal value, which automatically triggers updates`)
) )
@ -90,7 +89,8 @@ export function page({ pkg, info }){
Signals implement the ${el("a", { textContent: t`Publishsubscribe pattern`, ...references.wiki_pubsub })}, 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 })}. 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, This architecture allows different parts of your application to stay synchronized through a shared signal,
without direct dependencies on each other. without direct dependencies on each other. Compare for example with ${el("a", { textContent:
t`fpubsub library`, ...references.fpubsub })}.
`) `)
), ),
@ -110,7 +110,8 @@ export function page({ pkg, info }){
el("dd", t`S.on(signal, callback) → runs callback whenever signal changes`), el("dd", t`S.on(signal, callback) → runs callback whenever signal changes`),
el("dt", t`Unsubscribing`), el("dt", t`Unsubscribing`),
el("dd", t`S.on(signal, callback, { signal: abortController.signal })`) el("dd").append(...T`S.on(signal, callback, { signal: abortController.signal }) → Similarly to the
${el("code", "on")} function to register DOM events listener.`)
) )
), ),
el("p").append(...T` el("p").append(...T`
@ -139,15 +140,6 @@ export function page({ pkg, info }){
el("div", { class: "illustration" }).append( el("div", { class: "illustration" }).append(
el("h4", t`Actions vs. Direct Mutation`), el("h4", t`Actions vs. Direct Mutation`),
el("div", { class: "comparison" }).append( 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("div", { class: "good-practice" }).append(
el("h5", t`✅ With Actions`), el("h5", t`✅ With Actions`),
el(code, { content: `const todos = S([], { el(code, { content: `const todos = S([], {
@ -159,8 +151,25 @@ items.push("New todo");
// Use the action // Use the action
S.action(todos, "add", "New todo");`, page_id }) S.action(todos, "add", "New todo");`, page_id })
) ),
el("div", { class: "bad-practice" }).append(
el("h5", t`❌ Without Actions`),
el(code, { content: `
const todos = S([]);
// Directly mutating the array
const items = todos.get();
items.push("New todo");
// This WON'T trigger updates!`, page_id }))
),
), ),
el("p").append(...T`
In some way, you can compare it with ${el("a", { textContent: "useReducer", ...references.mdn_use_reducer })}
hook from React. So, the ${el("code", "S(<data>, <actions>)")} pattern creates a store “machine”. We can
then invoke (dispatch) registered action by calling ${el("code", "S.action(<signal>, <name>, ...<args>)")}
after the action call the signal calls all its listeners. This can be stopped by calling
${el("code", "this.stopPropagation()")} in the method representing the given action. As it can be seen in
examples, the “store” value is available also in the function for given action (${el("code", "this.value")}).
`),
el(example, { src: fileURL("./components/examples/signals/actions-demo.js"), page_id }), el(example, { src: fileURL("./components/examples/signals/actions-demo.js"), page_id }),
el("p").append(...T` el("p").append(...T`
@ -182,9 +191,7 @@ S.action(todos, "add", "New todo");`, page_id })
${el("strong", "Special Action Methods")}: Signal actions can implement special lifecycle hooks: ${el("strong", "Special Action Methods")}: Signal actions can implement special lifecycle hooks:
`), `),
el("ul").append( el("ul").append(
el("li", t`[S.symbols.onclear]() - Called when the signal is cleared`), el("li", t`[S.symbols.onclear]() - Called when the signal is cleared. Use it to clean up resources.`),
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`)
) )
), ),
@ -229,12 +236,12 @@ S.action(items, "push", "Dragonfruit"); // List updates automatically`, page_id
) )
), ),
el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }),
el("p").append(...T` el("p").append(...T`
The ${el("code", "assign")} and ${el("code", "el")} functions detect signals automatically and handle binding. 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 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("code", "classList")} for fine-grained control over specific attribute types.
`), `),
el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }),
el("p").append(...T` el("p").append(...T`
${el("code", "S.el()")} is especially powerful for conditional rendering and lists: ${el("code", "S.el()")} is especially powerful for conditional rendering and lists:

View File

@ -30,53 +30,44 @@ export function page({ pkg, info }){
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("h2", t`Building Maintainable UIs with Scopes and Components`), el("h2", t`Building Maintainable UIs with Scopes and Components`),
el("p").append(...T` el("p").append(...T`
Scopes provide a structured way to organize your UI code into reusable components that properly For state-less components we can use functions as UI components (see “Elements” page). But in real life,
manage their lifecycle, handle cleanup, and maintain clear boundaries between different parts of your application. we may need to handle the component live-cycle and provide JavaScript the way to properly use
the ${el("a", { textContent: t`Garbage collection`, ...references.garbage_collection })}.
`), `),
el("div", { className: "callout" }).append(
el("h4", t`Why Use Scopes?`),
el("ul").append(
el("li", t`Automatic resource cleanup when components are removed from DOM`),
el("li", t`Clear component boundaries with explicit host elements`),
el("li", t`Simplified event handling with proper "this" binding`),
el("li", t`Seamless integration with signals for reactive components`),
el("li", t`Better memory management with ${el("a", { textContent: t`GC`, ...references.garbage_collection })}`)
)
),
el(code, { src: fileURL("./components/examples/scopes/intro.js"), page_id }), el(code, { src: fileURL("./components/examples/scopes/intro.js"), page_id }),
el("p").append(...T`The library therefore use ${el("em", t`scopes`)} to provide these functionalities.`),
el(h3, t`Understanding Host Elements and Scopes`), el(h3, t`Understanding Host Elements and Scopes`),
el("p").append(...T`
The ${el("strong", "host")} is the name for the element representing the component. This is typically
element returned by function. To get reference, you can use ${el("code", "scope.host()")} to applly addons
just use ${el("code", "scope.host(...<addons>)")}.
`),
el("div", { className: "illustration" }).append( el("div", { className: "illustration" }).append(
el("h4", t`Component Anatomy`), el("h4", t`Component Anatomy`),
el("pre").append(el("code", ` el("pre").append(el("code", `
┌─────────────────────────────────┐ // 1. Component scope created
│ // 1. Component scope created │ el(MyComponent);
│ el(MyComponent); │
│ │ function MyComponent() {
│ function MyComponent() { │ // 2. access the host element
// 2. access the host element │ const { host } = scope;
│ const { host } = scope; │
// 3. Add behavior to host
// 3. Add behavior to host │ host(
host( │ on.click(handleClick)
on.click(handleClick) │ );
│ ); │
│ │ // 4. Return the host element
│ // 4. Return the host element │ return el("div", {
return el("div", { │ className: "my-component"
className: "my-component" │ }).append(
}).append( │ el("h2", "Title"),
el("h2", "Title"), │ el("p", "Content")
el("p", "Content") │ );
│ ); │ }
│ } │ `.trim()))
└─────────────────────────────────┘
`))
), ),
el("p").append(...T`
The ${el("strong", "host element")} is the root element of your component - typically the element returned
by your component function. It serves as the identity of your component in the DOM.
`),
el("div", { className: "function-table" }).append( el("div", { className: "function-table" }).append(
el("h4", t`scope.host()`), el("h4", t`scope.host()`),
el("dl").append( el("dl").append(
@ -91,34 +82,24 @@ export function page({ pkg, info }){
el("div", { className: "tip" }).append( el("div", { className: "tip" }).append(
el("p").append(...T` el("p").append(...T`
${el("strong", "Best Practice:")} Always capture the host reference at the beginning of your component function ${el("strong", "Best Practice:")} Always capture the host reference at the beginning of your component
using ${el("code", "const { host } = scope")} to avoid scope-related issues, especially with asynchronous code. function using ${el("code", "const { host } = scope")} to avoid scope-related issues, especially with
`) asynchronous code.
), `),
el(h3, t`Class-Based Components`),
el("p").append(...T`
While functional components are the primary pattern in DDE, you can also create class-based components
for more structured organization of component logic.
`),
el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }),
el("p").append(...T`
This pattern can be useful when:
`),
el("ul").append(
el("li", t`You have complex component logic that benefits from object-oriented organization`),
el("li", t`You need private methods and properties for your component`),
el("li", t`You're transitioning from another class-based component system`)
),
el("div", { className: "tip" }).append(
el("p").append(...T` el("p").append(...T`
${el("strong", "Note:")} Even with class-based components, follow the best practice of storing the host reference If you are interested in the implementation details, see Class-Based Components section.
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`Class-Based Components`),
el("p").append(...T`
While functional components are the primary pattern in DDE, you can also create class-based components.
For this, we implement function ${el("code", "elClass")} and use it to demonstrate implementation details
for better understanding of the scope logic.
`),
el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }),
el(h3, t`Automatic Cleanup with Scopes`), el(h3, t`Automatic Cleanup with Scopes`),
el("p").append(...T` el("p").append(...T`
One of the most powerful features of scopes is automatic cleanup when components are removed from the DOM. One of the most powerful features of scopes is automatic cleanup when components are removed from the DOM.
@ -149,77 +130,38 @@ export function page({ pkg, info }){
el(h3, t`Declarative vs Imperative Components`), el(h3, t`Declarative vs Imperative Components`),
el("p").append(...T` el("p").append(...T`
Scopes work best with a declarative approach to UI building, especially when combined The library DOM API and signals works best when used declaratively. It means, you split your app logic
with ${el("a", { textContent: "signals", ...references.signals })} for state management. into three parts as it was itroduced in ${el("a", { textContent: "Signals", ...references.signals })}.
`), `),
el("div", { className: "note" }).append(
el("p").append(...T`
Strictly speaking, the imperative way of using the library is not prohibited. Just be careful (rather avoid)
mixing declarative approach (using signals) and imperative manipulation of elements.
`)
),
el("div", { className: "tabs" }).append( el("div", { className: "tabs" }).append(
el("div", { className: "tab", "data-tab": "declarative" }).append( el("div", { className: "tab", "data-tab": "declarative" }).append(
el("h4", t`✅ Declarative Approach`), el("h4", t`✅ Declarative Approach`),
el("p", t`Define what your UI should look like based on state:`), el("p", t`Define what your UI should look like based on state:`),
el("pre").append(el("code", `function Counter() { el(code, { src: fileURL("./components/examples/scopes/declarative.js"), page_id }),
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", { className: "tab", "data-tab": "imperative" }).append( el("div", { className: "tab", "data-tab": "imperative" }).append(
el("h4", t`⚠️ Imperative Approach`), el("h4", t`⚠️ Imperative Approach`),
el("p", t`Manually update the DOM in response to events:`), el("p", t`Manually update the DOM in response to events:`),
el("pre").append(el("code", `function Counter() { el(code, { src: fileURL("./components/examples/scopes/imperative.js"), page_id }),
const { host } = scope; ),
el("div", { className: "tab", "data-tab": "imperative" }).append(
let count = 0; el("h4", t`❌ Mixed Approach`),
const counterText = el("p", "Count: 0"); el("p", t`Just AVOID:`),
el(code, { src: fileURL("./components/examples/scopes/mixed.js"), page_id }),
// 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("div", { className: "note" }).append(
el("p").append(...T`
While DDE supports both declarative and imperative approaches, the declarative style is recommended
as it leads to more maintainable code with fewer opportunities for bugs. Signals handle the complexity
of keeping your UI in sync with your data.
`)
),
el(code, { src: fileURL("./components/examples/scopes/imperative.js"), page_id }),
el(h3, t`Best Practices for Scopes and Components`), el(h3, t`Best Practices for Scopes and Components`),
el("ol").append( el("ol").append(
el("li").append(...T` el("li").append(...T`
${el("strong", "Capture host early:")} Use ${el("code", "const { host } = scope")} at component start ${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("li").append(...T`
${el("strong", "Prefer declarative patterns:")} Use signals to drive UI updates rather than manual DOM manipulation ${el("strong", "Prefer declarative patterns:")} Use signals to drive UI updates rather than manual DOM manipulation
`), `),

View File

@ -201,22 +201,16 @@ export function page({ pkg, info }){
el("div", { className: "illustration" }).append( el("div", { className: "illustration" }).append(
el("h4", t`Shadow DOM Encapsulation`), el("h4", t`Shadow DOM Encapsulation`),
el("pre").append(el("code", ` el("pre").append(el("code", `
┌─────────────────────────────────┐ <my-custom-element>
│ <my-custom-element> │
│ │ ┌─────────────────────────┐
┌─────────────────────────┐ │ #shadow-root
│ │ #shadow-root │ │
│ │ Created with DDE:
│ │ Created with DDE: │ │ ┌──────────────────┐
┌─────────────────┐ │ │ <div>
│ │ │ <div> │ │ │ <h2>Title</h2>
<h2>Title</h2> │ │ │ <p>Content</p>
│ │ │ <p>Content</p> │ │ │
│ │ └─────────────────┘ │ │
│ │ │ │
│ └─────────────────────────┘ │
│ │
└─────────────────────────────────┘
`)) `))
), ),
el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js"), page_id }), el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js"), page_id }),
@ -229,9 +223,10 @@ export function page({ pkg, info }){
el(h3, t`Working with Slots`), el(h3, t`Working with Slots`),
el("p").append(...T` el("p").append(...T`
Slots allow users of your component to insert content inside it. When using DDE, you can simulate the Besides the encapsulation, the Shadow DOM allows for using the ${el("a", references.mdn_shadow_dom_slot).append(
slot mechanism with the ${el("code", "simulateSlots")} function: el("strong", t`<slot>`), t` element(s)`)}. You can simulate this feature using ${el("code", "simulateSlots")}:
`), `),
el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js"), page_id }),
el("div", { className: "function-table" }).append( el("div", { className: "function-table" }).append(
el("h4", t`simulateSlots`), el("h4", t`simulateSlots`),
el("dl").append( el("dl").append(
@ -241,15 +236,6 @@ export function page({ pkg, info }){
el("dd", t`A mapping object of slot names to DOM elements`) el("dd", t`A mapping object of slot names to DOM elements`)
) )
), ),
el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js"), page_id }),
el("div", { className: "tip" }).append(
el("p").append(...T`
${el("strong", "When to use simulateSlots:")} This approach is useful when you need to distribute
content from the light DOM into specific locations in the shadow DOM, particularly in environments
where native slots might not be fully supported.
`)
),
el(h3, t`Best Practices for Web Components with DDE`), el(h3, t`Best Practices for Web Components with DDE`),
el("p").append(...T` el("p").append(...T`