1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2025-04-01 11:45:53 +02:00

v0.9.2 — 🐛 types, on.defer and other small (#36)

* 🔤  T now uses DocumentFragment

* 🔤

* 🔤 

* 🐛 lint

*  cleanup

*  🔤 lib download

*  🔤 ui

*  reorganize files

*  on.host

* 🐛 on.* types

*  🔤 cdn

* 🔤 converter

* 🐛 signal.set(value, force)

*  🔤

* 🔤  converter - convert also comments

*  bs/build

* 🔤 ui p14

* 🔤

* 🔤 Examples

* 🔤

* 🐛 now only el(..., string|number)

* 🐛 fixes #38

* 🔤

*  on.host → on.defer

* 🔤

* 📺
This commit is contained in:
Jan Andrle 2025-03-16 11:30:42 +01:00 committed by GitHub
parent 25d475ec04
commit f0dfdfde54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
83 changed files with 4624 additions and 2919 deletions

View File

@ -31,7 +31,7 @@ function EmojiCounter({ initial }) {
el("p", {
className: "output",
textContent: S(() =>
`Hello World ${emoji.get().repeat(clicks.get())}`),
`Hello World ${emoji.get().repeat(count.get())}`),
}),
// 🎮 Controls - Update state on events
@ -39,12 +39,12 @@ function EmojiCounter({ initial }) {
on("click", () => count.set(count.get() + 1))
),
el("select", null,
el("select", null, on.host(el=> el.value= initial),
on("change", e => emoji.set(e.target.value))
).append(
el(Option, "🎉", isSelected),
el(Option, "🚀", isSelected),
el(Option, "💖", isSelected),
el(Option, "🎉"),
el(Option, "🚀"),
el(Option, "💖"),
)
);
}
@ -93,18 +93,30 @@ into existing projects.
# npm install deka-dom-el
```
#### Direct Script
#### CDN / Direct Script
For CDN links and various build formats (ESM/IIFE, with/without signals, minified/unminified), see the [interactive
format selector](https://jaandrle.github.io/deka-dom-el/) on the documentation site.
```html
<!-- Example with IIFE build (creates a global DDE object) -->
<script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/iife-with-signals.min.js"></script>
<script type="module">
<script>
const { el, S } = DDE;
// Your code here
</script>
<!-- Or with ES modules -->
<script type="module">
import { el, S } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.min.js";
// Your code here
</script>
```
### Documentation
- [**Interactive Guide**](https://jaandrle.github.io/deka-dom-el): WIP
- [Examples](./examples/): TBD/WIP
- [**Interactive Guide**](https://jaandrle.github.io/deka-dom-el)
- [**Examples**](https://jaandrle.github.io/deka-dom-el/p15-examples.html)
## Understanding Signals
@ -117,10 +129,17 @@ Signals are the reactive backbone of Deka DOM Elements:
## Inspiration and Alternatives
- [vanjs-org/van](https://github.com/vanjs-org/van) - World's smallest reactive UI framework
- [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) -
- [vanjs-org/van](https://github.com/vanjs-org/van) — World's smallest reactive UI framework
- [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
- [AseasRoa/paintor](https://github.com/AseasRoa/paintor) - JavaScript library for building reactive client-side user
interfaces or HTML code.
- [pota](https://pota.quack.uy/) — small and pluggable Reactive Web Renderer. It's compiler-less, includes an html
function, and a optimized babel preset in case you fancy JSX.
- [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) —
Functional DOM components without JSX/virtual DOM
- [mxjp/rvx: A signal based frontend framework](https://github.com/mxjp/rvx)
- [TarekRaafat/eleva](https://github.com/TarekRaafat/eleva) — A minimalist, lightweight, pure vanilla JavaScript
frontend runtime framework.
- [didi/mpx](https://github.com/didi/mpx) — Mpx一款具有优秀开发体验和深度性能优化的增强型跨端小程序框架
- [mxjp/rvx](https://github.com/mxjp/rvx) — A signal based frontend framework

View File

@ -5,18 +5,18 @@ const files= [ "index", "index-with-signals" ];
$.api("")
.command("main", "Build main files", { default: true })
.option("--no-types", "Also generate d.ts files", false)
.action(async function main({ types }){
const regular = await build({
.action(function main({ types }){
const regular = build({
files,
filesOut,
minify: "no",
types,
});
const min = await build({
const min = build({
files,
filesOut(file, mark= "esm"){
const out= filesOut(file, mark);
const idx= out.lastIndexOf(".");
const idx= out.indexOf(".");
return out.slice(0, idx)+".min"+out.slice(idx);
},
minify: "full",
@ -25,8 +25,8 @@ $.api("")
return $.exit(regular + min);
})
.command("signals", "Build only signals (for example for analysis)")
.action(async function signals(){
const regular = await build({
.action(function signals(){
const regular = build({
files: [ "signals" ],
filesOut(file){ return "dist/."+file; },
minify: "no",

View File

@ -1,4 +1,5 @@
#!/usr/bin/env -S npx nodejsscript
import { buildSync as esbuildSync } from "esbuild";
const css= echo.css`
.info{ color: gray; }
`;
@ -8,8 +9,7 @@ export function build({ files, filesOut, minify= "partial", iife= true, types= t
const file= file_root+".js";
echo(`Processing ${file} (minified: ${minify})`);
const out= filesOut(file);
const esbuild_output= buildEsbuild({ file, out, minify });
echoVariant(esbuild_output.stderr.split("\n")[1].trim());
esbuild({ file, out, minify });
if(types){
const file_dts= file_root+".d.ts";
@ -31,14 +31,13 @@ export function build({ files, filesOut, minify= "partial", iife= true, types= t
const name= "DDE";
const out= filesOut(file_root+".js", fileMark);
const params= [
"--format=iife",
"--global-name="+name,
];
const dde_output= buildEsbuild({ file, out, minify, params });
echoVariant(`${out} (${name})`)
const params= {
format: "iife",
globalName: name
};
esbuild({ file, out, minify, params });
if(!types) return dde_output;
if(!types) return;
const file_dts= file_root+".d.ts";
const file_dts_out= filesOut(file_dts, fileMark);
echoVariant(file_dts_out, true);
@ -48,8 +47,6 @@ export function build({ files, filesOut, minify= "partial", iife= true, types= t
entry: file_dts,
})
echoVariant(file_dts_out);
return dde_output;
}
}
export function buildDts({ bundle, entry, name }){
@ -64,46 +61,39 @@ export function buildDts({ bundle, entry, name }){
].filter(Boolean).join(" "), { out, entry });
return dts_b_g_output;
}
class ErrorEsbuild extends Error{
constructor({ code, stderr }){
super(stderr);
this.code= code;
this.stderr= stderr;
}
}
function buildEsbuild({ file, out, minify= "partial", params= [] }){
try {
return esbuild({ file, out, minify, params });
} catch(e){
if(e instanceof ErrorEsbuild)
return $.exit(e.code, echo(e.stderr));
throw e;
}
}
export function esbuild({ file, out, minify= "partial", params= [] }){
const esbuild_output= s.$().run([
"npx esbuild '::file::'",
"--platform=neutral",
"--bundle",
minifyOption(minify),
"--legal-comments=inline",
"--packages=external",
...params,
"--outfile='::out::'"
].filter(Boolean).join(" "), { file, out });
if(esbuild_output.code)
throw new ErrorEsbuild(esbuild_output);
export function esbuild({ file, out, minify= "partial", params= {} }){
const esbuild_output= esbuildSync({
entryPoints: [file],
outfile: out,
platform: "neutral",
bundle: true,
legalComments: "inline",
packages: "external",
metafile: true,
...minifyOption(minify),
...params
});
pipe(
f=> f.replace(/^ +/gm, m=> "\t".repeat(m.length/2)),
f=> s.echo(f).to(out)
)(s.cat(out));
echoVariant(metaToLineStatus(esbuild_output.metafile, out));
return esbuild_output;
}
/** @param {"no"|"full"|"partial"} level */
function minifyOption(level= "partial"){
if("no"===level) return undefined;
if("full"===level) return "--minify";
return "--minify-syntax --minify-identifiers";
if("no"===level) return { minify: false };
if("full"===level) return { minify: true };
return { minifySyntax: true, minifyIdentifiers: true };
}
function metaToLineStatus(meta, file){
const status= meta.outputs[file];
if(!status) return `? ${file}: unknown`;
const { bytes }= status;
const kbytes= bytes/1024;
const kbytesR= kbytes.toFixed(2);
return `${file}: ${kbytesR} kB`;
}
function echoVariant(name, todo= false){
if(todo) return echo.use("-R", "~ "+name);

View File

@ -1,641 +0,0 @@
declare global{ /* ddeSignal */ }
type CustomElementTagNameMap= { '#text': Text, '#comment': Comment }
type SupportedElement=
HTMLElementTagNameMap[keyof HTMLElementTagNameMap]
| SVGElementTagNameMap[keyof SVGElementTagNameMap]
| MathMLElementTagNameMap[keyof MathMLElementTagNameMap]
| CustomElementTagNameMap[keyof CustomElementTagNameMap]
declare global {
type ddeComponentAttributes= Record<any, any> | undefined;
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node>= (element: El)=> any;
type ddeString= string | ddeSignal<string>
type ddeStringable= ddeString | number | ddeSignal<number>
}
type PascalCase=
`${Capitalize<string>}${string}`;
type AttrsModified= {
/**
* Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API).
*/
style: Partial<CSSStyleDeclaration> | ddeString
| Partial<{ [K in keyof CSSStyleDeclaration]: ddeSignal<CSSStyleDeclaration[K]> }>
/**
* Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1.
* In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))`
* for others.
*/
classList: Record<string,-1|0|1|boolean|ddeSignal<-1|0|1|boolean>>,
/**
* Used by the dataset HTML attribute to represent data for custom attributes added to elements.
* Values are converted to string (see {@link DOMStringMap}).
*
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMStringMap)
* */
dataset: Record<string, ddeStringable>,
/**
* Sets `aria-*` simiraly to `dataset`
* */
ariaset: Record<string, ddeString>,
} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, ddeString>
& Record<`.${string}`, any>
type _fromElsInterfaces<EL extends SupportedElement>= Omit<EL, keyof AttrsModified>;
type IsReadonly<T, K extends keyof T> =
T extends { readonly [P in K]: T[K] } ? true : false;
/**
* Just element attributtes
*
* In most cases, you can use native propertie such as
* [MDN WEB/API/Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) and so on
* (e.g. [`Text`](https://developer.mozilla.org/en-US/docs/Web/API/Text)).
*
* There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives.
* @private
*/
type ElementAttributes<T extends SupportedElement>= Partial<{
[K in keyof _fromElsInterfaces<T>]:
_fromElsInterfaces<T>[K] extends ((...p: any[])=> any)
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=>
ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
: (IsReadonly<_fromElsInterfaces<T>, K> extends false
? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
: ddeStringable)
} & AttrsModified> & Record<string, any>;
export function classListDeclarative<El extends SupportedElement>(
element: El,
classList: AttrsModified["classList"]
): El
export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El
export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(
element: El,
attr: ATT,
value: ElementAttributes<El>[ATT]
): 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
>(
component: (attr: A, ...rest: any[])=> EL,
attrs?: NoInfer<A>,
...addons: ddeElementAddon<EL>[]
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? EL
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
export function el<
A extends { textContent: ddeStringable },
EL extends SupportedElement | ddeDocumentFragment
>(
component: (attr: A, ...rest: any[])=> EL,
attrs?: NoInfer<A>["textContent"],
...addons: ddeElementAddon<EL>[]
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? EL
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
export function el<
TAG extends keyof ExtendedHTMLElementTagNameMap,
>(
tag_name: TAG,
attrs?: ElementAttributes<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]> | ddeStringable,
...addons: ddeElementAddon<
ExtendedHTMLElementTagNameMap[NoInfer<TAG>]
>[], // TODO: for now addons must have the same element
): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement
export function el(
tag_name?: "<>",
): ddeDocumentFragment
export function el(
tag_name: string,
attrs?: ElementAttributes<HTMLElement> | ddeStringable,
...addons: ddeElementAddon<HTMLElement>[]
): ddeHTMLElement
export { el as createElement }
export function elNS(
namespace: "http://www.w3.org/2000/svg"
): <
TAG extends keyof SVGElementTagNameMap & string,
EL extends ( TAG extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[TAG] : SVGElement ),
>(
tag_name: TAG,
attrs?: ElementAttributes<NoInfer<EL>> | ddeStringable,
...addons: ddeElementAddon<NoInfer<EL>>[]
)=> TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement
export function elNS(
namespace: "http://www.w3.org/1998/Math/MathML"
): <
TAG extends keyof MathMLElementTagNameMap & string,
EL extends ( TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement ),
>(
tag_name: TAG,
attrs?: ddeStringable | Partial<{
[key in keyof EL]: EL[key] | ddeSignal<EL[key]> | string | number | boolean
}>,
...addons: ddeElementAddon<NoInfer<EL>>[]
)=> ddeMathMLElement
export function elNS(
namespace: string
): (
tag_name: string,
attrs?: string | ddeStringable | Record<string, any>,
...addons: ddeElementAddon<SupportedElement>[]
)=> SupportedElement
export { elNS as createElementNS }
export function chainableAppend<EL extends SupportedElement>(el: EL): EL;
/** Simulate slots for ddeComponents */
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
root: EL,
): EL
/**
* Simulate slots in Custom Elements without using `shadowRoot`.
* @param el Custom Element root element
* @param body Body of the custom element
* */
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
el: HTMLElement,
body: EL,
): EL
export function dispatchEvent(name: keyof DocumentEventMap | string, element: SupportedElement):
(data?: any)=> void;
export function dispatchEvent(name: keyof DocumentEventMap | string, options?: EventInit):
(element: SupportedElement, data?: any)=> void;
export function dispatchEvent(
name: keyof DocumentEventMap | string,
options: EventInit | null,
element: SupportedElement | (()=> SupportedElement)
): (data?: any)=> void;
interface On{
/** Listens to the DOM event. See {@link Document.addEventListener} */
<
Event extends keyof DocumentEventMap,
EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>,
>(
type: Event,
listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any,
options?: AddEventListenerOptions
) : EE;
<
EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>,
>(
type: string,
listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent ) => any,
options?: AddEventListenerOptions
) : EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
connected<
EE extends ddeElementAddon<SupportedElement>,
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
>(
listener: (this: El, event: CustomEvent<El>) => any,
options?: AddEventListenerOptions
) : EE;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
disconnected<
EE extends ddeElementAddon<SupportedElement>,
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
>(
listener: (this: El, event: CustomEvent<void>) => any,
options?: AddEventListenerOptions
) : EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
attributeChanged<
EE extends ddeElementAddon<SupportedElement>,
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
>(
listener: (this: El, event: CustomEvent<[ string, string ]>) => any,
options?: AddEventListenerOptions
) : EE;
}
export const on: On;
type Scope= {
scope: Node | Function | Object,
host: ddeElementAddon<any>,
custom_element: false | HTMLElement,
prevent: boolean
};
/** Current scope created last time the `el(Function)` was invoke. (Or {@link scope.push}) */
export const scope: {
current: Scope,
/** Stops all automatizations. E. g. signals used as attributes in current scope
* registers removing these listeners (and clean signal if no other listeners are detected)
* on `disconnected` event. */
preventDefault<T extends boolean>(prevent: T): T,
/**
* This represents reference to the current host element `scope.host()`.
* It can be also used to register Addon(s) (functions to be called when component is initized)
* `scope.host(on.connected(console.log))`.
* */
host: (...addons: ddeElementAddon<SupportedElement>[])=> HTMLElement,
state: Scope[],
/** Adds new child scope. All attributes are inherited by default. */
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
/** Adds root scope as a child of the current scope. */
pushRoot(): ReturnType<Array<Scope>["push"]>,
/** Removes last/current child scope. */
pop(): ReturnType<Array<Scope>["pop"]>,
};
export function customElementRender<
EL extends HTMLElement,
P extends any = Record<string, string | ddeSignal<string>>
>(
target: ShadowRoot | EL,
render: (props: P)=> SupportedElement | DocumentFragment,
props?: P | ((el: EL)=> P)
): EL
export function customElementWithDDE<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL
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;
interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; }
interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; }
interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; }
interface ddeMathMLElement extends MathMLElement{ append: ddeAppend<ddeMathMLElement>; }
interface ddeHTMLElementTagNameMap {
"a": ddeHTMLAnchorElement;
"area": ddeHTMLAreaElement;
"audio": ddeHTMLAudioElement;
"base": ddeHTMLBaseElement;
"blockquote": ddeHTMLQuoteElement;
"body": ddeHTMLBodyElement;
"br": ddeHTMLBRElement;
"button": ddeHTMLButtonElement;
"canvas": ddeHTMLCanvasElement;
"caption": ddeHTMLTableCaptionElement;
"col": ddeHTMLTableColElement;
"colgroup": ddeHTMLTableColElement;
"data": ddeHTMLDataElement;
"datalist": ddeHTMLDataListElement;
"del": ddeHTMLModElement;
"details": ddeHTMLDetailsElement;
"dialog": ddeHTMLDialogElement;
"div": ddeHTMLDivElement;
"dl": ddeHTMLDListElement;
"embed": ddeHTMLEmbedElement;
"fieldset": ddeHTMLFieldSetElement;
"form": ddeHTMLFormElement;
"h1": ddeHTMLHeadingElement;
"h2": ddeHTMLHeadingElement;
"h3": ddeHTMLHeadingElement;
"h4": ddeHTMLHeadingElement;
"h5": ddeHTMLHeadingElement;
"h6": ddeHTMLHeadingElement;
"head": ddeHTMLHeadElement;
"hr": ddeHTMLHRElement;
"html": ddeHTMLHtmlElement;
"iframe": ddeHTMLIFrameElement;
"img": ddeHTMLImageElement;
"input": ddeHTMLInputElement;
"ins": ddeHTMLModElement;
"label": ddeHTMLLabelElement;
"legend": ddeHTMLLegendElement;
"li": ddeHTMLLIElement;
"link": ddeHTMLLinkElement;
"map": ddeHTMLMapElement;
"menu": ddeHTMLMenuElement;
"meta": ddeHTMLMetaElement;
"meter": ddeHTMLMeterElement;
"object": ddeHTMLObjectElement;
"ol": ddeHTMLOListElement;
"optgroup": ddeHTMLOptGroupElement;
"option": ddeHTMLOptionElement;
"output": ddeHTMLOutputElement;
"p": ddeHTMLParagraphElement;
"picture": ddeHTMLPictureElement;
"pre": ddeHTMLPreElement;
"progress": ddeHTMLProgressElement;
"q": ddeHTMLQuoteElement;
"script": ddeHTMLScriptElement;
"select": ddeHTMLSelectElement;
"slot": ddeHTMLSlotElement;
"source": ddeHTMLSourceElement;
"span": ddeHTMLSpanElement;
"style": ddeHTMLStyleElement;
"table": ddeHTMLTableElement;
"tbody": ddeHTMLTableSectionElement;
"td": ddeHTMLTableCellElement;
"template": ddeHTMLTemplateElement;
"textarea": ddeHTMLTextAreaElement;
"tfoot": ddeHTMLTableSectionElement;
"th": ddeHTMLTableCellElement;
"thead": ddeHTMLTableSectionElement;
"time": ddeHTMLTimeElement;
"title": ddeHTMLTitleElement;
"tr": ddeHTMLTableRowElement;
"track": ddeHTMLTrackElement;
"ul": ddeHTMLUListElement;
"video": ddeHTMLVideoElement;
}
interface ddeSVGElementTagNameMap {
"a": ddeSVGAElement;
"animate": ddeSVGAnimateElement;
"animateMotion": ddeSVGAnimateMotionElement;
"animateTransform": ddeSVGAnimateTransformElement;
"circle": ddeSVGCircleElement;
"clipPath": ddeSVGClipPathElement;
"defs": ddeSVGDefsElement;
"desc": ddeSVGDescElement;
"ellipse": ddeSVGEllipseElement;
"feBlend": ddeSVGFEBlendElement;
"feColorMatrix": ddeSVGFEColorMatrixElement;
"feComponentTransfer": ddeSVGFEComponentTransferElement;
"feComposite": ddeSVGFECompositeElement;
"feConvolveMatrix": ddeSVGFEConvolveMatrixElement;
"feDiffuseLighting": ddeSVGFEDiffuseLightingElement;
"feDisplacementMap": ddeSVGFEDisplacementMapElement;
"feDistantLight": ddeSVGFEDistantLightElement;
"feDropShadow": ddeSVGFEDropShadowElement;
"feFlood": ddeSVGFEFloodElement;
"feFuncA": ddeSVGFEFuncAElement;
"feFuncB": ddeSVGFEFuncBElement;
"feFuncG": ddeSVGFEFuncGElement;
"feFuncR": ddeSVGFEFuncRElement;
"feGaussianBlur": ddeSVGFEGaussianBlurElement;
"feImage": ddeSVGFEImageElement;
"feMerge": ddeSVGFEMergeElement;
"feMergeNode": ddeSVGFEMergeNodeElement;
"feMorphology": ddeSVGFEMorphologyElement;
"feOffset": ddeSVGFEOffsetElement;
"fePointLight": ddeSVGFEPointLightElement;
"feSpecularLighting": ddeSVGFESpecularLightingElement;
"feSpotLight": ddeSVGFESpotLightElement;
"feTile": ddeSVGFETileElement;
"feTurbulence": ddeSVGFETurbulenceElement;
"filter": ddeSVGFilterElement;
"foreignObject": ddeSVGForeignObjectElement;
"g": ddeSVGGElement;
"image": ddeSVGImageElement;
"line": ddeSVGLineElement;
"linearGradient": ddeSVGLinearGradientElement;
"marker": ddeSVGMarkerElement;
"mask": ddeSVGMaskElement;
"metadata": ddeSVGMetadataElement;
"mpath": ddeSVGMPathElement;
"path": ddeSVGPathElement;
"pattern": ddeSVGPatternElement;
"polygon": ddeSVGPolygonElement;
"polyline": ddeSVGPolylineElement;
"radialGradient": ddeSVGRadialGradientElement;
"rect": ddeSVGRectElement;
"script": ddeSVGScriptElement;
"set": ddeSVGSetElement;
"stop": ddeSVGStopElement;
"style": ddeSVGStyleElement;
"svg": ddeSVGSVGElement;
"switch": ddeSVGSwitchElement;
"symbol": ddeSVGSymbolElement;
"text": ddeSVGTextElement;
"textPath": ddeSVGTextPathElement;
"title": ddeSVGTitleElement;
"tspan": ddeSVGTSpanElement;
"use": ddeSVGUseElement;
"view": ddeSVGViewElement;
}
}
// editorconfig-checker-disable
interface ddeHTMLAnchorElement extends HTMLAnchorElement{ append: ddeAppend<ddeHTMLAnchorElement>; }
interface ddeHTMLAreaElement extends HTMLAreaElement{ append: ddeAppend<ddeHTMLAreaElement>; }
interface ddeHTMLAudioElement extends HTMLAudioElement{ append: ddeAppend<ddeHTMLAudioElement>; }
interface ddeHTMLBaseElement extends HTMLBaseElement{ append: ddeAppend<ddeHTMLBaseElement>; }
interface ddeHTMLQuoteElement extends HTMLQuoteElement{ append: ddeAppend<ddeHTMLQuoteElement>; }
interface ddeHTMLBodyElement extends HTMLBodyElement{ append: ddeAppend<ddeHTMLBodyElement>; }
interface ddeHTMLBRElement extends HTMLBRElement{ append: ddeAppend<ddeHTMLBRElement>; }
interface ddeHTMLButtonElement extends HTMLButtonElement{ append: ddeAppend<ddeHTMLButtonElement>; }
interface ddeHTMLCanvasElement extends HTMLCanvasElement{ append: ddeAppend<ddeHTMLCanvasElement>; }
interface ddeHTMLTableCaptionElement extends HTMLTableCaptionElement{ append: ddeAppend<ddeHTMLTableCaptionElement>; }
interface ddeHTMLTableColElement extends HTMLTableColElement{ append: ddeAppend<ddeHTMLTableColElement>; }
interface ddeHTMLTableColElement extends HTMLTableColElement{ append: ddeAppend<ddeHTMLTableColElement>; }
interface ddeHTMLDataElement extends HTMLDataElement{ append: ddeAppend<ddeHTMLDataElement>; }
interface ddeHTMLDataListElement extends HTMLDataListElement{ append: ddeAppend<ddeHTMLDataListElement>; }
interface ddeHTMLModElement extends HTMLModElement{ append: ddeAppend<ddeHTMLModElement>; }
interface ddeHTMLDetailsElement extends HTMLDetailsElement{ append: ddeAppend<ddeHTMLDetailsElement>; }
interface ddeHTMLDialogElement extends HTMLDialogElement{ append: ddeAppend<ddeHTMLDialogElement>; }
interface ddeHTMLDivElement extends HTMLDivElement{ append: ddeAppend<ddeHTMLDivElement>; }
interface ddeHTMLDListElement extends HTMLDListElement{ append: ddeAppend<ddeHTMLDListElement>; }
interface ddeHTMLEmbedElement extends HTMLEmbedElement{ append: ddeAppend<ddeHTMLEmbedElement>; }
interface ddeHTMLFieldSetElement extends HTMLFieldSetElement{ append: ddeAppend<ddeHTMLFieldSetElement>; }
interface ddeHTMLFormElement extends HTMLFormElement{ append: ddeAppend<ddeHTMLFormElement>; }
interface ddeHTMLHeadingElement extends HTMLHeadingElement{ append: ddeAppend<ddeHTMLHeadingElement>; }
interface ddeHTMLHeadElement extends HTMLHeadElement{ append: ddeAppend<ddeHTMLHeadElement>; }
interface ddeHTMLHRElement extends HTMLHRElement{ append: ddeAppend<ddeHTMLHRElement>; }
interface ddeHTMLHtmlElement extends HTMLHtmlElement{ append: ddeAppend<ddeHTMLHtmlElement>; }
interface ddeHTMLIFrameElement extends HTMLIFrameElement{ append: ddeAppend<ddeHTMLIFrameElement>; }
interface ddeHTMLImageElement extends HTMLImageElement{ append: ddeAppend<ddeHTMLImageElement>; }
interface ddeHTMLInputElement extends HTMLInputElement{ append: ddeAppend<ddeHTMLInputElement>; }
interface ddeHTMLLabelElement extends HTMLLabelElement{ append: ddeAppend<ddeHTMLLabelElement>; }
interface ddeHTMLLegendElement extends HTMLLegendElement{ append: ddeAppend<ddeHTMLLegendElement>; }
interface ddeHTMLLIElement extends HTMLLIElement{ append: ddeAppend<ddeHTMLLIElement>; }
interface ddeHTMLLinkElement extends HTMLLinkElement{ append: ddeAppend<ddeHTMLLinkElement>; }
interface ddeHTMLMapElement extends HTMLMapElement{ append: ddeAppend<ddeHTMLMapElement>; }
interface ddeHTMLMenuElement extends HTMLMenuElement{ append: ddeAppend<ddeHTMLMenuElement>; }
interface ddeHTMLMetaElement extends HTMLMetaElement{ append: ddeAppend<ddeHTMLMetaElement>; }
interface ddeHTMLMeterElement extends HTMLMeterElement{ append: ddeAppend<ddeHTMLMeterElement>; }
interface ddeHTMLObjectElement extends HTMLObjectElement{ append: ddeAppend<ddeHTMLObjectElement>; }
interface ddeHTMLOListElement extends HTMLOListElement{ append: ddeAppend<ddeHTMLOListElement>; }
interface ddeHTMLOptGroupElement extends HTMLOptGroupElement{ append: ddeAppend<ddeHTMLOptGroupElement>; }
interface ddeHTMLOptionElement extends HTMLOptionElement{ append: ddeAppend<ddeHTMLOptionElement>; }
interface ddeHTMLOutputElement extends HTMLOutputElement{ append: ddeAppend<ddeHTMLOutputElement>; }
interface ddeHTMLParagraphElement extends HTMLParagraphElement{ append: ddeAppend<ddeHTMLParagraphElement>; }
interface ddeHTMLPictureElement extends HTMLPictureElement{ append: ddeAppend<ddeHTMLPictureElement>; }
interface ddeHTMLPreElement extends HTMLPreElement{ append: ddeAppend<ddeHTMLPreElement>; }
interface ddeHTMLProgressElement extends HTMLProgressElement{ append: ddeAppend<ddeHTMLProgressElement>; }
interface ddeHTMLScriptElement extends HTMLScriptElement{ append: ddeAppend<ddeHTMLScriptElement>; }
interface ddeHTMLSelectElement extends HTMLSelectElement{ append: ddeAppend<ddeHTMLSelectElement>; }
interface ddeHTMLSlotElement extends HTMLSlotElement{ append: ddeAppend<ddeHTMLSlotElement>; }
interface ddeHTMLSourceElement extends HTMLSourceElement{ append: ddeAppend<ddeHTMLSourceElement>; }
interface ddeHTMLSpanElement extends HTMLSpanElement{ append: ddeAppend<ddeHTMLSpanElement>; }
interface ddeHTMLStyleElement extends HTMLStyleElement{ append: ddeAppend<ddeHTMLStyleElement>; }
interface ddeHTMLTableElement extends HTMLTableElement{ append: ddeAppend<ddeHTMLTableElement>; }
interface ddeHTMLTableSectionElement extends HTMLTableSectionElement{ append: ddeAppend<ddeHTMLTableSectionElement>; }
interface ddeHTMLTableCellElement extends HTMLTableCellElement{ append: ddeAppend<ddeHTMLTableCellElement>; }
interface ddeHTMLTemplateElement extends HTMLTemplateElement{ append: ddeAppend<ddeHTMLTemplateElement>; }
interface ddeHTMLTextAreaElement extends HTMLTextAreaElement{ append: ddeAppend<ddeHTMLTextAreaElement>; }
interface ddeHTMLTableCellElement extends HTMLTableCellElement{ append: ddeAppend<ddeHTMLTableCellElement>; }
interface ddeHTMLTimeElement extends HTMLTimeElement{ append: ddeAppend<ddeHTMLTimeElement>; }
interface ddeHTMLTitleElement extends HTMLTitleElement{ append: ddeAppend<ddeHTMLTitleElement>; }
interface ddeHTMLTableRowElement extends HTMLTableRowElement{ append: ddeAppend<ddeHTMLTableRowElement>; }
interface ddeHTMLTrackElement extends HTMLTrackElement{ append: ddeAppend<ddeHTMLTrackElement>; }
interface ddeHTMLUListElement extends HTMLUListElement{ append: ddeAppend<ddeHTMLUListElement>; }
interface ddeHTMLVideoElement extends HTMLVideoElement{ append: ddeAppend<ddeHTMLVideoElement>; }
interface ddeSVGAElement extends SVGAElement{ append: ddeAppend<ddeSVGAElement>; }
interface ddeSVGAnimateElement extends SVGAnimateElement{ append: ddeAppend<ddeSVGAnimateElement>; }
interface ddeSVGAnimateMotionElement extends SVGAnimateMotionElement{ append: ddeAppend<ddeSVGAnimateMotionElement>; }
interface ddeSVGAnimateTransformElement extends SVGAnimateTransformElement{ append: ddeAppend<ddeSVGAnimateTransformElement>; }
interface ddeSVGCircleElement extends SVGCircleElement{ append: ddeAppend<ddeSVGCircleElement>; }
interface ddeSVGClipPathElement extends SVGClipPathElement{ append: ddeAppend<ddeSVGClipPathElement>; }
interface ddeSVGDefsElement extends SVGDefsElement{ append: ddeAppend<ddeSVGDefsElement>; }
interface ddeSVGDescElement extends SVGDescElement{ append: ddeAppend<ddeSVGDescElement>; }
interface ddeSVGEllipseElement extends SVGEllipseElement{ append: ddeAppend<ddeSVGEllipseElement>; }
interface ddeSVGFEBlendElement extends SVGFEBlendElement{ append: ddeAppend<ddeSVGFEBlendElement>; }
interface ddeSVGFEColorMatrixElement extends SVGFEColorMatrixElement{ append: ddeAppend<ddeSVGFEColorMatrixElement>; }
interface ddeSVGFEComponentTransferElement extends SVGFEComponentTransferElement{ append: ddeAppend<ddeSVGFEComponentTransferElement>; }
interface ddeSVGFECompositeElement extends SVGFECompositeElement{ append: ddeAppend<ddeSVGFECompositeElement>; }
interface ddeSVGFEConvolveMatrixElement extends SVGFEConvolveMatrixElement{ append: ddeAppend<ddeSVGFEConvolveMatrixElement>; }
interface ddeSVGFEDiffuseLightingElement extends SVGFEDiffuseLightingElement{ append: ddeAppend<ddeSVGFEDiffuseLightingElement>; }
interface ddeSVGFEDisplacementMapElement extends SVGFEDisplacementMapElement{ append: ddeAppend<ddeSVGFEDisplacementMapElement>; }
interface ddeSVGFEDistantLightElement extends SVGFEDistantLightElement{ append: ddeAppend<ddeSVGFEDistantLightElement>; }
interface ddeSVGFEDropShadowElement extends SVGFEDropShadowElement{ append: ddeAppend<ddeSVGFEDropShadowElement>; }
interface ddeSVGFEFloodElement extends SVGFEFloodElement{ append: ddeAppend<ddeSVGFEFloodElement>; }
interface ddeSVGFEFuncAElement extends SVGFEFuncAElement{ append: ddeAppend<ddeSVGFEFuncAElement>; }
interface ddeSVGFEFuncBElement extends SVGFEFuncBElement{ append: ddeAppend<ddeSVGFEFuncBElement>; }
interface ddeSVGFEFuncGElement extends SVGFEFuncGElement{ append: ddeAppend<ddeSVGFEFuncGElement>; }
interface ddeSVGFEFuncRElement extends SVGFEFuncRElement{ append: ddeAppend<ddeSVGFEFuncRElement>; }
interface ddeSVGFEGaussianBlurElement extends SVGFEGaussianBlurElement{ append: ddeAppend<ddeSVGFEGaussianBlurElement>; }
interface ddeSVGFEImageElement extends SVGFEImageElement{ append: ddeAppend<ddeSVGFEImageElement>; }
interface ddeSVGFEMergeElement extends SVGFEMergeElement{ append: ddeAppend<ddeSVGFEMergeElement>; }
interface ddeSVGFEMergeNodeElement extends SVGFEMergeNodeElement{ append: ddeAppend<ddeSVGFEMergeNodeElement>; }
interface ddeSVGFEMorphologyElement extends SVGFEMorphologyElement{ append: ddeAppend<ddeSVGFEMorphologyElement>; }
interface ddeSVGFEOffsetElement extends SVGFEOffsetElement{ append: ddeAppend<ddeSVGFEOffsetElement>; }
interface ddeSVGFEPointLightElement extends SVGFEPointLightElement{ append: ddeAppend<ddeSVGFEPointLightElement>; }
interface ddeSVGFESpecularLightingElement extends SVGFESpecularLightingElement{ append: ddeAppend<ddeSVGFESpecularLightingElement>; }
interface ddeSVGFESpotLightElement extends SVGFESpotLightElement{ append: ddeAppend<ddeSVGFESpotLightElement>; }
interface ddeSVGFETileElement extends SVGFETileElement{ append: ddeAppend<ddeSVGFETileElement>; }
interface ddeSVGFETurbulenceElement extends SVGFETurbulenceElement{ append: ddeAppend<ddeSVGFETurbulenceElement>; }
interface ddeSVGFilterElement extends SVGFilterElement{ append: ddeAppend<ddeSVGFilterElement>; }
interface ddeSVGForeignObjectElement extends SVGForeignObjectElement{ append: ddeAppend<ddeSVGForeignObjectElement>; }
interface ddeSVGGElement extends SVGGElement{ append: ddeAppend<ddeSVGGElement>; }
interface ddeSVGImageElement extends SVGImageElement{ append: ddeAppend<ddeSVGImageElement>; }
interface ddeSVGLineElement extends SVGLineElement{ append: ddeAppend<ddeSVGLineElement>; }
interface ddeSVGLinearGradientElement extends SVGLinearGradientElement{ append: ddeAppend<ddeSVGLinearGradientElement>; }
interface ddeSVGMarkerElement extends SVGMarkerElement{ append: ddeAppend<ddeSVGMarkerElement>; }
interface ddeSVGMaskElement extends SVGMaskElement{ append: ddeAppend<ddeSVGMaskElement>; }
interface ddeSVGMetadataElement extends SVGMetadataElement{ append: ddeAppend<ddeSVGMetadataElement>; }
interface ddeSVGMPathElement extends SVGMPathElement{ append: ddeAppend<ddeSVGMPathElement>; }
interface ddeSVGPathElement extends SVGPathElement{ append: ddeAppend<ddeSVGPathElement>; }
interface ddeSVGPatternElement extends SVGPatternElement{ append: ddeAppend<ddeSVGPatternElement>; }
interface ddeSVGPolygonElement extends SVGPolygonElement{ append: ddeAppend<ddeSVGPolygonElement>; }
interface ddeSVGPolylineElement extends SVGPolylineElement{ append: ddeAppend<ddeSVGPolylineElement>; }
interface ddeSVGRadialGradientElement extends SVGRadialGradientElement{ append: ddeAppend<ddeSVGRadialGradientElement>; }
interface ddeSVGRectElement extends SVGRectElement{ append: ddeAppend<ddeSVGRectElement>; }
interface ddeSVGScriptElement extends SVGScriptElement{ append: ddeAppend<ddeSVGScriptElement>; }
interface ddeSVGSetElement extends SVGSetElement{ append: ddeAppend<ddeSVGSetElement>; }
interface ddeSVGStopElement extends SVGStopElement{ append: ddeAppend<ddeSVGStopElement>; }
interface ddeSVGStyleElement extends SVGStyleElement{ append: ddeAppend<ddeSVGStyleElement>; }
interface ddeSVGSVGElement extends SVGSVGElement{ append: ddeAppend<ddeSVGSVGElement>; }
interface ddeSVGSwitchElement extends SVGSwitchElement{ append: ddeAppend<ddeSVGSwitchElement>; }
interface ddeSVGSymbolElement extends SVGSymbolElement{ append: ddeAppend<ddeSVGSymbolElement>; }
interface ddeSVGTextElement extends SVGTextElement{ append: ddeAppend<ddeSVGTextElement>; }
interface ddeSVGTextPathElement extends SVGTextPathElement{ append: ddeAppend<ddeSVGTextPathElement>; }
interface ddeSVGTitleElement extends SVGTitleElement{ append: ddeAppend<ddeSVGTitleElement>; }
interface ddeSVGTSpanElement extends SVGTSpanElement{ append: ddeAppend<ddeSVGTSpanElement>; }
interface ddeSVGUseElement extends SVGUseElement{ append: ddeAppend<ddeSVGUseElement>; }
interface ddeSVGViewElement extends SVGViewElement{ append: ddeAppend<ddeSVGViewElement>; }
// editorconfig-checker-enable
export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
toJSON(): V;
valueOf(): V;
}
type Action<V>= (this: { value: V, stopPropagation(): void }, ...a: any[])=> typeof signal._ | void;
//type SymbolSignal= Symbol;
type SymbolOnclear= symbol;
type Actions<V>= Record<string | SymbolOnclear, Action<V>>;
type OnListenerOptions= Pick<AddEventListenerOptions, "signal"> & { first_time?: boolean };
interface signal{
_: Symbol
/**
* Computations signal. This creates a signal which is computed from other signals.
* */
<V extends ()=> any>(computation: V): Signal<ReturnType<V>, {}>
/**
* Simple example:
* ```js
* const hello= S("Hello Signal");
* ```
* simple todo signal:
* ```js
* const todos= S([], {
* add(v){ this.value.push(S(v)); },
* remove(i){ this.value.splice(i, 1); },
* [S.symbols.onclear](){ S.clear(...this.value); },
* });
* ```
* computed signal:
* ```js
* const name= S("Jan");
* const surname= S("Andrle");
* const fullname= S(()=> name.get()+" "+surname.get());
* ```
* @param value Initial signal value. Or function computing value from other signals.
* @param actions Use to define actions on the signal. Such as add item to the array.
* There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared
* by `S.clear`.
* */
<V, A extends Actions<V>>(value: V, actions?: A): Signal<V, A>;
action<S extends Signal<any, Actions<any>>, A extends (S extends Signal<any, infer A> ? A : never), N extends keyof A>(
signal: S,
name: N,
...params: A[N] extends (...args: infer P)=> any ? P : never
): void;
clear(...signals: Signal<any, any>[]): void;
on<T>(signal: Signal<T, any>, onchange: (a: T)=> void, options?: OnListenerOptions): void;
symbols: {
//signal: SymbolSignal;
onclear: SymbolOnclear;
}
/**
* Reactive element, which is rendered based on the given signal.
* ```js
* S.el(signal, value=> value ? el("b", "True") : el("i", "False"));
* S.el(listS, list=> list.map(li=> el("li", li)));
* ```
* */
el<S extends any>(signal: Signal<S, any>, el: (v: S)=> Element | Element[] | DocumentFragment): DocumentFragment;
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
}
export const signal: signal;
export const S: signal;
declare global {
type ddeSignal<T, A= {}>= Signal<T, A>;
type ddeAction<V>= Action<V>
type ddeActions<V>= Actions<V>
}

