mirror of
				https://github.com/jaandrle/deka-dom-el
				synced 2025-10-31 13:59:14 +01:00 
			
		
		
		
	Compare commits
	
		
			5 Commits
		
	
	
		
			main
			...
			330f702409
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 330f702409 | |||
| 2f57f115a0 | |||
| d1b017c1a1 | |||
| 992370b4e3 | |||
| 3301a6600c | 
							
								
								
									
										18
									
								
								.github/workflows/npm-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/npm-publish.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,18 +0,0 @@ | |||||||
| name: Publish Package to npmjs |  | ||||||
| on: |  | ||||||
|   workflow_dispatch: |  | ||||||
|   release: |  | ||||||
|     types: [created] |  | ||||||
| jobs: |  | ||||||
|   build: |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 |  | ||||||
|       - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 |  | ||||||
|         with: |  | ||||||
|           node-version: '20.16' |  | ||||||
|           registry-url: 'https://registry.npmjs.org' |  | ||||||
|       - run: npm ci |  | ||||||
|       - run: npm publish |  | ||||||
|         env: |  | ||||||
|           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} |  | ||||||
							
								
								
									
										3
									
								
								.npmrc
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								.npmrc
									
									
									
									
									
								
							| @@ -1,3 +0,0 @@ | |||||||
