mirror of
https://github.com/jaandrle/deka-dom-el
synced 2025-07-01 20:32:13 +02:00
Compare commits
7 Commits
v0.9.5-alp
...
93b905e677
Author | SHA1 | Date | |
---|---|---|---|
93b905e677
|
|||
2fdd8ebea9
|
|||
7cee9a4a14
|
|||
3c6cad5648
|
|||
9251e70015
|
|||
8756dabc55
|
|||
0a2d17ac6f
|
12
bs/build.js
12
bs/build.js
@ -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",
|
||||
|
641
dist/esm-with-signals.d.min.ts
vendored
641
dist/esm-with-signals.d.min.ts
vendored
@ -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>
|
||||
}
|
568
dist/esm.d.min.ts
vendored
568
dist/esm.d.min.ts
vendored
@ -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
|
BIN
docs/assets/devtools.png
Normal file
BIN
docs/assets/devtools.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 145 KiB |
@ -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;
|
||||
|
@ -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));
|
||||
|
@ -7,8 +7,6 @@ const paragraph= el("p", "See lifecycle events in console.",
|
||||
|
||||
document.body.append(
|
||||
paragraph,
|
||||
el("button", "Update attribute", on("click", ()=> paragraph.setAttribute("test", Math.random().toString()))),
|
||||
" ",
|
||||
el("button", "Remove", on("click", ()=> paragraph.remove()))
|
||||
);
|
||||
|
||||
|
@ -26,28 +26,23 @@ function Todos(){
|
||||
});
|
||||
});
|
||||
|
||||
// Setup hash change listener
|
||||
window.addEventListener("hashchange", () => {
|
||||
const hash = location.hash.replace("#", "") || "all";
|
||||
S.action(pageS, "set", /** @type {"all"|"active"|"completed"} */(hash));
|
||||
});
|
||||
|
||||
/** @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 +56,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",
|
||||
@ -82,50 +78,40 @@ function Todos(){
|
||||
)
|
||||
)
|
||||
)
|
||||
: 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(todos)
|
||||
),
|
||||
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.some(todo => todo.completed)
|
||||
? el()
|
||||
: el("button", { textContent: "Clear completed", className: "clear-completed" }, onClearCompleted)
|
||||
)
|
||||
: el()
|
||||
))
|
||||
)
|
||||
);
|
||||
/** @param {Todo[]} todos */
|
||||
function noOfLeft(todos){
|
||||
const { length }= todos.filter(todo => !todo.completed);
|
||||
return el("strong").append(
|
||||
length + " ",
|
||||
length === 1 ? "item left" : "items left"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -177,10 +163,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 });
|
||||
@ -209,16 +196,15 @@ function TodoItem({ id, title, completed }) {
|
||||
el("label", { textContent: title }, onStartEdit),
|
||||
el("button", { 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()
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -354,7 +340,7 @@ function todosSignal(){
|
||||
*/
|
||||
function routerSignal(signal){
|
||||
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
|
||||
@ -364,6 +350,14 @@ function routerSignal(signal){
|
||||
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));
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
8
docs/components/getLibraryUrl.css.js
Normal file
8
docs/components/getLibraryUrl.css.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { styles } from "../ssr.js";
|
||||
|
||||
styles.css`
|
||||
#library-url-form {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
`;
|
75
docs/components/getLibraryUrl.js.js
Normal file
75
docs/components/getLibraryUrl.js.js
Normal file
@ -0,0 +1,75 @@
|
||||
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 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()
|
||||
);
|
||||
const onCopy= on("click", ev => {
|
||||
const code= /** @type {HTMLDivElement} */ (ev.target).previousElementSibling;
|
||||
navigator.clipboard.writeText(code.textContent);
|
||||
});
|
||||
|
||||
return el("form", { id: "library-url-form" }, onSubmit).append(
|
||||
el("select", { name: "module" }, onChangeSubmit).append(
|
||||
el("option", { value: "esm", textContent: "ESM — modern JavaScript module" },
|
||||
isSelected(lib.get()[0])),
|
||||
el("option", { value: "iife", textContent: "IIFE — legacy JavaScript with DDE global variable" },
|
||||
isSelected(lib.get()[0])),
|
||||
),
|
||||
el("select", { name: "what" }, onChangeSubmit).append(
|
||||
el("option", { value: "", textContent: "only DOM part" },
|
||||
isSelected(lib.get()[1])),
|
||||
el("option", { value: "-with-signals", textContent: "DOM part and signals library" },
|
||||
isSelected(lib.get()[1])),
|
||||
),
|
||||
el("select", { name: "minified" }, onChangeSubmit).append(
|
||||
el("option", { value: "", textContent: "unminified" },
|
||||
isSelected(lib.get()[2])),
|
||||
el("option", { value: ".min", textContent: "minified" },
|
||||
isSelected(lib.get()[2])),
|
||||
),
|
||||
el("output").append(
|
||||
el("p", "Library URL:"),
|
||||
el("div", { className: "code", dataJs: "done", tabIndex: 0 }).append(
|
||||
el("code").append(
|
||||
el("pre", S(()=> url.get()+".js")),
|
||||
),
|
||||
el("button", {
|
||||
className: "copy-button",
|
||||
textContent: "Copy",
|
||||
ariaLabel: "Copy code to clipboard",
|
||||
}, onCopy),
|
||||
),
|
||||
el("p", "Library type definition:"),
|
||||
el("div", { className: "code", dataJs: "done", tabIndex: 0 }).append(
|
||||
el("code").append(
|
||||
el("pre", S(()=> url.get()+".d.ts")),
|
||||
),
|
||||
el("button", {
|
||||
className: "copy-button",
|
||||
textContent: "Copy",
|
||||
ariaLabel: "Copy code to clipboard",
|
||||
}, onCopy),
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
function isSelected(value){
|
||||
return element=> element.selected= element.value===value;
|
||||
}
|
@ -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)",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -193,6 +193,20 @@ pre code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
figure {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: var(--text-light);
|
||||
border: 1px dashed var(--border);
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
img {
|
||||
object-fit: contain;
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--shadow);
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
body {
|
||||
@ -234,7 +248,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 +281,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 {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import "./components/getLibraryUrl.css.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 { ireland } from "./components/ireland.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 (~10–15kB 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,82 @@ 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 application’s 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>. It’s based on ${el("a", {
|
||||
textContent: "MVC", ...references.w_mvc })} (${el("a", { textContent: "MVVM", ...references.w_mvv })}),
|
||||
but is there presented in simpler form.
|
||||
`),
|
||||
|
||||
el("div", { className: "note" }).append(
|
||||
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. You’ll learn more about this in the
|
||||
following sections.
|
||||
`),
|
||||
el("p").append(T`
|
||||
The 3PS pattern isn’t 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`
|
||||
To get builded dd<el> to be used immediately in your browser, you can download the latest version from:
|
||||
`),
|
||||
el(ireland, {
|
||||
src: fileURL("./components/getLibraryUrl.js.js"),
|
||||
exportName: "getLibraryUrl",
|
||||
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.
|
||||
Let’s get started with the basics of creating elements!
|
||||
`),
|
||||
|
@ -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 doesn’t 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`
|
||||
It’s 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.
|
||||
`),
|
||||
|
@ -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: "WebReflection’s 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,22 +139,22 @@ 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 it’s connected to the live DOM.
|
||||
You can think of an Addon as an "oncreate" event handler.
|
||||
You can think of an Addon as an “oncreate” event handler.
|
||||
`),
|
||||
el("p").append(...T`
|
||||
el("p").append(T`
|
||||
dd<el> provides two additional lifecycle events that correspond to ${el("a", { textContent:
|
||||
"custom element", ...references.mdn_customElements })} lifecycle callbacks:
|
||||
`),
|
||||
@ -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,28 @@ 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`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 +209,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
|
||||
`)
|
||||
),
|
||||
|
@ -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`Publish–subscribe 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, you’ll 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 can’t call ${el("code", ".set()")} on them. Their value is always
|
||||
computed from their dependencies. They’re perfect for transforming or combining data from other signals.
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/signals/computations-abort.js"), page_id }),
|
||||
|
||||
el(h3, t`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`
|
||||
Here’s a more complete example of a todo list using signal actions:
|
||||
`),
|
||||
el(example, { src: fileURL("./components/examples/signals/actions-todos.js"), page_id }),
|
||||
|
||||
el("div", { 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")}: Don’t 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")}: Don’t 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`)
|
||||
)
|
||||
|
@ -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 component’s 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 app’s 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")}
|
||||
`)
|
||||
|
@ -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`
|
||||
Let’s 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, we’re 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 element’s
|
||||
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 component’s 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
|
||||
`)
|
||||
),
|
||||
|
@ -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 aren’t
|
||||
happening.
|
||||
`),
|
||||
|
||||
el("h4", t`Inspecting signal values`),
|
||||
el("p").append(...T`
|
||||
The simplest way to debug a signal is to log its current value by calling the get method:
|
||||
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 isn’t 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()")}. Don’t 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 aren’t 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 isn’t updating as expected.
|
||||
You can inspect the elements between the comment nodes to see their current state and the
|
||||
signal connections through \`__dde_reactive\` of the host element.
|
||||
`),
|
||||
|
||||
el("h4", t`DOM inspection properties`),
|
||||
el("p").append(...T`
|
||||
Elements created with the dd<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 they’re
|
||||
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 browser’s 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 they’re
|
||||
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`)
|
||||
),
|
||||
|
||||
);
|
||||
}
|
||||
|
@ -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, it’s 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, it’s 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 DDE’s 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 DDE’s 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
|
||||
`)
|
||||
|
@ -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 aren’t 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.
|
||||
`)
|
||||
|
@ -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,7 +96,7 @@ 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: `
|
||||
@ -118,7 +118,7 @@ export function page({ pkg, info }){
|
||||
});
|
||||
`, 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 +177,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
|
||||
*/
|
||||
@ -200,7 +207,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,7 +215,7 @@ 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:
|
||||
`),
|
||||
|
||||
@ -233,12 +240,36 @@ export function page({ pkg, info }){
|
||||
)
|
||||
`, 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 +299,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,14 +320,14 @@ 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:
|
||||
`),
|
||||
|
||||
@ -309,7 +340,7 @@ export function page({ pkg, info }){
|
||||
)
|
||||
`, page_id }),
|
||||
|
||||
el("p").append(...T`
|
||||
el("p").append(T`
|
||||
This approach ensures that:
|
||||
`),
|
||||
el("ul").append(
|
||||
@ -329,14 +360,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 +375,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 +391,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 +434,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 +442,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 +454,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 +465,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 +493,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 +504,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 +512,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:
|
||||
`),
|
||||
|
||||
@ -520,7 +551,7 @@ export function page({ pkg, info }){
|
||||
`, 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 +559,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 +597,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 +606,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("li").append(T`
|
||||
${el("strong", "Focus Management:")} Reliable input focus with requestAnimationFrame
|
||||
`),
|
||||
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 +653,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.
|
||||
`),
|
||||
|
@ -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> isn’t 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 that’s 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. Here’s 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`
|
||||
Here’s 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! Here’s 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")} you’ve 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.
|
||||
|
@ -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`
|
||||
Here’s 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 aren’t 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,
|
||||
|
@ -32,45 +32,45 @@ 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`
|
||||
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("li").append(T`
|
||||
${el("strong", "Functional Composition:")} Building UIs through function composition rather than
|
||||
inheritance
|
||||
`),
|
||||
el("li").append(...T`
|
||||
el("li").append(T`
|
||||
${el("strong", "Clear Patterns:")} Promoting maintainable code organization with the 3PS pattern
|
||||
`),
|
||||
el("li").append(...T`
|
||||
el("li").append(T`
|
||||
${el("strong", "Targeted Reactivity:")} Using signals for efficient, fine-grained updates
|
||||
`),
|
||||
el("li").append(...T`
|
||||
el("li").append(T`
|
||||
${el("strong", "Unix Philosophy:")} Doing one thing well and allowing composability with other tools
|
||||
`)
|
||||
)
|
||||
@ -79,7 +79,7 @@ export function page({ pkg, info }){
|
||||
el(h3, t`Case Studies & Real-World Applications`),
|
||||
|
||||
el("h4", t`TodoMVC Implementation`),
|
||||
el("p").append(...T`
|
||||
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:
|
||||
`),
|
||||
@ -92,40 +92,40 @@ 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`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("li").append(T`
|
||||
${el("strong", "Start with state:")}: Convert global variables or ad-hoc state to signals
|
||||
`),
|
||||
el("li").append(...T`
|
||||
el("li").append(T`
|
||||
${el("strong", "Replace query selectors:")}: Replace getElementById/querySelector with direct references to elements
|
||||
`),
|
||||
el("li").append(...T`
|
||||
el("li").append(T`
|
||||
${el("strong", "Convert imperative updates:")}: Replace manual DOM updates with declarative signal bindings
|
||||
`),
|
||||
el("li").append(...T`
|
||||
el("li").append(T`
|
||||
${el("strong", "Refactor into components:")}: Organize related UI elements into component functions
|
||||
`)
|
||||
),
|
||||
@ -211,19 +211,19 @@ export function page({ pkg, info }){
|
||||
el("div").append(
|
||||
el("h4", t`Code Organization`),
|
||||
el("ul").append(
|
||||
el("li").append(...T`
|
||||
el("li").append(T`
|
||||
${el("strong", "Follow the 3PS pattern:")}: Separate state creation, binding to elements, and state updates
|
||||
`),
|
||||
el("li").append(...T`
|
||||
el("li").append(T`
|
||||
${el("strong", "Use component functions:")}: Create reusable UI components as functions
|
||||
`),
|
||||
el("li").append(...T`
|
||||
el("li").append(T`
|
||||
${el("strong", "Create signal factories:")}: Extract reusable signal patterns into factory functions
|
||||
`),
|
||||
el("li").append(...T`
|
||||
el("li").append(T`
|
||||
${el("strong", "Leverage scopes:")}: Use scope for component context and clean resource management
|
||||
`),
|
||||
el("li").append(...T`
|
||||
el("li").append(T`
|
||||
${el("strong", "Event delegation:")}: Prefer component-level event handlers over many individual handlers
|
||||
`)
|
||||
)
|
||||
@ -232,23 +232,23 @@ export function page({ pkg, info }){
|
||||
el("div").append(
|
||||
el("h4", t`Performance Optimization`),
|
||||
el("ul").append(
|
||||
el("li").append(...T`
|
||||
el("li").append(T`
|
||||
${el("strong", "Memoize list items:")}: Use ${el("code", "memo")} for items in frequently-updated lists
|
||||
`),
|
||||
el("li").append(...T`
|
||||
el("li").append(T`
|
||||
${el("strong", "Avoid unnecessary signal updates:")}: Only update signals when values actually change
|
||||
`),
|
||||
el("li").append(...T`
|
||||
el("li").append(T`
|
||||
${el("strong", "Use AbortSignals:")}: Clean up resources when components are removed
|
||||
`),
|
||||
el("li").append(...T`
|
||||
el("li").append(T`
|
||||
${el("strong", "Prefer derived signals:")}: Use computed values instead of manual updates
|
||||
`),
|
||||
el("li").append(...T`
|
||||
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.
|
||||
`)
|
||||
),
|
||||
@ -324,56 +324,56 @@ 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`
|
||||
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`
|
||||
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
|
||||
from simple interactive elements to complex applications while remaining close
|
||||
to what makes the web platform powerful.
|
||||
`),
|
||||
el("p").append(...T`
|
||||
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.
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
@ -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" }),
|
||||
)
|
||||
);
|
||||
}
|
@ -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)
|
||||
);
|
||||
}
|
@ -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);
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
@ -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>
|
@ -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>
|
@ -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")
|
||||
)
|
||||
)
|
||||
);
|
Reference in New Issue
Block a user