mirror of
				https://github.com/jaandrle/deka-dom-el
				synced 2025-10-31 13:59:14 +01:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
			v0.9.3-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)=> { | ||||
| @@ -13,3 +14,54 @@ const debouncedSet= debounce(value => inputSignal.set(value), 300); | ||||
|  | ||||
| // In your input handler | ||||
| 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) { | ||||
| 		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( | ||||
| 					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()}` | ||||
| 								}) | ||||
| 							) | ||||
| 						) | ||||
| 					), | ||||
| 				), | ||||
| 				!todos.some(todo => todo.completed) | ||||
| 					? el() | ||||
| 					: el("button", { textContent: "Clear completed", className: "clear-completed" }, onClearCompleted) | ||||
| 			) | ||||
| 		) | ||||
| 	); | ||||
| 	/** @param {Todo[]} todos */ | ||||
| 	function noOfLeft(todos){ | ||||
| 		const { length }= todos.filter(todo => !todo.completed); | ||||
| 		return el("strong").append( | ||||
| 			length + " ", | ||||
| 			length === 1 ? "item left" : "items left" | ||||
| 		) | ||||
| 					) | ||||
| 				), | ||||
| 				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() | ||||
| 				) | ||||
| 			) | ||||
| 			: el() | ||||
| 		)) | ||||
| 	); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -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