| //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} |  | ||||||
| registry=https://registry.npmjs.org/ |  | ||||||
| always-auth=true |  | ||||||
| @@ -1,134 +0,0 @@ | |||||||
|  |  | ||||||
| # Contributor Covenant Code of Conduct |  | ||||||
|  |  | ||||||
| ## Our Pledge |  | ||||||
|  |  | ||||||
| We as members, contributors, and leaders pledge to make participation in our |  | ||||||
| community a harassment-free experience for everyone, regardless of age, body |  | ||||||
| size, visible or invisible disability, ethnicity, sex characteristics, gender |  | ||||||
| identity and expression, level of experience, education, socio-economic status, |  | ||||||
| nationality, personal appearance, race, caste, color, religion, or sexual |  | ||||||
| identity and orientation. |  | ||||||
|  |  | ||||||
| We pledge to act and interact in ways that contribute to an open, welcoming, |  | ||||||
| diverse, inclusive, and healthy community. |  | ||||||
|  |  | ||||||
| ## Our Standards |  | ||||||
|  |  | ||||||
| Examples of behavior that contributes to a positive environment for our |  | ||||||
| community include: |  | ||||||
|  |  | ||||||
| * Demonstrating empathy and kindness toward other people |  | ||||||
| * Being respectful of differing opinions, viewpoints, and experiences |  | ||||||
| * Giving and gracefully accepting constructive feedback |  | ||||||
| * Accepting responsibility and apologizing to those affected by our mistakes, |  | ||||||
| 	and learning from the experience |  | ||||||
| * Focusing on what is best not just for us as individuals, but for the overall |  | ||||||
| 	community |  | ||||||
|  |  | ||||||
| Examples of unacceptable behavior include: |  | ||||||
|  |  | ||||||
| * The use of sexualized language or imagery, and sexual attention or advances of |  | ||||||
| 	any kind |  | ||||||
| * Trolling, insulting or derogatory comments, and personal or political attacks |  | ||||||
| * Public or private harassment |  | ||||||
| * Publishing others' private information, such as a physical or email address, |  | ||||||
| 	without their explicit permission |  | ||||||
| * Other conduct which could reasonably be considered inappropriate in a |  | ||||||
| 	professional setting |  | ||||||
|  |  | ||||||
| ## Enforcement Responsibilities |  | ||||||
|  |  | ||||||
| Community leaders are responsible for clarifying and enforcing our standards of |  | ||||||
| acceptable behavior and will take appropriate and fair corrective action in |  | ||||||
| response to any behavior that they deem inappropriate, threatening, offensive, |  | ||||||
| or harmful. |  | ||||||
|  |  | ||||||
| Community leaders have the right and responsibility to remove, edit, or reject |  | ||||||
| comments, commits, code, wiki edits, issues, and other contributions that are |  | ||||||
| not aligned to this Code of Conduct, and will communicate reasons for moderation |  | ||||||
| decisions when appropriate. |  | ||||||
|  |  | ||||||
| ## Scope |  | ||||||
|  |  | ||||||
| This Code of Conduct applies within all community spaces, and also applies when |  | ||||||
| an individual is officially representing the community in public spaces. |  | ||||||
| Examples of representing our community include using an official e-mail address, |  | ||||||
| posting via an official social media account, or acting as an appointed |  | ||||||
| representative at an online or offline event. |  | ||||||
|  |  | ||||||
| ## Enforcement |  | ||||||
|  |  | ||||||
| Instances of abusive, harassing, or otherwise unacceptable behavior may be |  | ||||||
| reported to the community leaders responsible for enforcement at |  | ||||||
| andrle.jan@centrum.cz. |  | ||||||
| All complaints will be reviewed and investigated promptly and fairly. |  | ||||||
|  |  | ||||||
| All community leaders are obligated to respect the privacy and security of the |  | ||||||
| reporter of any incident. |  | ||||||
|  |  | ||||||
| ## Enforcement Guidelines |  | ||||||
|  |  | ||||||
| Community leaders will follow these Community Impact Guidelines in determining |  | ||||||
| the consequences for any action they deem in violation of this Code of Conduct: |  | ||||||
|  |  | ||||||
| ### 1. Correction |  | ||||||
|  |  | ||||||
| **Community Impact**: Use of inappropriate language or other behavior deemed |  | ||||||
| unprofessional or unwelcome in the community. |  | ||||||
|  |  | ||||||
| **Consequence**: A private, written warning from community leaders, providing |  | ||||||
| clarity around the nature of the violation and an explanation of why the |  | ||||||
| behavior was inappropriate. A public apology may be requested. |  | ||||||
|  |  | ||||||
| ### 2. Warning |  | ||||||
|  |  | ||||||
| **Community Impact**: A violation through a single incident or series of |  | ||||||
| actions. |  | ||||||
|  |  | ||||||
| **Consequence**: A warning with consequences for continued behavior. No |  | ||||||
| interaction with the people involved, including unsolicited interaction with |  | ||||||
| those enforcing the Code of Conduct, for a specified period of time. This |  | ||||||
| includes avoiding interactions in community spaces as well as external channels |  | ||||||
| like social media. Violating these terms may lead to a temporary or permanent |  | ||||||
| ban. |  | ||||||
|  |  | ||||||
| ### 3. Temporary Ban |  | ||||||
|  |  | ||||||
| **Community Impact**: A serious violation of community standards, including |  | ||||||
| sustained inappropriate behavior. |  | ||||||
|  |  | ||||||
| **Consequence**: A temporary ban from any sort of interaction or public |  | ||||||
| communication with the community for a specified period of time. No public or |  | ||||||
| private interaction with the people involved, including unsolicited interaction |  | ||||||
| with those enforcing the Code of Conduct, is allowed during this period. |  | ||||||
| Violating these terms may lead to a permanent ban. |  | ||||||
|  |  | ||||||
| ### 4. Permanent Ban |  | ||||||
|  |  | ||||||
| **Community Impact**: Demonstrating a pattern of violation of community |  | ||||||
| standards, including sustained inappropriate behavior, harassment of an |  | ||||||
| individual, or aggression toward or disparagement of classes of individuals. |  | ||||||
|  |  | ||||||
| **Consequence**: A permanent ban from any sort of public interaction within the |  | ||||||
| community. |  | ||||||
|  |  | ||||||
| ## Attribution |  | ||||||
|  |  | ||||||
| This Code of Conduct is adapted from the [Contributor Covenant][homepage], |  | ||||||
| version 2.1, available at |  | ||||||
| [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. |  | ||||||
|  |  | ||||||
| Community Impact Guidelines were inspired by |  | ||||||
| [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. |  | ||||||
|  |  | ||||||
| For answers to common questions about this code of conduct, see the FAQ at |  | ||||||
| [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at |  | ||||||
| [https://www.contributor-covenant.org/translations][translations]. |  | ||||||
|  |  | ||||||
| [homepage]: https://www.contributor-covenant.org |  | ||||||
| [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html |  | ||||||
| [Mozilla CoC]: https://github.com/mozilla/diversity |  | ||||||
| [FAQ]: https://www.contributor-covenant.org/faq |  | ||||||
| [translations]: https://www.contributor-covenant.org/translations |  | ||||||
|  |  | ||||||
| @@ -75,14 +75,11 @@ where everyone feels comfortable participating. | |||||||
|  |  | ||||||
| …see [BS folder](./bs/README.md) for more info. | …see [BS folder](./bs/README.md) for more info. | ||||||
|  |  | ||||||
| ## Categorizing [](https://robinpokorny.github.io/git3moji/) <!-- editorconfig-checker-disable-line --> |  | ||||||
| We use [git3moji](https://git3moji.netlify.app/) for commit messages, issue titles, pull request titles and in other |  | ||||||
| areas. To make categorizing quick and consistent. |  | ||||||
|  |  | ||||||
| ## Commit Guidelines | ## Commit Guidelines | ||||||
|  |  | ||||||
| We use [git3moji](https://git3moji.netlify.app/) for commit messages. This helps keep the commit history clear and | We use | ||||||
| consistent. | [](https://robinpokorny.github.io/git3moji/) <!-- editorconfig-checker-disable-line --> | ||||||
|  | for commit messages. This helps keep the commit history clear and consistent. | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| :emoji: Short summary of the change | :emoji: Short summary of the change | ||||||
|   | |||||||
							
								
								
									
										153
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										153
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,47 +1,7 @@ | |||||||
| **Alpha** | **WIP** (the experimentation phase) | ||||||
| | [Docs&Examples](https://jaandrle.github.io/deka-dom-el "Official documentation and guide site") | | [source code on GitHub](https://github.com/jaandrle/deka-dom-el) | ||||||
| | [NPM](https://www.npmjs.com/package/deka-dom-el "Official NPM package page") | | [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el) | ||||||
| | [GitHub](https://github.com/jaandrle/deka-dom-el "Official GitHub repository") | | [](https://robinpokorny.github.io/git3moji/) <!-- editorconfig-checker-disable-line --> | ||||||
| ([*Gitea*](https://gitea.jaandrle.cz/jaandrle/deka-dom-el "GitHub repository mirror on my own Gitea instance")) |  | ||||||
|  |  | ||||||
| ***Vanilla for flavouring — a full-fledged feast for large projects*** |  | ||||||
|  |  | ||||||
| ```javascript |  | ||||||
| // 🌟 Reactive component with clear separation of concerns |  | ||||||
| document.body.append( |  | ||||||
| 	el(EmojiCounter, { initial: "🚀" }), |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| function EmojiCounter({ initial }) { |  | ||||||
| 	// ✨ - Define reactive data |  | ||||||
| 	const count = S(0); |  | ||||||
| 	const emoji = S(initial); |  | ||||||
| 	const textContent = S(() => `Hello World ${emoji.get().repeat(count.get())}`); |  | ||||||
|  |  | ||||||
| 	// 🔄 - UI updates automatically when signals change |  | ||||||
| 	return el().append( |  | ||||||
| 		el("p", { textContent, className: "output" }), |  | ||||||
|  |  | ||||||
| 		// 🎮 - Update state on events |  | ||||||
| 		el("button", { textContent: "Add Emoji" }, |  | ||||||
| 			on("click", () => count.set(count.get() + 1)), |  | ||||||
| 		), |  | ||||||
|  |  | ||||||
| 		el("select", null, |  | ||||||
| 			on.defer(el=> el.value= initial), |  | ||||||
| 			on("change", e => emoji.set(e.target.value)), |  | ||||||
| 		).append( |  | ||||||
| 			el(Option, "🎉"), |  | ||||||
| 			el(Option, "🚀"), |  | ||||||
| 			el(Option, "💖"), |  | ||||||
| 		), |  | ||||||
| 	); |  | ||||||
| } |  | ||||||
| function Option({ textContent }){ |  | ||||||
| 	return el("option", { value: textContent, textContent }); |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| *…use simple DOM API by default and library tools and logic when you need them* |  | ||||||
|  |  | ||||||
| <p align="center"> | <p align="center"> | ||||||
| 	<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180"> | 	<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180"> | ||||||
| @@ -49,6 +9,51 @@ function Option({ textContent }){ | |||||||
|  |  | ||||||
| # Deka DOM Elements (dd\<el\> or DDE) | # Deka DOM Elements (dd\<el\> or DDE) | ||||||
|  |  | ||||||
|  | ***Vanilla for flavouring — a full-fledged feast for large projects*** | ||||||
|  |  | ||||||
|  | *…use simple DOM API by default and library tools and logic when you need them* | ||||||
|  |  | ||||||
|  | ```javascript | ||||||
|  | // 🌟 Reactive component with clear separation of concerns | ||||||
|  | document.body.append( | ||||||
|  | 	el(EmojiCounter, { initial: "🚀" }) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | function EmojiCounter({ initial }) { | ||||||
|  | 	// ✨ State - Define reactive data | ||||||
|  | 	const count = S(0); | ||||||
|  | 	const emoji = S(initial); | ||||||
|  |  | ||||||
|  | 	/** @param {HTMLOptionElement} el */ | ||||||
|  | 	const isSelected= el=> (el.selected= el.value===initial); | ||||||
|  |  | ||||||
|  | 	// 🔄 View - UI updates automatically when signals change | ||||||
|  | 	return el().append( | ||||||
|  | 		el("p", { | ||||||
|  | 			className: "output", | ||||||
|  | 			textContent: S(() => | ||||||
|  | 				`Hello World ${emoji.get().repeat(count.get())}`), | ||||||
|  | 		}), | ||||||
|  |  | ||||||
|  | 		// 🎮 Controls - Update state on events | ||||||
|  | 		el("button", { textContent: "Add Emoji" }, | ||||||
|  | 			on("click", () => count.set(count.get() + 1)) | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		el("select", null, on.host(el=> el.value= initial), | ||||||
|  | 			on("change", e => emoji.set(e.target.value)) | ||||||
|  | 		).append( | ||||||
|  | 			el(Option, "🎉"), | ||||||
|  | 			el(Option, "🚀"), | ||||||
|  | 			el(Option, "💖"), | ||||||
|  | 		) | ||||||
|  | 	); | ||||||
|  | } | ||||||
|  | function Option({ textContent }){ | ||||||
|  | 	return el("option", { value: textContent, textContent }); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
| Creating reactive elements, components, and Web Components using the native | Creating reactive elements, components, and Web Components using the native | ||||||
| [IDL](https://developer.mozilla.org/en-US/docs/Glossary/IDL)/JavaScript DOM API enhanced with | [IDL](https://developer.mozilla.org/en-US/docs/Glossary/IDL)/JavaScript DOM API enhanced with | ||||||
| [**signals/observables**](#understanding-signals). | [**signals/observables**](#understanding-signals). | ||||||
| @@ -56,35 +61,43 @@ Creating reactive elements, components, and Web Components using the native | |||||||
| ## Features at a Glance | ## Features at a Glance | ||||||
|  |  | ||||||
| - ✅ **No build step required** — use directly in browsers or Node.js | - ✅ **No build step required** — use directly in browsers or Node.js | ||||||
| - ✅ **Minimalized footprint** — ~10-15kB minified bundle (original goal 10kB), **zero**/minimal dependencies and | - ☑️ **Lightweight** — ~10-15kB minified (original goal 10kB) with zero/minimal dependencies | ||||||
| 	small in-memory size (auto-releasing resources as much as possible) | - ✅ **Declarative & functional approach** for clean, maintainable code | ||||||
| - ✅ **Declarative & functional approach support** for clean, maintainable code |  | ||||||
| - ✅ **Signals and events** for reactive UI | - ✅ **Signals and events** for reactive UI | ||||||
| - ✅ **Memoization for performance** — optimize rendering with intelligent caching | - ✅ **Memoization for performance** — optimize rendering with intelligent caching | ||||||
| - ☑️ **Optional build-in signals** with support for custom reactive implementations (#39) | - ✅ **Optional build-in signals** with support for custom reactive implementations | ||||||
| - ☑️ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom) | - ✅ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom) | ||||||
| - ✅ **TypeScript support** | - ✅ **TypeScript support** (work in progress) | ||||||
| - ✅ **Support for debugging with browser DevTools** without extensions | - ☑️ **Support for debugging with browser DevTools** without extensions | ||||||
| - ☑️ **Enhanced Web Components** support | - ☑️ **Enhanced Web Components** support (work in progress) | ||||||
|  |  | ||||||
|  | ## Why Another Library? | ||||||
|  |  | ||||||
|  | This library bridges the gap between minimal solutions like van/hyperscript and more comprehensive frameworks like | ||||||
|  | [solid-js](https://github.com/solidjs/solid), offering a balanced trade-off between size, complexity, and usability. | ||||||
|  |  | ||||||
|  | Following functional programming principles, dd\<el\> starts with pure JavaScript (DOM API) and gradually adds | ||||||
|  | auxiliary functions. These range from minor improvements to advanced features for building complete declarative | ||||||
|  | reactive UI templates. | ||||||
|  |  | ||||||
|  | A key advantage: any internal function (`assign`, `classListDeclarative`, `on`, `dispatchEvent`, `S`, etc.) can be used | ||||||
|  | independently while also working seamlessly together. This modular approach makes it easier to integrate the library | ||||||
|  | into existing projects. | ||||||
|  |  | ||||||
| ## Getting Started | ## Getting Started | ||||||
|  |  | ||||||
| ### Quick Links |  | ||||||
|  |  | ||||||
| - [**Documentation and Guide**](https://jaandrle.github.io/deka-dom-el) |  | ||||||
| - [**Examples**](https://jaandrle.github.io/deka-dom-el/p15-examples.html) |  | ||||||
| - [**Changelog**](https://github.com/jaandrle/deka-dom-el/releases) |  | ||||||
|  |  | ||||||
| ### Installation | ### Installation | ||||||
|  |  | ||||||
|  | #### npm | ||||||
| ```bash | ```bash | ||||||
| npm install deka-dom-el --save | # TBD | ||||||
|  | # npm install deka-dom-el | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| …or via CDN / Direct Script: | #### CDN / Direct Script | ||||||
|  |  | ||||||
| For CDN links and various build formats (ESM/IIFE, with/without signals, minified/unminified), see the [interactive | For CDN links and various build formats (ESM/IIFE, with/without signals, minified/unminified), see the [interactive | ||||||
| format selector](https://jaandrle.github.io/deka-dom-el/#h-getting-started) on the documentation site. | format selector](https://jaandrle.github.io/deka-dom-el/) on the documentation site. | ||||||
|  |  | ||||||
| ```html | ```html | ||||||
| <!-- Example with IIFE build (creates a global DDE object) --> | <!-- Example with IIFE build (creates a global DDE object) --> | ||||||
| @@ -101,18 +114,10 @@ format selector](https://jaandrle.github.io/deka-dom-el/#h-getting-started) on t | |||||||
| </script> | </script> | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ## Why Another Library? | ### Documentation | ||||||
|  |  | ||||||
| This library bridges the gap between minimal solutions like van/hyperscript and more comprehensive frameworks like | - [**Interactive Guide**](https://jaandrle.github.io/deka-dom-el) | ||||||
| [solid-js](https://github.com/solidjs/solid), offering a balanced trade-off between size, complexity, and usability. | - [**Examples**](https://jaandrle.github.io/deka-dom-el/p15-examples.html) | ||||||
|  |  | ||||||
| Following functional programming principles, dd\<el\> starts with pure JavaScript (DOM API) and gradually adds |  | ||||||
| auxiliary functions. These range from minor improvements to advanced features for building complete declarative |  | ||||||
| reactive UI templates. |  | ||||||
|  |  | ||||||
| A key advantage: any internal function (`assign`, `classListDeclarative`, `on`, `dispatchEvent`, `S`, etc.) can be used |  | ||||||
| independently while also working seamlessly together. This modular approach makes it easier to integrate the library |  | ||||||
| into existing projects. |  | ||||||
|  |  | ||||||
| ## Understanding Signals | ## Understanding Signals | ||||||
|  |  | ||||||
| @@ -138,9 +143,9 @@ get started, coding standards, commit guidelines, and the pull request process. | |||||||
| 	interfaces or HTML code. | 	interfaces or HTML code. | ||||||
| - [pota](https://pota.quack.uy/) — small and pluggable Reactive Web Renderer. It's compiler-less, includes an html | - [pota](https://pota.quack.uy/) — small and pluggable Reactive Web Renderer. It's compiler-less, includes an html | ||||||
| 	function, and a optimized babel preset in case you fancy JSX. | 	function, and a optimized babel preset in case you fancy JSX. | ||||||
|  | - [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) — | ||||||
|  | 	Functional DOM components without JSX/virtual DOM | ||||||
| - [TarekRaafat/eleva](https://github.com/TarekRaafat/eleva) — A minimalist, lightweight, pure vanilla JavaScript | - [TarekRaafat/eleva](https://github.com/TarekRaafat/eleva) — A minimalist, lightweight, pure vanilla JavaScript | ||||||
| 	frontend runtime framework. | 	frontend runtime framework. | ||||||
| - [didi/mpx](https://github.com/didi/mpx) — Mpx,一款具有优秀开发体验和深度性能优化的增强型跨端小程序框架 | - [didi/mpx](https://github.com/didi/mpx) — Mpx,一款具有优秀开发体验和深度性能优化的增强型跨端小程序框架 | ||||||
| - [mxjp/rvx](https://github.com/mxjp/rvx) — A signal based frontend framework | - [mxjp/rvx](https://github.com/mxjp/rvx) — A signal based frontend framework | ||||||
| - [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) — |  | ||||||
| 	Functional DOM components without JSX/virtual DOM (my old library) |  | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| /* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */// editorconfig-checker-disable-line | /* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */// editorconfig-checker-disable-line | ||||||
| echo("Building static documentation files…"); | echo("Building static documentation files…"); | ||||||
| echo("Preparing…"); | echo("Preparing…"); | ||||||
| import { path_target, pages as pages_registered, styles, currentPageId, dispatchEvent, t } from "../docs/ssr.js"; | import { path_target, pages as pages_registered, styles, dispatchEvent, t } from "../docs/ssr.js"; | ||||||
| import { createHTMl } from "./docs/jsdom.js"; | import { createHTMl } from "./docs/jsdom.js"; | ||||||
| import { register, queue } from "../jsdom.js"; | import { register, queue } from "../jsdom.js"; | ||||||
| const pkg= s.cat("package.json").xargs(JSON.parse); | const pkg= s.cat("package.json").xargs(JSON.parse); | ||||||
| @@ -28,7 +28,6 @@ for(const { id, info } of pages){ | |||||||
| 	); | 	); | ||||||
| 	const { el }= await register(serverDOM.dom); | 	const { el }= await register(serverDOM.dom); | ||||||
| 	const { page }= await import(`../docs/${id}.html.js`); | 	const { page }= await import(`../docs/${id}.html.js`); | ||||||
| 	currentPageId(id) |  | ||||||
| 	serverDOM.document.body.append( | 	serverDOM.document.body.append( | ||||||
| 		el(page, { pkg, info }), | 		el(page, { pkg, info }), | ||||||
| 	); | 	); | ||||||
|   | |||||||
| @@ -9,4 +9,3 @@ npx editorconfig-checker -format gcc ${additional} | |||||||
| npx jshint index.js src ${additional} | npx jshint index.js src ${additional} | ||||||
| [ "$one" = 'vim' ] && exit 0 | [ "$one" = 'vim' ] && exit 0 | ||||||
| npx size-limit | npx size-limit | ||||||
| npx publint |  | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								dist/esm-with-signals.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								dist/esm-with-signals.js
									
									
									
									
										vendored
									
									
								
							| @@ -41,11 +41,6 @@ function observedAttributes(instance, observedAttribute2) { | |||||||
| function kebabToCamel(name) { | function kebabToCamel(name) { | ||||||
| 	return name.replace(/-./g, (x) => x[1].toUpperCase()); | 	return name.replace(/-./g, (x) => x[1].toUpperCase()); | ||||||
| } | } | ||||||
| function requestIdle() { |  | ||||||
| 	return new Promise(function(resolve) { |  | ||||||
| 		(globalThis.requestIdleCallback || requestAnimationFrame)(resolve); |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // src/dom-lib/common.js | // src/dom-lib/common.js | ||||||
| var enviroment = { | var enviroment = { | ||||||
| @@ -184,6 +179,11 @@ function connectionsChangesObserverConstructor() { | |||||||
| 		is_observing = false; | 		is_observing = false; | ||||||
| 		observer.disconnect(); | 		observer.disconnect(); | ||||||
| 	} | 	} | ||||||
|  | 	function requestIdle() { | ||||||
|  | 		return new Promise(function(resolve) { | ||||||
|  | 			(requestIdleCallback || requestAnimationFrame)(resolve); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
| 	async function collectChildren(element) { | 	async function collectChildren(element) { | ||||||
| 		if (store.size > 30) | 		if (store.size > 30) | ||||||
| 			await requestIdle(); | 			await requestIdle(); | ||||||
| @@ -430,10 +430,9 @@ function createElement(tag, attributes, ...addons) { | |||||||
| 			scoped = 1; | 			scoped = 1; | ||||||
| 			const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0); | 			const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0); | ||||||
| 			scope.push({ scope: tag, host }); | 			scope.push({ scope: tag, host }); | ||||||
| 			el = /** @type {Element} */ | 			el = tag(attributes || void 0); | ||||||
| 			tag(attributes || void 0); |  | ||||||
| 			if (el.nodeName === "#comment") break; |  | ||||||
| 			const is_fragment = isInstance(el, enviroment.F); | 			const is_fragment = isInstance(el, enviroment.F); | ||||||
|  | 			if (el.nodeName === "#comment") break; | ||||||
| 			const el_mark = createElement.mark({ | 			const el_mark = createElement.mark({ | ||||||
| 				type: "component", | 				type: "component", | ||||||
| 				name: tag.name, | 				name: tag.name, | ||||||
| @@ -697,10 +696,13 @@ var queueSignalWrite = /* @__PURE__ */ (() => { | |||||||
| })(); | })(); | ||||||
|  |  | ||||||
| // src/signals-lib/signals-lib.js | // src/signals-lib/signals-lib.js | ||||||
| var SignalReadOnly = oCreate(null, { | var Signal = oCreate(null, { | ||||||
| 	get: { value() { | 	get: { value() { | ||||||
| 		return read(this); | 		return read(this); | ||||||
| 	} }, | 	} }, | ||||||
|  | 	set: { value(...v) { | ||||||
|  | 		return write(this, ...v); | ||||||
|  | 	} }, | ||||||
| 	toJSON: { value() { | 	toJSON: { value() { | ||||||
| 		return read(this); | 		return read(this); | ||||||
| 	} }, | 	} }, | ||||||
| @@ -708,9 +710,9 @@ var SignalReadOnly = oCreate(null, { | |||||||
| 		return this[mark] && this[mark].value; | 		return this[mark] && this[mark].value; | ||||||
| 	} } | 	} } | ||||||
| }); | }); | ||||||
| var Signal = oCreate(SignalReadOnly, { | var SignalReadOnly = oCreate(Signal, { | ||||||
| 	set: { value(...v) { | 	set: { value() { | ||||||
| 		return write(this, ...v); | 		return; | ||||||
| 	} } | 	} } | ||||||
| }); | }); | ||||||
| function isSignal(candidate) { | function isSignal(candidate) { | ||||||
| @@ -822,7 +824,7 @@ signal.el = function(s, map) { | |||||||
| }; | }; | ||||||
| function requestCleanUpReactives(host) { | function requestCleanUpReactives(host) { | ||||||
| 	if (!host || !host[key_reactive]) return; | 	if (!host || !host[key_reactive]) return; | ||||||
| 	requestIdle().then(function() { | 	(requestIdleCallback || setTimeout)(function() { | ||||||
| 		host[key_reactive] = host[key_reactive].filter(([s, el]) => el.isConnected ? true : (removeSignalListener(...s), false)); | 		host[key_reactive] = host[key_reactive].filter(([s, el]) => el.isConnected ? true : (removeSignalListener(...s), false)); | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| @@ -887,10 +889,9 @@ var signals_config = { | |||||||
| function removeSignalsFromElements(s, listener, ...notes) { | function removeSignalsFromElements(s, listener, ...notes) { | ||||||
| 	const { current } = scope; | 	const { current } = scope; | ||||||
| 	current.host(function(element) { | 	current.host(function(element) { | ||||||
| 		const is_first = !element[key_reactive]; | 		if (!element[key_reactive]) element[key_reactive] = []; | ||||||
| 		if (is_first) element[key_reactive] = []; |  | ||||||
| 		element[key_reactive].push([[s, listener], ...notes]); | 		element[key_reactive].push([[s, listener], ...notes]); | ||||||
| 		if (!is_first || current.prevent) return; | 		if (current.prevent) return; | ||||||
| 		on.disconnected( | 		on.disconnected( | ||||||
| 			() => ( | 			() => ( | ||||||
| 				/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, …?). | 				/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, …?). | ||||||
| @@ -905,7 +906,7 @@ var cleanUpRegistry = new FinalizationRegistry(function(s) { | |||||||
| }); | }); | ||||||
| function create(is_readonly, value, actions) { | function create(is_readonly, value, actions) { | ||||||
| 	const varS = oCreate(is_readonly ? SignalReadOnly : Signal); | 	const varS = oCreate(is_readonly ? SignalReadOnly : Signal); | ||||||
| 	const SI = toSignal(varS, value, actions); | 	const SI = toSignal(varS, value, actions, is_readonly); | ||||||
| 	cleanUpRegistry.register(SI, SI[mark]); | 	cleanUpRegistry.register(SI, SI[mark]); | ||||||
| 	return SI; | 	return SI; | ||||||
| } | } | ||||||
| @@ -917,7 +918,7 @@ var protoSigal = oAssign(oCreate(), { | |||||||
| 		this.skip = true; | 		this.skip = true; | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| function toSignal(s, value, actions) { | function toSignal(s, value, actions, readonly = false) { | ||||||
| 	const onclear = []; | 	const onclear = []; | ||||||
| 	if (typeOf(actions) !== "[object Object]") | 	if (typeOf(actions) !== "[object Object]") | ||||||
| 		actions = {}; | 		actions = {}; | ||||||
| @@ -933,7 +934,8 @@ function toSignal(s, value, actions) { | |||||||
| 			actions, | 			actions, | ||||||
| 			onclear, | 			onclear, | ||||||
| 			host, | 			host, | ||||||
| 			listeners: /* @__PURE__ */ new Set() | 			listeners: /* @__PURE__ */ new Set(), | ||||||
|  | 			readonly | ||||||
| 		}), | 		}), | ||||||
| 		enumerable: false, | 		enumerable: false, | ||||||
| 		writable: false, | 		writable: false, | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								dist/esm-with-signals.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								dist/esm-with-signals.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										15
									
								
								dist/esm.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								dist/esm.js
									
									
									
									
										vendored
									
									
								
							| @@ -25,11 +25,6 @@ function onAbort(signal, listener) { | |||||||
| 		signal.removeEventListener("abort", listener); | 		signal.removeEventListener("abort", listener); | ||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
| function requestIdle() { |  | ||||||
| 	return new Promise(function(resolve) { |  | ||||||
| 		(globalThis.requestIdleCallback || requestAnimationFrame)(resolve); |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // src/dom-lib/common.js | // src/dom-lib/common.js | ||||||
| var enviroment = { | var enviroment = { | ||||||
| @@ -168,6 +163,11 @@ function connectionsChangesObserverConstructor() { | |||||||
| 		is_observing = false; | 		is_observing = false; | ||||||
| 		observer.disconnect(); | 		observer.disconnect(); | ||||||
| 	} | 	} | ||||||
|  | 	function requestIdle() { | ||||||
|  | 		return new Promise(function(resolve) { | ||||||
|  | 			(requestIdleCallback || requestAnimationFrame)(resolve); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
| 	async function collectChildren(element) { | 	async function collectChildren(element) { | ||||||
| 		if (store.size > 30) | 		if (store.size > 30) | ||||||
| 			await requestIdle(); | 			await requestIdle(); | ||||||
| @@ -414,10 +414,9 @@ function createElement(tag, attributes, ...addons) { | |||||||
| 			scoped = 1; | 			scoped = 1; | ||||||
| 			const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0); | 			const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0); | ||||||
| 			scope.push({ scope: tag, host }); | 			scope.push({ scope: tag, host }); | ||||||
| 			el = /** @type {Element} */ | 			el = tag(attributes || void 0); | ||||||
| 			tag(attributes || void 0); |  | ||||||
| 			if (el.nodeName === "#comment") break; |  | ||||||
| 			const is_fragment = isInstance(el, enviroment.F); | 			const is_fragment = isInstance(el, enviroment.F); | ||||||
|  | 			if (el.nodeName === "#comment") break; | ||||||
| 			const el_mark = createElement.mark({ | 			const el_mark = createElement.mark({ | ||||||
| 				type: "component", | 				type: "component", | ||||||
| 				name: tag.name, | 				name: tag.name, | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								dist/esm.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/esm.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										40
									
								
								dist/iife-with-signals.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								dist/iife-with-signals.js
									
									
									
									
										vendored
									
									
								
							| @@ -86,11 +86,6 @@ var DDE = (() => { | |||||||
| 	function kebabToCamel(name) { | 	function kebabToCamel(name) { | ||||||
| 		return name.replace(/-./g, (x) => x[1].toUpperCase()); | 		return name.replace(/-./g, (x) => x[1].toUpperCase()); | ||||||
| 	} | 	} | ||||||
| 	function requestIdle() { |  | ||||||
| 		return new Promise(function(resolve) { |  | ||||||
| 			(globalThis.requestIdleCallback || requestAnimationFrame)(resolve); |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// src/dom-lib/common.js | 	// src/dom-lib/common.js | ||||||
| 	var enviroment = { | 	var enviroment = { | ||||||
| @@ -229,6 +224,11 @@ var DDE = (() => { | |||||||
| 			is_observing = false; | 			is_observing = false; | ||||||
| 			observer.disconnect(); | 			observer.disconnect(); | ||||||
| 		} | 		} | ||||||
|  | 		function requestIdle() { | ||||||
|  | 			return new Promise(function(resolve) { | ||||||
|  | 				(requestIdleCallback || requestAnimationFrame)(resolve); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
| 		async function collectChildren(element) { | 		async function collectChildren(element) { | ||||||
| 			if (store.size > 30) | 			if (store.size > 30) | ||||||
| 				await requestIdle(); | 				await requestIdle(); | ||||||
| @@ -475,10 +475,9 @@ var DDE = (() => { | |||||||
| 				scoped = 1; | 				scoped = 1; | ||||||
| 				const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0); | 				const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0); | ||||||
| 				scope.push({ scope: tag, host }); | 				scope.push({ scope: tag, host }); | ||||||
| 				el = /** @type {Element} */ | 				el = tag(attributes || void 0); | ||||||
| 				tag(attributes || void 0); |  | ||||||
| 				if (el.nodeName === "#comment") break; |  | ||||||
| 				const is_fragment = isInstance(el, enviroment.F); | 				const is_fragment = isInstance(el, enviroment.F); | ||||||
|  | 				if (el.nodeName === "#comment") break; | ||||||
| 				const el_mark = createElement.mark({ | 				const el_mark = createElement.mark({ | ||||||
| 					type: "component", | 					type: "component", | ||||||
| 					name: tag.name, | 					name: tag.name, | ||||||
| @@ -742,10 +741,13 @@ var DDE = (() => { | |||||||
| 	})(); | 	})(); | ||||||
|  |  | ||||||
| 	// src/signals-lib/signals-lib.js | 	// src/signals-lib/signals-lib.js | ||||||
| 	var SignalReadOnly = oCreate(null, { | 	var Signal = oCreate(null, { | ||||||
| 		get: { value() { | 		get: { value() { | ||||||
| 			return read(this); | 			return read(this); | ||||||
| 		} }, | 		} }, | ||||||
|  | 		set: { value(...v) { | ||||||
|  | 			return write(this, ...v); | ||||||
|  | 		} }, | ||||||
| 		toJSON: { value() { | 		toJSON: { value() { | ||||||
| 			return read(this); | 			return read(this); | ||||||
| 		} }, | 		} }, | ||||||
| @@ -753,9 +755,9 @@ var DDE = (() => { | |||||||
| 			return this[mark] && this[mark].value; | 			return this[mark] && this[mark].value; | ||||||
| 		} } | 		} } | ||||||
| 	}); | 	}); | ||||||
| 	var Signal = oCreate(SignalReadOnly, { | 	var SignalReadOnly = oCreate(Signal, { | ||||||
| 		set: { value(...v) { | 		set: { value() { | ||||||
| 			return write(this, ...v); | 			return; | ||||||
| 		} } | 		} } | ||||||
| 	}); | 	}); | ||||||
| 	function isSignal(candidate) { | 	function isSignal(candidate) { | ||||||
| @@ -867,7 +869,7 @@ var DDE = (() => { | |||||||
| 	}; | 	}; | ||||||
| 	function requestCleanUpReactives(host) { | 	function requestCleanUpReactives(host) { | ||||||
| 		if (!host || !host[key_reactive]) return; | 		if (!host || !host[key_reactive]) return; | ||||||
| 		requestIdle().then(function() { | 		(requestIdleCallback || setTimeout)(function() { | ||||||
| 			host[key_reactive] = host[key_reactive].filter(([s, el]) => el.isConnected ? true : (removeSignalListener(...s), false)); | 			host[key_reactive] = host[key_reactive].filter(([s, el]) => el.isConnected ? true : (removeSignalListener(...s), false)); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| @@ -932,10 +934,9 @@ var DDE = (() => { | |||||||
| 	function removeSignalsFromElements(s, listener, ...notes) { | 	function removeSignalsFromElements(s, listener, ...notes) { | ||||||
| 		const { current } = scope; | 		const { current } = scope; | ||||||
| 		current.host(function(element) { | 		current.host(function(element) { | ||||||
| 			const is_first = !element[key_reactive]; | 			if (!element[key_reactive]) element[key_reactive] = []; | ||||||
| 			if (is_first) element[key_reactive] = []; |  | ||||||
| 			element[key_reactive].push([[s, listener], ...notes]); | 			element[key_reactive].push([[s, listener], ...notes]); | ||||||
| 			if (!is_first || current.prevent) return; | 			if (current.prevent) return; | ||||||
| 			on.disconnected( | 			on.disconnected( | ||||||
| 				() => ( | 				() => ( | ||||||
| 					/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, …?). | 					/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, …?). | ||||||
| @@ -950,7 +951,7 @@ var DDE = (() => { | |||||||
| 	}); | 	}); | ||||||
| 	function create(is_readonly, value, actions) { | 	function create(is_readonly, value, actions) { | ||||||
| 		const varS = oCreate(is_readonly ? SignalReadOnly : Signal); | 		const varS = oCreate(is_readonly ? SignalReadOnly : Signal); | ||||||
| 		const SI = toSignal(varS, value, actions); | 		const SI = toSignal(varS, value, actions, is_readonly); | ||||||
| 		cleanUpRegistry.register(SI, SI[mark]); | 		cleanUpRegistry.register(SI, SI[mark]); | ||||||
| 		return SI; | 		return SI; | ||||||
| 	} | 	} | ||||||
| @@ -962,7 +963,7 @@ var DDE = (() => { | |||||||
| 			this.skip = true; | 			this.skip = true; | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
| 	function toSignal(s, value, actions) { | 	function toSignal(s, value, actions, readonly = false) { | ||||||
| 		const onclear = []; | 		const onclear = []; | ||||||
| 		if (typeOf(actions) !== "[object Object]") | 		if (typeOf(actions) !== "[object Object]") | ||||||
| 			actions = {}; | 			actions = {}; | ||||||
| @@ -978,7 +979,8 @@ var DDE = (() => { | |||||||
| 				actions, | 				actions, | ||||||
| 				onclear, | 				onclear, | ||||||
| 				host, | 				host, | ||||||
| 				listeners: /* @__PURE__ */ new Set() | 				listeners: /* @__PURE__ */ new Set(), | ||||||
|  | 				readonly | ||||||
| 			}), | 			}), | ||||||
| 			enumerable: false, | 			enumerable: false, | ||||||
| 			writable: false, | 			writable: false, | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								dist/iife-with-signals.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								dist/iife-with-signals.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										15
									
								
								dist/iife.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								dist/iife.js
									
									
									
									
										vendored
									
									
								
							| @@ -67,11 +67,6 @@ var DDE = (() => { | |||||||
| 			signal.removeEventListener("abort", listener); | 			signal.removeEventListener("abort", listener); | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
| 	function requestIdle() { |  | ||||||
| 		return new Promise(function(resolve) { |  | ||||||
| 			(globalThis.requestIdleCallback || requestAnimationFrame)(resolve); |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// src/dom-lib/common.js | 	// src/dom-lib/common.js | ||||||
| 	var enviroment = { | 	var enviroment = { | ||||||
| @@ -210,6 +205,11 @@ var DDE = (() => { | |||||||
| 			is_observing = false; | 			is_observing = false; | ||||||
| 			observer.disconnect(); | 			observer.disconnect(); | ||||||
| 		} | 		} | ||||||
|  | 		function requestIdle() { | ||||||
|  | 			return new Promise(function(resolve) { | ||||||
|  | 				(requestIdleCallback || requestAnimationFrame)(resolve); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
| 		async function collectChildren(element) { | 		async function collectChildren(element) { | ||||||
| 			if (store.size > 30) | 			if (store.size > 30) | ||||||
| 				await requestIdle(); | 				await requestIdle(); | ||||||
| @@ -456,10 +456,9 @@ var DDE = (() => { | |||||||
| 				scoped = 1; | 				scoped = 1; | ||||||
| 				const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0); | 				const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0); | ||||||
| 				scope.push({ scope: tag, host }); | 				scope.push({ scope: tag, host }); | ||||||
| 				el = /** @type {Element} */ | 				el = tag(attributes || void 0); | ||||||
| 				tag(attributes || void 0); |  | ||||||
| 				if (el.nodeName === "#comment") break; |  | ||||||
| 				const is_fragment = isInstance(el, enviroment.F); | 				const is_fragment = isInstance(el, enviroment.F); | ||||||
|  | 				if (el.nodeName === "#comment") break; | ||||||
| 				const el_mark = createElement.mark({ | 				const el_mark = createElement.mark({ | ||||||
| 					type: "component", | 					type: "component", | ||||||
| 					name: tag.name, | 					name: tag.name, | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								dist/iife.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/iife.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| import { page_id, registerClientFile, styles } from "../ssr.js"; | import { registerClientFile, styles } from "../ssr.js"; | ||||||
| const host= "."+code.name; | const host= "."+code.name; | ||||||
| styles.css` | styles.css` | ||||||
| /* Code block styling */ | /* Code block styling */ | ||||||
| @@ -177,9 +177,6 @@ ${host}:hover .copy-button { | |||||||
| } | } | ||||||
| `; | `; | ||||||
| import { el } from "deka-dom-el"; | import { el } from "deka-dom-el"; | ||||||
| /** |  | ||||||
|  * @typedef {"js"|"ts"|"html"|"css"|"shell"|"-"} Language |  | ||||||
|  * */ |  | ||||||
| /** | /** | ||||||
|  * Prints code to the page and registers flems to make it interactive. |  * Prints code to the page and registers flems to make it interactive. | ||||||
|  * @param {object} attrs |  * @param {object} attrs | ||||||
| @@ -187,17 +184,15 @@ import { el } from "deka-dom-el"; | |||||||
|  * @param {string} [attrs.className] |  * @param {string} [attrs.className] | ||||||
|  * @param {URL} [attrs.src] Example code file path |  * @param {URL} [attrs.src] Example code file path | ||||||
|  * @param {string} [attrs.content] Example code |  * @param {string} [attrs.content] Example code | ||||||
|  * @param {Language} [attrs.language="-s"] Language of the code |  * @param {"js"|"ts"|"html"|"css"|"shell"} [attrs.language="js"] Language of the code | ||||||
|  |  * @param {string} [attrs.page_id] ID of the page, if setted it registers shiki | ||||||
|  * */ |  * */ | ||||||
| export function code({ id, src, content, language= "-", className= host.slice(1) }){ | export function code({ id, src, content, language= "js", className= host.slice(1), page_id }){ | ||||||
| 	if(src){ | 	if(src) content= s.cat(src); | ||||||
| 		content= s.cat(src); |  | ||||||
| 		if(language=== "-") language= /** @type {Language} */(src.pathname.split(".").pop()); |  | ||||||
| 	} |  | ||||||
| 	content= normalizeIndentation(content); | 	content= normalizeIndentation(content); | ||||||
| 	let dataJS; | 	let dataJS; | ||||||
| 	if(language!== "-"){ | 	if(page_id){ | ||||||
| 		registerClientPart(); | 		registerClientPart(page_id); | ||||||
| 		dataJS= "todo"; | 		dataJS= "todo"; | ||||||
| 	} | 	} | ||||||
| 	return el("div", { id, className, dataJS, tabIndex: 0 }).append( | 	return el("div", { id, className, dataJS, tabIndex: 0 }).append( | ||||||
| @@ -209,7 +204,8 @@ export function pre({ content }){ | |||||||
| 	return el("pre").append(el("code", content.trim())); | 	return el("pre").append(el("code", content.trim())); | ||||||
| } | } | ||||||
| let is_registered= {}; | let is_registered= {}; | ||||||
| function registerClientPart(){ | /** @param {string} page_id */ | ||||||
|  | function registerClientPart(page_id){ | ||||||
| 	if(is_registered[page_id]) return; | 	if(is_registered[page_id]) return; | ||||||
|  |  | ||||||
| 	// Add Shiki with a more reliable loading method | 	// Add Shiki with a more reliable loading method | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { page_id, styles } from "../ssr.js"; | import { styles } from "../ssr.js"; | ||||||
|  |  | ||||||
| styles.css` | styles.css` | ||||||
| #html-to-dde-converter { | #html-to-dde-converter { | ||||||
| @@ -149,11 +149,12 @@ import { ireland } from "./ireland.html.js"; | |||||||
| import { el } from "deka-dom-el"; | import { el } from "deka-dom-el"; | ||||||
| const fileURL= url=> new URL(url, import.meta.url); | const fileURL= url=> new URL(url, import.meta.url); | ||||||
|  |  | ||||||
| export function converter(){ | export function converter({ page_id }){ | ||||||
| 	registerClientPart(page_id); | 	registerClientPart(page_id); | ||||||
| 	return el(ireland, { | 	return el(ireland, { | ||||||
| 		src: fileURL("./converter.js.js"), | 		src: fileURL("./converter.js.js"), | ||||||
| 		exportName: "converter", | 		exportName: "converter", | ||||||
|  | 		page_id, | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { page_id, styles } from "../ssr.js"; | import { styles } from "../ssr.js"; | ||||||
| const host= "."+example.name; | const host= "."+example.name; | ||||||
| styles.css` | styles.css` | ||||||
| ${host} { | ${host} { | ||||||
| @@ -119,8 +119,9 @@ import { relative } from "node:path"; | |||||||
|  * @param {URL} attrs.src Example code file path |  * @param {URL} attrs.src Example code file path | ||||||
|  * @param {"js"|"ts"|"html"|"css"} [attrs.language="js"] Language of the code |  * @param {"js"|"ts"|"html"|"css"} [attrs.language="js"] Language of the code | ||||||
|  * @param {"normal"|"big"} [attrs.variant="normal"] Size of the example |  * @param {"normal"|"big"} [attrs.variant="normal"] Size of the example | ||||||
|  |  * @param {string} attrs.page_id ID of the page | ||||||
|  * */ |  * */ | ||||||
| export function example({ src, language= "js", variant= "normal" }){ | export function example({ src, language= "js", variant= "normal", page_id }){ | ||||||
| 	registerClientPart(page_id); | 	registerClientPart(page_id); | ||||||
| 	const content= s.cat(src).toString() | 	const content= s.cat(src).toString() | ||||||
| 		.replaceAll(/ from "deka-dom-el(\/signals)?";/g, ' from "./esm-with-signals.js";'); | 		.replaceAll(/ from "deka-dom-el(\/signals)?";/g, ' from "./esm-with-signals.js";'); | ||||||
|   | |||||||
| @@ -303,7 +303,6 @@ document.body.append( | |||||||
| 				padding: 1rem; | 				padding: 1rem; | ||||||
| 				margin-bottom: 1.5rem; | 				margin-bottom: 1.5rem; | ||||||
| 				box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); | 				box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); | ||||||
| 				overflow: auto; |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			.loading-spinner { | 			.loading-spinner { | ||||||
|   | |||||||
| @@ -82,10 +82,10 @@ export function ImageGallery(images= imagesSample) { | |||||||
| 				closeLightbox(); | 				closeLightbox(); | ||||||
| 				break; | 				break; | ||||||
| 			case 'ArrowLeft': | 			case 'ArrowLeft': | ||||||
| 				onPrevImage(e); | 				document.querySelector('.lightbox-prev-btn').click(); | ||||||
| 				break; | 				break; | ||||||
| 			case 'ArrowRight': | 			case 'ArrowRight': | ||||||
| 				onNextImage(e); | 				document.querySelector('.lightbox-next-btn').click(); | ||||||
| 				break; | 				break; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -126,12 +126,12 @@ export function ImageGallery(images= imagesSample) { | |||||||
| 						el("div", { | 						el("div", { | ||||||
| 							className: "gallery-item", | 							className: "gallery-item", | ||||||
| 							dataTag: image.alt.toLowerCase() | 							dataTag: image.alt.toLowerCase() | ||||||
| 						}, onImageClick(image.id)).append( | 						}).append( | ||||||
| 							el("img", { | 							el("img", { | ||||||
| 								src: image.src, | 								src: image.src, | ||||||
| 								alt: image.alt, | 								alt: image.alt, | ||||||
| 								loading: "lazy" | 								loading: "lazy" | ||||||
| 							}), | 							}, onImageClick(image.id)), | ||||||
| 							el("div", { className: "gallery-item-caption" }).append( | 							el("div", { className: "gallery-item-caption" }).append( | ||||||
| 								el("h3", image.title), | 								el("h3", image.title), | ||||||
| 								el("p", image.alt) | 								el("p", image.alt) | ||||||
| @@ -146,41 +146,41 @@ export function ImageGallery(images= imagesSample) { | |||||||
| 		S.el(isLightboxOpen, open => !open | 		S.el(isLightboxOpen, open => !open | ||||||
| 			? el() | 			? el() | ||||||
| 			: el("div", { className: "lightbox-overlay" }, on("click", closeLightbox)).append( | 			: el("div", { className: "lightbox-overlay" }, on("click", closeLightbox)).append( | ||||||
| 				el("div", { | 					el("div", { | ||||||
| 					className: "lightbox-content", | 						className: "lightbox-content", | ||||||
| 					onClick: e => e.stopPropagation() // Prevent closing when clicking inside | 						onClick: e => e.stopPropagation() // Prevent closing when clicking inside | ||||||
| 				}).append( | 					}).append( | ||||||
| 					el("button", { | 						el("button", { | ||||||
| 						className: "lightbox-close-btn", | 							className: "lightbox-close-btn", | ||||||
| 						ariaLabel: "Close lightbox", | 							"aria-label": "Close lightbox" | ||||||
| 					}, on("click", closeLightbox)).append("×"), | 						}, on("click", closeLightbox)).append("×"), | ||||||
|  |  | ||||||
| 					el("button", { | 						el("button", { | ||||||
| 						className: "lightbox-prev-btn", | 							className: "lightbox-prev-btn", | ||||||
| 						ariaLabel: "Previous image", | 							"aria-label": "Previous image" | ||||||
| 					}, on("click", onPrevImage)).append("❮"), | 						}, on("click", onPrevImage)).append("❮"), | ||||||
|  |  | ||||||
| 					el("button", { | 						el("button", { | ||||||
| 						className: "lightbox-next-btn", | 							className: "lightbox-next-btn", | ||||||
| 						ariaLabel: "Next image", | 							"aria-label": "Next image" | ||||||
| 					}, on("click", onNextImage)).append("❯"), | 						}, on("click", onNextImage)).append("❯"), | ||||||
|  |  | ||||||
| 					S.el(selectedImage, img => !img | 						S.el(selectedImage, img => !img | ||||||
| 						? el() | 							? el() | ||||||
| 						: el("div", { className: "lightbox-image-container" }).append( | 							: el("div", { className: "lightbox-image-container" }).append( | ||||||
| 							el("img", { | 								el("img", { | ||||||
| 								src: img.src, | 									src: img.src, | ||||||
| 								alt: img.alt, | 									alt: img.alt, | ||||||
| 								className: "lightbox-image", | 									className: "lightbox-image" | ||||||
| 							}), | 								}), | ||||||
| 							el("div", { className: "lightbox-caption" }).append( | 								el("div", { className: "lightbox-caption" }).append( | ||||||
| 								el("h2", img.title), | 									el("h2", img.title), | ||||||
| 								el("p", img.alt), | 									el("p", img.alt) | ||||||
| 							), | 								) | ||||||
|  | 							) | ||||||
| 						) | 						) | ||||||
| 					), | 					) | ||||||
| 				), | 				) | ||||||
| 			), |  | ||||||
| 		), | 		), | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,509 +0,0 @@ | |||||||
| import { el, on, scope } from "deka-dom-el"; |  | ||||||
| import { S } from "deka-dom-el/signals"; |  | ||||||
|  |  | ||||||
| export function ProductCatalog() { |  | ||||||
| 	const { signal }= scope; |  | ||||||
|  |  | ||||||
| 	const itemsPerPage = 5; |  | ||||||
| 	const products = asyncSignal(S, |  | ||||||
| 		fetchProducts, |  | ||||||
| 		{ initial: [], keepLast: true, signal }); |  | ||||||
| 	const searchTerm = S(""); |  | ||||||
| 	const handleSearch = (e) => searchTerm.set(e.target.value); |  | ||||||
| 	const sortOrder = S("default"); |  | ||||||
| 	const handleSort = (e) => sortOrder.set(e.target.value); |  | ||||||
| 	const page = S(1); |  | ||||||
| 	const handlePageChange = (newPage) => page.set(newPage); |  | ||||||
| 	const resetFilters = () => { |  | ||||||
| 		searchTerm.set(""); |  | ||||||
| 		sortOrder.set("default"); |  | ||||||
| 		page.set(1); |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	const filteredProducts = S(() => { |  | ||||||
| 		if (products.status.get() !== "resolved") return []; |  | ||||||
|  |  | ||||||
| 		const results = products.result.get().filter(product => |  | ||||||
| 			product.title.toLowerCase().includes(searchTerm.get().toLowerCase()) || |  | ||||||
| 			product.description.toLowerCase().includes(searchTerm.get().toLowerCase()) |  | ||||||
| 		); |  | ||||||
|  |  | ||||||
| 		return [...results].sort((a, b) => { |  | ||||||
| 			const order = sortOrder.get(); |  | ||||||
| 			if (order === "price-asc") return a.price - b.price; |  | ||||||
| 			if (order === "price-desc") return b.price - a.price; |  | ||||||
| 			if (order === "rating") return b.rating - a.rating; |  | ||||||
| 			return 0; // default: no sorting |  | ||||||
| 		}); |  | ||||||
| 	}); |  | ||||||
| 	const totalPages = S(() => Math.ceil(filteredProducts.get().length / itemsPerPage)); |  | ||||||
| 	const paginatedProducts = S(() => { |  | ||||||
| 		const currentPage = page.get(); |  | ||||||
| 		const filtered = filteredProducts.get(); |  | ||||||
| 		const start = (currentPage - 1) * itemsPerPage; |  | ||||||
| 		return filtered.slice(start, start + itemsPerPage); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	// Component structure |  | ||||||
| 	return el("div", { className: "product-catalog" }).append( |  | ||||||
| 		el("header", { className: "catalog-header" }).append( |  | ||||||
| 			el("h2", "Product Catalog"), |  | ||||||
| 			el("div", { className: "toolbar" }).append( |  | ||||||
| 				el("button", { |  | ||||||
| 					className: "refresh-btn", |  | ||||||
| 					textContent: "Refresh Products", |  | ||||||
| 					type: "button", |  | ||||||
| 					onclick: () => products.invoke(), |  | ||||||
| 				}), |  | ||||||
| 				el("button", { |  | ||||||
| 					className: "reset-btn", |  | ||||||
| 					textContent: "Reset Filters", |  | ||||||
| 					type: "button", |  | ||||||
| 					onclick: resetFilters, |  | ||||||
| 				}) |  | ||||||
| 			) |  | ||||||
| 		), |  | ||||||
|  |  | ||||||
| 		// Search and filter controls |  | ||||||
| 		el("div", { className: "controls" }).append( |  | ||||||
| 			el("div", { className: "search-box" }).append( |  | ||||||
| 				el("input", { |  | ||||||
| 					type: "search", |  | ||||||
| 					placeholder: "Search products...", |  | ||||||
| 					value: searchTerm, |  | ||||||
| 					oninput: handleSearch, |  | ||||||
| 				}) |  | ||||||
| 			), |  | ||||||
| 			el("div", { className: "sort-options" }).append( |  | ||||||
| 				el("label", "Sort by: "), |  | ||||||
| 				el("select", { onchange: handleSort }, on.defer(el => el.value = sortOrder.get())).append( |  | ||||||
| 					el("option", { value: "default", textContent: "Default" }), |  | ||||||
| 					el("option", { value: "price-asc", textContent: "Price: Low to High" }), |  | ||||||
| 					el("option", { value: "price-desc", textContent: "Price: High to Low" }), |  | ||||||
| 					el("option", { value: "rating", textContent: "Top Rated" }) |  | ||||||
| 				) |  | ||||||
| 			) |  | ||||||
| 		), |  | ||||||
|  |  | ||||||
| 		// Status indicators |  | ||||||
| 		el("div", { className: "status-container" }).append( |  | ||||||
| 			S.el(products.status, status => |  | ||||||
| 				status === "pending" ? |  | ||||||
| 					el("div", { className: "loader" }).append( |  | ||||||
| 						el("div", { className: "spinner" }), |  | ||||||
| 						el("p", "Loading products...") |  | ||||||
| 					) |  | ||||||
| 				: status === "rejected" ? |  | ||||||
| 					el("div", { className: "error-message" }).append( |  | ||||||
| 						el("p", products.error.get().message), |  | ||||||
| 						el("button", { |  | ||||||
| 							textContent: "Try Again", |  | ||||||
| 							onclick: () => products.invoke() |  | ||||||
| 						}) |  | ||||||
| 					) |  | ||||||
| 				: el() |  | ||||||
| 			) |  | ||||||
| 		), |  | ||||||
|  |  | ||||||
| 		// Results count |  | ||||||
| 		S.el(S(()=> [filteredProducts.get(), searchTerm.get()]), ([filtered, term]) => |  | ||||||
| 			products.status.get() === "resolved" |  | ||||||
| 				? el("div", { |  | ||||||
| 					className: "results-info", |  | ||||||
| 					textContent: term ? |  | ||||||
| 							`Found ${filtered.length} products matching "${term}"` |  | ||||||
| 							: `Showing all ${filtered.length} products` |  | ||||||
| 				}) |  | ||||||
| 				: el() |  | ||||||
| 		), |  | ||||||
|  |  | ||||||
| 		// Products grid |  | ||||||
| 		el("div", { className: "products-grid" }).append( |  | ||||||
| 			S.el(paginatedProducts, paginatedItems => |  | ||||||
| 				products.status.get() === "resolved" && paginatedItems.length > 0 ? |  | ||||||
| 					paginatedItems.map(product => el(ProductCard, { product })) |  | ||||||
| 					: products.status.get() === "resolved" && paginatedItems.length === 0 ? |  | ||||||
| 						el("p", { className: "no-results", textContent: "No products found matching your criteria." }) |  | ||||||
| 						: el() |  | ||||||
| 			) |  | ||||||
| 		), |  | ||||||
|  |  | ||||||
| 		// Pagination |  | ||||||
| 		S.el(S(()=> [totalPages.get(), page.get()]), ([total, current]) => |  | ||||||
| 			products.status.get() === "resolved" && total > 1 ? |  | ||||||
| 				el("div", { className: "pagination" }).append( |  | ||||||
| 					el("button", { |  | ||||||
| 						textContent: "Previous", |  | ||||||
| 						disabled: current === 1, |  | ||||||
| 						onclick: () => handlePageChange(current - 1) |  | ||||||
| 					}), |  | ||||||
| 					...Array.from({ length: total }, (_, i) => i + 1).map(num => |  | ||||||
| 						el("button", { |  | ||||||
| 							className: num === current ? "current-page" : "", |  | ||||||
| 							textContent: num, |  | ||||||
| 							onclick: () => handlePageChange(num) |  | ||||||
| 						}) |  | ||||||
| 					), |  | ||||||
| 					el("button", { |  | ||||||
| 						textContent: "Next", |  | ||||||
| 						disabled: current === total, |  | ||||||
| 						onclick: () => handlePageChange(current + 1) |  | ||||||
| 					}) |  | ||||||
| 				) |  | ||||||
| 				: el() |  | ||||||
| 		) |  | ||||||
| 	); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Product card component |  | ||||||
| function ProductCard({ product }) { |  | ||||||
| 	const showDetails = S(false); |  | ||||||
|  |  | ||||||
| 	return el("div", { className: "product-card" }).append( |  | ||||||
| 		el("div", { className: "product-image" }).append( |  | ||||||
| 			el("img", { src: product.thumbnail, alt: product.title }) |  | ||||||
| 		), |  | ||||||
| 		el("div", { className: "product-info" }).append( |  | ||||||
| 			el("h3", { className: "product-title", textContent: product.title }), |  | ||||||
| 			el("div", { className: "product-price-rating" }).append( |  | ||||||
| 				el("span", { className: "product-price", textContent: `$${product.price.toFixed(2)}` }), |  | ||||||
| 				el("span", { className: "product-rating" }).append( |  | ||||||
| 					el("span", { className: "stars", textContent: "★".repeat(Math.round(product.rating)) }), |  | ||||||
| 					el("span", { className: "rating-value", textContent: `(${product.rating})` }), |  | ||||||
| 				) |  | ||||||
| 			), |  | ||||||
| 			el("p", { className: "product-category", textContent: `Category: ${product.category}` }), |  | ||||||
| 			S.el(showDetails, details => |  | ||||||
| 				details ? |  | ||||||
| 					el("div", { className: "product-details" }).append( |  | ||||||
| 						el("p", { className: "product-description", textContent: product.description }), |  | ||||||
| 						el("div", { className: "product-meta" }).append( |  | ||||||
| 							el("p", `Brand: ${product.brand}`), |  | ||||||
| 							el("p", `Stock: ${product.stock} units`), |  | ||||||
| 							el("p", `Discount: ${product.discountPercentage}%`) |  | ||||||
| 						) |  | ||||||
| 					) |  | ||||||
| 					: el() |  | ||||||
| 			), |  | ||||||
| 			el("div", { className: "product-actions" }).append( |  | ||||||
| 				el("button", { |  | ||||||
| 					className: "details-btn", |  | ||||||
| 					textContent: S(() => showDetails.get() ? "Hide Details" : "Show Details"), |  | ||||||
| 					onclick: () => showDetails.set(!showDetails.get()) |  | ||||||
| 				}), |  | ||||||
| 				el("button", { |  | ||||||
| 					className: "add-to-cart-btn", |  | ||||||
| 					textContent: "Add to Cart" |  | ||||||
| 				}) |  | ||||||
| 			) |  | ||||||
| 		) |  | ||||||
| 	); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Data fetching function |  | ||||||
| async function fetchProducts({ signal }) { |  | ||||||
| 	await simulateNetworkDelay(); |  | ||||||
| 	// Simulate random errors for demonstration |  | ||||||
| 	if (Math.random() > 0.9) throw new Error("Failed to load products. Network error."); |  | ||||||
|  |  | ||||||
| 	const response = await fetch("https://dummyjson.com/products", { signal }); |  | ||||||
| 	if (!response.ok) throw new Error(`API error: ${response.status}`); |  | ||||||
|  |  | ||||||
| 	const data = await response.json(); |  | ||||||
| 	return data.products.slice(0, 20); // Limit to 20 products for the demo |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Utility for simulating network latency |  | ||||||
| function simulateNetworkDelay(min = 300, max = 1200) { |  | ||||||
| 	const delay = Math.floor(Math.random() * (max - min + 1)) + min; |  | ||||||
| 	return new Promise(resolve => setTimeout(resolve, delay)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Custom hook for async data fetching with signals |  | ||||||
|  * @template T |  | ||||||
|  * @param {typeof S} S - Signal constructor |  | ||||||
|  * @param {(params: { signal: AbortSignal }) => Promise<T>} invoker - Async function to execute |  | ||||||
|  * @param {{ initial?: T, keepLast?: boolean, signal?: AbortSignal }} options - Configuration options |  | ||||||
|  * @returns {Object} Status signals and control methods |  | ||||||
|  */ |  | ||||||
| export function asyncSignal(S, invoker, { initial, keepLast, signal } = {}) { |  | ||||||
| 	/** @type {(s: AbortSignal) => AbortSignal} */ |  | ||||||
| 	const anySignal = !signal || !AbortSignal.any // TODO: make better |  | ||||||
| 		? s=> s |  | ||||||
| 		: s=> AbortSignal.any([s, signal]); |  | ||||||
| 	// Status tracking signals |  | ||||||
| 	const status = S("pending"); |  | ||||||
| 	const result = S(initial); |  | ||||||
| 	const error = S(null); |  | ||||||
| 	let controller = null; |  | ||||||
|  |  | ||||||
| 	// Function to trigger data fetching |  | ||||||
| 	async function invoke() { |  | ||||||
| 		// Cancel any in-flight request |  | ||||||
| 		if (controller) controller.abort(); |  | ||||||
| 		controller = new AbortController(); |  | ||||||
|  |  | ||||||
| 		status.set("pending"); |  | ||||||
| 		error.set(null); |  | ||||||
| 		if (!keepLast) result.set(initial); |  | ||||||
|  |  | ||||||
| 		try { |  | ||||||
| 			const data = await invoker({ |  | ||||||
| 				signal: anySignal(controller.signal), |  | ||||||
| 			}); |  | ||||||
| 			if (!controller.signal.aborted) { |  | ||||||
| 				status.set("resolved"); |  | ||||||
| 				result.set(data); |  | ||||||
| 			} |  | ||||||
| 		} catch (e) { |  | ||||||
| 			if (e.name !== "AbortError") { |  | ||||||
| 				error.set(e); |  | ||||||
| 				status.set("rejected"); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Initial data fetch |  | ||||||
| 	invoke(); |  | ||||||
|  |  | ||||||
| 	return { status, result, error, invoke }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Initialize the component |  | ||||||
| document.body.append( |  | ||||||
| 	el(ProductCatalog), |  | ||||||
| 	el("style", ` |  | ||||||
| 		.product-catalog { |  | ||||||
| 			font-family: system-ui, -apple-system, sans-serif; |  | ||||||
| 			max-width: 1200px; |  | ||||||
| 			margin: 0 auto; |  | ||||||
| 			padding: 20px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.catalog-header { |  | ||||||
| 			display: flex; |  | ||||||
| 			justify-content: space-between; |  | ||||||
| 			align-items: center; |  | ||||||
| 			margin-bottom: 20px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.toolbar button { |  | ||||||
| 			margin-left: 10px; |  | ||||||
| 			padding: 8px 12px; |  | ||||||
| 			border-radius: 4px; |  | ||||||
| 			border: none; |  | ||||||
| 			background: #4a6cf7; |  | ||||||
| 			color: white; |  | ||||||
| 			cursor: pointer; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.controls { |  | ||||||
| 			display: flex; |  | ||||||
| 			justify-content: space-between; |  | ||||||
| 			margin-bottom: 20px; |  | ||||||
| 			gap: 15px; |  | ||||||
| 			flex-wrap: wrap; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.search-box input { |  | ||||||
| 			padding: 8px 12px; |  | ||||||
| 			border: 1px solid #ddd; |  | ||||||
| 			border-radius: 4px; |  | ||||||
| 			width: 300px; |  | ||||||
| 			max-width: 100%; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.sort-options select { |  | ||||||
| 			padding: 8px 12px; |  | ||||||
| 			border: 1px solid #ddd; |  | ||||||
| 			border-radius: 4px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.loader { |  | ||||||
| 			text-align: center; |  | ||||||
| 			padding: 40px 0; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.spinner { |  | ||||||
| 			display: inline-block; |  | ||||||
| 			width: 40px; |  | ||||||
| 			height: 40px; |  | ||||||
| 			border: 4px solid rgba(0, 0, 0, 0.1); |  | ||||||
| 			border-left-color: #4a6cf7; |  | ||||||
| 			border-radius: 50%; |  | ||||||
| 			animation: spin 1s linear infinite; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		@keyframes spin { |  | ||||||
| 			to { transform: rotate(360deg); } |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.error-message { |  | ||||||
| 			background: #ffebee; |  | ||||||
| 			color: #c62828; |  | ||||||
| 			padding: 15px; |  | ||||||
| 			border-radius: 4px; |  | ||||||
| 			margin: 20px 0; |  | ||||||
| 			text-align: center; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.results-info { |  | ||||||
| 			margin-bottom: 15px; |  | ||||||
| 			color: #666; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.products-grid { |  | ||||||
| 			display: grid; |  | ||||||
| 			grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); |  | ||||||
| 			gap: 20px; |  | ||||||
| 			margin-bottom: 30px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.product-card { |  | ||||||
| 			border: 1px solid #eee; |  | ||||||
| 			border-radius: 8px; |  | ||||||
| 			overflow: hidden; |  | ||||||
| 			transition: transform 0.2s, box-shadow 0.2s; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.product-card:hover { |  | ||||||
| 			transform: translateY(-5px); |  | ||||||
| 			box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.product-image img { |  | ||||||
| 			width: 100%; |  | ||||||
| 			height: 180px; |  | ||||||
| 			object-fit: cover; |  | ||||||
| 			display: block; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.product-info { |  | ||||||
| 			padding: 15px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.product-title { |  | ||||||
| 			margin: 0 0 10px; |  | ||||||
| 			font-size: 1.1rem; |  | ||||||
| 			height: 2.4rem; |  | ||||||
| 			overflow: hidden; |  | ||||||
| 			display: -webkit-box; |  | ||||||
| 			-webkit-line-clamp: 2; |  | ||||||
| 			-webkit-box-orient: vertical; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.product-price-rating { |  | ||||||
| 			display: flex; |  | ||||||
| 			justify-content: space-between; |  | ||||||
| 			margin-bottom: 10px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.product-price { |  | ||||||
| 			font-weight: bold; |  | ||||||
| 			color: #4a6cf7; |  | ||||||
| 			font-size: 1.2rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.stars { |  | ||||||
| 			color: gold; |  | ||||||
| 			margin-right: 5px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.product-category { |  | ||||||
| 			color: #666; |  | ||||||
| 			font-size: 0.9rem; |  | ||||||
| 			margin-bottom: 15px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.product-details { |  | ||||||
| 			margin: 15px 0; |  | ||||||
| 			font-size: 0.9rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.product-description { |  | ||||||
| 			line-height: 1.5; |  | ||||||
| 			margin-bottom: 10px; |  | ||||||
| 			color: #444; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.product-meta { |  | ||||||
| 			display: flex; |  | ||||||
| 			flex-wrap: wrap; |  | ||||||
| 			gap: 10px; |  | ||||||
| 			color: #666; |  | ||||||
| 			font-size: 0.85rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.product-actions { |  | ||||||
| 			display: flex; |  | ||||||
| 			gap: 10px; |  | ||||||
| 			margin-top: 15px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.product-actions button { |  | ||||||
| 			flex: 1; |  | ||||||
| 			padding: 8px 0; |  | ||||||
| 			border: none; |  | ||||||
| 			border-radius: 4px; |  | ||||||
| 			cursor: pointer; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.details-btn { |  | ||||||
| 			background: #eee; |  | ||||||
| 			color: #333; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.add-to-cart-btn { |  | ||||||
| 			background: #4a6cf7; |  | ||||||
| 			color: white; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.pagination { |  | ||||||
| 			display: flex; |  | ||||||
| 			justify-content: center; |  | ||||||
| 			gap: 5px; |  | ||||||
| 			margin-top: 30px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.pagination button { |  | ||||||
| 			padding: 8px 12px; |  | ||||||
| 			border: 1px solid #ddd; |  | ||||||
| 			background: white; |  | ||||||
| 			border-radius: 4px; |  | ||||||
| 			cursor: pointer; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.pagination button.current-page { |  | ||||||
| 			background: #4a6cf7; |  | ||||||
| 			color: white; |  | ||||||
| 			border-color: #4a6cf7; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.pagination button:disabled { |  | ||||||
| 			opacity: 0.5; |  | ||||||
| 			cursor: not-allowed; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.no-results { |  | ||||||
| 			grid-column: 1 / -1; |  | ||||||
| 			text-align: center; |  | ||||||
| 			padding: 40px; |  | ||||||
| 			color: #666; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		@media (max-width: 768px) { |  | ||||||
| 			.controls { |  | ||||||
| 				flex-direction: column; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			.search-box input { |  | ||||||
| 				width: 100%; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			.products-grid { |  | ||||||
| 				grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	`), |  | ||||||
| ); |  | ||||||
| @@ -483,7 +483,7 @@ document.body.append( | |||||||
|  |  | ||||||
| 		.task-board { | 		.task-board { | ||||||
| 			display: grid; | 			display: grid; | ||||||
| 			grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | 			grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | ||||||
| 			gap: 1.5rem; | 			gap: 1.5rem; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import { el } from "deka-dom-el"; | |||||||
| const button = el("button", { | const button = el("button", { | ||||||
| 	textContent: "Click me", | 	textContent: "Click me", | ||||||
| 	className: "primary", | 	className: "primary", | ||||||
| 	disabled: true, | 	disabled: true | ||||||
| }); | }); | ||||||
|  |  | ||||||
| // Shorter and more expressive | // Shorter and more expressive | ||||||
|   | |||||||
| @@ -6,6 +6,6 @@ import { el } from "deka-dom-el"; | |||||||
| document.body.append( | document.body.append( | ||||||
| 	el("div").append( | 	el("div").append( | ||||||
| 		el("h1", "Title"), | 		el("h1", "Title"), | ||||||
| 		el("p", "Paragraph"), | 		el("p", "Paragraph") | ||||||
| 	), | 	) | ||||||
| ); | ); | ||||||
|   | |||||||
| @@ -81,11 +81,11 @@ function Todos(){ | |||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		S.el(todosS, ({ length }) => !length | 		S.el(todosS, todos => !todos.length | ||||||
| 			? el() | 			? el() | ||||||
| 			: el("footer", { className: "footer" }).append( | 			: el("footer", { className: "footer" }).append( | ||||||
| 				el("span", { className: "todo-count" }).append( | 				el("span", { className: "todo-count" }).append( | ||||||
| 					el("strong", length + " " + (length === 1 ? "item" : "items")), | 					noOfLeft() | ||||||
| 				), | 				), | ||||||
| 				memo("filters", ()=> | 				memo("filters", ()=> | ||||||
| 					el("ul", { className: "filters" }).append( | 					el("ul", { className: "filters" }).append( | ||||||
| @@ -100,7 +100,7 @@ function Todos(){ | |||||||
| 						) | 						) | ||||||
| 					), | 					), | ||||||
| 				), | 				), | ||||||
| 				length - todosRemainingS.get() === 0 | 				todos.length - todosRemainingS.get() === 0 | ||||||
| 					? el() | 					? el() | ||||||
| 					: memo("delete", () => | 					: memo("delete", () => | ||||||
| 						el("button", | 						el("button", | ||||||
| @@ -110,6 +110,13 @@ function Todos(){ | |||||||
| 			) | 			) | ||||||
| 		) | 		) | ||||||
| 	); | 	); | ||||||
|  | 	function noOfLeft(){ | ||||||
|  | 		const length = todosRemainingS.get(); | ||||||
|  | 		return el("strong").append( | ||||||
|  | 			length + " ", | ||||||
|  | 			length === 1 ? "item left" : "items left" | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { styles, page_id } from "../ssr.js"; | import { styles } from "../ssr.js"; | ||||||
|  |  | ||||||
| styles.css` | styles.css` | ||||||
| #library-url-form { | #library-url-form { | ||||||
| @@ -74,7 +74,7 @@ styles.css` | |||||||
| import { el } from "deka-dom-el"; | import { el } from "deka-dom-el"; | ||||||
| import { ireland } from "./ireland.html.js"; | import { ireland } from "./ireland.html.js"; | ||||||
|  |  | ||||||
| export function getLibraryUrl(){ | export function getLibraryUrl({ page_id }){ | ||||||
| 	return el(ireland, { | 	return el(ireland, { | ||||||
| 		src: new URL("./getLibraryUrl.js.js", import.meta.url), | 		src: new URL("./getLibraryUrl.js.js", import.meta.url), | ||||||
| 		exportName: "getLibraryUrl", | 		exportName: "getLibraryUrl", | ||||||
|   | |||||||
| @@ -43,6 +43,7 @@ const componentsRegistry = new Map(); | |||||||
|  * @param {object} attrs |  * @param {object} attrs | ||||||
|  * @param {URL} attrs.src - Path to the file containing the component |  * @param {URL} attrs.src - Path to the file containing the component | ||||||
|  * @param {string} [attrs.exportName="default"] - Name of the export to use |  * @param {string} [attrs.exportName="default"] - Name of the export to use | ||||||
|  |  * @param {string} attrs.page_id - ID of the current page | ||||||
|  * @param {object} [attrs.props={}] - Props to pass to the component |  * @param {object} [attrs.props={}] - Props to pass to the component | ||||||
|  */ |  */ | ||||||
| export function ireland({ src, exportName = "default", props = {} }) { | export function ireland({ src, exportName = "default", props = {} }) { | ||||||
|   | |||||||
| @@ -12,10 +12,6 @@ export function mnemonic(){ | |||||||
| 			" — corresponds to custom elemnets callbacks ", el("code", "<live-cycle>Callback(...){...}"), | 			" — corresponds to custom elemnets callbacks ", el("code", "<live-cycle>Callback(...){...}"), | ||||||
| 			". To connect to custom element see following page, else it is simulated by MutationObserver." | 			". To connect to custom element see following page, else it is simulated by MutationObserver." | ||||||
| 		), | 		), | ||||||
| 		el("li").append( |  | ||||||
| 			el("code", "on.defer(<identity>=> <identity>)(<identity>)"), |  | ||||||
| 			" — calls callback later", |  | ||||||
| 		), |  | ||||||
| 		el("li").append( | 		el("li").append( | ||||||
| 			el("code", "dispatchEvent(<event>[, <options>])(element)"), | 			el("code", "dispatchEvent(<event>[, <options>])(element)"), | ||||||
| 			" — just ", el("code", "<element>.dispatchEvent(new Event(<event>[, <options>]))") | 			" — just ", el("code", "<element>.dispatchEvent(new Event(<event>[, <options>]))") | ||||||
|   | |||||||
| @@ -74,11 +74,13 @@ export function h3({ textContent, id }){ | |||||||
| 		if(!id) id= "h-"+textContent.toLowerCase().replaceAll(/\s/g, "-").replaceAll(/[^a-z-]/g, ""); | 		if(!id) id= "h-"+textContent.toLowerCase().replaceAll(/\s/g, "-").replaceAll(/[^a-z-]/g, ""); | ||||||
| 		return el("h3", { id }).append( | 		return el("h3", { id }).append( | ||||||
| 				el("a", { | 				el("a", { | ||||||
| 					className: "heading-anchor", | 						className: "heading-anchor", | ||||||
| 					href: "#"+id, | 						href: "#"+id, | ||||||
| 					title: `Link to this section: ${textContent}`, | 						textContent: "#", | ||||||
|  | 						title: `Link to this section: ${textContent}`, | ||||||
|  | 						"aria-label": `Link to section ${textContent}` | ||||||
| 				}), | 				}), | ||||||
| 				"# ", | 				" ", | ||||||
| 				textContent, | 				textContent, | ||||||
| 		); | 		); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,8 +3,8 @@ import { t, T } from "./utils/index.js"; | |||||||
| export const info= { | export const info= { | ||||||
| 	href: "./", | 	href: "./", | ||||||
| 	title: t`Introduction`, | 	title: t`Introduction`, | ||||||
| 	fullTitle: t`Vanilla for flavouring — a full-fledged feast for large projects`, | 	fullTitle: t`Vanilla for flavouring — a full-fledged feast for large projects`, | ||||||
| 	description: t`Reactive DOM library for creating dynamic UIs with a declarative syntax`, | 	description: t`A lightweight, reactive DOM library for creating dynamic UIs with a declarative syntax`, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| import { el } from "deka-dom-el"; | import { el } from "deka-dom-el"; | ||||||
| @@ -16,11 +16,7 @@ import { getLibraryUrl } from "./components/getLibraryUrl.html.js"; | |||||||
| /** @param {string} url */ | /** @param {string} url */ | ||||||
| const fileURL= url=> new URL(url, import.meta.url); | const fileURL= url=> new URL(url, import.meta.url); | ||||||
| const references= { | const references= { | ||||||
| 	npm: { | 	w_mvv:{ | ||||||
| 		title: t`NPM package page for dd<el>`, |  | ||||||
| 		href: "https://www.npmjs.com/package/deka-dom-el", |  | ||||||
| 	}, |  | ||||||
| 	w_mvv: { |  | ||||||
| 		title: t`Wikipedia: Model–view–viewmodel`, | 		title: t`Wikipedia: Model–view–viewmodel`, | ||||||
| 		href: "https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel", | 		href: "https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel", | ||||||
| 	}, | 	}, | ||||||
| @@ -31,9 +27,10 @@ const references= { | |||||||
| }; | }; | ||||||
| /** @param {import("./types.d.ts").PageAttrs} attrs */ | /** @param {import("./types.d.ts").PageAttrs} attrs */ | ||||||
| export function page({ pkg, info }){ | export function page({ pkg, info }){ | ||||||
|  | 	const page_id= info.id; | ||||||
| 	return el(simplePage, { info, pkg }).append( | 	return el(simplePage, { info, pkg }).append( | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			Welcome to Deka DOM Elements (dd<el> or DDE) — a library for building dynamic UIs with | 			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 | 			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. | 			without the complexity and overhead of larger frameworks. | ||||||
| 		`), | 		`), | ||||||
| @@ -41,18 +38,13 @@ export function page({ pkg, info }){ | |||||||
| 			el("h4", t`Key Benefits of dd<el>`), | 			el("h4", t`Key Benefits of dd<el>`), | ||||||
| 			el("ul").append( | 			el("ul").append( | ||||||
| 				el("li", t`No build step required — use directly in the browser`), | 				el("li", t`No build step required — use directly in the browser`), | ||||||
| 				el("li", t`Minimalized footprint:`), | 				el("li", t`Lightweight core (~10–15kB minified) without unnecessary dependencies (0 at now 😇)`), | ||||||
| 				el("ul").append( |  | ||||||
| 					el("li", t`lightweight core (~10–15kB minified)`), |  | ||||||
| 					el("li", t`…without unnecessary dependencies (0 at now 😇)`), |  | ||||||
| 					el("li", t`auto-releasing resources with focus on performance and development experience`), |  | ||||||
| 				), |  | ||||||
| 				el("li", t`Natural DOM API — work with real DOM nodes, not abstractions`), | 				el("li", t`Natural DOM API — work with real DOM nodes, not abstractions`), | ||||||
| 				el("li", t`Built-in (but optional) reactivity with simplified but powerful signals system`), | 				el("li", t`Built-in reactivity with simplified but powerful signals system`), | ||||||
| 				el("li", t`Clean code organization with the 3PS pattern`) | 				el("li", t`Clean code organization with the 3PS pattern`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el(example, { src: fileURL("./components/examples/introducing/helloWorld.js") }), | 		el(example, { src: fileURL("./components/examples/introducing/helloWorld.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, { textContent: t`The 3PS Pattern: Simplified architecture pattern`, id: "h-3ps" }), | 		el(h3, { textContent: t`The 3PS Pattern: Simplified architecture pattern`, id: "h-3ps" }), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -64,11 +56,11 @@ export function page({ pkg, info }){ | |||||||
| 			el("div", { className: "tabs" }).append( | 			el("div", { className: "tabs" }).append( | ||||||
| 				el("div", { className: "tab" }).append( | 				el("div", { className: "tab" }).append( | ||||||
| 					el("h5", t`Traditional DOM Manipulation`), | 					el("h5", t`Traditional DOM Manipulation`), | ||||||
| 					el(code, { src: fileURL("./components/examples/introducing/3ps-before.js") }), | 					el(code, { src: fileURL("./components/examples/introducing/3ps-before.js"), page_id }), | ||||||
| 				), | 				), | ||||||
| 				el("div", { className: "tab" }).append( | 				el("div", { className: "tab" }).append( | ||||||
| 					el("h5", t`dd<el>'s 3PS Pattern`), | 					el("h5", t`dd<el>'s 3PS Pattern`), | ||||||
| 					el(code, { src: fileURL("./components/examples/introducing/3ps.js") }), | 					el(code, { src: fileURL("./components/examples/introducing/3ps.js"), page_id }), | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| @@ -109,20 +101,16 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 		el(h3, t`Getting Started`), | 		el(h3, t`Getting Started`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			There are multiple ways to include dd<el> in your project. You can use npm for a full development setup, | 			There are multiple ways to include dd<el> in your project. You can use npm for a full development setup, | ||||||
| 			or directly include it from a CDN for quick prototyping. | 			or directly include it from a CDN for quick prototyping. | ||||||
| 		`), | 		`), | ||||||
| 		el("h4", "npm installation"), | 		el("h4", "npm installation"), | ||||||
| 		el(code, { content: "npm install deka-dom-el --save", language: "shell" }), | 		el(code, { content: "npm install deka-dom-el # Coming soon", language: "shell", page_id }), | ||||||
| 		el("p").append(T` |  | ||||||
| 			…see ${el("a", { textContent: "package page", ...references.npm, target: "_blank" })}. |  | ||||||
| 		`), |  | ||||||
|  |  | ||||||
| 		el("h4", "CDN / Direct Script Usage"), | 		el("h4", "CDN / Direct Script Usage"), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			Use the interactive selector below to choose your preferred format: | 			Use the interactive selector below to choose your preferred format: | ||||||
| 		`), | 		`), | ||||||
| 		el(getLibraryUrl), | 		el(getLibraryUrl, { page_id }), | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| 				Based on your selection, you can use dd<el> in your project like this: | 				Based on your selection, you can use dd<el> in your project like this: | ||||||
| @@ -131,10 +119,10 @@ export function page({ pkg, info }){ | |||||||
| 				// ESM format (modern JavaScript with import/export) | 				// ESM format (modern JavaScript with import/export) | ||||||
| 				import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.min.js"; | 				import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.min.js"; | ||||||
|  |  | ||||||
| 				// Or with IIFE format (creates a global DDE object) | 				// Or with IIFE format (creates a global DDE object) | ||||||
| 				// <script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/iife-with-signals.min.js"></script> | 				// <script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/iife-with-signals.min.js"></script> | ||||||
| 				const { el, on } = DDE; | 				const { el, on } = DDE; | ||||||
| 			`, language: "js" }), | 			`, language: "js", page_id }), | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`How to Use This Documentation`), | 		el(h3, t`How to Use This Documentation`), | ||||||
| @@ -158,7 +146,7 @@ export function page({ pkg, info }){ | |||||||
| 				Integrating third-party functionalities`), | 				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`), | 					.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`), | 				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>`), | 				rendering with dd<el>`), | ||||||
|   | |||||||
| @@ -151,7 +151,7 @@ export function header({ info: { href, title, description }, pkg }){ | |||||||
| 				), | 				), | ||||||
| 				el("span", { | 				el("span", { | ||||||
| 					className: "version-badge", | 					className: "version-badge", | ||||||
| 					ariaLabel: "Version", | 					"aria-label": "Version", | ||||||
| 					textContent: pkg.version || "" | 					textContent: pkg.version || "" | ||||||
| 				}) | 				}) | ||||||
| 			), | 			), | ||||||
| @@ -165,13 +165,13 @@ export function header({ info: { href, title, description }, pkg }){ | |||||||
| function nav({ href, pkg }){ | function nav({ href, pkg }){ | ||||||
| 	return el("nav", { | 	return el("nav", { | ||||||
| 		role: "navigation", | 		role: "navigation", | ||||||
| 		ariaLabel: "Main navigation", | 		"aria-label": "Main navigation", | ||||||
| 		className: nav.name | 		className: nav.name | ||||||
| 	}).append( | 	}).append( | ||||||
| 		el("a", { | 		el("a", { | ||||||
| 			href: pkg.homepage, | 			href: pkg.homepage, | ||||||
| 			className: "github-link", | 			className: "github-link", | ||||||
| 			ariaLabel: "View on GitHub", | 			"aria-label": "View on GitHub", | ||||||
| 			target: "_blank", | 			target: "_blank", | ||||||
| 			rel: "noopener noreferrer", | 			rel: "noopener noreferrer", | ||||||
| 		}).append( | 		}).append( | ||||||
| @@ -185,11 +185,11 @@ function nav({ href, pkg }){ | |||||||
| 			return el("a", { | 			return el("a", { | ||||||
| 				href: isIndex ? "./" : p.href, | 				href: isIndex ? "./" : p.href, | ||||||
| 				title: p.description || `Go to ${p.title}`, | 				title: p.description || `Go to ${p.title}`, | ||||||
| 				ariaCurrent: isCurrent ? "page" : null, | 				"aria-current": isCurrent ? "page" : null, | ||||||
| 			}).append( | 			}).append( | ||||||
| 				el("span", { | 				el("span", { | ||||||
| 					className: "nav-number", | 					className: "nav-number", | ||||||
| 					ariaHidden: "true", | 					"aria-hidden": "true", | ||||||
| 					textContent: `${i+1}. ` | 					textContent: `${i+1}. ` | ||||||
| 				}), | 				}), | ||||||
| 				p.title | 				p.title | ||||||
|   | |||||||
| @@ -47,11 +47,12 @@ const references= { | |||||||
| }; | }; | ||||||
| /** @param {import("./types.d.ts").PageAttrs} attrs */ | /** @param {import("./types.d.ts").PageAttrs} attrs */ | ||||||
| export function page({ pkg, info }){ | export function page({ pkg, info }){ | ||||||
|  | 	const page_id= info.id; | ||||||
| 	return el(simplePage, { info, pkg }).append( | 	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. | 			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, | 			dd<el> provides a simple yet powerful approach to element creation that is declarative, chainable, | ||||||
| 			and maintains a clean syntax close to HTML structure. | 			and maintains a clean syntax close to HTML structure. | ||||||
| 		`), | 		`), | ||||||
| 		el("div", { className: "callout" }).append( | 		el("div", { className: "callout" }).append( | ||||||
| 			el("h4", t`dd<el> Elements: Key Benefits`), | 			el("h4", t`dd<el> Elements: Key Benefits`), | ||||||
| @@ -64,7 +65,7 @@ export function page({ pkg, info }){ | |||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(code, { src: fileURL("./components/examples/elements/intro.js") }), | 		el(code, { src: fileURL("./components/examples/elements/intro.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Creating Elements: Native vs dd<el>`), | 		el(h3, t`Creating Elements: Native vs dd<el>`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -76,11 +77,11 @@ export function page({ pkg, info }){ | |||||||
| 			el("div", { className: "comparison" }).append( | 			el("div", { className: "comparison" }).append( | ||||||
| 				el("div").append( | 				el("div").append( | ||||||
| 					el("h5", t`Native DOM API`), | 					el("h5", t`Native DOM API`), | ||||||
| 					el(code, { src: fileURL("./components/examples/elements/native-dom-create.js") }) | 					el(code, { src: fileURL("./components/examples/elements/native-dom-create.js"), page_id }) | ||||||
| 				), | 				), | ||||||
| 				el("div").append( | 				el("div").append( | ||||||
| 					el("h5", t`dd<el> Approach`), | 					el("h5", t`dd<el> Approach`), | ||||||
| 					el(code, { src: fileURL("./components/examples/elements/dde-dom-create.js") }) | 					el(code, { src: fileURL("./components/examples/elements/dde-dom-create.js"), page_id }) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| @@ -88,7 +89,7 @@ export function page({ pkg, info }){ | |||||||
| 			The ${el("code", "el")} function provides a simple wrapper around ${el("code", "document.createElement")} | 			The ${el("code", "el")} function provides a simple wrapper around ${el("code", "document.createElement")} | ||||||
| 			with enhanced property assignment. | 			with enhanced property assignment. | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/elements/dekaCreateElement.js") }), | 		el(example, { src: fileURL("./components/examples/elements/dekaCreateElement.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Advanced Property Assignment`), | 		el(h3, t`Advanced Property Assignment`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -121,7 +122,7 @@ export function page({ pkg, info }){ | |||||||
| 				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") }), | 		el(example, { src: fileURL("./components/examples/elements/dekaAssign.js"), page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -141,11 +142,11 @@ export function page({ pkg, info }){ | |||||||
| 			el("div", { className: "comparison" }).append( | 			el("div", { className: "comparison" }).append( | ||||||
| 				el("div", { className: "bad-practice" }).append( | 				el("div", { className: "bad-practice" }).append( | ||||||
| 					el("h5", t`❌ Native DOM API`), | 					el("h5", t`❌ Native DOM API`), | ||||||
| 					el(code, { src: fileURL("./components/examples/elements/native-dom-tree.js") }) | 					el(code, { src: fileURL("./components/examples/elements/native-dom-tree.js"), page_id }) | ||||||
| 				), | 				), | ||||||
| 				el("div", { className: "good-practice" }).append( | 				el("div", { className: "good-practice" }).append( | ||||||
| 					el("h5", t`✅ dd<el> Approach`), | 					el("h5", t`✅ dd<el> Approach`), | ||||||
| 					el(code, { src: fileURL("./components/examples/elements/dde-dom-tree.js") }) | 					el(code, { src: fileURL("./components/examples/elements/dde-dom-tree.js"), page_id }) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| @@ -153,14 +154,14 @@ export function page({ pkg, info }){ | |||||||
| 			This chainable pattern is much cleaner and easier to follow, especially for deeply nested elements. | 			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. | 			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") }), | 		el(example, { src: fileURL("./components/examples/elements/dekaAppend.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Using Components to Build UI Fragments`), | 		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. | 			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: | 			This lets you refactor complex UI trees into reusable pieces: | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/elements/dekaBasicComponent.js") }), | 		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. | 			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. | 			This makes it easy to pass data down to components and create reusable UI fragments. | ||||||
| @@ -184,9 +185,9 @@ export function page({ pkg, info }){ | |||||||
| 			function which corresponds to the native ${el("a", references.mdn_ns).append(el("code", | 			function which corresponds to the native ${el("a", references.mdn_ns).append(el("code", | ||||||
| 				"document.createElementNS"))}: | 				"document.createElementNS"))}: | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/elements/dekaElNS.js") }), | 		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 | 			This function returns a namespace-specific element creator, allowing you to work with any element type | ||||||
| 			using the same consistent interface. | 			using the same consistent interface. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
|   | |||||||
| @@ -39,6 +39,7 @@ const references= { | |||||||
| }; | }; | ||||||
| /** @param {import("./types.d.ts").PageAttrs} attrs */ | /** @param {import("./types.d.ts").PageAttrs} attrs */ | ||||||
| export function page({ pkg, info }){ | export function page({ pkg, info }){ | ||||||
|  | 	const page_id= info.id; | ||||||
| 	return el(simplePage, { info, pkg }).append( | 	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 | 			Events are at the core of interactive web applications. dd<el> provides a clean, declarative approach to | ||||||
| @@ -56,7 +57,7 @@ export function page({ pkg, info }){ | |||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(code, { src: fileURL("./components/examples/events/intro.js") }), | 		el(code, { src: fileURL("./components/examples/events/intro.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Events and Listeners: Two Approaches`), | 		el(h3, t`Events and Listeners: Two Approaches`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -69,11 +70,11 @@ export function page({ pkg, info }){ | |||||||
| 			el("div", { className: "tabs" }).append( | 			el("div", { className: "tabs" }).append( | ||||||
| 				el("div", { className: "tab" }).append( | 				el("div", { className: "tab" }).append( | ||||||
| 					el("h5", t`Native DOM API`), | 					el("h5", t`Native DOM API`), | ||||||
| 					el(code, { content: `element.addEventListener("click", callback, options);`, language: "js" }) | 					el(code, { content: `element.addEventListener("click", callback, options);`, page_id }) | ||||||
| 				), | 				), | ||||||
| 				el("div", { className: "tab" }).append( | 				el("div", { className: "tab" }).append( | ||||||
| 					el("h5", t`dd<el> Approach`), | 					el("h5", t`dd<el> Approach`), | ||||||
| 					el(code, { content: `on("click", callback, options)(element);`, language: "js" }) | 					el(code, { content: `on("click", callback, options)(element);`, page_id }) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| @@ -81,7 +82,7 @@ export function page({ pkg, info }){ | |||||||
| 			The main benefit of dd<el>’s approach is that it works as an Addon (see below), making it easy to integrate | 			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. | 			directly into element declarations. | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/events/compare.js") }), | 		el(example, { src: fileURL("./components/examples/events/compare.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Removing Event Listeners`), | 		el(h3, t`Removing Event Listeners`), | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| @@ -90,7 +91,7 @@ export function page({ pkg, info }){ | |||||||
| 				${el("a", { textContent: "AbortSignal", ...references.mdn_abortListener })} for declarative removal: | 				${el("a", { textContent: "AbortSignal", ...references.mdn_abortListener })} for declarative removal: | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
| 		el(example, { src: fileURL("./components/examples/events/abortSignal.js") }), | 		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 ( | 			This is the same for signals (see next section) and works well with scopes and library extendability ( | ||||||
| 			see scopes and extensions section — mainly ${el("code", "scope.signal")}). | 			see scopes and extensions section — mainly ${el("code", "scope.signal")}). | ||||||
| @@ -100,26 +101,26 @@ export function page({ pkg, info }){ | |||||||
| 		el("div", { className: "tabs" }).append( | 		el("div", { className: "tabs" }).append( | ||||||
| 			el("div", { className: "tab", dataTab: "html-attr" }).append( | 			el("div", { className: "tab", dataTab: "html-attr" }).append( | ||||||
| 				el("h4", t`HTML Attribute Style`), | 				el("h4", t`HTML Attribute Style`), | ||||||
| 				el(code, { src: fileURL("./components/examples/events/attribute-event.js") }), | 				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 | 					Forces usage as an HTML attribute. Corresponds to | ||||||
| 					${el("code", `<button onclick="console.log(event)">click me</button>`)}. This can be particularly | 					${el("code", `<button onclick="console.log(event)">click me</button>`)}. This can be particularly | ||||||
| 					useful for SSR scenarios. | 					useful for SSR scenarios. | ||||||
| 				`) | 				`) | ||||||
| 			), | 			), | ||||||
| 			el("div", { className: "tab", dataTab: "property" }).append( | 			el("div", { className: "tab", dataTab: "property" }).append( | ||||||
| 				el("h4", t`Property Assignment`), | 				el("h4", t`Property Assignment`), | ||||||
| 				el(code, { src: fileURL("./components/examples/events/property-event.js") }), | 				el(code, { src: fileURL("./components/examples/events/property-event.js"), page_id }), | ||||||
| 				el("p", t`Assigns the event handler directly to the element’s property.`) | 				el("p", t`Assigns the event handler directly to the element’s property.`) | ||||||
| 			), | 			), | ||||||
| 			el("div", { className: "tab", dataTab: "addon" }).append( | 			el("div", { className: "tab", dataTab: "addon" }).append( | ||||||
| 				el("h4", t`Addon Approach`), | 				el("h4", t`Addon Approach`), | ||||||
| 				el(code, { src: fileURL("./components/examples/events/chain-event.js") }), | 				el(code, { src: fileURL("./components/examples/events/chain-event.js"), page_id }), | ||||||
| 				el("p", t`Uses the addon pattern (so adds the event listener to the element), see above.`) | 				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 | 			For a deeper comparison of these approaches, see | ||||||
| 			${el("a", { textContent: "WebReflection’s detailed analysis", ...references.web_events })}. | 			${el("a", { textContent: "WebReflection’s detailed analysis", ...references.web_events })}. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| @@ -142,7 +143,7 @@ export function page({ pkg, info }){ | |||||||
| 			You can use Addons as ≥3rd argument of the ${el("code", "el")} function, making it possible to | 			You can use Addons as ≥3rd argument of the ${el("code", "el")} function, making it possible to | ||||||
| 			extend your templates with additional functionality: | 			extend your templates with additional functionality: | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/events/templateWithListeners.js") }), | 		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 | 			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("code", "ddeElementAddon")}. Notice how Addons can also be used to get element references. | ||||||
| @@ -166,7 +167,7 @@ export function page({ pkg, info }){ | |||||||
| 				el("dd", t`Fires when the element is removed from the DOM`), | 				el("dd", t`Fires when the element is removed from the DOM`), | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el(example, { src: fileURL("./components/examples/events/live-cycle.js") }), | 		el(example, { src: fileURL("./components/examples/events/live-cycle.js"), page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -216,8 +217,8 @@ export function page({ pkg, info }){ | |||||||
| 			This makes it easy to implement component communication through events, following standard web platform | 			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. | 			patterns. The curried approach allows for easy reuse of event dispatchers throughout your application. | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/events/compareDispatch.js") }), | 		el(example, { src: fileURL("./components/examples/events/compareDispatch.js"), page_id }), | ||||||
| 		el(code, { src: fileURL("./components/examples/events/dispatch.js") }), | 		el(code, { src: fileURL("./components/examples/events/dispatch.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Best Practices`), | 		el(h3, t`Best Practices`), | ||||||
| 		el("ol").append( | 		el("ol").append( | ||||||
| @@ -250,6 +251,6 @@ export function page({ pkg, info }){ | |||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(mnemonic), | 		el(mnemonic) | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -42,6 +42,7 @@ const references= { | |||||||
| }; | }; | ||||||
| /** @param {import("./types.d.ts").PageAttrs} attrs */ | /** @param {import("./types.d.ts").PageAttrs} attrs */ | ||||||
| export function page({ pkg, info }){ | export function page({ pkg, info }){ | ||||||
|  | 	const page_id= info.id; | ||||||
| 	return el(simplePage, { info, pkg }).append( | 	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 | 			Signals provide a simple yet powerful way to create reactive applications with dd<el>. They handle the | ||||||
| @@ -57,7 +58,7 @@ export function page({ pkg, info }){ | |||||||
| 				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") }), | 		el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`The 3-Part Structure of Signals`), | 		el(h3, t`The 3-Part Structure of Signals`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -67,21 +68,21 @@ export function page({ pkg, info }){ | |||||||
| 		el("div", { className: "signal-diagram" }).append( | 		el("div", { className: "signal-diagram" }).append( | ||||||
| 			el("div", { className: "signal-part" }).append( | 			el("div", { className: "signal-part" }).append( | ||||||
| 				el("h4", t`PART 1: Create Signal`), | 				el("h4", t`PART 1: Create Signal`), | ||||||
| 				el(code, { content: "const count = S(0);", language: "js" }), | 				el(code, { content: "const count = S(0);", page_id }), | ||||||
| 				el("p", t`Define a reactive value that can be observed and changed`) | 				el("p", t`Define a reactive value that can be observed and changed`) | ||||||
| 			), | 			), | ||||||
| 			el("div", { className: "signal-part" }).append( | 			el("div", { className: "signal-part" }).append( | ||||||
| 				el("h4", t`PART 2: React to Changes`), | 				el("h4", t`PART 2: React to Changes`), | ||||||
| 				el(code, { content: "S.on(count, value => updateUI(value));", language: "js" }), | 				el(code, { content: "S.on(count, value => updateUI(value));", page_id }), | ||||||
| 				el("p", t`Subscribe to signal changes with callbacks or effects`) | 				el("p", t`Subscribe to signal changes with callbacks or effects`) | ||||||
| 			), | 			), | ||||||
| 			el("div", { className: "signal-part" }).append( | 			el("div", { className: "signal-part" }).append( | ||||||
| 				el("h4", t`PART 3: Update Signal`), | 				el("h4", t`PART 3: Update Signal`), | ||||||
| 				el(code, { content: "count.set(count.get() + 1);", language: "js" }), | 				el(code, { content: "count.set(count.get() + 1);", page_id }), | ||||||
| 				el("p", t`Modify the signal value, which automatically triggers updates`) | 				el("p", t`Modify the signal value, which automatically triggers updates`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el(example, { src: fileURL("./components/examples/signals/signals.js") }), | 		el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -124,12 +125,12 @@ export function page({ pkg, info }){ | |||||||
| 			Computed values (also called derived signals) automatically update when their dependencies change. | 			Computed values (also called derived signals) automatically update when their dependencies change. | ||||||
| 			Create them by passing ${el("strong", "a function")} to ${el("code", "S()")}: | 			Create them by passing ${el("strong", "a function")} to ${el("code", "S()")}: | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/signals/derived.js") }), | 		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 | 			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. | 			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") }), | 		el(example, { src: fileURL("./components/examples/signals/computations-abort.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Signal Actions: For Complex State`), | 		el(h3, t`Signal Actions: For Complex State`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -150,7 +151,7 @@ export function page({ pkg, info }){ | |||||||
| 						}); | 						}); | ||||||
| 						// Use the action | 						// Use the action | ||||||
| 						S.action(todos, "add", "New todo"); | 						S.action(todos, "add", "New todo"); | ||||||
| 					`, language: "js" }) | 					`, page_id }) | ||||||
| 				), | 				), | ||||||
| 				el("div", { className: "bad-practice" }).append( | 				el("div", { className: "bad-practice" }).append( | ||||||
| 					el("h5", t`❌ Without Actions`), | 					el("h5", t`❌ Without Actions`), | ||||||
| @@ -160,7 +161,7 @@ export function page({ pkg, info }){ | |||||||
| 						const items = todos.get(); | 						const items = todos.get(); | ||||||
| 						items.push("New todo"); | 						items.push("New todo"); | ||||||
| 						// This WON’T trigger updates! | 						// This WON’T trigger updates! | ||||||
| 					`, language: "js" })) | 					`, page_id })) | ||||||
| 				), | 				), | ||||||
| 		), | 		), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -171,7 +172,7 @@ export function page({ pkg, info }){ | |||||||
| 			${el("code", "this.stopPropagation()")} in the method representing the given action. As it can be seen in | 			${el("code", "this.stopPropagation()")} in the method representing the given action. As it can be seen in | ||||||
| 			examples, the “store” value is available also in the function for given action (${el("code", "this.value")}). | 			examples, the “store” value is available also in the function for given action (${el("code", "this.value")}). | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/signals/actions-demo.js") }), | 		el(example, { src: fileURL("./components/examples/signals/actions-demo.js"), page_id }), | ||||||
|  |  | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			Actions provide these benefits: | 			Actions provide these benefits: | ||||||
| @@ -185,7 +186,7 @@ export function page({ pkg, info }){ | |||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			Here’s a more complete example of a todo list using signal actions: | 			Here’s a more complete example of a todo list using signal actions: | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/signals/actions-todos.js") }), | 		el(example, { src: fileURL("./components/examples/signals/actions-todos.js"), page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -222,7 +223,7 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 					// Later: | 					// Later: | ||||||
| 					color.set("red"); // UI updates automatically | 					color.set("red"); // UI updates automatically | ||||||
| 				`, language: "js" }), | 				`, page_id }), | ||||||
| 			), | 			), | ||||||
| 			el("div", { className: "tab", dataTab: "elements" }).append( | 			el("div", { className: "tab", dataTab: "elements" }).append( | ||||||
| 				el("h4", t`Reactive Elements`), | 				el("h4", t`Reactive Elements`), | ||||||
| @@ -240,7 +241,7 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 					// Later: | 					// Later: | ||||||
| 					S.action(items, "push", "Dragonfruit"); // List updates automatically | 					S.action(items, "push", "Dragonfruit"); // List updates automatically | ||||||
| 				`, language: "js" }), | 				`, page_id }), | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| @@ -249,12 +250,12 @@ export function page({ pkg, info }){ | |||||||
| 			You can use special properties like ${el("code", "dataset")}, ${el("code", "ariaset")}, and | 			You can use special properties like ${el("code", "dataset")}, ${el("code", "ariaset")}, and | ||||||
| 			${el("code", "classList")} for fine-grained control over specific attribute types. | 			${el("code", "classList")} for fine-grained control over specific attribute types. | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/signals/dom-attrs.js") }), | 		el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }), | ||||||
|  |  | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			${el("code", "S.el()")} is especially powerful for conditional rendering and lists: | 			${el("code", "S.el()")} is especially powerful for conditional rendering and lists: | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/signals/dom-el.js") }), | 		el(example, { src: fileURL("./components/examples/signals/dom-el.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Best Practices for Signals`), | 		el(h3, t`Best Practices for Signals`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -288,7 +289,7 @@ export function page({ pkg, info }){ | |||||||
| 				el("h4", t`We can process form events without signals`), | 				el("h4", t`We can process form events without signals`), | ||||||
| 				el("p", t`This can be used when the form data doesn’t need to be reactive and we just waiting for | 				el("p", t`This can be used when the form data doesn’t need to be reactive and we just waiting for | ||||||
| 					results.`), | 					results.`), | ||||||
| 				el(code, { content: ` | 				el(code, { page_id, content: ` | ||||||
| 					const onFormSubmit = on("submit", e => { | 					const onFormSubmit = on("submit", e => { | ||||||
| 						e.preventDefault(); | 						e.preventDefault(); | ||||||
| 						const formData = new FormData(e.currentTarget); | 						const formData = new FormData(e.currentTarget); | ||||||
| @@ -300,13 +301,13 @@ export function page({ pkg, info }){ | |||||||
| 					return el("form", null, onFormSubmit).append( | 					return el("form", null, onFormSubmit).append( | ||||||
| 						// … | 						// … | ||||||
| 					); | 					); | ||||||
| 				`, language: "js" }) | 				` }) | ||||||
| 			), | 			), | ||||||
|  |  | ||||||
| 			el("div", { className: "tab", dataTab: "variables" }).append( | 			el("div", { className: "tab", dataTab: "variables" }).append( | ||||||
| 				el("h4", t`We can use variables without signals`), | 				el("h4", t`We can use variables without signals`), | ||||||
| 				el("p", t`We use this when we dont’t need to reflect changes in the elsewhere (UI).`), | 				el("p", t`We use this when we dont’t need to reflect changes in the elsewhere (UI).`), | ||||||
| 				el(code, { content: ` | 				el(code, { page_id, content: ` | ||||||
| 					let canSubmit = false; | 					let canSubmit = false; | ||||||
|  |  | ||||||
| 					const onFormSubmit = on("submit", e => { | 					const onFormSubmit = on("submit", e => { | ||||||
| @@ -317,14 +318,14 @@ export function page({ pkg, info }){ | |||||||
| 					const onAllowSubmit = on("click", e => { | 					const onAllowSubmit = on("click", e => { | ||||||
| 						canSubmit = true; | 						canSubmit = true; | ||||||
| 					}); | 					}); | ||||||
| 				`, language: "js" }), | 				`}), | ||||||
| 			), | 			), | ||||||
|  |  | ||||||
| 			el("div", { className: "tab", dataTab: "state" }).append( | 			el("div", { className: "tab", dataTab: "state" }).append( | ||||||
| 				el("h4", t`Using signals`), | 				el("h4", t`Using signals`), | ||||||
| 				el("p", t`We use this when we need to reflect changes for example in the UI (e.g. enable/disable | 				el("p", t`We use this when we need to reflect changes for example in the UI (e.g. enable/disable | ||||||
| 					buttons).`), | 					buttons).`), | ||||||
| 				el(code, { content: ` | 				el(code, { page_id, content: ` | ||||||
| 					const canSubmit = S(false); | 					const canSubmit = S(false); | ||||||
|  |  | ||||||
| 					const onFormSubmit = on("submit", e => { | 					const onFormSubmit = on("submit", e => { | ||||||
| @@ -340,7 +341,7 @@ export function page({ pkg, info }){ | |||||||
| 						el("button", { textContent: "Allow Submit", type: "button" }, onAllowSubmit), | 						el("button", { textContent: "Allow Submit", type: "button" }, onAllowSubmit), | ||||||
| 						el("button", { disabled: S(()=> !canSubmit), textContent: "Submit" }) | 						el("button", { disabled: S(()=> !canSubmit), textContent: "Submit" }) | ||||||
| 					); | 					); | ||||||
| 				`, language: "js" }), | 				`}), | ||||||
| 			), | 			), | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
|   | |||||||
| @@ -27,13 +27,14 @@ const references= { | |||||||
| }; | }; | ||||||
| /** @param {import("./types.d.ts").PageAttrs} attrs */ | /** @param {import("./types.d.ts").PageAttrs} attrs */ | ||||||
| export function page({ pkg, info }){ | export function page({ pkg, info }){ | ||||||
|  | 	const page_id= info.id; | ||||||
| 	return el(simplePage, { info, pkg }).append( | 	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, | 			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 | 			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 })}. | 			the ${el("a", { textContent: t`Garbage collection`, ...references.garbage_collection })}. | ||||||
| 		`), | 		`), | ||||||
| 		el(code, { src: fileURL("./components/examples/scopes/intro.js") }), | 		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(h3, t`Understanding Host Elements and Scopes`), | ||||||
| @@ -82,7 +83,7 @@ export function page({ pkg, info }){ | |||||||
| 				el("dd", t`Applies the addons to the host element (and returns the host element)`) | 				el("dd", t`Applies the addons to the host element (and returns the host element)`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el(example, { src: fileURL("./components/examples/scopes/scopes-and-hosts.js") }), | 		el(example, { src: fileURL("./components/examples/scopes/scopes-and-hosts.js"), page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -94,7 +95,7 @@ export function page({ pkg, info }){ | |||||||
| 				If you are interested in the implementation details, see Class-Based Components section. | 				If you are interested in the implementation details, see Class-Based Components section. | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
| 		el(code, { src: fileURL("./components/examples/scopes/good-practise.js") }), | 		el(code, { src: fileURL("./components/examples/scopes/good-practise.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Class-Based Components`), | 		el(h3, t`Class-Based Components`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -102,7 +103,7 @@ export function page({ pkg, info }){ | |||||||
| 			For this, we implement function ${el("code", "elClass")} and use it to demonstrate implementation details | 			For this, we implement function ${el("code", "elClass")} and use it to demonstrate implementation details | ||||||
| 			for better understanding of the scope logic. | 			for better understanding of the scope logic. | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/scopes/class-component.js") }), | 		el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Automatic Cleanup with Scopes`), | 		el(h3, t`Automatic Cleanup with Scopes`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -122,7 +123,7 @@ export function page({ pkg, info }){ | |||||||
| 					- Custom cleanup code (dd<el> and user) | 					- Custom cleanup code (dd<el> and user) | ||||||
| 			` }) | 			` }) | ||||||
| 		), | 		), | ||||||
| 		el(example, { src: fileURL("./components/examples/scopes/cleaning.js") }), | 		el(example, { src: fileURL("./components/examples/scopes/cleaning.js"), page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -148,17 +149,17 @@ export function page({ pkg, info }){ | |||||||
| 			el("div", { className: "tab", dataTab: "declarative" }).append( | 			el("div", { className: "tab", dataTab: "declarative" }).append( | ||||||
| 				el("h4", t`✅ Declarative Approach`), | 				el("h4", t`✅ Declarative Approach`), | ||||||
| 				el("p", t`Define what your UI should look like based on state:`), | 				el("p", t`Define what your UI should look like based on state:`), | ||||||
| 				el(code, { src: fileURL("./components/examples/scopes/declarative.js") }) | 				el(code, { src: fileURL("./components/examples/scopes/declarative.js"), page_id }) | ||||||
| 			), | 			), | ||||||
| 			el("div", { className: "tab", dataTab: "imperative" }).append( | 			el("div", { className: "tab", dataTab: "imperative" }).append( | ||||||
| 				el("h4", t`⚠️ Imperative Approach`), | 				el("h4", t`⚠️ Imperative Approach`), | ||||||
| 				el("p", t`Manually update the DOM in response to events:`), | 				el("p", t`Manually update the DOM in response to events:`), | ||||||
| 				el(code, { src: fileURL("./components/examples/scopes/imperative.js") }) | 				el(code, { src: fileURL("./components/examples/scopes/imperative.js"), page_id }) | ||||||
| 			), | 			), | ||||||
| 			el("div", { className: "tab", dataTab: "mixed" }).append( | 			el("div", { className: "tab", dataTab: "mixed" }).append( | ||||||
| 				el("h4", t`❌ Mixed Approach`), | 				el("h4", t`❌ Mixed Approach`), | ||||||
| 				el("p", t`This approach should be avoided:`), | 				el("p", t`This approach should be avoided:`), | ||||||
| 				el(code, { src: fileURL("./components/examples/scopes/mixed.js") }) | 				el(code, { src: fileURL("./components/examples/scopes/mixed.js"), page_id }) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
|   | |||||||
| @@ -57,6 +57,7 @@ const references= { | |||||||
| }; | }; | ||||||
| /** @param {import("./types.d.ts").PageAttrs} attrs */ | /** @param {import("./types.d.ts").PageAttrs} attrs */ | ||||||
| export function page({ pkg, info }){ | export function page({ pkg, info }){ | ||||||
|  | 	const page_id= info.id; | ||||||
| 	return el(simplePage, { info, pkg }).append( | 	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 | 			dd<el> pairs powerfully with ${el("a", references.mdn_web_components).append(el("strong", t`Web | ||||||
| @@ -72,7 +73,7 @@ export function page({ pkg, info }){ | |||||||
| 				el("li", t`Clean component lifecycle management`), | 				el("li", t`Clean component lifecycle management`), | ||||||
| 			), | 			), | ||||||
| 		), | 		), | ||||||
| 		el(code, { src: fileURL("./components/examples/customElement/intro.js") }), | 		el(code, { src: fileURL("./components/examples/customElement/intro.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Getting Started: Web Components Basics`), | 		el(h3, t`Getting Started: Web Components Basics`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -94,7 +95,7 @@ export function page({ pkg, info }){ | |||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			Let’s start with a basic Custom Element example without dd<el> to establish the foundation: | 			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") }), | 		el(code, { src: fileURL("./components/examples/customElement/native-basic.js"), page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -123,7 +124,7 @@ export function page({ pkg, info }){ | |||||||
| 				el("dd", t`Allows using on.connected(), on.disconnected() or S.observedAttributes().`) | 				el("dd", t`Allows using on.connected(), on.disconnected() or S.observedAttributes().`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js") }), | 		el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js"), page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -155,7 +156,7 @@ export function page({ pkg, info }){ | |||||||
| 				el("dd", t`The rendered DOM tree`) | 				el("dd", t`The rendered DOM tree`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el(example, { src: fileURL("./components/examples/customElement/dde.js") }), | 		el(example, { src: fileURL("./components/examples/customElement/dde.js"), page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -187,7 +188,7 @@ export function page({ pkg, info }){ | |||||||
| 			Using the ${el("code", "S.observedAttributes")} creates a reactive connection between your element’s | 			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! | 			attributes and its internal rendering. When attributes change, your component automatically updates! | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/customElement/observedAttributes.js") }), | 		el(example, { src: fileURL("./components/examples/customElement/observedAttributes.js"), page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "callout" }).append( | 		el("div", { className: "callout" }).append( | ||||||
| 			el("h4", t`How S.observedAttributes Works`), | 			el("h4", t`How S.observedAttributes Works`), | ||||||
| @@ -220,7 +221,7 @@ export function page({ pkg, info }){ | |||||||
| 								<p>Content</p> | 								<p>Content</p> | ||||||
| 			` }) | 			` }) | ||||||
| 		), | 		), | ||||||
| 		el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js") }), | 		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 | 			For more information on Shadow DOM, see | ||||||
| @@ -233,7 +234,7 @@ export function page({ pkg, info }){ | |||||||
| 			Besides the encapsulation, the Shadow DOM allows for using the ${el("a", references.mdn_shadow_dom_slot).append( | 			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")}: | 			el("strong", t`<slot>`), t` element(s)`)}. You can simulate this feature using ${el("code", "simulateSlots")}: | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js") }), | 		el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js"), page_id }), | ||||||
| 		el("div", { className: "function-table" }).append( | 		el("div", { className: "function-table" }).append( | ||||||
| 			el("h4", t`simulateSlots`), | 			el("h4", t`simulateSlots`), | ||||||
| 			el("dl").append( | 			el("dl").append( | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ const fileURL= url=> new URL(url, import.meta.url); | |||||||
|  |  | ||||||
| /** @param {import("./types.d.ts").PageAttrs} attrs */ | /** @param {import("./types.d.ts").PageAttrs} attrs */ | ||||||
| export function page({ pkg, info }){ | export function page({ pkg, info }){ | ||||||
|  | 	const page_id= info.id; | ||||||
| 	return el(simplePage, { info, pkg }).append( | 	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 | 			Debugging is an essential part of application development. This guide provides techniques | ||||||
| @@ -35,7 +36,7 @@ export function page({ pkg, info }){ | |||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			const signal = S(0); | 			const signal = S(0); | ||||||
| 			console.log('Current value:', signal.valueOf()); | 			console.log('Current value:', signal.valueOf()); | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
| 		el("div", { className: "warning" }).append( | 		el("div", { className: "warning" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| 				${el("code", "signal.get")} is OK, but in some situations may lead to unexpected results: | 				${el("code", "signal.get")} is OK, but in some situations may lead to unexpected results: | ||||||
| @@ -48,7 +49,7 @@ export function page({ pkg, info }){ | |||||||
| 					// but typically this is fine ↓ | 					// but typically this is fine ↓ | ||||||
| 					return signal.get() + 1; | 					return signal.get() + 1; | ||||||
| 				}); | 				}); | ||||||
| 			`, language: "js" }) | 			` }) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			You can also monitor signal changes by adding a listener: | 			You can also monitor signal changes by adding a listener: | ||||||
| @@ -56,7 +57,7 @@ export function page({ pkg, info }){ | |||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			// Log every time the signal changes | 			// Log every time the signal changes | ||||||
| 			S.on(signal, value => console.log('Signal changed:', value)); | 			S.on(signal, value => console.log('Signal changed:', value)); | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("h4", t`Debugging derived signals`), | 		el("h4", t`Debugging derived signals`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -68,7 +69,7 @@ export function page({ pkg, info }){ | |||||||
| 			el("li", t`Add logging/debugger inside the computation function to see when it runs`), | 			el("li", t`Add logging/debugger inside the computation function to see when it runs`), | ||||||
| 			el("li", t`Verify that the computation function actually accesses the signal values with .get()`) | 			el("li", t`Verify that the computation function actually accesses the signal values with .get()`) | ||||||
| 		), | 		), | ||||||
| 		el(example, { src: fileURL("./components/examples/debugging/consoleLog.js") }), | 		el(example, { src: fileURL("./components/examples/debugging/consoleLog.js"), page_id }), | ||||||
|  |  | ||||||
| 		el("h4", t`Examining signal via DevTools`), | 		el("h4", t`Examining signal via DevTools`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -76,11 +77,11 @@ export function page({ pkg, info }){ | |||||||
| 			signal objects. It contains the following information: | 			signal objects. It contains the following information: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| 			// TODO: value? |  | ||||||
| 			el("li", t`listeners: A Set of functions called when the signal value changes`), | 			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`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`onclear: Functions to run when the signal is cleared`), | ||||||
| 			el("li", t`host: Reference to the host element/scope in which the signal was created`), | 			el("li", t`host: Reference to the host element/scope in which the signal was created`), | ||||||
|  | 			el("li", t`readonly: Boolean flag indicating if the signal is read-only`) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			…to determine the current value of the signal, call ${el("code", "signal.valueOf()")}. Don’t hesitate to | 			…to determine the current value of the signal, call ${el("code", "signal.valueOf()")}. Don’t hesitate to | ||||||
| @@ -120,7 +121,7 @@ export function page({ pkg, info }){ | |||||||
| 				"S.el(S(()=> count.get() % 2), odd=> …)")}). | 				"S.el(S(()=> count.get() % 2), odd=> …)")}). | ||||||
| 			`), | 			`), | ||||||
| 		), | 		), | ||||||
| 		el(code, { src: fileURL("./components/examples/debugging/mutations.js") }), | 		el(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }), | ||||||
|  |  | ||||||
| 		el("h4", t`Memory leaks with signal listeners`), | 		el("h4", t`Memory leaks with signal listeners`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -136,7 +137,7 @@ export function page({ pkg, info }){ | |||||||
| 			el("li", t`Make sure derived signals don’t perform expensive calculations unnecessarily`), | 			el("li", t`Make sure derived signals don’t perform expensive calculations unnecessarily`), | ||||||
| 			el("li", t`Keep signal computations focused and minimal`) | 			el("li", t`Keep signal computations focused and minimal`) | ||||||
| 		), | 		), | ||||||
| 		el(code, { src: fileURL("./components/examples/debugging/debouncing.js") }), | 		el(code, { src: fileURL("./components/examples/debugging/debouncing.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Browser DevTools tips for components and reactivity`), | 		el(h3, t`Browser DevTools tips for components and reactivity`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -149,7 +150,7 @@ export function page({ pkg, info }){ | |||||||
| 			that are automatically updated when signal values change. These elements are wrapped in special | 			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): | 			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") }), | 		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. | 			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 | 			You can inspect the elements between the comment nodes to see their current state and the | ||||||
| @@ -168,22 +169,6 @@ export function page({ pkg, info }){ | |||||||
| 			el("li", t`name - The name of the component function`), | 			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("li", t`host - Indicates whether the host is "this" (for DocumentFragments) or "parentElement"`), | ||||||
| 		), | 		), | ||||||
| 		el("div", { className: "warning" }).append( |  | ||||||
| 			el("p").append(T` |  | ||||||
| 				There are edge case when the mark can be missing. For example, the (utility) components with reactive |  | ||||||
| 				keys such as ${el("code", ".textContent")}, ${el("code", ".innerText")} or ${el("code", ".innerHTML")}. |  | ||||||
| 				As they change the content of the host element. |  | ||||||
| 			`), |  | ||||||
| 			el(code, { content: ` |  | ||||||
| 				function Counter() { |  | ||||||
| 					const count = S(0); |  | ||||||
| 					return el("button", |  | ||||||
| 						{ textContent: count, type: "button" }, |  | ||||||
| 						on("click", () => count.set(count.get() + 1)), |  | ||||||
| 					); |  | ||||||
| 				} |  | ||||||
| 			`, language: "js" }), |  | ||||||
| 		), |  | ||||||
|  |  | ||||||
| 		el("h4", t`Identifying reactive elements in the DOM`), | 		el("h4", t`Identifying reactive elements in the DOM`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -202,7 +187,7 @@ export function page({ pkg, info }){ | |||||||
| 			so you can see the element and property that changes in the console right away. These properties make it | 			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. | 			easier to understand the reactive structure of your application when inspecting elements. | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/signals/debugging-dom.js") }), | 		el(example, { src: fileURL("./components/examples/signals/debugging-dom.js"), page_id }), | ||||||
|  |  | ||||||
| 		el("p", { className: "note" }).append(T` | 		el("p", { className: "note" }).append(T` | ||||||
| 			${el("code", "<element>.__dde_reactive")} - An array property on DOM elements that tracks signal-to-element | 			${el("code", "<element>.__dde_reactive")} - An array property on DOM elements that tracks signal-to-element | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ const fileURL= url=> new URL(url, import.meta.url); | |||||||
|  |  | ||||||
| /** @param {import("./types.js").PageAttrs} attrs */ | /** @param {import("./types.js").PageAttrs} attrs */ | ||||||
| export function page({ pkg, info }){ | export function page({ pkg, info }){ | ||||||
|  | 	const page_id= info.id; | ||||||
| 	return el(simplePage, { info, pkg }).append( | 	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 | 			dd<el> is designed with extensibility in mind. This page covers how to separate | ||||||
| @@ -48,7 +49,7 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 			// Using an addon | 			// Using an addon | ||||||
| 			el("div", { id: "example" }, myAddon({ option: "value" })); | 			el("div", { id: "example" }, myAddon({ option: "value" })); | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Resource Cleanup with Abort Signals`), | 		el(h3, t`Resource Cleanup with Abort Signals`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -82,7 +83,7 @@ export function page({ pkg, info }){ | |||||||
| 				const { signal }= scope; | 				const { signal }= scope; | ||||||
| 				return el("div", null, externalLibraryAddon({ option: "value" }, signal)); | 				return el("div", null, externalLibraryAddon({ option: "value" }, signal)); | ||||||
| 			} | 			} | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Building Library-Independent Extensions`), | 		el(h3, t`Building Library-Independent Extensions`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -103,7 +104,7 @@ export function page({ pkg, info }){ | |||||||
| 								}); | 								}); | ||||||
| 							}; | 							}; | ||||||
| 						} | 						} | ||||||
| 					`, language: "js" }) | 					`, page_id }) | ||||||
| 				), | 				), | ||||||
| 				el("div", { className: "tab" }).append( | 				el("div", { className: "tab" }).append( | ||||||
| 					el("h5", t`⚠️ Library-Dependent`), | 					el("h5", t`⚠️ Library-Dependent`), | ||||||
| @@ -117,7 +118,7 @@ export function page({ pkg, info }){ | |||||||
| 								})(element); | 								})(element); | ||||||
| 							}; | 							}; | ||||||
| 						} | 						} | ||||||
| 					`, language: "js" }) | 					`, page_id }) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| @@ -176,7 +177,7 @@ export function page({ pkg, info }){ | |||||||
| 					textContent: "All" | 					textContent: "All" | ||||||
| 				}) | 				}) | ||||||
| 			); | 			); | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "callout" }).append( | 		el("div", { className: "callout" }).append( | ||||||
| 			el("h4", t`Benefits of Signal Factories`), | 			el("h4", t`Benefits of Signal Factories`), | ||||||
| @@ -216,7 +217,7 @@ export function page({ pkg, info }){ | |||||||
| 			const counter = createEnhancedSignal(0); | 			const counter = createEnhancedSignal(0); | ||||||
| 			el("button", { textContent: "Increment", onclick: () => counter.increment() }); | 			el("button", { textContent: "Increment", onclick: () => counter.increment() }); | ||||||
| 			el("div", S.text\`Count: \${counter}\`); | 			el("div", S.text\`Count: \${counter}\`); | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -258,7 +259,7 @@ export function page({ pkg, info }){ | |||||||
| 			// Update signal value | 			// Update signal value | ||||||
| 			count.set(5); // Logs: 5 | 			count.set(5); // Logs: 5 | ||||||
| 			console.log(doubled.get()); // 10 | 			console.log(doubled.get()); // 10 | ||||||
| 		`, language: "js" }), | 		`, 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()")}, | 			The independent signals API includes all core functionality (${el("code", "S()")}, ${el("code", "S.on()")}, | ||||||
| 			${el("code", "S.action()")}). | 			${el("code", "S.action()")}). | ||||||
|   | |||||||
| @@ -43,6 +43,7 @@ const references= { | |||||||
|  |  | ||||||
| /** @param {import("./types.d.ts").PageAttrs} attrs */ | /** @param {import("./types.d.ts").PageAttrs} attrs */ | ||||||
| export function page({ pkg, info }){ | export function page({ pkg, info }){ | ||||||
|  | 	const page_id= info.id; | ||||||
| 	return el(simplePage, { info, pkg }).append( | 	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 | 			As your applications grow, performance becomes increasingly important. dd<el> provides several | ||||||
| @@ -59,7 +60,7 @@ export function page({ pkg, info }){ | |||||||
| 				el("li", t`Simple debugging for performance bottlenecks`) | 				el("li", t`Simple debugging for performance bottlenecks`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el(code, { src: fileURL("./components/examples/optimization/intro.js") }), | 		el(code, { src: fileURL("./components/examples/optimization/intro.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Memoization with memo: Native vs dd<el>`), | 		el(h3, t`Memoization with memo: Native vs dd<el>`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -83,7 +84,7 @@ export function page({ pkg, info }){ | |||||||
| 								)) | 								)) | ||||||
| 							); | 							); | ||||||
| 						} | 						} | ||||||
| 					`, language: "js" }) | 					`, page_id }) | ||||||
| 				), | 				), | ||||||
| 				el("div").append( | 				el("div").append( | ||||||
| 					el("h5", t`With dd<el>'s memo`), | 					el("h5", t`With dd<el>'s memo`), | ||||||
| @@ -101,7 +102,7 @@ export function page({ pkg, info }){ | |||||||
| 								))) | 								))) | ||||||
| 							); | 							); | ||||||
| 						} | 						} | ||||||
| 					`, language: "js" }) | 					`, page_id }) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| @@ -133,7 +134,7 @@ export function page({ pkg, info }){ | |||||||
| 						memo(todo.id, () => | 						memo(todo.id, () => | ||||||
| 							el(TodoItem, todo) | 							el(TodoItem, todo) | ||||||
| 			)))) | 			)))) | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			The ${el("code", "memo")} function in this context: | 			The ${el("code", "memo")} function in this context: | ||||||
| @@ -145,7 +146,7 @@ export function page({ pkg, info }){ | |||||||
| 			el("li", t`Only calls the generator function when rendering an item with a new key`) | 			el("li", t`Only calls the generator function when rendering an item with a new key`) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(example, { src: fileURL("./components/examples/optimization/memo.js") }), | 		el(example, { src: fileURL("./components/examples/optimization/memo.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Creating Memoization Scopes`), | 		el(h3, t`Creating Memoization Scopes`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -170,7 +171,7 @@ export function page({ pkg, info }){ | |||||||
| 			const container = el("div").append( | 			const container = el("div").append( | ||||||
| 				...items.map(item => renderItem(item)) | 				...items.map(item => renderItem(item)) | ||||||
| 			); | 			); | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			The scope function accepts options to customize its behavior: | 			The scope function accepts options to customize its behavior: | ||||||
| @@ -187,7 +188,7 @@ export function page({ pkg, info }){ | |||||||
| 				// Clear cache when signal is aborted | 				// Clear cache when signal is aborted | ||||||
| 				signal: controller.signal | 				signal: controller.signal | ||||||
| 			}); | 			}); | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			You can use custom memo scope as function in (e. g. ${el("code", "S.el(signal, renderList)")}) and as | 			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")}. | 			(Abort) signal use ${el("code", "scope.signal")}. | ||||||
| @@ -316,7 +317,7 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 			// On subsequent renders, the cached fragment is empty! | 			// On subsequent renders, the cached fragment is empty! | ||||||
| 			container.append(memoizedFragment); // Nothing gets appended | 			container.append(memoizedFragment); // Nothing gets appended | ||||||
| 		`, language: "js" }), | 		`, 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 | 			This happens because a DocumentFragment is emptied when it's appended to the DOM. When the fragment | ||||||
| @@ -337,7 +338,7 @@ export function page({ pkg, info }){ | |||||||
| 						S.el(itemsSignal, items => items.map(item => el("div", item))) | 						S.el(itemsSignal, items => items.map(item => el("div", item))) | ||||||
| 					) | 					) | ||||||
| 				); | 				); | ||||||
| 			`, language: "js" }) | 			`, page_id }) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
|   | |||||||
| @@ -43,6 +43,7 @@ const references= { | |||||||
|  |  | ||||||
| /** @param {import("./types.d.ts").PageAttrs} attrs */ | /** @param {import("./types.d.ts").PageAttrs} attrs */ | ||||||
| export function page({ pkg, info }){ | export function page({ pkg, info }){ | ||||||
|  | 	const page_id= info.id; | ||||||
| 	return el(simplePage, { info, pkg }).append( | 	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 | 			${el("a", references.todomvc).append("TodoMVC")} is a project that helps developers compare different | ||||||
| @@ -68,7 +69,7 @@ export function page({ pkg, info }){ | |||||||
| 			challenges in a clean, maintainable way. | 			challenges in a clean, maintainable way. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el(example, { src: fileURL("./components/examples/reallife/todomvc.js"), variant: "big" }), | 		el(example, { src: fileURL("./components/examples/reallife/todomvc.js"), variant: "big", page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Application Architecture Overview`), | 		el(h3, t`Application Architecture Overview`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -117,7 +118,7 @@ export function page({ pkg, info }){ | |||||||
| 				}); | 				}); | ||||||
| 			}); | 			}); | ||||||
| 			const todosRemainingS = S(()=> todosS.get().filter(todo => !todo.completed).length); | 			const todosRemainingS = S(()=> todosS.get().filter(todo => !todo.completed).length); | ||||||
| 		`, language: "js" }), | 		`, 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: | 			The ${el("code", "todosSignal")} function creates a custom signal with actions for manipulating the todos: | ||||||
| @@ -206,7 +207,7 @@ export function page({ pkg, info }){ | |||||||
| 				}); | 				}); | ||||||
| 				return out; | 				return out; | ||||||
| 			} | 			} | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -240,7 +241,7 @@ export function page({ pkg, info }){ | |||||||
| 					memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) | 					memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		`, language: "js" }), | 		`, 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, | 			The derived signal automatically recalculates whenever either the todos list or the current filter changes, | ||||||
| @@ -262,7 +263,7 @@ export function page({ pkg, info }){ | |||||||
| 				type: "checkbox" | 				type: "checkbox" | ||||||
| 			}, onToggleAll), | 			}, onToggleAll), | ||||||
| 			el("label", { htmlFor: "toggle-all", title: "Mark all as complete" }), | 			el("label", { htmlFor: "toggle-all", title: "Mark all as complete" }), | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			The "toggle all" checkbox allows users to mark all todos as completed or active. When the checkbox | 			The "toggle all" checkbox allows users to mark all todos as completed or active. When the checkbox | ||||||
| @@ -299,7 +300,7 @@ export function page({ pkg, info }){ | |||||||
| 					// Component content... | 					// Component content... | ||||||
| 				); | 				); | ||||||
| 			} | 			} | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			The TodoItem component maintains its own local UI state with signals, providing immediate | 			The TodoItem component maintains its own local UI state with signals, providing immediate | ||||||
| @@ -310,16 +311,16 @@ export function page({ pkg, info }){ | |||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			// Dynamic class attributes | 			// Dynamic class attributes | ||||||
| 			el("a", { | 			el("a", { | ||||||
| 				textContent, | 				textContent: "All", | ||||||
| 				classList: { selected: S(()=> pageS.get() === textContent.toLowerCase()) }, | 				className: S(()=> pageS.get() === "all" ? "selected" : ""), | ||||||
| 				href: \`#\${textContent.toLowerCase()}\` | 				href: "#" | ||||||
| 			}) | 			}) | ||||||
|  |  | ||||||
| 			// Reactive classList | 			// Reactive classList | ||||||
| 			el("li", { | 			el("li", { | ||||||
| 				classList: { completed: isCompleted, editing: isEditing } | 				classList: { completed: isCompleted, editing: isEditing } | ||||||
| 			}) | 			}) | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -340,7 +341,7 @@ export function page({ pkg, info }){ | |||||||
| 					memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) | 					memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			This approach ensures that: | 			This approach ensures that: | ||||||
| @@ -354,25 +355,18 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 		el("h4", t`Memoizing UI Sections`), | 		el("h4", t`Memoizing UI Sections`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			S.el(todosS, ({ length }) => !length | 			S.el(todosS, todos => memo(todos.length, length=> length | ||||||
| 				? el() | 				? el("footer", { className: "footer" }).append( | ||||||
| 				: el("footer", { className: "footer" }).append( | 					// Footer content... | ||||||
| 					// … |  | ||||||
| 					memo("filters", ()=> |  | ||||||
| 						// … |  | ||||||
| 								el("a", { |  | ||||||
| 									textContent, |  | ||||||
| 									classList: { selected: S(()=> pageS.get() === textContent.toLowerCase()) }, |  | ||||||
| 									href: \`#\${textContent.toLowerCase()}\` |  | ||||||
| 								}) |  | ||||||
| 					// … |  | ||||||
| 				) | 				) | ||||||
|  | 				: el() | ||||||
| 			)) | 			)) | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			We memoize the UI section and uses derived signal for the classList. Re-rendering this part is therefore | 			By memoizing based on the todos length, the entire footer component is only re-rendered | ||||||
| 			unnecessary when the number of todos changes. | 			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("div", { className: "tip" }).append( | ||||||
| @@ -395,11 +389,9 @@ export function page({ pkg, info }){ | |||||||
| 		`), | 		`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			// Event handlers in the main component | 			// Event handlers in the main component | ||||||
| 			const onDelete = on("todo:delete", ev => | 			const onDelete = on("todo:delete", ev => S.action(todosS, "delete", ev.detail)); | ||||||
| 				S.action(todosS, "delete", /** @type {{ detail: Todo["id"] }} */(ev).detail)); | 			const onEdit = on("todo:edit", ev => S.action(todosS, "edit", ev.detail)); | ||||||
| 			const onEdit = on("todo:edit", ev => | 		`, page_id }), | ||||||
| 				S.action(todosS, "edit", /** @type {{ detail: Partial<Todo> & { id: Todo["id"] } }} */(ev).detail)); |  | ||||||
| 		`, language: "js" }), |  | ||||||
|  |  | ||||||
| 		el("h4", t`2. The TodoItem Component with Scopes and Local State`), | 		el("h4", t`2. The TodoItem Component with Scopes and Local State`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -442,7 +434,7 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 				// Component implementation... | 				// Component implementation... | ||||||
| 			} | 			} | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -463,7 +455,7 @@ export function page({ pkg, info }){ | |||||||
| 			}).append( | 			}).append( | ||||||
| 				// Component content... | 				// Component content... | ||||||
| 			); | 			); | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			Benefits of using ${el("code", "classList")}: | 			Benefits of using ${el("code", "classList")}: | ||||||
| @@ -502,7 +494,7 @@ export function page({ pkg, info }){ | |||||||
| 				value: title, | 				value: title, | ||||||
| 				"data-id": id | 				"data-id": id | ||||||
| 			}, onBlurEdit, onKeyDown, addFocus) | 			}, onBlurEdit, onKeyDown, addFocus) | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			This approach offers several advantages: | 			This approach offers several advantages: | ||||||
| @@ -530,26 +522,27 @@ export function page({ pkg, info }){ | |||||||
| 		el("h4", t`Conditional Todo List`), | 		el("h4", t`Conditional Todo List`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			S.el(todosS, todos => todos.length | 			S.el(todosS, todos => todos.length | ||||||
| 				? el() | 				? el("main", { className: "main" }).append( | ||||||
| 				: el("main", { className: "main" }).append( |  | ||||||
| 					// Main content with toggle all and todo list | 					// Main content with toggle all and todo list | ||||||
| 				) | 				) | ||||||
|  | 				: el() | ||||||
| 			) | 			) | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("h4", t`Conditional Edit Form`), | 		el("h4", t`Conditional Edit Form`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			S.el(isEditing, editing => !editing | 			S.el(isEditing, editing => editing | ||||||
| 				? el() | 				? el("form", null, onSubmitEdit).append( | ||||||
| 				: el("form", null, onSubmitEdit).append( |  | ||||||
| 					el("input", { | 					el("input", { | ||||||
| 						className: "edit", | 						className: "edit", | ||||||
| 						name: formEdit, | 						name: "edit", | ||||||
| 						value: title, | 						value: title, | ||||||
|  | 						"data-id": id | ||||||
| 					}, onBlurEdit, onKeyDown, addFocus) | 					}, onBlurEdit, onKeyDown, addFocus) | ||||||
| 				) | 				) | ||||||
|  | 				: el() | ||||||
| 			) | 			) | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("h4", t`Conditional Clear Completed Button`), | 		el("h4", t`Conditional Clear Completed Button`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| @@ -560,7 +553,7 @@ export function page({ pkg, info }){ | |||||||
| 						{ textContent: "Clear completed", className: "clear-completed" }, | 						{ textContent: "Clear completed", className: "clear-completed" }, | ||||||
| 						onClearCompleted) | 						onClearCompleted) | ||||||
| 				) | 				) | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -606,7 +599,7 @@ export function page({ pkg, info }){ | |||||||
| 				if (event.key !== "Escape") return; | 				if (event.key !== "Escape") return; | ||||||
| 				isEditing.set(false); | 				isEditing.set(false); | ||||||
| 			}); | 			}); | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -637,7 +630,7 @@ export function page({ pkg, info }){ | |||||||
| 				${el("strong", "Declarative Class Management:")} Using the classList property for cleaner class handling | 				${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("strong", "Focus Management:")} Reliable input focus with setTimeout | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Persistent Storage:")} Automatically saving application state with signal listeners | 				${el("strong", "Persistent Storage:")} Automatically saving application state with signal listeners | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ const fileURL= url=> new URL(url, import.meta.url); | |||||||
|  |  | ||||||
| /** @param {import("./types.js").PageAttrs} attrs */ | /** @param {import("./types.js").PageAttrs} attrs */ | ||||||
| export function page({ pkg, info }){ | export function page({ pkg, info }){ | ||||||
|  | 	const page_id= info.id; | ||||||
| 	return el(simplePage, { info, pkg }).append( | 	return el(simplePage, { info, pkg }).append( | ||||||
| 		el("div", { className: "warning" }).append( | 		el("div", { className: "warning" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -44,7 +45,7 @@ export function page({ pkg, info }){ | |||||||
| 				than jsdom | 				than jsdom | ||||||
| 			`), | 			`), | ||||||
| 		), | 		), | ||||||
| 		el(code, { src: fileURL("./components/examples/ssr/intro.js") }), | 		el(code, { src: fileURL("./components/examples/ssr/intro.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Why Server-Side Rendering?`), | 		el(h3, t`Why Server-Side Rendering?`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -70,27 +71,27 @@ export function page({ pkg, info }){ | |||||||
| 			el("li", t`Provides a promise queue system for managing async operations during rendering`), | 			el("li", t`Provides a promise queue system for managing async operations during rendering`), | ||||||
| 			el("li", t`Handles DOM property/attribute mapping differences between browsers and jsdom`) | 			el("li", t`Handles DOM property/attribute mapping differences between browsers and jsdom`) | ||||||
| 		), | 		), | ||||||
| 		el(code, { src: fileURL("./components/examples/ssr/start.js") }), | 		el(code, { src: fileURL("./components/examples/ssr/start.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Basic SSR Example`), | 		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: | 			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") }), | 		el(code, { src: fileURL("./components/examples/ssr/basic-example.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Building a Static Site Generator`), | 		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 | 			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: | 			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") }), | 		el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Working with Async Content in SSR`), | 		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. | 			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. | 			This is crucial for components that fetch data or perform other async tasks. | ||||||
| 		`), | 		`), | ||||||
| 		el(code, { src: fileURL("./components/examples/ssr/async-data.js") }), | 		el(code, { src: fileURL("./components/examples/ssr/async-data.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Working with Dynamic Imports for SSR`), | 		el(h3, t`Working with Dynamic Imports for SSR`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -118,7 +119,7 @@ export function page({ pkg, info }){ | |||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			Follow this pattern when creating server-side rendered pages: | 			Follow this pattern when creating server-side rendered pages: | ||||||
| 		`), | 		`), | ||||||
| 		el(code, { src: fileURL("./components/examples/ssr/pages.js") }), | 		el(code, { src: fileURL("./components/examples/ssr/pages.js"), page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`SSR Considerations and Limitations`), | 		el(h3, t`SSR Considerations and Limitations`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -140,7 +141,7 @@ export function page({ pkg, info }){ | |||||||
| 			This documentation site itself is built using dd<el>’s SSR capabilities. | 			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. | 			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") }), | 		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, | 			The resulting static files can be deployed to any static hosting service, | ||||||
|   | |||||||
| @@ -1,8 +1,9 @@ | |||||||
| import { T, t } from "./utils/index.js"; | import { T, t } from "./utils/index.js"; | ||||||
| export const info= { | export const info= { | ||||||
| 	title: t`Ireland Components`, | 	title: t`Ireland Components`, | ||||||
| 	fullTitle: t`Server-Side Pre-Rendering and Client-Side Rehydration`, | 	fullTitle: t`Interactive Demo Components with Server-Side Pre-Rendering`, | ||||||
| 	description: t`Using Ireland components for server-side pre-rendering and client-side rehydration`, | 	description: t`Creating live, interactive component examples in documentation with server-side | ||||||
|  | 		rendering and client-side hydration.`, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| import { el } from "deka-dom-el"; | import { el } from "deka-dom-el"; | ||||||
| @@ -15,6 +16,7 @@ const fileURL= url=> new URL(url, import.meta.url); | |||||||
|  |  | ||||||
| /** @param {import("./types.js").PageAttrs} attrs */ | /** @param {import("./types.js").PageAttrs} attrs */ | ||||||
| export function page({ pkg, info }){ | export function page({ pkg, info }){ | ||||||
|  | 	const page_id= info.id; | ||||||
| 	return el(simplePage, { info, pkg }).append( | 	return el(simplePage, { info, pkg }).append( | ||||||
| 		el("div", { className: "warning" }).append( | 		el("div", { className: "warning" }).append( | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -65,7 +67,7 @@ export function page({ pkg, info }){ | |||||||
| 				src: fileURL("./components/examples/path/to/component.js"), | 				src: fileURL("./components/examples/path/to/component.js"), | ||||||
| 				exportName: "NamedExport", // optional, defaults to "default", | 				exportName: "NamedExport", // optional, defaults to "default", | ||||||
| 			}) | 			}) | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			During the build process (${el("code", "bs/docs.js")}), the following happens: | 			During the build process (${el("code", "bs/docs.js")}), the following happens: | ||||||
| @@ -117,7 +119,7 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 			// Final build step - trigger SSR end event | 			// Final build step - trigger SSR end event | ||||||
| 			dispatchEvent("onssrend"); | 			dispatchEvent("onssrend"); | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
| 		el("h4", t`File Registration`), | 		el("h4", t`File Registration`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			// From docs/ssr.js - File registration system | 			// From docs/ssr.js - File registration system | ||||||
| @@ -143,7 +145,7 @@ export function page({ pkg, info }){ | |||||||
| 				head[head instanceof HTMLScriptElement ? "src" : "href"] = file_name; | 				head[head instanceof HTMLScriptElement ? "src" : "href"] = file_name; | ||||||
| 				document.head.append(head); | 				document.head.append(head); | ||||||
| 			} | 			} | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
| 		el("h4", t`Server-Side Rendering`), | 		el("h4", t`Server-Side Rendering`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			// From docs/components/ireland.html.js - Server-side component implementation | 			// From docs/components/ireland.html.js - Server-side component implementation | ||||||
| @@ -224,7 +226,7 @@ export function page({ pkg, info }){ | |||||||
| 					\`.trim()) | 					\`.trim()) | ||||||
| 				); | 				); | ||||||
| 			} | 			} | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
| 		el("h4", t`Client-Side Hydration`), | 		el("h4", t`Client-Side Hydration`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			// From docs/components/ireland.js.js - Client-side hydration | 			// From docs/components/ireland.js.js - Client-side hydration | ||||||
| @@ -248,7 +250,7 @@ export function page({ pkg, info }){ | |||||||
| 				}); | 				}); | ||||||
| 				}); | 				}); | ||||||
| 			} | 			} | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Live Example`), | 		el(h3, t`Live Example`), | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
| @@ -257,10 +259,14 @@ export function page({ pkg, info }){ | |||||||
| 				rendered with the Ireland component system: | 				rendered with the Ireland component system: | ||||||
| 			`), | 			`), | ||||||
|  |  | ||||||
| 			el(code, { src: fileURL("./components/examples/ireland-test/counter.js") }), | 			el(code, { | ||||||
|  | 				src: fileURL("./components/examples/ireland-test/counter.js"), | ||||||
|  | 				page_id | ||||||
|  | 			}), | ||||||
| 			el(ireland, { | 			el(ireland, { | ||||||
| 				src: fileURL("./components/examples/ireland-test/counter.js"), | 				src: fileURL("./components/examples/ireland-test/counter.js"), | ||||||
| 				exportName: "CounterStandard", | 				exportName: "CounterStandard", | ||||||
|  | 				page_id | ||||||
| 			}), | 			}), | ||||||
|  |  | ||||||
| 			el("p").append(T` | 			el("p").append(T` | ||||||
|   | |||||||
| @@ -40,6 +40,7 @@ const references= { | |||||||
|  |  | ||||||
| /** @param {import("./types.d.ts").PageAttrs} attrs */ | /** @param {import("./types.d.ts").PageAttrs} attrs */ | ||||||
| export function page({ pkg, info }){ | export function page({ pkg, info }){ | ||||||
|  | 	const page_id= info.id; | ||||||
| 	return el(simplePage, { info, pkg }).append( | 	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, | 			This reference guide provides a comprehensive summary of dd<el>’s key concepts, best practices, | ||||||
| @@ -165,7 +166,7 @@ export function page({ pkg, info }){ | |||||||
| 					className: S(() => countS.get() > 10 ? 'warning' : '') | 					className: S(() => countS.get() > 10 ? 'warning' : '') | ||||||
| 				}) | 				}) | ||||||
| 			); | 			); | ||||||
| 		`, language: "js" }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Key Concepts Reference`), | 		el(h3, t`Key Concepts Reference`), | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ import { converter } from "./components/converter.html.js"; | |||||||
|  |  | ||||||
| /** @param {import("./types.d.ts").PageAttrs} attrs */ | /** @param {import("./types.d.ts").PageAttrs} attrs */ | ||||||
| export function page({ pkg, info }){ | export function page({ pkg, info }){ | ||||||
|  | 	const page_id= info.id; | ||||||
| 	return el(simplePage, { info, pkg }).append( | 	return el(simplePage, { info, pkg }).append( | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			Transitioning from HTML to dd<el> is simple with our interactive converter. This tool helps you quickly | 			Transitioning from HTML to dd<el> is simple with our interactive converter. This tool helps you quickly | ||||||
| @@ -27,7 +28,7 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		// The actual converter component | 		// The actual converter component | ||||||
| 		el(converter), | 		el(converter, { page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Next Steps`), | 		el(h3, t`Next Steps`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ const fileURL= url=> new URL(url, import.meta.url); | |||||||
|  |  | ||||||
| /** @param {import("./types.d.ts").PageAttrs} attrs */ | /** @param {import("./types.d.ts").PageAttrs} attrs */ | ||||||
| export function page({ pkg, info }){ | export function page({ pkg, info }){ | ||||||
|  | 	const page_id= info.id; | ||||||
| 	return el(simplePage, { info, pkg }).append( | 	return el(simplePage, { info, pkg }).append( | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			Real-world application examples showcasing how to build complete, production-ready interfaces with dd<el>: | 			Real-world application examples showcasing how to build complete, production-ready interfaces with dd<el>: | ||||||
| @@ -24,21 +25,23 @@ export function page({ pkg, info }){ | |||||||
| 			third-party charting library, data fetching and state management, responsive layout design, and multiple | 			third-party charting library, data fetching and state management, responsive layout design, and multiple | ||||||
| 			interactive components working together. | 			interactive components working together. | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/case-studies/data-dashboard.js"), variant: "big" }), | 		el(example, { src: fileURL("./components/examples/case-studies/data-dashboard.js"), variant: "big", page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Interactive Form`), | 		el(h3, t`Interactive Form`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			Complete form with real-time validation, conditional rendering, and responsive design. Form handling with | 			Complete form with real-time validation, conditional rendering, and responsive design. Form handling with | ||||||
| 			real-time validation, reactive UI updates, complex form state management, and clean separation of concerns. | 			real-time validation, reactive UI updates, complex form state management, and clean separation of concerns. | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/case-studies/interactive-form.js"), variant: "big" }), | 		el(example, { src: fileURL("./components/examples/case-studies/interactive-form.js"), variant: "big", page_id }), | ||||||
|  |  | ||||||
|  |  | ||||||
| 		el(h3, t`Interactive Image Gallery`), | 		el(h3, t`Interactive Image Gallery`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			Responsive image gallery with lightbox, keyboard navigation, and filtering. Dynamic loading of content, | 			Responsive image gallery with lightbox, keyboard navigation, and filtering. Dynamic loading of content, | ||||||
| 			lightbox functionality, animation handling, and keyboard and gesture navigation support. | 			lightbox functionality, animation handling, and keyboard and gesture navigation support. | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/case-studies/image-gallery.js"), variant: "big" }), | 		el(example, { src: fileURL("./components/examples/case-studies/image-gallery.js"), variant: "big", page_id }), | ||||||
|  |  | ||||||
|  |  | ||||||
| 		el(h3, t`Task Manager`), | 		el(h3, t`Task Manager`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -46,29 +49,8 @@ export function page({ pkg, info }){ | |||||||
| 			with signals, drag and drop functionality, local storage persistence, and responsive design for different | 			with signals, drag and drop functionality, local storage persistence, and responsive design for different | ||||||
| 			devices. | 			devices. | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/case-studies/task-manager.js"), variant: "big" }), | 		el(example, { src: fileURL("./components/examples/case-studies/task-manager.js"), variant: "big", page_id }), | ||||||
|  |  | ||||||
| 		el(h3, t`Product Catalog with asyncSignal`), |  | ||||||
| 		el("p").append(T` |  | ||||||
| 			Interactive product catalog with search, sorting, and pagination. Features include dynamic product filtering, |  | ||||||
| 			responsive UI with detailed view toggles, error handling with retry capability, and proper resource cleanup. |  | ||||||
| 			Demonstrates advanced signal usage, including derived signals, abortable async data fetching, and optimized |  | ||||||
| 			rendering patterns. |  | ||||||
| 		`), |  | ||||||
| 		el("div", { className: "callout" }).append( |  | ||||||
| 			el("h4", t`asyncSignal Utility`), |  | ||||||
| 			el("p").append(T` |  | ||||||
| 				This example showcases the asyncSignal utility, which is a powerful abstraction for handling async data |  | ||||||
| 				fetching with proper state management. It provides: |  | ||||||
| 			`), |  | ||||||
| 			el("ul").append( |  | ||||||
| 				el("li", t`Automatic tracking of loading, success, and error states`), |  | ||||||
| 				el("li", t`AbortController integration for request cancellation`), |  | ||||||
| 				el("li", t`Error handling and recovery`), |  | ||||||
| 				el("li", t`Options for caching previous data during loading states`) |  | ||||||
| 			) |  | ||||||
| 		), |  | ||||||
| 		el(example, { src: fileURL("./components/examples/case-studies/products.js"), variant: "big" }), |  | ||||||
|  |  | ||||||
| 		el(h3, t`TodoMVC`), | 		el(h3, t`TodoMVC`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
|   | |||||||
| @@ -1,8 +1,4 @@ | |||||||
| export { t } from "./utils/index.js"; | export { t } from "./utils/index.js"; | ||||||
| /** @type {string} */ |  | ||||||
| export let page_id; |  | ||||||
| /** @param {string} id */ |  | ||||||
| export function currentPageId(id){ page_id= id; } |  | ||||||
| export const path_target= { | export const path_target= { | ||||||
| 	root: "dist/docs/", | 	root: "dist/docs/", | ||||||
| 	css: "dist/docs/", | 	css: "dist/docs/", | ||||||
|   | |||||||
							
								
								
									
										69
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										69
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +1,12 @@ | |||||||
| { | { | ||||||
| 	"name": "deka-dom-el", | 	"name": "deka-dom-el", | ||||||
| 	"version": "0.9.4-alpha", | 	"version": "0.9.3-alpha", | ||||||
| 	"lockfileVersion": 3, | 	"lockfileVersion": 3, | ||||||
| 	"requires": true, | 	"requires": true, | ||||||
| 	"packages": { | 	"packages": { | ||||||
| 		"": { | 		"": { | ||||||
| 			"name": "deka-dom-el", | 			"name": "deka-dom-el", | ||||||
| 			"version": "0.9.4-alpha", | 			"version": "0.9.3-alpha", | ||||||
| 			"license": "MIT", | 			"license": "MIT", | ||||||
| 			"devDependencies": { | 			"devDependencies": { | ||||||
| 				"@size-limit/preset-small-lib": "~11.2", | 				"@size-limit/preset-small-lib": "~11.2", | ||||||
| @@ -15,8 +15,7 @@ | |||||||
| 				"esbuild": "~0.25", | 				"esbuild": "~0.25", | ||||||
| 				"jsdom": "~26.0", | 				"jsdom": "~26.0", | ||||||
| 				"jshint": "~2.13", | 				"jshint": "~2.13", | ||||||
| 				"nodejsscript": "^1.0", | 				"nodejsscript": "^1.0.2", | ||||||
| 				"publint": "^0.3", |  | ||||||
| 				"size-limit-node-esbuild": "~0.3" | 				"size-limit-node-esbuild": "~0.3" | ||||||
| 			}, | 			}, | ||||||
| 			"engines": { | 			"engines": { | ||||||
| @@ -615,19 +614,6 @@ | |||||||
| 				"node": ">= 8" | 				"node": ">= 8" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"node_modules/@publint/pack": { |  | ||||||
| 			"version": "0.1.2", |  | ||||||
| 			"resolved": "https://registry.npmjs.org/@publint/pack/-/pack-0.1.2.tgz", |  | ||||||
| 			"integrity": "sha512-S+9ANAvUmjutrshV4jZjaiG8XQyuJIZ8a4utWmN/vW1sgQ9IfBnPndwkmQYw53QmouOIytT874u65HEmu6H5jw==", |  | ||||||
| 			"dev": true, |  | ||||||
| 			"license": "MIT", |  | ||||||
| 			"engines": { |  | ||||||
| 				"node": ">=18" |  | ||||||
| 			}, |  | ||||||
| 			"funding": { |  | ||||||
| 				"url": "https://bjornlu.com/sponsor" |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 		"node_modules/@sindresorhus/merge-streams": { | 		"node_modules/@sindresorhus/merge-streams": { | ||||||
| 			"version": "2.3.0", | 			"version": "2.3.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", | 			"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", | ||||||
| @@ -2243,16 +2229,6 @@ | |||||||
| 				"node": ">=4" | 				"node": ">=4" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"node_modules/package-manager-detector": { |  | ||||||
| 			"version": "0.2.11", |  | ||||||
| 			"resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz", |  | ||||||
| 			"integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==", |  | ||||||
| 			"dev": true, |  | ||||||
| 			"license": "MIT", |  | ||||||
| 			"dependencies": { |  | ||||||
| 				"quansync": "^0.2.7" |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 		"node_modules/parse5": { | 		"node_modules/parse5": { | ||||||
| 			"version": "7.2.1", | 			"version": "7.2.1", | ||||||
| 			"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", | 			"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", | ||||||
| @@ -2330,28 +2306,6 @@ | |||||||
| 				"url": "https://github.com/sponsors/jonschlinkert" | 				"url": "https://github.com/sponsors/jonschlinkert" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"node_modules/publint": { |  | ||||||
| 			"version": "0.3.9", |  | ||||||
| 			"resolved": "https://registry.npmjs.org/publint/-/publint-0.3.9.tgz", |  | ||||||
| 			"integrity": "sha512-irTwfRfYW38vomkxxoiZQtFtUOQKpz5m0p9Z60z4xpXrl1KmvSrX1OMARvnnolB5usOXeNfvLj6d/W3rwXKfBQ==", |  | ||||||
| 			"dev": true, |  | ||||||
| 			"license": "MIT", |  | ||||||
| 			"dependencies": { |  | ||||||
| 				"@publint/pack": "^0.1.2", |  | ||||||
| 				"package-manager-detector": "^0.2.9", |  | ||||||
| 				"picocolors": "^1.1.1", |  | ||||||
| 				"sade": "^1.8.1" |  | ||||||
| 			}, |  | ||||||
| 			"bin": { |  | ||||||
| 				"publint": "src/cli.js" |  | ||||||
| 			}, |  | ||||||
| 			"engines": { |  | ||||||
| 				"node": ">=18" |  | ||||||
| 			}, |  | ||||||
| 			"funding": { |  | ||||||
| 				"url": "https://bjornlu.com/sponsor" |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 		"node_modules/punycode": { | 		"node_modules/punycode": { | ||||||
| 			"version": "2.3.1", | 			"version": "2.3.1", | ||||||
| 			"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", | 			"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", | ||||||
| @@ -2362,23 +2316,6 @@ | |||||||
| 				"node": ">=6" | 				"node": ">=6" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"node_modules/quansync": { |  | ||||||
| 			"version": "0.2.10", |  | ||||||
| 			"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.10.tgz", |  | ||||||
| 			"integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==", |  | ||||||
| 			"dev": true, |  | ||||||
| 			"funding": [ |  | ||||||
| 				{ |  | ||||||
| 					"type": "individual", |  | ||||||
| 					"url": "https://github.com/sponsors/antfu" |  | ||||||
| 				}, |  | ||||||
| 				{ |  | ||||||
| 					"type": "individual", |  | ||||||
| 					"url": "https://github.com/sponsors/sxzz" |  | ||||||
| 				} |  | ||||||
| 			], |  | ||||||
| 			"license": "MIT" |  | ||||||
| 		}, |  | ||||||
| 		"node_modules/queue-microtask": { | 		"node_modules/queue-microtask": { | ||||||
| 			"version": "1.2.3", | 			"version": "1.2.3", | ||||||
| 			"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", | 			"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,13 +1,13 @@ | |||||||
| { | { | ||||||
| 	"name": "deka-dom-el", | 	"name": "deka-dom-el", | ||||||
| 	"version": "0.9.5-alpha", | 	"version": "0.9.3-alpha", | ||||||
| 	"description": "A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks.", | 	"description": "A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks.", | ||||||
| 	"author": "Jan Andrle <andrle.jan@centrum.cz>", | 	"author": "Jan Andrle <andrle.jan@centrum.cz>", | ||||||
| 	"license": "MIT", | 	"license": "MIT", | ||||||
| 	"homepage": "https://github.com/jaandrle/deka-dom-el", | 	"homepage": "https://github.com/jaandrle/deka-dom-el", | ||||||
| 	"repository": { | 	"repository": { | ||||||
| 		"type": "git", | 		"type": "git", | ||||||
| 		"url": "git+ssh://git@github.com/jaandrle/deka-dom-el.git" | 		"url": "git@github.com:jaandrle/deka-dom-el.git" | ||||||
| 	}, | 	}, | ||||||
| 	"bugs": { | 	"bugs": { | ||||||
| 		"url": "https://github.com/jaandrle/deka-dom-el/issues" | 		"url": "https://github.com/jaandrle/deka-dom-el/issues" | ||||||
| @@ -17,20 +17,20 @@ | |||||||
| 	"type": "module", | 	"type": "module", | ||||||
| 	"exports": { | 	"exports": { | ||||||
| 		".": { | 		".": { | ||||||
| 			"types": "./index.d.ts", | 			"import": "./index.js", | ||||||
| 			"import": "./index.js" | 			"types": "./index.d.ts" | ||||||
| 		}, | 		}, | ||||||
| 		"./signals": { | 		"./signals": { | ||||||
| 			"types": "./signals.d.ts", | 			"import": "./signals.js", | ||||||
| 			"import": "./signals.js" | 			"types": "./signals.d.ts" | ||||||
| 		}, | 		}, | ||||||
| 		"./jsdom": { | 		"./jsdom": { | ||||||
| 			"types": "./jsdom.d.ts", | 			"import": "./jsdom.js", | ||||||
| 			"import": "./jsdom.js" | 			"types": "./jsdom.d.ts" | ||||||
| 		}, | 		}, | ||||||
| 		"./src/signals-lib": { | 		"./src/signals-lib": { | ||||||
| 			"types": "./src/signals-lib/signals-lib.d.ts", | 			"import": "./src/signals-lib/signals-lib.js", | ||||||
| 			"import": "./src/signals-lib/signals-lib.js" | 			"types": "./src/signals-lib/signals-lib.d.ts" | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 	"files": [ | 	"files": [ | ||||||
| @@ -52,6 +52,7 @@ | |||||||
| 		"maxdepth": 3, | 		"maxdepth": 3, | ||||||
| 		"maxcomplexity": 14, | 		"maxcomplexity": 14, | ||||||
| 		"globals": { | 		"globals": { | ||||||
|  | 			"requestIdleCallback": false, | ||||||
| 			"AbortController": false, | 			"AbortController": false, | ||||||
| 			"AbortSignal": false, | 			"AbortSignal": false, | ||||||
| 			"FinalizationRegistry": false | 			"FinalizationRegistry": false | ||||||
| @@ -60,25 +61,25 @@ | |||||||
| 	"size-limit": [ | 	"size-limit": [ | ||||||
| 		{ | 		{ | ||||||
| 			"path": "./index.js", | 			"path": "./index.js", | ||||||
| 			"limit": "10 kB", | 			"limit": "10.5 kB", | ||||||
| 			"gzip": false, | 			"gzip": false, | ||||||
| 			"brotli": false | 			"brotli": false | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"path": "./signals.js", | 			"path": "./signals.js", | ||||||
| 			"limit": "12.2 kB", | 			"limit": "12.5 kB", | ||||||
| 			"gzip": false, | 			"gzip": false, | ||||||
| 			"brotli": false | 			"brotli": false | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"path": "./index-with-signals.js", | 			"path": "./index-with-signals.js", | ||||||
| 			"limit": "14.75 kB", | 			"limit": "15 kB", | ||||||
| 			"gzip": false, | 			"gzip": false, | ||||||
| 			"brotli": false | 			"brotli": false | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"path": "./index-with-signals.js", | 			"path": "./index-with-signals.js", | ||||||
| 			"limit": "5.25 kB" | 			"limit": "5.5 kB" | ||||||
| 		} | 		} | ||||||
| 	], | 	], | ||||||
| 	"modifyEsbuildConfig": { | 	"modifyEsbuildConfig": { | ||||||
| @@ -88,7 +89,7 @@ | |||||||
| 		"test": "echo \"Error: no tests yet\"", | 		"test": "echo \"Error: no tests yet\"", | ||||||
| 		"build": "bs/build.js", | 		"build": "bs/build.js", | ||||||
| 		"lint": "bs/lint.sh", | 		"lint": "bs/lint.sh", | ||||||
| 		"docs": "bs/docs.js" | 		"docs": "bs/docs.sh" | ||||||
| 	}, | 	}, | ||||||
| 	"keywords": [ | 	"keywords": [ | ||||||
| 		"dom", | 		"dom", | ||||||
| @@ -103,8 +104,7 @@ | |||||||
| 		"esbuild": "~0.25", | 		"esbuild": "~0.25", | ||||||
| 		"jsdom": "~26.0", | 		"jsdom": "~26.0", | ||||||
| 		"jshint": "~2.13", | 		"jshint": "~2.13", | ||||||
| 		"nodejsscript": "^1.0", | 		"nodejsscript": "^1.0.2", | ||||||
| 		"publint": "^0.3", |  | ||||||
| 		"size-limit-node-esbuild": "~0.3" | 		"size-limit-node-esbuild": "~0.3" | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -51,9 +51,9 @@ export function createElement(tag, attributes, ...addons){ | |||||||
| 			const host= (...c)=> !c.length ? el_host : | 			const host= (...c)=> !c.length ? el_host : | ||||||
| 				(scoped===1 ? addons.unshift(...c) : c.forEach(c=> c(el_host)), undefined); | 				(scoped===1 ? addons.unshift(...c) : c.forEach(c=> c(el_host)), undefined); | ||||||
| 			scope.push({ scope: tag, host }); | 			scope.push({ scope: tag, host }); | ||||||
| 			el= /** @type {Element} */(tag(attributes || undefined)); | 			el= tag(attributes || undefined); | ||||||
| 			if(el.nodeName==="#comment") break; |  | ||||||
| 			const is_fragment= isInstance(el, env.F); | 			const is_fragment= isInstance(el, env.F); | ||||||
|  | 			if(el.nodeName==="#comment") break; | ||||||
| 			const el_mark= createElement.mark({ | 			const el_mark= createElement.mark({ | ||||||
| 				type: "component", | 				type: "component", | ||||||
| 				name: tag.name, | 				name: tag.name, | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { enviroment as env, evc, evd } from './common.js'; | import { enviroment as env, evc, evd } from './common.js'; | ||||||
| import { isInstance, requestIdle } from "../helpers.js"; | import { isInstance } from "../helpers.js"; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Connection changes observer for tracking element connection/disconnection |  * Connection changes observer for tracking element connection/disconnection | ||||||
| @@ -149,6 +149,15 @@ function connectionsChangesObserverConstructor(){ | |||||||
| 		observer.disconnect(); | 		observer.disconnect(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	//TODO: remount support? | ||||||
|  | 	/** | ||||||
|  | 	 * Schedule a task during browser idle time | ||||||
|  | 	 * @returns {Promise<void>} Promise that resolves when browser is idle | ||||||
|  | 	 */ | ||||||
|  | 	function requestIdle(){ return new Promise(function(resolve){ | ||||||
|  | 		(requestIdleCallback || requestAnimationFrame)(resolve); | ||||||
|  | 	}); } | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 * Collects child elements from the store that are contained by the given element | 	 * Collects child elements from the store that are contained by the given element | ||||||
| 	 * @param {Element} element - Parent element | 	 * @param {Element} element - Parent element | ||||||
|   | |||||||
| @@ -68,12 +68,3 @@ export function observedAttributes(instance, observedAttribute){ | |||||||
|  * @returns {string} The camelCase string |  * @returns {string} The camelCase string | ||||||
|  */ |  */ | ||||||
| function kebabToCamel(name){ return name.replace(/-./g, x=> x[1].toUpperCase()); } | function kebabToCamel(name){ return name.replace(/-./g, x=> x[1].toUpperCase()); } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Schedule a task during browser idle time |  | ||||||
|  * @returns {Promise<void>} Promise that resolves when browser is idle |  | ||||||
|  */ |  | ||||||
| export function requestIdle(){ return new Promise(function(resolve){ |  | ||||||
| 		(globalThis.requestIdleCallback || requestAnimationFrame)(resolve); |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -1,14 +1,15 @@ | |||||||
| import { queueSignalWrite, mark } from "./helpers.js"; | import { queueSignalWrite, mark } from "./helpers.js"; | ||||||
| export { mark }; | export { mark }; | ||||||
| import { hasOwn, oCreate, oAssign, requestIdle } from "../helpers.js"; | import { hasOwn, oCreate, oAssign } from "../helpers.js"; | ||||||
|  |  | ||||||
| const SignalReadOnly= oCreate(null, { | const Signal = oCreate(null, { | ||||||
| 	get: { value(){ return read(this); } }, | 	get: { value(){ return read(this); } }, | ||||||
|  | 	set: { value(...v){ return write(this, ...v); } }, | ||||||
| 	toJSON: { value(){ return read(this); } }, | 	toJSON: { value(){ return read(this); } }, | ||||||
| 	valueOf: { value(){ return this[mark] && this[mark].value; } } | 	valueOf: { value(){ return this[mark] && this[mark].value; } } | ||||||
| }); | }); | ||||||
| const Signal = oCreate(SignalReadOnly, { | const SignalReadOnly= oCreate(Signal, { | ||||||
| 	set: { value(...v){ return write(this, ...v); } }, | 	set: { value(){ return; } }, | ||||||
| }); | }); | ||||||
| /** | /** | ||||||
|  * Checks if a value is a signal |  * Checks if a value is a signal | ||||||
| @@ -213,7 +214,7 @@ signal.el= function(s, map){ | |||||||
|  */ |  */ | ||||||
| function requestCleanUpReactives(host){ | function requestCleanUpReactives(host){ | ||||||
| 	if(!host || !host[key_reactive]) return; | 	if(!host || !host[key_reactive]) return; | ||||||
| 	requestIdle().then(function(){ | 	(requestIdleCallback || setTimeout)(function(){ | ||||||
| 		host[key_reactive]= host[key_reactive] | 		host[key_reactive]= host[key_reactive] | ||||||
| 			.filter(([ s, el ])=> el.isConnected ? true : (removeSignalListener(...s), false)); | 			.filter(([ s, el ])=> el.isConnected ? true : (removeSignalListener(...s), false)); | ||||||
| 	}); | 	}); | ||||||
| @@ -313,14 +314,9 @@ export const signals_config= { | |||||||
| function removeSignalsFromElements(s, listener, ...notes){ | function removeSignalsFromElements(s, listener, ...notes){ | ||||||
| 	const { current }= scope; | 	const { current }= scope; | ||||||
| 	current.host(function(element){ | 	current.host(function(element){ | ||||||
| 		const is_first= !element[key_reactive]; | 		if(!element[key_reactive]) element[key_reactive]= []; | ||||||
| 		if(is_first) element[key_reactive]= []; |  | ||||||
| 		element[key_reactive].push([ [ s, listener ], ...notes ]); | 		element[key_reactive].push([ [ s, listener ], ...notes ]); | ||||||
| 		if( | 		if(current.prevent) return; // typically document.body, doenst need auto-remove as it should happen on page leave | ||||||
| 			!is_first |  | ||||||
| 			// typically document.body, doenst need auto-remove as it should happen on page leave |  | ||||||
| 			|| current.prevent |  | ||||||
| 		) return; |  | ||||||
| 		on.disconnected(()=> | 		on.disconnected(()=> | ||||||
| 			/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, …?). | 			/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, …?). | ||||||
| 				You can investigate the `__dde_reactive` key of the element. */ | 				You can investigate the `__dde_reactive` key of the element. */ | ||||||
| @@ -348,7 +344,7 @@ const cleanUpRegistry = new FinalizationRegistry(function(s){ | |||||||
|  */ |  */ | ||||||
| function create(is_readonly, value, actions){ | function create(is_readonly, value, actions){ | ||||||
| 	const varS = oCreate(is_readonly ? SignalReadOnly : Signal); | 	const varS = oCreate(is_readonly ? SignalReadOnly : Signal); | ||||||
| 	const SI= toSignal(varS, value, actions); | 	const SI= toSignal(varS, value, actions, is_readonly); | ||||||
| 	cleanUpRegistry.register(SI, SI[mark]); | 	cleanUpRegistry.register(SI, SI[mark]); | ||||||
| 	return SI; | 	return SI; | ||||||
| } | } | ||||||
| @@ -371,10 +367,11 @@ const protoSigal= oAssign(oCreate(), { | |||||||
|  * @param {Object} s - Object to transform |  * @param {Object} s - Object to transform | ||||||
|  * @param {any} value - Initial value |  * @param {any} value - Initial value | ||||||
|  * @param {Object} actions - Custom actions |  * @param {Object} actions - Custom actions | ||||||
|  |  * @param {boolean} [readonly=false] - Whether the signal is readonly | ||||||
|  * @returns {Object} Signal object with get() and set() methods |  * @returns {Object} Signal object with get() and set() methods | ||||||
|  * @private |  * @private | ||||||
|  */ |  */ | ||||||
| function toSignal(s, value, actions){ | function toSignal(s, value, actions, readonly= false){ | ||||||
| 	const onclear= []; | 	const onclear= []; | ||||||
| 	if(typeOf(actions)!=="[object Object]") | 	if(typeOf(actions)!=="[object Object]") | ||||||
| 		actions= {}; | 		actions= {}; | ||||||
| @@ -388,6 +385,7 @@ function toSignal(s, value, actions){ | |||||||
| 		value: oAssign(oCreate(protoSigal), { | 		value: oAssign(oCreate(protoSigal), { | ||||||
| 			value, actions, onclear, host, | 			value, actions, onclear, host, | ||||||
| 			listeners: new Set(), | 			listeners: new Set(), | ||||||
|  | 			readonly | ||||||
| 		}), | 		}), | ||||||
| 		enumerable: false, | 		enumerable: false, | ||||||
| 		writable: false, | 		writable: false, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user