mirror of
https://github.com/jaandrle/deka-dom-el
synced 2025-04-01 19:55:53 +02:00
🔤 UI/UX/wording
This commit is contained in:
parent
57a5ff2dfe
commit
7f4787d704
72
README.md
72
README.md
@ -3,7 +3,7 @@
|
||||
| [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el)
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180">
|
||||
<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180">
|
||||
</p>
|
||||
|
||||
# Deka DOM Elements
|
||||
@ -15,40 +15,40 @@
|
||||
```javascript
|
||||
// 🌟 Reactive component with clear separation of concerns
|
||||
document.body.append(
|
||||
el(EmojiCounter, { initial: "🚀" })
|
||||
el(EmojiCounter, { initial: "🚀" })
|
||||
);
|
||||
|
||||
function EmojiCounter({ initial }) {
|
||||
// ✨ State - Define reactive data
|
||||
const count = S(0);
|
||||
const emoji = S(initial);
|
||||
// ✨ State - Define reactive data
|
||||
const count = S(0);
|
||||
const emoji = S(initial);
|
||||
|
||||
/** @param {HTMLOptionElement} el */
|
||||
const isSelected= el=> (el.selected= el.value===initial);
|
||||
|
||||
// 🔄 View - UI updates automatically when signals change
|
||||
return el().append(
|
||||
el("p", {
|
||||
className: "output",
|
||||
textContent: S(() =>
|
||||
`Hello World ${emoji.get().repeat(clicks.get())}`),
|
||||
}),
|
||||
|
||||
// 🎮 Controls - Update state on events
|
||||
el("button", { textContent: "Add Emoji" },
|
||||
on("click", () => count.set(count.get() + 1))
|
||||
),
|
||||
|
||||
el("select", null, on("change", e => emoji.set(e.target.value)))
|
||||
/** @param {HTMLOptionElement} el */
|
||||
const isSelected= el=> (el.selected= el.value===initial);
|
||||
|
||||
// 🔄 View - UI updates automatically when signals change
|
||||
return el().append(
|
||||
el("p", {
|
||||
className: "output",
|
||||
textContent: S(() =>
|
||||
`Hello World ${emoji.get().repeat(clicks.get())}`),
|
||||
}),
|
||||
|
||||
// 🎮 Controls - Update state on events
|
||||
el("button", { textContent: "Add Emoji" },
|
||||
on("click", () => count.set(count.get() + 1))
|
||||
),
|
||||
|
||||
el("select", null, on("change", e => emoji.set(e.target.value)))
|
||||
.append(
|
||||
el(Option, "🎉", isSelected),
|
||||
el(Option, "🚀", isSelected),
|
||||
el(Option, "💖", isSelected),
|
||||
)
|
||||
);
|
||||
el(Option, "🎉", isSelected),
|
||||
el(Option, "🚀", isSelected),
|
||||
el(Option, "💖", isSelected),
|
||||
)
|
||||
);
|
||||
}
|
||||
function Option({ textContent }){
|
||||
return Ol("option", { value: textContent, textContent });
|
||||
return Ol("option", { value: textContent, textContent });
|
||||
}
|
||||
```
|
||||
|
||||
@ -68,11 +68,16 @@ Creating reactive elements, components, and Web Components using the native
|
||||
|
||||
## Why Another Library?
|
||||
|
||||
This library bridges the gap between minimal solutions like van/hyperscript and more comprehensive frameworks like [solid-js](https://github.com/solidjs/solid), offering a balanced trade-off between size, complexity, and usability.
|
||||
This library bridges the gap between minimal solutions like van/hyperscript and more comprehensive frameworks like
|
||||
[solid-js](https://github.com/solidjs/solid), offering a balanced trade-off between size, complexity, and usability.
|
||||
|
||||
Following functional programming principles, Deka DOM Elements starts with pure JavaScript (DOM API) and gradually adds auxiliary functions. These range from minor improvements to advanced features for building complete declarative reactive UI templates.
|
||||
Following functional programming principles, Deka DOM Elements starts with pure JavaScript (DOM API) and gradually adds
|
||||
auxiliary functions. These range from minor improvements to advanced features for building complete declarative
|
||||
reactive UI templates.
|
||||
|
||||
A key advantage: any internal function (`assign`, `classListDeclarative`, `on`, `dispatchEvent`, `S`, etc.) can be used independently while also working seamlessly together. This modular approach makes it easier to integrate the library into existing projects.
|
||||
A key advantage: any internal function (`assign`, `classListDeclarative`, `on`, `dispatchEvent`, `S`, etc.) can be used
|
||||
independently while also working seamlessly together. This modular approach makes it easier to integrate the library
|
||||
into existing projects.
|
||||
|
||||
## Getting Started
|
||||
|
||||
@ -88,7 +93,7 @@ A key advantage: any internal function (`assign`, `classListDeclarative`, `on`,
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/dde-with-signals.min.js"></script>
|
||||
<script type="module">
|
||||
const { el, S } = dde;
|
||||
const { el, S } = dde;
|
||||
</script>
|
||||
```
|
||||
|
||||
@ -112,4 +117,5 @@ Signals are the reactive backbone of Deka DOM Elements:
|
||||
- [adamhaile/S](https://github.com/adamhaile/S) - Simple, clean, fast reactive programming
|
||||
- [hyperhype/hyperscript](https://github.com/hyperhype/hyperscript) - Create HyperText with JavaScript
|
||||
- [potch/signals](https://github.com/potch/signals) - A small reactive signals library
|
||||
- [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) - Functional DOM components without JSX/virtual DOM
|
||||
- [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) -
|
||||
Functional DOM components without JSX/virtual DOM
|
||||
|
@ -42,7 +42,7 @@ s.echo(styles.content).to(path_target.css+styles.name);
|
||||
// Copy assets
|
||||
echo("Copying assets…");
|
||||
if(s.test("-d", "docs/assets")) {
|
||||
s.cp("-r", "docs/assets/*", path_target.assets);
|
||||
s.cp("-r", "docs/assets/*", path_target.assets);
|
||||
}
|
||||
|
||||
dispatchEvent("onssrend");
|
||||
|
35
dist/esm-with-signals.d.min.ts
vendored
35
dist/esm-with-signals.d.min.ts
vendored
@ -72,6 +72,20 @@ export function assignAttribute<El extends SupportedElement, ATT extends keyof E
|
||||
): ElementAttributes<El>[ATT]
|
||||
|
||||
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
|
||||
export namespace el {
|
||||
/**
|
||||
* Creates a marker comment for elements
|
||||
*
|
||||
* @param attrs - Marker attributes
|
||||
* @param [is_open=false] - Whether the marker is open-ended
|
||||
* @returns Comment node marker
|
||||
*/
|
||||
export function mark(
|
||||
attrs: { type: "component"|"reactive"|"later", name?: string, host?: "this"|"parentElement" },
|
||||
is_open?: boolean
|
||||
): Comment;
|
||||
}
|
||||
|
||||
export function el<
|
||||
A extends ddeComponentAttributes,
|
||||
EL extends SupportedElement | ddeDocumentFragment
|
||||
@ -251,6 +265,27 @@ export function customElementWithDDE<EL extends (new ()=> HTMLElement)>(custom_e
|
||||
export function lifecyclesToEvents<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL
|
||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>
|
||||
|
||||
/**
|
||||
* This is used primarly for server side rendering. To be sure that all async operations
|
||||
* are finished before the page is sent to the client.
|
||||
* ```
|
||||
* // on component
|
||||
* function component(){
|
||||
* …
|
||||
* queue(fetch(...).then(...));
|
||||
* }
|
||||
*
|
||||
* // building the page
|
||||
* async function build(){
|
||||
* const { component }= await import("./component.js");
|
||||
* document.body.append(el(component));
|
||||
* await queue();
|
||||
* retutn document.body.innerHTML;
|
||||
* }
|
||||
* ```
|
||||
* */
|
||||
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||
|
||||
/* TypeScript MEH */
|
||||
declare global{
|
||||
type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
|
||||
|
35
dist/esm-with-signals.d.ts
vendored
35
dist/esm-with-signals.d.ts
vendored
@ -72,6 +72,20 @@ export function assignAttribute<El extends SupportedElement, ATT extends keyof E
|
||||
): ElementAttributes<El>[ATT]
|
||||
|
||||
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
|
||||
export namespace el {
|
||||
/**
|
||||
* Creates a marker comment for elements
|
||||
*
|
||||
* @param attrs - Marker attributes
|
||||
* @param [is_open=false] - Whether the marker is open-ended
|
||||
* @returns Comment node marker
|
||||
*/
|
||||
export function mark(
|
||||
attrs: { type: "component"|"reactive"|"later", name?: string, host?: "this"|"parentElement" },
|
||||
is_open?: boolean
|
||||
): Comment;
|
||||
}
|
||||
|
||||
export function el<
|
||||
A extends ddeComponentAttributes,
|
||||
EL extends SupportedElement | ddeDocumentFragment
|
||||
@ -251,6 +265,27 @@ export function customElementWithDDE<EL extends (new ()=> HTMLElement)>(custom_e
|
||||
export function lifecyclesToEvents<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL
|
||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>
|
||||
|
||||
/**
|
||||
* This is used primarly for server side rendering. To be sure that all async operations
|
||||
* are finished before the page is sent to the client.
|
||||
* ```
|
||||
* // on component
|
||||
* function component(){
|
||||
* …
|
||||
* queue(fetch(...).then(...));
|
||||
* }
|
||||
*
|
||||
* // building the page
|
||||
* async function build(){
|
||||
* const { component }= await import("./component.js");
|
||||
* document.body.append(el(component));
|
||||
* await queue();
|
||||
* retutn document.body.innerHTML;
|
||||
* }
|
||||
* ```
|
||||
* */
|
||||
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||
|
||||
/* TypeScript MEH */
|
||||
declare global{
|
||||
type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
|
||||
|
35
dist/esm.d.min.ts
vendored
35
dist/esm.d.min.ts
vendored
@ -72,6 +72,20 @@ export function assignAttribute<El extends SupportedElement, ATT extends keyof E
|
||||
): ElementAttributes<El>[ATT]
|
||||
|
||||
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
|
||||
export namespace el {
|
||||
/**
|
||||
* Creates a marker comment for elements
|
||||
*
|
||||
* @param attrs - Marker attributes
|
||||
* @param [is_open=false] - Whether the marker is open-ended
|
||||
* @returns Comment node marker
|
||||
*/
|
||||
export function mark(
|
||||
attrs: { type: "component"|"reactive"|"later", name?: string, host?: "this"|"parentElement" },
|
||||
is_open?: boolean
|
||||
): Comment;
|
||||
}
|
||||
|
||||
export function el<
|
||||
A extends ddeComponentAttributes,
|
||||
EL extends SupportedElement | ddeDocumentFragment
|
||||
@ -251,6 +265,27 @@ export function customElementWithDDE<EL extends (new ()=> HTMLElement)>(custom_e
|
||||
export function lifecyclesToEvents<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL
|
||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>
|
||||
|
||||
/**
|
||||
* This is used primarly for server side rendering. To be sure that all async operations
|
||||
* are finished before the page is sent to the client.
|
||||
* ```
|
||||
* // on component
|
||||
* function component(){
|
||||
* …
|
||||
* queue(fetch(...).then(...));
|
||||
* }
|
||||
*
|
||||
* // building the page
|
||||
* async function build(){
|
||||
* const { component }= await import("./component.js");
|
||||
* document.body.append(el(component));
|
||||
* await queue();
|
||||
* retutn document.body.innerHTML;
|
||||
* }
|
||||
* ```
|
||||
* */
|
||||
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||
|
||||
/* TypeScript MEH */
|
||||
declare global{
|
||||
type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
|
||||
|
35
dist/esm.d.ts
vendored
35
dist/esm.d.ts
vendored
@ -72,6 +72,20 @@ export function assignAttribute<El extends SupportedElement, ATT extends keyof E
|
||||
): ElementAttributes<El>[ATT]
|
||||
|
||||
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
|
||||
export namespace el {
|
||||
/**
|
||||
* Creates a marker comment for elements
|
||||
*
|
||||
* @param attrs - Marker attributes
|
||||
* @param [is_open=false] - Whether the marker is open-ended
|
||||
* @returns Comment node marker
|
||||
*/
|
||||
export function mark(
|
||||
attrs: { type: "component"|"reactive"|"later", name?: string, host?: "this"|"parentElement" },
|
||||
is_open?: boolean
|
||||
): Comment;
|
||||
}
|
||||
|
||||
export function el<
|
||||
A extends ddeComponentAttributes,
|
||||
EL extends SupportedElement | ddeDocumentFragment
|
||||
@ -251,6 +265,27 @@ export function customElementWithDDE<EL extends (new ()=> HTMLElement)>(custom_e
|
||||
export function lifecyclesToEvents<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL
|
||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>
|
||||
|
||||
/**
|
||||
* This is used primarly for server side rendering. To be sure that all async operations
|
||||
* are finished before the page is sent to the client.
|
||||
* ```
|
||||
* // on component
|
||||
* function component(){
|
||||
* …
|
||||
* queue(fetch(...).then(...));
|
||||
* }
|
||||
*
|
||||
* // building the page
|
||||
* async function build(){
|
||||
* const { component }= await import("./component.js");
|
||||
* document.body.append(el(component));
|
||||
* await queue();
|
||||
* retutn document.body.innerHTML;
|
||||
* }
|
||||
* ```
|
||||
* */
|
||||
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||
|
||||
/* TypeScript MEH */
|
||||
declare global{
|
||||
type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
|
||||
|
@ -1,28 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#333333" />
|
||||
<stop offset="100%" stop-color="#222222" />
|
||||
</linearGradient>
|
||||
<linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#e32c2c" />
|
||||
<stop offset="100%" stop-color="#ff5252" />
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
|
||||
<feDropShadow dx="0" dy="0.5" stdDeviation="0.5" flood-color="#000" flood-opacity="0.3"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Square background with rounded corners -->
|
||||
<rect x="2" y="2" width="28" height="28" rx="4" ry="4" fill="url(#bgGradient)" />
|
||||
|
||||
<!-- Subtle code brackets as background element -->
|
||||
<g opacity="0.15" fill="#fff">
|
||||
<path d="M10,7.5 L6.25,16 L10,24.5" stroke="#fff" stroke-width="1" fill="none"/>
|
||||
<path d="M22,7.5 L25.75,16 L22,24.5" stroke="#fff" stroke-width="1" fill="none"/>
|
||||
</g>
|
||||
|
||||
<!-- lowercase dde letters -->
|
||||
<text x="16" y="21" text-anchor="middle" font-family="'Fira Code', 'JetBrains Mono', 'Source Code Pro', 'SF Mono', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace" font-size="14" font-weight="bold" fill="url(#textGradient)" filter="url(#shadow)">dde</text>
|
||||
<defs>
|
||||
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#333333" />
|
||||
<stop offset="100%" stop-color="#222222" />
|
||||
</linearGradient>
|
||||
<linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#e32c2c" />
|
||||
<stop offset="100%" stop-color="#ff5252" />
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
|
||||
<feDropShadow dx="0" dy="0.5" stdDeviation="0.5" flood-color="#000" flood-opacity="0.3"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Square background with rounded corners -->
|
||||
<rect x="2" y="2" width="28" height="28" rx="4" ry="4" fill="url(#bgGradient)" />
|
||||
|
||||
<!-- Subtle code brackets as background element -->
|
||||
<g opacity="0.15" fill="#fff">
|
||||
<path d="M10,7.5 L6.25,16 L10,24.5" stroke="#fff" stroke-width="1" fill="none"/>
|
||||
<path d="M22,7.5 L25.75,16 L22,24.5" stroke="#fff" stroke-width="1" fill="none"/>
|
||||
</g>
|
||||
|
||||
<!-- lowercase dde letters -->
|
||||
<text x="16" y="21" text-anchor="middle" font-family="'Fira Code', 'JetBrains Mono', 'Source Code Pro', 'SF Mono', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace" font-size="14" font-weight="bold" fill="url(#textGradient)" filter="url(#shadow)">dde</text>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@ -1,33 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="256" height="256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Gradients and effects -->
|
||||
<defs>
|
||||
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#333333" />
|
||||
<stop offset="100%" stop-color="#222222" />
|
||||
</linearGradient>
|
||||
<linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#e32c2c" />
|
||||
<stop offset="100%" stop-color="#ff5252" />
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
|
||||
<feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="#000" flood-opacity="0.3"/>
|
||||
</filter>
|
||||
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feGaussianBlur stdDeviation="4" result="blur"/>
|
||||
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Square background with rounded corners -->
|
||||
<rect x="18" y="18" width="220" height="220" rx="20" ry="20" fill="url(#bgGradient)" />
|
||||
|
||||
<!-- Subtle code brackets as background element -->
|
||||
<g opacity="0.15" fill="#fff" filter="url(#glow)">
|
||||
<path d="M80,60 L50,128 L80,196" stroke="#fff" stroke-width="8" fill="none"/>
|
||||
<path d="M176,60 L206,128 L176,196" stroke="#fff" stroke-width="8" fill="none"/>
|
||||
</g>
|
||||
|
||||
<!-- lowercase dde letters with shadow effect -->
|
||||
<text x="128" y="154" text-anchor="middle" font-family="'Fira Code', 'JetBrains Mono', 'Source Code Pro', 'SF Mono', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace" font-size="100" font-weight="bold" fill="url(#textGradient)" filter="url(#shadow)">dde</text>
|
||||
<!-- Gradients and effects -->
|
||||
<defs>
|
||||
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#333333" />
|
||||
<stop offset="100%" stop-color="#222222" />
|
||||
</linearGradient>
|
||||
<linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#e32c2c" />
|
||||
<stop offset="100%" stop-color="#ff5252" />
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
|
||||
<feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="#000" flood-opacity="0.3"/>
|
||||
</filter>
|
||||
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feGaussianBlur stdDeviation="4" result="blur"/>
|
||||
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Square background with rounded corners -->
|
||||
<rect x="18" y="18" width="220" height="220" rx="20" ry="20" fill="url(#bgGradient)" />
|
||||
|
||||
<!-- Subtle code brackets as background element -->
|
||||
<g opacity="0.15" fill="#fff" filter="url(#glow)">
|
||||
<path d="M80,60 L50,128 L80,196" stroke="#fff" stroke-width="8" fill="none"/>
|
||||
<path d="M176,60 L206,128 L176,196" stroke="#fff" stroke-width="8" fill="none"/>
|
||||
</g>
|
||||
|
||||
<!-- lowercase dde letters with shadow effect -->
|
||||
<text x="128" y="154" text-anchor="middle" font-family="'Fira Code', 'JetBrains Mono', 'Source Code Pro', 'SF Mono', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace" font-size="100" font-weight="bold" fill="url(#textGradient)" filter="url(#shadow)">dde</text>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.6 KiB |
@ -2,9 +2,9 @@ import { el } from "deka-dom-el";
|
||||
|
||||
// Create element with properties
|
||||
const button = el("button", {
|
||||
textContent: "Click me",
|
||||
className: "primary",
|
||||
disabled: true
|
||||
textContent: "Click me",
|
||||
className: "primary",
|
||||
disabled: true
|
||||
});
|
||||
|
||||
// Shorter and more expressive
|
||||
|
@ -6,12 +6,12 @@ button.disabled = true;
|
||||
|
||||
// Or using Object.assign()
|
||||
const button2 = Object.assign(
|
||||
document.createElement('button'),
|
||||
{
|
||||
textContent: "Click me",
|
||||
className: "primary",
|
||||
disabled: true
|
||||
}
|
||||
document.createElement('button'),
|
||||
{
|
||||
textContent: "Click me",
|
||||
className: "primary",
|
||||
disabled: true
|
||||
}
|
||||
);
|
||||
|
||||
// Add to DOM
|
||||
|
@ -2,7 +2,7 @@ import { el, on } from "deka-dom-el";
|
||||
|
||||
// Third approach - append with on addon
|
||||
el("button", {
|
||||
textContent: "click me"
|
||||
textContent: "click me"
|
||||
}).append(
|
||||
on("click", (e) => console.log("Clicked!", e))
|
||||
on("click", (e) => console.log("Clicked!", e))
|
||||
);
|
@ -2,6 +2,6 @@ import { el } from "deka-dom-el";
|
||||
|
||||
// Using events with HTML attribute style
|
||||
el("button", {
|
||||
textContent: "click me",
|
||||
"=onclick": "console.log(event)"
|
||||
textContent: "click me",
|
||||
"=onclick": "console.log(event)"
|
||||
});
|
@ -2,7 +2,7 @@ import { el, on } from "deka-dom-el";
|
||||
|
||||
// Using events as addons - chainable approach
|
||||
el("button", {
|
||||
textContent: "click me",
|
||||
},
|
||||
on("click", (e) => console.log("Clicked!", e))
|
||||
textContent: "click me",
|
||||
},
|
||||
on("click", (e) => console.log("Clicked!", e))
|
||||
);
|
@ -2,6 +2,6 @@ import { el } from "deka-dom-el";
|
||||
|
||||
// Using events with property assignment
|
||||
el("button", {
|
||||
textContent: "click me",
|
||||
onclick: console.log
|
||||
textContent: "click me",
|
||||
onclick: console.log
|
||||
});
|
@ -4,11 +4,11 @@ const button = document.querySelector('button');
|
||||
let count = 0;
|
||||
|
||||
button.addEventListener('click', () => {
|
||||
count++;
|
||||
document.querySelector('p').textContent =
|
||||
'Clicked ' + count + ' times';
|
||||
count++;
|
||||
document.querySelector('p').textContent =
|
||||
'Clicked ' + count + ' times';
|
||||
|
||||
if (count > 10) {
|
||||
button.disabled = true;
|
||||
}
|
||||
if (count > 10) {
|
||||
button.disabled = true;
|
||||
}
|
||||
});
|
||||
|
@ -4,11 +4,11 @@ const count = S(0);
|
||||
|
||||
// 2. React to state changes
|
||||
S.on(count, value => {
|
||||
updateUI(value);
|
||||
if (value > 10) disableButton();
|
||||
updateUI(value);
|
||||
if (value > 10) disableButton();
|
||||
});
|
||||
|
||||
// 3. Update state on events
|
||||
button.addEventListener('click', () => {
|
||||
count.set(count.get() + 1);
|
||||
count.set(count.get() + 1);
|
||||
});
|
||||
|
@ -3,28 +3,28 @@ import { S } from "deka-dom-el/signals";
|
||||
|
||||
// A HelloWorld component using the 3PS pattern
|
||||
function HelloWorld({ emoji = "🚀" }) {
|
||||
// PART 1: Create reactive state
|
||||
const clicks = S(0);
|
||||
|
||||
return el().append(
|
||||
// PART 2: Bind state to UI elements
|
||||
el("p", {
|
||||
className: "greeting",
|
||||
// This paragraph automatically updates when clicks changes
|
||||
textContent: S(() => `Hello World ${emoji.repeat(clicks.get())}`)
|
||||
}),
|
||||
|
||||
// PART 3: Update state in response to events
|
||||
el("button", {
|
||||
type: "button",
|
||||
textContent: "Add emoji",
|
||||
// When clicked, update the state
|
||||
onclick: () => clicks.set(clicks.get() + 1)
|
||||
})
|
||||
);
|
||||
// PART 1: Create reactive state
|
||||
const clicks = S(0);
|
||||
|
||||
return el().append(
|
||||
// PART 2: Bind state to UI elements
|
||||
el("p", {
|
||||
className: "greeting",
|
||||
// This paragraph automatically updates when clicks changes
|
||||
textContent: S(() => `Hello World ${emoji.repeat(clicks.get())}`)
|
||||
}),
|
||||
|
||||
// PART 3: Update state in response to events
|
||||
el("button", {
|
||||
type: "button",
|
||||
textContent: "Add emoji",
|
||||
// When clicked, update the state
|
||||
onclick: () => clicks.set(clicks.get() + 1)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Use the component in your app
|
||||
document.body.append(
|
||||
el(HelloWorld, { emoji: "🎉" })
|
||||
el(HelloWorld, { emoji: "🎉" })
|
||||
);
|
@ -2,13 +2,13 @@ import { scope } from "deka-dom-el";
|
||||
import { S } from "deka-dom-el/signals";
|
||||
|
||||
function customSignalLogic() {
|
||||
// Create an isolated scope for a specific operation
|
||||
scope.push(); // Start new scope
|
||||
// Create an isolated scope for a specific operation
|
||||
scope.push(); // Start new scope
|
||||
|
||||
// These signals are in the new scope
|
||||
const isolatedCount = S(0);
|
||||
const isolatedDerived = S(() => isolatedCount.get() * 2);
|
||||
// These signals are in the new scope
|
||||
const isolatedCount = S(0);
|
||||
const isolatedDerived = S(() => isolatedCount.get() * 2);
|
||||
|
||||
// Clean up by returning to previous scope
|
||||
scope.pop();
|
||||
// Clean up by returning to previous scope
|
||||
scope.pop();
|
||||
}
|
||||
|
@ -2,44 +2,44 @@ import { el, scope } from "deka-dom-el";
|
||||
import { S } from "deka-dom-el/signals";
|
||||
|
||||
function CounterWithIsolatedTimer() {
|
||||
const { host } = scope;
|
||||
const { host } = scope;
|
||||
|
||||
// Main component state
|
||||
const count = S(0);
|
||||
// Main component state
|
||||
const count = S(0);
|
||||
|
||||
// Create a timer in an isolated scope
|
||||
scope.isolate(() => {
|
||||
// These subscriptions won't be tied to the component lifecycle
|
||||
// They would continue to run even if the component was removed
|
||||
const timer = S(0);
|
||||
// Create a timer in an isolated scope
|
||||
scope.isolate(() => {
|
||||
// These subscriptions won't be tied to the component lifecycle
|
||||
// They would continue to run even if the component was removed
|
||||
const timer = S(0);
|
||||
|
||||
// Not recommended for real applications!
|
||||
// Just demonstrating scope isolation
|
||||
setInterval(() => {
|
||||
timer.set(timer.get() + 1);
|
||||
console.log(`Timer: ${timer.get()}`);
|
||||
}, 1000);
|
||||
});
|
||||
// Not recommended for real applications!
|
||||
// Just demonstrating scope isolation
|
||||
setInterval(() => {
|
||||
timer.set(timer.get() + 1);
|
||||
console.log(`Timer: ${timer.get()}`);
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Normal component functionality within main scope
|
||||
function increment() {
|
||||
count.set(count.get() + 1);
|
||||
}
|
||||
// Normal component functionality within main scope
|
||||
function increment() {
|
||||
count.set(count.get() + 1);
|
||||
}
|
||||
|
||||
return el("div").append(
|
||||
el("p").append(
|
||||
"Count: ",
|
||||
el("#text", S(() => count.get()))
|
||||
),
|
||||
el("button", {
|
||||
textContent: "Increment",
|
||||
onclick: increment
|
||||
}),
|
||||
el("p", "An isolated timer runs in console")
|
||||
);
|
||||
return el("div").append(
|
||||
el("p").append(
|
||||
"Count: ",
|
||||
el("#text", S(() => count.get()))
|
||||
),
|
||||
el("button", {
|
||||
textContent: "Increment",
|
||||
onclick: increment
|
||||
}),
|
||||
el("p", "An isolated timer runs in console")
|
||||
);
|
||||
}
|
||||
|
||||
// Usage
|
||||
document.body.append(
|
||||
el(CounterWithIsolatedTimer)
|
||||
el(CounterWithIsolatedTimer)
|
||||
);
|
||||
|
@ -20,7 +20,7 @@ export function ireland({ src, exportName = "default", props = {} }) {
|
||||
// relative src against the current directory
|
||||
const path= "./"+relative(dir, src.pathname);
|
||||
const id = "ireland-" + generateComponentId(src);
|
||||
const element = el.mark({ type: "ireland", name: ireland.name });
|
||||
const element = el.mark({ type: "later", name: ireland.name });
|
||||
queue(import(path).then(module => {
|
||||
const component = module[exportName];
|
||||
element.replaceWith(el(component, props, mark(id)));
|
||||
|
@ -104,7 +104,8 @@ export function page({ pkg, info }){
|
||||
el("li").append(...T`${el("strong", "Custom Elements")} — Building web components`),
|
||||
el("li").append(...T`${el("strong", "Debugging")} — Tools to help you build and fix your apps`),
|
||||
el("li").append(...T`${el("strong", "Extensions")} — Integrating third-party functionalities`),
|
||||
el("li").append(...T`${el("strong", "Ireland Components")} — Creating interactive demos with server-side pre-rendering`),
|
||||
el("li").append(...T`${el("strong", "Ireland Components")} —
|
||||
Creating interactive demos with server-side pre-rendering`),
|
||||
el("li").append(...T`${el("strong", "SSR")} — Server-side rendering with DDE`)
|
||||
),
|
||||
el("p").append(...T`
|
||||
@ -112,4 +113,4 @@ export function page({ pkg, info }){
|
||||
Let's get started with the basics of creating elements!
|
||||
`),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -55,21 +55,21 @@ export function page({ pkg, info }){
|
||||
el(MyComponent);
|
||||
|
||||
function MyComponent() {
|
||||
// 2. access the host element
|
||||
const { host } = scope;
|
||||
// 2. access the host element
|
||||
const { host } = scope;
|
||||
|
||||
// 3. Add behavior to host
|
||||
host(
|
||||
on.click(handleClick)
|
||||
);
|
||||
// 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")
|
||||
);
|
||||
// 4. Return the host element
|
||||
return el("div", {
|
||||
className: "my-component"
|
||||
}).append(
|
||||
el("h2", "Title"),
|
||||
el("p", "Content")
|
||||
);
|
||||
}
|
||||
`.trim()))
|
||||
),
|
||||
@ -118,9 +118,9 @@ function MyComponent() {
|
||||
3. Component interactions happen
|
||||
4. Component removed from DOM → disconnected event
|
||||
5. Automatic cleanup of:
|
||||
- Event listeners
|
||||
- Signal subscriptions
|
||||
- Custom cleanup code
|
||||
- Event listeners
|
||||
- Signal subscriptions
|
||||
- Custom cleanup code
|
||||
`))
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/scopes/cleaning.js"), page_id }),
|
||||
@ -167,6 +167,9 @@ function MyComponent() {
|
||||
el("li").append(...T`
|
||||
${el("strong", "Capture host early:")} Use ${el("code", "const { host } = scope")} at component start
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Define signals as constants:")} ${el("code", "const counter = S(0);")}
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Prefer declarative patterns:")} Use signals to drive UI updates rather than manual DOM manipulation
|
||||
`),
|
||||
|
@ -202,15 +202,14 @@ export function page({ pkg, info }){
|
||||
el("h4", t`Shadow DOM Encapsulation`),
|
||||
el("pre").append(el("code", `
|
||||
<my-custom-element>
|
||||
┌─────────────────────────┐
|
||||
#shadow-root
|
||||
|
||||
┌─────────────────────────┐
|
||||
#shadow-root
|
||||
|
||||
Created with DDE:
|
||||
┌──────────────────┐
|
||||
<div>
|
||||
<h2>Title</h2>
|
||||
<p>Content</p>
|
||||
Created with DDE:
|
||||
┌──────────────────┐
|
||||
<div>
|
||||
<h2>Title</h2>
|
||||
<p>Content</p>
|
||||
`))
|
||||
),
|
||||
el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js"), page_id }),
|
||||
|
@ -42,8 +42,8 @@ export function page({ pkg, info }){
|
||||
// Basic structure of an addon
|
||||
function myAddon(config) {
|
||||
return function(element) {
|
||||
// Apply functionality to element
|
||||
element.dataset.myAddon = config.option;
|
||||
// Apply functionality to element
|
||||
element.dataset.myAddon = config.option;
|
||||
};
|
||||
}
|
||||
|
||||
@ -67,18 +67,15 @@ el("div", { id: "example" }, myAddon({ option: "value" }));
|
||||
// Third-party library addon with proper cleanup
|
||||
function externalLibraryAddon(config, signal) {
|
||||
return function(element) {
|
||||
// Get an abort signal that triggers on element disconnection
|
||||
const signal = on.disconnectedAsAbort(element);
|
||||
// Initialize the third-party library
|
||||
const instance = new ExternalLibrary(element, config);
|
||||
|
||||
// Initialize the third-party library
|
||||
const instance = new ExternalLibrary(element, config);
|
||||
// Set up cleanup when the element is removed
|
||||
signal.addEventListener('abort', () => {
|
||||
instance.destroy();
|
||||
});
|
||||
|
||||
// Set up cleanup when the element is removed
|
||||
signal.addEventListener('abort', () => {
|
||||
instance.destroy();
|
||||
});
|
||||
|
||||
return element;
|
||||
return element;
|
||||
};
|
||||
}
|
||||
// dde component
|
||||
@ -157,8 +154,8 @@ function createEnhancedSignal(initialValue) {
|
||||
|
||||
// Return the original signal with added methods
|
||||
return Object.assign(signal, {
|
||||
increment,
|
||||
decrement
|
||||
increment,
|
||||
decrement
|
||||
});
|
||||
}
|
||||
|
||||
@ -240,10 +237,12 @@ console.log(doubled.get()); // 10`, page_id }),
|
||||
el("h4", t`Common Extension Pitfalls`),
|
||||
el("dl").append(
|
||||
el("dt", t`Leaking event listeners or resources`),
|
||||
el("dd", t`Always use AbortSignal-based cleanup to automatically remove listeners when elements are disconnected`),
|
||||
el("dd", t`Always use AbortSignal-based cleanup to automatically remove listeners when elements
|
||||
are disconnected`),
|
||||
|
||||
el("dt", t`Tight coupling with library internals`),
|
||||
el("dd", t`Focus on standard DOM APIs and clean interfaces rather than depending on deka-dom-el implementation details`),
|
||||
el("dd", t`Focus on standard DOM APIs and clean interfaces rather than depending on deka-dom-el
|
||||
implementation details`),
|
||||
|
||||
el("dt", t`Mutating element prototypes`),
|
||||
el("dd", t`Prefer compositional approaches with addons over modifying element prototypes`),
|
||||
|
@ -18,10 +18,11 @@ export function page({ pkg, info }){
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("div", { className: "warning" }).append(
|
||||
el("p").append(...T`
|
||||
This part of the documentation is primarily intended for technical enthusiasts and documentation
|
||||
authors. It describes an advanced feature, not a core part of the library. Most users will not need to
|
||||
implement this functionality directly in their applications. This capability will hopefully be covered
|
||||
by third-party libraries or frameworks that provide simpler SSR integration using deka-dom-el.
|
||||
This part of the documentation is primarily intended for technical enthusiasts and authors of
|
||||
3rd-party libraries. It describes an advanced feature, not a core part of the library. Most users will
|
||||
not need to implement this functionality directly in their applications. This capability will hopefully
|
||||
be covered by third-party libraries or frameworks that provide simpler SSR integration using
|
||||
deka-dom-el.
|
||||
`)
|
||||
),
|
||||
el("p").append(...T`
|
||||
|
@ -2,7 +2,8 @@ import { T, t } from "./utils/index.js";
|
||||
export const info= {
|
||||
title: t`Ireland Components`,
|
||||
fullTitle: t`Interactive Demo Components with Server-Side Pre-Rendering`,
|
||||
description: t`Creating live, interactive component examples in documentation with server-side rendering and client-side hydration.`,
|
||||
description: t`Creating live, interactive component examples in documentation with server-side
|
||||
rendering and client-side hydration.`,
|
||||
};
|
||||
|
||||
import { el } from "deka-dom-el";
|
||||
@ -19,10 +20,11 @@ export function page({ pkg, info }){
|
||||
return el(simplePage, { info, pkg }).append(
|
||||
el("div", { className: "warning" }).append(
|
||||
el("p").append(...T`
|
||||
This part of the documentation is primarily intended for technical enthusiasts and documentation
|
||||
authors. It describes an advanced feature, not a core part of the library. Most users will not need to
|
||||
implement this functionality directly in their applications. This capability will hopefully be covered
|
||||
by third-party libraries or frameworks that provide simpler SSR integration using deka-dom-el.
|
||||
This part of the documentation is primarily intended for technical enthusiasts and authors of
|
||||
3rd-party libraries. It describes an advanced feature, not a core part of the library. Most users will
|
||||
not need to implement this functionality directly in their applications. This capability will hopefully
|
||||
be covered by third-party libraries or frameworks that provide simpler SSR integration using
|
||||
deka-dom-el.
|
||||
`)
|
||||
),
|
||||
|
||||
@ -61,14 +63,15 @@ export function page({ pkg, info }){
|
||||
el(h3, t`Implementation Architecture`),
|
||||
el("p").append(...T`
|
||||
The core of the Ireland system is implemented in ${el("code", "docs/components/ireland.html.js")}.
|
||||
It integrates with the SSR build process using the ${el("code", "registerClientFile")} function from ${el("code", "docs/ssr.js")}.
|
||||
It integrates with the SSR build process using the ${el("code", "registerClientFile")} function
|
||||
from ${el("code", "docs/ssr.js")}.
|
||||
`),
|
||||
|
||||
el(code, { content: `
|
||||
// Basic usage of an ireland component
|
||||
el(ireland, {
|
||||
src: fileURL("./components/examples/path/to/component.js"),
|
||||
exportName: "NamedExport", // optional, defaults to "default",
|
||||
src: fileURL("./components/examples/path/to/component.js"),
|
||||
exportName: "NamedExport", // optional, defaults to "default",
|
||||
})`, page_id }),
|
||||
|
||||
el("p").append(...T`
|
||||
@ -96,27 +99,27 @@ import { path_target, dispatchEvent } from "../docs/ssr.js";
|
||||
|
||||
// For each page, render it on the server
|
||||
for(const { id, info } of pages) {
|
||||
// Create a virtual DOM environment for server-side rendering
|
||||
const serverDOM = createHTMl("");
|
||||
serverDOM.registerGlobally("HTMLScriptElement");
|
||||
|
||||
// Register deka-dom-el with the virtual DOM
|
||||
const { el } = await register(serverDOM.dom);
|
||||
|
||||
// Import and render the page component
|
||||
const { page } = await import(\`../docs/\${id}.html.js\`);
|
||||
serverDOM.document.body.append(
|
||||
el(page, { pkg, info }),
|
||||
);
|
||||
|
||||
// Process the queue of asynchronous operations
|
||||
await queue();
|
||||
|
||||
// Trigger render event handlers
|
||||
dispatchEvent("oneachrender", document);
|
||||
|
||||
// Write the HTML to the output file
|
||||
s.echo(serverDOM.serialize()).to(path_target.root+id+".html");
|
||||
// Create a virtual DOM environment for server-side rendering
|
||||
const serverDOM = createHTMl("");
|
||||
serverDOM.registerGlobally("HTMLScriptElement");
|
||||
|
||||
// Register deka-dom-el with the virtual DOM
|
||||
const { el } = await register(serverDOM.dom);
|
||||
|
||||
// Import and render the page component
|
||||
const { page } = await import(\`../docs/\${id}.html.js\`);
|
||||
serverDOM.document.body.append(
|
||||
el(page, { pkg, info }),
|
||||
);
|
||||
|
||||
// Process the queue of asynchronous operations
|
||||
await queue();
|
||||
|
||||
// Trigger render event handlers
|
||||
dispatchEvent("oneachrender", document);
|
||||
|
||||
// Write the HTML to the output file
|
||||
s.echo(serverDOM.serialize()).to(path_target.root+id+".html");
|
||||
}
|
||||
|
||||
// Final build step - trigger SSR end event
|
||||
@ -126,107 +129,107 @@ dispatchEvent("onssrend");
|
||||
el(code, { content: `
|
||||
// From docs/ssr.js - File registration system
|
||||
export function registerClientFile(url, { head, folder = "", replacer } = {}) {
|
||||
// Ensure folder path ends with a slash
|
||||
if(folder && !folder.endsWith("/")) folder += "/";
|
||||
|
||||
// Extract filename from URL
|
||||
const file_name = url.pathname.split("/").pop();
|
||||
|
||||
// Create target directory if needed
|
||||
s.mkdir("-p", path_target.root+folder);
|
||||
|
||||
// Get file content and apply optional replacer function
|
||||
let content = s.cat(url);
|
||||
if(replacer) content = s.echo(replacer(content.toString()));
|
||||
|
||||
// Write content to the output directory
|
||||
content.to(path_target.root+folder+file_name);
|
||||
// Ensure folder path ends with a slash
|
||||
if(folder && !folder.endsWith("/")) folder += "/";
|
||||
|
||||
// If a head element was provided, add it to the document
|
||||
if(!head) return;
|
||||
head[head instanceof HTMLScriptElement ? "src" : "href"] = file_name;
|
||||
document.head.append(head);
|
||||
// Extract filename from URL
|
||||
const file_name = url.pathname.split("/").pop();
|
||||
|
||||
// Create target directory if needed
|
||||
s.mkdir("-p", path_target.root+folder);
|
||||
|
||||
// Get file content and apply optional replacer function
|
||||
let content = s.cat(url);
|
||||
if(replacer) content = s.echo(replacer(content.toString()));
|
||||
|
||||
// Write content to the output directory
|
||||
content.to(path_target.root+folder+file_name);
|
||||
|
||||
// If a head element was provided, add it to the document
|
||||
if(!head) return;
|
||||
head[head instanceof HTMLScriptElement ? "src" : "href"] = file_name;
|
||||
document.head.append(head);
|
||||
}
|
||||
`, page_id }),
|
||||
el("h4", t`Server-Side Rendering`),
|
||||
el(code, { content: `
|
||||
// From docs/components/ireland.html.js - Server-side component implementation
|
||||
export function ireland({ src, exportName = "default", props = {} }) {
|
||||
// Calculate relative path for imports
|
||||
const path = "./"+relative(dir, src.pathname);
|
||||
|
||||
// Generate unique ID for this component instance
|
||||
const id = "ireland-" + generateComponentId(src);
|
||||
|
||||
// Create placeholder element
|
||||
const element = el.mark({ type: "ireland", name: ireland.name });
|
||||
|
||||
// Import and render the component during SSR
|
||||
queue(import(path).then(module => {
|
||||
const component = module[exportName];
|
||||
element.replaceWith(el(component, props, mark(id)));
|
||||
}));
|
||||
// Calculate relative path for imports
|
||||
const path = "./"+relative(dir, src.pathname);
|
||||
|
||||
// Register client-side hydration on first component
|
||||
if(!componentsRegistry.size)
|
||||
addEventListener("oneachrender", registerClientPart);
|
||||
|
||||
// Store component info for client-side hydration
|
||||
componentsRegistry.set(id, {
|
||||
src,
|
||||
path: dirFE+"/"+path.split("/").pop(),
|
||||
exportName,
|
||||
props,
|
||||
});
|
||||
// Generate unique ID for this component instance
|
||||
const id = "ireland-" + generateComponentId(src);
|
||||
|
||||
return element;
|
||||
// Create placeholder element
|
||||
const element = el.mark({ type: "later", name: ireland.name });
|
||||
|
||||
// Import and render the component during SSR
|
||||
queue(import(path).then(module => {
|
||||
const component = module[exportName];
|
||||
element.replaceWith(el(component, props, mark(id)));
|
||||
}));
|
||||
|
||||
// Register client-side hydration on first component
|
||||
if(!componentsRegistry.size)
|
||||
addEventListener("oneachrender", registerClientPart);
|
||||
|
||||
// Store component info for client-side hydration
|
||||
componentsRegistry.set(id, {
|
||||
src,
|
||||
path: dirFE+"/"+path.split("/").pop(),
|
||||
exportName,
|
||||
props,
|
||||
});
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
// Register client-side resources
|
||||
function registerClientPart() {
|
||||
// Process all component registrations
|
||||
const todo = Array.from(componentsRegistry.entries())
|
||||
.map(([ id, d ]) => {
|
||||
// Copy the component source file to output directory
|
||||
registerClientFile(d.src, {
|
||||
folder: dirFE,
|
||||
// Replace bare imports for browser compatibility
|
||||
replacer(file) {
|
||||
return file.replaceAll(
|
||||
/ from "deka-dom-el(\/signals)?";/g,
|
||||
\` from "./esm-with-signals.js";\`
|
||||
);
|
||||
}
|
||||
});
|
||||
return [ id, d ];
|
||||
});
|
||||
|
||||
// Serialize the component registry for client-side use
|
||||
const store = JSON.stringify(JSON.stringify(todo));
|
||||
|
||||
// Copy client-side scripts to output
|
||||
registerClientFile(new URL("./ireland.js.js", import.meta.url));
|
||||
registerClientFile(new URL("../../dist/esm-with-signals.js", import.meta.url), { folder: dirFE });
|
||||
|
||||
// Add import map for package resolution
|
||||
document.head.append(
|
||||
el("script", { type: "importmap" }).append(\`
|
||||
// Process all component registrations
|
||||
const todo = Array.from(componentsRegistry.entries())
|
||||
.map(([ id, d ]) => {
|
||||
// Copy the component source file to output directory
|
||||
registerClientFile(d.src, {
|
||||
folder: dirFE,
|
||||
// Replace bare imports for browser compatibility
|
||||
replacer(file) {
|
||||
return file.replaceAll(
|
||||
/ from "deka-dom-el(\/signals)?";/g,
|
||||
\` from "./esm-with-signals.js";\`
|
||||
);
|
||||
}
|
||||
});
|
||||
return [ id, d ];
|
||||
});
|
||||
|
||||
// Serialize the component registry for client-side use
|
||||
const store = JSON.stringify(JSON.stringify(todo));
|
||||
|
||||
// Copy client-side scripts to output
|
||||
registerClientFile(new URL("./ireland.js.js", import.meta.url));
|
||||
registerClientFile(new URL("../../dist/esm-with-signals.js", import.meta.url), { folder: dirFE });
|
||||
|
||||
// Add import map for package resolution
|
||||
document.head.append(
|
||||
el("script", { type: "importmap" }).append(\`
|
||||
{
|
||||
"imports": {
|
||||
"deka-dom-el": "./\${dirFE}/esm-with-signals.js",
|
||||
"deka-dom-el/signals": "./\${dirFE}/esm-with-signals.js"
|
||||
}
|
||||
"imports": {
|
||||
"deka-dom-el": "./\${dirFE}/esm-with-signals.js",
|
||||
"deka-dom-el/signals": "./\${dirFE}/esm-with-signals.js"
|
||||
}
|
||||
}
|
||||
\`.trim())
|
||||
);
|
||||
|
||||
// Add bootstrap script to load components
|
||||
document.body.append(
|
||||
el("script", { type: "module" }).append(\`
|
||||
\`.trim())
|
||||
);
|
||||
|
||||
// Add bootstrap script to load components
|
||||
document.body.append(
|
||||
el("script", { type: "module" }).append(\`
|
||||
import { loadIrelands } from "./ireland.js.js";
|
||||
loadIrelands(new Map(JSON.parse(\${store})));
|
||||
\`.trim())
|
||||
);
|
||||
\`.trim())
|
||||
);
|
||||
}
|
||||
`, page_id }),
|
||||
el("h4", t`Client-Side Hydration`),
|
||||
@ -235,22 +238,22 @@ loadIrelands(new Map(JSON.parse(\${store})));
|
||||
import { el } from "./irelands/esm-with-signals.js";
|
||||
|
||||
export function loadIrelands(store) {
|
||||
// Find all marked components in the DOM
|
||||
document.body.querySelectorAll("[data-dde-mark]").forEach(ireland => {
|
||||
const { ddeMark } = ireland.dataset;
|
||||
|
||||
// Skip if this component isn't in our registry
|
||||
if(!store.has(ddeMark)) return;
|
||||
|
||||
// Get component information
|
||||
const { path, exportName, props } = store.get(ddeMark);
|
||||
|
||||
// Dynamically import the component module
|
||||
import("./" + path).then(module => {
|
||||
// Replace the server-rendered element with the client-side version
|
||||
ireland.replaceWith(el(module[exportName], props));
|
||||
});
|
||||
});
|
||||
// Find all marked components in the DOM
|
||||
document.body.querySelectorAll("[data-dde-mark]").forEach(ireland => {
|
||||
const { ddeMark } = ireland.dataset;
|
||||
|
||||
// Skip if this component isn't in our registry
|
||||
if(!store.has(ddeMark)) return;
|
||||
|
||||
// Get component information
|
||||
const { path, exportName, props } = store.get(ddeMark);
|
||||
|
||||
// Dynamically import the component module
|
||||
import("./" + path).then(module => {
|
||||
// Replace the server-rendered element with the client-side version
|
||||
ireland.replaceWith(el(module[exportName], props));
|
||||
});
|
||||
});
|
||||
}
|
||||
`, page_id }),
|
||||
|
||||
@ -287,7 +290,7 @@ export function loadIrelands(store) {
|
||||
${el("strong", "Export a function:")} Components should be exported as named or default functions
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Return a DOM element:")} The function should return a valid DOM element created with ${el("code", "el()")}
|
||||
|
||||
`),
|
||||
el("li").append(...T`
|
||||
${el("strong", "Accept props:")} Components should accept a props object, even if not using it
|
||||
|
19
index.d.ts
vendored
19
index.d.ts
vendored
@ -73,14 +73,17 @@ export function assignAttribute<El extends SupportedElement, ATT extends keyof E
|
||||
|
||||
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
|
||||
export namespace el {
|
||||
/**
|
||||
* Creates a marker comment for elements
|
||||
*
|
||||
* @param {{ type: "component"|"reactive"|"ireland"|"later", name?: string, host?: "this"|"parentElement" }} attrs - Marker attributes
|
||||
* @param {boolean} [is_open=false] - Whether the marker is open-ended
|
||||
* @returns {Comment} Comment node marker
|
||||
*/
|
||||
export function mark(attrs: { type: "component"|"reactive"|"ireland"|"later", name?: string, host?: "this"|"parentElement" }, is_open?: boolean): Comment;
|
||||
/**
|
||||
* Creates a marker comment for elements
|
||||
*
|
||||
* @param attrs - Marker attributes
|
||||
* @param [is_open=false] - Whether the marker is open-ended
|
||||
* @returns Comment node marker
|
||||
*/
|
||||
export function mark(
|
||||
attrs: { type: "component"|"reactive"|"later", name?: string, host?: "this"|"parentElement" },
|
||||
is_open?: boolean
|
||||
): Comment;
|
||||
}
|
||||
|
||||
export function el<
|
||||
|
Loading…
x
Reference in New Issue
Block a user