View File

@ -4,7 +4,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}
@ -173,17 +173,30 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
export interface On {
/** Listens to the DOM event. See {@link Document.addEventListener} */
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & {
target: EL;
}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE;
connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[
string,
string
]>) => any, options?: AddEventListenerOptions): EE;
disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/**
* Fires after the next tick of the Javascript event loop.
* This is handy for example to apply some property depending on the element content:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.defer(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>;
}
export const on: On;
export type Scope = {

View File

@ -55,38 +55,7 @@ var Defined = class extends Error {
}
};
// src/signals-lib/common.js
var signals_global = {
/**
* Checks if a value is a signal
* @param {any} attributes - Value to check
* @returns {boolean} Whether the value is a signal
*/
isSignal(attributes) {
return false;
},
/**
* Processes an attribute that might be reactive
* @param {Element} obj - Element that owns the attribute
* @param {string} key - Attribute name
* @param {any} attr - Attribute value
* @param {Function} set - Function to set the attribute
* @returns {any} Processed attribute value
*/
processReactiveAttribute(obj, key, attr, set) {
return attr;
}
};
function registerReactivity(def, global = true) {
if (global) return oAssign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
function signals(_this) {
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
}
// src/dom-common.js
// src/dom-lib/common.js
var enviroment = {
setDeleteAttr,
ssr: "",
@ -112,7 +81,7 @@ var evc = "dde:connected";
var evd = "dde:disconnected";
var eva = "dde:attributeChanged";
// src/events-observer.js
// src/dom-lib/events-observer.js
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
get() {
return () => {
@ -276,7 +245,7 @@ function connectionsChangesObserverConstructor() {
}
}
// src/events.js
// src/dom-lib/events.js
function dispatchEvent(name, options, host) {
if (typeof options === "function") {
host = options;
@ -298,6 +267,7 @@ function on(event, listener, options) {
return element;
};
}
on.defer = (fn) => setTimeout.bind(null, fn, 0);
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
on.connected = function(listener, options) {
options = lifeOptions(options);
@ -321,10 +291,7 @@ on.disconnected = function(listener, options) {
};
};
// src/dom.js
function queue(promise) {
return enviroment.q(promise);
}
// src/dom-lib/scopes.js
var scopes = [{
get scope() {
return enviroment.D.body;
@ -399,6 +366,60 @@ var scope = {
return scopes.pop();
}
};
// src/signals-lib/common.js
var signals_global = {
/**
* Checks if a value is a signal
* @param {any} attributes - Value to check
* @returns {boolean} Whether the value is a signal
*/
isSignal(attributes) {
return false;
},
/**
* Processes an attribute that might be reactive
* @param {Element} obj - Element that owns the attribute
* @param {string} key - Attribute name
* @param {any} attr - Attribute value
* @param {Function} set - Function to set the attribute
* @returns {any} Processed attribute value
*/
processReactiveAttribute(obj, key, attr, set) {
return attr;
}
};
function registerReactivity(def, global = true) {
if (global) return oAssign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
function signals(_this) {
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
}
// src/dom-lib/helpers.js
function setRemove(obj, prop, key, val) {
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
}
function setRemoveNS(obj, prop, key, val, ns = null) {
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
}
function setDelete(obj, key, val) {
Reflect.set(obj, key, val);
if (!isUndef(val)) return;
return Reflect.deleteProperty(obj, key);
}
function elementAttribute(element, op, key, value) {
if (isInstance(element, enviroment.H))
return element[op + "Attribute"](key, value);
return element[op + "AttributeNS"](null, key, value);
}
// src/dom-lib/el.js
function queue(promise) {
return enviroment.q(promise);
}
function append(...els) {
this.appendOriginal(...els);
return this;
@ -414,7 +435,8 @@ function createElement(tag, attributes, ...addons) {
const s = signals(this);
let scoped = 0;
let el, el_host;
if (Object(attributes) !== attributes || s.isSignal(attributes))
const att_type = typeof attributes;
if (att_type === "string" || att_type === "number" || s.isSignal(attributes))
attributes = { textContent: attributes };
switch (true) {
case typeof tag === "function": {
@ -468,38 +490,6 @@ function createElementNS(ns) {
return el;
};
}
function simulateSlots(element, root = element) {
const mark_e = "\xB9\u2070", mark_s = "\u2713";
const slots = Object.fromEntries(
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
);
element.append = new Proxy(element.append, {
apply(orig, _, els) {
if (els[0] === root) return orig.apply(element, els);
for (const el of els) {
const name = (el.slot || "") + mark_e;
try {
elementAttribute(el, "remove", "slot");
} catch (_error) {
}
const slot = slots[name];
if (!slot) return;
if (!slot.name.startsWith(mark_s)) {
slot.childNodes.forEach((c) => c.remove());
slot.name = mark_s + name;
}
slot.append(el);
}
element.append = orig;
return element;
}
});
if (element !== root) {
const els = Array.from(element.childNodes);
element.append(...els);
}
return root;
}
var assign_context = /* @__PURE__ */ new WeakMap();
var { setDeleteAttr: setDeleteAttr2 } = enviroment;
function assign(element, ...attributes) {
@ -562,11 +552,6 @@ function classListDeclarative(element, toggle) {
);
return element;
}
function elementAttribute(element, op, key, value) {
if (isInstance(element, enviroment.H))
return element[op + "Attribute"](key, value);
return element[op + "AttributeNS"](null, key, value);
}
function isPropSetter(el, key) {
if (!(key in el)) return false;
const des = getPropDescriptor(el, key);
@ -590,19 +575,40 @@ function forEachEntries(s, target, element, obj, cb) {
cb(key, val);
});
}
function setRemove(obj, prop, key, val) {
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
}
function setRemoveNS(obj, prop, key, val, ns = null) {
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
}
function setDelete(obj, key, val) {
Reflect.set(obj, key, val);
if (!isUndef(val)) return;
return Reflect.deleteProperty(obj, key);
}
// src/customElement.js
// src/dom-lib/customElement.js
function simulateSlots(element, root = element) {
const mark_e = "\xB9\u2070", mark_s = "\u2713";
const slots = Object.fromEntries(
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
);
element.append = new Proxy(element.append, {
apply(orig, _, els) {
if (els[0] === root) return orig.apply(element, els);
for (const el of els) {
const name = (el.slot || "") + mark_e;
try {
elementAttribute(el, "remove", "slot");
} catch (_error) {
}
const slot = slots[name];
if (!slot) return;
if (!slot.name.startsWith(mark_s)) {
slot.childNodes.forEach((c) => c.remove());
slot.name = mark_s + name;
}
slot.append(el);
}
element.append = orig;
return element;
}
});
if (element !== root) {
const els = Array.from(element.childNodes);
element.append(...els);
}
return root;
}
function customElementRender(target, render, props = {}) {
const custom_element = target.host || target;
scope.push({
@ -683,19 +689,19 @@ memo.scope = function memoScope(fun, { signal: signal2, onlyLast } = {}) {
// src/signals-lib/helpers.js
var mark = "__dde_signal";
var queueSignalWrite = /* @__PURE__ */ (() => {
let pendingSignals = /* @__PURE__ */ new Set();
let pendingSignals = /* @__PURE__ */ new Map();
let scheduled = false;
function flushSignals() {
scheduled = false;
const todo = pendingSignals;
pendingSignals = /* @__PURE__ */ new Set();
for (const signal2 of todo) {
pendingSignals = /* @__PURE__ */ new Map();
for (const [signal2, force] of todo) {
const M = signal2[mark];
if (M) M.listeners.forEach((l) => l(M.value));
if (M) M.listeners.forEach((l) => l(M.value, force));
}
}
return function(s) {
pendingSignals.add(s);
return function(s, force = false) {
pendingSignals.set(s, pendingSignals.get(s) || force);
if (scheduled) return;
scheduled = true;
queueMicrotask(flushSignals);
@ -732,11 +738,11 @@ function signal(value, actions) {
return create(false, value, actions);
if (isSignal(value)) return value;
const out = create(true);
function contextReWatch() {
function contextReWatch(_, force) {
const [origin, ...deps_old] = deps.get(contextReWatch);
deps.set(contextReWatch, /* @__PURE__ */ new Set([origin]));
stack_watch.push(contextReWatch);
write(out, value());
write(out, value(), force);
stack_watch.pop();
if (!deps_old.length) return;
const deps_curr = deps.get(contextReWatch);
@ -758,7 +764,7 @@ signal.action = function(s, name, ...a) {
throw new Error(`Action "${name}" not defined. See ${mark}.actions.`);
actions[name].apply(M, a);
if (M.skip) return delete M.skip;
queueSignalWrite(s);
queueSignalWrite(s, true);
};
signal.on = function on2(s, listener, options = {}) {
const { signal: as } = options;
@ -967,7 +973,7 @@ function write(s, value, force) {
const M = s[mark];
if (!M || !force && M.value === value) return;
M.value = value;
queueSignalWrite(s);
queueSignalWrite(s, force);
return value;
}
function addSignalListener(s, listener) {
@ -1004,7 +1010,6 @@ export {
dispatchEvent,
createElement as el,
createElementNS as elNS,
elementAttribute,
isSignal,
lifecyclesToEvents,
memo,

View File

@ -4,7 +4,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}
@ -173,17 +173,30 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
export interface On {
/** Listens to the DOM event. See {@link Document.addEventListener} */
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & {
target: EL;
}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE;
connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[
string,
string
]>) => any, options?: AddEventListenerOptions): EE;
disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/**
* Fires after the next tick of the Javascript event loop.
* This is handy for example to apply some property depending on the element content:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.defer(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>;
}
export const on: On;
export type Scope = {

File diff suppressed because one or more lines are too long

568
dist/esm.d.min.ts vendored
View File

@ -1,568 +0,0 @@
declare global{ /* ddeSignal */ }
type CustomElementTagNameMap= { '#text': Text, '#comment': Comment }
type SupportedElement=
HTMLElementTagNameMap[keyof HTMLElementTagNameMap]
| SVGElementTagNameMap[keyof SVGElementTagNameMap]
| MathMLElementTagNameMap[keyof MathMLElementTagNameMap]
| CustomElementTagNameMap[keyof CustomElementTagNameMap]
declare global {
type ddeComponentAttributes= Record<any, any> | undefined;
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node>= (element: El)=> any;
type ddeString= string | ddeSignal<string>
type ddeStringable= ddeString | number | ddeSignal<number>
}
type PascalCase=
`${Capitalize<string>}${string}`;
type AttrsModified= {
/**
* Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API).
*/
style: Partial<CSSStyleDeclaration> | ddeString
| Partial<{ [K in keyof CSSStyleDeclaration]: ddeSignal<CSSStyleDeclaration[K]> }>
/**
* Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1.
* In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))`
* for others.
*/
classList: Record<string,-1|0|1|boolean|ddeSignal<-1|0|1|boolean>>,
/**
* Used by the dataset HTML attribute to represent data for custom attributes added to elements.
* Values are converted to string (see {@link DOMStringMap}).
*
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMStringMap)
* */
dataset: Record<string, ddeStringable>,
/**
* Sets `aria-*` simiraly to `dataset`
* */
ariaset: Record<string, ddeString>,
} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, ddeString>
& Record<`.${string}`, any>
type _fromElsInterfaces<EL extends SupportedElement>= Omit<EL, keyof AttrsModified>;
type IsReadonly<T, K extends keyof T> =
T extends { readonly [P in K]: T[K] } ? true : false;
/**
* Just element attributtes
*
* In most cases, you can use native propertie such as
* [MDN WEB/API/Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) and so on
* (e.g. [`Text`](https://developer.mozilla.org/en-US/docs/Web/API/Text)).
*
* There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives.
* @private
*/
type ElementAttributes<T extends SupportedElement>= Partial<{
[K in keyof _fromElsInterfaces<T>]:
_fromElsInterfaces<T>[K] extends ((...p: any[])=> any)
? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=>
ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>)
: (IsReadonly<_fromElsInterfaces<T>, K> extends false
? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]>
: ddeStringable)
} & AttrsModified> & Record<string, any>;
export function classListDeclarative<El extends SupportedElement>(
element: El,
classList: AttrsModified["classList"]
): El
export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El
export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(
element: El,
attr: ATT,
value: ElementAttributes<El>[ATT]
): 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
>(
component: (attr: A, ...rest: any[])=> EL,
attrs?: NoInfer<A>,
...addons: ddeElementAddon<EL>[]
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? EL
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
export function el<
A extends { textContent: ddeStringable },
EL extends SupportedElement | ddeDocumentFragment
>(
component: (attr: A, ...rest: any[])=> EL,
attrs?: NoInfer<A>["textContent"],
...addons: ddeElementAddon<EL>[]
): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap]
? EL
: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement )
export function el<
TAG extends keyof ExtendedHTMLElementTagNameMap,
>(
tag_name: TAG,
attrs?: ElementAttributes<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]> | ddeStringable,
...addons: ddeElementAddon<
ExtendedHTMLElementTagNameMap[NoInfer<TAG>]
>[], // TODO: for now addons must have the same element
): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement
export function el(
tag_name?: "<>",
): ddeDocumentFragment
export function el(
tag_name: string,
attrs?: ElementAttributes<HTMLElement> | ddeStringable,
...addons: ddeElementAddon<HTMLElement>[]
): ddeHTMLElement
export { el as createElement }
export function elNS(
namespace: "http://www.w3.org/2000/svg"
): <
TAG extends keyof SVGElementTagNameMap & string,
EL extends ( TAG extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[TAG] : SVGElement ),
>(
tag_name: TAG,
attrs?: ElementAttributes<NoInfer<EL>> | ddeStringable,
...addons: ddeElementAddon<NoInfer<EL>>[]
)=> TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement
export function elNS(
namespace: "http://www.w3.org/1998/Math/MathML"
): <
TAG extends keyof MathMLElementTagNameMap & string,
EL extends ( TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement ),
>(
tag_name: TAG,
attrs?: ddeStringable | Partial<{
[key in keyof EL]: EL[key] | ddeSignal<EL[key]> | string | number | boolean
}>,
...addons: ddeElementAddon<NoInfer<EL>>[]
)=> ddeMathMLElement
export function elNS(
namespace: string
): (
tag_name: string,
attrs?: string | ddeStringable | Record<string, any>,
...addons: ddeElementAddon<SupportedElement>[]
)=> SupportedElement
export { elNS as createElementNS }
export function chainableAppend<EL extends SupportedElement>(el: EL): EL;
/** Simulate slots for ddeComponents */
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
root: EL,
): EL
/**
* Simulate slots in Custom Elements without using `shadowRoot`.
* @param el Custom Element root element
* @param body Body of the custom element
* */
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(
el: HTMLElement,
body: EL,
): EL
export function dispatchEvent(name: keyof DocumentEventMap | string, element: SupportedElement):
(data?: any)=> void;
export function dispatchEvent(name: keyof DocumentEventMap | string, options?: EventInit):
(element: SupportedElement, data?: any)=> void;
export function dispatchEvent(
name: keyof DocumentEventMap | string,
options: EventInit | null,
element: SupportedElement | (()=> SupportedElement)
): (data?: any)=> void;
interface On{
/** Listens to the DOM event. See {@link Document.addEventListener} */
<
Event extends keyof DocumentEventMap,
EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>,
>(
type: Event,
listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any,
options?: AddEventListenerOptions
) : EE;
<
EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>,
>(
type: string,
listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent ) => any,
options?: AddEventListenerOptions
) : EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
connected<
EE extends ddeElementAddon<SupportedElement>,
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
>(
listener: (this: El, event: CustomEvent<El>) => any,
options?: AddEventListenerOptions
) : EE;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
disconnected<
EE extends ddeElementAddon<SupportedElement>,
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
>(
listener: (this: El, event: CustomEvent<void>) => any,
options?: AddEventListenerOptions
) : EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
attributeChanged<
EE extends ddeElementAddon<SupportedElement>,
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
>(
listener: (this: El, event: CustomEvent<[ string, string ]>) => any,
options?: AddEventListenerOptions
) : EE;
}
export const on: On;
type Scope= {
scope: Node | Function | Object,
host: ddeElementAddon<any>,
custom_element: false | HTMLElement,
prevent: boolean
};
/** Current scope created last time the `el(Function)` was invoke. (Or {@link scope.push}) */
export const scope: {
current: Scope,
/** Stops all automatizations. E. g. signals used as attributes in current scope
* registers removing these listeners (and clean signal if no other listeners are detected)
* on `disconnected` event. */
preventDefault<T extends boolean>(prevent: T): T,
/**
* This represents reference to the current host element `scope.host()`.
* It can be also used to register Addon(s) (functions to be called when component is initized)
* `scope.host(on.connected(console.log))`.
* */
host: (...addons: ddeElementAddon<SupportedElement>[])=> HTMLElement,
state: Scope[],
/** Adds new child scope. All attributes are inherited by default. */
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>,
/** Adds root scope as a child of the current scope. */
pushRoot(): ReturnType<Array<Scope>["push"]>,
/** Removes last/current child scope. */
pop(): ReturnType<Array<Scope>["pop"]>,
};
export function customElementRender<
EL extends HTMLElement,
P extends any = Record<string, string | ddeSignal<string>>
>(
target: ShadowRoot | EL,
render: (props: P)=> SupportedElement | DocumentFragment,
props?: P | ((el: EL)=> P)
): EL
export function customElementWithDDE<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL
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;
interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; }
interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; }
interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; }
interface ddeMathMLElement extends MathMLElement{ append: ddeAppend<ddeMathMLElement>; }
interface ddeHTMLElementTagNameMap {
"a": ddeHTMLAnchorElement;
"area": ddeHTMLAreaElement;
"audio": ddeHTMLAudioElement;
"base": ddeHTMLBaseElement;
"blockquote": ddeHTMLQuoteElement;
"body": ddeHTMLBodyElement;
"br": ddeHTMLBRElement;
"button": ddeHTMLButtonElement;
"canvas": ddeHTMLCanvasElement;
"caption": ddeHTMLTableCaptionElement;
"col": ddeHTMLTableColElement;
"colgroup": ddeHTMLTableColElement;
"data": ddeHTMLDataElement;
"datalist": ddeHTMLDataListElement;
"del": ddeHTMLModElement;
"details": ddeHTMLDetailsElement;
"dialog": ddeHTMLDialogElement;
"div": ddeHTMLDivElement;
"dl": ddeHTMLDListElement;
"embed": ddeHTMLEmbedElement;
"fieldset": ddeHTMLFieldSetElement;
"form": ddeHTMLFormElement;
"h1": ddeHTMLHeadingElement;
"h2": ddeHTMLHeadingElement;
"h3": ddeHTMLHeadingElement;
"h4": ddeHTMLHeadingElement;
"h5": ddeHTMLHeadingElement;
"h6": ddeHTMLHeadingElement;
"head": ddeHTMLHeadElement;
"hr": ddeHTMLHRElement;
"html": ddeHTMLHtmlElement;
"iframe": ddeHTMLIFrameElement;
"img": ddeHTMLImageElement;
"input": ddeHTMLInputElement;
"ins": ddeHTMLModElement;
"label": ddeHTMLLabelElement;
"legend": ddeHTMLLegendElement;
"li": ddeHTMLLIElement;
"link": ddeHTMLLinkElement;
"map": ddeHTMLMapElement;
"menu": ddeHTMLMenuElement;
"meta": ddeHTMLMetaElement;
"meter": ddeHTMLMeterElement;
"object": ddeHTMLObjectElement;
"ol": ddeHTMLOListElement;
"optgroup": ddeHTMLOptGroupElement;
"option": ddeHTMLOptionElement;
"output": ddeHTMLOutputElement;
"p": ddeHTMLParagraphElement;
"picture": ddeHTMLPictureElement;
"pre": ddeHTMLPreElement;
"progress": ddeHTMLProgressElement;
"q": ddeHTMLQuoteElement;
"script": ddeHTMLScriptElement;
"select": ddeHTMLSelectElement;
"slot": ddeHTMLSlotElement;
"source": ddeHTMLSourceElement;
"span": ddeHTMLSpanElement;
"style": ddeHTMLStyleElement;
"table": ddeHTMLTableElement;
"tbody": ddeHTMLTableSectionElement;
"td": ddeHTMLTableCellElement;
"template": ddeHTMLTemplateElement;
"textarea": ddeHTMLTextAreaElement;
"tfoot": ddeHTMLTableSectionElement;
"th": ddeHTMLTableCellElement;
"thead": ddeHTMLTableSectionElement;
"time": ddeHTMLTimeElement;
"title": ddeHTMLTitleElement;
"tr": ddeHTMLTableRowElement;
"track": ddeHTMLTrackElement;
"ul": ddeHTMLUListElement;
"video": ddeHTMLVideoElement;
}
interface ddeSVGElementTagNameMap {
"a": ddeSVGAElement;
"animate": ddeSVGAnimateElement;
"animateMotion": ddeSVGAnimateMotionElement;
"animateTransform": ddeSVGAnimateTransformElement;
"circle": ddeSVGCircleElement;
"clipPath": ddeSVGClipPathElement;
"defs": ddeSVGDefsElement;
"desc": ddeSVGDescElement;
"ellipse": ddeSVGEllipseElement;
"feBlend": ddeSVGFEBlendElement;
"feColorMatrix": ddeSVGFEColorMatrixElement;
"feComponentTransfer": ddeSVGFEComponentTransferElement;
"feComposite": ddeSVGFECompositeElement;
"feConvolveMatrix": ddeSVGFEConvolveMatrixElement;
"feDiffuseLighting": ddeSVGFEDiffuseLightingElement;
"feDisplacementMap": ddeSVGFEDisplacementMapElement;
"feDistantLight": ddeSVGFEDistantLightElement;
"feDropShadow": ddeSVGFEDropShadowElement;
"feFlood": ddeSVGFEFloodElement;
"feFuncA": ddeSVGFEFuncAElement;
"feFuncB": ddeSVGFEFuncBElement;
"feFuncG": ddeSVGFEFuncGElement;
"feFuncR": ddeSVGFEFuncRElement;
"feGaussianBlur": ddeSVGFEGaussianBlurElement;
"feImage": ddeSVGFEImageElement;
"feMerge": ddeSVGFEMergeElement;
"feMergeNode": ddeSVGFEMergeNodeElement;
"feMorphology": ddeSVGFEMorphologyElement;
"feOffset": ddeSVGFEOffsetElement;
"fePointLight": ddeSVGFEPointLightElement;
"feSpecularLighting": ddeSVGFESpecularLightingElement;
"feSpotLight": ddeSVGFESpotLightElement;
"feTile": ddeSVGFETileElement;
"feTurbulence": ddeSVGFETurbulenceElement;
"filter": ddeSVGFilterElement;
"foreignObject": ddeSVGForeignObjectElement;
"g": ddeSVGGElement;
"image": ddeSVGImageElement;
"line": ddeSVGLineElement;
"linearGradient": ddeSVGLinearGradientElement;
"marker": ddeSVGMarkerElement;
"mask": ddeSVGMaskElement;
"metadata": ddeSVGMetadataElement;
"mpath": ddeSVGMPathElement;
"path": ddeSVGPathElement;
"pattern": ddeSVGPatternElement;
"polygon": ddeSVGPolygonElement;
"polyline": ddeSVGPolylineElement;
"radialGradient": ddeSVGRadialGradientElement;
"rect": ddeSVGRectElement;
"script": ddeSVGScriptElement;
"set": ddeSVGSetElement;
"stop": ddeSVGStopElement;
"style": ddeSVGStyleElement;
"svg": ddeSVGSVGElement;
"switch": ddeSVGSwitchElement;
"symbol": ddeSVGSymbolElement;
"text": ddeSVGTextElement;
"textPath": ddeSVGTextPathElement;
"title": ddeSVGTitleElement;
"tspan": ddeSVGTSpanElement;
"use": ddeSVGUseElement;
"view": ddeSVGViewElement;
}
}
// editorconfig-checker-disable
interface ddeHTMLAnchorElement extends HTMLAnchorElement{ append: ddeAppend<ddeHTMLAnchorElement>; }
interface ddeHTMLAreaElement extends HTMLAreaElement{ append: ddeAppend<ddeHTMLAreaElement>; }
interface ddeHTMLAudioElement extends HTMLAudioElement{ append: ddeAppend<ddeHTMLAudioElement>; }
interface ddeHTMLBaseElement extends HTMLBaseElement{ append: ddeAppend<ddeHTMLBaseElement>; }
interface ddeHTMLQuoteElement extends HTMLQuoteElement{ append: ddeAppend<ddeHTMLQuoteElement>; }
interface ddeHTMLBodyElement extends HTMLBodyElement{ append: ddeAppend<ddeHTMLBodyElement>; }
interface ddeHTMLBRElement extends HTMLBRElement{ append: ddeAppend<ddeHTMLBRElement>; }
interface ddeHTMLButtonElement extends HTMLButtonElement{ append: ddeAppend<ddeHTMLButtonElement>; }
interface ddeHTMLCanvasElement extends HTMLCanvasElement{ append: ddeAppend<ddeHTMLCanvasElement>; }
interface ddeHTMLTableCaptionElement extends HTMLTableCaptionElement{ append: ddeAppend<ddeHTMLTableCaptionElement>; }
interface ddeHTMLTableColElement extends HTMLTableColElement{ append: ddeAppend<ddeHTMLTableColElement>; }
interface ddeHTMLTableColElement extends HTMLTableColElement{ append: ddeAppend<ddeHTMLTableColElement>; }
interface ddeHTMLDataElement extends HTMLDataElement{ append: ddeAppend<ddeHTMLDataElement>; }
interface ddeHTMLDataListElement extends HTMLDataListElement{ append: ddeAppend<ddeHTMLDataListElement>; }
interface ddeHTMLModElement extends HTMLModElement{ append: ddeAppend<ddeHTMLModElement>; }
interface ddeHTMLDetailsElement extends HTMLDetailsElement{ append: ddeAppend<ddeHTMLDetailsElement>; }
interface ddeHTMLDialogElement extends HTMLDialogElement{ append: ddeAppend<ddeHTMLDialogElement>; }
interface ddeHTMLDivElement extends HTMLDivElement{ append: ddeAppend<ddeHTMLDivElement>; }
interface ddeHTMLDListElement extends HTMLDListElement{ append: ddeAppend<ddeHTMLDListElement>; }
interface ddeHTMLEmbedElement extends HTMLEmbedElement{ append: ddeAppend<ddeHTMLEmbedElement>; }
interface ddeHTMLFieldSetElement extends HTMLFieldSetElement{ append: ddeAppend<ddeHTMLFieldSetElement>; }
interface ddeHTMLFormElement extends HTMLFormElement{ append: ddeAppend<ddeHTMLFormElement>; }
interface ddeHTMLHeadingElement extends HTMLHeadingElement{ append: ddeAppend<ddeHTMLHeadingElement>; }
interface ddeHTMLHeadElement extends HTMLHeadElement{ append: ddeAppend<ddeHTMLHeadElement>; }
interface ddeHTMLHRElement extends HTMLHRElement{ append: ddeAppend<ddeHTMLHRElement>; }
interface ddeHTMLHtmlElement extends HTMLHtmlElement{ append: ddeAppend<ddeHTMLHtmlElement>; }
interface ddeHTMLIFrameElement extends HTMLIFrameElement{ append: ddeAppend<ddeHTMLIFrameElement>; }
interface ddeHTMLImageElement extends HTMLImageElement{ append: ddeAppend<ddeHTMLImageElement>; }
interface ddeHTMLInputElement extends HTMLInputElement{ append: ddeAppend<ddeHTMLInputElement>; }
interface ddeHTMLLabelElement extends HTMLLabelElement{ append: ddeAppend<ddeHTMLLabelElement>; }
interface ddeHTMLLegendElement extends HTMLLegendElement{ append: ddeAppend<ddeHTMLLegendElement>; }
interface ddeHTMLLIElement extends HTMLLIElement{ append: ddeAppend<ddeHTMLLIElement>; }
interface ddeHTMLLinkElement extends HTMLLinkElement{ append: ddeAppend<ddeHTMLLinkElement>; }
interface ddeHTMLMapElement extends HTMLMapElement{ append: ddeAppend<ddeHTMLMapElement>; }
interface ddeHTMLMenuElement extends HTMLMenuElement{ append: ddeAppend<ddeHTMLMenuElement>; }
interface ddeHTMLMetaElement extends HTMLMetaElement{ append: ddeAppend<ddeHTMLMetaElement>; }
interface ddeHTMLMeterElement extends HTMLMeterElement{ append: ddeAppend<ddeHTMLMeterElement>; }
interface ddeHTMLObjectElement extends HTMLObjectElement{ append: ddeAppend<ddeHTMLObjectElement>; }
interface ddeHTMLOListElement extends HTMLOListElement{ append: ddeAppend<ddeHTMLOListElement>; }
interface ddeHTMLOptGroupElement extends HTMLOptGroupElement{ append: ddeAppend<ddeHTMLOptGroupElement>; }
interface ddeHTMLOptionElement extends HTMLOptionElement{ append: ddeAppend<ddeHTMLOptionElement>; }
interface ddeHTMLOutputElement extends HTMLOutputElement{ append: ddeAppend<ddeHTMLOutputElement>; }
interface ddeHTMLParagraphElement extends HTMLParagraphElement{ append: ddeAppend<ddeHTMLParagraphElement>; }
interface ddeHTMLPictureElement extends HTMLPictureElement{ append: ddeAppend<ddeHTMLPictureElement>; }
interface ddeHTMLPreElement extends HTMLPreElement{ append: ddeAppend<ddeHTMLPreElement>; }
interface ddeHTMLProgressElement extends HTMLProgressElement{ append: ddeAppend<ddeHTMLProgressElement>; }
interface ddeHTMLScriptElement extends HTMLScriptElement{ append: ddeAppend<ddeHTMLScriptElement>; }
interface ddeHTMLSelectElement extends HTMLSelectElement{ append: ddeAppend<ddeHTMLSelectElement>; }
interface ddeHTMLSlotElement extends HTMLSlotElement{ append: ddeAppend<ddeHTMLSlotElement>; }
interface ddeHTMLSourceElement extends HTMLSourceElement{ append: ddeAppend<ddeHTMLSourceElement>; }
interface ddeHTMLSpanElement extends HTMLSpanElement{ append: ddeAppend<ddeHTMLSpanElement>; }
interface ddeHTMLStyleElement extends HTMLStyleElement{ append: ddeAppend<ddeHTMLStyleElement>; }
interface ddeHTMLTableElement extends HTMLTableElement{ append: ddeAppend<ddeHTMLTableElement>; }
interface ddeHTMLTableSectionElement extends HTMLTableSectionElement{ append: ddeAppend<ddeHTMLTableSectionElement>; }
interface ddeHTMLTableCellElement extends HTMLTableCellElement{ append: ddeAppend<ddeHTMLTableCellElement>; }
interface ddeHTMLTemplateElement extends HTMLTemplateElement{ append: ddeAppend<ddeHTMLTemplateElement>; }
interface ddeHTMLTextAreaElement extends HTMLTextAreaElement{ append: ddeAppend<ddeHTMLTextAreaElement>; }
interface ddeHTMLTableCellElement extends HTMLTableCellElement{ append: ddeAppend<ddeHTMLTableCellElement>; }
interface ddeHTMLTimeElement extends HTMLTimeElement{ append: ddeAppend<ddeHTMLTimeElement>; }
interface ddeHTMLTitleElement extends HTMLTitleElement{ append: ddeAppend<ddeHTMLTitleElement>; }
interface ddeHTMLTableRowElement extends HTMLTableRowElement{ append: ddeAppend<ddeHTMLTableRowElement>; }
interface ddeHTMLTrackElement extends HTMLTrackElement{ append: ddeAppend<ddeHTMLTrackElement>; }
interface ddeHTMLUListElement extends HTMLUListElement{ append: ddeAppend<ddeHTMLUListElement>; }
interface ddeHTMLVideoElement extends HTMLVideoElement{ append: ddeAppend<ddeHTMLVideoElement>; }
interface ddeSVGAElement extends SVGAElement{ append: ddeAppend<ddeSVGAElement>; }
interface ddeSVGAnimateElement extends SVGAnimateElement{ append: ddeAppend<ddeSVGAnimateElement>; }
interface ddeSVGAnimateMotionElement extends SVGAnimateMotionElement{ append: ddeAppend<ddeSVGAnimateMotionElement>; }
interface ddeSVGAnimateTransformElement extends SVGAnimateTransformElement{ append: ddeAppend<ddeSVGAnimateTransformElement>; }
interface ddeSVGCircleElement extends SVGCircleElement{ append: ddeAppend<ddeSVGCircleElement>; }
interface ddeSVGClipPathElement extends SVGClipPathElement{ append: ddeAppend<ddeSVGClipPathElement>; }
interface ddeSVGDefsElement extends SVGDefsElement{ append: ddeAppend<ddeSVGDefsElement>; }
interface ddeSVGDescElement extends SVGDescElement{ append: ddeAppend<ddeSVGDescElement>; }
interface ddeSVGEllipseElement extends SVGEllipseElement{ append: ddeAppend<ddeSVGEllipseElement>; }
interface ddeSVGFEBlendElement extends SVGFEBlendElement{ append: ddeAppend<ddeSVGFEBlendElement>; }
interface ddeSVGFEColorMatrixElement extends SVGFEColorMatrixElement{ append: ddeAppend<ddeSVGFEColorMatrixElement>; }
interface ddeSVGFEComponentTransferElement extends SVGFEComponentTransferElement{ append: ddeAppend<ddeSVGFEComponentTransferElement>; }
interface ddeSVGFECompositeElement extends SVGFECompositeElement{ append: ddeAppend<ddeSVGFECompositeElement>; }
interface ddeSVGFEConvolveMatrixElement extends SVGFEConvolveMatrixElement{ append: ddeAppend<ddeSVGFEConvolveMatrixElement>; }
interface ddeSVGFEDiffuseLightingElement extends SVGFEDiffuseLightingElement{ append: ddeAppend<ddeSVGFEDiffuseLightingElement>; }
interface ddeSVGFEDisplacementMapElement extends SVGFEDisplacementMapElement{ append: ddeAppend<ddeSVGFEDisplacementMapElement>; }
interface ddeSVGFEDistantLightElement extends SVGFEDistantLightElement{ append: ddeAppend<ddeSVGFEDistantLightElement>; }
interface ddeSVGFEDropShadowElement extends SVGFEDropShadowElement{ append: ddeAppend<ddeSVGFEDropShadowElement>; }
interface ddeSVGFEFloodElement extends SVGFEFloodElement{ append: ddeAppend<ddeSVGFEFloodElement>; }
interface ddeSVGFEFuncAElement extends SVGFEFuncAElement{ append: ddeAppend<ddeSVGFEFuncAElement>; }
interface ddeSVGFEFuncBElement extends SVGFEFuncBElement{ append: ddeAppend<ddeSVGFEFuncBElement>; }
interface ddeSVGFEFuncGElement extends SVGFEFuncGElement{ append: ddeAppend<ddeSVGFEFuncGElement>; }
interface ddeSVGFEFuncRElement extends SVGFEFuncRElement{ append: ddeAppend<ddeSVGFEFuncRElement>; }
interface ddeSVGFEGaussianBlurElement extends SVGFEGaussianBlurElement{ append: ddeAppend<ddeSVGFEGaussianBlurElement>; }
interface ddeSVGFEImageElement extends SVGFEImageElement{ append: ddeAppend<ddeSVGFEImageElement>; }
interface ddeSVGFEMergeElement extends SVGFEMergeElement{ append: ddeAppend<ddeSVGFEMergeElement>; }
interface ddeSVGFEMergeNodeElement extends SVGFEMergeNodeElement{ append: ddeAppend<ddeSVGFEMergeNodeElement>; }
interface ddeSVGFEMorphologyElement extends SVGFEMorphologyElement{ append: ddeAppend<ddeSVGFEMorphologyElement>; }
interface ddeSVGFEOffsetElement extends SVGFEOffsetElement{ append: ddeAppend<ddeSVGFEOffsetElement>; }
interface ddeSVGFEPointLightElement extends SVGFEPointLightElement{ append: ddeAppend<ddeSVGFEPointLightElement>; }
interface ddeSVGFESpecularLightingElement extends SVGFESpecularLightingElement{ append: ddeAppend<ddeSVGFESpecularLightingElement>; }
interface ddeSVGFESpotLightElement extends SVGFESpotLightElement{ append: ddeAppend<ddeSVGFESpotLightElement>; }
interface ddeSVGFETileElement extends SVGFETileElement{ append: ddeAppend<ddeSVGFETileElement>; }
interface ddeSVGFETurbulenceElement extends SVGFETurbulenceElement{ append: ddeAppend<ddeSVGFETurbulenceElement>; }
interface ddeSVGFilterElement extends SVGFilterElement{ append: ddeAppend<ddeSVGFilterElement>; }
interface ddeSVGForeignObjectElement extends SVGForeignObjectElement{ append: ddeAppend<ddeSVGForeignObjectElement>; }
interface ddeSVGGElement extends SVGGElement{ append: ddeAppend<ddeSVGGElement>; }
interface ddeSVGImageElement extends SVGImageElement{ append: ddeAppend<ddeSVGImageElement>; }
interface ddeSVGLineElement extends SVGLineElement{ append: ddeAppend<ddeSVGLineElement>; }
interface ddeSVGLinearGradientElement extends SVGLinearGradientElement{ append: ddeAppend<ddeSVGLinearGradientElement>; }
interface ddeSVGMarkerElement extends SVGMarkerElement{ append: ddeAppend<ddeSVGMarkerElement>; }
interface ddeSVGMaskElement extends SVGMaskElement{ append: ddeAppend<ddeSVGMaskElement>; }
interface ddeSVGMetadataElement extends SVGMetadataElement{ append: ddeAppend<ddeSVGMetadataElement>; }
interface ddeSVGMPathElement extends SVGMPathElement{ append: ddeAppend<ddeSVGMPathElement>; }
interface ddeSVGPathElement extends SVGPathElement{ append: ddeAppend<ddeSVGPathElement>; }
interface ddeSVGPatternElement extends SVGPatternElement{ append: ddeAppend<ddeSVGPatternElement>; }
interface ddeSVGPolygonElement extends SVGPolygonElement{ append: ddeAppend<ddeSVGPolygonElement>; }
interface ddeSVGPolylineElement extends SVGPolylineElement{ append: ddeAppend<ddeSVGPolylineElement>; }
interface ddeSVGRadialGradientElement extends SVGRadialGradientElement{ append: ddeAppend<ddeSVGRadialGradientElement>; }
interface ddeSVGRectElement extends SVGRectElement{ append: ddeAppend<ddeSVGRectElement>; }
interface ddeSVGScriptElement extends SVGScriptElement{ append: ddeAppend<ddeSVGScriptElement>; }
interface ddeSVGSetElement extends SVGSetElement{ append: ddeAppend<ddeSVGSetElement>; }
interface ddeSVGStopElement extends SVGStopElement{ append: ddeAppend<ddeSVGStopElement>; }
interface ddeSVGStyleElement extends SVGStyleElement{ append: ddeAppend<ddeSVGStyleElement>; }
interface ddeSVGSVGElement extends SVGSVGElement{ append: ddeAppend<ddeSVGSVGElement>; }
interface ddeSVGSwitchElement extends SVGSwitchElement{ append: ddeAppend<ddeSVGSwitchElement>; }
interface ddeSVGSymbolElement extends SVGSymbolElement{ append: ddeAppend<ddeSVGSymbolElement>; }
interface ddeSVGTextElement extends SVGTextElement{ append: ddeAppend<ddeSVGTextElement>; }
interface ddeSVGTextPathElement extends SVGTextPathElement{ append: ddeAppend<ddeSVGTextPathElement>; }
interface ddeSVGTitleElement extends SVGTitleElement{ append: ddeAppend<ddeSVGTitleElement>; }
interface ddeSVGTSpanElement extends SVGTSpanElement{ append: ddeAppend<ddeSVGTSpanElement>; }
interface ddeSVGUseElement extends SVGUseElement{ append: ddeAppend<ddeSVGUseElement>; }
interface ddeSVGViewElement extends SVGViewElement{ append: ddeAppend<ddeSVGViewElement>; }
// editorconfig-checker-enable

31
dist/esm.d.ts vendored
View File

@ -4,7 +4,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}
@ -172,17 +172,30 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
export interface On {
/** Listens to the DOM event. See {@link Document.addEventListener} */
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & {
target: EL;
}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE;
connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[
string,
string
]>) => any, options?: AddEventListenerOptions): EE;
disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/**
* Fires after the next tick of the Javascript event loop.
* This is handy for example to apply some property depending on the element content:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.defer(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>;
}
export const on: On;
export type Scope = {

183
dist/esm.js vendored
View File

@ -26,38 +26,7 @@ function onAbort(signal, listener) {
};
}
// src/signals-lib/common.js
var signals_global = {
/**
* Checks if a value is a signal
* @param {any} attributes - Value to check
* @returns {boolean} Whether the value is a signal
*/
isSignal(attributes) {
return false;
},
/**
* Processes an attribute that might be reactive
* @param {Element} obj - Element that owns the attribute
* @param {string} key - Attribute name
* @param {any} attr - Attribute value
* @param {Function} set - Function to set the attribute
* @returns {any} Processed attribute value
*/
processReactiveAttribute(obj, key, attr, set) {
return attr;
}
};
function registerReactivity(def, global = true) {
if (global) return oAssign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
function signals(_this) {
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
}
// src/dom-common.js
// src/dom-lib/common.js
var enviroment = {
setDeleteAttr,
ssr: "",
@ -83,7 +52,7 @@ var evc = "dde:connected";
var evd = "dde:disconnected";
var eva = "dde:attributeChanged";
// src/events-observer.js
// src/dom-lib/events-observer.js
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
get() {
return () => {
@ -247,7 +216,7 @@ function connectionsChangesObserverConstructor() {
}
}
// src/events.js
// src/dom-lib/events.js
function dispatchEvent(name, options, host) {
if (typeof options === "function") {
host = options;
@ -269,6 +238,7 @@ function on(event, listener, options) {
return element;
};
}
on.defer = (fn) => setTimeout.bind(null, fn, 0);
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
on.connected = function(listener, options) {
options = lifeOptions(options);
@ -292,10 +262,7 @@ on.disconnected = function(listener, options) {
};
};
// src/dom.js
function queue(promise) {
return enviroment.q(promise);
}
// src/dom-lib/scopes.js
var scopes = [{
get scope() {
return enviroment.D.body;
@ -370,6 +337,60 @@ var scope = {
return scopes.pop();
}
};
// src/signals-lib/common.js
var signals_global = {
/**
* Checks if a value is a signal
* @param {any} attributes - Value to check
* @returns {boolean} Whether the value is a signal
*/
isSignal(attributes) {
return false;
},
/**
* Processes an attribute that might be reactive
* @param {Element} obj - Element that owns the attribute
* @param {string} key - Attribute name
* @param {any} attr - Attribute value
* @param {Function} set - Function to set the attribute
* @returns {any} Processed attribute value
*/
processReactiveAttribute(obj, key, attr, set) {
return attr;
}
};
function registerReactivity(def, global = true) {
if (global) return oAssign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
function signals(_this) {
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
}
// src/dom-lib/helpers.js
function setRemove(obj, prop, key, val) {
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
}
function setRemoveNS(obj, prop, key, val, ns = null) {
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
}
function setDelete(obj, key, val) {
Reflect.set(obj, key, val);
if (!isUndef(val)) return;
return Reflect.deleteProperty(obj, key);
}
function elementAttribute(element, op, key, value) {
if (isInstance(element, enviroment.H))
return element[op + "Attribute"](key, value);
return element[op + "AttributeNS"](null, key, value);
}
// src/dom-lib/el.js
function queue(promise) {
return enviroment.q(promise);
}
function append(...els) {
this.appendOriginal(...els);
return this;
@ -385,7 +406,8 @@ function createElement(tag, attributes, ...addons) {
const s = signals(this);
let scoped = 0;
let el, el_host;
if (Object(attributes) !== attributes || s.isSignal(attributes))
const att_type = typeof attributes;
if (att_type === "string" || att_type === "number" || s.isSignal(attributes))
attributes = { textContent: attributes };
switch (true) {
case typeof tag === "function": {
@ -439,38 +461,6 @@ function createElementNS(ns) {
return el;
};
}
function simulateSlots(element, root = element) {
const mark_e = "\xB9\u2070", mark_s = "\u2713";
const slots = Object.fromEntries(
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
);
element.append = new Proxy(element.append, {
apply(orig, _, els) {
if (els[0] === root) return orig.apply(element, els);
for (const el of els) {
const name = (el.slot || "") + mark_e;
try {
elementAttribute(el, "remove", "slot");
} catch (_error) {
}
const slot = slots[name];
if (!slot) return;
if (!slot.name.startsWith(mark_s)) {
slot.childNodes.forEach((c) => c.remove());
slot.name = mark_s + name;
}
slot.append(el);
}
element.append = orig;
return element;
}
});
if (element !== root) {
const els = Array.from(element.childNodes);
element.append(...els);
}
return root;
}
var assign_context = /* @__PURE__ */ new WeakMap();
var { setDeleteAttr: setDeleteAttr2 } = enviroment;
function assign(element, ...attributes) {
@ -533,11 +523,6 @@ function classListDeclarative(element, toggle) {
);
return element;
}
function elementAttribute(element, op, key, value) {
if (isInstance(element, enviroment.H))
return element[op + "Attribute"](key, value);
return element[op + "AttributeNS"](null, key, value);
}
function isPropSetter(el, key) {
if (!(key in el)) return false;
const des = getPropDescriptor(el, key);
@ -561,19 +546,40 @@ function forEachEntries(s, target, element, obj, cb) {
cb(key, val);
});
}
function setRemove(obj, prop, key, val) {
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
}
function setRemoveNS(obj, prop, key, val, ns = null) {
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
}
function setDelete(obj, key, val) {
Reflect.set(obj, key, val);
if (!isUndef(val)) return;
return Reflect.deleteProperty(obj, key);
}
// src/customElement.js
// src/dom-lib/customElement.js
function simulateSlots(element, root = element) {
const mark_e = "\xB9\u2070", mark_s = "\u2713";
const slots = Object.fromEntries(
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
);
element.append = new Proxy(element.append, {
apply(orig, _, els) {
if (els[0] === root) return orig.apply(element, els);
for (const el of els) {
const name = (el.slot || "") + mark_e;
try {
elementAttribute(el, "remove", "slot");
} catch (_error) {
}
const slot = slots[name];
if (!slot) return;
if (!slot.name.startsWith(mark_s)) {
slot.childNodes.forEach((c) => c.remove());
slot.name = mark_s + name;
}
slot.append(el);
}
element.append = orig;
return element;
}
});
if (element !== root) {
const els = Array.from(element.childNodes);
element.append(...els);
}
return root;
}
function customElementRender(target, render, props = {}) {
const custom_element = target.host || target;
scope.push({
@ -662,7 +668,6 @@ export {
dispatchEvent,
createElement as el,
createElementNS as elNS,
elementAttribute,
lifecyclesToEvents,
memo,
on,

31
dist/esm.min.d.ts vendored
View File

@ -4,7 +4,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}
@ -172,17 +172,30 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
export interface On {
/** Listens to the DOM event. See {@link Document.addEventListener} */
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & {
target: EL;
}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE;
connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[
string,
string
]>) => any, options?: AddEventListenerOptions): EE;
disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/**
* Fires after the next tick of the Javascript event loop.
* This is handy for example to apply some property depending on the element content:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.defer(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>;
}
export const on: On;
export type Scope = {

2
dist/esm.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}
@ -173,17 +173,30 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
export interface On {
/** Listens to the DOM event. See {@link Document.addEventListener} */
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & {
target: EL;
}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE;
connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[
string,
string
]>) => any, options?: AddEventListenerOptions): EE;
disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/**
* Fires after the next tick of the Javascript event loop.
* This is handy for example to apply some property depending on the element content:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.defer(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>;
}
export const on: On;
export type Scope = {

View File

@ -32,7 +32,6 @@ var DDE = (() => {
dispatchEvent: () => dispatchEvent,
el: () => createElement,
elNS: () => createElementNS,
elementAttribute: () => elementAttribute,
isSignal: () => isSignal,
lifecyclesToEvents: () => lifecyclesToEvents,
memo: () => memo,
@ -101,38 +100,7 @@ var DDE = (() => {
}
};
// src/signals-lib/common.js
var signals_global = {
/**
* Checks if a value is a signal
* @param {any} attributes - Value to check
* @returns {boolean} Whether the value is a signal
*/
isSignal(attributes) {
return false;
},
/**
* Processes an attribute that might be reactive
* @param {Element} obj - Element that owns the attribute
* @param {string} key - Attribute name
* @param {any} attr - Attribute value
* @param {Function} set - Function to set the attribute
* @returns {any} Processed attribute value
*/
processReactiveAttribute(obj, key, attr, set) {
return attr;
}
};
function registerReactivity(def, global = true) {
if (global) return oAssign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
function signals(_this) {
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
}
// src/dom-common.js
// src/dom-lib/common.js
var enviroment = {
setDeleteAttr,
ssr: "",
@ -158,7 +126,7 @@ var DDE = (() => {
var evd = "dde:disconnected";
var eva = "dde:attributeChanged";
// src/events-observer.js
// src/dom-lib/events-observer.js
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
get() {
return () => {
@ -322,7 +290,7 @@ var DDE = (() => {
}
}
// src/events.js
// src/dom-lib/events.js
function dispatchEvent(name, options, host) {
if (typeof options === "function") {
host = options;
@ -344,6 +312,7 @@ var DDE = (() => {
return element;
};
}
on.defer = (fn) => setTimeout.bind(null, fn, 0);
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
on.connected = function(listener, options) {
options = lifeOptions(options);
@ -367,10 +336,7 @@ var DDE = (() => {
};
};
// src/dom.js
function queue(promise) {
return enviroment.q(promise);
}
// src/dom-lib/scopes.js
var scopes = [{
get scope() {
return enviroment.D.body;
@ -445,6 +411,60 @@ var DDE = (() => {
return scopes.pop();
}
};
// src/signals-lib/common.js
var signals_global = {
/**
* Checks if a value is a signal
* @param {any} attributes - Value to check
* @returns {boolean} Whether the value is a signal
*/
isSignal(attributes) {
return false;
},
/**
* Processes an attribute that might be reactive
* @param {Element} obj - Element that owns the attribute
* @param {string} key - Attribute name
* @param {any} attr - Attribute value
* @param {Function} set - Function to set the attribute
* @returns {any} Processed attribute value
*/
processReactiveAttribute(obj, key, attr, set) {
return attr;
}
};
function registerReactivity(def, global = true) {
if (global) return oAssign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
function signals(_this) {
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
}
// src/dom-lib/helpers.js
function setRemove(obj, prop, key, val) {
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
}
function setRemoveNS(obj, prop, key, val, ns = null) {
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
}
function setDelete(obj, key, val) {
Reflect.set(obj, key, val);
if (!isUndef(val)) return;
return Reflect.deleteProperty(obj, key);
}
function elementAttribute(element, op, key, value) {
if (isInstance(element, enviroment.H))
return element[op + "Attribute"](key, value);
return element[op + "AttributeNS"](null, key, value);
}
// src/dom-lib/el.js
function queue(promise) {
return enviroment.q(promise);
}
function append(...els) {
this.appendOriginal(...els);
return this;
@ -460,7 +480,8 @@ var DDE = (() => {
const s = signals(this);
let scoped = 0;
let el, el_host;
if (Object(attributes) !== attributes || s.isSignal(attributes))
const att_type = typeof attributes;
if (att_type === "string" || att_type === "number" || s.isSignal(attributes))
attributes = { textContent: attributes };
switch (true) {
case typeof tag === "function": {
@ -514,38 +535,6 @@ var DDE = (() => {
return el;
};
}
function simulateSlots(element, root = element) {
const mark_e = "\xB9\u2070", mark_s = "\u2713";
const slots = Object.fromEntries(
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
);
element.append = new Proxy(element.append, {
apply(orig, _, els) {
if (els[0] === root) return orig.apply(element, els);
for (const el of els) {
const name = (el.slot || "") + mark_e;
try {
elementAttribute(el, "remove", "slot");
} catch (_error) {
}
const slot = slots[name];
if (!slot) return;
if (!slot.name.startsWith(mark_s)) {
slot.childNodes.forEach((c) => c.remove());
slot.name = mark_s + name;
}
slot.append(el);
}
element.append = orig;
return element;
}
});
if (element !== root) {
const els = Array.from(element.childNodes);
element.append(...els);
}
return root;
}
var assign_context = /* @__PURE__ */ new WeakMap();
var { setDeleteAttr: setDeleteAttr2 } = enviroment;
function assign(element, ...attributes) {
@ -608,11 +597,6 @@ var DDE = (() => {
);
return element;
}
function elementAttribute(element, op, key, value) {
if (isInstance(element, enviroment.H))
return element[op + "Attribute"](key, value);
return element[op + "AttributeNS"](null, key, value);
}
function isPropSetter(el, key) {
if (!(key in el)) return false;
const des = getPropDescriptor(el, key);
@ -636,19 +620,40 @@ var DDE = (() => {
cb(key, val);
});
}
function setRemove(obj, prop, key, val) {
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
}
function setRemoveNS(obj, prop, key, val, ns = null) {
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
}
function setDelete(obj, key, val) {
Reflect.set(obj, key, val);
if (!isUndef(val)) return;
return Reflect.deleteProperty(obj, key);
}
// src/customElement.js
// src/dom-lib/customElement.js
function simulateSlots(element, root = element) {
const mark_e = "\xB9\u2070", mark_s = "\u2713";
const slots = Object.fromEntries(
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
);
element.append = new Proxy(element.append, {
apply(orig, _, els) {
if (els[0] === root) return orig.apply(element, els);
for (const el of els) {
const name = (el.slot || "") + mark_e;
try {
elementAttribute(el, "remove", "slot");
} catch (_error) {
}
const slot = slots[name];
if (!slot) return;
if (!slot.name.startsWith(mark_s)) {
slot.childNodes.forEach((c) => c.remove());
slot.name = mark_s + name;
}
slot.append(el);
}
element.append = orig;
return element;
}
});
if (element !== root) {
const els = Array.from(element.childNodes);
element.append(...els);
}
return root;
}
function customElementRender(target, render, props = {}) {
const custom_element = target.host || target;
scope.push({
@ -729,19 +734,19 @@ var DDE = (() => {
// src/signals-lib/helpers.js
var mark = "__dde_signal";
var queueSignalWrite = /* @__PURE__ */ (() => {
let pendingSignals = /* @__PURE__ */ new Set();
let pendingSignals = /* @__PURE__ */ new Map();
let scheduled = false;
function flushSignals() {
scheduled = false;
const todo = pendingSignals;
pendingSignals = /* @__PURE__ */ new Set();
for (const signal2 of todo) {
pendingSignals = /* @__PURE__ */ new Map();
for (const [signal2, force] of todo) {
const M = signal2[mark];
if (M) M.listeners.forEach((l) => l(M.value));
if (M) M.listeners.forEach((l) => l(M.value, force));
}
}
return function(s) {
pendingSignals.add(s);
return function(s, force = false) {
pendingSignals.set(s, pendingSignals.get(s) || force);
if (scheduled) return;
scheduled = true;
queueMicrotask(flushSignals);
@ -778,11 +783,11 @@ var DDE = (() => {
return create(false, value, actions);
if (isSignal(value)) return value;
const out = create(true);
function contextReWatch() {
function contextReWatch(_, force) {
const [origin, ...deps_old] = deps.get(contextReWatch);
deps.set(contextReWatch, /* @__PURE__ */ new Set([origin]));
stack_watch.push(contextReWatch);
write(out, value());
write(out, value(), force);
stack_watch.pop();
if (!deps_old.length) return;
const deps_curr = deps.get(contextReWatch);
@ -804,7 +809,7 @@ var DDE = (() => {
throw new Error(`Action "${name}" not defined. See ${mark}.actions.`);
actions[name].apply(M, a);
if (M.skip) return delete M.skip;
queueSignalWrite(s);
queueSignalWrite(s, true);
};
signal.on = function on2(s, listener, options = {}) {
const { signal: as } = options;
@ -1013,7 +1018,7 @@ var DDE = (() => {
const M = s[mark];
if (!M || !force && M.value === value) return;
M.value = value;
queueSignalWrite(s);
queueSignalWrite(s, force);
return value;
}
function addSignalListener(s, listener) {

View File

@ -4,7 +4,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}
@ -173,17 +173,30 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
export interface On {
/** Listens to the DOM event. See {@link Document.addEventListener} */
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & {
target: EL;
}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE;
connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[
string,
string
]>) => any, options?: AddEventListenerOptions): EE;
disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/**
* Fires after the next tick of the Javascript event loop.
* This is handy for example to apply some property depending on the element content:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.defer(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>;
}
export const on: On;
export type Scope = {

File diff suppressed because one or more lines are too long

31
dist/iife.d.ts vendored
View File

@ -4,7 +4,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}
@ -172,17 +172,30 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
export interface On {
/** Listens to the DOM event. See {@link Document.addEventListener} */
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & {
target: EL;
}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE;
connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[
string,
string
]>) => any, options?: AddEventListenerOptions): EE;
disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/**
* Fires after the next tick of the Javascript event loop.
* This is handy for example to apply some property depending on the element content:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.defer(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>;
}
export const on: On;
export type Scope = {

183
dist/iife.js vendored
View File

@ -31,7 +31,6 @@ var DDE = (() => {
dispatchEvent: () => dispatchEvent,
el: () => createElement,
elNS: () => createElementNS,
elementAttribute: () => elementAttribute,
lifecyclesToEvents: () => lifecyclesToEvents,
memo: () => memo,
on: () => on,
@ -69,38 +68,7 @@ var DDE = (() => {
};
}
// src/signals-lib/common.js
var signals_global = {
/**
* Checks if a value is a signal
* @param {any} attributes - Value to check
* @returns {boolean} Whether the value is a signal
*/
isSignal(attributes) {
return false;
},
/**
* Processes an attribute that might be reactive
* @param {Element} obj - Element that owns the attribute
* @param {string} key - Attribute name
* @param {any} attr - Attribute value
* @param {Function} set - Function to set the attribute
* @returns {any} Processed attribute value
*/
processReactiveAttribute(obj, key, attr, set) {
return attr;
}
};
function registerReactivity(def, global = true) {
if (global) return oAssign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
function signals(_this) {
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
}
// src/dom-common.js
// src/dom-lib/common.js
var enviroment = {
setDeleteAttr,
ssr: "",
@ -126,7 +94,7 @@ var DDE = (() => {
var evd = "dde:disconnected";
var eva = "dde:attributeChanged";
// src/events-observer.js
// src/dom-lib/events-observer.js
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
get() {
return () => {
@ -290,7 +258,7 @@ var DDE = (() => {
}
}
// src/events.js
// src/dom-lib/events.js
function dispatchEvent(name, options, host) {
if (typeof options === "function") {
host = options;
@ -312,6 +280,7 @@ var DDE = (() => {
return element;
};
}
on.defer = (fn) => setTimeout.bind(null, fn, 0);
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
on.connected = function(listener, options) {
options = lifeOptions(options);
@ -335,10 +304,7 @@ var DDE = (() => {
};
};
// src/dom.js
function queue(promise) {
return enviroment.q(promise);
}
// src/dom-lib/scopes.js
var scopes = [{
get scope() {
return enviroment.D.body;
@ -413,6 +379,60 @@ var DDE = (() => {
return scopes.pop();
}
};
// src/signals-lib/common.js
var signals_global = {
/**
* Checks if a value is a signal
* @param {any} attributes - Value to check
* @returns {boolean} Whether the value is a signal
*/
isSignal(attributes) {
return false;
},
/**
* Processes an attribute that might be reactive
* @param {Element} obj - Element that owns the attribute
* @param {string} key - Attribute name
* @param {any} attr - Attribute value
* @param {Function} set - Function to set the attribute
* @returns {any} Processed attribute value
*/
processReactiveAttribute(obj, key, attr, set) {
return attr;
}
};
function registerReactivity(def, global = true) {
if (global) return oAssign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
function signals(_this) {
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
}
// src/dom-lib/helpers.js
function setRemove(obj, prop, key, val) {
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
}
function setRemoveNS(obj, prop, key, val, ns = null) {
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
}
function setDelete(obj, key, val) {
Reflect.set(obj, key, val);
if (!isUndef(val)) return;
return Reflect.deleteProperty(obj, key);
}
function elementAttribute(element, op, key, value) {
if (isInstance(element, enviroment.H))
return element[op + "Attribute"](key, value);
return element[op + "AttributeNS"](null, key, value);
}
// src/dom-lib/el.js
function queue(promise) {
return enviroment.q(promise);
}
function append(...els) {
this.appendOriginal(...els);
return this;
@ -428,7 +448,8 @@ var DDE = (() => {
const s = signals(this);
let scoped = 0;
let el, el_host;
if (Object(attributes) !== attributes || s.isSignal(attributes))
const att_type = typeof attributes;
if (att_type === "string" || att_type === "number" || s.isSignal(attributes))
attributes = { textContent: attributes };
switch (true) {
case typeof tag === "function": {
@ -482,38 +503,6 @@ var DDE = (() => {
return el;
};
}
function simulateSlots(element, root = element) {
const mark_e = "\xB9\u2070", mark_s = "\u2713";
const slots = Object.fromEntries(
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
);
element.append = new Proxy(element.append, {
apply(orig, _, els) {
if (els[0] === root) return orig.apply(element, els);
for (const el of els) {
const name = (el.slot || "") + mark_e;
try {
elementAttribute(el, "remove", "slot");
} catch (_error) {
}
const slot = slots[name];
if (!slot) return;
if (!slot.name.startsWith(mark_s)) {
slot.childNodes.forEach((c) => c.remove());
slot.name = mark_s + name;
}
slot.append(el);
}
element.append = orig;
return element;
}
});
if (element !== root) {
const els = Array.from(element.childNodes);
element.append(...els);
}
return root;
}
var assign_context = /* @__PURE__ */ new WeakMap();
var { setDeleteAttr: setDeleteAttr2 } = enviroment;
function assign(element, ...attributes) {
@ -576,11 +565,6 @@ var DDE = (() => {
);
return element;
}
function elementAttribute(element, op, key, value) {
if (isInstance(element, enviroment.H))
return element[op + "Attribute"](key, value);
return element[op + "AttributeNS"](null, key, value);
}
function isPropSetter(el, key) {
if (!(key in el)) return false;
const des = getPropDescriptor(el, key);
@ -604,19 +588,40 @@ var DDE = (() => {
cb(key, val);
});
}
function setRemove(obj, prop, key, val) {
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
}
function setRemoveNS(obj, prop, key, val, ns = null) {
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
}
function setDelete(obj, key, val) {
Reflect.set(obj, key, val);
if (!isUndef(val)) return;
return Reflect.deleteProperty(obj, key);
}
// src/customElement.js
// src/dom-lib/customElement.js
function simulateSlots(element, root = element) {
const mark_e = "\xB9\u2070", mark_s = "\u2713";
const slots = Object.fromEntries(
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
);
element.append = new Proxy(element.append, {
apply(orig, _, els) {
if (els[0] === root) return orig.apply(element, els);
for (const el of els) {
const name = (el.slot || "") + mark_e;
try {
elementAttribute(el, "remove", "slot");
} catch (_error) {
}
const slot = slots[name];
if (!slot) return;
if (!slot.name.startsWith(mark_s)) {
slot.childNodes.forEach((c) => c.remove());
slot.name = mark_s + name;
}
slot.append(el);
}
element.append = orig;
return element;
}
});
if (element !== root) {
const els = Array.from(element.childNodes);
element.append(...els);
}
return root;
}
function customElementRender(target, render, props = {}) {
const custom_element = target.host || target;
scope.push({

31
dist/iife.min.d.ts vendored
View File

@ -4,7 +4,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}
@ -172,17 +172,30 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
export interface On {
/** Listens to the DOM event. See {@link Document.addEventListener} */
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & {
target: EL;
}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE;
connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[
string,
string
]>) => any, options?: AddEventListenerOptions): EE;
disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/**
* Fires after the next tick of the Javascript event loop.
* This is handy for example to apply some property depending on the element content:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.defer(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>;
}
export const on: On;
export type Scope = {

2
dist/iife.min.js vendored

File diff suppressed because one or more lines are too long

BIN
docs/assets/devtools.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

View File

@ -184,7 +184,7 @@ import { el } from "deka-dom-el";
* @param {string} [attrs.className]
* @param {URL} [attrs.src] Example code file path
* @param {string} [attrs.content] Example code
* @param {"js"|"ts"|"html"|"css"} [attrs.language="js"] Language of the code
* @param {"js"|"ts"|"html"|"css"|"shell"} [attrs.language="js"] Language of the code
* @param {string} [attrs.page_id] ID of the page, if setted it registers shiki
* */
export function code({ id, src, content, language= "js", className= host.slice(1), page_id }){

View File

@ -0,0 +1,176 @@
import { styles } from "../ssr.js";
styles.css`
#html-to-dde-converter {
grid-column: full-main;
display: flex;
flex-direction: column;
gap: 1.5rem;
padding: 1.5rem;
border-radius: var(--border-radius);
background-color: var(--bg-sidebar);
box-shadow: var(--shadow);
border: 1px solid var(--border);
}
#html-to-dde-converter h3 {
margin-top: 0;
color: var(--primary);
}
#html-to-dde-converter .description {
color: var(--text-light);
font-size: 0.95rem;
margin-top: -1rem;
}
#html-to-dde-converter .converter-form {
display: flex;
flex-direction: column;
gap: 1rem;
}
#html-to-dde-converter .input-group,
#html-to-dde-converter .output-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
#html-to-dde-converter [type="number"]{
width: 3em;
font-variant-numeric: tabular-nums;
font-size: 1rem;
}
#html-to-dde-converter label {
font-weight: 500;
display: flex;
justify-content: space-between;
}
#html-to-dde-converter .options {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-bottom: 0.5rem;
}
#html-to-dde-converter .option-group {
display: flex;
align-items: center;
gap: 0.5rem;
}
#html-to-dde-converter textarea {
font-family: var(--font-mono);
font-size: 0.9rem;
padding: 1rem;
border-radius: var(--border-radius);
border: 1px solid var(--border);
background-color: var(--bg);
color: var(--text);
min-height: 200px;
height: 25em;
resize: vertical;
}
#html-to-dde-converter textarea:focus {
outline: 2px solid var(--primary-light);
outline-offset: 1px;
}
#html-to-dde-converter .button-group {
display: flex;
gap: 0.5rem;
justify-content: space-between;
align-items: center;
}
#html-to-dde-converter button {
padding: 0.5rem 1rem;
border-radius: var(--border-radius);
border: none;
background-color: var(--primary);
color: var(--button-text);
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s ease;
}
#html-to-dde-converter button:hover {
background-color: var(--primary-dark);
}
#html-to-dde-converter button.secondary {
background-color: transparent;
border: 1px solid var(--border);
color: var(--text);
}
#html-to-dde-converter button.secondary:hover {
background-color: var(--bg);
border-color: var(--primary);
}
#html-to-dde-converter .copy-button {
background-color: var(--secondary);
}
#html-to-dde-converter .copy-button:hover {
background-color: var(--secondary-dark);
}
#html-to-dde-converter .status {
font-size: 0.9rem;
color: var(--text-light);
}
#html-to-dde-converter .error {
color: hsl(0, 100%, 60%);
font-size: 0.9rem;
margin-top: 0.5rem;
}
/* Sample HTML examples list */
#html-to-dde-converter .examples-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.5rem;
}
#html-to-dde-converter .example-button {
font-size: 0.85rem;
padding: 0.25rem 0.5rem;
}
`;
import { ireland } from "./ireland.html.js";
import { el } from "deka-dom-el";
const fileURL= url=> new URL(url, import.meta.url);
export function converter({ page_id }){
registerClientPart(page_id);
return el(ireland, {
src: fileURL("./converter.js.js"),
exportName: "converter",
page_id,
});
}
let is_registered= {};
/** @param {string} page_id */
function registerClientPart(page_id){
if(is_registered[page_id]) return;
document.head.append(
el("script", {
// src: "https://unpkg.com/@beforesemicolon/html-parser/dist/client.js",
src: "https://cdn.jsdelivr.net/npm/@beforesemicolon/html-parser/dist/client.js",
type: "text/javascript",
charset: "utf-8",
defer: true
}),
);
is_registered[page_id]= true;
}

View File

@ -0,0 +1,384 @@
import { el, on } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
const { parse }= globalThis.BFS || { parse(){ return { children: [ "not implemented" ] } } };
// Example HTML snippets
const examples = [
{
name: "Simple Component",
html: `<div class="card">
<img src="image.jpg" alt="Card Image" class="card-image">
<h2 class="card-title">Card Title</h2>
<p class="card-text">This is a simple card component</p>
<button aria-pressed="mixed" type="button" class="card-button">Click Me</button>
</div>`
},
{
name: "Navigation",
html: `<nav class="main-nav">
<ul>
<li><a href="/" class="active">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>`
},
{
name: "Form",
html: `<form class="contact-form" onsubmit="submitForm(event)">
<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="message">Message:</label>
<textarea id="message" name="message" rows="4" required></textarea>
</div>
<button type="submit" class="submit-btn">Send Message</button>
</form>`
}
];
// Convert HTML to dd<el> code
function convertHTMLtoDDE(html, options = {}) {
try {
const parsed = parse(html);
const content = parsed.children[0] || parsed.childNodes[0];
return !content ? "" : nodeToDDE(content, options);
} catch (error) {
console.error("Parsing error:", error);
return `// Error parsing HTML: ${error.message}`;
}
}
// Node types based on standard DOM nodeType values
const NODE_TYPE = {
ELEMENT: 1, // Standard element node (equivalent to node.type === "element")
TEXT: 3, // Text node (equivalent to node.type === "text")
COMMENT: 8 // Comment node (equivalent to node.type === "comment")
};
// Convert a parsed node to dd<el> code
function nodeToDDE(node, options = {}, level = 0) {
const tab= options.indent === "-1" ? "\t" : " ".repeat(options.indent);
const indent = tab.repeat(level);
const nextIndent = tab.repeat(level + 1);
const { nodeType } = node;
// Handle text nodes
if (nodeType === NODE_TYPE.TEXT) {
const text = el("i", { innerText: node.nodeValue }).textContent;
if (!text.trim()) return null;
// Return as plain text or template string for longer text
return text.includes("\n") || text.includes('"')
? `\`${text}\``
: `"${text}"`;
}
// Handle comment nodes
if (nodeType === NODE_TYPE.COMMENT) {
const text = node.nodeValue;
if (!text.trim()) return null;
return text.includes("\n")
? [ "/*", ...text.trim().split("\n").map(l=> tab+l), "*/" ]
: [ `// ${text}` ];
}
// For element nodes
if (nodeType === NODE_TYPE.ELEMENT) {
// Special case for SVG elements
const isNS = node.tagName === "svg";
const elFunction = isNS ? "elNS" : "el";
// Get tag name
let tagStr = `"${node.tagName}"`;
// Process attributes
const attrs = [];
const sets = {
aria: {},
data: {},
}
for (const { name: key, value } of node.attributes) {
// Handle class attribute
if (key === "class") {
attrs.push(`className: "${value}"`);
continue;
}
// Handle style attribute
if (key === "style") {
if (options.styleAsObject) {
// Convert inline style to object
const styleObj = {};
value.split(";").forEach(part => {
const [propRaw, valueRaw] = part.split(":");
if (propRaw && valueRaw) {
const prop = propRaw.trim();
const propCamel = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
styleObj[propCamel] = valueRaw.trim();
}
});
if (Object.keys(styleObj).length > 0) {
const styleStr = JSON.stringify(styleObj).replace(/"([^"]+)":/g, "$1:");
attrs.push(`style: ${styleStr}`);
}
} else {
// Keep as string
attrs.push(`style: "${value}"`);
}
continue;
}
// Handle boolean attributes
if (value === "" || value === key) {
attrs.push(`${key}: true`);
continue;
}
// Handle data/aria attributes
if (key.startsWith("data-") || key.startsWith("aria-")) {
const keyName = key.startsWith("aria-") ? "aria" : "data";
const keyCamel = key.slice(keyName.length + 1).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
sets[keyName][keyCamel] = value;
continue;
}
// Regular attributes
const keyRegular = key==="for"
? "htmlFor"
: key.startsWith("on")
? `"=${key}"`
: key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
attrs.push(`${keyRegular}: "${value}"`);
}
// Process sets
for (const [name, set] of Object.entries(sets)) {
if(options.dataAttrsAsCamel)
for (const [key, value] of Object.entries(set))
attrs.push(`${name}${key[0].toUpperCase() + key.substring(1)}: "${value}"`);
else {
const setStr= Object.entries(set).map(([key, value]) => `${key}: "${value}"`).join(",");
if (setStr !== "")
attrs.push(`${name}set: { ${setStr} }`);
}
}
// Process children
const children = [];
for (const child of node.childNodes) {
const childCode = nodeToDDE(child, options, level + 1);
if (!childCode) continue;
children.push(childCode);
}
if(node.childNodes.length===1 && node.childNodes[0].nodeType===NODE_TYPE.TEXT){
const textContent= children.pop().slice(1, -1);
attrs.unshift(`textContent: "${textContent}"`);
}
// Build the element creation code
let result = `${elFunction}("${node.tagName.toLowerCase()}"`;
// Add attributes if any
if (attrs.length > 0) {
const tooLong= attrs.join(``).length+result.length > 55;
if(options.expaned || tooLong || attrs.length > 3)
result += `, {\n${nextIndent}${attrs.join(`,\n${nextIndent}`)},\n${indent}}`;
else
result += `, { ${attrs.join(", ")} }`;
}
// Add children if any
if (children.length > 0) {
const chs= children.map(ch=>
Array.isArray(ch) ? ch.map(l=> nextIndent + l).join("\n") :
nextIndent + ch + ",");
result += `).append(\n${chs.join("\n")}\n${indent})`;
} else {
result += ")";
}
return result;
}
return null;
}
export function converter() {
// State for the converter
const htmlInput = S(examples[0].html);
const error = S("");
const status = S("");
const showStatus= msg => {
status.set(msg);
// Clear status after 3 seconds
setTimeout(() => status.set(""), 3000);
};
// Options state
const options = {
styleAsObject: {
title: "Convert style to object",
value: S(true),
},
dataAttrsAsCamel: {
title: "dataKey/ariaKey (or dataset/ariaset)",
value: S(true),
},
indent: {
title: "Indentation (-1 for tabs)",
value: S("-1"),
type: "number",
},
expaned: {
title: "Force multiline",
value: S(false),
}
};
const getOptions = ()=> Object.fromEntries(Object.entries(options)
.map(([key, option]) => ([
key,
option.value.get()
]))
);
// Update the dd<el> output when input or options change
const ddeOutput = S(() => {
try {
const result = convertHTMLtoDDE(htmlInput.get(), getOptions());
error.set("");
return result;
} catch (err) {
error.set(`Error: ${err.message}`);
return "";
}
});
// Event handlers
const onConvert = on("submit", e => {
e.preventDefault();
htmlInput.set(htmlInput.get(), true);
showStatus("Converted!");
});
const onCopy = on("click", async () => {
if (!ddeOutput.get()) return;
try {
await navigator.clipboard.writeText(ddeOutput.get());
showStatus("Copied to clipboard!");
} catch (err) {
error.set(`Could not copy: ${err.message}`);
}
});
const onClear = on("click", () => {
htmlInput.set("");
showStatus("Input cleared");
});
const onExampleLoad = (example) => on("click", () => {
htmlInput.set(example.html);
showStatus(`Loaded "${example.name}" example`);
});
const optionsElements = () => Object.entries(options)
.map(([key, option]) =>
el("label", { className: "option-group" }).append(
option.type==="number"
? el("input", {
type: option.type || "checkbox",
name: key,
value: option.value.get(),
max: 10,
}, on("change", e => option.value.set(e.target.value)))
: el("input", {
type: option.type || "checkbox",
name: key,
checked: option.value.get(),
}, on("change", e => option.value.set(e.target.checked))),
option.title,
)
);
const exampleButtons = examples.map(example =>
el("button", {
type: "button",
className: "secondary example-button"
}, onExampleLoad(example)).append(example.name)
);
return el("div", { id: "html-to-dde-converter" }).append(
el("h3", "HTML to dd<el> Converter"),
el("p", { className: "description" }).append(
"Convert HTML markup to dd<el> JavaScript code. Paste your HTML below or choose from an example."
),
el("form", { className: "converter-form" }, onConvert).append(
el("div", { className: "options" }).append(...optionsElements()),
el("div", { className: "examples-list" }).append(
el("label", "Examples: "),
...exampleButtons
),
el("div", { className: "editor-container" }).append(
el("div", { className: "input-group" }).append(
el("label", { htmlFor: "html-input" }).append(
"HTML Input",
el("div", { className: "button-group" }).append(
el("button", {
type: "button",
className: "secondary",
title: "Clear input"
}, onClear).append("Clear")
)
),
el("textarea", {
id: "html-input",
spellcheck: false,
value: htmlInput,
placeholder: "Paste your HTML here or choose an example",
oninput: e => htmlInput.set(e.target.value)
})
),
el("div", { className: "output-group" }).append(
el("label", { htmlFor: "dde-output" }).append(
"dd<el> Output",
el("div", { className: "button-group" }).append(
el("button", {
textContent: "Copy",
type: "button",
className: "copy-button",
title: "Copy to clipboard",
disabled: S(() => !ddeOutput.get())
}, onCopy)
)
),
el("textarea", {
id: "dde-output",
readonly: true,
spellcheck: false,
placeholder: "The converted dd<el> code will appear here",
value: S(() => ddeOutput.get() || "// Convert HTML to see results here")
})
)
),
el("div", { className: "button-group" }).append(
S.el(error, error => !error ? el() : el("div", { className: "error" }).append(error)),
el("div", { className: "status", textContent: status }),
el("button", { type: "submit" }).append("Convert")
)
)
);
}

View File

@ -3,7 +3,6 @@ const host= "."+example.name;
styles.css`
${host} {
grid-column: full-main;
width: calc(100% - .75em);
height: calc(4/6 * var(--body-max-width));
border-radius: var(--border-radius);
box-shadow: var(--shadow);
@ -84,7 +83,6 @@ html[data-theme="light"] .cm-s-material .cm-error { color: #f44336 !important; }
@media (max-width: 767px) {
${host} {
height: 50vh;
max-width: 100%;
}
${host} main {
flex-grow: 1;
@ -97,7 +95,7 @@ html[data-theme="light"] .cm-s-material .cm-error { color: #f44336 !important; }
}
}
${host}[data-variant=big]{
height: 100vh;
height: 150vh;
main {
flex-flow: column nowrap;

View File

@ -0,0 +1,394 @@
/**
* Case Study: Data Dashboard with Charts
*
* This example demonstrates:
* - Integration with a third-party charting library
* - Data fetching and state management
* - Responsive layout design
* - Multiple interactive components working together
*/
import { el, on } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
/**
* Data Dashboard Component with Chart Integration
* @returns {HTMLElement} Dashboard element
*/
export function DataDashboard() {
// Mock data for demonstration
const DATA = {
sales: [42, 58, 65, 49, 72, 85, 63, 70, 78, 89, 95, 86],
visitors: [1420, 1620, 1750, 1850, 2100, 2400, 2250, 2500, 2750, 2900, 3100, 3200],
conversion: [2.9, 3.5, 3.7, 2.6, 3.4, 3.5, 2.8, 2.8, 2.8, 3.1, 3.0, 2.7],
months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
};
// Application state
const selectedYear = S(2024);
const selectedDataType = S(/** @type {'sales' | 'visitors' | 'conversion'} */ ('sales'));
const isLoading = S(false);
const error = S(null);
// Filter options
const years = [2022, 2023, 2024];
const dataTypes = [
{ id: 'sales', label: 'Sales', unit: 'K' },
{ id: 'visitors', label: 'Visitors', unit: '' },
{ id: 'conversion', label: 'Conversion Rate', unit: '%' }
];
// Computed values
const selectedData = S(() => {
return DATA[selectedDataType.get()];
});
const currentDataType = S(() => {
return dataTypes.find(type => type.id === selectedDataType.get());
});
const totalValue = S(() => {
const data = selectedData.get();
return data.reduce((sum, value) => sum + value, 0);
});
const averageValue = S(() => {
const data = selectedData.get();
return data.reduce((sum, value) => sum + value, 0) / data.length;
});
const highestValue = S(() => {
return Math.max(...selectedData.get());
});
// Event handlers
const onYearChange = on("change", e => {
selectedYear.set(parseInt(/** @type {HTMLSelectElement} */(e.target).value));
loadData();
});
const onDataTypeChange = on("click", e => {
const type = /** @type {'sales' | 'visitors' | 'conversion'} */(
/** @type {HTMLButtonElement} */(e.currentTarget).dataset.type);
selectedDataType.set(type);
});
// Simulate data loading
function loadData() {
isLoading.set(true);
error.set(null);
// Simulate API call
setTimeout(() => {
if (Math.random() > 0.9) {
// Simulate occasional error
error.set('Failed to load data. Please try again.');
}
isLoading.set(false);
}, 800);
}
// Reactive chart rendering
const chart = S(()=> {
const chart= el("canvas", { id: "chart-canvas", width: 800, height: 400 });
const ctx = chart.getContext('2d');
const data = selectedData.get();
const months = DATA.months;
const width = chart.width;
const height = chart.height;
const maxValue = Math.max(...data) * 1.1;
const barWidth = width / data.length - 10;
// Clear canvas
ctx.clearRect(0, 0, width, height);
// Draw background grid
ctx.beginPath();
ctx.strokeStyle = '#f0f0f0';
ctx.lineWidth = 1;
for(let i = 0; i < 5; i++) {
const y = height - (height * (i / 5)) - 30;
ctx.moveTo(50, y);
ctx.lineTo(width - 20, y);
// Draw grid labels
ctx.fillStyle = '#999';
ctx.font = '12px Arial';
ctx.fillText(Math.round(maxValue * (i / 5)), 20, y + 5);
}
ctx.stroke();
// Draw bars
data.forEach((value, index) => {
const x = index * (barWidth + 10) + 60;
const barHeight = (value / maxValue) * (height - 60);
// Bar
ctx.fillStyle = '#4a90e2';
ctx.fillRect(x, height - barHeight - 30, barWidth, barHeight);
// Month label
ctx.fillStyle = '#666';
ctx.font = '12px Arial';
ctx.fillText(months[index], x + barWidth/2 - 10, height - 10);
});
// Chart title
ctx.fillStyle = '#333';
ctx.font = 'bold 14px Arial';
ctx.fillText(`${currentDataType.get().label} (${selectedYear.get()})`, width/2 - 80, 20);
return chart;
});
return el("div", { className: "dashboard" }).append(
el("header", { className: "dashboard-header" }).append(
el("h1", "Sales Performance Dashboard"),
el("div", { className: "year-filter" }).append(
el("label", { htmlFor: "yearSelect", textContent: "Select Year:" }),
el("select", { id: "yearSelect" },
on.defer(el=> el.value = selectedYear.get().toString()),
onYearChange
).append(
...years.map(year => el("option", { value: year, textContent: year }))
)
)
),
// Error message (only shown when there's an error)
S.el(error, errorMsg => !errorMsg
? el()
: el("div", { className: "error-message" }).append(
el("p", errorMsg),
el("button", { textContent: "Retry", type: "button" }, on("click", loadData)),
),
),
// Loading indicator
S.el(isLoading, loading => !loading
? el()
: el("div", { className: "loading-spinner" })
),
// Main dashboard content
el("div", { className: "dashboard-content" }).append(
// Metrics cards
el("div", { className: "metrics-container" }).append(
el("div", { className: "metric-card" }).append(
el("h3", "Total"),
el("#text", S(() => `${totalValue.get().toLocaleString()}${currentDataType.get().unit}`)),
),
el("div", { className: "metric-card" }).append(
el("h3", "Average"),
el("#text", S(() => `${averageValue.get().toFixed(1)}${currentDataType.get().unit}`)),
),
el("div", { className: "metric-card" }).append(
el("h3", "Highest"),
el("#text", S(() => `${highestValue.get()}${currentDataType.get().unit}`)),
),
),
// Data type selection tabs
el("div", { className: "data-type-tabs" }).append(
...dataTypes.map(type =>
el("button", {
type: "button",
className: S(() => selectedDataType.get() === type.id ? 'active' : ''),
dataType: type.id,
textContent: type.label
}, onDataTypeChange)
)
),
// Chart container
el("div", { className: "chart-container" }).append(
S.el(chart, chart => chart)
)
),
);
}
// Render the component
document.body.append(
el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append(
el(DataDashboard)
),
el("style", `
.dashboard {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 1rem;
background: #fff;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid #eee;
}
.dashboard-header h1 {
font-size: 1.5rem;
margin: 0;
color: #333;
}
.year-filter {
display: flex;
align-items: center;
gap: 0.5rem;
}
.year-filter select {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.metrics-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
margin-bottom: 1.5rem;
}
.metric-card {
background: #f9f9f9;
border-radius: 8px;
padding: 1rem;
text-align: center;
transition: transform 0.2s ease;
}
.metric-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
}
.metric-card h3 {
margin-top: 0;
color: #666;
font-size: 0.9rem;
margin-bottom: 0.5rem;
}
.metric-card p {
font-size: 1.5rem;
font-weight: bold;
color: #333;
margin: 0;
}
.data-type-tabs {
display: flex;
border-bottom: 1px solid #eee;
margin-bottom: 1.5rem;
}
.data-type-tabs button {
background: none;
border: none;
padding: 0.75rem 1.5rem;
font-size: 1rem;
cursor: pointer;
color: #666;
position: relative;
}
.data-type-tabs button.active {
color: #4a90e2;
font-weight: 500;
}
.data-type-tabs button.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
width: 100%;
height: 3px;
background: #4a90e2;
border-radius: 3px 3px 0 0;
}
.chart-container {
background: #fff;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.loading-spinner {
display: flex;
justify-content: center;
align-items: center;
height: 100px;
}
.loading-spinner::before {
content: '';
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #4a90e2;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error-message {
background: #ffecec;
color: #e74c3c;
padding: 1rem;
border-radius: 4px;
margin-bottom: 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.error-message p {
margin: 0;
}
.error-message button {
background: #e74c3c;
color: white;
border: none;
border-radius: 4px;
padding: 0.5rem 1rem;
cursor: pointer;
}
@media (max-width: 768px) {
.metrics-container {
grid-template-columns: 1fr;
}
.dashboard-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.year-filter {
width: 100%;
}
.year-filter select {
flex-grow: 1;
}
}
`)
);

View File

@ -0,0 +1,417 @@
/**
* Case Study: Interactive Image Gallery
*
* This example demonstrates:
* - Dynamic loading of content
* - Lightbox functionality
* - Animation handling
* - Keyboard and gesture navigation
*/
import { el, memo, on } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
// Sample image data
const imagesSample = (url=> [
{ id: 1, src: url+'nature', alt: 'Nature', title: 'Beautiful Landscape' },
{ id: 2, src: url+'places', alt: 'City', title: 'Urban Architecture' },
{ id: 3, src: url+'people', alt: 'People', title: 'Street Photography' },
{ id: 4, src: url+'food', alt: 'Food', title: 'Culinary Delights' },
{ id: 5, src: url+'animals', alt: 'Animals', title: 'Wildlife' },
{ id: 6, src: url+'travel', alt: 'Travel', title: 'Adventure Awaits' },
{ id: 7, src: url+'computer', alt: 'Technology', title: 'Modern Tech' },
{ id: 8, src: url+'music', alt: 'Art', title: 'Creative Expression' },
])('https://api.algobook.info/v1/randomimage?category=');
/**
* Interactive Image Gallery Component
* @returns {HTMLElement} Gallery element
*/
export function ImageGallery(images= imagesSample) {
// Application state
const selectedImageId = S(null);
const filterTag = S('all');
const imagesToDisplay = S(() => {
const tag = filterTag.get();
if (tag === 'all') return images;
else return images.filter(img => img.alt.toLowerCase() === tag);
})
// Derived state
const selectedImage = S(() => {
const id = selectedImageId.get();
return id ? images.find(img => img.id === id) : null;
});
const isLightboxOpen = S(() => selectedImage.get() !== null);
// Event handlers
const onImageClick = id => on("click", () => {
selectedImageId.set(id);
document.body.style.overflow = 'hidden'; // Prevent scrolling when lightbox is open
// Add keyboard event listeners when lightbox opens
document.addEventListener('keydown', handleKeyDown);
});
const closeLightbox = () => {
selectedImageId.set(null);
document.body.style.overflow = ''; // Restore scrolling
// Remove keyboard event listeners when lightbox closes
document.removeEventListener('keydown', handleKeyDown);
};
const onPrevImage = e => {
e.stopPropagation(); // Prevent closing the lightbox
const images = imagesToDisplay.get();
const currentId = selectedImageId.get();
const currentIndex = images.findIndex(img => img.id === currentId);
const prevIndex = (currentIndex - 1 + images.length) % images.length;
selectedImageId.set(images[prevIndex].id);
};
const onNextImage = e => {
e.stopPropagation(); // Prevent closing the lightbox
const images = imagesToDisplay.get();
const currentId = selectedImageId.get();
const currentIndex = images.findIndex(img => img.id === currentId);
const nextIndex = (currentIndex + 1) % images.length;
selectedImageId.set(images[nextIndex].id);
};
const onFilterChange = tag => on("click", () => {
filterTag.set(tag);
});
// Keyboard navigation handler
function handleKeyDown(e) {
switch(e.key) {
case 'Escape':
closeLightbox();
break;
case 'ArrowLeft':
document.querySelector('.lightbox-prev-btn').click();
break;
case 'ArrowRight':
document.querySelector('.lightbox-next-btn').click();
break;
}
}
// Build the gallery UI
return el("div", { className: "gallery-container" }).append(
// Gallery header
el("header", { className: "gallery-header" }).append(
el("h1", "Interactive Image Gallery"),
el("p", "Click on any image to view it in the lightbox. Use arrow keys for navigation.")
),
// Filter options
el("div", { className: "gallery-filters" }).append(
el("button", {
classList: { active: S(() => filterTag.get() === 'all') },
textContent: "All"
}, onFilterChange('all')),
el("button", {
classList: { active: S(() => filterTag.get() === 'nature') },
textContent: "Nature"
}, onFilterChange('nature')),
el("button", {
classList: { active: S(() => filterTag.get() === 'urban') },
textContent: "Urban"
}, onFilterChange('urban')),
el("button", {
classList: { active: S(() => filterTag.get() === 'people') },
textContent: "People"
}, onFilterChange('people'))
),
// Image grid
el("div", { className: "gallery-grid" }).append(
S.el(imagesToDisplay, images =>
images.map(image =>
memo(image.id, ()=>
el("div", {
className: "gallery-item",
dataTag: image.alt.toLowerCase()
}).append(
el("img", {
src: image.src,
alt: image.alt,
loading: "lazy"
}, onImageClick(image.id)),
el("div", { className: "gallery-item-caption" }).append(
el("h3", image.title),
el("p", image.alt)
)
)
)
)
)
),
// Lightbox (only shown when an image is selected)
S.el(isLightboxOpen, open => !open
? el()
: el("div", { className: "lightbox-overlay" }, on("click", closeLightbox)).append(
el("div", {
className: "lightbox-content",
onClick: e => e.stopPropagation() // Prevent closing when clicking inside
}).append(
el("button", {
className: "lightbox-close-btn",
"aria-label": "Close lightbox"
}, on("click", closeLightbox)).append("×"),
el("button", {
className: "lightbox-prev-btn",
"aria-label": "Previous image"
}, on("click", onPrevImage)).append(""),
el("button", {
className: "lightbox-next-btn",
"aria-label": "Next image"
}, on("click", onNextImage)).append(""),
S.el(selectedImage, img => !img
? el()
: el("div", { className: "lightbox-image-container" }).append(
el("img", {
src: img.src,
alt: img.alt,
className: "lightbox-image"
}),
el("div", { className: "lightbox-caption" }).append(
el("h2", img.title),
el("p", img.alt)
)
)
)
)
)
),
);
}
// Render the component
document.body.append(
el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append(
el(ImageGallery)
),
el("style", `
.gallery-container {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.gallery-header {
text-align: center;
margin-bottom: 2rem;
}
.gallery-header h1 {
margin-bottom: 0.5rem;
color: #333;
}
.gallery-header p {
color: #666;
}
.gallery-filters {
display: flex;
justify-content: center;
margin-bottom: 2rem;
flex-wrap: wrap;
}
.gallery-filters button {
background: none;
border: none;
padding: 0.5rem 1.5rem;
margin: 0 0.5rem;
font-size: 1rem;
cursor: pointer;
border-radius: 30px;
transition: all 0.3s ease;
color: #555;
}
.gallery-filters button:hover {
background: #f0f0f0;
}
.gallery-filters button.active {
background: #4a90e2;
color: white;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1.5rem;
}
.gallery-item {
position: relative;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
cursor: pointer;
}
.gallery-item:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
}
.gallery-item img {
width: 100%;
height: 200px;
object-fit: cover;
display: block;
transition: transform 0.5s ease;
}
.gallery-item:hover img {
transform: scale(1.05);
}
.gallery-item-caption {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
color: white;
padding: 1rem;
transform: translateY(100%);
transition: transform 0.3s ease;
}
.gallery-item:hover .gallery-item-caption {
transform: translateY(0);
}
.gallery-item-caption h3 {
margin: 0 0 0.5rem;
font-size: 1.2rem;
}
.gallery-item-caption p {
margin: 0;
font-size: 0.9rem;
opacity: 0.8;
}
/* Lightbox styles */
.lightbox-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.9);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
padding: 2rem;
}
.lightbox-content {
position: relative;
max-width: 90%;
max-height: 90%;
}
.lightbox-image-container {
overflow: hidden;
border-radius: 4px;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
background: #000;
}
.lightbox-image {
max-width: 100%;
max-height: 80vh;
display: block;
margin: 0 auto;
}
.lightbox-caption {
background: #222;
color: white;
padding: 1rem;
text-align: center;
}
.lightbox-caption h2 {
margin: 0 0 0.5rem;
}
.lightbox-caption p {
margin: 0;
opacity: 0.8;
}
.lightbox-close-btn,
.lightbox-prev-btn,
.lightbox-next-btn {
background: rgba(0, 0, 0, 0.5);
color: white;
border: none;
font-size: 1.5rem;
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: background 0.3s ease;
position: absolute;
}
.lightbox-close-btn:hover,
.lightbox-prev-btn:hover,
.lightbox-next-btn:hover {
background: rgba(0, 0, 0, 0.8);
}
.lightbox-close-btn {
top: -25px;
right: -25px;
}
.lightbox-prev-btn {
left: -25px;
top: 50%;
transform: translateY(-50%);
}
.lightbox-next-btn {
right: -25px;
top: 50%;
transform: translateY(-50%);
}
@media (max-width: 768px) {
.gallery-container {
padding: 1rem;
}
.gallery-grid {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 1rem;
}
.lightbox-prev-btn,
.lightbox-next-btn {
width: 40px;
height: 40px;
font-size: 1.2rem;
}
}
`)
);

View File

@ -0,0 +1,342 @@
/**
* Case Study: Interactive Form with Validation
*
* This example demonstrates:
* - Form handling with real-time validation
* - Reactive UI updates based on input state
* - Complex form state management
* - Clean separation of concerns (data, validation, UI)
*/
import { dispatchEvent, el, on, scope } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
/**
* @typedef {Object} FormState
* @property {string} name
* @property {string} email
* @property {string} password
* @property {string} confirmPassword
* @property {boolean} agreedToTerms
* */
/**
* Interactive Form with Validation Component
* @returns {HTMLElement} Form element
*/
export function InteractiveForm() {
const submitted = S(false);
/** @type {FormState|null} */
let formState = null;
/** @param {CustomEvent<FormState>} event */
const onSubmit = ({ detail }) => {
submitted.set(true);
formState = detail;
};
const onAnotherAccount = () => {
submitted.set(false)
formState = null;
};
return el("div", { className: "form-container" }).append(
S.el(submitted, s => s
? el("div", { className: "success-message" }).append(
el("h3", "Thank you for registering!"),
el("p", `Welcome, ${formState.name}! Your account has been created successfully.`),
el("button", { textContent: "Register another account", type: "button" },
on("click", onAnotherAccount)
),
)
: el(Form, { initial: formState }, on("form:submit", onSubmit))
)
);
}
/**
* Form Component
* @type {(props: { initial: FormState | null }) => HTMLElement}
* */
export function Form({ initial }) {
const { host }= scope;
// Form state management
const formState = S(initial || {
name: '',
email: '',
password: '',
confirmPassword: '',
agreedToTerms: false
}, {
/**
* @template {keyof FormState} K
* @param {K} key
* @param {FormState[K]} value
* */
update(key, value) {
this.value[key] = value;
}
});
// Derived signals for validation
const nameValid = S(() => formState.get().name.length >= 3);
const emailValid = S(() => {
const email = formState.get().email;
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
});
const passwordValid = S(() => {
const password = formState.get().password;
return password.length >= 8 && /[A-Z]/.test(password) && /[0-9]/.test(password);
});
const passwordsMatch = S(() => {
const { password, confirmPassword } = formState.get();
return password === confirmPassword && confirmPassword !== '';
});
const termsAgreed = S(() => formState.get().agreedToTerms);
// Overall form validity
const formValid = S(() =>
nameValid.get() &&
emailValid.get() &&
passwordValid.get() &&
passwordsMatch.get() &&
termsAgreed.get()
);
// Event handlers
/**
* Event handler for input events
* @param {"value"|"checked"} prop
* @returns {(ev: Event) => void}
* */
const onChange= prop => ev => {
const input = /** @type {HTMLInputElement} */(ev.target);
S.action(formState, "update", /** @type {keyof FormState} */(input.id), input[prop]);
};
const dispatcSubmit = dispatchEvent("form:submit", host);
const onSubmit = on("submit", e => {
e.preventDefault();
if (!formValid.get()) {
return;
}
dispatcSubmit(formState.get());
});
// Component UI
return el("form", { className: "registration-form" }, onSubmit).append(
el("h2", "Create an Account"),
// Name field
el("div", { classList: {
"form-group": true,
valid: nameValid,
invalid: S(()=> !nameValid.get() && formState.get().name)
}}).append(
el("label", { htmlFor: "name", textContent: "Full Name" }),
el("input", {
id: "name",
type: "text",
value: formState.get().name,
placeholder: "Enter your full name"
}, on("input", onChange("value"))),
el("div", { className: "validation-message", textContent: "Name must be at least 3 characters long" }),
),
// Email field
el("div", { classList: {
"form-group": true,
valid: emailValid,
invalid: S(()=> !emailValid.get() && formState.get().email)
}}).append(
el("label", { htmlFor: "email", textContent: "Email Address" }),
el("input", {
id: "email",
type: "email",
value: formState.get().email,
placeholder: "Enter your email address"
}, on("input", onChange("value"))),
el("div", { className: "validation-message", textContent: "Please enter a valid email address" })
),
// Password field
el("div", { classList: {
"form-group": true,
valid: passwordValid,
invalid: S(()=> !passwordValid.get() && formState.get().password)
}}).append(
el("label", { htmlFor: "password", textContent: "Password" }),
el("input", {
id: "password",
type: "password",
value: formState.get().password,
placeholder: "Create a password"
}, on("input", onChange("value"))),
el("div", {
className: "validation-message",
textContent: "Password must be at least 8 characters with at least one uppercase letter and one number",
}),
),
// Confirm password field
el("div", { classList: {
"form-group": true,
valid: passwordsMatch,
invalid: S(()=> !passwordsMatch.get() && formState.get().confirmPassword)
}}).append(
el("label", { htmlFor: "confirmPassword", textContent: "Confirm Password" }),
el("input", {
id: "confirmPassword",
type: "password",
value: formState.get().confirmPassword,
placeholder: "Confirm your password"
}, on("input", onChange("value"))),
el("div", { className: "validation-message", textContent: "Passwords must match" }),
),
// Terms agreement
el("div", { className: "form-group checkbox-group" }).append(
el("input", {
id: "agreedToTerms",
type: "checkbox",
checked: formState.get().agreedToTerms
}, on("change", onChange("checked"))),
el("label", { htmlFor: "agreedToTerms", textContent: "I agree to the Terms and Conditions" }),
),
// Submit button
el("button", {
textContent: "Create Account",
type: "submit",
className: "submit-button",
disabled: S(() => !formValid.get())
}),
);
}
// Render the component
document.body.append(
el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append(
el(InteractiveForm)
),
el("style", `
.form-container {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
max-width: 500px;
margin: 0 auto;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
background: #fff;
}
h2 {
margin-top: 0;
color: #333;
margin-bottom: 1.5rem;
}
.form-group {
margin-bottom: 1.5rem;
position: relative;
transition: all 0.3s ease;
}
label {
display: block;
margin-bottom: 0.5rem;
color: #555;
font-weight: 500;
}
input[type="text"],
input[type="email"],
input[type="password"] {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
transition: border-color 0.3s ease;
}
input:focus {
outline: none;
border-color: #4a90e2;
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
}
.checkbox-group {
display: flex;
align-items: center;
}
.checkbox-group label {
margin: 0 0 0 0.5rem;
}
.validation-message {
font-size: 0.85rem;
color: #e74c3c;
margin-top: 0.5rem;
height: 0;
overflow: hidden;
opacity: 0;
transition: all 0.3s ease;
}
.form-group.invalid .validation-message {
height: auto;
opacity: 1;
}
.form-group.valid input {
border-color: #2ecc71;
}
.form-group.invalid input {
border-color: #e74c3c;
}
.submit-button {
background-color: #4a90e2;
color: white;
border: none;
border-radius: 4px;
padding: 0.75rem 1.5rem;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s ease;
width: 100%;
}
.submit-button:hover:not(:disabled) {
background-color: #3a7bc8;
}
.submit-button:disabled {
background-color: #b5b5b5;
cursor: not-allowed;
}
.success-message {
text-align: center;
color: #2ecc71;
}
.success-message h3 {
margin-top: 0;
}
.success-message button {
background-color: #2ecc71;
color: white;
border: none;
border-radius: 4px;
padding: 0.75rem 1.5rem;
font-size: 1rem;
cursor: pointer;
margin-top: 1rem;
}
.success-message button:hover {
background-color: #27ae60;
}
`),
);

View File

@ -0,0 +1,715 @@
/**
* Case Study: Task Manager Application
*
* This example demonstrates:
* - Complex state management with signals
* - Drag and drop functionality
* - Local storage persistence
* - Responsive design for different devices
*/
import { el, on, dispatchEvent, scope } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
/** @typedef {{ id: number, title: string, description: string, priority: string, status: string }} Task */
/**
* Task Manager Component
* @returns {HTMLElement} Task manager UI
*/
export function TaskManager() {
// <Tasks store>
const STORAGE_KEY = 'dde-task-manager';
const STATUSES = {
TODO: 'todo',
IN_PROGRESS: 'in-progress',
DONE: 'done'
};
/** @type {Task[]} */
let initialTasks = [];
try {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
initialTasks = JSON.parse(saved);
}
} catch (e) {
console.error('Failed to load tasks from localStorage', e);
}
if (!initialTasks.length) {
initialTasks = [
{ id: 1, title: 'Create project structure', description: 'Set up folders and initial files',
status: STATUSES.DONE, priority: 'high' },
{ id: 2, title: 'Design UI components', description: 'Create mockups for main views',
status: STATUSES.IN_PROGRESS, priority: 'medium' },
{ id: 3, title: 'Implement authentication', description: 'Set up user login and registration',
status: STATUSES.TODO, priority: 'high' },
{ id: 4, title: 'Write documentation', description: 'Document API endpoints and usage examples',
status: STATUSES.TODO, priority: 'low' },
];
}
const tasks = S(initialTasks, {
add(task) { this.value.push(task); },
remove(id) { this.value = this.value.filter(task => task.id !== id); },
update(id, task) {
const current= this.value.find(t => t.id === id);
if (current) Object.assign(current, task);
}
});
S.on(tasks, value => {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(value));
} catch (e) {
console.error('Failed to save tasks to localStorage', e);
}
});
// </Tasks store>
const filterPriority = S('all');
const searchQuery = S('');
// Filtered tasks based on priority and search query
const filteredTasks = S(() => {
let filtered = tasks.get();
// Filter by priority
if (filterPriority.get() !== 'all') {
filtered = filtered.filter(task => task.priority === filterPriority.get());
}
// Filter by search query
const query = searchQuery.get().toLowerCase();
if (query) {
filtered = filtered.filter(task =>
task.title.toLowerCase().includes(query) ||
task.description.toLowerCase().includes(query)
);
}
return filtered;
});
/** Tasks grouped by status for display in columns */
const tasksByStatus = S(() => {
const filtered = filteredTasks.get();
return {
[STATUSES.TODO]: filtered.filter(t => t.status === STATUSES.TODO),
[STATUSES.IN_PROGRESS]: filtered.filter(t => t.status === STATUSES.IN_PROGRESS),
[STATUSES.DONE]: filtered.filter(t => t.status === STATUSES.DONE)
};
});
// <Add> signals and handlers for adding new tasks
const newTask = { title: '', description: '', priority: 'medium' };
const onAddTask = e => {
e.preventDefault();
if (!newTask.title) return;
S.action(tasks, "add", {
id: Date.now(),
status: STATUSES.TODO,
...newTask
});
e.target.reset();
};
// </Add>
const onCardEdit= on("card:edit", /** @param {CardEditEvent} ev */({ detail: [ id, task ] })=>
S.action(tasks, "update", id, task));
const onCardDelete= on("card:delete", /** @param {CardDeleteEvent} ev */({ detail: id })=>
S.action(tasks, "remove", id));
const { onDragable, onDragArea }= moveElementAddon(
(id, status) => S.action(tasks, "update", id, { status })
);
// Build the task manager UI
return el("div", { className: "task-manager" }).append(
el("header", { className: "app-header" }).append(
el("h1", "DDE Task Manager"),
el("div", { className: "app-controls" }).append(
el("input", {
type: "text",
placeholder: "Search tasks...",
value: searchQuery.get()
}, on("input", e => searchQuery.set(e.target.value))),
el("select", null,
on.defer(el=> el.value= filterPriority.get()),
on("change", e => filterPriority.set(e.target.value))
).append(
el("option", { value: "all", textContent: "All Priorities" }),
el("option", { value: "low", textContent: "Low Priority" }),
el("option", { value: "medium", textContent: "Medium Priority" }),
el("option", { value: "high", textContent: "High Priority" })
)
)
),
// Add new task form
el("form", { className: "new-task-form" }, on("submit", onAddTask)).append(
el("div", { className: "form-row" }).append(
el("input", {
type: "text",
placeholder: "New task title",
value: newTask.title,
required: true
}, on("input", e => newTask.title= e.target.value.trim())),
el("select", null,
on.defer(el=> el.value= newTask.priority),
on("change", e => newTask.priority= e.target.value)
).append(
el("option", { value: "low", textContent: "Low" }),
el("option", { value: "medium", textContent: "Medium" }),
el("option", { value: "high", textContent: "High" })
),
el("button", { type: "submit", className: "add-btn" }).append("Add Task")
),
el("textarea", {
placeholder: "Task description (optional)",
value: newTask.description
}, on("input", e => newTask.description= e.target.value.trim()))
),
// Task board with columns
el("div", { className: "task-board" }).append(
// Todo column
el("div", {
id: `column-${STATUSES.TODO}`,
className: "task-column"
}, onDragArea(STATUSES.TODO)).append(
el("h2", { className: "column-header" }).append(
"To Do ",
el("span", {
textContent: S(() => tasksByStatus.get()[STATUSES.TODO].length),
className: "task-count"
}),
),
S.el(S(() => tasksByStatus.get()[STATUSES.TODO]), tasks =>
el("div", { className: "column-tasks" }).append(
...tasks.map(task=> el(TaskCard, { task, onDragable }, onCardEdit, onCardDelete))
)
)
),
// In Progress column
el("div", {
id: `column-${STATUSES.IN_PROGRESS}`,
className: "task-column"
}, onDragArea(STATUSES.IN_PROGRESS)).append(
el("h2", { className: "column-header" }).append(
"In Progress ",
el("span", {
textContent: S(() => tasksByStatus.get()[STATUSES.IN_PROGRESS].length),
className: "task-count",
}),
),
S.el(S(() => tasksByStatus.get()[STATUSES.IN_PROGRESS]), tasks =>
el("div", { className: "column-tasks" }).append(
...tasks.map(task=> el(TaskCard, { task, onDragable }, onCardEdit, onCardDelete))
)
)
),
// Done column
el("div", {
id: `column-${STATUSES.DONE}`,
className: "task-column"
}, onDragArea(STATUSES.DONE)).append(
el("h2", { className: "column-header" }).append(
"Done ",
el("span", {
textContent: S(() => tasksByStatus.get()[STATUSES.DONE].length),
className: "task-count",
}),
),
S.el(S(() => tasksByStatus.get()[STATUSES.DONE]), tasks =>
el("div", { className: "column-tasks" }).append(
...tasks.map(task=> el(TaskCard, { task, onDragable }, onCardEdit, onCardDelete))
)
)
)
),
);
}
/** @typedef {CustomEvent<[ string, Task ]>} CardEditEvent */
/** @typedef {CustomEvent<string>} CardDeleteEvent */
/**
* Task Card Component
* @type {(props: { task: Task, onDragable: (id: number) => ddeElementAddon<HTMLDivElement> }) => HTMLElement}
* @fires {CardEditEvent} card:edit
* @fires {CardDeleteEvent} card:delete
* */
function TaskCard({ task, onDragable }){
const { host }= scope;
const isEditing = S(false);
const onEditStart = () => isEditing.set(true);
const dispatchEdit= dispatchEvent("card:edit", host);
const dispatchDelete= dispatchEvent("card:delete", host).bind(null, task.id);
return el("div", {
id: `task-${task.id}`,
className: `task-card priority-${task.priority}`,
draggable: true
}, onDragable(task.id)).append(
S.el(isEditing, editing => editing
? el(EditMode)
: el().append(
el("div", { className: "task-header" }).append(
el("h3", { className: "task-title", textContent: task.title }),
el("div", { className: "task-actions" }).append(
el("button", {
textContent: "✎",
className: "edit-btn",
ariaLabel: "Edit task"
}, on("click", onEditStart)),
el("button", {
textContent: "✕",
className: "delete-btn",
ariaLabel: "Delete task"
}, on("click", dispatchDelete))
)
),
!task.description
? el()
: el("p", { className: "task-description", textContent: task.description }),
el("div", { className: "task-meta" }).append(
el("span", {
className: `priority-badge priority-${task.priority}`,
textContent: task.priority.charAt(0).toUpperCase() + task.priority.slice(1)
})
)
)
)
);
function EditMode(){
const onSubmit = on("submit", e => {
e.preventDefault();
const formData = new FormData(/** @type {HTMLFormElement} */(e.target));
const title = formData.get("title");
const description = formData.get("description");
const priority = formData.get("priority");
isEditing.set(false);
dispatchEdit([ task.id, { title, description, priority } ]);
})
const onEditCancel = () => isEditing.set(false);
return el("form", { className: "task-edit-form" }, onSubmit).append(
el("input", {
name: "title",
className: "task-title-input",
defaultValue: task.title,
placeholder: "Task title",
required: true,
autoFocus: true
}),
el("textarea", {
name: "description",
className: "task-desc-input",
defaultValue: task.description,
placeholder: "Description (optional)"
}),
el("select", {
name: "priority",
}, on.defer(el=> el.value = task.priority)).append(
el("option", { value: "low", textContent: "Low Priority" }),
el("option", { value: "medium", textContent: "Medium Priority" }),
el("option", { value: "high", textContent: "High Priority" })
),
el("div", { className: "task-edit-actions" }).append(
el("button", {
textContent: "Cancel",
type: "button",
className: "cancel-btn"
}, on("click", onEditCancel)),
el("button", {
textContent: "Save",
type: "submit",
className: "save-btn"
})
)
);
}
}
/**
* Helper function to handle move an element
* @param {(id: string, status: string) => void} onMoved
* */
function moveElementAddon(onMoved){
let draggedTaskId = null;
function onDragable(id) {
return element => {
on("dragstart", e => {
draggedTaskId= id;
e.dataTransfer.effectAllowed = 'move';
// Add some styling to the element being dragged
setTimeout(() => {
const el = document.getElementById(`task-${id}`);
if (el) el.classList.add('dragging');
}, 0);
})(element);
on("dragend", () => {
draggedTaskId= null;
// Remove the styling
const el = document.getElementById(`task-${id}`);
if (el) el.classList.remove('dragging');
})(element);
};
}
function onDragArea(status) {
return element => {
on("dragover", e => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
// Add a visual indicator for the drop target
const column = document.getElementById(`column-${status}`);
if (column) column.classList.add('drag-over');
})(element);
on("dragleave", () => {
// Remove the visual indicator
const column = document.getElementById(`column-${status}`);
if (column) column.classList.remove('drag-over');
})(element);
on("drop", e => {
e.preventDefault();
const id = draggedTaskId;
if (id) onMoved(id, status);
// Remove the visual indicator
const column = document.getElementById(`column-${status}`);
if (column) column.classList.remove('drag-over');
})(element);
};
}
return { onDragable, onDragArea };
}
// Render the component
document.body.append(
el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append(
el(TaskManager)
),
el("style", `
.task-manager {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
color: #333;
}
.app-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
flex-wrap: wrap;
gap: 1rem;
}
.app-header h1 {
margin: 0;
color: #2d3748;
}
.app-controls {
display: flex;
gap: 1rem;
}
.app-controls input,
.app-controls select {
padding: 0.5rem;
border: 1px solid #e2e8f0;
border-radius: 4px;
font-size: 0.9rem;
}
.new-task-form {
background: white;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
margin-bottom: 2rem;
}
.form-row {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
}
.form-row input {
flex-grow: 1;
padding: 0.75rem;
border: 1px solid #e2e8f0;
border-radius: 4px;
font-size: 1rem;
}
.form-row select {
width: 100px;
padding: 0.75rem;
border: 1px solid #e2e8f0;
border-radius: 4px;
font-size: 1rem;
}
.add-btn {
background: #4a90e2;
color: white;
border: none;
border-radius: 4px;
padding: 0.75rem 1.5rem;
font-size: 1rem;
cursor: pointer;
transition: background 0.2s ease;
}
.add-btn:hover {
background: #3a7bc8;
}
.new-task-form textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #e2e8f0;
border-radius: 4px;
font-size: 1rem;
resize: vertical;
min-height: 80px;
}
.task-board {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
.task-column {
background: #f7fafc;
border-radius: 8px;
padding: 1rem;
min-height: 400px;
transition: background 0.2s ease;
}
.column-header {
margin-top: 0;
padding-bottom: 0.75rem;
border-bottom: 2px solid #e2e8f0;
font-size: 1.25rem;
color: #2d3748;
display: flex;
align-items: center;
}
.task-count {
display: inline-flex;
justify-content: center;
align-items: center;
background: #e2e8f0;
color: #4a5568;
border-radius: 50%;
width: 25px;
height: 25px;
font-size: 0.875rem;
margin-left: 0.5rem;
}
.column-tasks {
margin-top: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
min-height: 200px;
}
.task-card {
background: white;
border-radius: 6px;
padding: 1rem;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
cursor: grab;
transition: transform 0.2s ease, box-shadow 0.2s ease;
position: relative;
border-left: 4px solid #ccc;
}
.task-card:hover {
transform: translateY(-3px);
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
}
.task-card.dragging {
opacity: 0.5;
cursor: grabbing;
}
.task-card.priority-low {
border-left-color: #38b2ac;
}
.task-card.priority-medium {
border-left-color: #ecc94b;
}
.task-card.priority-high {
border-left-color: #e53e3e;
}
.task-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 0.5rem;
}
.task-title {
margin: 0;
font-size: 1.1rem;
color: #2d3748;
word-break: break-word;
}
.task-description {
margin: 0.5rem 0;
font-size: 0.9rem;
color: #4a5568;
word-break: break-word;
}
.task-actions {
display: flex;
gap: 0.5rem;
}
.edit-btn,
.delete-btn {
background: none;
border: none;
font-size: 1rem;
cursor: pointer;
width: 24px;
height: 24px;
display: inline-flex;
justify-content: center;
align-items: center;
border-radius: 50%;
color: #718096;
transition: background 0.2s ease, color 0.2s ease;
}
.edit-btn:hover {
background: #edf2f7;
color: #4a5568;
}
.delete-btn:hover {
background: #fed7d7;
color: #e53e3e;
}
.task-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 0.75rem;
}
.priority-badge {
font-size: 0.75rem;
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-weight: 500;
}
.priority-badge.priority-low {
background: #e6fffa;
color: #2c7a7b;
}
.priority-badge.priority-medium {
background: #fefcbf;
color: #975a16;
}
.priority-badge.priority-high {
background: #fed7d7;
color: #c53030;
}
.drag-over {
background: #f0f9ff;
border: 2px dashed #4a90e2;
}
.task-edit-form {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.task-title-input,
.task-desc-input {
width: 100%;
padding: 0.5rem;
border: 1px solid #e2e8f0;
border-radius: 4px;
font-size: 0.9rem;
}
.task-desc-input {
min-height: 60px;
resize: vertical;
}
.task-edit-actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
margin-top: 0.5rem;
}
.cancel-btn,
.save-btn {
padding: 0.4rem 0.75rem;
border-radius: 4px;
font-size: 0.9rem;
cursor: pointer;
}
.cancel-btn {
background: #edf2f7;
color: #4a5568;
border: 1px solid #e2e8f0;
}
.save-btn {
background: #4a90e2;
color: white;
border: none;
}
@media (max-width: 768px) {
.app-header {
flex-direction: column;
align-items: flex-start;
}
.app-controls {
width: 100%;
flex-direction: column;
}
.form-row {
flex-direction: column;
}
.task-board {
grid-template-columns: 1fr;
}
}
`)
);

View File

@ -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));

View File

@ -1,15 +1,28 @@
import { el, on } from "deka-dom-el";
const paragraph= el("p", "See lifecycle events in console.",
el=> log({ type: "dde:created", detail: el }),
on.connected(log),
on.disconnected(log),
);
function allLifecycleEvents(){
return el("form", null,
el=> log({ type: "dde:created", detail: el }),
on.connected(log),
on.disconnected(log),
).append(
el("select", { id: "country" }, on.defer(select => {
// This runs when the select is ready with all its options
select.value = "cz"; // Pre-select Czechia
log({ type: "dde:on.defer", detail: select });
})).append(
el("option", { value: "au", textContent: "Australia" }),
el("option", { value: "ca", textContent: "Canada" }),
el("option", { value: "cz", textContent: "Czechia" }),
),
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()))
el(allLifecycleEvents),
el("button", "Remove Element", on("click", function(){
this.previousSibling.remove();
}))
);
/** @param {Partial<CustomEvent>} event */

View File

@ -7,20 +7,20 @@ function HelloWorld({ emoji = "🚀" }) {
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 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 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)
})
);
}

View File

@ -9,7 +9,7 @@ document.body.append(
padding: 1em;
margin: 1em;
}
`.trim())
`.trim())
);
export function CounterStandard() {

View File

@ -13,41 +13,38 @@ import { S } from "deka-dom-el/signals";
* @returns {HTMLElement} The root TodoMVC application element
*/
function Todos(){
const pageS = routerSignal(S);
const { signal } = scope;
const pageS = routerSignal(S, signal);
const todosS = todosSignal();
/** Derived signal that filters todos based on current route */
const filteredTodosS = S(()=> {
const todosFilteredS = S(()=> {
const todos = todosS.get();
const filter = pageS.get();
if (filter === "all") return todos;
return todos.filter(todo => {
if (filter === "active") return !todo.completed;
if (filter === "completed") return todo.completed;
return true; // "all"
});
});
// Setup hash change listener
window.addEventListener("hashchange", () => {
const hash = location.hash.replace("#", "") || "all";
S.action(pageS, "set", /** @type {"all"|"active"|"completed"} */(hash));
});
const todosRemainingS = S(()=> todosS.get().filter(todo => !todo.completed).length);
/** @type {ddeElementAddon<HTMLInputElement>} */
const onToggleAll = on("change", event => {
const checked = /** @type {HTMLInputElement} */ (event.target).checked;
S.action(todosS, "completeAll", checked);
});
const formNewTodo = "newTodo";
/** @type {ddeElementAddon<HTMLFormElement>} */
const onSubmitNewTodo = on("submit", event => {
event.preventDefault();
const input = /** @type {HTMLInputElement} */(
/** @type {HTMLFormElement} */(event.target).elements.namedItem("newTodo")
/** @type {HTMLFormElement} */(event.target).elements.namedItem(formNewTodo)
);
const title = input.value.trim();
if (title) {
S.action(todosS, "add", title);
input.value = "";
}
if (!title) return;
S.action(todosS, "add", title);
input.value = "";
});
const onClearCompleted = on("click", () => S.action(todosS, "clearCompleted"));
const onDelete = on("todo:delete", ev =>
@ -61,15 +58,16 @@ function Todos(){
el("form", null, onSubmitNewTodo).append(
el("input", {
className: "new-todo",
name: "newTodo",
name: formNewTodo,
placeholder: "What needs to be done?",
autocomplete: "off",
autofocus: true
})
)
),
S.el(todosS, todos => todos.length
? el("main", { className: "main" }).append(
S.el(todosS, todos => !todos.length
? el()
: el("main", { className: "main" }).append(
el("input", {
id: "toggle-all",
className: "toggle-all",
@ -77,55 +75,48 @@ function Todos(){
}, onToggleAll),
el("label", { htmlFor: "toggle-all", title: "Mark all as complete" }),
el("ul", { className: "todo-list" }).append(
S.el(filteredTodosS, filteredTodos => filteredTodos.map(todo =>
S.el(todosFilteredS, filteredTodos => filteredTodos.map(todo =>
memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit)))
)
)
)
: el()
),
S.el(todosS, todos => memo(todos.length, length=> length
? el("footer", { className: "footer" }).append(
S.el(todosS, todos => !todos.length
? el()
: el("footer", { className: "footer" }).append(
el("span", { className: "todo-count" }).append(
S.el(S(() => todosS.get().filter(todo => !todo.completed).length),
length=> el("strong").append(
length + " ",
length === 1 ? "item left" : "items left"
noOfLeft()
),
memo("filters", ()=>
el("ul", { className: "filters" }).append(
...[ "All", "Active", "Completed" ].map(textContent =>
el("li").append(
el("a", {
textContent,
classList: { selected: S(()=> pageS.get() === textContent.toLowerCase()) },
href: `#${textContent.toLowerCase()}`
})
)
)
)
),
el("ul", { className: "filters" }).append(
el("li").append(
el("a", {
textContent: "All",
className: S(()=> pageS.get() === "all" ? "selected" : ""),
href: "#"
}),
),
el("li").append(
el("a", {
textContent: "Active",
className: S(()=> pageS.get() === "active" ? "selected" : ""),
href: "#active"
}),
),
el("li").append(
el("a", {
textContent: "Completed",
className: S(()=> pageS.get() === "completed" ? "selected" : ""),
href: "#completed"
}),
)
),
S.el(S(() => todosS.get().some(todo => todo.completed)),
hasTodosCompleted=> hasTodosCompleted
? el("button", { textContent: "Clear completed", className: "clear-completed" }, onClearCompleted)
: el()
)
todos.length - todosRemainingS.get() === 0
? el()
: memo("delete", () =>
el("button",
{ textContent: "Clear completed", className: "clear-completed" },
onClearCompleted)
)
)
: el()
))
)
);
function noOfLeft(){
const length = todosRemainingS.get();
return el("strong").append(
length + " ",
length === 1 ? "item left" : "items left"
)
}
}
/**
@ -177,10 +168,11 @@ function TodoItem({ id, title, completed }) {
}
isEditing.set(false);
});
const formEdit = "edit";
/** @type {ddeElementAddon<HTMLFormElement>} */
const onSubmitEdit = on("submit", event => {
event.preventDefault();
const input = /** @type {HTMLFormElement} */(event.target).elements.namedItem("edit");
const input = /** @type {HTMLFormElement} */(event.target).elements.namedItem(formEdit);
const value = /** @type {HTMLInputElement} */(input).value.trim();
if (value) {
dispatchEdit({ id, title: value });
@ -207,18 +199,17 @@ function TodoItem({ id, title, completed }) {
checked: completed
}, onToggleCompleted),
el("label", { textContent: title }, onStartEdit),
el("button", { className: "destroy" }, onDelete)
el("button", { ariaLabel: "Delete todo", className: "destroy" }, onDelete)
),
S.el(isEditing, editing => editing
? el("form", null, onSubmitEdit).append(
S.el(isEditing, editing => !editing
? el()
: el("form", null, onSubmitEdit).append(
el("input", {
className: "edit",
name: "edit",
name: formEdit,
value: title,
"data-id": id
}, onBlurEdit, onKeyDown, addFocus)
)
: el()
)
);
}
@ -342,6 +333,7 @@ function todosSignal(){
localStorage.setItem(store_key, JSON.stringify(value));
} catch (e) {
console.error("Failed to save todos to localStorage", e);
// Optionally, provide user feedback
}
});
return out;
@ -350,20 +342,30 @@ function todosSignal(){
/**
* Creates a signal for managing route state
*
* @param {typeof S} signal - The signal constructor
* @param {typeof S} signal - The signal constructor from a library
* @param {AbortSignal} abortSignal
*/
function routerSignal(signal){
function routerSignal(signal, abortSignal){
const initial = location.hash.replace("#", "") || "all";
return signal(initial, {
const out = signal(initial, {
/**
* Set the current route
* @param {"all"|"active"|"completed"} hash - The route to set
*/
set(hash){
location.hash = hash;
this.value = hash;
}
//this.value = hash;
},
});
// Setup hash change listener
window.addEventListener("hashchange", () => {
const hash = location.hash.replace("#", "") || "all";
//S.action(out, "set", /** @type {"all"|"active"|"completed"} */(hash));
out.set(hash);
}, { signal: abortSignal });
return out;
}
/**

View File

@ -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);
}
});

View File

@ -0,0 +1,83 @@
import { styles } from "../ssr.js";
styles.css`
#library-url-form {
display: flex;
flex-flow: column nowrap;
gap: 1rem;
padding: 1.5rem;
border-radius: var(--border-radius);
background-color: var(--bg-sidebar);
box-shadow: var(--shadow);
border: 1px solid var(--border);
margin: 1.5rem 0;
}
#library-url-form .selectors {
display: flex;
flex-flow: row wrap;
gap: 0.75rem;
}
#library-url-form output {
display: flex;
flex-flow: column nowrap;
gap: 0.75rem;
margin-top: 0.5rem;
}
#library-url-form output p {
font-weight: 500;
margin: 0.25rem 0;
color: var(--text-light);
}
#library-url-form .url-title {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: -0.25rem;
}
#library-url-form .url-title strong {
font-family: var(--font-mono);
font-size: 0.95rem;
}
#library-url-form .url-title span {
color: var(--text-light);
font-size: 0.9rem;
}
#library-url-form .code {
margin-bottom: 1rem;
}
#library-url-form .info-text {
font-size: 0.9rem;
font-style: italic;
margin-top: 1rem;
color: var(--text-light);
}
@media (max-width: 768px) {
#library-url-form .selectors {
flex-direction: column;
}
#library-url-form select {
width: 100%;
}
}
`;
import { el } from "deka-dom-el";
import { ireland } from "./ireland.html.js";
export function getLibraryUrl({ page_id }){
return el(ireland, {
src: new URL("./getLibraryUrl.js.js", import.meta.url),
exportName: "getLibraryUrl",
page_id,
});
}

View File

@ -0,0 +1,92 @@
import { el, on } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
const url_base= {
jsdeka: "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/",
};
export function getLibraryUrl(){
const lib= S([ "esm", "-with-signals", ".min" ]);
const url= S(()=> url_base.jsdeka+lib.get().join(""));
const urlLabel= S(() => {
const [format, signalsPart, minified] = lib.get();
const formatText = format === "esm" ? "ES Module" : "IIFE";
const signalsText = signalsPart ? " with signals" : "";
const minText = minified ? " (minified)" : "";
return `${formatText}${signalsText}${minText}`;
})
const onSubmit= on("submit", ev => {
ev.preventDefault();
const form= new FormData(/** @type {HTMLFormElement} */ (ev.target));
lib.set([
"module",
"what",
"minified",
].map(name => /** @type {string} */(form.get(name))));
});
const onChangeSubmit= on("change",
ev=> /** @type {HTMLSelectElement} */(ev.target).form.requestSubmit()
);
return el("form", { id: "library-url-form" }, onSubmit).append(
el("h4", "Select your preferred library format:"),
el("div", { className: "selectors" }).append(
el("select", { name: "module" }, onChangeSubmit,
on.defer(select => select.value = lib.get()[0]),
).append(
el("option", { value: "esm", textContent: "ESM — modern JavaScript module" }),
el("option", { value: "iife", textContent: "IIFE — legacy JavaScript with DDE global variable" }),
),
el("select", { name: "what" }, onChangeSubmit,
on.defer(select => select.value = lib.get()[1]),
).append(
el("option", { value: "", textContent: "DOM part only" }),
el("option", { value: "-with-signals", textContent: "DOM + signals" }),
),
el("select", { name: "minified" }, onChangeSubmit,
on.defer(select => select.value = lib.get()[2]),
).append(
el("option", { value: "", textContent: "Unminified" }),
el("option", { value: ".min", textContent: "Minified" }),
),
),
el("output").append(
el("div", { className: "url-title" }).append(
el("strong", "JavaScript:"),
el("span", urlLabel),
),
el(code, { value: S(()=> url.get()+".js") }),
el("div", { className: "url-title" }).append(
el("strong", "TypeScript definition:")
),
el(code, { value: S(()=> url.get()+".d.ts") }),
el("p", { className: "info-text",
textContent: "Use the CDN URL in your HTML or import it in your JavaScript files."
})
)
)
}
/** @param {{ value: ddeSignal<string> }} props */
function code({ value }){
/** @type {ddeSignal<"Copy"|"Copied!">} */
const textContent= S("Copy");
const onCopy= on("click", () => {
navigator.clipboard.writeText(value.get());
textContent.set("Copied!");
setTimeout(() => {
textContent.set("Copy");
}, 1500);
});
return el("div", { className: "code", dataJs: "done", tabIndex: 0 }).append(
el("code").append(
el("pre", value),
),
el("button", {
className: "copy-button",
textContent,
ariaLabel: "Copy code to clipboard",
}, onCopy)
)
;
}

View File

@ -1,3 +1,33 @@
import { styles } from "../ssr.js";
styles.css`
[data-dde-mark] {
opacity: .5;
filter: grayscale();
@media (prefers-reduced-motion: no-preference) {
animation: fadein 2s infinite ease forwards;;
}
position: relative;
&::after {
content: "Loading Ireland…";
background-color: rgba(0, 0, 0, .5);
color: white;
font-weight: bold;
border-radius: 5px;
padding: 5px 10px;
position: absolute;
top: 3%;
left: 50%;
transform: translateX(-50%);
}
}
@keyframes fadein {
from { opacity: .5; }
to { opacity: .85; }
}
`;
import { el, queue } from "deka-dom-el";
import { addEventListener, registerClientFile } from "../ssr.js";
import { relative } from "node:path";
@ -21,10 +51,17 @@ export function ireland({ src, exportName = "default", props = {} }) {
const path= "./"+relative(dir, src.pathname);
const id = "ireland-" + generateComponentId(src);
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)));
}));
queue(
import(path)
.then(module => {
const component = module[exportName];
const content= el(component, props, mark(id));
element.replaceWith(content);
content.querySelectorAll("input, textarea, button")
.forEach(el=> el.disabled= true);
})
.catch(console.error)
);
if(!componentsRegistry.size)
addEventListener("oneachrender", registerClientPart);

View File

@ -14,10 +14,6 @@ export function mnemonic(){
el("code", "S.on(<signal>, <listener>[, <options>])"),
" — listen to the signal value changes",
),
el("li").append(
el("code", "S.clear(...<signals>)"),
" — off and clear signals",
),
el("li").append(
el("code", "S(<value>, <actions>)"),
" — signal: pattern to create complex reactive objects/arrays",
@ -29,6 +25,11 @@ export function mnemonic(){
el("li").append(
el("code", "S.el(<signal>, <function-returning-dom>)"),
" — render partial dom structure (template) based on the current signal value",
)
),
el("li").append(
el("code", "S.clear(...<signals>)"),
" — off and clear signals (most of the time it is not needed as reactive ",
"attributes and elements are cleared automatically)",
),
);
}

View File

@ -37,3 +37,14 @@ styles.css`
}
}
`;
import { el } from "deka-dom-el";
import { ireland } from "./ireland.html.js";
export function scrollTop(){
return el(ireland, {
src: new URL("./scrollTop.js.js", import.meta.url),
exportName: "scrollTop",
page_id: "*",
});
}

View File

@ -86,7 +86,7 @@ html {
}
:focus-visible {
outline: 3px solid hsl(231, 48%, 70%);
outline: 3px solid var(--primary-light);
outline-offset: 2px;
}
@ -193,6 +193,34 @@ 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%;
}
}
select {
padding: 0.5rem 0.75rem;
border-radius: var(--border-radius);
border: 1px solid var(--border);
background-color: var(--bg);
color: var(--text);
cursor: pointer;
font-size: 0.95rem;
font-family: var(--font-main);
}
select:hover {
border-color: var(--primary);
}
/* Layout */
body {
@ -234,7 +262,7 @@ body > main {
}
body > main > *, body > main slot > * {
width: 100%;
max-width: 100%;
max-width: calc(var(--body-max-width) * 5/3);
margin-inline: auto;
grid-column: main;
}
@ -267,9 +295,8 @@ body > main h3, body > main h4 {
/* Boxes */
.illustration{
grid-column: full-main;
width: calc(100% - .75em);
}
.illustration:not(:has( .comparison)){
.illustration:not(:has( .comparison)):not(:has( .tabs)) {
grid-column: main;
pre {

View File

@ -1,3 +1,4 @@
import "./components/getLibraryUrl.html.js";
import { t, T } from "./utils/index.js";
export const info= {
href: "./",
@ -11,6 +12,7 @@ import { simplePage } from "./layout/simplePage.html.js";
import { h3 } from "./components/pageUtils.html.js";
import { example } from "./components/example.html.js";
import { code } from "./components/code.html.js";
import { getLibraryUrl } from "./components/getLibraryUrl.html.js";
/** @param {string} url */
const fileURL= url=> new URL(url, import.meta.url);
const references= {
@ -27,13 +29,13 @@ const references= {
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(...T`
el("p").append(T`
Welcome to Deka DOM Elements (dd<el> or DDE) a lightweight library for building dynamic UIs with
a declarative syntax that stays close to the native DOM API. dd<el> gives you powerful reactive tools
without the complexity and overhead of larger frameworks.
`),
el("div", { className: "callout" }).append(
el("h4", t`What Makes dd<el> Special`),
el("h4", t`Key Benefits of dd<el>`),
el("ul").append(
el("li", t`No build step required — use directly in the browser`),
el("li", t`Lightweight core (~1015kB minified) without unnecessary dependencies (0 at now 😇)`),
@ -44,8 +46,8 @@ 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("p").append(...T`
el(h3, { textContent: t`The 3PS Pattern: Simplified architecture pattern`, id: "h-3ps" }),
el("p").append(T`
At the heart of dd<el> 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
to reason about.
@ -62,67 +64,98 @@ export function page({ pkg, info }){
)
)
),
el("p").append(...T`
el("p").append(T`
The 3PS pattern separates your code into three clear parts:
`),
el("ol").append(
el("li").append(...T`
el("li").append(T`
${el("strong", "Create State")}: Define your applications reactive data using signals
`),
el("li").append(...T`
${el("strong", "Bind to Elements")}: Define how UI elements react to state changes
el("li").append(T`
${el("strong", "React to Changes")}: Define how UI elements and other parts of your app react to state
changes
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Update State")}: Modify state in response to user events or other triggers
`)
),
el("p").append(...T`
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<el>. Its 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(
el("p").append(...T`
el("p").append(T`
The 3PS pattern becomes especially powerful when combined with components, allowing you to create
reusable pieces of UI with encapsulated state and behavior. Youll learn more about this in the
following sections.
`),
el("p").append(T`
The 3PS pattern isnt required to use with dd<el> but it is good practice to follow it or some similar
software architecture.
`)
),
el(h3, t`Getting Started`),
el("p").append(T`
There are multiple ways to include dd<el> in your project. You can use npm for a full development setup,
or directly include it from a CDN for quick prototyping.
`),
el("h4", "npm installation"),
el(code, { content: "npm install deka-dom-el # Coming soon", language: "shell", page_id }),
el("h4", "CDN / Direct Script Usage"),
el("p").append(T`
Use the interactive selector below to choose your preferred format:
`),
el(getLibraryUrl, { page_id }),
el("div", { className: "note" }).append(
el("p").append(T`
Based on your selection, you can use dd<el> in your project like this:
`),
el(code, { content: `
// ESM format (modern JavaScript with import/export)
import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.min.js";
// Or with IIFE format (creates a global DDE object)
// <script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/iife-with-signals.min.js"></script>
const { el, on } = DDE;
`, language: "js", page_id }),
),
el(h3, t`How to Use This Documentation`),
el("p").append(...T`
el("p").append(T`
This guide will take you through dd<el>s features step by step:
`),
el("ol", { start: 2 }).append(
el("li").append(...T`${el("a", { href: "p02-elements.html" }).append(el("strong", "Elements"))} — Creating
el("li").append(T`${el("a", { href: "p02-elements.html" }).append(el("strong", "Elements"))} — Creating
and manipulating DOM elements`),
el("li").append(...T`${el("a", { href: "p03-events.html" }).append(el("strong", "Events and Addons"))} —
el("li").append(T`${el("a", { href: "p03-events.html" }).append(el("strong", "Events and Addons"))} —
Handling user interactions and lifecycle events`),
el("li").append(...T`${el("a", { href: "p04-signals.html" }).append(el("strong", "Signals"))} — Adding
el("li").append(T`${el("a", { href: "p04-signals.html" }).append(el("strong", "Signals"))} — Adding
reactivity to your UI`),
el("li").append(...T`${el("a", { href: "p05-scopes.html" }).append(el("strong", "Scopes"))} — Managing
el("li").append(T`${el("a", { href: "p05-scopes.html" }).append(el("strong", "Scopes"))} — Managing
component lifecycles`),
el("li").append(...T`${el("a", { href: "p06-customElement.html" }).append(el("strong", "Web Components"))} —
el("li").append(T`${el("a", { href: "p06-customElement.html" }).append(el("strong", "Web Components"))} —
Building native custom elements`),
el("li").append(...T`${el("a", { href: "p07-debugging.html" }).append(el("strong", "Debugging"))} — Tools to
el("li").append(T`${el("a", { href: "p07-debugging.html" }).append(el("strong", "Debugging"))} — Tools to
help you build and fix your apps`),
el("li").append(...T`${el("a", { href: "p08-extensions.html" }).append(el("strong", "Extensions"))} —
el("li").append(T`${el("a", { href: "p08-extensions.html" }).append(el("strong", "Extensions"))} —
Integrating third-party functionalities`),
el("li").append(...T`${el("a", { href: "p09-optimization.html" })
el("li").append(T`${el("a", { href: "p09-optimization.html" })
.append(el("strong", "Performance Optimization"))} Techniques for optimizing your applications`),
el("li").append(...T`${el("a", { href: "p10-todomvc.html" }).append(el("strong", "TodoMVC"))} — A real-world
el("li").append(T`${el("a", { href: "p10-todomvc.html" }).append(el("strong", "TodoMVC"))} — A real-world
application implementation`),
el("li").append(...T`${el("a", { href: "p11-ssr.html" }).append(el("strong", "SSR"))} — Server-side
el("li").append(T`${el("a", { href: "p11-ssr.html" }).append(el("strong", "SSR"))} — Server-side
rendering with dd<el>`),
el("li").append(...T`${el("a", { href: "p12-ireland.html" }).append(el("strong", "Ireland Components"))} —
el("li").append(T`${el("a", { href: "p12-ireland.html" }).append(el("strong", "Ireland Components"))} —
Interactive demos with server-side pre-rendering`),
el("li").append(...T`${el("a", { href: "p13-appendix.html" }).append(el("strong", "Appendix & Summary"))} —
el("li").append(T`${el("a", { href: "p13-appendix.html" }).append(el("strong", "Appendix & Summary"))} —
Comprehensive reference and best practices`),
),
el("p").append(...T`
el("p").append(T`
Each section builds on the previous ones, so we recommend following them in order.
Lets get started with the basics of creating elements!
`),

View File

@ -3,10 +3,7 @@ import { el, simulateSlots } from "deka-dom-el";
import { header } from "./head.html.js";
import { prevNext } from "../components/pageUtils.html.js";
import { ireland } from "../components/ireland.html.js";
import "../components/scrollTop.css.js";
/** @param {string} url */
const fileURL= url=> new URL(url, import.meta.url);
import { scrollTop } from "../components/scrollTop.html.js";
/** @param {Pick<import("../types.d.ts").PageAttrs, "pkg" | "info">} attrs */
export function simplePage({ pkg, info }){
@ -33,6 +30,6 @@ export function simplePage({ pkg, info }){
),
// Scroll to top button
el(ireland, { src: fileURL("../components/scrollTop.js.js"), exportName: "scrollTop" })
el(scrollTop),
));
}

View File

@ -49,7 +49,7 @@ const references= {
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(...T`
el("p").append(T`
Building user interfaces in JavaScript often involves creating and manipulating DOM elements.
dd<el> provides a simple yet powerful approach to element creation that is declarative, chainable,
and maintains a clean syntax close to HTML structure.
@ -68,7 +68,7 @@ export function page({ pkg, info }){
el(code, { src: fileURL("./components/examples/elements/intro.js"), page_id }),
el(h3, t`Creating Elements: Native vs dd<el>`),
el("p").append(...T`
el("p").append(T`
In standard JavaScript, you create DOM elements using the
${el("a", references.mdn_create).append(el("code", "document.createElement()"))} method
and then set properties individually or with ${el("code", "Object.assign()")}:
@ -85,14 +85,14 @@ export function page({ pkg, info }){
)
)
),
el("p").append(...T`
el("p").append(T`
The ${el("code", "el")} function provides a simple wrapper around ${el("code", "document.createElement")}
with enhanced property assignment.
`),
el(example, { src: fileURL("./components/examples/elements/dekaCreateElement.js"), page_id }),
el(h3, t`Advanced Property Assignment`),
el("p").append(...T`
el("p").append(T`
The ${el("code", "assign")} function is the heart of dd<el>s element property handling. It is internally
used to assign properties using the ${el("code", "el")} function. ${el("code", "assign")} provides
intelligent assignment of both ${el("a", { textContent: "properties (IDL)", ...references.mdn_idl })}
@ -104,28 +104,28 @@ 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("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`),
el("dd").append(...T`Use ${el("code", "=")} prefix to force attribute mode, ${el("code", ".")} prefix to
el("dd").append(T`Use ${el("code", "=")} prefix to force attribute mode, ${el("code", ".")} prefix to
force property mode`),
el("dt", t`Attribute Removal`),
el("dd").append(...T`Pass ${el("code", "undefined")} to remove a property or attribute`)
el("dd").append(T`Pass ${el("code", "undefined")} to remove a property or attribute`)
)
),
el(example, { src: fileURL("./components/examples/elements/dekaAssign.js"), page_id }),
el("div", { className: "note" }).append(
el("p").append(...T`
el("p").append(T`
You can explore standard HTML element properties in the MDN documentation for
${el("a", { textContent: "HTMLElement", ...references.mdn_el })} (base class)
and specific element interfaces like ${el("a", { textContent: "HTMLParagraphElement", ...references.mdn_p })}.
@ -133,7 +133,7 @@ export function page({ pkg, info }){
),
el(h3, t`Building DOM Trees with Chainable Methods`),
el("p").append(...T`
el("p").append(T`
One of the most powerful features of dd<el> is its approach to building element trees.
Unlike the native DOM API which doesnt return the parent after ${el("code", "append()")}, dd<el>s
${el("code", "append()")} always returns the parent element:
@ -150,28 +150,28 @@ export function page({ pkg, info }){
)
)
),
el("p").append(...T`
el("p").append(T`
This chainable pattern is much cleaner and easier to follow, especially for deeply nested elements.
It also makes it simple to add multiple children to a parent element in a single fluent expression.
`),
el(example, { src: fileURL("./components/examples/elements/dekaAppend.js"), page_id }),
el(h3, t`Using Components to Build UI Fragments`),
el("p").append(...T`
el("p").append(T`
The ${el("code", "el")} function is overloaded to support both tag names and function components.
This lets you refactor complex UI trees into reusable pieces:
`),
el(example, { src: fileURL("./components/examples/elements/dekaBasicComponent.js"), page_id }),
el("p").append(...T`
el("p").append(T`
Component functions receive the properties object as their first argument, just like regular elements.
This makes it easy to pass data down to components and create reusable UI fragments.
`),
el("div", { className: "tip" }).append(
el("p").append(...T`
el("p").append(T`
Its helpful to use naming conventions similar to native DOM elements for your components.
This allows you to keeps your code consistent with the DOM API.
`),
el("p").append(...T`
el("p").append(T`
Use ${el("a", { textContent: "destructuring assignment", ...references.mdn_destruct })}
to extract the properties from the ${el("code", "props")} and pass them to the component element:
${el("code", "function component({ className }){ return el(\"p\", { className }); }")} for make
@ -180,29 +180,29 @@ export function page({ pkg, info }){
),
el(h3, t`Working with SVG and Other Namespaces`),
el("p").append(...T`
el("p").append(T`
For non-HTML elements like SVG, MathML, or custom namespaces, dd<el> provides the ${el("code", "elNS")}
function which corresponds to the native ${el("a", references.mdn_ns).append(el("code",
"document.createElementNS"))}:
`),
el(example, { src: fileURL("./components/examples/elements/dekaElNS.js"), page_id }),
el("p").append(...T`
el("p").append(T`
This function returns a namespace-specific element creator, allowing you to work with any element type
using the same consistent interface.
`),
el(h3, t`Best Practices for Declarative DOM Creation`),
el("ol").append(
el("li").append(...T`
el("li").append(T`
${el("strong", "Use component functions for reusable UI fragments:")} Extract common UI patterns
into reusable functions that return elements.
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Leverage destructuring for cleaner component code:")} Use
${el("a", { textContent: "destructuring", ...references.mdn_destruct })} to extract properties
from the props object for cleaner component code.
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Leverage chainable methods for better performance:")} Use chainable methods
${el("code", ".append()")} to build complex DOM trees for better performance and cleaner code.
`),

View File

@ -41,7 +41,7 @@ const references= {
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(...T`
el("p").append(T`
Events are at the core of interactive web applications. dd<el> provides a clean, declarative approach to
handling DOM events and extends this pattern with a powerful Addon system to incorporate additional
functionalities into your UI templates.
@ -60,7 +60,7 @@ export function page({ pkg, info }){
el(code, { src: fileURL("./components/examples/events/intro.js"), page_id }),
el(h3, t`Events and Listeners: Two Approaches`),
el("p").append(...T`
el("p").append(T`
In JavaScript you can listen to native DOM events using
${el("a", references.mdn_listen).append(el("code", "element.addEventListener(type, listener, options)"))}.
dd<el> provides an alternative approach with arguments ordered differently to better fit its declarative
@ -78,7 +78,7 @@ export function page({ pkg, info }){
)
)
),
el("p").append(...T`
el("p").append(T`
The main benefit of dd<el>s approach is that it works as an Addon (see below), making it easy to integrate
directly into element declarations.
`),
@ -86,15 +86,15 @@ 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<el> uses the ${el("a", {
textContent: "AbortSignal", ...references.mdn_abortListener })} for declarative approach for removal:
el("p").append(T`
Unlike the native addEventListener/removeEventListener pattern, dd<el> 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`
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`),
@ -102,7 +102,7 @@ export function page({ pkg, info }){
el("div", { className: "tab", dataTab: "html-attr" }).append(
el("h4", t`HTML Attribute Style`),
el(code, { src: fileURL("./components/examples/events/attribute-event.js"), page_id }),
el("p").append(...T`
el("p").append(T`
Forces usage as an HTML attribute. Corresponds to
${el("code", `<button onclick="console.log(event)">click me</button>`)}. This can be particularly
useful for SSR scenarios.
@ -119,13 +119,13 @@ export function page({ pkg, info }){
el("p", t`Uses the addon pattern (so adds the event listener to the element), see above.`)
)
),
el("p").append(...T`
el("p").append(T`
For a deeper comparison of these approaches, see
${el("a", { textContent: "WebReflections detailed analysis", ...references.web_events })}.
`),
el(h3, t`Understanding Addons`),
el("p").append(...T`
el("p").append(T`
Addons are a powerful pattern in dd<el> that extends beyond just event handling.
An Addon is any function that accepts an HTML element as its first parameter.
`),
@ -139,24 +139,24 @@ export function page({ pkg, info }){
el("li", t`Capture element references`)
)
),
el("p").append(...T`
el("p").append(T`
You can use Addons as 3rd argument of the ${el("code", "el")} function, making it possible to
extend your templates with additional functionality:
`),
el(example, { src: fileURL("./components/examples/events/templateWithListeners.js"), page_id }),
el("p").append(...T`
el("p").append(T`
As the example shows, you can provide types in JSDoc+TypeScript using the global type
${el("code", "ddeElementAddon")}. Notice how Addons can also be used to get element references.
`),
el(h3, t`Lifecycle Events`),
el("p").append(...T`
el("p").append(T`
Addons are called immediately when an element is created, even before its 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`
el("p").append(T`
dd<el> provides two additional lifecycle events that correspond to ${el("a", { textContent:
"custom element", ...references.mdn_customElements })} lifecycle callbacks:
"custom element", ...references.mdn_customElements })} lifecycle callbacks and component patterns:
`),
el("div", { className: "function-table" }).append(
el("dl").append(
@ -170,7 +170,7 @@ export function page({ pkg, info }){
el(example, { src: fileURL("./components/examples/events/live-cycle.js"), page_id }),
el("div", { className: "note" }).append(
el("p").append(...T`
el("p").append(T`
For regular elements (non-custom elements), dd<el> uses ${el("a",
references.mdn_mutation).append(el("code", "MutationObserver"), " | MDN")} internally to track
lifecycle events.
@ -179,28 +179,41 @@ export function page({ pkg, info }){
el("div", { className: "warning" }).append(
el("ul").append(
el("li").append(...T`
el("li").append(T`
Always use ${el("code", "on.*")} functions as library must ensure proper (MutationObserver)
registration, not ${el("code", "on('dde:*', ...)")}, even the native event system is used with event
names prefixed with ${el("code", "dde:")}.
`),
el("li").append(...T`
el("li").append(T`
Use lifecycle events sparingly, as they require internal tracking
`),
el("li").append(...T`
el("li").append(T`
Leverage parent-child relationships: when a parent is removed, all children are also removed
`),
el("li").append(...T`
el("li").append(T`
see section later in documentation regarding hosts elements
`),
el("li").append(...T`
el("li").append(T`
dd<el> ensures that connected/disconnected events fire only once for better predictability
`)
)
),
el(h3, t`Utility Helpers`),
el("p").append(T`
You can use the ${el("code", "on.defer")} helper to defer execution to the next event loop.
This is useful for example when you wan to set some element properties based on the current element
body (typically the ${el("code", "<select value=\"...\">")}).
`),
el("div", { className: "function-table" }).append(
el("dl").append(
el("dt", t`on.defer(callback)`),
el("dd", t`Helper that defers function execution to the next event loop (using setTimeout)`),
)
),
el(h3, t`Dispatching Custom Events`),
el("p").append(...T`
el("p").append(T`
This makes it easy to implement component communication through events, following standard web platform
patterns. The curried approach allows for easy reuse of event dispatchers throughout your application.
`),
@ -209,17 +222,17 @@ export function page({ pkg, info }){
el(h3, t`Best Practices`),
el("ol").append(
el("li").append(...T`
el("li").append(T`
${el("strong", "Clean up listeners")}: Use AbortSignal to prevent memory leaks
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Leverage lifecycle events")}: For component setup and teardown
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Delegate when possible")}: Add listeners to container elements when handling many
similar elements
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Maintain consistency")}: Choose one event binding approach and stick with it
`)
),

View File

@ -44,7 +44,7 @@ const references= {
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(...T`
el("p").append(T`
Signals provide a simple yet powerful way to create reactive applications with dd<el>. They handle the
fundamental challenge of keeping your UI in sync with changing data in a declarative, efficient way.
`),
@ -55,13 +55,13 @@ export function page({ pkg, info }){
el("li", t`Automatic UI updates when data changes`),
el("li", t`Clean separation between data, logic, and UI`),
el("li", t`Small runtime with minimal overhead`),
el("li").append(...T`${el("strong", "In future")} no dependencies or framework lock-in`)
el("li").append(T`${el("strong", "In future")} no dependencies or framework lock-in`)
)
),
el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }),
el(h3, t`The 3-Part Structure of Signals`),
el("p").append(...T`
el("p").append(T`
Signals organize your code into three distinct parts, following the
${el("a", { textContent: t`3PS principle`, href: "./#h-3ps" })}:
`),
@ -85,7 +85,7 @@ export function page({ pkg, info }){
el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }),
el("div", { className: "note" }).append(
el("p").append(...T`
el("p").append(T`
Signals implement the ${el("a", { textContent: t`Publishsubscribe pattern`, ...references.wiki_pubsub
})}, a form of ${el("a", { textContent: t`Event-driven programming`, ...references.wiki_event_driven
})}. This architecture allows different parts of your application to stay synchronized through
@ -110,30 +110,30 @@ export function page({ pkg, info }){
el("dd", t`S.on(signal, callback) → runs callback whenever signal changes`),
el("dt", t`Unsubscribing`),
el("dd").append(...T`S.on(signal, callback, { signal: abortController.signal }) → Similarly to the
el("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`
Signals can be created with any type of value, but they work best with ${el("a", { textContent:
t`primitive types`, ...references.mdn_primitive })} like strings, numbers, and booleans. For complex
data types like objects and arrays, youll want to use Actions (covered below).
`),
el(h3, t`Derived Signals: Computed Values`),
el("p").append(...T`
el("p").append(T`
Computed values (also called derived signals) automatically update when their dependencies change.
Create them by passing ${el("strong", "a function")} to ${el("code", "S()")}:
`),
el(example, { src: fileURL("./components/examples/signals/derived.js"), page_id }),
el("p").append(...T`
el("p").append(T`
Derived signals are read-only - you cant call ${el("code", ".set()")} on them. Their value is always
computed from their dependencies. Theyre perfect for transforming or combining data from other signals.
`),
el(example, { src: fileURL("./components/examples/signals/computations-abort.js"), page_id }),
el(h3, t`Signal Actions: For Complex State`),
el("p").append(...T`
el("p").append(T`
When working with objects, arrays, or other complex data structures. Signal Actions provide
a structured way to modify state while maintaining reactivity.
`),
@ -164,7 +164,7 @@ export function page({ pkg, info }){
`, page_id }))
),
),
el("p").append(...T`
el("p").append(T`
In some way, you can compare it with ${el("a", { textContent: "useReducer", ...references.mdn_use_reducer })}
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>)")}
@ -174,7 +174,7 @@ export function page({ pkg, info }){
`),
el(example, { src: fileURL("./components/examples/signals/actions-demo.js"), page_id }),
el("p").append(...T`
el("p").append(T`
Actions provide these benefits:
`),
el("ul").append(
@ -183,17 +183,17 @@ export function page({ pkg, info }){
el("li", t`Prevent accidental direct mutations`),
el("li", t`Act similar to reducers in other state management libraries`)
),
el("p").append(...T`
el("p").append(T`
Heres a more complete example of a todo list using signal actions:
`),
el(example, { src: fileURL("./components/examples/signals/actions-todos.js"), page_id }),
el("div", { className: "tip" }).append(
el("p").append(...T`
el("p").append(T`
${el("strong", "Special Action Methods")}: Signal actions can implement special lifecycle hooks:
`),
el("ul").append(
el("li").append(...T`
el("li").append(T`
${el("code", "[S.symbols.onclear]()")} - Called when the signal is cleared. Use it to clean up
resources.
`),
@ -201,7 +201,7 @@ export function page({ pkg, info }){
),
el(h3, t`Connecting Signals to the DOM`),
el("p").append(...T`
el("p").append(T`
Signals really shine when connected to your UI. dd<el> provides several ways to bind signals to DOM elements:
`),
@ -245,37 +245,37 @@ export function page({ pkg, info }){
)
),
el("p").append(...T`
el("p").append(T`
The ${el("code", "assign")} and ${el("code", "el")} functions detect signals automatically and handle binding.
You can use special properties like ${el("code", "dataset")}, ${el("code", "ariaset")}, and
${el("code", "classList")} for fine-grained control over specific attribute types.
`),
el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }),
el("p").append(...T`
el("p").append(T`
${el("code", "S.el()")} is especially powerful for conditional rendering and lists:
`),
el(example, { src: fileURL("./components/examples/signals/dom-el.js"), page_id }),
el(h3, t`Best Practices for Signals`),
el("p").append(...T`
el("p").append(T`
Follow these guidelines to get the most out of signals:
`),
el("ol").append(
el("li").append(...T`
el("li").append(T`
${el("strong", "Keep signals small and focused")}: Use many small signals rather than a few large ones
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Use derived signals for computations")}: Dont recompute values in multiple places
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Clean up signal subscriptions")}: Use AbortController (scope.host()) to prevent memory
leaks
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Use actions for complex state")}: Dont directly mutate objects or arrays in signals
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Avoid infinite loops")}: Be careful when one signal updates another in a subscription
`)
),
@ -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`)
)

View File

@ -29,21 +29,21 @@ const references= {
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(...T`
el("p").append(T`
For state-less components we can use functions as UI components (see Elements page). But in real life,
we may need to handle the components life-cycle and provide JavaScript the way to properly use
the ${el("a", { textContent: t`Garbage collection`, ...references.garbage_collection })}.
`),
el(code, { src: fileURL("./components/examples/scopes/intro.js"), page_id }),
el("p").append(...T`The library therefore uses ${el("em", t`scopes`)} to provide these functionalities.`),
el("p").append(T`The library therefore uses ${el("em", t`scopes`)} to provide these functionalities.`),
el(h3, t`Understanding Host Elements and Scopes`),
el("p").append(...T`
el("p").append(T`
The ${el("strong", "host")} is the name for the element representing the component. This is typically the
element returned by a function. To get a reference, you can use ${el("code", "scope.host()")}. To apply addons,
just use ${el("code", "scope.host(...<addons>)")}.
`),
el("p").append(...T`
el("p").append(T`
Scopes are primarily needed when signals are used in DOM templates (with ${el("code", "el")}, ${el("code",
"assign")}, or ${el("code", "S.el")}). They provide a way for automatically removing signal listeners
and cleaning up unused signals when components are removed from the DOM.
@ -68,7 +68,7 @@ export function page({ pkg, info }){
className: "my-component"
}).append(
el("h2", "Title"),
el("p", "Content")
el("p", "Content"),
);
}
` })
@ -86,19 +86,19 @@ export function page({ pkg, info }){
el(example, { src: fileURL("./components/examples/scopes/scopes-and-hosts.js"), page_id }),
el("div", { className: "tip" }).append(
el("p").append(...T`
el("p").append(T`
${el("strong", "Best Practice:")} Always capture the host reference (or other scope related values) at
the beginning of your component function using ${el("code", "const { host } = scope")} to avoid
scope-related issues, especially with ${el("em", "asynchronous code")}.
`),
el("p").append(...T`
el("p").append(T`
If you are interested in the implementation details, see Class-Based Components section.
`)
),
el(code, { src: fileURL("./components/examples/scopes/good-practise.js"), page_id }),
el(h3, t`Class-Based Components`),
el("p").append(...T`
el("p").append(T`
While functional components are the primary pattern in dd<el>, 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.
@ -106,7 +106,7 @@ export function page({ pkg, info }){
el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }),
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.
This prevents memory leaks and ensures resources are properly released.
`),
@ -126,7 +126,7 @@ export function page({ pkg, info }){
el(example, { src: fileURL("./components/examples/scopes/cleaning.js"), page_id }),
el("div", { className: "note" }).append(
el("p").append(...T`
el("p").append(T`
In this example, when you click "Remove", the component is removed from the DOM, and all its associated
resources are automatically cleaned up, including ${el("em",
"the signal subscription that updates the text content")}. This happens because the library
@ -135,12 +135,12 @@ export function page({ pkg, info }){
),
el(h3, t`Declarative vs Imperative Components`),
el("p").append(...T`
el("p").append(T`
The library DOM API and signals work best when used declaratively. It means you split your apps logic
into three parts as introduced in ${el("a", { textContent: "Signals (3PS)", ...references.signals })}.
`),
el("div", { className: "note" }).append(
el("p").append(...T`
el("p").append(T`
Strictly speaking, the imperative way of using the library is not prohibited. Just be careful to avoid
mixing the declarative approach (using signals) with imperative manipulation of elements.
`)
@ -165,20 +165,20 @@ export function page({ pkg, info }){
el(h3, t`Best Practices for Scopes and Components`),
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("li").append(...T`
el("li").append(T`
${el("strong", "Define signals as constants:")} ${el("code", "const counter = S(0);")}
`),
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("li").append(...T`
el("li").append(T`
${el("strong", "Keep components focused:")} Each component should do one thing well
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Add explicit cleanup:")} For resources not managed by dd<el>, use ${el("code",
"on.disconnected")}
`)

View File

@ -59,7 +59,7 @@ const references= {
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(...T`
el("p").append(T`
dd<el> pairs powerfully with ${el("a", references.mdn_web_components).append(el("strong", t`Web
Components`))} to create reusable, encapsulated custom elements with all the benefits of dd<el>s
declarative DOM construction and reactivity system.
@ -76,29 +76,29 @@ export function page({ pkg, info }){
el(code, { src: fileURL("./components/examples/customElement/intro.js"), page_id }),
el(h3, t`Getting Started: Web Components Basics`),
el("p").append(...T`
el("p").append(T`
Web Components are a set of standard browser APIs that let you create custom HTML elements with
encapsulated functionality. They consist of three main technologies:
`),
el("ul").append(
el("li").append(...T`
el("li").append(T`
${el("strong", "Custom Elements:")} Create your own HTML tags with JS-defined behavior
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Shadow DOM:")} Encapsulate styles and markup within a component
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "HTML Templates:")} Define reusable markup structures (${el("em",
"the dd<el> replaces this part")})
`)
),
el("p").append(...T`
el("p").append(T`
Lets start with a basic Custom Element example without dd<el> to establish the foundation:
`),
el(code, { src: fileURL("./components/examples/customElement/native-basic.js"), page_id }),
el("div", { className: "note" }).append(
el("p").append(...T`
el("p").append(T`
For complete information on Web Components, see the
${el("a", references.mdn_custom_elements).append(el("strong", t`MDN documentation`))}.
Also, ${el("a", references.custom_elements_tips).append(el("strong", t`Handy Custom Elements Patterns`))}
@ -107,10 +107,10 @@ export function page({ pkg, info }){
),
el(h3, t`dd<el> Integration: Step 1 - Event Handling`),
el("p").append(...T`
el("p").append(T`
The first step in integrating dd<el> with Web Components is enabling dd<el>s event system to work with your
Custom Elements. This is done with ${el("code", "customElementWithDDE")}, which makes your Custom Element
compatible with dd<el>s event handling. (${el("em").append(...T`Notice that customElementWithDDE is
compatible with dd<el>s event handling. (${el("em").append(T`Notice that customElementWithDDE is
actually`)} ${el("a", { textContent: "decorator", ...references.decorators })})
`),
el("div", { className: "function-table" }).append(
@ -127,14 +127,14 @@ export function page({ pkg, info }){
el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js"), page_id }),
el("div", { className: "tip" }).append(
el("p").append(...T`
el("p").append(T`
${el("strong", "Key Point:")} The ${el("code", "customElementWithDDE")} function adds event dispatching
to your Custom Element lifecycle methods, making them work seamlessly with dd<el>s event system.
`)
),
el(h3, t`dd<el> Integration: Step 2 - Rendering Components`),
el("p").append(...T`
el("p").append(T`
The next step is to use dd<el>s component rendering within your Custom Element. This is done with
${el("code", "customElementRender")}, which connects your dd<el> component function to the Custom Element.
`),
@ -159,32 +159,32 @@ export function page({ pkg, info }){
el(example, { src: fileURL("./components/examples/customElement/dde.js"), page_id }),
el("div", { className: "note" }).append(
el("p").append(...T`
el("p").append(T`
In this example, were using Shadow DOM (${el("code", "this.attachShadow()")}) for encapsulation,
but you can also render directly to the element with ${el("code", "customElementRender(this, ...)")}.
`)
),
el(h3, t`Reactive Web Components with Signals`),
el("p").append(...T`
el("p").append(T`
One of the most powerful features of integrating dd<el> with Web Components is connecting HTML attributes
to dd<el>s reactive signals system. This creates truly reactive custom elements.
`),
el("div", { className: "tip" }).append(
el("p").append(...T`
el("p").append(T`
${el("strong", "Two Ways to Handle Attributes:")}
`),
el("ol").append(
el("li").append(...T`
el("li").append(T`
Using standard attribute access (${el("code", "this.getAttribute(<name>)")}) - Passes attributes as
regular values (static)
`),
el("li").append(...T`
el("li").append(T`
${el("code", "S.observedAttributes")} - Transforms attributes into signals (reactive)
`)
)
),
el("p").append(...T`
el("p").append(T`
Using the ${el("code", "S.observedAttributes")} creates a reactive connection between your elements
attributes and its internal rendering. When attributes change, your component automatically updates!
`),
@ -203,7 +203,7 @@ export function page({ pkg, info }){
),
el(h3, t`Working with Shadow DOM`),
el("p").append(...T`
el("p").append(T`
Shadow DOM provides encapsulation for your components styles and markup. When using dd<el> with Shadow DOM,
you get the best of both worlds: encapsulation plus declarative DOM creation.
`),
@ -223,14 +223,14 @@ export function page({ pkg, info }){
),
el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js"), page_id }),
el("p").append(...T`
el("p").append(T`
For more information on Shadow DOM, see
${el("a", { textContent: t`Using Shadow DOM`, ...references.mdn_shadow_dom_depth })}, or the comprehensive
${el("a", { textContent: t`Shadow DOM in Depth`, ...references.shadow_dom_depth })}.
`),
el(h3, t`Working with Slots`),
el("p").append(...T`
el("p").append(T`
Besides the encapsulation, the Shadow DOM allows for using the ${el("a", references.mdn_shadow_dom_slot).append(
el("strong", t`<slot>`), t` element(s)`)}. You can simulate this feature using ${el("code", "simulateSlots")}:
`),
@ -246,23 +246,23 @@ export function page({ pkg, info }){
),
el(h3, t`Best Practices for Web Components with dd<el>`),
el("p").append(...T`
el("p").append(T`
When combining dd<el> with Web Components, follow these recommendations:
`),
el("ol").append(
el("li").append(...T`
el("li").append(T`
${el("strong", "Always use customElementWithDDE")} to enable event integration
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Prefer S.observedAttributes")} for reactive attribute connections
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Create reusable component functions")} that your custom elements render
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Use scope.host()")} to clean up event listeners and subscriptions
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Add setters and getters")} for better property access to your element
`)
),

View File

@ -17,28 +17,41 @@ const fileURL= url=> new URL(url, import.meta.url);
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(...T`
el("p").append(T`
Debugging is an essential part of application development. This guide provides techniques
and best practices for debugging applications built with dd<el>, with a focus on signals.
`),
el(h3, t`Debugging signals`),
el("p").append(...T`
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 arent
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:
el("p").append(T`
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("p").append(...T`
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:
`),
el(code, { content: `
@ -47,7 +60,7 @@ export function page({ pkg, info }){
`, page_id }),
el("h4", t`Debugging derived signals`),
el("p").append(...T`
el("p").append(T`
With derived signals (created with ${el("code", "S(() => computation))")}), debugging is a bit more complex
because the value depends on other signals. To understand why a derived signal isnt updating correctly:
`),
@ -58,6 +71,43 @@ export function page({ pkg, info }){
),
el(example, { src: fileURL("./components/examples/debugging/consoleLog.js"), page_id }),
el("h4", t`Examining signal via DevTools`),
el("p").append(T`
${el("code", "<signal>.__dde_signal")} - A Symbol property used to identify and store the internal state of
signal objects. It contains the following information:
`),
el("ul").append(
el("li", t`listeners: A Set of functions called when the signal value changes`),
el("li", t`actions: Custom actions that can be performed on the signal`),
el("li", t`onclear: Functions to run when the signal is cleared`),
el("li", t`host: Reference to the host element/scope`),
el("li", t`defined: Stack trace information for debugging`),
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()")}. Dont hesitate to
use the debugger to inspect the signal object.
`),
el("h4", t`Debugging with breakpoints`),
el("p").append(T`
Effective use of breakpoints can help track signal flow:
`),
el("ul").append(
el("li").append(T`
Set breakpoints in signal update methods to track when values change
`),
el("li").append(T`
Use conditional breakpoints to only break when specific signals change to certain values
`),
el("li").append(T`
Set breakpoints in your signal computation functions to see when derived signals recalculate
`),
el("li").append(T`
Use performance profiling to identify bottlenecks in signal updates
`)
),
el(h3, t`Common signal debugging issues`),
el("h4", t`Signal updates not triggering UI changes`),
el("p", t`If signal updates arent reflected in the UI, check:`),
@ -69,9 +119,10 @@ export function page({ pkg, info }){
el(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }),
el("h4", t`Memory leaks with signal listeners`),
el("p").append(...T`
el("p").append(T`
Signal listeners can cause memory leaks if not properly cleaned up. Always use AbortSignal
to cancel listeners.
to cancel listeners when they are used ouside the dd<el> knowledge (el, assign, S.el, auto cleanup
unnecessarily signals automatically).
`),
el("h4", t`Performance issues with frequently updating signals`),
@ -83,74 +134,39 @@ export function page({ pkg, info }){
),
el(code, { src: fileURL("./components/examples/debugging/debouncing.js"), page_id }),
el(h3, t`Browser DevTools tips for dd<el>`),
el("p").append(...T`
el(h3, t`Browser DevTools tips for components and reactivity`),
el("p").append(T`
When debugging in the browser, dd<el> provides several helpful DevTools-friendly features:
`),
el("h4", t`Identifying components in the DOM`),
el("p").append(...T`
dd<el> 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", `<!--<dde:mark type="component" name="MyComponent" host="parentElement"/>-->`)} 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`
el("p").append(T`
When using ${el("code", "S.el()")}, dd<el> 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`
el("p").append(T`
This is particularly useful when debugging why a reactive section isnt 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<el> library have special properties to aid in debugging:
`),
el("p").append(...T`
${el("code", "<element>.__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 theyre
bound to. Each entry in the array contains:
el("h4", t`Identifying components in the DOM`),
el("p").append(T`
dd<el> 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", `<!--<dde:mark type="component" name="MyComponent" host="parentElement"/>-->`)} and
includes:
`),
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("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("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("p").append(...T`
${el("code", "<signal>.__dde_signal")} - A Symbol property used to identify and store the internal state of
signal objects. It contains the following information:
`),
el("ul").append(
el("li", t`listeners: A Set of functions called when the signal value changes`),
el("li", t`actions: Custom actions that can be performed on the signal`),
el("li", t`onclear: Functions to run when the signal is cleared`),
el("li", t`host: Reference to the host element/scope`),
el("li", t`defined: Stack trace information for debugging`),
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`
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:
@ -161,29 +177,41 @@ export function page({ pkg, info }){
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`
el("p").append(T`
the structure of \`__dde_reactive\` utilizes the browsers behavior of packing the first field,
so you can see the element and property that changes in the console right away.
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", "<element>.__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 theyre
bound to. Each entry in the array contains:
`),
el("h4", t`Debugging with breakpoints`),
el("p").append(...T`
Effective use of breakpoints can help track signal flow:
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("ul").append(
el("li").append(...T`
Set breakpoints in signal update methods to track when values change
`),
el("li").append(...T`
Use conditional breakpoints to only break when specific signals change to certain values
`),
el("li").append(...T`
Set breakpoints in your signal computation functions to see when derived signals recalculate
`),
el("li").append(...T`
Use performance profiling to identify bottlenecks in signal updates
`)
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>`)
),
el("p").append(T`
Additionally, dd<el> 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<el> debugging information with event listeners and reactive
markers`)
),
);
}

View File

@ -16,21 +16,21 @@ const fileURL= url=> new URL(url, import.meta.url);
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(...T`
el("p").append(T`
dd<el> is designed with extensibility in mind. This page covers how to separate
third-party functionalities and integrate them seamlessly with the library, focusing on
proper resource cleanup and interoperability.
`),
el(h3, t`DOM Element Extensions with Addons`),
el("p").append(...T`
el("p").append(T`
The primary method for extending DOM elements in dd<el> is through the Addon pattern.
Addons are functions that take an element and applying some functionality to it. This pattern enables
a clean, functional approach to element enhancement.
`),
el("div", { className: "callout" }).append(
el("h4", t`What are Addons?`),
el("p").append(...T`
el("p").append(T`
Addons are simply functions with the signature: (element) => void. They:
`),
el("ul").append(
@ -52,13 +52,13 @@ export function page({ pkg, info }){
`, page_id }),
el(h3, t`Resource Cleanup with Abort Signals`),
el("p").append(...T`
el("p").append(T`
When extending elements with functionality that uses resources like event listeners, timers,
or external subscriptions, its critical to clean up these resources when the element is removed
from the DOM. dd<el> provides utilities for this through AbortSignal integration.
`),
el("div", { className: "tip" }).append(
el("p").append(...T`
el("p").append(T`
The ${el("code", "scope.signal")} property creates an AbortSignal that automatically
triggers when an element is disconnected from the DOM, making cleanup much easier to manage.
`)
@ -86,12 +86,11 @@ export function page({ pkg, info }){
`, page_id }),
el(h3, t`Building Library-Independent Extensions`),
el("p").append(...T`
el("p").append(T`
When creating extensions, its a good practice to make them as library-independent as possible.
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`),
@ -125,13 +124,13 @@ export function page({ pkg, info }){
),
el(h3, t`Signal Extensions and Factory Patterns`),
el("p").append(...T`
el("p").append(T`
Unlike DOM elements, signal functionality in dd<el> currently lacks a standardized
way to create library-independent extensions. This is because signals are implemented
differently across libraries.
`),
el("div", { className: "note" }).append(
el("p").append(...T`
el("p").append(T`
In the future, JavaScript may include built-in signals through the
${el("a", { href: "https://github.com/tc39/proposal-signals", textContent: "TC39 Signals Proposal" })}.
dd<el> is designed with future compatibility in mind and will hopefully support these
@ -140,7 +139,7 @@ export function page({ pkg, info }){
),
el("h4", t`The Signal Factory Pattern`),
el("p").append(...T`
el("p").append(T`
A powerful approach for extending signal functionality is the "Signal Factory" pattern.
This approach encapsulates specific behavior in a function that creates and configures a signal.
`),
@ -191,13 +190,13 @@ export function page({ pkg, info }){
)
),
el("p").append(...T`
el("p").append(T`
Note how the factory accepts the signal constructor as a parameter, making it easier to test
and potentially migrate to different signal implementations in the future.
`),
el("h4", t`Other Signal Extension Approaches`),
el("p").append(...T`
el("p").append(T`
For simpler cases, you can also extend signals with clear interfaces and isolation to make
future migration easier.
`),
@ -221,7 +220,7 @@ export function page({ pkg, info }){
`, page_id }),
el("div", { className: "tip" }).append(
el("p").append(...T`
el("p").append(T`
When designing signal extensions, consider creating specialized signals for common patterns like:
forms, API requests, persistence, animations, or routing. These can significantly reduce
boilerplate code in your applications.
@ -229,19 +228,19 @@ export function page({ pkg, info }){
),
el(h3, t`Using Signals Independently`),
el("p").append(...T`
el("p").append(T`
While signals are tightly integrated with DDEs DOM elements, you can also use them independently.
This can be useful when you need reactivity in non-UI code or want to integrate with other libraries.
`),
el("p").append(...T`
el("p").append(T`
There are two ways to import signals:
`),
el("ol").append(
el("li").append(...T`
el("li").append(T`
${el("strong", "Standard import")}: ${el("code", `import { S } from "deka-dom-el/signals";`)}
This automatically registers signals with DDEs DOM reactivity system
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Independent import")}: ${el("code", `import { S } from "deka-dom-el/src/signals-lib";`)}
This gives you just the signal system without DOM integration
`)
@ -261,7 +260,7 @@ export function page({ pkg, info }){
count.set(5); // Logs: 5
console.log(doubled.get()); // 10
`, page_id }),
el("p").append(...T`
el("p").append(T`
The independent signals API includes all core functionality (${el("code", "S()")}, ${el("code", "S.on()")},
${el("code", "S.action()")}).
`),
@ -276,27 +275,27 @@ export function page({ pkg, info }){
el(h3, t`Best Practices for Extensions`),
el("ol").append(
el("li").append(...T`
el("li").append(T`
${el("strong", "Use AbortSignals for cleanup:")} Always implement proper resource cleanup with
${el("code", "scope.signal")} or similar mechanisms
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Separate core logic from library adaptation:")} Make your core functionality work
with standard DOM APIs when possible
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Use signal factories for common patterns:")} Create reusable signal factories that encapsulate
domain-specific behavior and state logic
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Document clearly:")} Provide clear documentation on how your extension works
and what resources it uses
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Follow the Addon pattern:")} Keep to the (element) => element signature for
DOM element extensions
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Avoid modifying global state:")} Extensions should be self-contained and not
affect other parts of the application
`)

View File

@ -45,7 +45,7 @@ const references= {
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(...T`
el("p").append(T`
As your applications grow, performance becomes increasingly important. dd<el> provides several
techniques to optimize rendering performance, especially when dealing with large lists or frequently
updating components. This guide focuses on memoization and other optimization strategies.
@ -63,7 +63,7 @@ export function page({ pkg, info }){
el(code, { src: fileURL("./components/examples/optimization/intro.js"), page_id }),
el(h3, t`Memoization with memo: Native vs dd<el>`),
el("p").append(...T`
el("p").append(T`
In standard JavaScript applications, optimizing list rendering often involves manual caching
or relying on complex virtual DOM diffing algorithms. dd<el>'s ${el("code", "memo")} function
provides a simpler, more direct approach:
@ -106,13 +106,13 @@ export function page({ pkg, info }){
)
)
),
el("p").append(...T`
el("p").append(T`
The ${el("a", references.memo_docs).append(el("code", "memo"))} function in dd<el> allows you to
cache and reuse DOM elements instead of recreating them on every render, which can
significantly improve performance for components that render frequently or contain heavy computations.
`),
el("p").append(...T`
el("p").append(T`
The memo system is particularly useful for:
`),
el("ul").append(
@ -122,7 +122,7 @@ export function page({ pkg, info }){
),
el(h3, t`Using memo with Signal Rendering`),
el("p").append(...T`
el("p").append(T`
The most common use case for memoization is within ${el("code", "S.el()")} when rendering lists with
${el("code", "map()")}:
`),
@ -136,7 +136,7 @@ export function page({ pkg, info }){
))))
`, page_id }),
el("p").append(...T`
el("p").append(T`
The ${el("code", "memo")} function in this context:
`),
el("ol").append(
@ -149,7 +149,7 @@ export function page({ pkg, info }){
el(example, { src: fileURL("./components/examples/optimization/memo.js"), page_id }),
el(h3, t`Creating Memoization Scopes`),
el("p").append(...T`
el("p").append(T`
The ${el("code", "memo()")} uses cache store defined via the ${el("code", "memo.scope")} function.
That is actually what the ${el("code", "S.el")} is doing under the hood:
`),
@ -173,7 +173,7 @@ export function page({ pkg, info }){
);
`, page_id }),
el("p").append(...T`
el("p").append(T`
The scope function accepts options to customize its behavior:
`),
el(code, { content: `
@ -189,16 +189,20 @@ 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(
el("dt", t`onlyLast Option`),
el("dd").append(...T`Only keeps the cache from the most recent function call,
el("dd").append(T`Only keeps the cache from the most recent function call,
which is useful when the entire collection is replaced. ${el("strong", "This is default behavior of ")
.append(el("code", "S.el"))}!`),
el("dt", t`signal Option`),
el("dd").append(...T`An ${el("a", references.mdn_abort).append(el("code", "AbortSignal"))}
el("dd").append(T`An ${el("a", references.mdn_abort).append(el("code", "AbortSignal"))}
that will clear the cache when aborted, helping with memory management`)
)
),
@ -206,18 +210,16 @@ export function page({ pkg, info }){
el(h3, t`Additional Optimization Techniques`),
el("h4", t`Minimizing Signal Updates`),
el("p").append(...T`
el("p").append(T`
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`),
el("p").append(...T`
el("p").append(T`
Beyond memoization, consider these approaches for optimizing list rendering:
`),
el("ul").append(
@ -228,17 +230,17 @@ export function page({ pkg, info }){
),
el("div", { className: "tip" }).append(
el("p").append(...T`
el("p").append(T`
Memoization works best when your keys are stable and unique. Use IDs or other persistent
identifiers rather than array indices, which can change when items are reordered.
`),
el("p").append(...T`
el("p").append(T`
Alternatively you can use any jsonable value as key, when the primitive values arent enough.
`)
),
el("h4", t`Memory Management`),
el("p").append(...T`
el("p").append(T`
To prevent memory leaks and reduce memory consumption:
`),
el("ul").append(
@ -249,43 +251,55 @@ 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:
el("p").append(T`
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.
`)
)
),
el(h3, t`Known Issues and Limitations`),
el("p").append(...T`
el("p").append(T`
While memoization is a powerful optimization technique, there are some limitations and edge cases to be aware of:
`),
el("h4", t`Document Fragments and Memoization`),
el("p").append(...T`
el("p").append(T`
One important limitation to understand is how memoization interacts with
${el("a", references.mdn_fragment).append("DocumentFragment")} objects.
Functions like ${el("code", "S.el")} internally use DocumentFragment to efficiently handle multiple elements,
@ -305,7 +319,7 @@ export function page({ pkg, info }){
container.append(memoizedFragment); // Nothing gets appended
`, page_id }),
el("p").append(...T`
el("p").append(T`
This happens because a DocumentFragment is emptied when it's appended to the DOM. When the fragment
is cached by memo and reused, it's already empty.
`),
@ -327,7 +341,7 @@ export function page({ pkg, info }){
`, page_id })
),
el("p").append(...T`
el("p").append(T`
Generally, you should either:
`),
el("ol").append(
@ -337,7 +351,7 @@ export function page({ pkg, info }){
),
el("div", { className: "note" }).append(
el("p").append(...T`
el("p").append(T`
This limitation isn't specific to dd<el> but is related to how DocumentFragment works in the DOM.
Once a fragment is appended to the DOM, its child nodes are moved from the fragment to the target element,
leaving the original fragment empty.
@ -345,11 +359,11 @@ export function page({ pkg, info }){
),
el(h3, t`Performance Debugging`),
el("p").append(...T`
el("p").append(T`
To identify performance bottlenecks in your dd<el> applications:
`),
el("ol").append(
el("li").append(...T`Use ${el("a", references.mdn_perf).append("browser performance tools")} to profile
el("li").append(T`Use ${el("a", references.mdn_perf).append("browser performance tools")} to profile
rendering times`),
el("li", t`Check for excessive signal updates using S.on() listeners with console.log`),
el("li", t`Verify memo usage by inspecting cache hit rates`),
@ -357,26 +371,26 @@ export function page({ pkg, info }){
),
el("div", { className: "note" }).append(
el("p").append(...T`
el("p").append(T`
For more details on debugging, see the ${el("a", { href: "p07-debugging.html", textContent: "Debugging" })} page.
`)
),
el(h3, t`Best Practices for Optimized Rendering`),
el("ol").append(
el("li").append(...T`
el("li").append(T`
${el("strong", "Use memo for list items:")} Memoize items in lists, especially when they contain complex components.
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Clean up with AbortSignals:")} Connect memo caches to component lifecycles using AbortSignals.
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Profile before optimizing:")} Identify actual bottlenecks before adding optimization.
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Use derived signals:")} Compute derived values efficiently with signal computations.
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Avoid memoizing fragments:")} Memoize individual elements or use container elements
instead of DocumentFragments.
`)

View File

@ -45,7 +45,7 @@ const references= {
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(...T`
el("p").append(T`
${el("a", references.todomvc).append("TodoMVC")} is a project that helps developers compare different
frameworks by implementing the same todo application. This implementation showcases how dd<el>
can be used to build a complete, real-world application with all the expected features of a modern
@ -63,7 +63,7 @@ export function page({ pkg, info }){
el("li", t`Component scopes for proper encapsulation`)
)
),
el("p").append(...T`
el("p").append(T`
Below is a fully working TodoMVC implementation. You can interact with it directly in this
documentation page. The example demonstrates how dd<el> handles common app development
challenges in a clean, maintainable way.
@ -72,7 +72,7 @@ export function page({ pkg, info }){
el(example, { src: fileURL("./components/examples/reallife/todomvc.js"), variant: "big", page_id }),
el(h3, t`Application Architecture Overview`),
el("p").append(...T`
el("p").append(T`
The TodoMVC implementation is structured around several key components:
`),
el("div", { className: "function-table" }).append(
@ -96,29 +96,31 @@ export function page({ pkg, info }){
),
el(h3, t`Reactive State Management with Signals`),
el("p").append(...T`
el("p").append(T`
The application uses three primary signals to manage state:
`),
el(code, { content: `
// Signal for current route (all/active/completed)
const pageS = routerSignal(S);
const { signal } = scope;
const pageS = routerSignal(S, signal);
// Signal for the todos collection with custom actions
const todosS = todosSignal();
// Derived signal that filters todos based on current route
const filteredTodosS = S(()=> {
const todosFilteredS = S(()=> {
const todos = todosS.get();
const filter = pageS.get();
if (filter === "all") return todos;
return todos.filter(todo => {
if (filter === "active") return !todo.completed;
if (filter === "completed") return todo.completed;
return true; // "all"
});
});
const todosRemainingS = S(()=> todosS.get().filter(todo => !todo.completed).length);
`, page_id }),
el("p").append(...T`
el("p").append(T`
The ${el("code", "todosSignal")} function creates a custom signal with actions for manipulating the todos:
`),
el(code, { content: `
@ -177,6 +179,13 @@ export function page({ pkg, info }){
clearCompleted() {
this.value = this.value.filter(todo => !todo.completed);
},
/**
* Mark all todos as completed or active
* @param {boolean} state - Whether to mark todos as completed or active
*/
completeAll(state = true) {
this.value.forEach(todo => todo.completed = state);
},
/**
* Handle cleanup when signal is cleared
*/
@ -193,6 +202,7 @@ export function page({ pkg, info }){
localStorage.setItem(store_key, JSON.stringify(value));
} catch (e) {
console.error("Failed to save todos to localStorage", e);
// Optionally, provide user feedback
}
});
return out;
@ -200,7 +210,7 @@ export function page({ pkg, info }){
`, page_id }),
el("div", { className: "note" }).append(
el("p").append(...T`
el("p").append(T`
Using ${el("a", references.mdn_storage).append("localStorage")} allows the application to persist todos
even when the page is refreshed. The ${el("code", "S.on")} listener ensures todos are saved
after every state change, providing automatic persistence without explicit calls.
@ -208,37 +218,61 @@ export function page({ pkg, info }){
),
el(h3, t`Integration of Signals and Reactive UI`),
el("p").append(...T`
el("p").append(T`
The implementation demonstrates a clean integration between signal state and reactive UI:
`),
el("h4", t`1. Derived Signals for Filtering`),
el(code, { content: `
/** Derived signal that filters todos based on current route */
const filteredTodosS = S(()=> {
const todosFilteredS = S(()=> {
const todos = todosS.get();
const filter = pageS.get();
if (filter === "all") return todos;
return todos.filter(todo => {
if (filter === "active") return !todo.completed;
if (filter === "completed") return todo.completed;
return true; // "all"
});
});
// Using the derived signal in the UI
el("ul", { className: "todo-list" }).append(
S.el(filteredTodosS, filteredTodos => filteredTodos.map(todo =>
S.el(todosFilteredS, filteredTodos => filteredTodos.map(todo =>
memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit)))
)
)
`, page_id }),
el("p").append(...T`
el("p").append(T`
The derived signal automatically recalculates whenever either the todos list or the current filter changes,
ensuring the UI always shows the correct filtered todos.
`),
el("h4", t`2. Local Component State`),
el("h4", t`2. Toggle All Functionality`),
el(code, { content: `
/** @type {ddeElementAddon<HTMLInputElement>} */
const onToggleAll = on("change", event => {
const checked = /** @type {HTMLInputElement} */ (event.target).checked;
S.action(todosS, "completeAll", checked);
});
// Using the toggle-all functionality in the UI
el("input", {
id: "toggle-all",
className: "toggle-all",
type: "checkbox"
}, onToggleAll),
el("label", { htmlFor: "toggle-all", title: "Mark all as complete" }),
`, page_id }),
el("p").append(T`
The "toggle all" checkbox allows users to mark all todos as completed or active. When the checkbox
is toggled, it calls the ${el("code", "completeAll")} action on the todos signal, passing the current
checked state. This is a good example of how signals and actions can be used to manage application
state in a clean, declarative way.
`),
el("h4", t`3. Local Component State`),
el(code, { content: `
function TodoItem({ id, title, completed }) {
const { host }= scope;
@ -268,12 +302,12 @@ export function page({ pkg, info }){
}
`, page_id }),
el("p").append(...T`
el("p").append(T`
The TodoItem component maintains its own local UI state with signals, providing immediate
UI feedback while still communicating changes to the parent via events.
`),
el("h4", t`3. Reactive Properties`),
el("h4", t`4. Reactive Properties`),
el(code, { content: `
// Dynamic class attributes
el("a", {
@ -289,27 +323,27 @@ export function page({ pkg, info }){
`, page_id }),
el("div", { className: "tip" }).append(
el("p").append(...T`
el("p").append(T`
Binding signals directly to element properties creates a reactive UI that automatically updates
when state changes, without the need for explicit DOM manipulation or virtual DOM diffing.
`)
),
el(h3, t`Performance Optimization with Memoization`),
el("p").append(...T`
el("p").append(T`
The implementation uses ${el("code", "memo")} to optimize performance in several key areas:
`),
el("h4", t`Memoizing Todo Items`),
el(code, { content: `
el("ul", { className: "todo-list" }).append(
S.el(filteredTodosS, filteredTodos => filteredTodos.map(todo =>
S.el(todosFilteredS, filteredTodos => filteredTodos.map(todo =>
memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit)))
)
)
`, page_id }),
el("p").append(...T`
el("p").append(T`
This approach ensures that:
`),
el("ul").append(
@ -329,14 +363,14 @@ export function page({ pkg, info }){
))
`, page_id }),
el("p").append(...T`
el("p").append(T`
By memoizing based on the todos length, the entire footer component is only re-rendered
when todos are added or removed, not when their properties change. This improves performance
by avoiding unnecessary DOM operations.
`),
el("div", { className: "tip" }).append(
el("p").append(...T`
el("p").append(T`
Memoization is especially important for UI elements that are expensive to render or that contain
many child elements. The ${el("code", "memo")} function allows precise control over when components
should re-render, avoiding the overhead of virtual DOM diffing algorithms.
@ -344,13 +378,13 @@ export function page({ pkg, info }){
),
el(h3, t`Component-Based Architecture with Events`),
el("p").append(...T`
el("p").append(T`
The TodoMVC implementation demonstrates a clean component architecture with custom events
for communication between components:
`),
el("h4", t`1. Main Component Event Handling`),
el("p").append(...T`
el("p").append(T`
The main Todos component sets up event listeners to handle actions from child components:
`),
el(code, { content: `
@ -360,7 +394,7 @@ export function page({ pkg, info }){
`, page_id }),
el("h4", t`2. The TodoItem Component with Scopes and Local State`),
el("p").append(...T`
el("p").append(T`
Each todo item is rendered by the TodoItem component that uses scopes, local signals, and custom events:
`),
el(code, { content: `
@ -403,7 +437,7 @@ export function page({ pkg, info }){
`, page_id }),
el("div", { className: "tip" }).append(
el("p").append(...T`
el("p").append(T`
Using ${el("code", "scope")} and ${el("a", references.mdn_events).append("custom events")}
creates a clean separation of concerns. Each TodoItem component dispatches events up to the parent
without directly manipulating the application state, following a unidirectional data flow pattern.
@ -411,7 +445,7 @@ export function page({ pkg, info }){
),
el(h3, t`Improved DOM Updates with classList`),
el("p").append(...T`
el("p").append(T`
The implementation uses the reactive ${el("code", "classList")} property for efficient class updates:
`),
el(code, { content: `
@ -423,7 +457,7 @@ export function page({ pkg, info }){
);
`, page_id }),
el("p").append(...T`
el("p").append(T`
Benefits of using ${el("code", "classList")}:
`),
el("ul").append(
@ -434,7 +468,7 @@ export function page({ pkg, info }){
),
el(h3, t`Improved Focus Management`),
el("p").append(...T`
el("p").append(T`
The implementation uses a dedicated function for managing focus in edit inputs:
`),
el(code, { content: `
@ -462,7 +496,7 @@ export function page({ pkg, info }){
}, onBlurEdit, onKeyDown, addFocus)
`, page_id }),
el("p").append(...T`
el("p").append(T`
This approach offers several advantages:
`),
el("ul").append(
@ -473,7 +507,7 @@ export function page({ pkg, info }){
),
el("div", { className: "note" }).append(
el("p").append(...T`
el("p").append(T`
Using ${el("a", references.mdn_raf).append("requestAnimationFrame")} ensures that the focus operation
happens after the browser has finished rendering the DOM changes, which is more reliable than
using setTimeout.
@ -481,7 +515,7 @@ export function page({ pkg, info }){
),
el(h3, t`Efficient Conditional Rendering`),
el("p").append(...T`
el("p").append(T`
The implementation uses signals for efficient conditional rendering:
`),
@ -512,15 +546,17 @@ export function page({ pkg, info }){
el("h4", t`Conditional Clear Completed Button`),
el(code, { content: `
S.el(S(() => todosS.get().some(todo => todo.completed)),
hasTodosCompleted=> hasTodosCompleted
? el("button", { textContent: "Clear completed", className: "clear-completed" }, onClearCompleted)
: el()
)
todos.length - todosRemainingS.get() === 0
? el()
: memo("delete", () =>
el("button",
{ textContent: "Clear completed", className: "clear-completed" },
onClearCompleted)
)
`, page_id }),
el("div", { className: "note" }).append(
el("p").append(...T`
el("p").append(T`
Unlike frameworks that use a virtual DOM, dd<el> directly updates only the specific DOM elements
that need to change. This approach is often more efficient for small to medium-sized applications,
especially when combined with strategic memoization.
@ -528,7 +564,7 @@ export function page({ pkg, info }){
),
el(h3, t`Type Safety with JSDoc Comments`),
el("p").append(...T`
el("p").append(T`
The implementation uses comprehensive JSDoc comments to provide type safety without requiring TypeScript:
`),
el(code, { content: `
@ -566,7 +602,7 @@ export function page({ pkg, info }){
`, page_id }),
el("div", { className: "tip" }).append(
el("p").append(...T`
el("p").append(T`
Using JSDoc comments provides many of the benefits of TypeScript (autocomplete, type checking,
documentation) while maintaining pure JavaScript code. This approach works well with modern
IDEs that support JSDoc type inference.
@ -575,41 +611,41 @@ export function page({ pkg, info }){
el(h3, t`Best Practices Demonstrated`),
el("ol").append(
el("li").append(...T`
el("li").append(T`
${el("strong", "Component Composition:")} Breaking the UI into focused, reusable components
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Performance Optimization:")} Strategic memoization to minimize DOM operations
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Reactive State Management:")} Using signals with derived computations
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Event-Based Communication:")} Using custom events for component communication
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Local Component State:")} Maintaining UI state within components for better encapsulation
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Declarative Class Management:")} Using the classList property for cleaner class handling
`),
el("li").append(...T`
${el("strong", "Focus Management:")} Reliable input focus with requestAnimationFrame
el("li").append(T`
${el("strong", "Focus Management:")} Reliable input focus with setTimeout
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Persistent Storage:")} Automatically saving application state with signal listeners
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Type Safety:")} Using comprehensive JSDoc comments for type checking and documentation
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Composable Event Handlers:")} Attaching multiple event handlers to elements
`)
),
el("div", { className: "callout" }).append(
el("h4", t`Key Takeaways`),
el("p").append(...T`
el("p").append(T`
This TodoMVC implementation showcases the strengths of dd<el> for building real-world applications:
`),
el("ul").append(
@ -622,7 +658,7 @@ export function page({ pkg, info }){
)
),
el("p").append(...T`
el("p").append(T`
You can find the ${el("a", references.github_example).append("complete source code")} for this example on GitHub.
Feel free to use it as a reference for your own projects or as a starting point for more complex applications.
`),

View File

@ -17,7 +17,7 @@ export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("div", { className: "warning" }).append(
el("p").append(...T`
el("p").append(T`
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
@ -25,21 +25,21 @@ export function page({ pkg, info }){
dd<el>.
`)
),
el("p").append(...T`
el("p").append(T`
dd<el> isnt limited to browser environments. Thanks to its flexible architecture,
it can be used for server-side rendering (SSR) to generate static HTML files.
This is achieved through integration with for example ${el("a", { href: "https://github.com/tmpvar/jsdom",
textContent: "jsdom" })}, a JavaScript implementation of web standards for Node.js.
`),
el("p").append(...T`
el("p").append(T`
Additionally, you might consider using these alternative solutions:
`),
el("ul").append(
el("li").append(...T`
el("li").append(T`
${el("a", { href: "https://github.com/capricorn86/happy-dom", textContent: "happy-dom" })}
A JavaScript implementation of a web browser without its graphical user interface thats faster than jsdom
`),
el("li").append(...T`
el("li").append(T`
${el("a", { href: "https://github.com/WebReflection/linkedom", textContent: "linkedom" })}
A lightweight DOM implementation specifically designed for SSR with significantly better performance
than jsdom
@ -48,7 +48,7 @@ export function page({ pkg, info }){
el(code, { src: fileURL("./components/examples/ssr/intro.js"), page_id }),
el(h3, t`Why Server-Side Rendering?`),
el("p").append(...T`
el("p").append(T`
SSR offers several benefits:
`),
el("ul").append(
@ -60,7 +60,7 @@ export function page({ pkg, info }){
),
el(h3, t`How jsdom Integration Works`),
el("p").append(...T`
el("p").append(T`
The jsdom export in dd<el> provides the necessary tools to use the library in Node.js
by integrating with jsdom. Heres what it does:
`),
@ -74,55 +74,55 @@ export function page({ pkg, info }){
el(code, { src: fileURL("./components/examples/ssr/start.js"), page_id }),
el(h3, t`Basic SSR Example`),
el("p").append(...T`
el("p").append(T`
Heres a simple example of how to use dd<el> for server-side rendering in a Node.js script:
`),
el(code, { src: fileURL("./components/examples/ssr/basic-example.js"), page_id }),
el(h3, t`Building a Static Site Generator`),
el("p").append(...T`
el("p").append(T`
You can build a complete static site generator with dd<el>. In fact, this documentation site
is built using dd<el> for server-side rendering! Heres how the documentation build process works:
`),
el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js"), page_id }),
el(h3, t`Working with Async Content in SSR`),
el("p").append(...T`
el("p").append(T`
The jsdom export includes a queue system to handle asynchronous operations during rendering.
This is crucial for components that fetch data or perform other async tasks.
`),
el(code, { src: fileURL("./components/examples/ssr/async-data.js"), page_id }),
el(h3, t`Working with Dynamic Imports for SSR`),
el("p").append(...T`
el("p").append(T`
When structuring server-side rendering code, a crucial pattern to follow is using dynamic imports
for both the deka-dom-el/jsdom module and your page components.
`),
el("p").append(...T`
el("p").append(T`
Why is this important?
`),
el("ul").append(
el("li").append(...T`
el("li").append(T`
${el("strong", "Static imports are hoisted:")} JavaScript hoists import statements to the top of the file,
executing them before any other code
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Environment registration timing:")} The jsdom module auto-registers the DOM environment
when imported, which must happen ${el("em", "after")} youve created your JSDOM instance and
${el("em", "before")} you import your components using ${el("code", "import { el } from \"deka-dom-el\";")}.
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Correct initialization order:")} You need to control the exact sequence of:
create JSDOM register environment import components
`)
),
el("p").append(...T`
el("p").append(T`
Follow this pattern when creating server-side rendered pages:
`),
el(code, { src: fileURL("./components/examples/ssr/pages.js"), page_id }),
el(h3, t`SSR Considerations and Limitations`),
el("p").append(...T`
el("p").append(T`
When using dd<el> for SSR, keep these considerations in mind:
`),
el("ul").append(
@ -131,19 +131,19 @@ export function page({ pkg, info }){
el("li", t`Some DOM features may behave differently in jsdom compared to real browsers`),
el("li", t`For large sites, you may need to optimize memory usage by creating a new jsdom instance for each page`)
),
el("p").append(...T`
el("p").append(T`
For advanced SSR applications, consider implementing hydration on the client-side to restore
interactivity after the initial render.
`),
el(h3, t`Real Example: How This Documentation is Built`),
el("p").append(...T`
el("p").append(T`
This documentation site itself is built using dd<el>s SSR capabilities.
The build process collects all page components, renders them with jsdom, and outputs static HTML files.
`),
el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js"), page_id }),
el("p").append(...T`
el("p").append(T`
The resulting static files can be deployed to any static hosting service,
providing fast loading times and excellent SEO without the need for client-side JavaScript
to render the initial content.

View File

@ -19,7 +19,7 @@ export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("div", { className: "warning" }).append(
el("p").append(...T`
el("p").append(T`
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
@ -29,7 +29,7 @@ export function page({ pkg, info }){
),
el(h3, t`What Are Ireland Components?`),
el("p").append(...T`
el("p").append(T`
Ireland components are a special type of component that:
`),
el("ul").append(
@ -38,24 +38,24 @@ export function page({ pkg, info }){
),
el(h3, t`How Ireland Components Work`),
el("p").append(...T`
el("p").append(T`
The Ireland component system consists of several parts working together:
`),
el("ol").append(
el("li").append(...T`
el("li").append(T`
${el("strong", "Server-side rendering:")} Components are pre-rendered during the documentation build process
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Component registration:")} Source files are copied to the documentation output directory
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Client-side scripting:")} JavaScript code is generated to load and render components
`),
),
el(h3, t`Implementation Architecture`),
el("p").append(...T`
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")}.
@ -69,7 +69,7 @@ export function page({ pkg, info }){
})
`, page_id }),
el("p").append(...T`
el("p").append(T`
During the build process (${el("code", "bs/docs.js")}), the following happens:
`),
@ -81,7 +81,7 @@ export function page({ pkg, info }){
),
el(h3, t`Core Implementation Details`),
el("p").append(...T`
el("p").append(T`
Let's look at the key parts of the ireland component implementation:
`),
@ -253,7 +253,7 @@ export function page({ pkg, info }){
`, page_id }),
el(h3, t`Live Example`),
el("p").append(...T`
el("p").append(T`
Heres a live example of an Ireland component showing a standard counter.
The component is defined in ${el("code", "docs/components/examples/ireland-test/counter.js")} and
rendered with the Ireland component system:
@ -269,21 +269,21 @@ export function page({ pkg, info }){
page_id
}),
el("p").append(...T`
el("p").append(T`
When the page is loaded, the component is also loaded and rendered. The counter state is maintained
using signals, allowing for reactive updates as you click the buttons to increment and decrement the
value.
`),
el(h3, t`Practical Considerations and Limitations`),
el("p").append(...T`
el("p").append(T`
When implementing Ireland components in real documentation, there are several important
considerations to keep in mind:
`),
el("div", { className: "warning" }).append(
el("h4", t`Module Resolution and Bundling`),
el("p").append(...T`
el("p").append(T`
The examples shown here use bare module specifiers like ${el("code",
`import { el } from "deka-dom-el"`)} which arent supported in all browsers without importmaps.
In a production implementation, you would need to: `),
@ -292,7 +292,7 @@ export function page({ pkg, info }){
el("li", t`Bundle component dependencies to avoid multiple requests`),
el("li", t`Ensure all module dependencies are properly resolved and copied to the output directory`)
),
el("p").append(...T`
el("p").append(T`
In this documentation, we replace the paths with ${el("code", "./esm-with-signals.js")} and provide
a bundled version of the library, but more complex components might require a dedicated bundling step.
`)
@ -300,7 +300,7 @@ export function page({ pkg, info }){
el("div", { className: "note" }).append(
el("h4", t`Component Dependencies`),
el("p").append(...T`
el("p").append(T`
Real-world components typically depend on multiple modules and assets. The Ireland system would need
to be extended to:
`),
@ -312,7 +312,7 @@ export function page({ pkg, info }){
),
el(h3, t`Advanced Usage`),
el("p").append(...T`
el("p").append(T`
The Ireland system can be extended in several ways to address these limitations:
`),
@ -325,7 +325,7 @@ export function page({ pkg, info }){
el("li", t`Implement state persistence between runs`)
),
el("p").append(...T`
el("p").append(T`
This documentation site itself is built using the techniques described here,
showcasing how dd<el> can be used to create both the documentation and
the interactive examples within it. The implementation here is simplified for clarity,

View File

@ -2,7 +2,7 @@ import { T, t } from "./utils/index.js";
export const info= {
title: t`Appendix & Summary`,
fullTitle: t`dd<el> Comprehensive Reference`,
description: t`A final overview, case studies, key concepts, and best practices for working with deka-dom-el.`,
description: t`A final overview, case studies, key concepts, and best practices for working with deka-dom-el.`,
};
import { el } from "deka-dom-el";
@ -25,6 +25,16 @@ const references= {
performance: {
title: t`Performance Optimization Guide`,
href: "p09-optimization.html",
},
/** Examples gallery */
examples: {
title: t`Examples Gallery`,
href: "p15-examples.html",
},
/** Converter */
converter: {
title: t`HTML to dd<el> Converter`,
href: "p14-converter.html",
}
};
@ -32,56 +42,64 @@ const references= {
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(...T`
This reference guide provides a comprehensive summary of dd<el>'s key concepts, best practices,
case studies, and advanced techniques. Use it as a quick reference when working with the library
el("p").append(T`
This reference guide provides a comprehensive summary of dd<el>s key concepts, best practices,
case studies, and advanced techniques. Use it as a quick reference when working with the library
or to deepen your understanding of its design principles and patterns.
`),
el(h3, t`Core Principles of dd<el>`),
el("p").append(...T`
el("p").append(T`
At its foundation, dd<el> is built on several core principles that shape its design and usage:
`),
el("div", { className: "callout" }).append(
el("h4", t`Guiding Principles`),
el("ul").append(
el("li").append(...T`
el("li").append(T`
${el("strong", "DOM-First Approach:")} Working directly with the real DOM instead of virtual DOM
abstractions
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Declarative Syntax:")} Creating UIs by describing what they should look like, not
how to create them
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Minimal Overhead:")} Staying close to standard Web APIs without unnecessary
abstractions
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Progressive Enhancement:")} Starting simple and adding complexity only when needed
`),
el("li").append(...T`
${el("strong", "Functional Composition:")} Building UIs through function composition rather than
inheritance
el("li").append(T`
${el("strong", "Flexibility:")} Using what you need, whether thats plain DOM elements, event
handling, or signals for reactivity
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Functional Composition:")} Building UIs through function composition
`),
el("li").append(T`
${el("strong", "Clear Patterns:")} Promoting maintainable code organization with the 3PS pattern
`),
el("li").append(...T`
${el("strong", "Targeted Reactivity:")} Using signals for efficient, fine-grained updates
el("li").append(T`
${el("strong", "Targeted Reactivity:")} Using signals for efficient, fine-grained updates only when
needed
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Unix Philosophy:")} Doing one thing well and allowing composability with other tools
`)
)
),
el(h3, t`Case Studies & Real-World Applications`),
el("p").append(T`
Explore our ${el("a", references.examples).append("Examples Gallery")} to see how dd<el> can be used to build
various real-world applications, from simple components to complex interactive UIs.
`),
el("h4", t`TodoMVC Implementation`),
el("p").append(...T`
The ${el("a", references.todomvc).append("TodoMVC implementation")} showcases how dd<el> handles a complete,
real-world application with all standard features of a modern web app:
el("p").append(T`
The ${el("a", references.todomvc).append("TodoMVC implementation")} showcases how dd<el> handles a complete,
real-world application with all standard features of a modern web app:
`),
el("ul").append(
el("li", t`Persistent storage with localStorage`),
@ -92,41 +110,112 @@ export function page({ pkg, info }){
el("li", t`Custom event system for component communication`),
el("li", t`Proper focus management and accessibility`)
),
el("p").append(...T`
el("p").append(T`
Key takeaways from the TodoMVC example:
`),
el("ul").append(
el("li").append(...T`
el("li").append(T`
Signal factories like ${el("code", "routerSignal")} and ${el("code", "todosSignal")}
encapsulate related functionality
`),
el("li").append(...T`
el("li").append(T`
Custom events provide clean communication between components
`),
el("li").append(...T`
el("li").append(T`
Targeted memoization improves rendering performance dramatically
`),
el("li").append(...T`
el("li").append(T`
Derived signals simplify complex UI logic like filtering
`)
),
el("h4", t`Using Signals Appropriately`),
el("p").append(T`
While signals provide powerful reactivity for complex UI interactions, theyre not always necessary.
`),
el("div", { className: "tabs" }).append(
el("div", { className: "tab", dataTab: "events" }).append(
el("h4", t`We can process form events without signals`),
el("p", t`This can be used when the form data doesnt need to be reactive and we just waiting for
results.`),
el(code, { page_id, content: `
const onFormSubmit = on("submit", e => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
// this can be sent to a server
// or processed locally
// e.g.: console.log(Object.fromEntries(formData))
});
// …
return el("form", null, onFormSubmit).append(
// …
);
` })
),
el("div", { className: "tab", dataTab: "variables" }).append(
el("h4", t`We can use variables without signals`),
el("p", t`We use this when we dontt need to reflect changes in the elsewhere (UI).`),
el(code, { page_id, content: `
let canSubmit = false;
const onFormSubmit = on("submit", e => {
e.preventDefault();
if(!canSubmit) return; // some message
// …
});
const onAllowSubmit = on("click", e => {
canSubmit = true;
});
`}),
),
el("div", { className: "tab", dataTab: "state" }).append(
el("h4", t`Using signals`),
el("p", t`We use this when we need to reflect changes for example in the UI (e.g. enable/disable
buttons).`),
el(code, { page_id, content: `
const canSubmit = S(false);
const onFormSubmit = on("submit", e => {
e.preventDefault();
// …
});
const onAllowSubmit = on("click", e => {
canSubmit.set(true);
});
return el("form", null, onFormSubmit).append(
// ...
el("button", { textContent: "Allow Submit", type: "button" }, onAllowSubmit),
el("button", { disabled: S(()=> !canSubmit), textContent: "Submit" })
);
`}),
),
el("p").append(T`
A good approach is to started with variables/constants and when necessary, convert them to signals.
`),
),
el("h4", t`Migrating from Traditional Approaches`),
el("p").append(...T`
el("p").append(T`
When migrating from traditional DOM manipulation or other frameworks to dd<el>:
`),
el("ol").append(
el("li").append(...T`
${el("strong", "Start with state:")}: Convert global variables or ad-hoc state to signals
el("li").append(T`
${el("strong", "Start with state")}: Convert global variables or ad-hoc state to signals
`),
el("li").append(...T`
${el("strong", "Replace query selectors:")}: Replace getElementById/querySelector with direct references to elements
el("li").append(T`
${el("strong", "Replace query selectors")}: Replace getElementById/querySelector with direct references
to elements
`),
el("li").append(...T`
${el("strong", "Convert imperative updates:")}: Replace manual DOM updates with declarative signal bindings
el("li").append(T`
${el("strong", "Convert imperative updates")}: Replace manual DOM updates with declarative signal
bindings
`),
el("li").append(...T`
${el("strong", "Refactor into components:")}: Organize related UI elements into component functions
el("li").append(T`
${el("strong", "Refactor into components")}: Organize related UI elements into component functions
`)
),
el(code, { content: `
@ -157,7 +246,7 @@ export function page({ pkg, info }){
el("dd", t`Core function for creating DOM elements with declarative properties`),
el("dt", t`el().append(...children)`),
el("dd", t`Add child elements to a parent element`),
el("dd", t`Add child elements to a parent element`),
el("dt", t`memo(key, () => element)`),
el("dd", t`Cache and reuse DOM elements for performance optimization`),
@ -171,22 +260,22 @@ export function page({ pkg, info }){
el("h4", t`Signals & Reactivity`),
el("dl").append(
el("dt", t`S(initialValue)`),
el("dd", t`Create a signal with an initial value`),
el("dd", t`Create a signal with an initial value`),
el("dt", t`S(() => computation)`),
el("dd", t`Create a derived signal that updates when dependencies change`),
el("dd", t`Create a derived signal that updates when dependencies change`),
el("dt", t`S.el(signal, data => element)`),
el("dd", t`Create reactive elements that update when a signal changes`),
el("dd", t`Create reactive elements that update when a signal changes`),
el("dt", t`S.action(signal, "method", ...args)`),
el("dd", t`Call custom methods defined on a signal`),
el("dd", t`Call custom methods defined on a signal`),
el("dt", t`signal.get()`),
el("dd", t`Get the current value of a signal`),
el("dd", t`Get the current value of a signal`),
el("dt", t`signal.set(newValue)`),
el("dd", t`Update a signal's value and trigger reactive updates`)
el("dd", t`Update a signals value and trigger reactive updates`)
)
),
@ -200,7 +289,7 @@ export function page({ pkg, info }){
el("dd", t`Provides access to component context, signal, host element`),
el("dt", t`dispatchEvent(type, element)`),
el("dd", t`Creates a function for dispatching custom events`),
el("dd", t`Creates a function for dispatching custom events`),
el("dt", t`Signal Factories`),
el("dd", t`Functions that create and configure signals with domain-specific behavior`)
@ -211,20 +300,46 @@ export function page({ pkg, info }){
el("div").append(
el("h4", t`Code Organization`),
el("ul").append(
el("li").append(...T`
${el("strong", "Follow the 3PS pattern:")}: Separate state creation, binding to elements, and state updates
el("li").append(T`
${el("strong", "Follow the 3PS pattern")}: Separate state creation, binding to elements, and state updates
`),
el("li").append(...T`
${el("strong", "Use component functions:")}: Create reusable UI components as functions
el("li").append(T`
${el("strong", "Use component functions")}: Create reusable UI components as functions
`),
el("li").append(...T`
${el("strong", "Create signal factories:")}: Extract reusable signal patterns into factory functions
el("li").append(T`
${el("strong", "Create signal factories")}: Extract reusable signal patterns into factory functions
`),
el("li").append(...T`
${el("strong", "Leverage scopes:")}: Use scope for component context and clean resource management
el("li").append(T`
${el("strong", "Leverage scopes")}: Use scope for component context and clean resource management
`),
el("li").append(...T`
${el("strong", "Event delegation:")}: Prefer component-level event handlers over many individual handlers
el("li").append(T`
${el("strong", "Event delegation")}: Prefer component-level event handlers over many individual handlers
`)
)
),
el("div").append(
el("h4", t`When to Use Signals vs. Plain DOM`),
el("ul").append(
el("li").append(T`
${el("strong", "Use signals for")}: Data that changes frequently, multiple elements that need to
stay in sync, computed values dependent on other state
`),
el("li").append(T`
${el("strong", "Use plain DOM for")}: Static content, one-time DOM operations, simple toggling of
elements, single-element updates
`),
el("li").append(T`
${el("strong", "Mixed approach")}: Start with plain DOM and events, then add signals only where
needed for reactivity
`),
el("li").append(T`
${el("strong", "Consider derived signals")}: For complex transformations of data rather than manual
updates
`),
el("li").append(T`
${el("strong", "Use event delegation")}: For handling multiple similar interactions without
individual signal bindings
`)
)
),
@ -232,23 +347,23 @@ export function page({ pkg, info }){
el("div").append(
el("h4", t`Performance Optimization`),
el("ul").append(
el("li").append(...T`
${el("strong", "Memoize list items:")}: Use ${el("code", "memo")} for items in frequently-updated lists
el("li").append(T`
${el("strong", "Memoize list items")}: Use ${el("code", "memo")} for items in frequently-updated lists
`),
el("li").append(...T`
${el("strong", "Avoid unnecessary signal updates:")}: Only update signals when values actually change
el("li").append(T`
${el("strong", "Avoid unnecessary signal updates")}: Only update signals when values actually change
`),
el("li").append(...T`
${el("strong", "Use AbortSignals:")}: Clean up resources when components are removed
el("li").append(T`
${el("strong", "Use AbortSignals")}: Clean up resources when components are removed
`),
el("li").append(...T`
${el("strong", "Prefer derived signals:")}: Use computed values instead of manual updates
el("li").append(T`
${el("strong", "Prefer derived signals")}: Use computed values instead of manual updates
`),
el("li").append(...T`
${el("strong", "Avoid memoizing fragments:")}: Never memoize DocumentFragments, only individual elements
el("li").append(T`
${el("strong", "Avoid memoizing fragments")}: Never memoize DocumentFragments, only individual elements
`)
),
el("p").append(...T`
el("p").append(T`
See the ${el("a", references.performance).append("Performance Optimization Guide")} for detailed strategies.
`)
),
@ -263,7 +378,7 @@ export function page({ pkg, info }){
el("dd", t`Use scope.signal or AbortSignals to handle resource cleanup when elements are removed`),
el("dt", t`Circular Signal Dependencies`),
el("dd", t`Avoid signals that depend on each other in a circular way, which can cause infinite update loops`),
el("dd", t`Avoid signals that depend on each other in a circular way, which can cause infinite update loops`),
el("dt", t`Memoizing with Unstable Keys`),
el("dd", t`Always use stable, unique identifiers as memo keys, not array indices or objects`),
@ -324,59 +439,76 @@ export function page({ pkg, info }){
),
el(h3, t`Looking Ahead: Future Directions`),
el("p").append(...T`
el("p").append(T`
The dd<el> library continues to evolve, with several areas of focus for future development:
`),
el("ul").append(
el("li").append(...T`
el("li").append(T`
${el("strong", "Future Compatibility:")} Alignment with the TC39 Signals proposal for native browser support
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "SSR Improvements:")} Enhanced server-side rendering capabilities
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Ecosystem Growth:")} More utilities, patterns, and integrations with other libraries
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "Documentation Expansion:")} Additional examples, tutorials, and best practices
`),
el("li").append(...T`
el("li").append(T`
${el("strong", "TypeScript Enhancements:")} Improved type definitions and inference
`)
),
el(h3, t`Contribution and Community`),
el("p").append(...T`
dd<el> is an open-source project that welcomes contributions from the community:
el("p").append(T`
dd<el> is an open-source project that welcomes contributions from the community:
`),
el("ul").append(
el("li").append(...T`
el("li").append(T`
${el("a", references.github).append("GitHub Repository")}: Star, fork, and contribute to the project
`),
el("li").append(...T`
el("li").append(T`
Bug reports and feature requests: Open issues on GitHub
`),
el("li").append(...T`
el("li").append(T`
Documentation improvements: Help expand and clarify these guides
`),
el("li").append(...T`
el("li").append(T`
Examples and case studies: Share your implementations and solutions
`)
),
el("div", { className: "callout" }).append(
el("h4", t`Final Thoughts`),
el("p").append(...T`
dd<el> provides a lightweight yet powerful approach to building modern web interfaces
el("p").append(T`
dd<el> provides a lightweight yet powerful approach to building modern web interfaces
with minimal overhead and maximal flexibility. By embracing standard web technologies
rather than abstracting them away, it offers a development experience that scales
rather than abstracting them away, it offers a development experience that scales
from simple interactive elements to complex applications while remaining close
to what makes the web platform powerful.
`),
el("p").append(...T`
Whether you're building a small interactive component or a full-featured application,
dd<el>'s combination of declarative syntax, targeted reactivity, and pragmatic design
provides the tools you need without the complexity you don't.
el("p").append(T`
Whether youre building a small interactive component or a full-featured application,
dd<el>s combination of declarative syntax, targeted reactivity, and pragmatic design
provides the tools you need without the complexity you dont.
`)
),
el(h3, t`Tools and Resources`),
el("p").append(T`
To help you get started with dd<el>, we provide several tools and resources:
`),
el("ul").append(
el("li").append(T`
${el("a", references.converter).append("HTML to dd<el> Converter")}: Easily convert existing HTML markup
to dd<el> JavaScript code
`),
el("li").append(T`
${el("a", references.examples).append("Examples Gallery")}: Browse real-world examples and code snippets
`),
el("li").append(T`
${el("a", references.github).append("Documentation")}: Comprehensive guides and API reference
`)
),
);

View File

@ -0,0 +1,53 @@
import "./components/converter.html.js";
import { T, t } from "./utils/index.js";
export const info= {
title: t`Convert to dd<el>`,
fullTitle: t`HTML to dd<el> Converter`,
description: t`Convert your HTML markup to dd<el> JavaScript code with our interactive tool`,
};
import { el } from "deka-dom-el";
import { simplePage } from "./layout/simplePage.html.js";
import { h3 } from "./components/pageUtils.html.js";
import { converter } from "./components/converter.html.js";
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(T`
Transitioning from HTML to dd<el> is simple with our interactive converter. This tool helps you quickly
transform existing HTML markup into dd<el> JavaScript code, making it easier to adopt dd<el> in your projects.
`),
el("div", { className: "warning" }).append(
el("p").append(T`
While the converter handles most basic HTML patterns, complex attributes or specialized elements might
need manual adjustments. Always review the generated code before using it in production.
`)
),
// The actual converter component
el(converter, { page_id }),
el(h3, t`Next Steps`),
el("p").append(T`
After converting your HTML to dd<el>, you might want to:
`),
el("ul").append(
el("li").append(T`
Add signal bindings for dynamic content (see ${el("a", { href: "p04-signals.html",
textContent: "Signals section" })})
`),
el("li").append(T`
Add event handlers for interactivity (see ${el("a", { href: "p03-events.html",
textContent: "Events section" })})
`),
el("li").append(T`
Organize your components with components (see ${el("a", { href:
"p02-elements.html#h-using-components-to-build-ui-fragments", textContent: "Components section" })}
and ${el("a", { href: "p05-scopes.html", textContent: "Scopes section" })})
`),
)
);
}

63
docs/p15-examples.html.js Normal file
View File

@ -0,0 +1,63 @@
import { T, t } from "./utils/index.js";
export const info= {
title: t`Examples Gallery`,
fullTitle: t`DDE Examples & Code Snippets`,
description: t`A comprehensive collection of examples and code snippets for working with Deka DOM Elements.`,
};
import { el } from "deka-dom-el";
import { simplePage } from "./layout/simplePage.html.js";
import { h3 } from "./components/pageUtils.html.js";
import { example } from "./components/example.html.js";
/** @param {string} url */
const fileURL= url=> new URL(url, import.meta.url);
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(T`
Real-world application examples showcasing how to build complete, production-ready interfaces with dd<el>:
`),
el(h3, t`Data Dashboard`),
el("p").append(T`
Data visualization dashboard with charts, filters, and responsive layout. Integration with a
third-party charting library, data fetching and state management, responsive layout design, and multiple
interactive components working together.
`),
el(example, { src: fileURL("./components/examples/case-studies/data-dashboard.js"), variant: "big", page_id }),
el(h3, t`Interactive Form`),
el("p").append(T`
Complete form with real-time validation, conditional rendering, and responsive design. Form handling with
real-time validation, reactive UI updates, complex form state management, and clean separation of concerns.
`),
el(example, { src: fileURL("./components/examples/case-studies/interactive-form.js"), variant: "big", page_id }),
el(h3, t`Interactive Image Gallery`),
el("p").append(T`
Responsive image gallery with lightbox, keyboard navigation, and filtering. Dynamic loading of content,
lightbox functionality, animation handling, and keyboard and gesture navigation support.
`),
el(example, { src: fileURL("./components/examples/case-studies/image-gallery.js"), variant: "big", page_id }),
el(h3, t`Task Manager`),
el("p").append(T`
Kanban-style task management app with drag-and-drop and localStorage persistence. Complex state management
with signals, drag and drop functionality, local storage persistence, and responsive design for different
devices.
`),
el(example, { src: fileURL("./components/examples/case-studies/task-manager.js"), variant: "big", page_id }),
el(h3, t`TodoMVC`),
el("p").append(T`
Complete TodoMVC implementation with local storage and routing. TodoMVC implementation showing routing,
local storage persistence, filtering, and component architecture patterns. For commented code, see the
dedicated page ${el("a", { href: "./p10-todomvc.html" }).append(T`TodoMVC`)}.
`),
);
}

View File

@ -17,12 +17,14 @@
*
* @param {TemplateStringsArray} strings
* @param {...(string|Node)} values
* @returns {(string|Node)[]}
* @returns {DocumentFragment}
* */
export function T(strings, ...values){
const out= [];
tT(s=> out.push(s), strings, ...values);
return out;
const fragment= document.createDocumentFragment();
fragment.append(...out);
return fragment;
}
/**
* Similarly to {@link T}, but for only strings.

View File

@ -1,51 +0,0 @@
import { style, el, S, isSignal } from '../exports.js';
const className= style.host(thirdParty).css`
:host {
color: green;
}
`;
const store_adapter= {
read(){ return (new URL(location)).searchParams; },
write(data){ console.log(data); history.replaceState("", "", "?"+(new URLSearchParams(data)).toString()); }
};
export function thirdParty(){
const store= S({
value: S("initial")
}, {
set(key, value){
const p= this.value[key] || S();
p.set(value);
this.value[key]= p;
}
});
// Array.from((new URL(location)).searchParams.entries())
// .forEach(([ key, value ])=> S.action(store, "set", key, value));
// S.on(store, data=> history.replaceState("", "",
// "?"+(new URLSearchParams(JSON.parse(JSON.stringify(data)))).toString()));
useStore(store_adapter, {
onread(data){
Array.from(data.entries())
.forEach(([ key, value ])=> S.action(store, "set", key, value));
return store;
}
})();
return el("input", {
className,
value: store.get().value.get(),
type: "text",
onchange: ev=> S.action(store, "set", "value", ev.target.value)
});
}
function useStore(adapter_in, { onread, onbeforewrite }= {}){
const adapter= typeof adapter_in === "function" ? { read: adapter_in } : adapter_in;
if(!onread) onread= S;
if(!onbeforewrite) onbeforewrite= data=> JSON.parse(JSON.stringify(data));
return function useStoreInner(data_read){
const signal= onread(adapter.read(data_read)); //TODO OK as synchronous
if(adapter.write && isSignal(signal))
S.on(signal, data=> adapter.write(onbeforewrite(data)));
return signal;
};
}

View File

@ -1,42 +0,0 @@
import { style, el, elNS, on, S, scope } from '../exports.js';
const className= style.host(fullNameComponent).css`
:host form{
display: flex;
flex-flow: column nowrap;
}
`;
export function fullNameComponent(){
const labels= [ "Name", "Surname" ];
const name= labels.map(_=> S(""));
const full_name= S(()=>
name.map(l=> l.get()).filter(Boolean).join(" ") || "-");
scope.host(
on.connected(()=> console.log(fullNameComponent)),
on.disconnected(()=> console.log(fullNameComponent))
);
const count= S(0);
setInterval(()=> count.set(count.get()+1), 5000);
const style= { height: "80px", display: "block", fill: "currentColor" };
const elSVG= elNS("http://www.w3.org/2000/svg");
return el("div", { className }).append(
el("h2", "Simple form:"),
el("p", { textContent: S(()=> "Count: "+count.get()),
dataset: { count }, classList: { count: S(() => count.get()%2 === 0) } }),
el("form", { onsubmit: ev=> ev.preventDefault() }).append(
...name.map((v, i)=>
el("label", labels[i]).append(
el("input", { type: "text", name: labels[i], value: v.get() }, on("change", ev=> v.set(ev.target.value)))
))
),
el("p").append(
el("strong", "Full name"),
": ",
el("#text", full_name)
),
elSVG("svg", { viewBox: "0 0 240 80", style }).append(
//elSVG("style", { })
elSVG("text", { x: 20, y: 35, textContent: "Text" }),
)
);
}

View File

@ -1,93 +0,0 @@
import { style, el, dispatchEvent, on, S, scope } from '../exports.js';
const className= style.host(todosComponent).css`
:host{
display: flex;
flex-flow: column nowrap;
}
:host input{
margin-inline-start: .5em;
}
:host button{
margin-inline-start: 1em;
}
:host output{
white-space: pre;
}
`;
/** @param {{ todos: string[] }} */
export function todosComponent({ todos= [ "Task A" ] }= {}){
let key= 0;
const todosO= S( /** @type {Map<number, ddeSignal<string>>} */ (new Map()), {
/** @param {string} v */
add(v){ this.value.set(key++, S(v)); },
/** @param {number} key */
remove(key){ S.clear(this.value.get(key)); this.value.delete(key); }
});
todos.forEach(text=> S.action(todosO, "add", text));
const name= "todoName";
const onsubmitAdd= on("submit", event=> {
const el_form= /** @type {HTMLFormElement} */ (event.target);
const el= /** @type {HTMLInputElement} */ (el_form.elements[name]);
event.preventDefault();
S.action(todosO, "add", el.value);
el.value= "";
});
const onremove= on("remove", /** @param {CustomEvent<number>} event */
event=> S.action(todosO, "remove", event.detail));
return el("div", { className }).append(
el("div").append(
el("h2", "Todos:"),
el("h3", "List of todos:"),
S.el(todosO, (ts, memo)=> !ts.size
? el("p", "No todos yet")
: el("ul").append(
...Array.from(ts).map(([ value, textContent ])=>
memo(value, ()=> el(todoComponent, { textContent, value, className }, onremove)))
),
),
el("p", "Click to the text to edit it.")
),
el("form", null, onsubmitAdd).append(
el("h3", "Add a todo:"),
el("label", "New todo: ").append(
el("input", { name, type: "text", required: true }),
),
el("button", "+"),
),
el("div").append(
el("h3", "Output (JSON):"),
el("output", S(()=> JSON.stringify(Array.from(todosO.get()), null, "\t")))
)
)
}
/**
* @param {{ textContent: ddeSignal<string>, value: ddeSignal<number> }} attrs
* @dispatchs {number} remove
* */
function todoComponent({ textContent, value }){
const { host }= scope;
const dispatchRemove= /** @type {(data: number) => void} */
(dispatchEvent("remove", null, host));
const onclick= on("click", event=> {
const el= /** @type {HTMLButtonElement} */ (event.target);
const value= Number(el.value);
event.preventDefault();
event.stopPropagation();
dispatchRemove(value);
});
const is_editable= S(false);
const onedited= on("change", ev=> {
const el= /** @type {HTMLInputElement} */ (ev.target);
textContent.set(el.value);
is_editable.set(false);
});
return el("li").append(
S.el(is_editable, is=> is
? el("input", { value: textContent.get(), type: "text" }, onedited)
: el("span", { textContent, onclick: ()=> is_editable.set(true) })
),
el("button", { type: "button", value, textContent: "-" }, onclick)
);
}

View File

@ -1,70 +0,0 @@
import { el, on, customElementRender, customElementWithDDE, scope, simulateSlots } from "../../index.js";
import { S } from "../../signals.js";
/**
* Compatible with `npx -y web-component-analyzer examples/components/webComponent.js`
* @element custom-test
* */
export class CustomHTMLTestElement extends HTMLElement{
static tagName= "custom-test";
static get observedAttributes(){
return [ "name", "pre-name" ];
}
connectedCallback(){
if(!this.hasAttribute("pre-name")) this.setAttribute("pre-name", "default");
customElementRender(this.attachShadow({ mode: "open" }), this.render, this.attributes)
}
attributes(element){
const observed= S.observedAttributes(element);
return Object.assign({ test: element.test }, observed);
}
render({ name, preName, test }){
console.log(scope.state);
scope.host(
on.connected(console.log),
on.attributeChanged(e=> console.log(e)),
on.disconnected(()=> console.log(CustomHTMLTestElement))
);
const text= text=> el().append(
el("#text", text),
" | "
);
return el("p").append(
text(test),
text(name),
text(preName),
el("button", { type: "button", textContent: "pre-name", onclick: ()=> preName.set("Ahoj") }),
" | ",
el("slot", { className: "test", name: "test" }),
);
}
test= "A";
get name(){ return this.getAttribute("name"); }
set name(value){ this.setAttribute("name", value); }
/** @attr pre-name */
get preName(){ return this.getAttribute("pre-name"); }
set preName(value){ this.setAttribute("pre-name", value); }
}
customElementWithDDE(CustomHTMLTestElement);
customElements.define(CustomHTMLTestElement.tagName, CustomHTMLTestElement);
export class CustomSlottingHTMLElement extends HTMLElement{
static tagName= "custom-slotting";
render(){
return simulateSlots(this, el().append(
el("p").append(
"Ahoj ", el("slot", { name: "name", className: "name", textContent: "World" })
),
el("p").append(
"BTW ", el("slot")
)
));
}
connectedCallback(){
customElementRender(this, this.render);
}
}
customElementWithDDE(CustomSlottingHTMLElement);
customElements.define(CustomSlottingHTMLElement.tagName, CustomSlottingHTMLElement);

View File

@ -1,30 +0,0 @@
import * as dde_dom from "../index.js";
export * from "../index.js";
import * as dde_s from "../signals.js";
export * from "../signals.js";
Object.assign(globalThis, dde_dom, dde_s);
//import * as dde_dom from "../dist/esm-with-signals.js";
//export * from "../dist/esm-with-signals.js";
//Object.assign(globalThis, dde_dom);
export const style= createStyle();
function createStyle(){
const element= dde_dom.el("style");
const store= new WeakSet();
let host;
return {
element,
host(k, h= k.name){
if(store.has(k)) return { css: ()=> {} };
store.add(k);
host= h;
return this;
},
css(...args){
const textContent= String.raw(...args).replaceAll(":host", "."+host);
const className= host;
element.appendChild(el("#text", { textContent }));
return className;
}
};
}

View File

@ -1,26 +0,0 @@
<!DOCTYPE html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- DEL CSP https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP | https://github.com/Prinzhorn/minimal-csp -->
<!-- DEL https://github.com/jensimmons/cssremedy -->
<!-- <link rel="stylesheet" href="https://github.com/jensimmons/cssremedy/raw/master/css/remedy.css"> -->
<title>Small DOM element creation enhancements</title>
<meta name="description" content="Making creatig elements easier">
<script type="module">
import { el } from "../index.js";
document.body.append(
el("input", { type: "checkbox", name: "name", checked: true, id: undefined }),
el("p", { textContent: "Ahoj", style: { color: "green" } }),
el("select", { value: "a" }).append(
el("option", { value: "a", textContent: "A" }),
el("option", { value: "b", textContent: "B" })
)
);
</script>
</head>
<body>
</body>
</html>

View File

@ -1,16 +0,0 @@
<!DOCTYPE html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- DEL CSP https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP | https://github.com/Prinzhorn/minimal-csp -->
<!-- DEL https://github.com/jensimmons/cssremedy -->
<!-- <link rel="stylesheet" href="https://github.com/jensimmons/cssremedy/raw/master/css/remedy.css"> -->
<title>Small DOM element creation enhancements</title>
<meta name="description" content="Making creatig elements easier">
<script src="index.js" type="module"></script>
</head>
<body>
</body>
</html>

View File

@ -1,30 +0,0 @@
import { style, el, S } from './exports.js';
style.css`
:root{
color-scheme: dark light;
}
`;
document.head.append(style.element);
import { fullNameComponent } from './components/fullNameComponent.js';
import { todosComponent } from './components/todosComponent.js';
import { CustomHTMLTestElement, CustomSlottingHTMLElement } from "./components/webComponent.js";
import { thirdParty } from "./components/3rd-party.js";
const toggle= S(false);
document.body.append(
el("h1", "Experiments:"),
el(fullNameComponent),
el(todosComponent),
el(CustomHTMLTestElement.tagName, { name: "attr" }).append(
el("span", { textContent: "test", slot: "test" }),
el("span", { textContent: "test", slot: "test" }),
),
el(thirdParty),
el(CustomSlottingHTMLElement.tagName, { onclick: ()=> toggle.set(!toggle.get()) }).append(
el("strong", { slot: "name", textContent: "Honzo" }),
S.el(toggle, is=> is
? el("span", "…default slot")
: el("span", "…custom slot")
)
)
);

48
index.d.ts vendored
View File

@ -184,12 +184,12 @@ interface On{
/** Listens to the DOM event. See {@link Document.addEventListener} */
<
Event extends keyof DocumentEventMap,
EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>,
EL extends SupportedElement
>(
type: Event,
listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any,
listener: (this: EL, ev: DocumentEventMap[Event] & { target: EL }) => any,
options?: AddEventListenerOptions
) : EE;
) : ddeElementAddon<EL>;
<
EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>,
>(
@ -199,28 +199,38 @@ interface On{
) : EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
connected<
EE extends ddeElementAddon<SupportedElement>,
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
EL extends SupportedElement
>(
listener: (this: El, event: CustomEvent<El>) => any,
listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any,
options?: AddEventListenerOptions
) : EE;
) : ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
disconnected<
EE extends ddeElementAddon<SupportedElement>,
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
EL extends SupportedElement
>(
listener: (this: El, event: CustomEvent<void>) => any,
listener: (this: EL, event: CustomEvent<void>) => any,
options?: AddEventListenerOptions
) : EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
attributeChanged<
EE extends ddeElementAddon<SupportedElement>,
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
>(
listener: (this: El, event: CustomEvent<[ string, string ]>) => any,
options?: AddEventListenerOptions
) : EE;
) : ddeElementAddon<EL>;
/**
* Fires after the next tick of the Javascript event loop.
* This is handy for example to apply some property depending on the element content:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.defer(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
defer<
EL extends SupportedElement
>(
listener: (element: EL) => any,
) : ddeElementAddon<EL>;
}
export const on: On;

View File

@ -1,5 +1,3 @@
export * from "./src/dom.js";
export * from "./src/customElement.js";
export * from "./src/events.js";
export { registerReactivity } from "./src/signals-lib/common.js";
export * from "./src/dom-lib/index.js";
export { memo } from "./src/memo.js";
export { registerReactivity } from "./src/signals-lib/common.js";

View File

@ -1,4 +1,4 @@
import { enviroment as env } from './src/dom-common.js';
import { enviroment as env } from './src/dom-lib/common.js';
env.ssr= " ssr";
const q_store= new Set();

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "deka-dom-el",
"version": "0.9.1-alpha",
"version": "0.9.2-alpha",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "deka-dom-el",
"version": "0.9.1-alpha",
"version": "0.9.2-alpha",
"license": "MIT",
"devDependencies": {
"@size-limit/preset-small-lib": "~11.2",

View File

@ -1,6 +1,6 @@
{
"name": "deka-dom-el",
"version": "0.9.1-alpha",
"version": "0.9.2-alpha",
"description": "A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks.",
"author": "Jan Andrle <andrle.jan@centrum.cz>",
"license": "MIT",

2
signals.d.ts vendored
View File

@ -2,7 +2,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}

View File

@ -21,7 +21,7 @@ export const enviroment= {
M: globalThis.MutationObserver,
q: p=> p || Promise.resolve(),
};
import { isInstance, isUndef } from './helpers.js';
import { isInstance, isUndef } from '../helpers.js';
/**
* Handles attribute setting with special undefined handling

View File

@ -1,6 +1,47 @@
import { keyLTE, evc, evd, eva } from "./dom-common.js";
import { scope } from "./dom.js";
import { keyLTE, evc, evd, eva } from "./common.js";
import { scope } from "./scopes.js";
import { c_ch_o } from "./events-observer.js";
import { elementAttribute } from "./helpers.js";
/**
* Simulates slot functionality for elements
*
* @param {HTMLElement} element - Parent element
* @param {HTMLElement} [root=element] - Root element containing slots
* @returns {HTMLElement} The root element
*/
export function simulateSlots(element, root= element){
const mark_e= "¹⁰", mark_s= "✓"; //NOTE: Markers to identify slots processed by this function. Also “prevents” native behavior as it is unlikely to use these in names. // editorconfig-checker-disable-line
const slots= Object.fromEntries(
Array.from(root.querySelectorAll("slot"))
.filter(s => !s.name.endsWith(mark_e))
.map(s => [(s.name += mark_e), s]));
element.append= new Proxy(element.append, {
apply(orig, _, els){
if(els[0]===root) return orig.apply(element, els);
for(const el of els){
const name= (el.slot||"")+mark_e;
try{ elementAttribute(el, "remove", "slot"); } catch(_error){}
const slot= slots[name];
if(!slot) return;
if(!slot.name.startsWith(mark_s)){
slot.childNodes.forEach(c=> c.remove());
slot.name= mark_s+name;
}
slot.append(el);
//TODO?: el.dispatchEvent(new CustomEvent("dde:slotchange", { detail: slot }));
}
element.append= orig; //TODO?: better memory management, but non-native behavior!
return element;
}
});
if(element!==root){
const els= Array.from(element.childNodes);
//TODO?: els.forEach(el=> el.remove());
element.append(...els);
}
return root;
}
/**
* Renders content into a custom element or shadow root

View File

@ -1,7 +1,6 @@
import { signals } from "./signals-lib/common.js";
import { enviroment as env } from './dom-common.js';
import { isInstance, isUndef, oAssign } from "./helpers.js";
import { on } from "./events.js";
import { signals } from "../signals-lib/common.js";
import { enviroment as env } from './common.js';
import { isInstance, isUndef, oAssign } from "../helpers.js";
/**
* Queues a promise, this is helpful for crossplatform components (on server side we can wait for all registered
@ -11,84 +10,6 @@ import { on } from "./events.js";
*/
export function queue(promise){ return env.q(promise); }
/**
* Array of scope contexts for tracking component hierarchies
* @type {{ scope: object, prevent: boolean, host: function }[]}
*/
const scopes= [ {
get scope(){ return env.D.body; },
host: c=> c ? c(env.D.body) : env.D.body,
prevent: true,
} ];
/** Store for disconnect abort controllers */
const store_abort= new WeakMap();
/**
* Scope management utility for tracking component hierarchies
*/
export const scope= {
/**
* Gets the current scope
* @returns {Object} Current scope context
*/
get current(){ return scopes[scopes.length-1]; },
/**
* Gets the host element of the current scope
* @returns {Function} Host accessor function
*/
get host(){ return this.current.host; },
/**
* Creates/gets an AbortController that triggers when the element disconnects
* */
get signal(){
const { host }= this;
if(store_abort.has(host)) return store_abort.get(host);
const a= new AbortController();
store_abort.set(host, a);
host(on.disconnected(()=> a.abort()));
return a.signal;
},
/**
* Prevents default behavior in the current scope
* @returns {Object} Current scope context
*/
preventDefault(){
const { current }= this;
current.prevent= true;
return current;
},
/**
* Gets a copy of the current scope stack
* @returns {Array} Copy of scope stack
*/
get state(){ return [ ...scopes ]; },
/**
* Pushes a new scope to the stack
* @param {Object} [s={}] - Scope object to push
* @returns {number} New length of the scope stack
*/
push(s= {}){ return scopes.push(oAssign({}, this.current, { prevent: false }, s)); },
/**
* Pushes the root scope to the stack
* @returns {number} New length of the scope stack
*/
pushRoot(){ return scopes.push(scopes[0]); },
/**
* Pops the current scope from the stack
* @returns {Object|undefined} Popped scope or undefined if only one scope remains
*/
pop(){
if(scopes.length===1) return;
return scopes.pop();
},
};
/**
* Chainable append function for elements
* @private
@ -107,6 +28,7 @@ export function chainableAppend(el){
/** Current namespace for element creation */
let namespace;
import { scope } from "./scopes.js";
/**
* Creates a DOM element with specified tag, attributes and addons
*
@ -116,11 +38,12 @@ let namespace;
* @returns {Element|DocumentFragment} Created element
*/
export function createElement(tag, attributes, ...addons){
/* jshint maxcomplexity: 15 */
/* jshint maxcomplexity: 16 */
const s= signals(this);
let scoped= 0;
let el, el_host;
if(Object(attributes)!==attributes || s.isSignal(attributes))
const att_type= typeof attributes;
if(att_type==="string" || att_type==="number" || s.isSignal(attributes))
attributes= { textContent: attributes };
switch(true){
case typeof tag==="function": {
@ -191,46 +114,6 @@ export function createElementNS(ns){
/** Alias for createElementNS */
export { createElementNS as elNS };
/**
* Simulates slot functionality for elements
*
* @param {HTMLElement} element - Parent element
* @param {HTMLElement} [root=element] - Root element containing slots
* @returns {HTMLElement} The root element
*/
export function simulateSlots(element, root= element){
const mark_e= "¹⁰", mark_s= "✓"; //NOTE: Markers to identify slots processed by this function. Also “prevents” native behavior as it is unlikely to use these in names. // editorconfig-checker-disable-line
const slots= Object.fromEntries(
Array.from(root.querySelectorAll("slot"))
.filter(s => !s.name.endsWith(mark_e))
.map(s => [(s.name += mark_e), s]));
element.append= new Proxy(element.append, {
apply(orig, _, els){
if(els[0]===root) return orig.apply(element, els);
for(const el of els){
const name= (el.slot||"")+mark_e;
try{ elementAttribute(el, "remove", "slot"); } catch(_error){}
const slot= slots[name];
if(!slot) return;
if(!slot.name.startsWith(mark_s)){
slot.childNodes.forEach(c=> c.remove());
slot.name= mark_s+name;
}
slot.append(el);
//TODO?: el.dispatchEvent(new CustomEvent("dde:slotchange", { detail: slot }));
}
element.append= orig; //TODO?: better memory management, but non-native behavior!
return element;
}
});
if(element!==root){
const els= Array.from(element.childNodes);
//TODO?: els.forEach(el=> el.remove());
element.append(...els);
}
return root;
}
/** Store for element assignment contexts */
const assign_context= new WeakMap();
const { setDeleteAttr }= env;
@ -251,6 +134,7 @@ export function assign(element, ...attributes){
assign_context.delete(element);
return element;
}
import { setDelete } from "./helpers.js";
/**
* Assigns a single attribute to an element
*
@ -290,6 +174,7 @@ export function assignAttribute(element, key, value){
}
return isPropSetter(element, key) ? setDeleteAttr(element, key, value) : setRemoveAttr(key, value);
}
import { setRemove, setRemoveNS } from "./helpers.js";
/**
* Gets or creates assignment context for an element
*
@ -320,21 +205,6 @@ export function classListDeclarative(element, toggle){
return element;
}
/**
* Generic element attribute manipulation
*
* @param {Element} element - Element to manipulate
* @param {string} op - Operation ("set" or "remove")
* @param {string} key - Attribute name
* @param {any} [value] - Attribute value
* @returns {void}
*/
export function elementAttribute(element, op, key, value){
if(isInstance(element, env.H))
return element[op+"Attribute"](key, value);
return element[op+"AttributeNS"](null, key, value);
}
//TODO: add cache? `(Map/Set)<el.tagName+key,isUndef>`
/**
* Checks if a property can be set on an element
@ -385,45 +255,3 @@ function forEachEntries(s, target, element, obj, cb){
cb(key, val);
});
}
/**
* Sets or removes an attribute based on value
*
* @param {Element} obj - Element to modify
* @param {string} prop - Property suffix ("Attribute")
* @param {string} key - Attribute name
* @param {any} val - Attribute value
* @returns {void}
* @private
*/
function setRemove(obj, prop, key, val){
return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, val);
}
/**
* Sets or removes a namespaced attribute based on value
*
* @param {Element} obj - Element to modify
* @param {string} prop - Property suffix ("Attribute")
* @param {string} key - Attribute name
* @param {any} val - Attribute value
* @param {string|null} [ns=null] - Namespace URI
* @returns {void}
* @private
*/
function setRemoveNS(obj, prop, key, val, ns= null){
return obj[ (isUndef(val) ? "remove" : "set") + prop + "NS" ](ns, key, val);
}
/**
* Sets or deletes a property based on value
*
* @param {Object} obj - Object to modify
* @param {string} key - Property name
* @param {any} val - Property value
* @returns {void}
* @private
*/
function setDelete(obj, key, val){
Reflect.set(obj, key, val); if(!isUndef(val)) return; return Reflect.deleteProperty(obj, key);
}

View File

@ -1,5 +1,5 @@
import { enviroment as env, evc, evd } from './dom-common.js';
import { isInstance } from "./helpers.js";
import { enviroment as env, evc, evd } from './common.js';
import { isInstance } from "../helpers.js";
/**
* Connection changes observer for tracking element connection/disconnection

View File

@ -1,5 +1,5 @@
import { keyLTE, evc, evd } from './dom-common.js';
import { oAssign, onAbort } from './helpers.js';
import { keyLTE, evc, evd } from './common.js';
import { oAssign, onAbort } from '../helpers.js';
/**
* Creates a function to dispatch events on elements
@ -38,6 +38,8 @@ export function on(event, listener, options){
};
}
on.defer= fn=> setTimeout.bind(null, fn, 0);
import { c_ch_o } from "./events-observer.js";
/**

57
src/dom-lib/helpers.js Normal file
View File

@ -0,0 +1,57 @@
import { enviroment as env } from './common.js';
import { isInstance, isUndef } from "../helpers.js";
/**
* Sets or removes an attribute based on value
*
* @param {Element} obj - Element to modify
* @param {string} prop - Property suffix ("Attribute")
* @param {string} key - Attribute name
* @param {any} val - Attribute value
* @returns {void}
* @private
*/
export function setRemove(obj, prop, key, val){
return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, val);
}
/**
* Sets or removes a namespaced attribute based on value
*
* @param {Element} obj - Element to modify
* @param {string} prop - Property suffix ("Attribute")
* @param {string} key - Attribute name
* @param {any} val - Attribute value
* @param {string|null} [ns=null] - Namespace URI
* @returns {void}
* @private
*/
export function setRemoveNS(obj, prop, key, val, ns= null){
return obj[ (isUndef(val) ? "remove" : "set") + prop + "NS" ](ns, key, val);
}
/**
* Sets or deletes a property based on value
*
* @param {Object} obj - Object to modify
* @param {string} key - Property name
* @param {any} val - Property value
* @returns {void}
* @private
*/
export function setDelete(obj, key, val){
Reflect.set(obj, key, val); if(!isUndef(val)) return; return Reflect.deleteProperty(obj, key);
}
/**
* Generic element attribute manipulation
*
* @param {Element} element - Element to manipulate
* @param {string} op - Operation ("set" or "remove")
* @param {string} key - Attribute name
* @param {any} [value] - Attribute value
* @returns {void}
*/
export function elementAttribute(element, op, key, value){
if(isInstance(element, env.H))
return element[op+"Attribute"](key, value);
return element[op+"AttributeNS"](null, key, value);
}

4
src/dom-lib/index.js Normal file
View File

@ -0,0 +1,4 @@
export * from "./scopes.js";
export * from "./el.js";
export * from "./events.js";
export * from "./customElement.js";

82
src/dom-lib/scopes.js Normal file
View File

@ -0,0 +1,82 @@
import { enviroment as env } from './common.js';
import { oAssign } from "../helpers.js";
import { on } from "./events.js";
/**
* Array of scope contexts for tracking component hierarchies
* @type {{ scope: object, prevent: boolean, host: function }[]}
*/
const scopes= [ {
get scope(){ return env.D.body; },
host: c=> c ? c(env.D.body) : env.D.body,
prevent: true,
} ];
/** Store for disconnect abort controllers */
const store_abort= new WeakMap();
/**
* Scope management utility for tracking component hierarchies
*/
export const scope= {
/**
* Gets the current scope
* @returns {Object} Current scope context
*/
get current(){ return scopes[scopes.length-1]; },
/**
* Gets the host element of the current scope
* @returns {Function} Host accessor function
*/
get host(){ return this.current.host; },
/**
* Creates/gets an AbortController that triggers when the element disconnects
* */
get signal(){
const { host }= this;
if(store_abort.has(host)) return store_abort.get(host);
const a= new AbortController();
store_abort.set(host, a);
host(on.disconnected(()=> a.abort()));
return a.signal;
},
/**
* Prevents default behavior in the current scope
* @returns {Object} Current scope context
*/
preventDefault(){
const { current }= this;
current.prevent= true;
return current;
},
/**
* Gets a copy of the current scope stack
* @returns {Array} Copy of scope stack
*/
get state(){ return [ ...scopes ]; },
/**
* Pushes a new scope to the stack
* @param {Object} [s={}] - Scope object to push
* @returns {number} New length of the scope stack
*/
push(s= {}){ return scopes.push(oAssign({}, this.current, { prevent: false }, s)); },
/**
* Pushes the root scope to the stack
* @returns {number} New length of the scope stack
*/
pushRoot(){ return scopes.push(scopes[0]); },
/**
* Pops the current scope from the stack
* @returns {Object|undefined} Popped scope or undefined if only one scope remains
*/
pop(){
if(scopes.length===1) return;
return scopes.pop();
},
};

View File

@ -9,7 +9,8 @@ export const mark= "__dde_signal";
* @type {Function}
*/
export const queueSignalWrite= (()=> {
let pendingSignals= new Set();
/** @type {Map<ddeSignal, boolean>} */
let pendingSignals= new Map();
let scheduled= false;
/**
@ -19,19 +20,20 @@ export const queueSignalWrite= (()=> {
function flushSignals() {
scheduled = false;
const todo= pendingSignals;
pendingSignals= new Set();
for(const signal of todo){
pendingSignals= new Map();
for(const [ signal, force ] of todo){
const M = signal[mark];
if(M) M.listeners.forEach(l => l(M.value));
if(M) M.listeners.forEach(l => l(M.value, force));
}
}
/**
* Queues a signal for update
* @param {Object} s - Signal to queue
* @param {ddeSignal} s - Signal to queue
* @param {boolean} force - Forced update
*/
return function(s){
pendingSignals.add(s);
return function(s, force= false){
pendingSignals.set(s, pendingSignals.get(s) || force);
if(scheduled) return;
scheduled = true;
queueMicrotask(flushSignals);

View File

@ -57,12 +57,12 @@ export function signal(value, actions){
* Updates the derived signal when dependencies change
* @private
*/
function contextReWatch(){
function contextReWatch(_, force){
const [ origin, ...deps_old ]= deps.get(contextReWatch);
deps.set(contextReWatch, new Set([ origin ]));
stack_watch.push(contextReWatch);
write(out, value());
write(out, value(), force);
stack_watch.pop();
if(!deps_old.length) return;
@ -95,7 +95,7 @@ signal.action= function(s, name, ...a){
throw new Error(`Action "${name}" not defined. See ${mark}.actions.`);
actions[name].apply(M, a);
if(M.skip) return (delete M.skip);
queueSignalWrite(s);
queueSignalWrite(s, true);
};
/**
@ -159,10 +159,10 @@ signal.clear= function(...signals){
};
/** Property key for tracking reactive elements */
const key_reactive= "__dde_reactive";
import { enviroment as env, eva } from "../dom-common.js";
import { el } from "../dom.js";
import { scope } from "../dom.js";
import { on } from "../events.js";
import { enviroment as env, eva } from "../dom-lib/common.js";
import { el } from "../dom-lib/index.js";
import { scope } from "../dom-lib/scopes.js";
import { on } from "../dom-lib/events.js";
import { memo } from "../memo.js";
/**
@ -434,7 +434,7 @@ function write(s, value, force){
if(!M || (!force && M.value===value)) return;
M.value= value;
queueSignalWrite(s);
queueSignalWrite(s, force);
return value;
}