mirror of
				https://github.com/jaandrle/deka-dom-el
				synced 2025-10-31 13:59:14 +01:00 
			
		
		
		
	Compare commits
	
		
			5 Commits
		
	
	
		
			93b905e677
			...
			v0.9.5-alp
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f2c85ec983 | |||
| 4c450ae763 | |||
| 04f93345f8 | |||
| 5076771410 | |||
| f0dfdfde54 | 
							
								
								
									
										40
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | --- | ||||||
|  | name: Bug report | ||||||
|  | about: Create a report to help us improve | ||||||
|  | title: ":bug: " | ||||||
|  | labels: bug | ||||||
|  | assignees: '' | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## Bug Description | ||||||
|  | <!-- A clear and concise description of what the bug is --> | ||||||
|  |  | ||||||
|  | ## Steps to Reproduce | ||||||
|  | <!-- Steps to reproduce the behavior --> | ||||||
|  | 1.  | ||||||
|  | 2.  | ||||||
|  | 3.  | ||||||
|  |  | ||||||
|  | ## Expected Behavior | ||||||
|  | <!-- A clear and concise description of what you expected to happen --> | ||||||
|  |  | ||||||
|  | ## Actual Behavior | ||||||
|  | <!-- A clear and concise description of what actually happened --> | ||||||
|  |  | ||||||
|  | ## Code Sample | ||||||
|  | <!-- If applicable, add minimal code sample to reproduce the issue --> | ||||||
|  | ```js | ||||||
|  | // Your code here | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Environment | ||||||
|  | - Browser and version: <!-- e.g. Chrome 120, Firefox 120, Safari 17 --> | ||||||
|  | - OS: <!-- e.g. Windows 11, macOS Sonoma, Ubuntu 22.04 --> | ||||||
|  | - dd<el> version: <!-- e.g. 0.9.2 --> | ||||||
|  | - Other relevant details: | ||||||
|  |  | ||||||
|  | ## Screenshots | ||||||
|  | <!-- If applicable, add screenshots to help explain your problem --> | ||||||
|  |  | ||||||
|  | ## Additional Context | ||||||
|  | <!-- Add any other context about the problem here --> | ||||||
							
								
								
									
										22
									
								
								.github/ISSUE_TEMPLATE/documentation.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.github/ISSUE_TEMPLATE/documentation.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | --- | ||||||
|  | name: Documentation improvement | ||||||
|  | about: Suggest improvements to the documentation | ||||||
|  | title: ":abc: " | ||||||
|  | labels: documentation | ||||||
|  | assignees: '' | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## Documentation Area | ||||||
|  | <!-- Which part of the documentation needs improvement? Provide links if applicable --> | ||||||
|  |  | ||||||
|  | ## Current Issue | ||||||
|  | <!-- What's currently unclear, missing, or incorrect in the documentation? --> | ||||||
|  |  | ||||||
|  | ## Suggested Improvement | ||||||
|  | <!-- Describe the improvement or addition you'd like to see --> | ||||||
|  |  | ||||||
|  | ## Example Content | ||||||
|  | <!-- If applicable, provide example content or wording --> | ||||||
|  |  | ||||||
|  | ## Additional Context | ||||||
|  | <!-- Any other context or screenshots about the documentation request --> | ||||||
							
								
								
									
										29
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | --- | ||||||
|  | name: Feature request | ||||||
|  | about: Suggest an idea for this project | ||||||
|  | title: ":zap: " | ||||||
|  | labels: enhancement | ||||||
|  | assignees: '' | ||||||
|  | --- | ||||||
|  | <!-- Consider open discussion: https://github.com/jaandrle/deka-dom-el/discussions first --> | ||||||
|  |  | ||||||
|  | ## Problem Statement | ||||||
|  | <!-- A clear and concise description of the problem this feature would solve --> | ||||||
|  |  | ||||||
|  | ## Proposed Solution | ||||||
|  | <!-- A detailed description of the feature you're suggesting --> | ||||||
|  |  | ||||||
|  | ## Use Cases | ||||||
|  | <!-- Describe specific use cases where this feature would be beneficial --> | ||||||
|  |  | ||||||
|  | ## Example Implementation | ||||||
|  | <!-- If possible, provide example code or pseudocode for how this feature might work --> | ||||||
|  | ```js | ||||||
|  | // Example code | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Alternatives Considered | ||||||
|  | <!-- A description of any alternative solutions or features you've considered --> | ||||||
|  |  | ||||||
|  | ## Additional Context | ||||||
|  | <!-- Any other context, screenshots, or examples that might be helpful --> | ||||||
							
								
								
									
										39
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | <!--  | ||||||
|  | Please use an appropriate git3moji in your PR title: https://robinpokorny.github.io/git3moji/ | ||||||
|  | Examples: | ||||||
|  | - :bug: Fix signal update not triggering on nested properties | ||||||
|  | - :zap: Improve event delegation performance | ||||||
|  | - :abc: Add documentation for custom elements | ||||||
|  | --> | ||||||
|  |  | ||||||
|  | ## Description | ||||||
|  | <!-- Describe the changes introduced by this PR --> | ||||||
|  |  | ||||||
|  | ## Related Issues | ||||||
|  | <!-- Link any related issues using the format #ISSUE_NUMBER --> | ||||||
|  |  | ||||||
|  | ## Type of Change | ||||||
|  | - [ ] Bug fix (non-breaking change that fixes an issue) | ||||||
|  | - [ ] New feature (non-breaking change that adds functionality) | ||||||
|  | - [ ] Breaking change (fix or feature that would cause existing functionality to change) | ||||||
|  | - [ ] Documentation update | ||||||
|  | - [ ] Code refactoring | ||||||
|  | - [ ] Performance improvement | ||||||
|  | - [ ] Test update | ||||||
|  |  | ||||||
|  | ## Testing Performed | ||||||
|  | <!-- Describe the tests you've done to verify your changes --> | ||||||
|  |  | ||||||
|  | ## Screenshots | ||||||
|  | <!-- If applicable, add screenshots to help explain your changes --> | ||||||
|  |  | ||||||
|  | ## Checklist | ||||||
|  | - [ ] My code follows the code style of this project | ||||||
|  | - [ ] I have performed a self-review of my own code | ||||||
|  | - [ ] I have added tests that prove my fix is effective or that my feature works | ||||||
|  | - [ ] I have updated the documentation accordingly | ||||||
|  | - [ ] My changes generate no new warnings | ||||||
|  | - [ ] All existing tests are passing | ||||||
|  |  | ||||||
|  | ## Additional Notes | ||||||
|  | <!-- Any additional information that might be helpful for reviewers --> | ||||||
							
								
								
									
										18
									
								
								.github/workflows/npm-publish.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/npm-publish.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | 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
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.npmrc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} | ||||||
|  | registry=https://registry.npmjs.org/ | ||||||
|  | always-auth=true | ||||||
							
								
								
									
										134
									
								
								CODE_OF_CONDUCT.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								CODE_OF_CONDUCT.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | |||||||
|  |  | ||||||
|  | # 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 | ||||||
|  |  | ||||||
							
								
								
									
										177
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | |||||||
|  | # Contributing to Deka DOM Elements | ||||||
|  |  | ||||||
|  | Thank you for your interest in contributing to Deka DOM Elements (dd<el> or DDE)! This document provides guidelines and | ||||||
|  | instructions for contributing to the project. | ||||||
|  |  | ||||||
|  | ## Table of Contents | ||||||
|  |  | ||||||
|  | - [Code of Conduct](#code-of-conduct) | ||||||
|  | - [Getting Started](#getting-started) | ||||||
|  | - [Development Workflow](#development-workflow) | ||||||
|  | - [Commit Guidelines](#commit-guidelines) | ||||||
|  | - [Pull Request Process](#pull-request-process) | ||||||
|  | - [Issue Guidelines](#issue-guidelines) | ||||||
|  | - [Coding Standards](#coding-standards) | ||||||
|  | - [Testing](#testing) | ||||||
|  | - [Documentation](#documentation) | ||||||
|  |  | ||||||
|  | ## Code of Conduct | ||||||
|  |  | ||||||
|  | Please be respectful and inclusive in your interactions with other contributors. We aim to foster a welcoming community | ||||||
|  | where everyone feels comfortable participating. | ||||||
|  |  | ||||||
|  | ## Getting Started | ||||||
|  |  | ||||||
|  | 1. **Fork the repository**: | ||||||
|  | 	- Click the "Fork" button on the GitHub repository | ||||||
|  |  | ||||||
|  | 2. **Clone your fork**: | ||||||
|  | 	```bash | ||||||
|  | 	git clone https://github.com/YOUR-USERNAME/deka-dom-el.git | ||||||
|  | 	cd deka-dom-el | ||||||
|  | 	``` | ||||||
|  |  | ||||||
|  | 3. **Set up the development environment**: | ||||||
|  | 	```bash | ||||||
|  | 	npm ci | ||||||
|  | 	``` | ||||||
|  |  | ||||||
|  | 4. **Add the upstream repository**: | ||||||
|  | 	```bash | ||||||
|  | 	git remote add upstream https://github.com/jaandrle/deka-dom-el.git | ||||||
|  | 	``` | ||||||
|  |  | ||||||
|  | ## Development Workflow | ||||||
|  |  | ||||||
|  | 1. **Create a new branch**: | ||||||
|  | 	```bash | ||||||
|  | 	git checkout -b your-feature-branch | ||||||
|  | 	``` | ||||||
|  | 	Use descriptive branch names that reflect the changes you're making. | ||||||
|  |  | ||||||
|  | 2. **Make your changes**: | ||||||
|  | 	- Write clean, modular code | ||||||
|  | 	- Follow the project's coding standards (see [Coding Standards](#coding-standards)) | ||||||
|  | 	- Include relevant tests for your changes | ||||||
|  |  | ||||||
|  | 3. ~**Run tests**:~ | ||||||
|  | 	```bash | ||||||
|  | 	#npm test | ||||||
|  | 	``` | ||||||
|  |  | ||||||
|  | 4. **Build the project**: | ||||||
|  | 	```bash | ||||||
|  | 	npm run build | ||||||
|  | 	#or | ||||||
|  | 	bs/build.js | ||||||
|  | 	``` | ||||||
|  |  | ||||||
|  | 5. **Preview documentation changes** (if applicable): | ||||||
|  | 	```bash | ||||||
|  | 	npm run docs | ||||||
|  | 	#or | ||||||
|  | 	bs/docs.js | ||||||
|  | 	``` | ||||||
|  |  | ||||||
|  | …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 | ||||||
|  |  | ||||||
|  | We use [git3moji](https://git3moji.netlify.app/) for commit messages. This helps keep the commit history clear and | ||||||
|  | consistent. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | :emoji: Short summary of the change | ||||||
|  | ``` | ||||||
|  | …for example: | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | :bug: Fix signal update not triggering on nested properties | ||||||
|  | :zap: Improve event delegation performance | ||||||
|  | :abc: Add documentation for custom elements | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Pull Request Process | ||||||
|  |  | ||||||
|  | 1. **Push your changes**: | ||||||
|  | 	```bash | ||||||
|  | 	git push origin your-feature-branch | ||||||
|  | 	``` | ||||||
|  |  | ||||||
|  | 2. **Open a Pull Request**: | ||||||
|  | 	- Go to the repository on GitHub | ||||||
|  | 	- Click "New Pull Request" | ||||||
|  | 	- Select your branch | ||||||
|  | 	- Provide a clear description of your changes | ||||||
|  |  | ||||||
|  | 3. **PR Guidelines**: | ||||||
|  | 	- Use a clear, descriptive title with the appropriate git3moji | ||||||
|  | 	- Reference any related issues | ||||||
|  | 	- Explain what the changes do and why they are needed | ||||||
|  | 	- List any dependencies that are required for the change | ||||||
|  | 	- Include screenshots or examples if applicable | ||||||
|  |  | ||||||
|  | 4. **Code Review**: | ||||||
|  | 	- Address any feedback from reviewers | ||||||
|  | 	- Make necessary changes and push to your branch | ||||||
|  | 	- The PR will be updated automatically | ||||||
|  |  | ||||||
|  | 5. **Merge**: | ||||||
|  | 	- Once approved, a maintainer will merge your PR | ||||||
|  | 	- The main branch is protected, so you cannot push directly to it | ||||||
|  |  | ||||||
|  | ## Issue Guidelines | ||||||
|  |  | ||||||
|  | When creating an issue, please use the appropriate template and include as much information as possible: | ||||||
|  |  | ||||||
|  | ### Bug Reports | ||||||
|  |  | ||||||
|  | - Use the `:bug:` emoji in the title | ||||||
|  | - Clearly describe the issue | ||||||
|  | - Include steps to reproduce | ||||||
|  | - Mention your environment (browser, OS, etc.) | ||||||
|  | - Add screenshots if applicable | ||||||
|  |  | ||||||
|  | ### Feature Requests | ||||||
|  |  | ||||||
|  | - Use the `:zap:` emoji in the title | ||||||
|  | - Describe the feature clearly | ||||||
|  | - Explain why it would be valuable | ||||||
|  | - Include examples or mockups if possible | ||||||
|  |  | ||||||
|  | ### Documentation Improvements | ||||||
|  |  | ||||||
|  | - Use the `:abc:` emoji in the title | ||||||
|  | - Identify what documentation needs improvement | ||||||
|  | - Suggest specific changes or additions | ||||||
|  |  | ||||||
|  | ## Coding Standards | ||||||
|  |  | ||||||
|  | - Follow the existing code style in the project | ||||||
|  | - Use meaningful variable and function names | ||||||
|  | - Keep functions small and focused | ||||||
|  | - Add comments for complex logic | ||||||
|  | - Use TypeScript types appropriately | ||||||
|  |  | ||||||
|  | <!-- | ||||||
|  | ## Testing | ||||||
|  |  | ||||||
|  | - Add tests for new features | ||||||
|  | - Update tests for modified code | ||||||
|  | - Ensure all tests pass before submitting a PR | ||||||
|  | --> | ||||||
|  |  | ||||||
|  | ## Documentation | ||||||
|  |  | ||||||
|  | - Update the documentation when you add or modify features | ||||||
|  | - Document both API usage and underlying concepts | ||||||
|  | - Use clear, concise language | ||||||
|  | - Include examples where appropriate | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | Thank you for contributing to Deka DOM Elements! Your efforts help make the project better for everyone. | ||||||
							
								
								
									
										152
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										152
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,57 +1,53 @@ | |||||||
| **WIP** (the experimentation phase) | **Alpha** | ||||||
| | [source code on GitHub](https://github.com/jaandrle/deka-dom-el) | | [Docs&Examples](https://jaandrle.github.io/deka-dom-el "Official documentation and guide site") | ||||||
| | [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el) | | [NPM](https://www.npmjs.com/package/deka-dom-el "Official NPM package page") | ||||||
|  | | [GitHub](https://github.com/jaandrle/deka-dom-el "Official GitHub repository") | ||||||
| <p align="center"> | ([*Gitea*](https://gitea.jaandrle.cz/jaandrle/deka-dom-el "GitHub repository mirror on my own Gitea instance")) | ||||||
| 	<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180"> |  | ||||||
| </p> |  | ||||||
|  |  | ||||||
| # Deka DOM Elements (dd\<el\> or DDE) |  | ||||||
|  |  | ||||||
| ***Vanilla for flavouring — a full-fledged feast for large projects*** | ***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 | ```javascript | ||||||
| // 🌟 Reactive component with clear separation of concerns | // 🌟 Reactive component with clear separation of concerns | ||||||
| document.body.append( | document.body.append( | ||||||
| 	el(EmojiCounter, { initial: "🚀" }) | 	el(EmojiCounter, { initial: "🚀" }), | ||||||
| ); | ); | ||||||
|  |  | ||||||
| function EmojiCounter({ initial }) { | function EmojiCounter({ initial }) { | ||||||
| 	// ✨ State - Define reactive data | 	// ✨ - Define reactive data | ||||||
| 	const count = S(0); | 	const count = S(0); | ||||||
| 	const emoji = S(initial); | 	const emoji = S(initial); | ||||||
|  | 	const textContent = S(() => `Hello World ${emoji.get().repeat(count.get())}`); | ||||||
|  |  | ||||||
| 	/** @param {HTMLOptionElement} el */ | 	// 🔄 - UI updates automatically when signals change | ||||||
| 	const isSelected= el=> (el.selected= el.value===initial); |  | ||||||
|  |  | ||||||
| 	// 🔄 View - UI updates automatically when signals change |  | ||||||
| 	return el().append( | 	return el().append( | ||||||
| 		el("p", { | 		el("p", { textContent, className: "output" }), | ||||||
| 			className: "output", |  | ||||||
| 			textContent: S(() => |  | ||||||
| 				`Hello World ${emoji.get().repeat(clicks.get())}`), |  | ||||||
| 		}), |  | ||||||
|  |  | ||||||
| 		// 🎮 Controls - Update state on events | 		// 🎮 - Update state on events | ||||||
| 		el("button", { textContent: "Add Emoji" }, | 		el("button", { textContent: "Add Emoji" }, | ||||||
| 			on("click", () => count.set(count.get() + 1)) | 			on("click", () => count.set(count.get() + 1)), | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("select", null, | 		el("select", null, | ||||||
| 			on("change", e => emoji.set(e.target.value)) | 			on.defer(el=> el.value= initial), | ||||||
|  | 			on("change", e => emoji.set(e.target.value)), | ||||||
| 		).append( | 		).append( | ||||||
| 			el(Option, "🎉", isSelected), | 			el(Option, "🎉"), | ||||||
| 			el(Option, "🚀", isSelected), | 			el(Option, "🚀"), | ||||||
| 			el(Option, "💖", isSelected), | 			el(Option, "💖"), | ||||||
| 		) | 		), | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
| function Option({ textContent }){ | function Option({ textContent }){ | ||||||
| 	return el("option", { value: textContent, 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"> | ||||||
|  | 	<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180"> | ||||||
|  | </p> | ||||||
|  |  | ||||||
|  | # Deka DOM Elements (dd\<el\> or DDE) | ||||||
|  |  | ||||||
| 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 | ||||||
| @@ -60,15 +56,50 @@ 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 | ||||||
| - ☑️ **Lightweight** — ~10-15kB minified (original goal 10kB) with zero/minimal dependencies | - ✅ **Minimalized footprint** — ~10-15kB minified bundle (original goal 10kB), **zero**/minimal dependencies and | ||||||
| - ✅ **Declarative & functional approach** for clean, maintainable code | 	small in-memory size (auto-releasing resources as much as possible) | ||||||
|  | - ✅ **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 | - ☑️ **Optional build-in signals** with support for custom reactive implementations (#39) | ||||||
| - ✅ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom) | - ☑️ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom) | ||||||
| - ✅ **TypeScript support** (work in progress) | - ✅ **TypeScript support** | ||||||
| - ☑️ **Support for debugging with browser DevTools** without extensions | - ✅ **Support for debugging with browser DevTools** without extensions | ||||||
| - ☑️ **Enhanced Web Components** support (work in progress) | - ☑️ **Enhanced Web Components** support | ||||||
|  |  | ||||||
|  | ## 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 | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | npm install deka-dom-el --save | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | …or via CDN / Direct Script: | ||||||
|  |  | ||||||
|  | For CDN links and various build formats (ESM/IIFE, with/without signals, minified/unminified), see the [interactive | ||||||
|  | format selector](https://jaandrle.github.io/deka-dom-el/#h-getting-started) on the documentation site. | ||||||
|  |  | ||||||
|  | ```html | ||||||
|  | <!-- Example with IIFE build (creates a global DDE object) --> | ||||||
|  | <script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/iife-with-signals.min.js"></script> | ||||||
|  | <script> | ||||||
|  | 	const { el, S } = DDE; | ||||||
|  | 	// Your code here | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <!-- Or with ES modules --> | ||||||
|  | <script type="module"> | ||||||
|  | 	import { el, S } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.min.js"; | ||||||
|  | 	// Your code here | ||||||
|  | </script> | ||||||
|  | ``` | ||||||
|  |  | ||||||
| ## Why Another Library? | ## Why Another Library? | ||||||
|  |  | ||||||
| @@ -83,29 +114,6 @@ A key advantage: any internal function (`assign`, `classListDeclarative`, `on`, | |||||||
| independently while also working seamlessly together. This modular approach makes it easier to integrate the library | independently while also working seamlessly together. This modular approach makes it easier to integrate the library | ||||||
| into existing projects. | into existing projects. | ||||||
|  |  | ||||||
| ## Getting Started |  | ||||||
|  |  | ||||||
| ### Installation |  | ||||||
|  |  | ||||||
| #### npm |  | ||||||
| ```bash |  | ||||||
| # TBD |  | ||||||
| # npm install deka-dom-el |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| #### Direct Script |  | ||||||
| ```html |  | ||||||
| <script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/iife-with-signals.min.js"></script> |  | ||||||
| <script type="module"> |  | ||||||
| 	const { el, S } = DDE; |  | ||||||
| </script> |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### Documentation |  | ||||||
|  |  | ||||||
| - [**Interactive Guide**](https://jaandrle.github.io/deka-dom-el): WIP |  | ||||||
| - [Examples](./examples/): TBD/WIP |  | ||||||
|  |  | ||||||
| ## Understanding Signals | ## Understanding Signals | ||||||
|  |  | ||||||
| Signals are the reactive backbone of Deka DOM Elements: | Signals are the reactive backbone of Deka DOM Elements: | ||||||
| @@ -115,12 +123,24 @@ Signals are the reactive backbone of Deka DOM Elements: | |||||||
| - [TC39 Signals Proposal](https://github.com/tc39/proposal-signals) (future standard) | - [TC39 Signals Proposal](https://github.com/tc39/proposal-signals) (future standard) | ||||||
| - [Observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) (underlying concept) | - [Observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) (underlying concept) | ||||||
|  |  | ||||||
|  | ## Contributing | ||||||
|  |  | ||||||
|  | We welcome contributions from the community! Please see our [Contributing Guide](CONTRIBUTING.md) for details on how to | ||||||
|  | get started, coding standards, commit guidelines, and the pull request process. | ||||||
|  |  | ||||||
| ## Inspiration and Alternatives | ## Inspiration and Alternatives | ||||||
|  |  | ||||||
| - [vanjs-org/van](https://github.com/vanjs-org/van) - World's smallest reactive UI framework | - [vanjs-org/van](https://github.com/vanjs-org/van) — World's smallest reactive UI framework | ||||||
| - [adamhaile/S](https://github.com/adamhaile/S) - Simple, clean, fast reactive programming | - [adamhaile/S](https://github.com/adamhaile/S) — Simple, clean, fast reactive programming | ||||||
| - [hyperhype/hyperscript](https://github.com/hyperhype/hyperscript) - Create HyperText with JavaScript | - [hyperhype/hyperscript](https://github.com/hyperhype/hyperscript) — Create HyperText with JavaScript | ||||||
| - [potch/signals](https://github.com/potch/signals) - A small reactive signals library | - [potch/signals](https://github.com/potch/signals) — A small reactive signals library | ||||||
| - [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) - | - [AseasRoa/paintor](https://github.com/AseasRoa/paintor) - JavaScript library for building reactive client-side user | ||||||
| 	Functional DOM components without JSX/virtual DOM | 	interfaces or HTML code. | ||||||
| - [mxjp/rvx: A signal based frontend framework](https://github.com/mxjp/rvx) | - [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. | ||||||
|  | - [TarekRaafat/eleva](https://github.com/TarekRaafat/eleva) — A minimalist, lightweight, pure vanilla JavaScript | ||||||
|  | 	frontend runtime framework. | ||||||
|  | - [didi/mpx](https://github.com/didi/mpx) — Mpx,一款具有优秀开发体验和深度性能优化的增强型跨端小程序框架 | ||||||
|  | - [mxjp/rvx](https://github.com/mxjp/rvx) — A signal based frontend framework | ||||||
|  | - [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) — | ||||||
|  | 	Functional DOM components without JSX/virtual DOM (my old library) | ||||||
|   | |||||||
| @@ -2,15 +2,18 @@ | |||||||
| This project uses [jaandrle/bs: The simplest possible build system using executable/bash scripts]( | This project uses [jaandrle/bs: The simplest possible build system using executable/bash scripts]( | ||||||
| https://github.com/jaandrle/bs). | https://github.com/jaandrle/bs). | ||||||
|  |  | ||||||
| #### bs/build.js [--minify|--help] | #### bs/build.js [main|signals] [--no-types|--help] | ||||||
| Generates alternative versions of the project (other than native ESM code). | Generates alternative versions of the project (other than native ESM code). | ||||||
| Also generates typescript definitions. | Also generates typescript definitions. | ||||||
|  |  | ||||||
| #### bs/docs.js | #### bs/docs.js | ||||||
| Generates documentation, from `docs/`. Uses “SSR” technique, using deka-dom-el itself. | Generates documentation, from `docs/`. Uses “SSR” technique, using deka-dom-el itself. | ||||||
|  |  | ||||||
|  | For running use `npx serve dist/docs`. | ||||||
|  |  | ||||||
| #### bs/lint.sh | #### bs/lint.sh | ||||||
| Lints size of the project, jshint. See configs: | Lints size of the project, jshint. See configs: | ||||||
|  |  | ||||||
| - `package.json`: key `size-limit` | - `package.json`: key `size-limit` | ||||||
| - `package.json`: key `jshintConfig` | - `package.json`: key `jshintConfig` | ||||||
|  | - `.editorconfig` | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								bs/build.js
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								bs/build.js
									
									
									
									
									
								
							| @@ -5,18 +5,18 @@ const files= [ "index", "index-with-signals" ]; | |||||||
| $.api("") | $.api("") | ||||||
| .command("main", "Build main files", { default: true }) | .command("main", "Build main files", { default: true }) | ||||||
| .option("--no-types", "Also generate d.ts files", false) | .option("--no-types", "Also generate d.ts files", false) | ||||||
| .action(async function main({ types }){ | .action(function main({ types }){ | ||||||
| 	const regular = await build({ | 	const regular = build({ | ||||||
| 		files, | 		files, | ||||||
| 		filesOut, | 		filesOut, | ||||||
| 		minify: "no", | 		minify: "no", | ||||||
| 		types, | 		types, | ||||||
| 	}); | 	}); | ||||||
| 	const min = await build({ | 	const min = build({ | ||||||
| 		files, | 		files, | ||||||
| 		filesOut(file, mark= "esm"){ | 		filesOut(file, mark= "esm"){ | ||||||
| 			const out= filesOut(file, mark); | 			const out= filesOut(file, mark); | ||||||
| 			const idx= out.lastIndexOf("."); | 			const idx= out.indexOf("."); | ||||||
| 			return out.slice(0, idx)+".min"+out.slice(idx); | 			return out.slice(0, idx)+".min"+out.slice(idx); | ||||||
| 		}, | 		}, | ||||||
| 		minify: "full", | 		minify: "full", | ||||||
| @@ -25,8 +25,8 @@ $.api("") | |||||||
| 	return $.exit(regular + min); | 	return $.exit(regular + min); | ||||||
| }) | }) | ||||||
| .command("signals", "Build only signals (for example for analysis)") | .command("signals", "Build only signals (for example for analysis)") | ||||||
| .action(async function signals(){ | .action(function signals(){ | ||||||
| 	const regular = await build({ | 	const regular = build({ | ||||||
| 		files: [ "signals" ], | 		files: [ "signals" ], | ||||||
| 		filesOut(file){ return "dist/."+file; }, | 		filesOut(file){ return "dist/."+file; }, | ||||||
| 		minify: "no", | 		minify: "no", | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| #!/usr/bin/env -S npx nodejsscript | #!/usr/bin/env -S npx nodejsscript | ||||||
|  | import { buildSync as esbuildSync } from "esbuild"; | ||||||
| const css= echo.css` | const css= echo.css` | ||||||
| 	.info{ color: gray; } | 	.info{ color: gray; } | ||||||
| `; | `; | ||||||
| @@ -8,8 +9,7 @@ export function build({ files, filesOut, minify= "partial", iife= true, types= t | |||||||
| 		const file= file_root+".js"; | 		const file= file_root+".js"; | ||||||
| 		echo(`Processing ${file} (minified: ${minify})`); | 		echo(`Processing ${file} (minified: ${minify})`); | ||||||
| 		const out= filesOut(file); | 		const out= filesOut(file); | ||||||
| 		const esbuild_output= buildEsbuild({ file, out, minify }); | 		esbuild({ file, out, minify }); | ||||||
| 		echoVariant(esbuild_output.stderr.split("\n")[1].trim()); |  | ||||||
|  |  | ||||||
| 		if(types){ | 		if(types){ | ||||||
| 			const file_dts= file_root+".d.ts"; | 			const file_dts= file_root+".d.ts"; | ||||||
| @@ -31,14 +31,13 @@ export function build({ files, filesOut, minify= "partial", iife= true, types= t | |||||||
| 		const name= "DDE"; | 		const name= "DDE"; | ||||||
| 		const out= filesOut(file_root+".js", fileMark); | 		const out= filesOut(file_root+".js", fileMark); | ||||||
|  |  | ||||||
| 		const params= [ | 		const params= { | ||||||
| 			"--format=iife", | 			format: "iife", | ||||||
| 			"--global-name="+name, | 			globalName: name | ||||||
| 		]; | 		}; | ||||||
| 		const dde_output= buildEsbuild({ file, out, minify, params }); | 		esbuild({ file, out, minify, params }); | ||||||
| 		echoVariant(`${out} (${name})`) |  | ||||||
|  |  | ||||||
| 		if(!types) return dde_output; | 		if(!types) return; | ||||||
| 		const file_dts= file_root+".d.ts"; | 		const file_dts= file_root+".d.ts"; | ||||||
| 		const file_dts_out= filesOut(file_dts, fileMark); | 		const file_dts_out= filesOut(file_dts, fileMark); | ||||||
| 		echoVariant(file_dts_out, true); | 		echoVariant(file_dts_out, true); | ||||||
| @@ -48,8 +47,6 @@ export function build({ files, filesOut, minify= "partial", iife= true, types= t | |||||||
| 			entry: file_dts, | 			entry: file_dts, | ||||||
| 		}) | 		}) | ||||||
| 		echoVariant(file_dts_out); | 		echoVariant(file_dts_out); | ||||||
|  |  | ||||||
| 		return dde_output; |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| export function buildDts({ bundle, entry, name }){ | export function buildDts({ bundle, entry, name }){ | ||||||
| @@ -64,46 +61,39 @@ export function buildDts({ bundle, entry, name }){ | |||||||
| 	].filter(Boolean).join(" "), { out, entry }); | 	].filter(Boolean).join(" "), { out, entry }); | ||||||
| 	return dts_b_g_output; | 	return dts_b_g_output; | ||||||
| } | } | ||||||
| class ErrorEsbuild extends Error{ | export function esbuild({ file, out, minify= "partial", params= {} }){ | ||||||
| 	constructor({ code, stderr }){ | 	const esbuild_output= esbuildSync({ | ||||||
| 		super(stderr); | 		entryPoints: [file], | ||||||
| 		this.code= code; | 		outfile: out, | ||||||
| 		this.stderr= stderr; | 		platform: "neutral", | ||||||
| 	} | 		bundle: true, | ||||||
| } | 		legalComments: "inline", | ||||||
| function buildEsbuild({ file, out, minify= "partial", params= [] }){ | 		packages: "external", | ||||||
| 	try { | 		metafile: true, | ||||||
| 		return esbuild({ file, out, minify, params }); | 		...minifyOption(minify), | ||||||
| 	} catch(e){ | 		...params | ||||||
| 		if(e instanceof ErrorEsbuild) | 	}); | ||||||
| 			return $.exit(e.code, echo(e.stderr)); |  | ||||||
| 		throw e; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| export function esbuild({ file, out, minify= "partial", params= [] }){ |  | ||||||
| 	const esbuild_output= s.$().run([ |  | ||||||
| 		"npx esbuild '::file::'", |  | ||||||
| 		"--platform=neutral", |  | ||||||
| 		"--bundle", |  | ||||||
| 		minifyOption(minify), |  | ||||||
| 		"--legal-comments=inline", |  | ||||||
| 		"--packages=external", |  | ||||||
| 		...params, |  | ||||||
| 		"--outfile='::out::'" |  | ||||||
| 	].filter(Boolean).join(" "), { file, out }); |  | ||||||
| 	if(esbuild_output.code) |  | ||||||
| 		throw new ErrorEsbuild(esbuild_output); |  | ||||||
| 	pipe( | 	pipe( | ||||||
| 		f=> f.replace(/^ +/gm, m=> "\t".repeat(m.length/2)), | 		f=> f.replace(/^ +/gm, m=> "\t".repeat(m.length/2)), | ||||||
| 		f=> s.echo(f).to(out) | 		f=> s.echo(f).to(out) | ||||||
| 	)(s.cat(out)); | 	)(s.cat(out)); | ||||||
|  |  | ||||||
|  | 	echoVariant(metaToLineStatus(esbuild_output.metafile, out)); | ||||||
| 	return esbuild_output; | 	return esbuild_output; | ||||||
| } | } | ||||||
| /** @param {"no"|"full"|"partial"} level */ | /** @param {"no"|"full"|"partial"} level */ | ||||||
| function minifyOption(level= "partial"){ | function minifyOption(level= "partial"){ | ||||||
| 	if("no"===level) return undefined; | 	if("no"===level) return { minify: false }; | ||||||
| 	if("full"===level) return "--minify"; | 	if("full"===level) return { minify: true }; | ||||||
| 	return "--minify-syntax --minify-identifiers"; | 	return { minifySyntax: true, minifyIdentifiers: true }; | ||||||
|  | } | ||||||
|  | function metaToLineStatus(meta, file){ | ||||||
|  | 	const status= meta.outputs[file]; | ||||||
|  | 	if(!status) return `? ${file}: unknown`; | ||||||
|  | 	const { bytes }= status; | ||||||
|  | 	const kbytes= bytes/1024; | ||||||
|  | 	const kbytesR= kbytes.toFixed(2); | ||||||
|  | 	return `${file}: ${kbytesR} kB`; | ||||||
| } | } | ||||||
| function echoVariant(name, todo= false){ | function echoVariant(name, todo= false){ | ||||||
| 	if(todo) return echo.use("-R", "~ "+name); | 	if(todo) return echo.use("-R", "~ "+name); | ||||||
|   | |||||||
| @@ -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, dispatchEvent, t } from "../docs/ssr.js"; | import { path_target, pages as pages_registered, styles, currentPageId, 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,6 +28,7 @@ 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,3 +9,4 @@ 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 | ||||||
|   | |||||||
							
								
								
									
										641
									
								
								dist/esm-with-signals.d.min.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										641
									
								
								dist/esm-with-signals.d.min.ts
									
									
									
									
										vendored
									
									
								
							| @@ -1,641 +0,0 @@ | |||||||
| declare global{ /* ddeSignal */ } |  | ||||||
| type CustomElementTagNameMap= { '#text': Text, '#comment': Comment } |  | ||||||
| type SupportedElement= |  | ||||||
| 		HTMLElementTagNameMap[keyof HTMLElementTagNameMap] |  | ||||||
| 	|	SVGElementTagNameMap[keyof SVGElementTagNameMap] |  | ||||||
| 	|	MathMLElementTagNameMap[keyof MathMLElementTagNameMap] |  | ||||||
| 	|	CustomElementTagNameMap[keyof CustomElementTagNameMap] |  | ||||||
| declare global { |  | ||||||
| 	type ddeComponentAttributes= Record<any, any> | undefined; |  | ||||||
| 	type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node>= (element: El)=> any; |  | ||||||
| 	type ddeString= string | ddeSignal<string> |  | ||||||
| 	type ddeStringable= ddeString | number | ddeSignal<number> |  | ||||||
| } |  | ||||||
| type PascalCase= |  | ||||||
| `${Capitalize<string>}${string}`; |  | ||||||
| type AttrsModified= { |  | ||||||
| 	/** |  | ||||||
| 	 * Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API). |  | ||||||
| 	 */ |  | ||||||
| 	style: Partial<CSSStyleDeclaration> | ddeString |  | ||||||
| 		| Partial<{ [K in keyof CSSStyleDeclaration]: ddeSignal<CSSStyleDeclaration[K]> }> |  | ||||||
| 	/** |  | ||||||
| 	 * Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1. |  | ||||||
| 	 * In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))` |  | ||||||
| 	 * for others. |  | ||||||
| 	 */ |  | ||||||
| 	classList: Record<string,-1|0|1|boolean|ddeSignal<-1|0|1|boolean>>, |  | ||||||
| 	/** |  | ||||||
| 	 * Used by the dataset HTML attribute to represent data for custom attributes added to elements. |  | ||||||
| 	 * Values are converted to string (see {@link DOMStringMap}). |  | ||||||
| 	 * |  | ||||||
| 	 * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMStringMap) |  | ||||||
| 	 * */ |  | ||||||
| 	dataset: Record<string, ddeStringable>, |  | ||||||
| 	/** |  | ||||||
| 	 * Sets `aria-*` simiraly to `dataset` |  | ||||||
| 	 * */ |  | ||||||
| 	ariaset: Record<string, ddeString>, |  | ||||||
| }	& Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, ddeString> |  | ||||||
| 	& Record<`.${string}`, any> |  | ||||||
| type _fromElsInterfaces<EL extends SupportedElement>= Omit<EL, keyof AttrsModified>; |  | ||||||
| type IsReadonly<T, K extends keyof T> = |  | ||||||
| 	T extends { readonly [P in K]: T[K] } ? true : false; |  | ||||||
| /** |  | ||||||
|  * Just element attributtes |  | ||||||
|  * |  | ||||||
|  * In most cases, you can use native propertie such as |  | ||||||
|  * [MDN WEB/API/Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) and so on |  | ||||||
|  * (e.g. [`Text`](https://developer.mozilla.org/en-US/docs/Web/API/Text)). |  | ||||||
|  * |  | ||||||
|  * There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives. |  | ||||||
|  * @private |  | ||||||
|  */ |  | ||||||
| type ElementAttributes<T extends SupportedElement>= Partial<{ |  | ||||||
| 	[K in keyof _fromElsInterfaces<T>]: |  | ||||||
| 		_fromElsInterfaces<T>[K] extends ((...p: any[])=> any) |  | ||||||
| 			? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=> |  | ||||||
| 																	ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>) |  | ||||||
| 			: (IsReadonly<_fromElsInterfaces<T>, K> extends false |  | ||||||
| 				? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]> |  | ||||||
| 				: ddeStringable) |  | ||||||
| } & AttrsModified> & Record<string, any>; |  | ||||||
| export function classListDeclarative<El extends SupportedElement>( |  | ||||||
| 	element: El, |  | ||||||
| 	classList: AttrsModified["classList"] |  | ||||||
| ): El |  | ||||||
| export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El |  | ||||||
| export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>( |  | ||||||
| 	element: El, |  | ||||||
| 	attr: ATT, |  | ||||||
| 	value: ElementAttributes<El>[ATT] |  | ||||||
| ): ElementAttributes<El>[ATT] |  | ||||||
|  |  | ||||||
| type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap; |  | ||||||
| export namespace el { |  | ||||||
| 	/** |  | ||||||
| 	 * Creates a marker comment for elements |  | ||||||
| 	 * |  | ||||||
| 	 * @param attrs - Marker attributes |  | ||||||
| 	 * @param [is_open=false] - Whether the marker is open-ended |  | ||||||
| 	 * @returns Comment node marker |  | ||||||
| 	 */ |  | ||||||
| 	export function mark( |  | ||||||
| 		attrs: { type: "component"|"reactive"|"later", name?: string, host?: "this"|"parentElement" }, |  | ||||||
| 		is_open?: boolean |  | ||||||
| 	): Comment; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function el< |  | ||||||
| 	A extends ddeComponentAttributes, |  | ||||||
| 	EL extends SupportedElement | ddeDocumentFragment |  | ||||||
| >( |  | ||||||
| 	component: (attr: A, ...rest: any[])=> EL, |  | ||||||
| 	attrs?: NoInfer<A>, |  | ||||||
| 	...addons: ddeElementAddon<EL>[] |  | ||||||
| ): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] |  | ||||||
| 	? EL |  | ||||||
| 	: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement ) |  | ||||||
| export function el< |  | ||||||
| 	A extends { textContent: ddeStringable }, |  | ||||||
| 	EL extends SupportedElement | ddeDocumentFragment |  | ||||||
| >( |  | ||||||
| 	component: (attr: A, ...rest: any[])=> EL, |  | ||||||
| 	attrs?: NoInfer<A>["textContent"], |  | ||||||
| 	...addons: ddeElementAddon<EL>[] |  | ||||||
| ): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] |  | ||||||
| 	? EL |  | ||||||
| 	: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement ) |  | ||||||
| export function el< |  | ||||||
| 	TAG extends keyof ExtendedHTMLElementTagNameMap, |  | ||||||
| >( |  | ||||||
| 	tag_name: TAG, |  | ||||||
| 	attrs?: ElementAttributes<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]> | ddeStringable, |  | ||||||
| 	...addons: ddeElementAddon< |  | ||||||
| 		ExtendedHTMLElementTagNameMap[NoInfer<TAG>] |  | ||||||
| 	>[], // TODO: for now addons must have the same element |  | ||||||
| ): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement |  | ||||||
| export function el( |  | ||||||
| 	tag_name?: "<>", |  | ||||||
| ): ddeDocumentFragment |  | ||||||
| export function el( |  | ||||||
| 	tag_name: string, |  | ||||||
| 	attrs?: ElementAttributes<HTMLElement> | ddeStringable, |  | ||||||
| 	...addons: ddeElementAddon<HTMLElement>[] |  | ||||||
| ): ddeHTMLElement |  | ||||||
| export { el as createElement } |  | ||||||
|  |  | ||||||
| export function elNS( |  | ||||||
| 	namespace: "http://www.w3.org/2000/svg" |  | ||||||
| ): < |  | ||||||
| 	TAG extends keyof SVGElementTagNameMap & string, |  | ||||||
| 	EL extends ( TAG extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[TAG] : SVGElement ), |  | ||||||
| >( |  | ||||||
| 	tag_name: TAG, |  | ||||||
| 	attrs?: ElementAttributes<NoInfer<EL>> | ddeStringable, |  | ||||||
| 	...addons: ddeElementAddon<NoInfer<EL>>[] |  | ||||||
| )=>  TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement |  | ||||||
| export function elNS( |  | ||||||
| 	namespace: "http://www.w3.org/1998/Math/MathML" |  | ||||||
| ): < |  | ||||||
| 	TAG extends keyof MathMLElementTagNameMap & string, |  | ||||||
| 	EL extends ( TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement ), |  | ||||||
| >( |  | ||||||
| 	tag_name: TAG, |  | ||||||
| 	attrs?: ddeStringable | Partial<{ |  | ||||||
| 		[key in keyof EL]: EL[key] | ddeSignal<EL[key]> | string | number | boolean |  | ||||||
| 	}>, |  | ||||||
| 	...addons: ddeElementAddon<NoInfer<EL>>[] |  | ||||||
| )=> ddeMathMLElement |  | ||||||
| export function elNS( |  | ||||||
| 	namespace: string |  | ||||||
| ): ( |  | ||||||
| 	tag_name: string, |  | ||||||
| 	attrs?: string | ddeStringable | Record<string, any>, |  | ||||||
| 	...addons: ddeElementAddon<SupportedElement>[] |  | ||||||
| )=> SupportedElement |  | ||||||
| export { elNS as createElementNS } |  | ||||||
|  |  | ||||||
| export function chainableAppend<EL extends SupportedElement>(el: EL): EL; |  | ||||||
| /** Simulate slots for ddeComponents */ |  | ||||||
| export function simulateSlots<EL extends SupportedElement | DocumentFragment>( |  | ||||||
| 	root: EL, |  | ||||||
| ): EL |  | ||||||
| /** |  | ||||||
|  * Simulate slots in Custom Elements without using `shadowRoot`. |  | ||||||
|  * @param el Custom Element root element |  | ||||||
|  * @param body Body of the custom element |  | ||||||
|  * */ |  | ||||||
| export function simulateSlots<EL extends SupportedElement | DocumentFragment>( |  | ||||||
| 	el: HTMLElement, |  | ||||||
| 	body: EL, |  | ||||||
| ): EL |  | ||||||
|  |  | ||||||
| export function dispatchEvent(name: keyof DocumentEventMap | string, element: SupportedElement): |  | ||||||
| 	(data?: any)=> void; |  | ||||||
| export function dispatchEvent(name: keyof DocumentEventMap | string, options?: EventInit): |  | ||||||
| 	(element: SupportedElement, data?: any)=> void; |  | ||||||
| export function dispatchEvent( |  | ||||||
| 	name: keyof DocumentEventMap | string, |  | ||||||
| 	options: EventInit | null, |  | ||||||
| 	element: SupportedElement | (()=> SupportedElement) |  | ||||||
| ): (data?: any)=> void; |  | ||||||
| interface On{ |  | ||||||
| 	/** Listens to the DOM event. See {@link Document.addEventListener} */ |  | ||||||
| 	< |  | ||||||
| 		Event extends keyof DocumentEventMap, |  | ||||||
| 		EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>, |  | ||||||
| 		>( |  | ||||||
| 			type: Event, |  | ||||||
| 			listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, |  | ||||||
| 			options?: AddEventListenerOptions |  | ||||||
| 		) : EE; |  | ||||||
| 	< |  | ||||||
| 		EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>, |  | ||||||
| 		>( |  | ||||||
| 			type: string, |  | ||||||
| 			listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent ) => any, |  | ||||||
| 			options?: AddEventListenerOptions |  | ||||||
| 		) : EE; |  | ||||||
| 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line |  | ||||||
| 	connected< |  | ||||||
| 		EE extends ddeElementAddon<SupportedElement>, |  | ||||||
| 		El extends ( EE extends ddeElementAddon<infer El> ? El : never ) |  | ||||||
| 		>( |  | ||||||
| 			listener: (this: El, event: CustomEvent<El>) => any, |  | ||||||
| 			options?: AddEventListenerOptions |  | ||||||
| 		) : EE; |  | ||||||
| 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line |  | ||||||
| 	disconnected< |  | ||||||
| 		EE extends ddeElementAddon<SupportedElement>, |  | ||||||
| 		El extends ( EE extends ddeElementAddon<infer El> ? El : never ) |  | ||||||
| 		>( |  | ||||||
| 			listener: (this: El, event: CustomEvent<void>) => any, |  | ||||||
| 			options?: AddEventListenerOptions |  | ||||||
| 		) : EE; |  | ||||||
| 	/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line |  | ||||||
| 	attributeChanged< |  | ||||||
| 		EE extends ddeElementAddon<SupportedElement>, |  | ||||||
| 		El extends ( EE extends ddeElementAddon<infer El> ? El : never ) |  | ||||||
| 		>( |  | ||||||
| 			listener: (this: El, event: CustomEvent<[ string, string ]>) => any, |  | ||||||
| 			options?: AddEventListenerOptions |  | ||||||
| 		) : EE; |  | ||||||
| } |  | ||||||
| export const on: On; |  | ||||||
|  |  | ||||||
| type Scope= { |  | ||||||
| 	scope: Node | Function | Object, |  | ||||||
| 	host: ddeElementAddon<any>, |  | ||||||
| 	custom_element: false | HTMLElement, |  | ||||||
| 	prevent: boolean |  | ||||||
| }; |  | ||||||
| /** Current scope created last time the `el(Function)` was invoke. (Or {@link scope.push}) */ |  | ||||||
| export const scope: { |  | ||||||
| 	current: Scope, |  | ||||||
| 	/** Stops all automatizations. E. g. signals used as attributes in current scope |  | ||||||
| 	 * registers removing these listeners (and clean signal if no other listeners are detected) |  | ||||||
| 	 * on `disconnected` event. */ |  | ||||||
| 	preventDefault<T extends boolean>(prevent: T): T, |  | ||||||
| 	/** |  | ||||||
| 	 * This represents reference to the current host element — `scope.host()`. |  | ||||||
| 	 * It can be also used to register Addon(s) (functions to be called when component is initized) |  | ||||||
| 	 * — `scope.host(on.connected(console.log))`. |  | ||||||
| 	 * */ |  | ||||||
| 	host: (...addons: ddeElementAddon<SupportedElement>[])=> HTMLElement, |  | ||||||
|  |  | ||||||
| 	state: Scope[], |  | ||||||
| 	/** Adds new child scope. All attributes are inherited by default. */ |  | ||||||
| 	push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>, |  | ||||||
| 	/** Adds root scope as a child of the current scope. */ |  | ||||||
| 	pushRoot(): ReturnType<Array<Scope>["push"]>, |  | ||||||
| 	/** Removes last/current child scope. */ |  | ||||||
| 	pop(): ReturnType<Array<Scope>["pop"]>, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export function customElementRender< |  | ||||||
| 	EL extends HTMLElement, |  | ||||||
| 	P extends any = Record<string, string | ddeSignal<string>> |  | ||||||
| >( |  | ||||||
| 	target: ShadowRoot | EL, |  | ||||||
| 	render: (props: P)=> SupportedElement | DocumentFragment, |  | ||||||
| 	props?: P | ((el: EL)=> P) |  | ||||||
| ): EL |  | ||||||
| export function customElementWithDDE<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL |  | ||||||
| export function lifecyclesToEvents<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL |  | ||||||
| export function observedAttributes(custom_element: HTMLElement): Record<string, string> |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * This is used primarly for server side rendering. To be sure that all async operations |  | ||||||
|  * are finished before the page is sent to the client. |  | ||||||
|  * ``` |  | ||||||
|  *	// on component |  | ||||||
|  *	function component(){ |  | ||||||
|  *		… |  | ||||||
|  *		queue(fetch(...).then(...)); |  | ||||||
|  *	} |  | ||||||
|  * |  | ||||||
|  * // building the page |  | ||||||
|  * async function build(){ |  | ||||||
|  *		const { component }= await import("./component.js"); |  | ||||||
|  *		document.body.append(el(component)); |  | ||||||
|  *		await queue(); |  | ||||||
|  *		retutn document.body.innerHTML; |  | ||||||
|  *	} |  | ||||||
|  * ``` |  | ||||||
|  * */ |  | ||||||
| export function queue(promise?: Promise<unknown>): Promise<unknown>; |  | ||||||
|  |  | ||||||
| /* TypeScript MEH */ |  | ||||||
| declare global{ |  | ||||||
| 	type ddeAppend<el>= (...nodes: (Node | string)[])=> el; |  | ||||||
|  |  | ||||||
| 	interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; } |  | ||||||
| 	interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; } |  | ||||||
| 	interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; } |  | ||||||
| 	interface ddeMathMLElement extends MathMLElement{ append: ddeAppend<ddeMathMLElement>; } |  | ||||||
|  |  | ||||||
| 	interface ddeHTMLElementTagNameMap { |  | ||||||
| 		"a": ddeHTMLAnchorElement; |  | ||||||
| 		"area": ddeHTMLAreaElement; |  | ||||||
| 		"audio": ddeHTMLAudioElement; |  | ||||||
| 		"base": ddeHTMLBaseElement; |  | ||||||
| 		"blockquote": ddeHTMLQuoteElement; |  | ||||||
| 		"body": ddeHTMLBodyElement; |  | ||||||
| 		"br": ddeHTMLBRElement; |  | ||||||
| 		"button": ddeHTMLButtonElement; |  | ||||||
| 		"canvas": ddeHTMLCanvasElement; |  | ||||||
| 		"caption": ddeHTMLTableCaptionElement; |  | ||||||
| 		"col": ddeHTMLTableColElement; |  | ||||||
| 		"colgroup": ddeHTMLTableColElement; |  | ||||||
| 		"data": ddeHTMLDataElement; |  | ||||||
| 		"datalist": ddeHTMLDataListElement; |  | ||||||
| 		"del": ddeHTMLModElement; |  | ||||||
| 		"details": ddeHTMLDetailsElement; |  | ||||||
| 		"dialog": ddeHTMLDialogElement; |  | ||||||
| 		"div": ddeHTMLDivElement; |  | ||||||
| 		"dl": ddeHTMLDListElement; |  | ||||||
| 		"embed": ddeHTMLEmbedElement; |  | ||||||
| 		"fieldset": ddeHTMLFieldSetElement; |  | ||||||
| 		"form": ddeHTMLFormElement; |  | ||||||
| 		"h1": ddeHTMLHeadingElement; |  | ||||||
| 		"h2": ddeHTMLHeadingElement; |  | ||||||
| 		"h3": ddeHTMLHeadingElement; |  | ||||||
| 		"h4": ddeHTMLHeadingElement; |  | ||||||
| 		"h5": ddeHTMLHeadingElement; |  | ||||||
| 		"h6": ddeHTMLHeadingElement; |  | ||||||
| 		"head": ddeHTMLHeadElement; |  | ||||||
| 		"hr": ddeHTMLHRElement; |  | ||||||
| 		"html": ddeHTMLHtmlElement; |  | ||||||
| 		"iframe": ddeHTMLIFrameElement; |  | ||||||
| 		"img": ddeHTMLImageElement; |  | ||||||
| 		"input": ddeHTMLInputElement; |  | ||||||
| 		"ins": ddeHTMLModElement; |  | ||||||
| 		"label": ddeHTMLLabelElement; |  | ||||||
| 		"legend": ddeHTMLLegendElement; |  | ||||||
| 		"li": ddeHTMLLIElement; |  | ||||||
| 		"link": ddeHTMLLinkElement; |  | ||||||
| 		"map": ddeHTMLMapElement; |  | ||||||
| 		"menu": ddeHTMLMenuElement; |  | ||||||
| 		"meta": ddeHTMLMetaElement; |  | ||||||
| 		"meter": ddeHTMLMeterElement; |  | ||||||
| 		"object": ddeHTMLObjectElement; |  | ||||||
| 		"ol": ddeHTMLOListElement; |  | ||||||
| 		"optgroup": ddeHTMLOptGroupElement; |  | ||||||
| 		"option": ddeHTMLOptionElement; |  | ||||||
| 		"output": ddeHTMLOutputElement; |  | ||||||
| 		"p": ddeHTMLParagraphElement; |  | ||||||
| 		"picture": ddeHTMLPictureElement; |  | ||||||
| 		"pre": ddeHTMLPreElement; |  | ||||||
| 		"progress": ddeHTMLProgressElement; |  | ||||||
| 		"q": ddeHTMLQuoteElement; |  | ||||||
| 		"script": ddeHTMLScriptElement; |  | ||||||
| 		"select": ddeHTMLSelectElement; |  | ||||||
| 		"slot": ddeHTMLSlotElement; |  | ||||||
| 		"source": ddeHTMLSourceElement; |  | ||||||
| 		"span": ddeHTMLSpanElement; |  | ||||||
| 		"style": ddeHTMLStyleElement; |  | ||||||
| 		"table": ddeHTMLTableElement; |  | ||||||
| 		"tbody": ddeHTMLTableSectionElement; |  | ||||||
| 		"td": ddeHTMLTableCellElement; |  | ||||||
| 		"template": ddeHTMLTemplateElement; |  | ||||||
| 		"textarea": ddeHTMLTextAreaElement; |  | ||||||
| 		"tfoot": ddeHTMLTableSectionElement; |  | ||||||
| 		"th": ddeHTMLTableCellElement; |  | ||||||
| 		"thead": ddeHTMLTableSectionElement; |  | ||||||
| 		"time": ddeHTMLTimeElement; |  | ||||||
| 		"title": ddeHTMLTitleElement; |  | ||||||
| 		"tr": ddeHTMLTableRowElement; |  | ||||||
| 		"track": ddeHTMLTrackElement; |  | ||||||
| 		"ul": ddeHTMLUListElement; |  | ||||||
| 		"video": ddeHTMLVideoElement; |  | ||||||
| 	} |  | ||||||
| 	interface ddeSVGElementTagNameMap { |  | ||||||
| 		"a": ddeSVGAElement; |  | ||||||
| 		"animate": ddeSVGAnimateElement; |  | ||||||
| 		"animateMotion": ddeSVGAnimateMotionElement; |  | ||||||
| 		"animateTransform": ddeSVGAnimateTransformElement; |  | ||||||
| 		"circle": ddeSVGCircleElement; |  | ||||||
| 		"clipPath": ddeSVGClipPathElement; |  | ||||||
| 		"defs": ddeSVGDefsElement; |  | ||||||
| 		"desc": ddeSVGDescElement; |  | ||||||
| 		"ellipse": ddeSVGEllipseElement; |  | ||||||
| 		"feBlend": ddeSVGFEBlendElement; |  | ||||||
| 		"feColorMatrix": ddeSVGFEColorMatrixElement; |  | ||||||
| 		"feComponentTransfer": ddeSVGFEComponentTransferElement; |  | ||||||
| 		"feComposite": ddeSVGFECompositeElement; |  | ||||||
| 		"feConvolveMatrix": ddeSVGFEConvolveMatrixElement; |  | ||||||
| 		"feDiffuseLighting": ddeSVGFEDiffuseLightingElement; |  | ||||||
| 		"feDisplacementMap": ddeSVGFEDisplacementMapElement; |  | ||||||
| 		"feDistantLight": ddeSVGFEDistantLightElement; |  | ||||||
| 		"feDropShadow": ddeSVGFEDropShadowElement; |  | ||||||
| 		"feFlood": ddeSVGFEFloodElement; |  | ||||||
| 		"feFuncA": ddeSVGFEFuncAElement; |  | ||||||
| 		"feFuncB": ddeSVGFEFuncBElement; |  | ||||||
| 		"feFuncG": ddeSVGFEFuncGElement; |  | ||||||
| 		"feFuncR": ddeSVGFEFuncRElement; |  | ||||||
| 		"feGaussianBlur": ddeSVGFEGaussianBlurElement; |  | ||||||
| 		"feImage": ddeSVGFEImageElement; |  | ||||||
| 		"feMerge": ddeSVGFEMergeElement; |  | ||||||
| 		"feMergeNode": ddeSVGFEMergeNodeElement; |  | ||||||
| 		"feMorphology": ddeSVGFEMorphologyElement; |  | ||||||
| 		"feOffset": ddeSVGFEOffsetElement; |  | ||||||
| 		"fePointLight": ddeSVGFEPointLightElement; |  | ||||||
| 		"feSpecularLighting": ddeSVGFESpecularLightingElement; |  | ||||||
| 		"feSpotLight": ddeSVGFESpotLightElement; |  | ||||||
| 		"feTile": ddeSVGFETileElement; |  | ||||||
| 		"feTurbulence": ddeSVGFETurbulenceElement; |  | ||||||
| 		"filter": ddeSVGFilterElement; |  | ||||||
| 		"foreignObject": ddeSVGForeignObjectElement; |  | ||||||
| 		"g": ddeSVGGElement; |  | ||||||
| 		"image": ddeSVGImageElement; |  | ||||||
| 		"line": ddeSVGLineElement; |  | ||||||
| 		"linearGradient": ddeSVGLinearGradientElement; |  | ||||||
| 		"marker": ddeSVGMarkerElement; |  | ||||||
| 		"mask": ddeSVGMaskElement; |  | ||||||
| 		"metadata": ddeSVGMetadataElement; |  | ||||||
| 		"mpath": ddeSVGMPathElement; |  | ||||||
| 		"path": ddeSVGPathElement; |  | ||||||
| 		"pattern": ddeSVGPatternElement; |  | ||||||
| 		"polygon": ddeSVGPolygonElement; |  | ||||||
| 		"polyline": ddeSVGPolylineElement; |  | ||||||
| 		"radialGradient": ddeSVGRadialGradientElement; |  | ||||||
| 		"rect": ddeSVGRectElement; |  | ||||||
| 		"script": ddeSVGScriptElement; |  | ||||||
| 		"set": ddeSVGSetElement; |  | ||||||
| 		"stop": ddeSVGStopElement; |  | ||||||
| 		"style": ddeSVGStyleElement; |  | ||||||
| 		"svg": ddeSVGSVGElement; |  | ||||||
| 		"switch": ddeSVGSwitchElement; |  | ||||||
| 		"symbol": ddeSVGSymbolElement; |  | ||||||
| 		"text": ddeSVGTextElement; |  | ||||||
| 		"textPath": ddeSVGTextPathElement; |  | ||||||
| 		"title": ddeSVGTitleElement; |  | ||||||
| 		"tspan": ddeSVGTSpanElement; |  | ||||||
| 		"use": ddeSVGUseElement; |  | ||||||
| 		"view": ddeSVGViewElement; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // editorconfig-checker-disable |  | ||||||
| interface ddeHTMLAnchorElement extends HTMLAnchorElement{ append: ddeAppend<ddeHTMLAnchorElement>; } |  | ||||||
| interface ddeHTMLAreaElement extends HTMLAreaElement{ append: ddeAppend<ddeHTMLAreaElement>; } |  | ||||||
| interface ddeHTMLAudioElement extends HTMLAudioElement{ append: ddeAppend<ddeHTMLAudioElement>; } |  | ||||||
| interface ddeHTMLBaseElement extends HTMLBaseElement{ append: ddeAppend<ddeHTMLBaseElement>; } |  | ||||||
| interface ddeHTMLQuoteElement extends HTMLQuoteElement{ append: ddeAppend<ddeHTMLQuoteElement>; } |  | ||||||
| interface ddeHTMLBodyElement extends HTMLBodyElement{ append: ddeAppend<ddeHTMLBodyElement>; } |  | ||||||
| interface ddeHTMLBRElement extends HTMLBRElement{ append: ddeAppend<ddeHTMLBRElement>; } |  | ||||||
| interface ddeHTMLButtonElement extends HTMLButtonElement{ append: ddeAppend<ddeHTMLButtonElement>; } |  | ||||||
| interface ddeHTMLCanvasElement extends HTMLCanvasElement{ append: ddeAppend<ddeHTMLCanvasElement>; } |  | ||||||
| interface ddeHTMLTableCaptionElement extends HTMLTableCaptionElement{ append: ddeAppend<ddeHTMLTableCaptionElement>; } |  | ||||||
| interface ddeHTMLTableColElement extends HTMLTableColElement{ append: ddeAppend<ddeHTMLTableColElement>; } |  | ||||||
| interface ddeHTMLTableColElement extends HTMLTableColElement{ append: ddeAppend<ddeHTMLTableColElement>; } |  | ||||||
| interface ddeHTMLDataElement extends HTMLDataElement{ append: ddeAppend<ddeHTMLDataElement>; } |  | ||||||
| interface ddeHTMLDataListElement extends HTMLDataListElement{ append: ddeAppend<ddeHTMLDataListElement>; } |  | ||||||
| interface ddeHTMLModElement extends HTMLModElement{ append: ddeAppend<ddeHTMLModElement>; } |  | ||||||
| interface ddeHTMLDetailsElement extends HTMLDetailsElement{ append: ddeAppend<ddeHTMLDetailsElement>; } |  | ||||||
| interface ddeHTMLDialogElement extends HTMLDialogElement{ append: ddeAppend<ddeHTMLDialogElement>; } |  | ||||||
| interface ddeHTMLDivElement extends HTMLDivElement{ append: ddeAppend<ddeHTMLDivElement>; } |  | ||||||
| interface ddeHTMLDListElement extends HTMLDListElement{ append: ddeAppend<ddeHTMLDListElement>; } |  | ||||||
| interface ddeHTMLEmbedElement extends HTMLEmbedElement{ append: ddeAppend<ddeHTMLEmbedElement>; } |  | ||||||
| interface ddeHTMLFieldSetElement extends HTMLFieldSetElement{ append: ddeAppend<ddeHTMLFieldSetElement>; } |  | ||||||
| interface ddeHTMLFormElement extends HTMLFormElement{ append: ddeAppend<ddeHTMLFormElement>; } |  | ||||||
| interface ddeHTMLHeadingElement extends HTMLHeadingElement{ append: ddeAppend<ddeHTMLHeadingElement>; } |  | ||||||
| interface ddeHTMLHeadElement extends HTMLHeadElement{ append: ddeAppend<ddeHTMLHeadElement>; } |  | ||||||
| interface ddeHTMLHRElement extends HTMLHRElement{ append: ddeAppend<ddeHTMLHRElement>; } |  | ||||||
| interface ddeHTMLHtmlElement extends HTMLHtmlElement{ append: ddeAppend<ddeHTMLHtmlElement>; } |  | ||||||
| interface ddeHTMLIFrameElement extends HTMLIFrameElement{ append: ddeAppend<ddeHTMLIFrameElement>; } |  | ||||||
| interface ddeHTMLImageElement extends HTMLImageElement{ append: ddeAppend<ddeHTMLImageElement>; } |  | ||||||
| interface ddeHTMLInputElement extends HTMLInputElement{ append: ddeAppend<ddeHTMLInputElement>; } |  | ||||||
| interface ddeHTMLLabelElement extends HTMLLabelElement{ append: ddeAppend<ddeHTMLLabelElement>; } |  | ||||||
| interface ddeHTMLLegendElement extends HTMLLegendElement{ append: ddeAppend<ddeHTMLLegendElement>; } |  | ||||||
| interface ddeHTMLLIElement extends HTMLLIElement{ append: ddeAppend<ddeHTMLLIElement>; } |  | ||||||
| interface ddeHTMLLinkElement extends HTMLLinkElement{ append: ddeAppend<ddeHTMLLinkElement>; } |  | ||||||
| interface ddeHTMLMapElement extends HTMLMapElement{ append: ddeAppend<ddeHTMLMapElement>; } |  | ||||||
| interface ddeHTMLMenuElement extends HTMLMenuElement{ append: ddeAppend<ddeHTMLMenuElement>; } |  | ||||||
| interface ddeHTMLMetaElement extends HTMLMetaElement{ append: ddeAppend<ddeHTMLMetaElement>; } |  | ||||||
| interface ddeHTMLMeterElement extends HTMLMeterElement{ append: ddeAppend<ddeHTMLMeterElement>; } |  | ||||||
| interface ddeHTMLObjectElement extends HTMLObjectElement{ append: ddeAppend<ddeHTMLObjectElement>; } |  | ||||||
| interface ddeHTMLOListElement extends HTMLOListElement{ append: ddeAppend<ddeHTMLOListElement>; } |  | ||||||
| interface ddeHTMLOptGroupElement extends HTMLOptGroupElement{ append: ddeAppend<ddeHTMLOptGroupElement>; } |  | ||||||
| interface ddeHTMLOptionElement extends HTMLOptionElement{ append: ddeAppend<ddeHTMLOptionElement>; } |  | ||||||
| interface ddeHTMLOutputElement extends HTMLOutputElement{ append: ddeAppend<ddeHTMLOutputElement>; } |  | ||||||
| interface ddeHTMLParagraphElement extends HTMLParagraphElement{ append: ddeAppend<ddeHTMLParagraphElement>; } |  | ||||||
| interface ddeHTMLPictureElement extends HTMLPictureElement{ append: ddeAppend<ddeHTMLPictureElement>; } |  | ||||||
| interface ddeHTMLPreElement extends HTMLPreElement{ append: ddeAppend<ddeHTMLPreElement>; } |  | ||||||
| interface ddeHTMLProgressElement extends HTMLProgressElement{ append: ddeAppend<ddeHTMLProgressElement>; } |  | ||||||
| interface ddeHTMLScriptElement extends HTMLScriptElement{ append: ddeAppend<ddeHTMLScriptElement>; } |  | ||||||
| interface ddeHTMLSelectElement extends HTMLSelectElement{ append: ddeAppend<ddeHTMLSelectElement>; } |  | ||||||
| interface ddeHTMLSlotElement extends HTMLSlotElement{ append: ddeAppend<ddeHTMLSlotElement>; } |  | ||||||
| interface ddeHTMLSourceElement extends HTMLSourceElement{ append: ddeAppend<ddeHTMLSourceElement>; } |  | ||||||
| interface ddeHTMLSpanElement extends HTMLSpanElement{ append: ddeAppend<ddeHTMLSpanElement>; } |  | ||||||
| interface ddeHTMLStyleElement extends HTMLStyleElement{ append: ddeAppend<ddeHTMLStyleElement>; } |  | ||||||
| interface ddeHTMLTableElement extends HTMLTableElement{ append: ddeAppend<ddeHTMLTableElement>; } |  | ||||||
| interface ddeHTMLTableSectionElement extends HTMLTableSectionElement{ append: ddeAppend<ddeHTMLTableSectionElement>; } |  | ||||||
| interface ddeHTMLTableCellElement extends HTMLTableCellElement{ append: ddeAppend<ddeHTMLTableCellElement>; } |  | ||||||
| interface ddeHTMLTemplateElement extends HTMLTemplateElement{ append: ddeAppend<ddeHTMLTemplateElement>; } |  | ||||||
| interface ddeHTMLTextAreaElement extends HTMLTextAreaElement{ append: ddeAppend<ddeHTMLTextAreaElement>; } |  | ||||||
| interface ddeHTMLTableCellElement extends HTMLTableCellElement{ append: ddeAppend<ddeHTMLTableCellElement>; } |  | ||||||
| interface ddeHTMLTimeElement extends HTMLTimeElement{ append: ddeAppend<ddeHTMLTimeElement>; } |  | ||||||
| interface ddeHTMLTitleElement extends HTMLTitleElement{ append: ddeAppend<ddeHTMLTitleElement>; } |  | ||||||
| interface ddeHTMLTableRowElement extends HTMLTableRowElement{ append: ddeAppend<ddeHTMLTableRowElement>; } |  | ||||||
| interface ddeHTMLTrackElement extends HTMLTrackElement{ append: ddeAppend<ddeHTMLTrackElement>; } |  | ||||||
| interface ddeHTMLUListElement extends HTMLUListElement{ append: ddeAppend<ddeHTMLUListElement>; } |  | ||||||
| interface ddeHTMLVideoElement extends HTMLVideoElement{ append: ddeAppend<ddeHTMLVideoElement>; } |  | ||||||
| interface ddeSVGAElement extends SVGAElement{ append: ddeAppend<ddeSVGAElement>; } |  | ||||||
| interface ddeSVGAnimateElement extends SVGAnimateElement{ append: ddeAppend<ddeSVGAnimateElement>; } |  | ||||||
| interface ddeSVGAnimateMotionElement extends SVGAnimateMotionElement{ append: ddeAppend<ddeSVGAnimateMotionElement>; } |  | ||||||
| interface ddeSVGAnimateTransformElement extends SVGAnimateTransformElement{ append: ddeAppend<ddeSVGAnimateTransformElement>; } |  | ||||||
| interface ddeSVGCircleElement extends SVGCircleElement{ append: ddeAppend<ddeSVGCircleElement>; } |  | ||||||
| interface ddeSVGClipPathElement extends SVGClipPathElement{ append: ddeAppend<ddeSVGClipPathElement>; } |  | ||||||
| interface ddeSVGDefsElement extends SVGDefsElement{ append: ddeAppend<ddeSVGDefsElement>; } |  | ||||||
| interface ddeSVGDescElement extends SVGDescElement{ append: ddeAppend<ddeSVGDescElement>; } |  | ||||||
| interface ddeSVGEllipseElement extends SVGEllipseElement{ append: ddeAppend<ddeSVGEllipseElement>; } |  | ||||||
| interface ddeSVGFEBlendElement extends SVGFEBlendElement{ append: ddeAppend<ddeSVGFEBlendElement>; } |  | ||||||
| interface ddeSVGFEColorMatrixElement extends SVGFEColorMatrixElement{ append: ddeAppend<ddeSVGFEColorMatrixElement>; } |  | ||||||
| interface ddeSVGFEComponentTransferElement extends SVGFEComponentTransferElement{ append: ddeAppend<ddeSVGFEComponentTransferElement>; } |  | ||||||
| interface ddeSVGFECompositeElement extends SVGFECompositeElement{ append: ddeAppend<ddeSVGFECompositeElement>; } |  | ||||||
| interface ddeSVGFEConvolveMatrixElement extends SVGFEConvolveMatrixElement{ append: ddeAppend<ddeSVGFEConvolveMatrixElement>; } |  | ||||||
| interface ddeSVGFEDiffuseLightingElement extends SVGFEDiffuseLightingElement{ append: ddeAppend<ddeSVGFEDiffuseLightingElement>; } |  | ||||||
| interface ddeSVGFEDisplacementMapElement extends SVGFEDisplacementMapElement{ append: ddeAppend<ddeSVGFEDisplacementMapElement>; } |  | ||||||
| interface ddeSVGFEDistantLightElement extends SVGFEDistantLightElement{ append: ddeAppend<ddeSVGFEDistantLightElement>; } |  | ||||||
| interface ddeSVGFEDropShadowElement extends SVGFEDropShadowElement{ append: ddeAppend<ddeSVGFEDropShadowElement>; } |  | ||||||
| interface ddeSVGFEFloodElement extends SVGFEFloodElement{ append: ddeAppend<ddeSVGFEFloodElement>; } |  | ||||||
| interface ddeSVGFEFuncAElement extends SVGFEFuncAElement{ append: ddeAppend<ddeSVGFEFuncAElement>; } |  | ||||||
| interface ddeSVGFEFuncBElement extends SVGFEFuncBElement{ append: ddeAppend<ddeSVGFEFuncBElement>; } |  | ||||||
| interface ddeSVGFEFuncGElement extends SVGFEFuncGElement{ append: ddeAppend<ddeSVGFEFuncGElement>; } |  | ||||||
| interface ddeSVGFEFuncRElement extends SVGFEFuncRElement{ append: ddeAppend<ddeSVGFEFuncRElement>; } |  | ||||||
| interface ddeSVGFEGaussianBlurElement extends SVGFEGaussianBlurElement{ append: ddeAppend<ddeSVGFEGaussianBlurElement>; } |  | ||||||
| interface ddeSVGFEImageElement extends SVGFEImageElement{ append: ddeAppend<ddeSVGFEImageElement>; } |  | ||||||
| interface ddeSVGFEMergeElement extends SVGFEMergeElement{ append: ddeAppend<ddeSVGFEMergeElement>; } |  | ||||||
| interface ddeSVGFEMergeNodeElement extends SVGFEMergeNodeElement{ append: ddeAppend<ddeSVGFEMergeNodeElement>; } |  | ||||||
| interface ddeSVGFEMorphologyElement extends SVGFEMorphologyElement{ append: ddeAppend<ddeSVGFEMorphologyElement>; } |  | ||||||
| interface ddeSVGFEOffsetElement extends SVGFEOffsetElement{ append: ddeAppend<ddeSVGFEOffsetElement>; } |  | ||||||
| interface ddeSVGFEPointLightElement extends SVGFEPointLightElement{ append: ddeAppend<ddeSVGFEPointLightElement>; } |  | ||||||
| interface ddeSVGFESpecularLightingElement extends SVGFESpecularLightingElement{ append: ddeAppend<ddeSVGFESpecularLightingElement>; } |  | ||||||
| interface ddeSVGFESpotLightElement extends SVGFESpotLightElement{ append: ddeAppend<ddeSVGFESpotLightElement>; } |  | ||||||
| interface ddeSVGFETileElement extends SVGFETileElement{ append: ddeAppend<ddeSVGFETileElement>; } |  | ||||||
| interface ddeSVGFETurbulenceElement extends SVGFETurbulenceElement{ append: ddeAppend<ddeSVGFETurbulenceElement>; } |  | ||||||
| interface ddeSVGFilterElement extends SVGFilterElement{ append: ddeAppend<ddeSVGFilterElement>; } |  | ||||||
| interface ddeSVGForeignObjectElement extends SVGForeignObjectElement{ append: ddeAppend<ddeSVGForeignObjectElement>; } |  | ||||||
| interface ddeSVGGElement extends SVGGElement{ append: ddeAppend<ddeSVGGElement>; } |  | ||||||
| interface ddeSVGImageElement extends SVGImageElement{ append: ddeAppend<ddeSVGImageElement>; } |  | ||||||
| interface ddeSVGLineElement extends SVGLineElement{ append: ddeAppend<ddeSVGLineElement>; } |  | ||||||
| interface ddeSVGLinearGradientElement extends SVGLinearGradientElement{ append: ddeAppend<ddeSVGLinearGradientElement>; } |  | ||||||
| interface ddeSVGMarkerElement extends SVGMarkerElement{ append: ddeAppend<ddeSVGMarkerElement>; } |  | ||||||
| interface ddeSVGMaskElement extends SVGMaskElement{ append: ddeAppend<ddeSVGMaskElement>; } |  | ||||||
| interface ddeSVGMetadataElement extends SVGMetadataElement{ append: ddeAppend<ddeSVGMetadataElement>; } |  | ||||||
| interface ddeSVGMPathElement extends SVGMPathElement{ append: ddeAppend<ddeSVGMPathElement>; } |  | ||||||
| interface ddeSVGPathElement extends SVGPathElement{ append: ddeAppend<ddeSVGPathElement>; } |  | ||||||
| interface ddeSVGPatternElement extends SVGPatternElement{ append: ddeAppend<ddeSVGPatternElement>; } |  | ||||||
| interface ddeSVGPolygonElement extends SVGPolygonElement{ append: ddeAppend<ddeSVGPolygonElement>; } |  | ||||||
| interface ddeSVGPolylineElement extends SVGPolylineElement{ append: ddeAppend<ddeSVGPolylineElement>; } |  | ||||||
| interface ddeSVGRadialGradientElement extends SVGRadialGradientElement{ append: ddeAppend<ddeSVGRadialGradientElement>; } |  | ||||||
| interface ddeSVGRectElement extends SVGRectElement{ append: ddeAppend<ddeSVGRectElement>; } |  | ||||||
| interface ddeSVGScriptElement extends SVGScriptElement{ append: ddeAppend<ddeSVGScriptElement>; } |  | ||||||
| interface ddeSVGSetElement extends SVGSetElement{ append: ddeAppend<ddeSVGSetElement>; } |  | ||||||
| interface ddeSVGStopElement extends SVGStopElement{ append: ddeAppend<ddeSVGStopElement>; } |  | ||||||
| interface ddeSVGStyleElement extends SVGStyleElement{ append: ddeAppend<ddeSVGStyleElement>; } |  | ||||||
| interface ddeSVGSVGElement extends SVGSVGElement{ append: ddeAppend<ddeSVGSVGElement>; } |  | ||||||
| interface ddeSVGSwitchElement extends SVGSwitchElement{ append: ddeAppend<ddeSVGSwitchElement>; } |  | ||||||
| interface ddeSVGSymbolElement extends SVGSymbolElement{ append: ddeAppend<ddeSVGSymbolElement>; } |  | ||||||
| interface ddeSVGTextElement extends SVGTextElement{ append: ddeAppend<ddeSVGTextElement>; } |  | ||||||
| interface ddeSVGTextPathElement extends SVGTextPathElement{ append: ddeAppend<ddeSVGTextPathElement>; } |  | ||||||
| interface ddeSVGTitleElement extends SVGTitleElement{ append: ddeAppend<ddeSVGTitleElement>; } |  | ||||||
| interface ddeSVGTSpanElement extends SVGTSpanElement{ append: ddeAppend<ddeSVGTSpanElement>; } |  | ||||||
| interface ddeSVGUseElement extends SVGUseElement{ append: ddeAppend<ddeSVGUseElement>; } |  | ||||||
| interface ddeSVGViewElement extends SVGViewElement{ append: ddeAppend<ddeSVGViewElement>; } |  | ||||||
| // editorconfig-checker-enable |  | ||||||
| export interface Signal<V, A> { |  | ||||||
| 	/** The current value of the signal */ |  | ||||||
| 	get(): V; |  | ||||||
| 	/** Set new value of the signal */ |  | ||||||
| 	set(value: V): V; |  | ||||||
| 	toJSON(): V; |  | ||||||
| 	valueOf(): V; |  | ||||||
| } |  | ||||||
| type Action<V>= (this: { value: V, stopPropagation(): void }, ...a: any[])=> typeof signal._ | void; |  | ||||||
| //type SymbolSignal= Symbol; |  | ||||||
| type SymbolOnclear= symbol; |  | ||||||
| type Actions<V>= Record<string | SymbolOnclear, Action<V>>; |  | ||||||
| type OnListenerOptions= Pick<AddEventListenerOptions, "signal"> & { first_time?: boolean }; |  | ||||||
| interface signal{ |  | ||||||
| 	_: Symbol |  | ||||||
| 	/** |  | ||||||
| 	 * Computations signal. This creates a signal which is computed from other signals. |  | ||||||
| 	 * */ |  | ||||||
| 	<V extends ()=> any>(computation: V): Signal<ReturnType<V>, {}> |  | ||||||
| 	/** |  | ||||||
| 	 * Simple example: |  | ||||||
| 	 * ```js |  | ||||||
| 	 * const hello= S("Hello Signal"); |  | ||||||
| 	 * ``` |  | ||||||
| 	 * …simple todo signal: |  | ||||||
| 	 * ```js |  | ||||||
| 	 * const todos= S([], { |  | ||||||
| 	 * 	add(v){ this.value.push(S(v)); }, |  | ||||||
| 	 * 	remove(i){ this.value.splice(i, 1); }, |  | ||||||
| 	 * 	[S.symbols.onclear](){ S.clear(...this.value); }, |  | ||||||
| 	 * }); |  | ||||||
| 	 * ``` |  | ||||||
| 	 * …computed signal: |  | ||||||
| 	 * ```js |  | ||||||
| 	 * const name= S("Jan"); |  | ||||||
| 	 * const surname= S("Andrle"); |  | ||||||
| 	 * const fullname= S(()=> name.get()+" "+surname.get()); |  | ||||||
| 	 * ``` |  | ||||||
| 	 * @param value Initial signal value. Or function computing value from other signals. |  | ||||||
| 	 * @param actions Use to define actions on the signal. Such as add item to the array. |  | ||||||
| 	 *		There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared |  | ||||||
| 	 *		by `S.clear`. |  | ||||||
| 	 * */ |  | ||||||
| 	<V, A extends Actions<V>>(value: V, actions?: A): Signal<V, A>; |  | ||||||
| 	action<S extends Signal<any, Actions<any>>, A extends (S extends Signal<any, infer A> ? A : never), N extends keyof A>( |  | ||||||
| 		signal: S, |  | ||||||
| 		name: N, |  | ||||||
| 		...params: A[N] extends (...args: infer P)=> any ? P : never |  | ||||||
| 	): void; |  | ||||||
| 	clear(...signals: Signal<any, any>[]): void; |  | ||||||
| 	on<T>(signal: Signal<T, any>, onchange: (a: T)=> void, options?: OnListenerOptions): void; |  | ||||||
| 	symbols: { |  | ||||||
| 		//signal: SymbolSignal; |  | ||||||
| 		onclear: SymbolOnclear; |  | ||||||
| 	} |  | ||||||
| 	/** |  | ||||||
| 	 * Reactive element, which is rendered based on the given signal. |  | ||||||
| 	 * ```js |  | ||||||
| 	 * S.el(signal, value=> value ? el("b", "True") : el("i", "False")); |  | ||||||
| 	 * S.el(listS, list=> list.map(li=> el("li", li))); |  | ||||||
| 	 * ``` |  | ||||||
| 	 * */ |  | ||||||
| 	el<S extends any>(signal: Signal<S, any>, el: (v: S)=> Element | Element[] | DocumentFragment): DocumentFragment; |  | ||||||
|  |  | ||||||
| 	observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>; |  | ||||||
| } |  | ||||||
| export const signal: signal; |  | ||||||
| export const S: signal; |  | ||||||
| declare global { |  | ||||||
| 	type ddeSignal<T, A= {}>= Signal<T, A>; |  | ||||||
| 	type ddeAction<V>= Action<V> |  | ||||||
| 	type ddeActions<V>= Actions<V> |  | ||||||
| } |  | ||||||
							
								
								
									
										31
									
								
								dist/esm-with-signals.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								dist/esm-with-signals.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -4,7 +4,7 @@ export interface Signal<V, A> { | |||||||
| 	/** The current value of the signal */ | 	/** The current value of the signal */ | ||||||
| 	get(): V; | 	get(): V; | ||||||
| 	/** Set new value of the signal */ | 	/** Set new value of the signal */ | ||||||
| 	set(value: V): V; | 	set(value: V, force?: boolean): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
| @@ -173,17 +173,30 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options? | |||||||
| declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void; | declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void; | ||||||
| export interface On { | export interface On { | ||||||
| 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | ||||||
| 	<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE; | 	<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & { | ||||||
|  | 		target: EL; | ||||||
|  | 	}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE; | 	<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE; | ||||||
| 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | ||||||
| 	connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE; | 	connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | ||||||
| 	disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE; | 	disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** | ||||||
| 	attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[ | 	 * Fires after the next tick of the Javascript event loop. | ||||||
| 		string, | 	 * This is handy for example to apply some property depending on the element content: | ||||||
| 		string | 	 * ```js | ||||||
| 	]>) => any, options?: AddEventListenerOptions): EE; | 	 * const selected= "Z"; | ||||||
|  | 	 * //... | ||||||
|  | 	 * return el("form").append( | ||||||
|  | 	 *		el("select", null, on.defer(e=> e.value=selected)).append( | ||||||
|  | 	 *			el("option", { value: "A", textContent: "A" }), | ||||||
|  | 	 *			//... | ||||||
|  | 	 *			el("option", { value: "Z", textContent: "Z" }), | ||||||
|  | 	 *		), | ||||||
|  | 	 * ); | ||||||
|  | 	 * ``` | ||||||
|  | 	 * */ | ||||||
|  | 	defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>; | ||||||
| } | } | ||||||
| export const on: On; | export const on: On; | ||||||
| export type Scope = { | export type Scope = { | ||||||
|   | |||||||
							
								
								
									
										282
									
								
								dist/esm-with-signals.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										282
									
								
								dist/esm-with-signals.js
									
									
									
									
										vendored
									
									
								
							| @@ -41,52 +41,13 @@ 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()); | ||||||
| } | } | ||||||
| var Defined = class extends Error { | function requestIdle() { | ||||||
| 	constructor() { | 	return new Promise(function(resolve) { | ||||||
| 		super(); | 		(globalThis.requestIdleCallback || requestAnimationFrame)(resolve); | ||||||
| 		const [curr, ...rest] = this.stack.split("\n"); | 	}); | ||||||
| 		const curr_file = curr.slice(curr.indexOf("@"), curr.indexOf(".js:") + 4); |  | ||||||
| 		const curr_lib = curr_file.includes("src/helpers.js") ? "src/" : curr_file; |  | ||||||
| 		this.stack = rest.find((l) => !l.includes(curr_lib)) || curr; |  | ||||||
| 	} |  | ||||||
| 	get compact() { |  | ||||||
| 		const { stack } = this; |  | ||||||
| 		return stack.slice(0, stack.indexOf("@") + 1) + "\u2026" + stack.slice(stack.lastIndexOf("/")); |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| // src/signals-lib/common.js |  | ||||||
| var signals_global = { |  | ||||||
| 	/** |  | ||||||
| 	* Checks if a value is a signal |  | ||||||
| 	* @param {any} attributes - Value to check |  | ||||||
| 	* @returns {boolean} Whether the value is a signal |  | ||||||
| 	*/ |  | ||||||
| 	isSignal(attributes) { |  | ||||||
| 		return false; |  | ||||||
| 	}, |  | ||||||
| 	/** |  | ||||||
| 	* Processes an attribute that might be reactive |  | ||||||
| 	* @param {Element} obj - Element that owns the attribute |  | ||||||
| 	* @param {string} key - Attribute name |  | ||||||
| 	* @param {any} attr - Attribute value |  | ||||||
| 	* @param {Function} set - Function to set the attribute |  | ||||||
| 	* @returns {any} Processed attribute value |  | ||||||
| 	*/ |  | ||||||
| 	processReactiveAttribute(obj, key, attr, set) { |  | ||||||
| 		return attr; |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
| function registerReactivity(def, global = true) { |  | ||||||
| 	if (global) return oAssign(signals_global, def); |  | ||||||
| 	Object.setPrototypeOf(def, signals_global); |  | ||||||
| 	return def; |  | ||||||
| } |  | ||||||
| function signals(_this) { |  | ||||||
| 	return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // src/dom-common.js | // src/dom-lib/common.js | ||||||
| var enviroment = { | var enviroment = { | ||||||
| 	setDeleteAttr, | 	setDeleteAttr, | ||||||
| 	ssr: "", | 	ssr: "", | ||||||
| @@ -112,7 +73,7 @@ var evc = "dde:connected"; | |||||||
| var evd = "dde:disconnected"; | var evd = "dde:disconnected"; | ||||||
| var eva = "dde:attributeChanged"; | var eva = "dde:attributeChanged"; | ||||||
|  |  | ||||||
| // src/events-observer.js | // src/dom-lib/events-observer.js | ||||||
| var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, { | var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, { | ||||||
| 	get() { | 	get() { | ||||||
| 		return () => { | 		return () => { | ||||||
| @@ -223,11 +184,6 @@ 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(); | ||||||
| @@ -276,7 +232,7 @@ function connectionsChangesObserverConstructor() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // src/events.js | // src/dom-lib/events.js | ||||||
| function dispatchEvent(name, options, host) { | function dispatchEvent(name, options, host) { | ||||||
| 	if (typeof options === "function") { | 	if (typeof options === "function") { | ||||||
| 		host = options; | 		host = options; | ||||||
| @@ -298,6 +254,7 @@ function on(event, listener, options) { | |||||||
| 		return element; | 		return element; | ||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
|  | on.defer = (fn) => setTimeout.bind(null, fn, 0); | ||||||
| var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true }); | var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true }); | ||||||
| on.connected = function(listener, options) { | on.connected = function(listener, options) { | ||||||
| 	options = lifeOptions(options); | 	options = lifeOptions(options); | ||||||
| @@ -321,10 +278,7 @@ on.disconnected = function(listener, options) { | |||||||
| 	}; | 	}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // src/dom.js | // src/dom-lib/scopes.js | ||||||
| function queue(promise) { |  | ||||||
| 	return enviroment.q(promise); |  | ||||||
| } |  | ||||||
| var scopes = [{ | var scopes = [{ | ||||||
| 	get scope() { | 	get scope() { | ||||||
| 		return enviroment.D.body; | 		return enviroment.D.body; | ||||||
| @@ -336,7 +290,7 @@ var store_abort = /* @__PURE__ */ new WeakMap(); | |||||||
| var scope = { | var scope = { | ||||||
| 	/** | 	/** | ||||||
| 	* Gets the current scope | 	* Gets the current scope | ||||||
| 	* @returns {Object} Current scope context | 	* @returns {typeof scopes[number]} Current scope context | ||||||
| 	*/ | 	*/ | ||||||
| 	get current() { | 	get current() { | ||||||
| 		return scopes[scopes.length - 1]; | 		return scopes[scopes.length - 1]; | ||||||
| @@ -399,6 +353,60 @@ var scope = { | |||||||
| 		return scopes.pop(); | 		return scopes.pop(); | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | // src/signals-lib/common.js | ||||||
|  | var signals_global = { | ||||||
|  | 	/** | ||||||
|  | 	* Checks if a value is a signal | ||||||
|  | 	* @param {any} attributes - Value to check | ||||||
|  | 	* @returns {boolean} Whether the value is a signal | ||||||
|  | 	*/ | ||||||
|  | 	isSignal(attributes) { | ||||||
|  | 		return false; | ||||||
|  | 	}, | ||||||
|  | 	/** | ||||||
|  | 	* Processes an attribute that might be reactive | ||||||
|  | 	* @param {Element} obj - Element that owns the attribute | ||||||
|  | 	* @param {string} key - Attribute name | ||||||
|  | 	* @param {any} attr - Attribute value | ||||||
|  | 	* @param {Function} set - Function to set the attribute | ||||||
|  | 	* @returns {any} Processed attribute value | ||||||
|  | 	*/ | ||||||
|  | 	processReactiveAttribute(obj, key, attr, set) { | ||||||
|  | 		return attr; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | function registerReactivity(def, global = true) { | ||||||
|  | 	if (global) return oAssign(signals_global, def); | ||||||
|  | 	Object.setPrototypeOf(def, signals_global); | ||||||
|  | 	return def; | ||||||
|  | } | ||||||
|  | function signals(_this) { | ||||||
|  | 	return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // src/dom-lib/helpers.js | ||||||
|  | function setRemove(obj, prop, key, val) { | ||||||
|  | 	return obj[(isUndef(val) ? "remove" : "set") + prop](key, val); | ||||||
|  | } | ||||||
|  | function setRemoveNS(obj, prop, key, val, ns = null) { | ||||||
|  | 	return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val); | ||||||
|  | } | ||||||
|  | function setDelete(obj, key, val) { | ||||||
|  | 	Reflect.set(obj, key, val); | ||||||
|  | 	if (!isUndef(val)) return; | ||||||
|  | 	return Reflect.deleteProperty(obj, key); | ||||||
|  | } | ||||||
|  | function elementAttribute(element, op, key, value) { | ||||||
|  | 	if (isInstance(element, enviroment.H)) | ||||||
|  | 		return element[op + "Attribute"](key, value); | ||||||
|  | 	return element[op + "AttributeNS"](null, key, value); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // src/dom-lib/el.js | ||||||
|  | function queue(promise) { | ||||||
|  | 	return enviroment.q(promise); | ||||||
|  | } | ||||||
| function append(...els) { | function append(...els) { | ||||||
| 	this.appendOriginal(...els); | 	this.appendOriginal(...els); | ||||||
| 	return this; | 	return this; | ||||||
| @@ -414,16 +422,18 @@ function createElement(tag, attributes, ...addons) { | |||||||
| 	const s = signals(this); | 	const s = signals(this); | ||||||
| 	let scoped = 0; | 	let scoped = 0; | ||||||
| 	let el, el_host; | 	let el, el_host; | ||||||
| 	if (Object(attributes) !== attributes || s.isSignal(attributes)) | 	const att_type = typeof attributes; | ||||||
|  | 	if (att_type === "string" || att_type === "number" || s.isSignal(attributes)) | ||||||
| 		attributes = { textContent: attributes }; | 		attributes = { textContent: attributes }; | ||||||
| 	switch (true) { | 	switch (true) { | ||||||
| 		case typeof tag === "function": { | 		case typeof tag === "function": { | ||||||
| 			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 = tag(attributes || void 0); | 			el = /** @type {Element} */ | ||||||
| 			const is_fragment = isInstance(el, enviroment.F); | 			tag(attributes || void 0); | ||||||
| 			if (el.nodeName === "#comment") break; | 			if (el.nodeName === "#comment") break; | ||||||
|  | 			const is_fragment = isInstance(el, enviroment.F); | ||||||
| 			const el_mark = createElement.mark({ | 			const el_mark = createElement.mark({ | ||||||
| 				type: "component", | 				type: "component", | ||||||
| 				name: tag.name, | 				name: tag.name, | ||||||
| @@ -468,38 +478,6 @@ function createElementNS(ns) { | |||||||
| 		return el; | 		return el; | ||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
| function simulateSlots(element, root = element) { |  | ||||||
| 	const mark_e = "\xB9\u2070", mark_s = "\u2713"; |  | ||||||
| 	const slots = Object.fromEntries( |  | ||||||
| 		Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s]) |  | ||||||
| 	); |  | ||||||
| 	element.append = new Proxy(element.append, { |  | ||||||
| 		apply(orig, _, els) { |  | ||||||
| 			if (els[0] === root) return orig.apply(element, els); |  | ||||||
| 			for (const el of els) { |  | ||||||
| 				const name = (el.slot || "") + mark_e; |  | ||||||
| 				try { |  | ||||||
| 					elementAttribute(el, "remove", "slot"); |  | ||||||
| 				} catch (_error) { |  | ||||||
| 				} |  | ||||||
| 				const slot = slots[name]; |  | ||||||
| 				if (!slot) return; |  | ||||||
| 				if (!slot.name.startsWith(mark_s)) { |  | ||||||
| 					slot.childNodes.forEach((c) => c.remove()); |  | ||||||
| 					slot.name = mark_s + name; |  | ||||||
| 				} |  | ||||||
| 				slot.append(el); |  | ||||||
| 			} |  | ||||||
| 			element.append = orig; |  | ||||||
| 			return element; |  | ||||||
| 		} |  | ||||||
| 	}); |  | ||||||
| 	if (element !== root) { |  | ||||||
| 		const els = Array.from(element.childNodes); |  | ||||||
| 		element.append(...els); |  | ||||||
| 	} |  | ||||||
| 	return root; |  | ||||||
| } |  | ||||||
| var assign_context = /* @__PURE__ */ new WeakMap(); | var assign_context = /* @__PURE__ */ new WeakMap(); | ||||||
| var { setDeleteAttr: setDeleteAttr2 } = enviroment; | var { setDeleteAttr: setDeleteAttr2 } = enviroment; | ||||||
| function assign(element, ...attributes) { | function assign(element, ...attributes) { | ||||||
| @@ -562,11 +540,6 @@ function classListDeclarative(element, toggle) { | |||||||
| 	); | 	); | ||||||
| 	return element; | 	return element; | ||||||
| } | } | ||||||
| function elementAttribute(element, op, key, value) { |  | ||||||
| 	if (isInstance(element, enviroment.H)) |  | ||||||
| 		return element[op + "Attribute"](key, value); |  | ||||||
| 	return element[op + "AttributeNS"](null, key, value); |  | ||||||
| } |  | ||||||
| function isPropSetter(el, key) { | function isPropSetter(el, key) { | ||||||
| 	if (!(key in el)) return false; | 	if (!(key in el)) return false; | ||||||
| 	const des = getPropDescriptor(el, key); | 	const des = getPropDescriptor(el, key); | ||||||
| @@ -590,19 +563,40 @@ function forEachEntries(s, target, element, obj, cb) { | |||||||
| 		cb(key, val); | 		cb(key, val); | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| function setRemove(obj, prop, key, val) { |  | ||||||
| 	return obj[(isUndef(val) ? "remove" : "set") + prop](key, val); |  | ||||||
| } |  | ||||||
| function setRemoveNS(obj, prop, key, val, ns = null) { |  | ||||||
| 	return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val); |  | ||||||
| } |  | ||||||
| function setDelete(obj, key, val) { |  | ||||||
| 	Reflect.set(obj, key, val); |  | ||||||
| 	if (!isUndef(val)) return; |  | ||||||
| 	return Reflect.deleteProperty(obj, key); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // src/customElement.js | // src/dom-lib/customElement.js | ||||||
|  | function simulateSlots(element, root = element) { | ||||||
|  | 	const mark_e = "\xB9\u2070", mark_s = "\u2713"; | ||||||
|  | 	const slots = Object.fromEntries( | ||||||
|  | 		Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s]) | ||||||
|  | 	); | ||||||
|  | 	element.append = new Proxy(element.append, { | ||||||
|  | 		apply(orig, _, els) { | ||||||
|  | 			if (els[0] === root) return orig.apply(element, els); | ||||||
|  | 			for (const el of els) { | ||||||
|  | 				const name = (el.slot || "") + mark_e; | ||||||
|  | 				try { | ||||||
|  | 					elementAttribute(el, "remove", "slot"); | ||||||
|  | 				} catch (_error) { | ||||||
|  | 				} | ||||||
|  | 				const slot = slots[name]; | ||||||
|  | 				if (!slot) return; | ||||||
|  | 				if (!slot.name.startsWith(mark_s)) { | ||||||
|  | 					slot.childNodes.forEach((c) => c.remove()); | ||||||
|  | 					slot.name = mark_s + name; | ||||||
|  | 				} | ||||||
|  | 				slot.append(el); | ||||||
|  | 			} | ||||||
|  | 			element.append = orig; | ||||||
|  | 			return element; | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 	if (element !== root) { | ||||||
|  | 		const els = Array.from(element.childNodes); | ||||||
|  | 		element.append(...els); | ||||||
|  | 	} | ||||||
|  | 	return root; | ||||||
|  | } | ||||||
| function customElementRender(target, render, props = {}) { | function customElementRender(target, render, props = {}) { | ||||||
| 	const custom_element = target.host || target; | 	const custom_element = target.host || target; | ||||||
| 	scope.push({ | 	scope.push({ | ||||||
| @@ -657,9 +651,9 @@ function memo(key, generator) { | |||||||
| memo.isScope = function(obj) { | memo.isScope = function(obj) { | ||||||
| 	return obj[memoMark]; | 	return obj[memoMark]; | ||||||
| }; | }; | ||||||
| memo.scope = function memoScope(fun, { signal: signal2, onlyLast } = {}) { | memo.scope = function memoScopeCreate(fun, { signal: signal2, onlyLast } = {}) { | ||||||
| 	let cache = oCreate(); | 	let cache = oCreate(); | ||||||
| 	function memoScope2(...args) { | 	function memoScope(...args) { | ||||||
| 		if (signal2 && signal2.aborted) | 		if (signal2 && signal2.aborted) | ||||||
| 			return fun.apply(this, args); | 			return fun.apply(this, args); | ||||||
| 		let cache_local = onlyLast ? cache : oCreate(); | 		let cache_local = onlyLast ? cache : oCreate(); | ||||||
| @@ -674,28 +668,28 @@ memo.scope = function memoScope(fun, { signal: signal2, onlyLast } = {}) { | |||||||
| 		cache = cache_local; | 		cache = cache_local; | ||||||
| 		return out; | 		return out; | ||||||
| 	} | 	} | ||||||
| 	memoScope2[memoMark] = true; | 	memoScope[memoMark] = true; | ||||||
| 	memoScope2.clear = () => cache = oCreate(); | 	memoScope.clear = () => cache = oCreate(); | ||||||
| 	if (signal2) signal2.addEventListener("abort", memoScope2.clear); | 	if (signal2) signal2.addEventListener("abort", memoScope.clear); | ||||||
| 	return memoScope2; | 	return memoScope; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // src/signals-lib/helpers.js | // src/signals-lib/helpers.js | ||||||
| var mark = "__dde_signal"; | var mark = "__dde_signal"; | ||||||
| var queueSignalWrite = /* @__PURE__ */ (() => { | var queueSignalWrite = /* @__PURE__ */ (() => { | ||||||
| 	let pendingSignals = /* @__PURE__ */ new Set(); | 	let pendingSignals = /* @__PURE__ */ new Map(); | ||||||
| 	let scheduled = false; | 	let scheduled = false; | ||||||
| 	function flushSignals() { | 	function flushSignals() { | ||||||
| 		scheduled = false; | 		scheduled = false; | ||||||
| 		const todo = pendingSignals; | 		const todo = pendingSignals; | ||||||
| 		pendingSignals = /* @__PURE__ */ new Set(); | 		pendingSignals = /* @__PURE__ */ new Map(); | ||||||
| 		for (const signal2 of todo) { | 		for (const [signal2, force] of todo) { | ||||||
| 			const M = signal2[mark]; | 			const M = signal2[mark]; | ||||||
| 			if (M) M.listeners.forEach((l) => l(M.value)); | 			if (M) M.listeners.forEach((l) => l(M.value, force)); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return function(s) { | 	return function(s, force = false) { | ||||||
| 		pendingSignals.add(s); | 		pendingSignals.set(s, pendingSignals.get(s) || force); | ||||||
| 		if (scheduled) return; | 		if (scheduled) return; | ||||||
| 		scheduled = true; | 		scheduled = true; | ||||||
| 		queueMicrotask(flushSignals); | 		queueMicrotask(flushSignals); | ||||||
| @@ -703,13 +697,10 @@ var queueSignalWrite = /* @__PURE__ */ (() => { | |||||||
| })(); | })(); | ||||||
|  |  | ||||||
| // src/signals-lib/signals-lib.js | // src/signals-lib/signals-lib.js | ||||||
| var Signal = oCreate(null, { | var SignalReadOnly = 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); | ||||||
| 	} }, | 	} }, | ||||||
| @@ -717,9 +708,9 @@ var Signal = oCreate(null, { | |||||||
| 		return this[mark] && this[mark].value; | 		return this[mark] && this[mark].value; | ||||||
| 	} } | 	} } | ||||||
| }); | }); | ||||||
| var SignalReadOnly = oCreate(Signal, { | var Signal = oCreate(SignalReadOnly, { | ||||||
| 	set: { value() { | 	set: { value(...v) { | ||||||
| 		return; | 		return write(this, ...v); | ||||||
| 	} } | 	} } | ||||||
| }); | }); | ||||||
| function isSignal(candidate) { | function isSignal(candidate) { | ||||||
| @@ -732,11 +723,11 @@ function signal(value, actions) { | |||||||
| 		return create(false, value, actions); | 		return create(false, value, actions); | ||||||
| 	if (isSignal(value)) return value; | 	if (isSignal(value)) return value; | ||||||
| 	const out = create(true); | 	const out = create(true); | ||||||
| 	function contextReWatch() { | 	function contextReWatch(_, force) { | ||||||
| 		const [origin, ...deps_old] = deps.get(contextReWatch); | 		const [origin, ...deps_old] = deps.get(contextReWatch); | ||||||
| 		deps.set(contextReWatch, /* @__PURE__ */ new Set([origin])); | 		deps.set(contextReWatch, /* @__PURE__ */ new Set([origin])); | ||||||
| 		stack_watch.push(contextReWatch); | 		stack_watch.push(contextReWatch); | ||||||
| 		write(out, value()); | 		write(out, value(), force); | ||||||
| 		stack_watch.pop(); | 		stack_watch.pop(); | ||||||
| 		if (!deps_old.length) return; | 		if (!deps_old.length) return; | ||||||
| 		const deps_curr = deps.get(contextReWatch); | 		const deps_curr = deps.get(contextReWatch); | ||||||
| @@ -758,7 +749,7 @@ signal.action = function(s, name, ...a) { | |||||||
| 		throw new Error(`Action "${name}" not defined. See ${mark}.actions.`); | 		throw new Error(`Action "${name}" not defined. See ${mark}.actions.`); | ||||||
| 	actions[name].apply(M, a); | 	actions[name].apply(M, a); | ||||||
| 	if (M.skip) return delete M.skip; | 	if (M.skip) return delete M.skip; | ||||||
| 	queueSignalWrite(s); | 	queueSignalWrite(s, true); | ||||||
| }; | }; | ||||||
| signal.on = function on2(s, listener, options = {}) { | signal.on = function on2(s, listener, options = {}) { | ||||||
| 	const { signal: as } = options; | 	const { signal: as } = options; | ||||||
| @@ -794,17 +785,17 @@ signal.clear = function(...signals2) { | |||||||
| }; | }; | ||||||
| var key_reactive = "__dde_reactive"; | var key_reactive = "__dde_reactive"; | ||||||
| signal.el = function(s, map) { | signal.el = function(s, map) { | ||||||
| 	map = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | 	const mapScoped = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | ||||||
| 	const mark_start = createElement.mark({ type: "reactive", source: new Defined().compact }, true); | 	const { current } = scope, { scope: sc } = current; | ||||||
|  | 	const mark_start = createElement.mark({ type: "reactive", component: sc && sc.name || "" }, true); | ||||||
| 	const mark_end = mark_start.end; | 	const mark_end = mark_start.end; | ||||||
| 	const out = enviroment.D.createDocumentFragment(); | 	const out = enviroment.D.createDocumentFragment(); | ||||||
| 	out.append(mark_start, mark_end); | 	out.append(mark_start, mark_end); | ||||||
| 	const { current } = scope; |  | ||||||
| 	const reRenderReactiveElement = (v) => { | 	const reRenderReactiveElement = (v) => { | ||||||
| 		if (!mark_start.parentNode || !mark_end.parentNode) | 		if (!mark_start.parentNode || !mark_end.parentNode) | ||||||
| 			return removeSignalListener(s, reRenderReactiveElement); | 			return removeSignalListener(s, reRenderReactiveElement); | ||||||
| 		scope.push(current); | 		scope.push(current); | ||||||
| 		let els = map(v); | 		let els = mapScoped(v); | ||||||
| 		scope.pop(); | 		scope.pop(); | ||||||
| 		if (!Array.isArray(els)) | 		if (!Array.isArray(els)) | ||||||
| 			els = [els]; | 			els = [els]; | ||||||
| @@ -824,14 +815,14 @@ signal.el = function(s, map) { | |||||||
| 	current.host(on.disconnected( | 	current.host(on.disconnected( | ||||||
| 		() => ( | 		() => ( | ||||||
| 			/*! Clears cached elements for reactive element `S.el` */ | 			/*! Clears cached elements for reactive element `S.el` */ | ||||||
| 			map.clear() | 			mapScoped.clear() | ||||||
| 		) | 		) | ||||||
| 	)); | 	)); | ||||||
| 	return out; | 	return out; | ||||||
| }; | }; | ||||||
| function requestCleanUpReactives(host) { | function requestCleanUpReactives(host) { | ||||||
| 	if (!host || !host[key_reactive]) return; | 	if (!host || !host[key_reactive]) return; | ||||||
| 	(requestIdleCallback || setTimeout)(function() { | 	requestIdle().then(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)); | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| @@ -896,10 +887,10 @@ 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) { | ||||||
| 		if (element[key_reactive]) | 		const is_first = !element[key_reactive]; | ||||||
| 			return element[key_reactive].push([[s, listener], ...notes]); | 		if (is_first) element[key_reactive] = []; | ||||||
| 		element[key_reactive] = []; | 		element[key_reactive].push([[s, listener], ...notes]); | ||||||
| 		if (current.prevent) return; | 		if (!is_first || 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`, …?). | ||||||
| @@ -914,7 +905,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, is_readonly); | 	const SI = toSignal(varS, value, actions); | ||||||
| 	cleanUpRegistry.register(SI, SI[mark]); | 	cleanUpRegistry.register(SI, SI[mark]); | ||||||
| 	return SI; | 	return SI; | ||||||
| } | } | ||||||
| @@ -926,7 +917,7 @@ var protoSigal = oAssign(oCreate(), { | |||||||
| 		this.skip = true; | 		this.skip = true; | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| function toSignal(s, value, actions, readonly = false) { | function toSignal(s, value, actions) { | ||||||
| 	const onclear = []; | 	const onclear = []; | ||||||
| 	if (typeOf(actions) !== "[object Object]") | 	if (typeOf(actions) !== "[object Object]") | ||||||
| 		actions = {}; | 		actions = {}; | ||||||
| @@ -942,9 +933,7 @@ function toSignal(s, value, actions, readonly = false) { | |||||||
| 			actions, | 			actions, | ||||||
| 			onclear, | 			onclear, | ||||||
| 			host, | 			host, | ||||||
| 			listeners: /* @__PURE__ */ new Set(), | 			listeners: /* @__PURE__ */ new Set() | ||||||
| 			defined: new Defined().stack, |  | ||||||
| 			readonly |  | ||||||
| 		}), | 		}), | ||||||
| 		enumerable: false, | 		enumerable: false, | ||||||
| 		writable: false, | 		writable: false, | ||||||
| @@ -967,7 +956,7 @@ function write(s, value, force) { | |||||||
| 	const M = s[mark]; | 	const M = s[mark]; | ||||||
| 	if (!M || !force && M.value === value) return; | 	if (!M || !force && M.value === value) return; | ||||||
| 	M.value = value; | 	M.value = value; | ||||||
| 	queueSignalWrite(s); | 	queueSignalWrite(s, force); | ||||||
| 	return value; | 	return value; | ||||||
| } | } | ||||||
| function addSignalListener(s, listener) { | function addSignalListener(s, listener) { | ||||||
| @@ -1004,7 +993,6 @@ export { | |||||||
| 	dispatchEvent, | 	dispatchEvent, | ||||||
| 	createElement as el, | 	createElement as el, | ||||||
| 	createElementNS as elNS, | 	createElementNS as elNS, | ||||||
| 	elementAttribute, |  | ||||||
| 	isSignal, | 	isSignal, | ||||||
| 	lifecyclesToEvents, | 	lifecyclesToEvents, | ||||||
| 	memo, | 	memo, | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								dist/esm-with-signals.min.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								dist/esm-with-signals.min.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -4,7 +4,7 @@ export interface Signal<V, A> { | |||||||
| 	/** The current value of the signal */ | 	/** The current value of the signal */ | ||||||
| 	get(): V; | 	get(): V; | ||||||
| 	/** Set new value of the signal */ | 	/** Set new value of the signal */ | ||||||
| 	set(value: V): V; | 	set(value: V, force?: boolean): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
| @@ -173,17 +173,30 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options? | |||||||
| declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void; | declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void; | ||||||
| export interface On { | export interface On { | ||||||
| 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | ||||||
| 	<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE; | 	<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & { | ||||||
|  | 		target: EL; | ||||||
|  | 	}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE; | 	<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE; | ||||||
| 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | ||||||
| 	connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE; | 	connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | ||||||
| 	disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE; | 	disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** | ||||||
| 	attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[ | 	 * Fires after the next tick of the Javascript event loop. | ||||||
| 		string, | 	 * This is handy for example to apply some property depending on the element content: | ||||||
| 		string | 	 * ```js | ||||||
| 	]>) => any, options?: AddEventListenerOptions): EE; | 	 * const selected= "Z"; | ||||||
|  | 	 * //... | ||||||
|  | 	 * return el("form").append( | ||||||
|  | 	 *		el("select", null, on.defer(e=> e.value=selected)).append( | ||||||
|  | 	 *			el("option", { value: "A", textContent: "A" }), | ||||||
|  | 	 *			//... | ||||||
|  | 	 *			el("option", { value: "Z", textContent: "Z" }), | ||||||
|  | 	 *		), | ||||||
|  | 	 * ); | ||||||
|  | 	 * ``` | ||||||
|  | 	 * */ | ||||||
|  | 	defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>; | ||||||
| } | } | ||||||
| export const on: On; | export const on: On; | ||||||
| export type Scope = { | export type Scope = { | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								dist/esm-with-signals.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								dist/esm-with-signals.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										568
									
								
								dist/esm.d.min.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										568
									
								
								dist/esm.d.min.ts
									
									
									
									
										vendored
									
									
								
							| @@ -1,568 +0,0 @@ | |||||||
| declare global{ /* ddeSignal */ } |  | ||||||
| type CustomElementTagNameMap= { '#text': Text, '#comment': Comment } |  | ||||||
| type SupportedElement= |  | ||||||
| 		HTMLElementTagNameMap[keyof HTMLElementTagNameMap] |  | ||||||
| 	|	SVGElementTagNameMap[keyof SVGElementTagNameMap] |  | ||||||
| 	|	MathMLElementTagNameMap[keyof MathMLElementTagNameMap] |  | ||||||
| 	|	CustomElementTagNameMap[keyof CustomElementTagNameMap] |  | ||||||
| declare global { |  | ||||||
| 	type ddeComponentAttributes= Record<any, any> | undefined; |  | ||||||
| 	type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node>= (element: El)=> any; |  | ||||||
| 	type ddeString= string | ddeSignal<string> |  | ||||||
| 	type ddeStringable= ddeString | number | ddeSignal<number> |  | ||||||
| } |  | ||||||
| type PascalCase= |  | ||||||
| `${Capitalize<string>}${string}`; |  | ||||||
| type AttrsModified= { |  | ||||||
| 	/** |  | ||||||
| 	 * Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API). |  | ||||||
| 	 */ |  | ||||||
| 	style: Partial<CSSStyleDeclaration> | ddeString |  | ||||||
| 		| Partial<{ [K in keyof CSSStyleDeclaration]: ddeSignal<CSSStyleDeclaration[K]> }> |  | ||||||
| 	/** |  | ||||||
| 	 * Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1. |  | ||||||
| 	 * In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))` |  | ||||||
| 	 * for others. |  | ||||||
| 	 */ |  | ||||||
| 	classList: Record<string,-1|0|1|boolean|ddeSignal<-1|0|1|boolean>>, |  | ||||||
| 	/** |  | ||||||
| 	 * Used by the dataset HTML attribute to represent data for custom attributes added to elements. |  | ||||||
| 	 * Values are converted to string (see {@link DOMStringMap}). |  | ||||||
| 	 * |  | ||||||
| 	 * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMStringMap) |  | ||||||
| 	 * */ |  | ||||||
| 	dataset: Record<string, ddeStringable>, |  | ||||||
| 	/** |  | ||||||
| 	 * Sets `aria-*` simiraly to `dataset` |  | ||||||
| 	 * */ |  | ||||||
| 	ariaset: Record<string, ddeString>, |  | ||||||
| }	& Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, ddeString> |  | ||||||
| 	& Record<`.${string}`, any> |  | ||||||
| type _fromElsInterfaces<EL extends SupportedElement>= Omit<EL, keyof AttrsModified>; |  | ||||||
| type IsReadonly<T, K extends keyof T> = |  | ||||||
| 	T extends { readonly [P in K]: T[K] } ? true : false; |  | ||||||
| /** |  | ||||||
|  * Just element attributtes |  | ||||||
|  * |  | ||||||
|  * In most cases, you can use native propertie such as |  | ||||||
|  * [MDN WEB/API/Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) and so on |  | ||||||
|  * (e.g. [`Text`](https://developer.mozilla.org/en-US/docs/Web/API/Text)). |  | ||||||
|  * |  | ||||||
|  * There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives. |  | ||||||
|  * @private |  | ||||||
|  */ |  | ||||||
| type ElementAttributes<T extends SupportedElement>= Partial<{ |  | ||||||
| 	[K in keyof _fromElsInterfaces<T>]: |  | ||||||
| 		_fromElsInterfaces<T>[K] extends ((...p: any[])=> any) |  | ||||||
| 			? _fromElsInterfaces<T>[K] | ((...p: Parameters<_fromElsInterfaces<T>[K]>)=> |  | ||||||
| 																	ddeSignal<ReturnType<_fromElsInterfaces<T>[K]>>) |  | ||||||
| 			: (IsReadonly<_fromElsInterfaces<T>, K> extends false |  | ||||||
| 				? _fromElsInterfaces<T>[K] | ddeSignal<_fromElsInterfaces<T>[K]> |  | ||||||
| 				: ddeStringable) |  | ||||||
| } & AttrsModified> & Record<string, any>; |  | ||||||
| export function classListDeclarative<El extends SupportedElement>( |  | ||||||
| 	element: El, |  | ||||||
| 	classList: AttrsModified["classList"] |  | ||||||
| ): El |  | ||||||
| export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El |  | ||||||
| export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>( |  | ||||||
| 	element: El, |  | ||||||
| 	attr: ATT, |  | ||||||
| 	value: ElementAttributes<El>[ATT] |  | ||||||
| ): ElementAttributes<El>[ATT] |  | ||||||
|  |  | ||||||
| type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap; |  | ||||||
| export namespace el { |  | ||||||
| 	/** |  | ||||||
| 	 * Creates a marker comment for elements |  | ||||||
| 	 * |  | ||||||
| 	 * @param attrs - Marker attributes |  | ||||||
| 	 * @param [is_open=false] - Whether the marker is open-ended |  | ||||||
| 	 * @returns Comment node marker |  | ||||||
| 	 */ |  | ||||||
| 	export function mark( |  | ||||||
| 		attrs: { type: "component"|"reactive"|"later", name?: string, host?: "this"|"parentElement" }, |  | ||||||
| 		is_open?: boolean |  | ||||||
| 	): Comment; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function el< |  | ||||||
| 	A extends ddeComponentAttributes, |  | ||||||
| 	EL extends SupportedElement | ddeDocumentFragment |  | ||||||
| >( |  | ||||||
| 	component: (attr: A, ...rest: any[])=> EL, |  | ||||||
| 	attrs?: NoInfer<A>, |  | ||||||
| 	...addons: ddeElementAddon<EL>[] |  | ||||||
| ): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] |  | ||||||
| 	? EL |  | ||||||
| 	: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement ) |  | ||||||
| export function el< |  | ||||||
| 	A extends { textContent: ddeStringable }, |  | ||||||
| 	EL extends SupportedElement | ddeDocumentFragment |  | ||||||
| >( |  | ||||||
| 	component: (attr: A, ...rest: any[])=> EL, |  | ||||||
| 	attrs?: NoInfer<A>["textContent"], |  | ||||||
| 	...addons: ddeElementAddon<EL>[] |  | ||||||
| ): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] |  | ||||||
| 	? EL |  | ||||||
| 	: ( EL extends ddeDocumentFragment ? EL : ddeHTMLElement ) |  | ||||||
| export function el< |  | ||||||
| 	TAG extends keyof ExtendedHTMLElementTagNameMap, |  | ||||||
| >( |  | ||||||
| 	tag_name: TAG, |  | ||||||
| 	attrs?: ElementAttributes<ExtendedHTMLElementTagNameMap[NoInfer<TAG>]> | ddeStringable, |  | ||||||
| 	...addons: ddeElementAddon< |  | ||||||
| 		ExtendedHTMLElementTagNameMap[NoInfer<TAG>] |  | ||||||
| 	>[], // TODO: for now addons must have the same element |  | ||||||
| ): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement |  | ||||||
| export function el( |  | ||||||
| 	tag_name?: "<>", |  | ||||||
| ): ddeDocumentFragment |  | ||||||
| export function el( |  | ||||||
| 	tag_name: string, |  | ||||||
| 	attrs?: ElementAttributes<HTMLElement> | ddeStringable, |  | ||||||
| 	...addons: ddeElementAddon<HTMLElement>[] |  | ||||||
| ): ddeHTMLElement |  | ||||||
| export { el as createElement } |  | ||||||
|  |  | ||||||
| export function elNS( |  | ||||||
| 	namespace: "http://www.w3.org/2000/svg" |  | ||||||
| ): < |  | ||||||
| 	TAG extends keyof SVGElementTagNameMap & string, |  | ||||||
| 	EL extends ( TAG extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[TAG] : SVGElement ), |  | ||||||
| >( |  | ||||||
| 	tag_name: TAG, |  | ||||||
| 	attrs?: ElementAttributes<NoInfer<EL>> | ddeStringable, |  | ||||||
| 	...addons: ddeElementAddon<NoInfer<EL>>[] |  | ||||||
| )=>  TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement |  | ||||||
| export function elNS( |  | ||||||
| 	namespace: "http://www.w3.org/1998/Math/MathML" |  | ||||||
| ): < |  | ||||||
| 	TAG extends keyof MathMLElementTagNameMap & string, |  | ||||||
| 	EL extends ( TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement ), |  | ||||||
| >( |  | ||||||
| 	tag_name: TAG, |  | ||||||
| 	attrs?: ddeStringable | Partial<{ |  | ||||||
| 		[key in keyof EL]: EL[key] | ddeSignal<EL[key]> | string | number | boolean |  | ||||||
| 	}>, |  | ||||||
| 	...addons: ddeElementAddon<NoInfer<EL>>[] |  | ||||||
| )=> ddeMathMLElement |  | ||||||
| export function elNS( |  | ||||||
| 	namespace: string |  | ||||||
| ): ( |  | ||||||
| 	tag_name: string, |  | ||||||
| 	attrs?: string | ddeStringable | Record<string, any>, |  | ||||||
| 	...addons: ddeElementAddon<SupportedElement>[] |  | ||||||
| )=> SupportedElement |  | ||||||
| export { elNS as createElementNS } |  | ||||||
|  |  | ||||||
| export function chainableAppend<EL extends SupportedElement>(el: EL): EL; |  | ||||||
| /** Simulate slots for ddeComponents */ |  | ||||||
| export function simulateSlots<EL extends SupportedElement | DocumentFragment>( |  | ||||||
| 	root: EL, |  | ||||||
| ): EL |  | ||||||
| /** |  | ||||||
|  * Simulate slots in Custom Elements without using `shadowRoot`. |  | ||||||
|  * @param el Custom Element root element |  | ||||||
|  * @param body Body of the custom element |  | ||||||
|  * */ |  | ||||||
| export function simulateSlots<EL extends SupportedElement | DocumentFragment>( |  | ||||||
| 	el: HTMLElement, |  | ||||||
| 	body: EL, |  | ||||||
| ): EL |  | ||||||
|  |  | ||||||
| export function dispatchEvent(name: keyof DocumentEventMap | string, element: SupportedElement): |  | ||||||
| 	(data?: any)=> void; |  | ||||||
| export function dispatchEvent(name: keyof DocumentEventMap | string, options?: EventInit): |  | ||||||
| 	(element: SupportedElement, data?: any)=> void; |  | ||||||
| export function dispatchEvent( |  | ||||||
| 	name: keyof DocumentEventMap | string, |  | ||||||
| 	options: EventInit | null, |  | ||||||
| 	element: SupportedElement | (()=> SupportedElement) |  | ||||||
| ): (data?: any)=> void; |  | ||||||
| interface On{ |  | ||||||
| 	/** Listens to the DOM event. See {@link Document.addEventListener} */ |  | ||||||
| 	< |  | ||||||
| 		Event extends keyof DocumentEventMap, |  | ||||||
| 		EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>, |  | ||||||
| 		>( |  | ||||||
| 			type: Event, |  | ||||||
| 			listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, |  | ||||||
| 			options?: AddEventListenerOptions |  | ||||||
| 		) : EE; |  | ||||||
| 	< |  | ||||||
| 		EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>, |  | ||||||
| 		>( |  | ||||||
| 			type: string, |  | ||||||
| 			listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent ) => any, |  | ||||||
| 			options?: AddEventListenerOptions |  | ||||||
| 		) : EE; |  | ||||||
| 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line |  | ||||||
| 	connected< |  | ||||||
| 		EE extends ddeElementAddon<SupportedElement>, |  | ||||||
| 		El extends ( EE extends ddeElementAddon<infer El> ? El : never ) |  | ||||||
| 		>( |  | ||||||
| 			listener: (this: El, event: CustomEvent<El>) => any, |  | ||||||
| 			options?: AddEventListenerOptions |  | ||||||
| 		) : EE; |  | ||||||
| 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line |  | ||||||
| 	disconnected< |  | ||||||
| 		EE extends ddeElementAddon<SupportedElement>, |  | ||||||
| 		El extends ( EE extends ddeElementAddon<infer El> ? El : never ) |  | ||||||
| 		>( |  | ||||||
| 			listener: (this: El, event: CustomEvent<void>) => any, |  | ||||||
| 			options?: AddEventListenerOptions |  | ||||||
| 		) : EE; |  | ||||||
| 	/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line |  | ||||||
| 	attributeChanged< |  | ||||||
| 		EE extends ddeElementAddon<SupportedElement>, |  | ||||||
| 		El extends ( EE extends ddeElementAddon<infer El> ? El : never ) |  | ||||||
| 		>( |  | ||||||
| 			listener: (this: El, event: CustomEvent<[ string, string ]>) => any, |  | ||||||
| 			options?: AddEventListenerOptions |  | ||||||
| 		) : EE; |  | ||||||
| } |  | ||||||
| export const on: On; |  | ||||||
|  |  | ||||||
| type Scope= { |  | ||||||
| 	scope: Node | Function | Object, |  | ||||||
| 	host: ddeElementAddon<any>, |  | ||||||
| 	custom_element: false | HTMLElement, |  | ||||||
| 	prevent: boolean |  | ||||||
| }; |  | ||||||
| /** Current scope created last time the `el(Function)` was invoke. (Or {@link scope.push}) */ |  | ||||||
| export const scope: { |  | ||||||
| 	current: Scope, |  | ||||||
| 	/** Stops all automatizations. E. g. signals used as attributes in current scope |  | ||||||
| 	 * registers removing these listeners (and clean signal if no other listeners are detected) |  | ||||||
| 	 * on `disconnected` event. */ |  | ||||||
| 	preventDefault<T extends boolean>(prevent: T): T, |  | ||||||
| 	/** |  | ||||||
| 	 * This represents reference to the current host element — `scope.host()`. |  | ||||||
| 	 * It can be also used to register Addon(s) (functions to be called when component is initized) |  | ||||||
| 	 * — `scope.host(on.connected(console.log))`. |  | ||||||
| 	 * */ |  | ||||||
| 	host: (...addons: ddeElementAddon<SupportedElement>[])=> HTMLElement, |  | ||||||
|  |  | ||||||
| 	state: Scope[], |  | ||||||
| 	/** Adds new child scope. All attributes are inherited by default. */ |  | ||||||
| 	push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>, |  | ||||||
| 	/** Adds root scope as a child of the current scope. */ |  | ||||||
| 	pushRoot(): ReturnType<Array<Scope>["push"]>, |  | ||||||
| 	/** Removes last/current child scope. */ |  | ||||||
| 	pop(): ReturnType<Array<Scope>["pop"]>, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export function customElementRender< |  | ||||||
| 	EL extends HTMLElement, |  | ||||||
| 	P extends any = Record<string, string | ddeSignal<string>> |  | ||||||
| >( |  | ||||||
| 	target: ShadowRoot | EL, |  | ||||||
| 	render: (props: P)=> SupportedElement | DocumentFragment, |  | ||||||
| 	props?: P | ((el: EL)=> P) |  | ||||||
| ): EL |  | ||||||
| export function customElementWithDDE<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL |  | ||||||
| export function lifecyclesToEvents<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL |  | ||||||
| export function observedAttributes(custom_element: HTMLElement): Record<string, string> |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * This is used primarly for server side rendering. To be sure that all async operations |  | ||||||
|  * are finished before the page is sent to the client. |  | ||||||
|  * ``` |  | ||||||
|  *	// on component |  | ||||||
|  *	function component(){ |  | ||||||
|  *		… |  | ||||||
|  *		queue(fetch(...).then(...)); |  | ||||||
|  *	} |  | ||||||
|  * |  | ||||||
|  * // building the page |  | ||||||
|  * async function build(){ |  | ||||||
|  *		const { component }= await import("./component.js"); |  | ||||||
|  *		document.body.append(el(component)); |  | ||||||
|  *		await queue(); |  | ||||||
|  *		retutn document.body.innerHTML; |  | ||||||
|  *	} |  | ||||||
|  * ``` |  | ||||||
|  * */ |  | ||||||
| export function queue(promise?: Promise<unknown>): Promise<unknown>; |  | ||||||
|  |  | ||||||
| /* TypeScript MEH */ |  | ||||||
| declare global{ |  | ||||||
| 	type ddeAppend<el>= (...nodes: (Node | string)[])=> el; |  | ||||||
|  |  | ||||||
| 	interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; } |  | ||||||
| 	interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; } |  | ||||||
| 	interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; } |  | ||||||
| 	interface ddeMathMLElement extends MathMLElement{ append: ddeAppend<ddeMathMLElement>; } |  | ||||||
|  |  | ||||||
| 	interface ddeHTMLElementTagNameMap { |  | ||||||
| 		"a": ddeHTMLAnchorElement; |  | ||||||
| 		"area": ddeHTMLAreaElement; |  | ||||||
| 		"audio": ddeHTMLAudioElement; |  | ||||||
| 		"base": ddeHTMLBaseElement; |  | ||||||
| 		"blockquote": ddeHTMLQuoteElement; |  | ||||||
| 		"body": ddeHTMLBodyElement; |  | ||||||
| 		"br": ddeHTMLBRElement; |  | ||||||
| 		"button": ddeHTMLButtonElement; |  | ||||||
| 		"canvas": ddeHTMLCanvasElement; |  | ||||||
| 		"caption": ddeHTMLTableCaptionElement; |  | ||||||
| 		"col": ddeHTMLTableColElement; |  | ||||||
| 		"colgroup": ddeHTMLTableColElement; |  | ||||||
| 		"data": ddeHTMLDataElement; |  | ||||||
| 		"datalist": ddeHTMLDataListElement; |  | ||||||
| 		"del": ddeHTMLModElement; |  | ||||||
| 		"details": ddeHTMLDetailsElement; |  | ||||||
| 		"dialog": ddeHTMLDialogElement; |  | ||||||
| 		"div": ddeHTMLDivElement; |  | ||||||
| 		"dl": ddeHTMLDListElement; |  | ||||||
| 		"embed": ddeHTMLEmbedElement; |  | ||||||
| 		"fieldset": ddeHTMLFieldSetElement; |  | ||||||
| 		"form": ddeHTMLFormElement; |  | ||||||
| 		"h1": ddeHTMLHeadingElement; |  | ||||||
| 		"h2": ddeHTMLHeadingElement; |  | ||||||
| 		"h3": ddeHTMLHeadingElement; |  | ||||||
| 		"h4": ddeHTMLHeadingElement; |  | ||||||
| 		"h5": ddeHTMLHeadingElement; |  | ||||||
| 		"h6": ddeHTMLHeadingElement; |  | ||||||
| 		"head": ddeHTMLHeadElement; |  | ||||||
| 		"hr": ddeHTMLHRElement; |  | ||||||
| 		"html": ddeHTMLHtmlElement; |  | ||||||
| 		"iframe": ddeHTMLIFrameElement; |  | ||||||
| 		"img": ddeHTMLImageElement; |  | ||||||
| 		"input": ddeHTMLInputElement; |  | ||||||
| 		"ins": ddeHTMLModElement; |  | ||||||
| 		"label": ddeHTMLLabelElement; |  | ||||||
| 		"legend": ddeHTMLLegendElement; |  | ||||||
| 		"li": ddeHTMLLIElement; |  | ||||||
| 		"link": ddeHTMLLinkElement; |  | ||||||
| 		"map": ddeHTMLMapElement; |  | ||||||
| 		"menu": ddeHTMLMenuElement; |  | ||||||
| 		"meta": ddeHTMLMetaElement; |  | ||||||
| 		"meter": ddeHTMLMeterElement; |  | ||||||
| 		"object": ddeHTMLObjectElement; |  | ||||||
| 		"ol": ddeHTMLOListElement; |  | ||||||
| 		"optgroup": ddeHTMLOptGroupElement; |  | ||||||
| 		"option": ddeHTMLOptionElement; |  | ||||||
| 		"output": ddeHTMLOutputElement; |  | ||||||
| 		"p": ddeHTMLParagraphElement; |  | ||||||
| 		"picture": ddeHTMLPictureElement; |  | ||||||
| 		"pre": ddeHTMLPreElement; |  | ||||||
| 		"progress": ddeHTMLProgressElement; |  | ||||||
| 		"q": ddeHTMLQuoteElement; |  | ||||||
| 		"script": ddeHTMLScriptElement; |  | ||||||
| 		"select": ddeHTMLSelectElement; |  | ||||||
| 		"slot": ddeHTMLSlotElement; |  | ||||||
| 		"source": ddeHTMLSourceElement; |  | ||||||
| 		"span": ddeHTMLSpanElement; |  | ||||||
| 		"style": ddeHTMLStyleElement; |  | ||||||
| 		"table": ddeHTMLTableElement; |  | ||||||
| 		"tbody": ddeHTMLTableSectionElement; |  | ||||||
| 		"td": ddeHTMLTableCellElement; |  | ||||||
| 		"template": ddeHTMLTemplateElement; |  | ||||||
| 		"textarea": ddeHTMLTextAreaElement; |  | ||||||
| 		"tfoot": ddeHTMLTableSectionElement; |  | ||||||
| 		"th": ddeHTMLTableCellElement; |  | ||||||
| 		"thead": ddeHTMLTableSectionElement; |  | ||||||
| 		"time": ddeHTMLTimeElement; |  | ||||||
| 		"title": ddeHTMLTitleElement; |  | ||||||
| 		"tr": ddeHTMLTableRowElement; |  | ||||||
| 		"track": ddeHTMLTrackElement; |  | ||||||
| 		"ul": ddeHTMLUListElement; |  | ||||||
| 		"video": ddeHTMLVideoElement; |  | ||||||
| 	} |  | ||||||
| 	interface ddeSVGElementTagNameMap { |  | ||||||
| 		"a": ddeSVGAElement; |  | ||||||
| 		"animate": ddeSVGAnimateElement; |  | ||||||
| 		"animateMotion": ddeSVGAnimateMotionElement; |  | ||||||
| 		"animateTransform": ddeSVGAnimateTransformElement; |  | ||||||
| 		"circle": ddeSVGCircleElement; |  | ||||||
| 		"clipPath": ddeSVGClipPathElement; |  | ||||||
| 		"defs": ddeSVGDefsElement; |  | ||||||
| 		"desc": ddeSVGDescElement; |  | ||||||
| 		"ellipse": ddeSVGEllipseElement; |  | ||||||
| 		"feBlend": ddeSVGFEBlendElement; |  | ||||||
| 		"feColorMatrix": ddeSVGFEColorMatrixElement; |  | ||||||
| 		"feComponentTransfer": ddeSVGFEComponentTransferElement; |  | ||||||
| 		"feComposite": ddeSVGFECompositeElement; |  | ||||||
| 		"feConvolveMatrix": ddeSVGFEConvolveMatrixElement; |  | ||||||
| 		"feDiffuseLighting": ddeSVGFEDiffuseLightingElement; |  | ||||||
| 		"feDisplacementMap": ddeSVGFEDisplacementMapElement; |  | ||||||
| 		"feDistantLight": ddeSVGFEDistantLightElement; |  | ||||||
| 		"feDropShadow": ddeSVGFEDropShadowElement; |  | ||||||
| 		"feFlood": ddeSVGFEFloodElement; |  | ||||||
| 		"feFuncA": ddeSVGFEFuncAElement; |  | ||||||
| 		"feFuncB": ddeSVGFEFuncBElement; |  | ||||||
| 		"feFuncG": ddeSVGFEFuncGElement; |  | ||||||
| 		"feFuncR": ddeSVGFEFuncRElement; |  | ||||||
| 		"feGaussianBlur": ddeSVGFEGaussianBlurElement; |  | ||||||
| 		"feImage": ddeSVGFEImageElement; |  | ||||||
| 		"feMerge": ddeSVGFEMergeElement; |  | ||||||
| 		"feMergeNode": ddeSVGFEMergeNodeElement; |  | ||||||
| 		"feMorphology": ddeSVGFEMorphologyElement; |  | ||||||
| 		"feOffset": ddeSVGFEOffsetElement; |  | ||||||
| 		"fePointLight": ddeSVGFEPointLightElement; |  | ||||||
| 		"feSpecularLighting": ddeSVGFESpecularLightingElement; |  | ||||||
| 		"feSpotLight": ddeSVGFESpotLightElement; |  | ||||||
| 		"feTile": ddeSVGFETileElement; |  | ||||||
| 		"feTurbulence": ddeSVGFETurbulenceElement; |  | ||||||
| 		"filter": ddeSVGFilterElement; |  | ||||||
| 		"foreignObject": ddeSVGForeignObjectElement; |  | ||||||
| 		"g": ddeSVGGElement; |  | ||||||
| 		"image": ddeSVGImageElement; |  | ||||||
| 		"line": ddeSVGLineElement; |  | ||||||
| 		"linearGradient": ddeSVGLinearGradientElement; |  | ||||||
| 		"marker": ddeSVGMarkerElement; |  | ||||||
| 		"mask": ddeSVGMaskElement; |  | ||||||
| 		"metadata": ddeSVGMetadataElement; |  | ||||||
| 		"mpath": ddeSVGMPathElement; |  | ||||||
| 		"path": ddeSVGPathElement; |  | ||||||
| 		"pattern": ddeSVGPatternElement; |  | ||||||
| 		"polygon": ddeSVGPolygonElement; |  | ||||||
| 		"polyline": ddeSVGPolylineElement; |  | ||||||
| 		"radialGradient": ddeSVGRadialGradientElement; |  | ||||||
| 		"rect": ddeSVGRectElement; |  | ||||||
| 		"script": ddeSVGScriptElement; |  | ||||||
| 		"set": ddeSVGSetElement; |  | ||||||
| 		"stop": ddeSVGStopElement; |  | ||||||
| 		"style": ddeSVGStyleElement; |  | ||||||
| 		"svg": ddeSVGSVGElement; |  | ||||||
| 		"switch": ddeSVGSwitchElement; |  | ||||||
| 		"symbol": ddeSVGSymbolElement; |  | ||||||
| 		"text": ddeSVGTextElement; |  | ||||||
| 		"textPath": ddeSVGTextPathElement; |  | ||||||
| 		"title": ddeSVGTitleElement; |  | ||||||
| 		"tspan": ddeSVGTSpanElement; |  | ||||||
| 		"use": ddeSVGUseElement; |  | ||||||
| 		"view": ddeSVGViewElement; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // editorconfig-checker-disable |  | ||||||
| interface ddeHTMLAnchorElement extends HTMLAnchorElement{ append: ddeAppend<ddeHTMLAnchorElement>; } |  | ||||||
| interface ddeHTMLAreaElement extends HTMLAreaElement{ append: ddeAppend<ddeHTMLAreaElement>; } |  | ||||||
| interface ddeHTMLAudioElement extends HTMLAudioElement{ append: ddeAppend<ddeHTMLAudioElement>; } |  | ||||||
| interface ddeHTMLBaseElement extends HTMLBaseElement{ append: ddeAppend<ddeHTMLBaseElement>; } |  | ||||||
| interface ddeHTMLQuoteElement extends HTMLQuoteElement{ append: ddeAppend<ddeHTMLQuoteElement>; } |  | ||||||
| interface ddeHTMLBodyElement extends HTMLBodyElement{ append: ddeAppend<ddeHTMLBodyElement>; } |  | ||||||
| interface ddeHTMLBRElement extends HTMLBRElement{ append: ddeAppend<ddeHTMLBRElement>; } |  | ||||||
| interface ddeHTMLButtonElement extends HTMLButtonElement{ append: ddeAppend<ddeHTMLButtonElement>; } |  | ||||||
| interface ddeHTMLCanvasElement extends HTMLCanvasElement{ append: ddeAppend<ddeHTMLCanvasElement>; } |  | ||||||
| interface ddeHTMLTableCaptionElement extends HTMLTableCaptionElement{ append: ddeAppend<ddeHTMLTableCaptionElement>; } |  | ||||||
| interface ddeHTMLTableColElement extends HTMLTableColElement{ append: ddeAppend<ddeHTMLTableColElement>; } |  | ||||||
| interface ddeHTMLTableColElement extends HTMLTableColElement{ append: ddeAppend<ddeHTMLTableColElement>; } |  | ||||||
| interface ddeHTMLDataElement extends HTMLDataElement{ append: ddeAppend<ddeHTMLDataElement>; } |  | ||||||
| interface ddeHTMLDataListElement extends HTMLDataListElement{ append: ddeAppend<ddeHTMLDataListElement>; } |  | ||||||
| interface ddeHTMLModElement extends HTMLModElement{ append: ddeAppend<ddeHTMLModElement>; } |  | ||||||
| interface ddeHTMLDetailsElement extends HTMLDetailsElement{ append: ddeAppend<ddeHTMLDetailsElement>; } |  | ||||||
| interface ddeHTMLDialogElement extends HTMLDialogElement{ append: ddeAppend<ddeHTMLDialogElement>; } |  | ||||||
| interface ddeHTMLDivElement extends HTMLDivElement{ append: ddeAppend<ddeHTMLDivElement>; } |  | ||||||
| interface ddeHTMLDListElement extends HTMLDListElement{ append: ddeAppend<ddeHTMLDListElement>; } |  | ||||||
| interface ddeHTMLEmbedElement extends HTMLEmbedElement{ append: ddeAppend<ddeHTMLEmbedElement>; } |  | ||||||
| interface ddeHTMLFieldSetElement extends HTMLFieldSetElement{ append: ddeAppend<ddeHTMLFieldSetElement>; } |  | ||||||
| interface ddeHTMLFormElement extends HTMLFormElement{ append: ddeAppend<ddeHTMLFormElement>; } |  | ||||||
| interface ddeHTMLHeadingElement extends HTMLHeadingElement{ append: ddeAppend<ddeHTMLHeadingElement>; } |  | ||||||
| interface ddeHTMLHeadElement extends HTMLHeadElement{ append: ddeAppend<ddeHTMLHeadElement>; } |  | ||||||
| interface ddeHTMLHRElement extends HTMLHRElement{ append: ddeAppend<ddeHTMLHRElement>; } |  | ||||||
| interface ddeHTMLHtmlElement extends HTMLHtmlElement{ append: ddeAppend<ddeHTMLHtmlElement>; } |  | ||||||
| interface ddeHTMLIFrameElement extends HTMLIFrameElement{ append: ddeAppend<ddeHTMLIFrameElement>; } |  | ||||||
| interface ddeHTMLImageElement extends HTMLImageElement{ append: ddeAppend<ddeHTMLImageElement>; } |  | ||||||
| interface ddeHTMLInputElement extends HTMLInputElement{ append: ddeAppend<ddeHTMLInputElement>; } |  | ||||||
| interface ddeHTMLLabelElement extends HTMLLabelElement{ append: ddeAppend<ddeHTMLLabelElement>; } |  | ||||||
| interface ddeHTMLLegendElement extends HTMLLegendElement{ append: ddeAppend<ddeHTMLLegendElement>; } |  | ||||||
| interface ddeHTMLLIElement extends HTMLLIElement{ append: ddeAppend<ddeHTMLLIElement>; } |  | ||||||
| interface ddeHTMLLinkElement extends HTMLLinkElement{ append: ddeAppend<ddeHTMLLinkElement>; } |  | ||||||
| interface ddeHTMLMapElement extends HTMLMapElement{ append: ddeAppend<ddeHTMLMapElement>; } |  | ||||||
| interface ddeHTMLMenuElement extends HTMLMenuElement{ append: ddeAppend<ddeHTMLMenuElement>; } |  | ||||||
| interface ddeHTMLMetaElement extends HTMLMetaElement{ append: ddeAppend<ddeHTMLMetaElement>; } |  | ||||||
| interface ddeHTMLMeterElement extends HTMLMeterElement{ append: ddeAppend<ddeHTMLMeterElement>; } |  | ||||||
| interface ddeHTMLObjectElement extends HTMLObjectElement{ append: ddeAppend<ddeHTMLObjectElement>; } |  | ||||||
| interface ddeHTMLOListElement extends HTMLOListElement{ append: ddeAppend<ddeHTMLOListElement>; } |  | ||||||
| interface ddeHTMLOptGroupElement extends HTMLOptGroupElement{ append: ddeAppend<ddeHTMLOptGroupElement>; } |  | ||||||
| interface ddeHTMLOptionElement extends HTMLOptionElement{ append: ddeAppend<ddeHTMLOptionElement>; } |  | ||||||
| interface ddeHTMLOutputElement extends HTMLOutputElement{ append: ddeAppend<ddeHTMLOutputElement>; } |  | ||||||
| interface ddeHTMLParagraphElement extends HTMLParagraphElement{ append: ddeAppend<ddeHTMLParagraphElement>; } |  | ||||||
| interface ddeHTMLPictureElement extends HTMLPictureElement{ append: ddeAppend<ddeHTMLPictureElement>; } |  | ||||||
| interface ddeHTMLPreElement extends HTMLPreElement{ append: ddeAppend<ddeHTMLPreElement>; } |  | ||||||
| interface ddeHTMLProgressElement extends HTMLProgressElement{ append: ddeAppend<ddeHTMLProgressElement>; } |  | ||||||
| interface ddeHTMLScriptElement extends HTMLScriptElement{ append: ddeAppend<ddeHTMLScriptElement>; } |  | ||||||
| interface ddeHTMLSelectElement extends HTMLSelectElement{ append: ddeAppend<ddeHTMLSelectElement>; } |  | ||||||
| interface ddeHTMLSlotElement extends HTMLSlotElement{ append: ddeAppend<ddeHTMLSlotElement>; } |  | ||||||
| interface ddeHTMLSourceElement extends HTMLSourceElement{ append: ddeAppend<ddeHTMLSourceElement>; } |  | ||||||
| interface ddeHTMLSpanElement extends HTMLSpanElement{ append: ddeAppend<ddeHTMLSpanElement>; } |  | ||||||
| interface ddeHTMLStyleElement extends HTMLStyleElement{ append: ddeAppend<ddeHTMLStyleElement>; } |  | ||||||
| interface ddeHTMLTableElement extends HTMLTableElement{ append: ddeAppend<ddeHTMLTableElement>; } |  | ||||||
| interface ddeHTMLTableSectionElement extends HTMLTableSectionElement{ append: ddeAppend<ddeHTMLTableSectionElement>; } |  | ||||||
| interface ddeHTMLTableCellElement extends HTMLTableCellElement{ append: ddeAppend<ddeHTMLTableCellElement>; } |  | ||||||
| interface ddeHTMLTemplateElement extends HTMLTemplateElement{ append: ddeAppend<ddeHTMLTemplateElement>; } |  | ||||||
| interface ddeHTMLTextAreaElement extends HTMLTextAreaElement{ append: ddeAppend<ddeHTMLTextAreaElement>; } |  | ||||||
| interface ddeHTMLTableCellElement extends HTMLTableCellElement{ append: ddeAppend<ddeHTMLTableCellElement>; } |  | ||||||
| interface ddeHTMLTimeElement extends HTMLTimeElement{ append: ddeAppend<ddeHTMLTimeElement>; } |  | ||||||
| interface ddeHTMLTitleElement extends HTMLTitleElement{ append: ddeAppend<ddeHTMLTitleElement>; } |  | ||||||
| interface ddeHTMLTableRowElement extends HTMLTableRowElement{ append: ddeAppend<ddeHTMLTableRowElement>; } |  | ||||||
| interface ddeHTMLTrackElement extends HTMLTrackElement{ append: ddeAppend<ddeHTMLTrackElement>; } |  | ||||||
| interface ddeHTMLUListElement extends HTMLUListElement{ append: ddeAppend<ddeHTMLUListElement>; } |  | ||||||
| interface ddeHTMLVideoElement extends HTMLVideoElement{ append: ddeAppend<ddeHTMLVideoElement>; } |  | ||||||
| interface ddeSVGAElement extends SVGAElement{ append: ddeAppend<ddeSVGAElement>; } |  | ||||||
| interface ddeSVGAnimateElement extends SVGAnimateElement{ append: ddeAppend<ddeSVGAnimateElement>; } |  | ||||||
| interface ddeSVGAnimateMotionElement extends SVGAnimateMotionElement{ append: ddeAppend<ddeSVGAnimateMotionElement>; } |  | ||||||
| interface ddeSVGAnimateTransformElement extends SVGAnimateTransformElement{ append: ddeAppend<ddeSVGAnimateTransformElement>; } |  | ||||||
| interface ddeSVGCircleElement extends SVGCircleElement{ append: ddeAppend<ddeSVGCircleElement>; } |  | ||||||
| interface ddeSVGClipPathElement extends SVGClipPathElement{ append: ddeAppend<ddeSVGClipPathElement>; } |  | ||||||
| interface ddeSVGDefsElement extends SVGDefsElement{ append: ddeAppend<ddeSVGDefsElement>; } |  | ||||||
| interface ddeSVGDescElement extends SVGDescElement{ append: ddeAppend<ddeSVGDescElement>; } |  | ||||||
| interface ddeSVGEllipseElement extends SVGEllipseElement{ append: ddeAppend<ddeSVGEllipseElement>; } |  | ||||||
| interface ddeSVGFEBlendElement extends SVGFEBlendElement{ append: ddeAppend<ddeSVGFEBlendElement>; } |  | ||||||
| interface ddeSVGFEColorMatrixElement extends SVGFEColorMatrixElement{ append: ddeAppend<ddeSVGFEColorMatrixElement>; } |  | ||||||
| interface ddeSVGFEComponentTransferElement extends SVGFEComponentTransferElement{ append: ddeAppend<ddeSVGFEComponentTransferElement>; } |  | ||||||
| interface ddeSVGFECompositeElement extends SVGFECompositeElement{ append: ddeAppend<ddeSVGFECompositeElement>; } |  | ||||||
| interface ddeSVGFEConvolveMatrixElement extends SVGFEConvolveMatrixElement{ append: ddeAppend<ddeSVGFEConvolveMatrixElement>; } |  | ||||||
| interface ddeSVGFEDiffuseLightingElement extends SVGFEDiffuseLightingElement{ append: ddeAppend<ddeSVGFEDiffuseLightingElement>; } |  | ||||||
| interface ddeSVGFEDisplacementMapElement extends SVGFEDisplacementMapElement{ append: ddeAppend<ddeSVGFEDisplacementMapElement>; } |  | ||||||
| interface ddeSVGFEDistantLightElement extends SVGFEDistantLightElement{ append: ddeAppend<ddeSVGFEDistantLightElement>; } |  | ||||||
| interface ddeSVGFEDropShadowElement extends SVGFEDropShadowElement{ append: ddeAppend<ddeSVGFEDropShadowElement>; } |  | ||||||
| interface ddeSVGFEFloodElement extends SVGFEFloodElement{ append: ddeAppend<ddeSVGFEFloodElement>; } |  | ||||||
| interface ddeSVGFEFuncAElement extends SVGFEFuncAElement{ append: ddeAppend<ddeSVGFEFuncAElement>; } |  | ||||||
| interface ddeSVGFEFuncBElement extends SVGFEFuncBElement{ append: ddeAppend<ddeSVGFEFuncBElement>; } |  | ||||||
| interface ddeSVGFEFuncGElement extends SVGFEFuncGElement{ append: ddeAppend<ddeSVGFEFuncGElement>; } |  | ||||||
| interface ddeSVGFEFuncRElement extends SVGFEFuncRElement{ append: ddeAppend<ddeSVGFEFuncRElement>; } |  | ||||||
| interface ddeSVGFEGaussianBlurElement extends SVGFEGaussianBlurElement{ append: ddeAppend<ddeSVGFEGaussianBlurElement>; } |  | ||||||
| interface ddeSVGFEImageElement extends SVGFEImageElement{ append: ddeAppend<ddeSVGFEImageElement>; } |  | ||||||
| interface ddeSVGFEMergeElement extends SVGFEMergeElement{ append: ddeAppend<ddeSVGFEMergeElement>; } |  | ||||||
| interface ddeSVGFEMergeNodeElement extends SVGFEMergeNodeElement{ append: ddeAppend<ddeSVGFEMergeNodeElement>; } |  | ||||||
| interface ddeSVGFEMorphologyElement extends SVGFEMorphologyElement{ append: ddeAppend<ddeSVGFEMorphologyElement>; } |  | ||||||
| interface ddeSVGFEOffsetElement extends SVGFEOffsetElement{ append: ddeAppend<ddeSVGFEOffsetElement>; } |  | ||||||
| interface ddeSVGFEPointLightElement extends SVGFEPointLightElement{ append: ddeAppend<ddeSVGFEPointLightElement>; } |  | ||||||
| interface ddeSVGFESpecularLightingElement extends SVGFESpecularLightingElement{ append: ddeAppend<ddeSVGFESpecularLightingElement>; } |  | ||||||
| interface ddeSVGFESpotLightElement extends SVGFESpotLightElement{ append: ddeAppend<ddeSVGFESpotLightElement>; } |  | ||||||
| interface ddeSVGFETileElement extends SVGFETileElement{ append: ddeAppend<ddeSVGFETileElement>; } |  | ||||||
| interface ddeSVGFETurbulenceElement extends SVGFETurbulenceElement{ append: ddeAppend<ddeSVGFETurbulenceElement>; } |  | ||||||
| interface ddeSVGFilterElement extends SVGFilterElement{ append: ddeAppend<ddeSVGFilterElement>; } |  | ||||||
| interface ddeSVGForeignObjectElement extends SVGForeignObjectElement{ append: ddeAppend<ddeSVGForeignObjectElement>; } |  | ||||||
| interface ddeSVGGElement extends SVGGElement{ append: ddeAppend<ddeSVGGElement>; } |  | ||||||
| interface ddeSVGImageElement extends SVGImageElement{ append: ddeAppend<ddeSVGImageElement>; } |  | ||||||
| interface ddeSVGLineElement extends SVGLineElement{ append: ddeAppend<ddeSVGLineElement>; } |  | ||||||
| interface ddeSVGLinearGradientElement extends SVGLinearGradientElement{ append: ddeAppend<ddeSVGLinearGradientElement>; } |  | ||||||
| interface ddeSVGMarkerElement extends SVGMarkerElement{ append: ddeAppend<ddeSVGMarkerElement>; } |  | ||||||
| interface ddeSVGMaskElement extends SVGMaskElement{ append: ddeAppend<ddeSVGMaskElement>; } |  | ||||||
| interface ddeSVGMetadataElement extends SVGMetadataElement{ append: ddeAppend<ddeSVGMetadataElement>; } |  | ||||||
| interface ddeSVGMPathElement extends SVGMPathElement{ append: ddeAppend<ddeSVGMPathElement>; } |  | ||||||
| interface ddeSVGPathElement extends SVGPathElement{ append: ddeAppend<ddeSVGPathElement>; } |  | ||||||
| interface ddeSVGPatternElement extends SVGPatternElement{ append: ddeAppend<ddeSVGPatternElement>; } |  | ||||||
| interface ddeSVGPolygonElement extends SVGPolygonElement{ append: ddeAppend<ddeSVGPolygonElement>; } |  | ||||||
| interface ddeSVGPolylineElement extends SVGPolylineElement{ append: ddeAppend<ddeSVGPolylineElement>; } |  | ||||||
| interface ddeSVGRadialGradientElement extends SVGRadialGradientElement{ append: ddeAppend<ddeSVGRadialGradientElement>; } |  | ||||||
| interface ddeSVGRectElement extends SVGRectElement{ append: ddeAppend<ddeSVGRectElement>; } |  | ||||||
| interface ddeSVGScriptElement extends SVGScriptElement{ append: ddeAppend<ddeSVGScriptElement>; } |  | ||||||
| interface ddeSVGSetElement extends SVGSetElement{ append: ddeAppend<ddeSVGSetElement>; } |  | ||||||
| interface ddeSVGStopElement extends SVGStopElement{ append: ddeAppend<ddeSVGStopElement>; } |  | ||||||
| interface ddeSVGStyleElement extends SVGStyleElement{ append: ddeAppend<ddeSVGStyleElement>; } |  | ||||||
| interface ddeSVGSVGElement extends SVGSVGElement{ append: ddeAppend<ddeSVGSVGElement>; } |  | ||||||
| interface ddeSVGSwitchElement extends SVGSwitchElement{ append: ddeAppend<ddeSVGSwitchElement>; } |  | ||||||
| interface ddeSVGSymbolElement extends SVGSymbolElement{ append: ddeAppend<ddeSVGSymbolElement>; } |  | ||||||
| interface ddeSVGTextElement extends SVGTextElement{ append: ddeAppend<ddeSVGTextElement>; } |  | ||||||
| interface ddeSVGTextPathElement extends SVGTextPathElement{ append: ddeAppend<ddeSVGTextPathElement>; } |  | ||||||
| interface ddeSVGTitleElement extends SVGTitleElement{ append: ddeAppend<ddeSVGTitleElement>; } |  | ||||||
| interface ddeSVGTSpanElement extends SVGTSpanElement{ append: ddeAppend<ddeSVGTSpanElement>; } |  | ||||||
| interface ddeSVGUseElement extends SVGUseElement{ append: ddeAppend<ddeSVGUseElement>; } |  | ||||||
| interface ddeSVGViewElement extends SVGViewElement{ append: ddeAppend<ddeSVGViewElement>; } |  | ||||||
| // editorconfig-checker-enable |  | ||||||
							
								
								
									
										31
									
								
								dist/esm.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								dist/esm.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -4,7 +4,7 @@ export interface Signal<V, A> { | |||||||
| 	/** The current value of the signal */ | 	/** The current value of the signal */ | ||||||
| 	get(): V; | 	get(): V; | ||||||
| 	/** Set new value of the signal */ | 	/** Set new value of the signal */ | ||||||
| 	set(value: V): V; | 	set(value: V, force?: boolean): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
| @@ -172,17 +172,30 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options? | |||||||
| declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void; | declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void; | ||||||
| export interface On { | export interface On { | ||||||
| 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | ||||||
| 	<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE; | 	<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & { | ||||||
|  | 		target: EL; | ||||||
|  | 	}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE; | 	<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE; | ||||||
| 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | ||||||
| 	connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE; | 	connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | ||||||
| 	disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE; | 	disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** | ||||||
| 	attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[ | 	 * Fires after the next tick of the Javascript event loop. | ||||||
| 		string, | 	 * This is handy for example to apply some property depending on the element content: | ||||||
| 		string | 	 * ```js | ||||||
| 	]>) => any, options?: AddEventListenerOptions): EE; | 	 * const selected= "Z"; | ||||||
|  | 	 * //... | ||||||
|  | 	 * return el("form").append( | ||||||
|  | 	 *		el("select", null, on.defer(e=> e.value=selected)).append( | ||||||
|  | 	 *			el("option", { value: "A", textContent: "A" }), | ||||||
|  | 	 *			//... | ||||||
|  | 	 *			el("option", { value: "Z", textContent: "Z" }), | ||||||
|  | 	 *		), | ||||||
|  | 	 * ); | ||||||
|  | 	 * ``` | ||||||
|  | 	 * */ | ||||||
|  | 	defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>; | ||||||
| } | } | ||||||
| export const on: On; | export const on: On; | ||||||
| export type Scope = { | export type Scope = { | ||||||
|   | |||||||
							
								
								
									
										210
									
								
								dist/esm.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										210
									
								
								dist/esm.js
									
									
									
									
										vendored
									
									
								
							| @@ -25,39 +25,13 @@ function onAbort(signal, listener) { | |||||||
| 		signal.removeEventListener("abort", listener); | 		signal.removeEventListener("abort", listener); | ||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
|  | function requestIdle() { | ||||||
| // src/signals-lib/common.js | 	return new Promise(function(resolve) { | ||||||
| var signals_global = { | 		(globalThis.requestIdleCallback || requestAnimationFrame)(resolve); | ||||||
| 	/** | 	}); | ||||||
| 	* Checks if a value is a signal |  | ||||||
| 	* @param {any} attributes - Value to check |  | ||||||
| 	* @returns {boolean} Whether the value is a signal |  | ||||||
| 	*/ |  | ||||||
| 	isSignal(attributes) { |  | ||||||
| 		return false; |  | ||||||
| 	}, |  | ||||||
| 	/** |  | ||||||
| 	* Processes an attribute that might be reactive |  | ||||||
| 	* @param {Element} obj - Element that owns the attribute |  | ||||||
| 	* @param {string} key - Attribute name |  | ||||||
| 	* @param {any} attr - Attribute value |  | ||||||
| 	* @param {Function} set - Function to set the attribute |  | ||||||
| 	* @returns {any} Processed attribute value |  | ||||||
| 	*/ |  | ||||||
| 	processReactiveAttribute(obj, key, attr, set) { |  | ||||||
| 		return attr; |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
| function registerReactivity(def, global = true) { |  | ||||||
| 	if (global) return oAssign(signals_global, def); |  | ||||||
| 	Object.setPrototypeOf(def, signals_global); |  | ||||||
| 	return def; |  | ||||||
| } |  | ||||||
| function signals(_this) { |  | ||||||
| 	return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // src/dom-common.js | // src/dom-lib/common.js | ||||||
| var enviroment = { | var enviroment = { | ||||||
| 	setDeleteAttr, | 	setDeleteAttr, | ||||||
| 	ssr: "", | 	ssr: "", | ||||||
| @@ -83,7 +57,7 @@ var evc = "dde:connected"; | |||||||
| var evd = "dde:disconnected"; | var evd = "dde:disconnected"; | ||||||
| var eva = "dde:attributeChanged"; | var eva = "dde:attributeChanged"; | ||||||
|  |  | ||||||
| // src/events-observer.js | // src/dom-lib/events-observer.js | ||||||
| var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, { | var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, { | ||||||
| 	get() { | 	get() { | ||||||
| 		return () => { | 		return () => { | ||||||
| @@ -194,11 +168,6 @@ 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(); | ||||||
| @@ -247,7 +216,7 @@ function connectionsChangesObserverConstructor() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // src/events.js | // src/dom-lib/events.js | ||||||
| function dispatchEvent(name, options, host) { | function dispatchEvent(name, options, host) { | ||||||
| 	if (typeof options === "function") { | 	if (typeof options === "function") { | ||||||
| 		host = options; | 		host = options; | ||||||
| @@ -269,6 +238,7 @@ function on(event, listener, options) { | |||||||
| 		return element; | 		return element; | ||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
|  | on.defer = (fn) => setTimeout.bind(null, fn, 0); | ||||||
| var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true }); | var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true }); | ||||||
| on.connected = function(listener, options) { | on.connected = function(listener, options) { | ||||||
| 	options = lifeOptions(options); | 	options = lifeOptions(options); | ||||||
| @@ -292,10 +262,7 @@ on.disconnected = function(listener, options) { | |||||||
| 	}; | 	}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // src/dom.js | // src/dom-lib/scopes.js | ||||||
| function queue(promise) { |  | ||||||
| 	return enviroment.q(promise); |  | ||||||
| } |  | ||||||
| var scopes = [{ | var scopes = [{ | ||||||
| 	get scope() { | 	get scope() { | ||||||
| 		return enviroment.D.body; | 		return enviroment.D.body; | ||||||
| @@ -307,7 +274,7 @@ var store_abort = /* @__PURE__ */ new WeakMap(); | |||||||
| var scope = { | var scope = { | ||||||
| 	/** | 	/** | ||||||
| 	* Gets the current scope | 	* Gets the current scope | ||||||
| 	* @returns {Object} Current scope context | 	* @returns {typeof scopes[number]} Current scope context | ||||||
| 	*/ | 	*/ | ||||||
| 	get current() { | 	get current() { | ||||||
| 		return scopes[scopes.length - 1]; | 		return scopes[scopes.length - 1]; | ||||||
| @@ -370,6 +337,60 @@ var scope = { | |||||||
| 		return scopes.pop(); | 		return scopes.pop(); | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | // src/signals-lib/common.js | ||||||
|  | var signals_global = { | ||||||
|  | 	/** | ||||||
|  | 	* Checks if a value is a signal | ||||||
|  | 	* @param {any} attributes - Value to check | ||||||
|  | 	* @returns {boolean} Whether the value is a signal | ||||||
|  | 	*/ | ||||||
|  | 	isSignal(attributes) { | ||||||
|  | 		return false; | ||||||
|  | 	}, | ||||||
|  | 	/** | ||||||
|  | 	* Processes an attribute that might be reactive | ||||||
|  | 	* @param {Element} obj - Element that owns the attribute | ||||||
|  | 	* @param {string} key - Attribute name | ||||||
|  | 	* @param {any} attr - Attribute value | ||||||
|  | 	* @param {Function} set - Function to set the attribute | ||||||
|  | 	* @returns {any} Processed attribute value | ||||||
|  | 	*/ | ||||||
|  | 	processReactiveAttribute(obj, key, attr, set) { | ||||||
|  | 		return attr; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | function registerReactivity(def, global = true) { | ||||||
|  | 	if (global) return oAssign(signals_global, def); | ||||||
|  | 	Object.setPrototypeOf(def, signals_global); | ||||||
|  | 	return def; | ||||||
|  | } | ||||||
|  | function signals(_this) { | ||||||
|  | 	return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // src/dom-lib/helpers.js | ||||||
|  | function setRemove(obj, prop, key, val) { | ||||||
|  | 	return obj[(isUndef(val) ? "remove" : "set") + prop](key, val); | ||||||
|  | } | ||||||
|  | function setRemoveNS(obj, prop, key, val, ns = null) { | ||||||
|  | 	return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val); | ||||||
|  | } | ||||||
|  | function setDelete(obj, key, val) { | ||||||
|  | 	Reflect.set(obj, key, val); | ||||||
|  | 	if (!isUndef(val)) return; | ||||||
|  | 	return Reflect.deleteProperty(obj, key); | ||||||
|  | } | ||||||
|  | function elementAttribute(element, op, key, value) { | ||||||
|  | 	if (isInstance(element, enviroment.H)) | ||||||
|  | 		return element[op + "Attribute"](key, value); | ||||||
|  | 	return element[op + "AttributeNS"](null, key, value); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // src/dom-lib/el.js | ||||||
|  | function queue(promise) { | ||||||
|  | 	return enviroment.q(promise); | ||||||
|  | } | ||||||
| function append(...els) { | function append(...els) { | ||||||
| 	this.appendOriginal(...els); | 	this.appendOriginal(...els); | ||||||
| 	return this; | 	return this; | ||||||
| @@ -385,16 +406,18 @@ function createElement(tag, attributes, ...addons) { | |||||||
| 	const s = signals(this); | 	const s = signals(this); | ||||||
| 	let scoped = 0; | 	let scoped = 0; | ||||||
| 	let el, el_host; | 	let el, el_host; | ||||||
| 	if (Object(attributes) !== attributes || s.isSignal(attributes)) | 	const att_type = typeof attributes; | ||||||
|  | 	if (att_type === "string" || att_type === "number" || s.isSignal(attributes)) | ||||||
| 		attributes = { textContent: attributes }; | 		attributes = { textContent: attributes }; | ||||||
| 	switch (true) { | 	switch (true) { | ||||||
| 		case typeof tag === "function": { | 		case typeof tag === "function": { | ||||||
| 			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 = tag(attributes || void 0); | 			el = /** @type {Element} */ | ||||||
| 			const is_fragment = isInstance(el, enviroment.F); | 			tag(attributes || void 0); | ||||||
| 			if (el.nodeName === "#comment") break; | 			if (el.nodeName === "#comment") break; | ||||||
|  | 			const is_fragment = isInstance(el, enviroment.F); | ||||||
| 			const el_mark = createElement.mark({ | 			const el_mark = createElement.mark({ | ||||||
| 				type: "component", | 				type: "component", | ||||||
| 				name: tag.name, | 				name: tag.name, | ||||||
| @@ -439,38 +462,6 @@ function createElementNS(ns) { | |||||||
| 		return el; | 		return el; | ||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
| function simulateSlots(element, root = element) { |  | ||||||
| 	const mark_e = "\xB9\u2070", mark_s = "\u2713"; |  | ||||||
| 	const slots = Object.fromEntries( |  | ||||||
| 		Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s]) |  | ||||||
| 	); |  | ||||||
| 	element.append = new Proxy(element.append, { |  | ||||||
| 		apply(orig, _, els) { |  | ||||||
| 			if (els[0] === root) return orig.apply(element, els); |  | ||||||
| 			for (const el of els) { |  | ||||||
| 				const name = (el.slot || "") + mark_e; |  | ||||||
| 				try { |  | ||||||
| 					elementAttribute(el, "remove", "slot"); |  | ||||||
| 				} catch (_error) { |  | ||||||
| 				} |  | ||||||
| 				const slot = slots[name]; |  | ||||||
| 				if (!slot) return; |  | ||||||
| 				if (!slot.name.startsWith(mark_s)) { |  | ||||||
| 					slot.childNodes.forEach((c) => c.remove()); |  | ||||||
| 					slot.name = mark_s + name; |  | ||||||
| 				} |  | ||||||
| 				slot.append(el); |  | ||||||
| 			} |  | ||||||
| 			element.append = orig; |  | ||||||
| 			return element; |  | ||||||
| 		} |  | ||||||
| 	}); |  | ||||||
| 	if (element !== root) { |  | ||||||
| 		const els = Array.from(element.childNodes); |  | ||||||
| 		element.append(...els); |  | ||||||
| 	} |  | ||||||
| 	return root; |  | ||||||
| } |  | ||||||
| var assign_context = /* @__PURE__ */ new WeakMap(); | var assign_context = /* @__PURE__ */ new WeakMap(); | ||||||
| var { setDeleteAttr: setDeleteAttr2 } = enviroment; | var { setDeleteAttr: setDeleteAttr2 } = enviroment; | ||||||
| function assign(element, ...attributes) { | function assign(element, ...attributes) { | ||||||
| @@ -533,11 +524,6 @@ function classListDeclarative(element, toggle) { | |||||||
| 	); | 	); | ||||||
| 	return element; | 	return element; | ||||||
| } | } | ||||||
| function elementAttribute(element, op, key, value) { |  | ||||||
| 	if (isInstance(element, enviroment.H)) |  | ||||||
| 		return element[op + "Attribute"](key, value); |  | ||||||
| 	return element[op + "AttributeNS"](null, key, value); |  | ||||||
| } |  | ||||||
| function isPropSetter(el, key) { | function isPropSetter(el, key) { | ||||||
| 	if (!(key in el)) return false; | 	if (!(key in el)) return false; | ||||||
| 	const des = getPropDescriptor(el, key); | 	const des = getPropDescriptor(el, key); | ||||||
| @@ -561,19 +547,40 @@ function forEachEntries(s, target, element, obj, cb) { | |||||||
| 		cb(key, val); | 		cb(key, val); | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| function setRemove(obj, prop, key, val) { |  | ||||||
| 	return obj[(isUndef(val) ? "remove" : "set") + prop](key, val); |  | ||||||
| } |  | ||||||
| function setRemoveNS(obj, prop, key, val, ns = null) { |  | ||||||
| 	return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val); |  | ||||||
| } |  | ||||||
| function setDelete(obj, key, val) { |  | ||||||
| 	Reflect.set(obj, key, val); |  | ||||||
| 	if (!isUndef(val)) return; |  | ||||||
| 	return Reflect.deleteProperty(obj, key); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // src/customElement.js | // src/dom-lib/customElement.js | ||||||
|  | function simulateSlots(element, root = element) { | ||||||
|  | 	const mark_e = "\xB9\u2070", mark_s = "\u2713"; | ||||||
|  | 	const slots = Object.fromEntries( | ||||||
|  | 		Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s]) | ||||||
|  | 	); | ||||||
|  | 	element.append = new Proxy(element.append, { | ||||||
|  | 		apply(orig, _, els) { | ||||||
|  | 			if (els[0] === root) return orig.apply(element, els); | ||||||
|  | 			for (const el of els) { | ||||||
|  | 				const name = (el.slot || "") + mark_e; | ||||||
|  | 				try { | ||||||
|  | 					elementAttribute(el, "remove", "slot"); | ||||||
|  | 				} catch (_error) { | ||||||
|  | 				} | ||||||
|  | 				const slot = slots[name]; | ||||||
|  | 				if (!slot) return; | ||||||
|  | 				if (!slot.name.startsWith(mark_s)) { | ||||||
|  | 					slot.childNodes.forEach((c) => c.remove()); | ||||||
|  | 					slot.name = mark_s + name; | ||||||
|  | 				} | ||||||
|  | 				slot.append(el); | ||||||
|  | 			} | ||||||
|  | 			element.append = orig; | ||||||
|  | 			return element; | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 	if (element !== root) { | ||||||
|  | 		const els = Array.from(element.childNodes); | ||||||
|  | 		element.append(...els); | ||||||
|  | 	} | ||||||
|  | 	return root; | ||||||
|  | } | ||||||
| function customElementRender(target, render, props = {}) { | function customElementRender(target, render, props = {}) { | ||||||
| 	const custom_element = target.host || target; | 	const custom_element = target.host || target; | ||||||
| 	scope.push({ | 	scope.push({ | ||||||
| @@ -628,9 +635,9 @@ function memo(key, generator) { | |||||||
| memo.isScope = function(obj) { | memo.isScope = function(obj) { | ||||||
| 	return obj[memoMark]; | 	return obj[memoMark]; | ||||||
| }; | }; | ||||||
| memo.scope = function memoScope(fun, { signal, onlyLast } = {}) { | memo.scope = function memoScopeCreate(fun, { signal, onlyLast } = {}) { | ||||||
| 	let cache = oCreate(); | 	let cache = oCreate(); | ||||||
| 	function memoScope2(...args) { | 	function memoScope(...args) { | ||||||
| 		if (signal && signal.aborted) | 		if (signal && signal.aborted) | ||||||
| 			return fun.apply(this, args); | 			return fun.apply(this, args); | ||||||
| 		let cache_local = onlyLast ? cache : oCreate(); | 		let cache_local = onlyLast ? cache : oCreate(); | ||||||
| @@ -645,10 +652,10 @@ memo.scope = function memoScope(fun, { signal, onlyLast } = {}) { | |||||||
| 		cache = cache_local; | 		cache = cache_local; | ||||||
| 		return out; | 		return out; | ||||||
| 	} | 	} | ||||||
| 	memoScope2[memoMark] = true; | 	memoScope[memoMark] = true; | ||||||
| 	memoScope2.clear = () => cache = oCreate(); | 	memoScope.clear = () => cache = oCreate(); | ||||||
| 	if (signal) signal.addEventListener("abort", memoScope2.clear); | 	if (signal) signal.addEventListener("abort", memoScope.clear); | ||||||
| 	return memoScope2; | 	return memoScope; | ||||||
| }; | }; | ||||||
| export { | export { | ||||||
| 	assign, | 	assign, | ||||||
| @@ -662,7 +669,6 @@ export { | |||||||
| 	dispatchEvent, | 	dispatchEvent, | ||||||
| 	createElement as el, | 	createElement as el, | ||||||
| 	createElementNS as elNS, | 	createElementNS as elNS, | ||||||
| 	elementAttribute, |  | ||||||
| 	lifecyclesToEvents, | 	lifecyclesToEvents, | ||||||
| 	memo, | 	memo, | ||||||
| 	on, | 	on, | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								dist/esm.min.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								dist/esm.min.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -4,7 +4,7 @@ export interface Signal<V, A> { | |||||||
| 	/** The current value of the signal */ | 	/** The current value of the signal */ | ||||||
| 	get(): V; | 	get(): V; | ||||||
| 	/** Set new value of the signal */ | 	/** Set new value of the signal */ | ||||||
| 	set(value: V): V; | 	set(value: V, force?: boolean): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
| @@ -172,17 +172,30 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options? | |||||||
| declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void; | declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void; | ||||||
| export interface On { | export interface On { | ||||||
| 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | ||||||
| 	<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE; | 	<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & { | ||||||
|  | 		target: EL; | ||||||
|  | 	}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE; | 	<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE; | ||||||
| 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | ||||||
| 	connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE; | 	connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | ||||||
| 	disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE; | 	disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** | ||||||
| 	attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[ | 	 * Fires after the next tick of the Javascript event loop. | ||||||
| 		string, | 	 * This is handy for example to apply some property depending on the element content: | ||||||
| 		string | 	 * ```js | ||||||
| 	]>) => any, options?: AddEventListenerOptions): EE; | 	 * const selected= "Z"; | ||||||
|  | 	 * //... | ||||||
|  | 	 * return el("form").append( | ||||||
|  | 	 *		el("select", null, on.defer(e=> e.value=selected)).append( | ||||||
|  | 	 *			el("option", { value: "A", textContent: "A" }), | ||||||
|  | 	 *			//... | ||||||
|  | 	 *			el("option", { value: "Z", textContent: "Z" }), | ||||||
|  | 	 *		), | ||||||
|  | 	 * ); | ||||||
|  | 	 * ``` | ||||||
|  | 	 * */ | ||||||
|  | 	defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>; | ||||||
| } | } | ||||||
| export const on: On; | export const on: On; | ||||||
| export type Scope = { | export type Scope = { | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								dist/esm.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/esm.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										31
									
								
								dist/iife-with-signals.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								dist/iife-with-signals.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -4,7 +4,7 @@ export interface Signal<V, A> { | |||||||
| 	/** The current value of the signal */ | 	/** The current value of the signal */ | ||||||
| 	get(): V; | 	get(): V; | ||||||
| 	/** Set new value of the signal */ | 	/** Set new value of the signal */ | ||||||
| 	set(value: V): V; | 	set(value: V, force?: boolean): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
| @@ -173,17 +173,30 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options? | |||||||
| declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void; | declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void; | ||||||
| export interface On { | export interface On { | ||||||
| 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | ||||||
| 	<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE; | 	<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & { | ||||||
|  | 		target: EL; | ||||||
|  | 	}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE; | 	<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE; | ||||||
| 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | ||||||
| 	connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE; | 	connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | ||||||
| 	disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE; | 	disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** | ||||||
| 	attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[ | 	 * Fires after the next tick of the Javascript event loop. | ||||||
| 		string, | 	 * This is handy for example to apply some property depending on the element content: | ||||||
| 		string | 	 * ```js | ||||||
| 	]>) => any, options?: AddEventListenerOptions): EE; | 	 * const selected= "Z"; | ||||||
|  | 	 * //... | ||||||
|  | 	 * return el("form").append( | ||||||
|  | 	 *		el("select", null, on.defer(e=> e.value=selected)).append( | ||||||
|  | 	 *			el("option", { value: "A", textContent: "A" }), | ||||||
|  | 	 *			//... | ||||||
|  | 	 *			el("option", { value: "Z", textContent: "Z" }), | ||||||
|  | 	 *		), | ||||||
|  | 	 * ); | ||||||
|  | 	 * ``` | ||||||
|  | 	 * */ | ||||||
|  | 	defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>; | ||||||
| } | } | ||||||
| export const on: On; | export const on: On; | ||||||
| export type Scope = { | export type Scope = { | ||||||
|   | |||||||
							
								
								
									
										282
									
								
								dist/iife-with-signals.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										282
									
								
								dist/iife-with-signals.js
									
									
									
									
										vendored
									
									
								
							| @@ -32,7 +32,6 @@ var DDE = (() => { | |||||||
| 		dispatchEvent: () => dispatchEvent, | 		dispatchEvent: () => dispatchEvent, | ||||||
| 		el: () => createElement, | 		el: () => createElement, | ||||||
| 		elNS: () => createElementNS, | 		elNS: () => createElementNS, | ||||||
| 		elementAttribute: () => elementAttribute, |  | ||||||
| 		isSignal: () => isSignal, | 		isSignal: () => isSignal, | ||||||
| 		lifecyclesToEvents: () => lifecyclesToEvents, | 		lifecyclesToEvents: () => lifecyclesToEvents, | ||||||
| 		memo: () => memo, | 		memo: () => memo, | ||||||
| @@ -87,52 +86,13 @@ var DDE = (() => { | |||||||
| 	function kebabToCamel(name) { | 	function kebabToCamel(name) { | ||||||
| 		return name.replace(/-./g, (x) => x[1].toUpperCase()); | 		return name.replace(/-./g, (x) => x[1].toUpperCase()); | ||||||
| 	} | 	} | ||||||
| 	var Defined = class extends Error { | 	function requestIdle() { | ||||||
| 		constructor() { | 		return new Promise(function(resolve) { | ||||||
| 			super(); | 			(globalThis.requestIdleCallback || requestAnimationFrame)(resolve); | ||||||
| 			const [curr, ...rest] = this.stack.split("\n"); | 		}); | ||||||
| 			const curr_file = curr.slice(curr.indexOf("@"), curr.indexOf(".js:") + 4); |  | ||||||
| 			const curr_lib = curr_file.includes("src/helpers.js") ? "src/" : curr_file; |  | ||||||
| 			this.stack = rest.find((l) => !l.includes(curr_lib)) || curr; |  | ||||||
| 		} |  | ||||||
| 		get compact() { |  | ||||||
| 			const { stack } = this; |  | ||||||
| 			return stack.slice(0, stack.indexOf("@") + 1) + "\u2026" + stack.slice(stack.lastIndexOf("/")); |  | ||||||
| 		} |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	// src/signals-lib/common.js |  | ||||||
| 	var signals_global = { |  | ||||||
| 		/** |  | ||||||
| 		* Checks if a value is a signal |  | ||||||
| 		* @param {any} attributes - Value to check |  | ||||||
| 		* @returns {boolean} Whether the value is a signal |  | ||||||
| 		*/ |  | ||||||
| 		isSignal(attributes) { |  | ||||||
| 			return false; |  | ||||||
| 		}, |  | ||||||
| 		/** |  | ||||||
| 		* Processes an attribute that might be reactive |  | ||||||
| 		* @param {Element} obj - Element that owns the attribute |  | ||||||
| 		* @param {string} key - Attribute name |  | ||||||
| 		* @param {any} attr - Attribute value |  | ||||||
| 		* @param {Function} set - Function to set the attribute |  | ||||||
| 		* @returns {any} Processed attribute value |  | ||||||
| 		*/ |  | ||||||
| 		processReactiveAttribute(obj, key, attr, set) { |  | ||||||
| 			return attr; |  | ||||||
| 		} |  | ||||||
| 	}; |  | ||||||
| 	function registerReactivity(def, global = true) { |  | ||||||
| 		if (global) return oAssign(signals_global, def); |  | ||||||
| 		Object.setPrototypeOf(def, signals_global); |  | ||||||
| 		return def; |  | ||||||
| 	} |  | ||||||
| 	function signals(_this) { |  | ||||||
| 		return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global; |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// src/dom-common.js | 	// src/dom-lib/common.js | ||||||
| 	var enviroment = { | 	var enviroment = { | ||||||
| 		setDeleteAttr, | 		setDeleteAttr, | ||||||
| 		ssr: "", | 		ssr: "", | ||||||
| @@ -158,7 +118,7 @@ var DDE = (() => { | |||||||
| 	var evd = "dde:disconnected"; | 	var evd = "dde:disconnected"; | ||||||
| 	var eva = "dde:attributeChanged"; | 	var eva = "dde:attributeChanged"; | ||||||
|  |  | ||||||
| 	// src/events-observer.js | 	// src/dom-lib/events-observer.js | ||||||
| 	var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, { | 	var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, { | ||||||
| 		get() { | 		get() { | ||||||
| 			return () => { | 			return () => { | ||||||
| @@ -269,11 +229,6 @@ 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(); | ||||||
| @@ -322,7 +277,7 @@ var DDE = (() => { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// src/events.js | 	// src/dom-lib/events.js | ||||||
| 	function dispatchEvent(name, options, host) { | 	function dispatchEvent(name, options, host) { | ||||||
| 		if (typeof options === "function") { | 		if (typeof options === "function") { | ||||||
| 			host = options; | 			host = options; | ||||||
| @@ -344,6 +299,7 @@ var DDE = (() => { | |||||||
| 			return element; | 			return element; | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
|  | 	on.defer = (fn) => setTimeout.bind(null, fn, 0); | ||||||
| 	var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true }); | 	var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true }); | ||||||
| 	on.connected = function(listener, options) { | 	on.connected = function(listener, options) { | ||||||
| 		options = lifeOptions(options); | 		options = lifeOptions(options); | ||||||
| @@ -367,10 +323,7 @@ var DDE = (() => { | |||||||
| 		}; | 		}; | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	// src/dom.js | 	// src/dom-lib/scopes.js | ||||||
| 	function queue(promise) { |  | ||||||
| 		return enviroment.q(promise); |  | ||||||
| 	} |  | ||||||
| 	var scopes = [{ | 	var scopes = [{ | ||||||
| 		get scope() { | 		get scope() { | ||||||
| 			return enviroment.D.body; | 			return enviroment.D.body; | ||||||
| @@ -382,7 +335,7 @@ var DDE = (() => { | |||||||
| 	var scope = { | 	var scope = { | ||||||
| 		/** | 		/** | ||||||
| 		* Gets the current scope | 		* Gets the current scope | ||||||
| 		* @returns {Object} Current scope context | 		* @returns {typeof scopes[number]} Current scope context | ||||||
| 		*/ | 		*/ | ||||||
| 		get current() { | 		get current() { | ||||||
| 			return scopes[scopes.length - 1]; | 			return scopes[scopes.length - 1]; | ||||||
| @@ -445,6 +398,60 @@ var DDE = (() => { | |||||||
| 			return scopes.pop(); | 			return scopes.pop(); | ||||||
| 		} | 		} | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
|  | 	// src/signals-lib/common.js | ||||||
|  | 	var signals_global = { | ||||||
|  | 		/** | ||||||
|  | 		* Checks if a value is a signal | ||||||
|  | 		* @param {any} attributes - Value to check | ||||||
|  | 		* @returns {boolean} Whether the value is a signal | ||||||
|  | 		*/ | ||||||
|  | 		isSignal(attributes) { | ||||||
|  | 			return false; | ||||||
|  | 		}, | ||||||
|  | 		/** | ||||||
|  | 		* Processes an attribute that might be reactive | ||||||
|  | 		* @param {Element} obj - Element that owns the attribute | ||||||
|  | 		* @param {string} key - Attribute name | ||||||
|  | 		* @param {any} attr - Attribute value | ||||||
|  | 		* @param {Function} set - Function to set the attribute | ||||||
|  | 		* @returns {any} Processed attribute value | ||||||
|  | 		*/ | ||||||
|  | 		processReactiveAttribute(obj, key, attr, set) { | ||||||
|  | 			return attr; | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 	function registerReactivity(def, global = true) { | ||||||
|  | 		if (global) return oAssign(signals_global, def); | ||||||
|  | 		Object.setPrototypeOf(def, signals_global); | ||||||
|  | 		return def; | ||||||
|  | 	} | ||||||
|  | 	function signals(_this) { | ||||||
|  | 		return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// src/dom-lib/helpers.js | ||||||
|  | 	function setRemove(obj, prop, key, val) { | ||||||
|  | 		return obj[(isUndef(val) ? "remove" : "set") + prop](key, val); | ||||||
|  | 	} | ||||||
|  | 	function setRemoveNS(obj, prop, key, val, ns = null) { | ||||||
|  | 		return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val); | ||||||
|  | 	} | ||||||
|  | 	function setDelete(obj, key, val) { | ||||||
|  | 		Reflect.set(obj, key, val); | ||||||
|  | 		if (!isUndef(val)) return; | ||||||
|  | 		return Reflect.deleteProperty(obj, key); | ||||||
|  | 	} | ||||||
|  | 	function elementAttribute(element, op, key, value) { | ||||||
|  | 		if (isInstance(element, enviroment.H)) | ||||||
|  | 			return element[op + "Attribute"](key, value); | ||||||
|  | 		return element[op + "AttributeNS"](null, key, value); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// src/dom-lib/el.js | ||||||
|  | 	function queue(promise) { | ||||||
|  | 		return enviroment.q(promise); | ||||||
|  | 	} | ||||||
| 	function append(...els) { | 	function append(...els) { | ||||||
| 		this.appendOriginal(...els); | 		this.appendOriginal(...els); | ||||||
| 		return this; | 		return this; | ||||||
| @@ -460,16 +467,18 @@ var DDE = (() => { | |||||||
| 		const s = signals(this); | 		const s = signals(this); | ||||||
| 		let scoped = 0; | 		let scoped = 0; | ||||||
| 		let el, el_host; | 		let el, el_host; | ||||||
| 		if (Object(attributes) !== attributes || s.isSignal(attributes)) | 		const att_type = typeof attributes; | ||||||
|  | 		if (att_type === "string" || att_type === "number" || s.isSignal(attributes)) | ||||||
| 			attributes = { textContent: attributes }; | 			attributes = { textContent: attributes }; | ||||||
| 		switch (true) { | 		switch (true) { | ||||||
| 			case typeof tag === "function": { | 			case typeof tag === "function": { | ||||||
| 				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 = tag(attributes || void 0); | 				el = /** @type {Element} */ | ||||||
| 				const is_fragment = isInstance(el, enviroment.F); | 				tag(attributes || void 0); | ||||||
| 				if (el.nodeName === "#comment") break; | 				if (el.nodeName === "#comment") break; | ||||||
|  | 				const is_fragment = isInstance(el, enviroment.F); | ||||||
| 				const el_mark = createElement.mark({ | 				const el_mark = createElement.mark({ | ||||||
| 					type: "component", | 					type: "component", | ||||||
| 					name: tag.name, | 					name: tag.name, | ||||||
| @@ -514,38 +523,6 @@ var DDE = (() => { | |||||||
| 			return el; | 			return el; | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
| 	function simulateSlots(element, root = element) { |  | ||||||
| 		const mark_e = "\xB9\u2070", mark_s = "\u2713"; |  | ||||||
| 		const slots = Object.fromEntries( |  | ||||||
| 			Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s]) |  | ||||||
| 		); |  | ||||||
| 		element.append = new Proxy(element.append, { |  | ||||||
| 			apply(orig, _, els) { |  | ||||||
| 				if (els[0] === root) return orig.apply(element, els); |  | ||||||
| 				for (const el of els) { |  | ||||||
| 					const name = (el.slot || "") + mark_e; |  | ||||||
| 					try { |  | ||||||
| 						elementAttribute(el, "remove", "slot"); |  | ||||||
| 					} catch (_error) { |  | ||||||
| 					} |  | ||||||
| 					const slot = slots[name]; |  | ||||||
| 					if (!slot) return; |  | ||||||
| 					if (!slot.name.startsWith(mark_s)) { |  | ||||||
| 						slot.childNodes.forEach((c) => c.remove()); |  | ||||||
| 						slot.name = mark_s + name; |  | ||||||
| 					} |  | ||||||
| 					slot.append(el); |  | ||||||
| 				} |  | ||||||
| 				element.append = orig; |  | ||||||
| 				return element; |  | ||||||
| 			} |  | ||||||
| 		}); |  | ||||||
| 		if (element !== root) { |  | ||||||
| 			const els = Array.from(element.childNodes); |  | ||||||
| 			element.append(...els); |  | ||||||
| 		} |  | ||||||
| 		return root; |  | ||||||
| 	} |  | ||||||
| 	var assign_context = /* @__PURE__ */ new WeakMap(); | 	var assign_context = /* @__PURE__ */ new WeakMap(); | ||||||
| 	var { setDeleteAttr: setDeleteAttr2 } = enviroment; | 	var { setDeleteAttr: setDeleteAttr2 } = enviroment; | ||||||
| 	function assign(element, ...attributes) { | 	function assign(element, ...attributes) { | ||||||
| @@ -608,11 +585,6 @@ var DDE = (() => { | |||||||
| 		); | 		); | ||||||
| 		return element; | 		return element; | ||||||
| 	} | 	} | ||||||
| 	function elementAttribute(element, op, key, value) { |  | ||||||
| 		if (isInstance(element, enviroment.H)) |  | ||||||
| 			return element[op + "Attribute"](key, value); |  | ||||||
| 		return element[op + "AttributeNS"](null, key, value); |  | ||||||
| 	} |  | ||||||
| 	function isPropSetter(el, key) { | 	function isPropSetter(el, key) { | ||||||
| 		if (!(key in el)) return false; | 		if (!(key in el)) return false; | ||||||
| 		const des = getPropDescriptor(el, key); | 		const des = getPropDescriptor(el, key); | ||||||
| @@ -636,19 +608,40 @@ var DDE = (() => { | |||||||
| 			cb(key, val); | 			cb(key, val); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 	function setRemove(obj, prop, key, val) { |  | ||||||
| 		return obj[(isUndef(val) ? "remove" : "set") + prop](key, val); |  | ||||||
| 	} |  | ||||||
| 	function setRemoveNS(obj, prop, key, val, ns = null) { |  | ||||||
| 		return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val); |  | ||||||
| 	} |  | ||||||
| 	function setDelete(obj, key, val) { |  | ||||||
| 		Reflect.set(obj, key, val); |  | ||||||
| 		if (!isUndef(val)) return; |  | ||||||
| 		return Reflect.deleteProperty(obj, key); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// src/customElement.js | 	// src/dom-lib/customElement.js | ||||||
|  | 	function simulateSlots(element, root = element) { | ||||||
|  | 		const mark_e = "\xB9\u2070", mark_s = "\u2713"; | ||||||
|  | 		const slots = Object.fromEntries( | ||||||
|  | 			Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s]) | ||||||
|  | 		); | ||||||
|  | 		element.append = new Proxy(element.append, { | ||||||
|  | 			apply(orig, _, els) { | ||||||
|  | 				if (els[0] === root) return orig.apply(element, els); | ||||||
|  | 				for (const el of els) { | ||||||
|  | 					const name = (el.slot || "") + mark_e; | ||||||
|  | 					try { | ||||||
|  | 						elementAttribute(el, "remove", "slot"); | ||||||
|  | 					} catch (_error) { | ||||||
|  | 					} | ||||||
|  | 					const slot = slots[name]; | ||||||
|  | 					if (!slot) return; | ||||||
|  | 					if (!slot.name.startsWith(mark_s)) { | ||||||
|  | 						slot.childNodes.forEach((c) => c.remove()); | ||||||
|  | 						slot.name = mark_s + name; | ||||||
|  | 					} | ||||||
|  | 					slot.append(el); | ||||||
|  | 				} | ||||||
|  | 				element.append = orig; | ||||||
|  | 				return element; | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 		if (element !== root) { | ||||||
|  | 			const els = Array.from(element.childNodes); | ||||||
|  | 			element.append(...els); | ||||||
|  | 		} | ||||||
|  | 		return root; | ||||||
|  | 	} | ||||||
| 	function customElementRender(target, render, props = {}) { | 	function customElementRender(target, render, props = {}) { | ||||||
| 		const custom_element = target.host || target; | 		const custom_element = target.host || target; | ||||||
| 		scope.push({ | 		scope.push({ | ||||||
| @@ -703,9 +696,9 @@ var DDE = (() => { | |||||||
| 	memo.isScope = function(obj) { | 	memo.isScope = function(obj) { | ||||||
| 		return obj[memoMark]; | 		return obj[memoMark]; | ||||||
| 	}; | 	}; | ||||||
| 	memo.scope = function memoScope(fun, { signal: signal2, onlyLast } = {}) { | 	memo.scope = function memoScopeCreate(fun, { signal: signal2, onlyLast } = {}) { | ||||||
| 		let cache = oCreate(); | 		let cache = oCreate(); | ||||||
| 		function memoScope2(...args) { | 		function memoScope(...args) { | ||||||
| 			if (signal2 && signal2.aborted) | 			if (signal2 && signal2.aborted) | ||||||
| 				return fun.apply(this, args); | 				return fun.apply(this, args); | ||||||
| 			let cache_local = onlyLast ? cache : oCreate(); | 			let cache_local = onlyLast ? cache : oCreate(); | ||||||
| @@ -720,28 +713,28 @@ var DDE = (() => { | |||||||
| 			cache = cache_local; | 			cache = cache_local; | ||||||
| 			return out; | 			return out; | ||||||
| 		} | 		} | ||||||
| 		memoScope2[memoMark] = true; | 		memoScope[memoMark] = true; | ||||||
| 		memoScope2.clear = () => cache = oCreate(); | 		memoScope.clear = () => cache = oCreate(); | ||||||
| 		if (signal2) signal2.addEventListener("abort", memoScope2.clear); | 		if (signal2) signal2.addEventListener("abort", memoScope.clear); | ||||||
| 		return memoScope2; | 		return memoScope; | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	// src/signals-lib/helpers.js | 	// src/signals-lib/helpers.js | ||||||
| 	var mark = "__dde_signal"; | 	var mark = "__dde_signal"; | ||||||
| 	var queueSignalWrite = /* @__PURE__ */ (() => { | 	var queueSignalWrite = /* @__PURE__ */ (() => { | ||||||
| 		let pendingSignals = /* @__PURE__ */ new Set(); | 		let pendingSignals = /* @__PURE__ */ new Map(); | ||||||
| 		let scheduled = false; | 		let scheduled = false; | ||||||
| 		function flushSignals() { | 		function flushSignals() { | ||||||
| 			scheduled = false; | 			scheduled = false; | ||||||
| 			const todo = pendingSignals; | 			const todo = pendingSignals; | ||||||
| 			pendingSignals = /* @__PURE__ */ new Set(); | 			pendingSignals = /* @__PURE__ */ new Map(); | ||||||
| 			for (const signal2 of todo) { | 			for (const [signal2, force] of todo) { | ||||||
| 				const M = signal2[mark]; | 				const M = signal2[mark]; | ||||||
| 				if (M) M.listeners.forEach((l) => l(M.value)); | 				if (M) M.listeners.forEach((l) => l(M.value, force)); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		return function(s) { | 		return function(s, force = false) { | ||||||
| 			pendingSignals.add(s); | 			pendingSignals.set(s, pendingSignals.get(s) || force); | ||||||
| 			if (scheduled) return; | 			if (scheduled) return; | ||||||
| 			scheduled = true; | 			scheduled = true; | ||||||
| 			queueMicrotask(flushSignals); | 			queueMicrotask(flushSignals); | ||||||
| @@ -749,13 +742,10 @@ var DDE = (() => { | |||||||
| 	})(); | 	})(); | ||||||
|  |  | ||||||
| 	// src/signals-lib/signals-lib.js | 	// src/signals-lib/signals-lib.js | ||||||
| 	var Signal = oCreate(null, { | 	var SignalReadOnly = 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); | ||||||
| 		} }, | 		} }, | ||||||
| @@ -763,9 +753,9 @@ var DDE = (() => { | |||||||
| 			return this[mark] && this[mark].value; | 			return this[mark] && this[mark].value; | ||||||
| 		} } | 		} } | ||||||
| 	}); | 	}); | ||||||
| 	var SignalReadOnly = oCreate(Signal, { | 	var Signal = oCreate(SignalReadOnly, { | ||||||
| 		set: { value() { | 		set: { value(...v) { | ||||||
| 			return; | 			return write(this, ...v); | ||||||
| 		} } | 		} } | ||||||
| 	}); | 	}); | ||||||
| 	function isSignal(candidate) { | 	function isSignal(candidate) { | ||||||
| @@ -778,11 +768,11 @@ var DDE = (() => { | |||||||
| 			return create(false, value, actions); | 			return create(false, value, actions); | ||||||
| 		if (isSignal(value)) return value; | 		if (isSignal(value)) return value; | ||||||
| 		const out = create(true); | 		const out = create(true); | ||||||
| 		function contextReWatch() { | 		function contextReWatch(_, force) { | ||||||
| 			const [origin, ...deps_old] = deps.get(contextReWatch); | 			const [origin, ...deps_old] = deps.get(contextReWatch); | ||||||
| 			deps.set(contextReWatch, /* @__PURE__ */ new Set([origin])); | 			deps.set(contextReWatch, /* @__PURE__ */ new Set([origin])); | ||||||
| 			stack_watch.push(contextReWatch); | 			stack_watch.push(contextReWatch); | ||||||
| 			write(out, value()); | 			write(out, value(), force); | ||||||
| 			stack_watch.pop(); | 			stack_watch.pop(); | ||||||
| 			if (!deps_old.length) return; | 			if (!deps_old.length) return; | ||||||
| 			const deps_curr = deps.get(contextReWatch); | 			const deps_curr = deps.get(contextReWatch); | ||||||
| @@ -804,7 +794,7 @@ var DDE = (() => { | |||||||
| 			throw new Error(`Action "${name}" not defined. See ${mark}.actions.`); | 			throw new Error(`Action "${name}" not defined. See ${mark}.actions.`); | ||||||
| 		actions[name].apply(M, a); | 		actions[name].apply(M, a); | ||||||
| 		if (M.skip) return delete M.skip; | 		if (M.skip) return delete M.skip; | ||||||
| 		queueSignalWrite(s); | 		queueSignalWrite(s, true); | ||||||
| 	}; | 	}; | ||||||
| 	signal.on = function on2(s, listener, options = {}) { | 	signal.on = function on2(s, listener, options = {}) { | ||||||
| 		const { signal: as } = options; | 		const { signal: as } = options; | ||||||
| @@ -840,17 +830,17 @@ var DDE = (() => { | |||||||
| 	}; | 	}; | ||||||
| 	var key_reactive = "__dde_reactive"; | 	var key_reactive = "__dde_reactive"; | ||||||
| 	signal.el = function(s, map) { | 	signal.el = function(s, map) { | ||||||
| 		map = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | 		const mapScoped = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | ||||||
| 		const mark_start = createElement.mark({ type: "reactive", source: new Defined().compact }, true); | 		const { current } = scope, { scope: sc } = current; | ||||||
|  | 		const mark_start = createElement.mark({ type: "reactive", component: sc && sc.name || "" }, true); | ||||||
| 		const mark_end = mark_start.end; | 		const mark_end = mark_start.end; | ||||||
| 		const out = enviroment.D.createDocumentFragment(); | 		const out = enviroment.D.createDocumentFragment(); | ||||||
| 		out.append(mark_start, mark_end); | 		out.append(mark_start, mark_end); | ||||||
| 		const { current } = scope; |  | ||||||
| 		const reRenderReactiveElement = (v) => { | 		const reRenderReactiveElement = (v) => { | ||||||
| 			if (!mark_start.parentNode || !mark_end.parentNode) | 			if (!mark_start.parentNode || !mark_end.parentNode) | ||||||
| 				return removeSignalListener(s, reRenderReactiveElement); | 				return removeSignalListener(s, reRenderReactiveElement); | ||||||
| 			scope.push(current); | 			scope.push(current); | ||||||
| 			let els = map(v); | 			let els = mapScoped(v); | ||||||
| 			scope.pop(); | 			scope.pop(); | ||||||
| 			if (!Array.isArray(els)) | 			if (!Array.isArray(els)) | ||||||
| 				els = [els]; | 				els = [els]; | ||||||
| @@ -870,14 +860,14 @@ var DDE = (() => { | |||||||
| 		current.host(on.disconnected( | 		current.host(on.disconnected( | ||||||
| 			() => ( | 			() => ( | ||||||
| 				/*! Clears cached elements for reactive element `S.el` */ | 				/*! Clears cached elements for reactive element `S.el` */ | ||||||
| 				map.clear() | 				mapScoped.clear() | ||||||
| 			) | 			) | ||||||
| 		)); | 		)); | ||||||
| 		return out; | 		return out; | ||||||
| 	}; | 	}; | ||||||
| 	function requestCleanUpReactives(host) { | 	function requestCleanUpReactives(host) { | ||||||
| 		if (!host || !host[key_reactive]) return; | 		if (!host || !host[key_reactive]) return; | ||||||
| 		(requestIdleCallback || setTimeout)(function() { | 		requestIdle().then(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)); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| @@ -942,10 +932,10 @@ 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) { | ||||||
| 			if (element[key_reactive]) | 			const is_first = !element[key_reactive]; | ||||||
| 				return element[key_reactive].push([[s, listener], ...notes]); | 			if (is_first) element[key_reactive] = []; | ||||||
| 			element[key_reactive] = []; | 			element[key_reactive].push([[s, listener], ...notes]); | ||||||
| 			if (current.prevent) return; | 			if (!is_first || 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`, …?). | ||||||
| @@ -960,7 +950,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, is_readonly); | 		const SI = toSignal(varS, value, actions); | ||||||
| 		cleanUpRegistry.register(SI, SI[mark]); | 		cleanUpRegistry.register(SI, SI[mark]); | ||||||
| 		return SI; | 		return SI; | ||||||
| 	} | 	} | ||||||
| @@ -972,7 +962,7 @@ var DDE = (() => { | |||||||
| 			this.skip = true; | 			this.skip = true; | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
| 	function toSignal(s, value, actions, readonly = false) { | 	function toSignal(s, value, actions) { | ||||||
| 		const onclear = []; | 		const onclear = []; | ||||||
| 		if (typeOf(actions) !== "[object Object]") | 		if (typeOf(actions) !== "[object Object]") | ||||||
| 			actions = {}; | 			actions = {}; | ||||||
| @@ -988,9 +978,7 @@ var DDE = (() => { | |||||||
| 				actions, | 				actions, | ||||||
| 				onclear, | 				onclear, | ||||||
| 				host, | 				host, | ||||||
| 				listeners: /* @__PURE__ */ new Set(), | 				listeners: /* @__PURE__ */ new Set() | ||||||
| 				defined: new Defined().stack, |  | ||||||
| 				readonly |  | ||||||
| 			}), | 			}), | ||||||
| 			enumerable: false, | 			enumerable: false, | ||||||
| 			writable: false, | 			writable: false, | ||||||
| @@ -1013,7 +1001,7 @@ var DDE = (() => { | |||||||
| 		const M = s[mark]; | 		const M = s[mark]; | ||||||
| 		if (!M || !force && M.value === value) return; | 		if (!M || !force && M.value === value) return; | ||||||
| 		M.value = value; | 		M.value = value; | ||||||
| 		queueSignalWrite(s); | 		queueSignalWrite(s, force); | ||||||
| 		return value; | 		return value; | ||||||
| 	} | 	} | ||||||
| 	function addSignalListener(s, listener) { | 	function addSignalListener(s, listener) { | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								dist/iife-with-signals.min.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								dist/iife-with-signals.min.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -4,7 +4,7 @@ export interface Signal<V, A> { | |||||||
| 	/** The current value of the signal */ | 	/** The current value of the signal */ | ||||||
| 	get(): V; | 	get(): V; | ||||||
| 	/** Set new value of the signal */ | 	/** Set new value of the signal */ | ||||||
| 	set(value: V): V; | 	set(value: V, force?: boolean): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
| @@ -173,17 +173,30 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options? | |||||||
| declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void; | declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void; | ||||||
| export interface On { | export interface On { | ||||||
| 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | ||||||
| 	<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE; | 	<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & { | ||||||
|  | 		target: EL; | ||||||
|  | 	}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE; | 	<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE; | ||||||
| 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | ||||||
| 	connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE; | 	connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | ||||||
| 	disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE; | 	disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** | ||||||
| 	attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[ | 	 * Fires after the next tick of the Javascript event loop. | ||||||
| 		string, | 	 * This is handy for example to apply some property depending on the element content: | ||||||
| 		string | 	 * ```js | ||||||
| 	]>) => any, options?: AddEventListenerOptions): EE; | 	 * const selected= "Z"; | ||||||
|  | 	 * //... | ||||||
|  | 	 * return el("form").append( | ||||||
|  | 	 *		el("select", null, on.defer(e=> e.value=selected)).append( | ||||||
|  | 	 *			el("option", { value: "A", textContent: "A" }), | ||||||
|  | 	 *			//... | ||||||
|  | 	 *			el("option", { value: "Z", textContent: "Z" }), | ||||||
|  | 	 *		), | ||||||
|  | 	 * ); | ||||||
|  | 	 * ``` | ||||||
|  | 	 * */ | ||||||
|  | 	defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>; | ||||||
| } | } | ||||||
| export const on: On; | export const on: On; | ||||||
| export type Scope = { | export type Scope = { | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								dist/iife-with-signals.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								dist/iife-with-signals.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										31
									
								
								dist/iife.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								dist/iife.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -4,7 +4,7 @@ export interface Signal<V, A> { | |||||||
| 	/** The current value of the signal */ | 	/** The current value of the signal */ | ||||||
| 	get(): V; | 	get(): V; | ||||||
| 	/** Set new value of the signal */ | 	/** Set new value of the signal */ | ||||||
| 	set(value: V): V; | 	set(value: V, force?: boolean): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
| @@ -172,17 +172,30 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options? | |||||||
| declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void; | declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void; | ||||||
| export interface On { | export interface On { | ||||||
| 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | ||||||
| 	<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE; | 	<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & { | ||||||
|  | 		target: EL; | ||||||
|  | 	}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE; | 	<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE; | ||||||
| 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | ||||||
| 	connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE; | 	connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | ||||||
| 	disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE; | 	disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** | ||||||
| 	attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[ | 	 * Fires after the next tick of the Javascript event loop. | ||||||
| 		string, | 	 * This is handy for example to apply some property depending on the element content: | ||||||
| 		string | 	 * ```js | ||||||
| 	]>) => any, options?: AddEventListenerOptions): EE; | 	 * const selected= "Z"; | ||||||
|  | 	 * //... | ||||||
|  | 	 * return el("form").append( | ||||||
|  | 	 *		el("select", null, on.defer(e=> e.value=selected)).append( | ||||||
|  | 	 *			el("option", { value: "A", textContent: "A" }), | ||||||
|  | 	 *			//... | ||||||
|  | 	 *			el("option", { value: "Z", textContent: "Z" }), | ||||||
|  | 	 *		), | ||||||
|  | 	 * ); | ||||||
|  | 	 * ``` | ||||||
|  | 	 * */ | ||||||
|  | 	defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>; | ||||||
| } | } | ||||||
| export const on: On; | export const on: On; | ||||||
| export type Scope = { | export type Scope = { | ||||||
|   | |||||||
							
								
								
									
										210
									
								
								dist/iife.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										210
									
								
								dist/iife.js
									
									
									
									
										vendored
									
									
								
							| @@ -31,7 +31,6 @@ var DDE = (() => { | |||||||
| 		dispatchEvent: () => dispatchEvent, | 		dispatchEvent: () => dispatchEvent, | ||||||
| 		el: () => createElement, | 		el: () => createElement, | ||||||
| 		elNS: () => createElementNS, | 		elNS: () => createElementNS, | ||||||
| 		elementAttribute: () => elementAttribute, |  | ||||||
| 		lifecyclesToEvents: () => lifecyclesToEvents, | 		lifecyclesToEvents: () => lifecyclesToEvents, | ||||||
| 		memo: () => memo, | 		memo: () => memo, | ||||||
| 		on: () => on, | 		on: () => on, | ||||||
| @@ -68,39 +67,13 @@ var DDE = (() => { | |||||||
| 			signal.removeEventListener("abort", listener); | 			signal.removeEventListener("abort", listener); | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
|  | 	function requestIdle() { | ||||||
| 	// src/signals-lib/common.js | 		return new Promise(function(resolve) { | ||||||
| 	var signals_global = { | 			(globalThis.requestIdleCallback || requestAnimationFrame)(resolve); | ||||||
| 		/** | 		}); | ||||||
| 		* Checks if a value is a signal |  | ||||||
| 		* @param {any} attributes - Value to check |  | ||||||
| 		* @returns {boolean} Whether the value is a signal |  | ||||||
| 		*/ |  | ||||||
| 		isSignal(attributes) { |  | ||||||
| 			return false; |  | ||||||
| 		}, |  | ||||||
| 		/** |  | ||||||
| 		* Processes an attribute that might be reactive |  | ||||||
| 		* @param {Element} obj - Element that owns the attribute |  | ||||||
| 		* @param {string} key - Attribute name |  | ||||||
| 		* @param {any} attr - Attribute value |  | ||||||
| 		* @param {Function} set - Function to set the attribute |  | ||||||
| 		* @returns {any} Processed attribute value |  | ||||||
| 		*/ |  | ||||||
| 		processReactiveAttribute(obj, key, attr, set) { |  | ||||||
| 			return attr; |  | ||||||
| 		} |  | ||||||
| 	}; |  | ||||||
| 	function registerReactivity(def, global = true) { |  | ||||||
| 		if (global) return oAssign(signals_global, def); |  | ||||||
| 		Object.setPrototypeOf(def, signals_global); |  | ||||||
| 		return def; |  | ||||||
| 	} |  | ||||||
| 	function signals(_this) { |  | ||||||
| 		return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global; |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// src/dom-common.js | 	// src/dom-lib/common.js | ||||||
| 	var enviroment = { | 	var enviroment = { | ||||||
| 		setDeleteAttr, | 		setDeleteAttr, | ||||||
| 		ssr: "", | 		ssr: "", | ||||||
| @@ -126,7 +99,7 @@ var DDE = (() => { | |||||||
| 	var evd = "dde:disconnected"; | 	var evd = "dde:disconnected"; | ||||||
| 	var eva = "dde:attributeChanged"; | 	var eva = "dde:attributeChanged"; | ||||||
|  |  | ||||||
| 	// src/events-observer.js | 	// src/dom-lib/events-observer.js | ||||||
| 	var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, { | 	var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, { | ||||||
| 		get() { | 		get() { | ||||||
| 			return () => { | 			return () => { | ||||||
| @@ -237,11 +210,6 @@ 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(); | ||||||
| @@ -290,7 +258,7 @@ var DDE = (() => { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// src/events.js | 	// src/dom-lib/events.js | ||||||
| 	function dispatchEvent(name, options, host) { | 	function dispatchEvent(name, options, host) { | ||||||
| 		if (typeof options === "function") { | 		if (typeof options === "function") { | ||||||
| 			host = options; | 			host = options; | ||||||
| @@ -312,6 +280,7 @@ var DDE = (() => { | |||||||
| 			return element; | 			return element; | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
|  | 	on.defer = (fn) => setTimeout.bind(null, fn, 0); | ||||||
| 	var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true }); | 	var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true }); | ||||||
| 	on.connected = function(listener, options) { | 	on.connected = function(listener, options) { | ||||||
| 		options = lifeOptions(options); | 		options = lifeOptions(options); | ||||||
| @@ -335,10 +304,7 @@ var DDE = (() => { | |||||||
| 		}; | 		}; | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	// src/dom.js | 	// src/dom-lib/scopes.js | ||||||
| 	function queue(promise) { |  | ||||||
| 		return enviroment.q(promise); |  | ||||||
| 	} |  | ||||||
| 	var scopes = [{ | 	var scopes = [{ | ||||||
| 		get scope() { | 		get scope() { | ||||||
| 			return enviroment.D.body; | 			return enviroment.D.body; | ||||||
| @@ -350,7 +316,7 @@ var DDE = (() => { | |||||||
| 	var scope = { | 	var scope = { | ||||||
| 		/** | 		/** | ||||||
| 		* Gets the current scope | 		* Gets the current scope | ||||||
| 		* @returns {Object} Current scope context | 		* @returns {typeof scopes[number]} Current scope context | ||||||
| 		*/ | 		*/ | ||||||
| 		get current() { | 		get current() { | ||||||
| 			return scopes[scopes.length - 1]; | 			return scopes[scopes.length - 1]; | ||||||
| @@ -413,6 +379,60 @@ var DDE = (() => { | |||||||
| 			return scopes.pop(); | 			return scopes.pop(); | ||||||
| 		} | 		} | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
|  | 	// src/signals-lib/common.js | ||||||
|  | 	var signals_global = { | ||||||
|  | 		/** | ||||||
|  | 		* Checks if a value is a signal | ||||||
|  | 		* @param {any} attributes - Value to check | ||||||
|  | 		* @returns {boolean} Whether the value is a signal | ||||||
|  | 		*/ | ||||||
|  | 		isSignal(attributes) { | ||||||
|  | 			return false; | ||||||
|  | 		}, | ||||||
|  | 		/** | ||||||
|  | 		* Processes an attribute that might be reactive | ||||||
|  | 		* @param {Element} obj - Element that owns the attribute | ||||||
|  | 		* @param {string} key - Attribute name | ||||||
|  | 		* @param {any} attr - Attribute value | ||||||
|  | 		* @param {Function} set - Function to set the attribute | ||||||
|  | 		* @returns {any} Processed attribute value | ||||||
|  | 		*/ | ||||||
|  | 		processReactiveAttribute(obj, key, attr, set) { | ||||||
|  | 			return attr; | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 	function registerReactivity(def, global = true) { | ||||||
|  | 		if (global) return oAssign(signals_global, def); | ||||||
|  | 		Object.setPrototypeOf(def, signals_global); | ||||||
|  | 		return def; | ||||||
|  | 	} | ||||||
|  | 	function signals(_this) { | ||||||
|  | 		return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// src/dom-lib/helpers.js | ||||||
|  | 	function setRemove(obj, prop, key, val) { | ||||||
|  | 		return obj[(isUndef(val) ? "remove" : "set") + prop](key, val); | ||||||
|  | 	} | ||||||
|  | 	function setRemoveNS(obj, prop, key, val, ns = null) { | ||||||
|  | 		return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val); | ||||||
|  | 	} | ||||||
|  | 	function setDelete(obj, key, val) { | ||||||
|  | 		Reflect.set(obj, key, val); | ||||||
|  | 		if (!isUndef(val)) return; | ||||||
|  | 		return Reflect.deleteProperty(obj, key); | ||||||
|  | 	} | ||||||
|  | 	function elementAttribute(element, op, key, value) { | ||||||
|  | 		if (isInstance(element, enviroment.H)) | ||||||
|  | 			return element[op + "Attribute"](key, value); | ||||||
|  | 		return element[op + "AttributeNS"](null, key, value); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// src/dom-lib/el.js | ||||||
|  | 	function queue(promise) { | ||||||
|  | 		return enviroment.q(promise); | ||||||
|  | 	} | ||||||
| 	function append(...els) { | 	function append(...els) { | ||||||
| 		this.appendOriginal(...els); | 		this.appendOriginal(...els); | ||||||
| 		return this; | 		return this; | ||||||
| @@ -428,16 +448,18 @@ var DDE = (() => { | |||||||
| 		const s = signals(this); | 		const s = signals(this); | ||||||
| 		let scoped = 0; | 		let scoped = 0; | ||||||
| 		let el, el_host; | 		let el, el_host; | ||||||
| 		if (Object(attributes) !== attributes || s.isSignal(attributes)) | 		const att_type = typeof attributes; | ||||||
|  | 		if (att_type === "string" || att_type === "number" || s.isSignal(attributes)) | ||||||
| 			attributes = { textContent: attributes }; | 			attributes = { textContent: attributes }; | ||||||
| 		switch (true) { | 		switch (true) { | ||||||
| 			case typeof tag === "function": { | 			case typeof tag === "function": { | ||||||
| 				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 = tag(attributes || void 0); | 				el = /** @type {Element} */ | ||||||
| 				const is_fragment = isInstance(el, enviroment.F); | 				tag(attributes || void 0); | ||||||
| 				if (el.nodeName === "#comment") break; | 				if (el.nodeName === "#comment") break; | ||||||
|  | 				const is_fragment = isInstance(el, enviroment.F); | ||||||
| 				const el_mark = createElement.mark({ | 				const el_mark = createElement.mark({ | ||||||
| 					type: "component", | 					type: "component", | ||||||
| 					name: tag.name, | 					name: tag.name, | ||||||
| @@ -482,38 +504,6 @@ var DDE = (() => { | |||||||
| 			return el; | 			return el; | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
| 	function simulateSlots(element, root = element) { |  | ||||||
| 		const mark_e = "\xB9\u2070", mark_s = "\u2713"; |  | ||||||
| 		const slots = Object.fromEntries( |  | ||||||
| 			Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s]) |  | ||||||
| 		); |  | ||||||
| 		element.append = new Proxy(element.append, { |  | ||||||
| 			apply(orig, _, els) { |  | ||||||
| 				if (els[0] === root) return orig.apply(element, els); |  | ||||||
| 				for (const el of els) { |  | ||||||
| 					const name = (el.slot || "") + mark_e; |  | ||||||
| 					try { |  | ||||||
| 						elementAttribute(el, "remove", "slot"); |  | ||||||
| 					} catch (_error) { |  | ||||||
| 					} |  | ||||||
| 					const slot = slots[name]; |  | ||||||
| 					if (!slot) return; |  | ||||||
| 					if (!slot.name.startsWith(mark_s)) { |  | ||||||
| 						slot.childNodes.forEach((c) => c.remove()); |  | ||||||
| 						slot.name = mark_s + name; |  | ||||||
| 					} |  | ||||||
| 					slot.append(el); |  | ||||||
| 				} |  | ||||||
| 				element.append = orig; |  | ||||||
| 				return element; |  | ||||||
| 			} |  | ||||||
| 		}); |  | ||||||
| 		if (element !== root) { |  | ||||||
| 			const els = Array.from(element.childNodes); |  | ||||||
| 			element.append(...els); |  | ||||||
| 		} |  | ||||||
| 		return root; |  | ||||||
| 	} |  | ||||||
| 	var assign_context = /* @__PURE__ */ new WeakMap(); | 	var assign_context = /* @__PURE__ */ new WeakMap(); | ||||||
| 	var { setDeleteAttr: setDeleteAttr2 } = enviroment; | 	var { setDeleteAttr: setDeleteAttr2 } = enviroment; | ||||||
| 	function assign(element, ...attributes) { | 	function assign(element, ...attributes) { | ||||||
| @@ -576,11 +566,6 @@ var DDE = (() => { | |||||||
| 		); | 		); | ||||||
| 		return element; | 		return element; | ||||||
| 	} | 	} | ||||||
| 	function elementAttribute(element, op, key, value) { |  | ||||||
| 		if (isInstance(element, enviroment.H)) |  | ||||||
| 			return element[op + "Attribute"](key, value); |  | ||||||
| 		return element[op + "AttributeNS"](null, key, value); |  | ||||||
| 	} |  | ||||||
| 	function isPropSetter(el, key) { | 	function isPropSetter(el, key) { | ||||||
| 		if (!(key in el)) return false; | 		if (!(key in el)) return false; | ||||||
| 		const des = getPropDescriptor(el, key); | 		const des = getPropDescriptor(el, key); | ||||||
| @@ -604,19 +589,40 @@ var DDE = (() => { | |||||||
| 			cb(key, val); | 			cb(key, val); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 	function setRemove(obj, prop, key, val) { |  | ||||||
| 		return obj[(isUndef(val) ? "remove" : "set") + prop](key, val); |  | ||||||
| 	} |  | ||||||
| 	function setRemoveNS(obj, prop, key, val, ns = null) { |  | ||||||
| 		return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val); |  | ||||||
| 	} |  | ||||||
| 	function setDelete(obj, key, val) { |  | ||||||
| 		Reflect.set(obj, key, val); |  | ||||||
| 		if (!isUndef(val)) return; |  | ||||||
| 		return Reflect.deleteProperty(obj, key); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// src/customElement.js | 	// src/dom-lib/customElement.js | ||||||
|  | 	function simulateSlots(element, root = element) { | ||||||
|  | 		const mark_e = "\xB9\u2070", mark_s = "\u2713"; | ||||||
|  | 		const slots = Object.fromEntries( | ||||||
|  | 			Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s]) | ||||||
|  | 		); | ||||||
|  | 		element.append = new Proxy(element.append, { | ||||||
|  | 			apply(orig, _, els) { | ||||||
|  | 				if (els[0] === root) return orig.apply(element, els); | ||||||
|  | 				for (const el of els) { | ||||||
|  | 					const name = (el.slot || "") + mark_e; | ||||||
|  | 					try { | ||||||
|  | 						elementAttribute(el, "remove", "slot"); | ||||||
|  | 					} catch (_error) { | ||||||
|  | 					} | ||||||
|  | 					const slot = slots[name]; | ||||||
|  | 					if (!slot) return; | ||||||
|  | 					if (!slot.name.startsWith(mark_s)) { | ||||||
|  | 						slot.childNodes.forEach((c) => c.remove()); | ||||||
|  | 						slot.name = mark_s + name; | ||||||
|  | 					} | ||||||
|  | 					slot.append(el); | ||||||
|  | 				} | ||||||
|  | 				element.append = orig; | ||||||
|  | 				return element; | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 		if (element !== root) { | ||||||
|  | 			const els = Array.from(element.childNodes); | ||||||
|  | 			element.append(...els); | ||||||
|  | 		} | ||||||
|  | 		return root; | ||||||
|  | 	} | ||||||
| 	function customElementRender(target, render, props = {}) { | 	function customElementRender(target, render, props = {}) { | ||||||
| 		const custom_element = target.host || target; | 		const custom_element = target.host || target; | ||||||
| 		scope.push({ | 		scope.push({ | ||||||
| @@ -671,9 +677,9 @@ var DDE = (() => { | |||||||
| 	memo.isScope = function(obj) { | 	memo.isScope = function(obj) { | ||||||
| 		return obj[memoMark]; | 		return obj[memoMark]; | ||||||
| 	}; | 	}; | ||||||
| 	memo.scope = function memoScope(fun, { signal, onlyLast } = {}) { | 	memo.scope = function memoScopeCreate(fun, { signal, onlyLast } = {}) { | ||||||
| 		let cache = oCreate(); | 		let cache = oCreate(); | ||||||
| 		function memoScope2(...args) { | 		function memoScope(...args) { | ||||||
| 			if (signal && signal.aborted) | 			if (signal && signal.aborted) | ||||||
| 				return fun.apply(this, args); | 				return fun.apply(this, args); | ||||||
| 			let cache_local = onlyLast ? cache : oCreate(); | 			let cache_local = onlyLast ? cache : oCreate(); | ||||||
| @@ -688,10 +694,10 @@ var DDE = (() => { | |||||||
| 			cache = cache_local; | 			cache = cache_local; | ||||||
| 			return out; | 			return out; | ||||||
| 		} | 		} | ||||||
| 		memoScope2[memoMark] = true; | 		memoScope[memoMark] = true; | ||||||
| 		memoScope2.clear = () => cache = oCreate(); | 		memoScope.clear = () => cache = oCreate(); | ||||||
| 		if (signal) signal.addEventListener("abort", memoScope2.clear); | 		if (signal) signal.addEventListener("abort", memoScope.clear); | ||||||
| 		return memoScope2; | 		return memoScope; | ||||||
| 	}; | 	}; | ||||||
| 	return __toCommonJS(index_exports); | 	return __toCommonJS(index_exports); | ||||||
| })(); | })(); | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								dist/iife.min.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								dist/iife.min.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -4,7 +4,7 @@ export interface Signal<V, A> { | |||||||
| 	/** The current value of the signal */ | 	/** The current value of the signal */ | ||||||
| 	get(): V; | 	get(): V; | ||||||
| 	/** Set new value of the signal */ | 	/** Set new value of the signal */ | ||||||
| 	set(value: V): V; | 	set(value: V, force?: boolean): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
| @@ -172,17 +172,30 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options? | |||||||
| declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void; | declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void; | ||||||
| export interface On { | export interface On { | ||||||
| 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | ||||||
| 	<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE; | 	<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & { | ||||||
|  | 		target: EL; | ||||||
|  | 	}) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE; | 	<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE; | ||||||
| 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | ||||||
| 	connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE; | 	connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | ||||||
| 	disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE; | 	disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line | 	/** | ||||||
| 	attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[ | 	 * Fires after the next tick of the Javascript event loop. | ||||||
| 		string, | 	 * This is handy for example to apply some property depending on the element content: | ||||||
| 		string | 	 * ```js | ||||||
| 	]>) => any, options?: AddEventListenerOptions): EE; | 	 * const selected= "Z"; | ||||||
|  | 	 * //... | ||||||
|  | 	 * return el("form").append( | ||||||
|  | 	 *		el("select", null, on.defer(e=> e.value=selected)).append( | ||||||
|  | 	 *			el("option", { value: "A", textContent: "A" }), | ||||||
|  | 	 *			//... | ||||||
|  | 	 *			el("option", { value: "Z", textContent: "Z" }), | ||||||
|  | 	 *		), | ||||||
|  | 	 * ); | ||||||
|  | 	 * ``` | ||||||
|  | 	 * */ | ||||||
|  | 	defer<EL extends SupportedElement>(listener: (element: EL) => any): ddeElementAddon<EL>; | ||||||
| } | } | ||||||
| export const on: On; | export const on: On; | ||||||
| export type Scope = { | export type Scope = { | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								dist/iife.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/iife.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/assets/devtools.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/assets/devtools.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 145 KiB | 
| @@ -1,4 +1,4 @@ | |||||||
| import { registerClientFile, styles } from "../ssr.js"; | import { page_id, registerClientFile, styles } from "../ssr.js"; | ||||||
| const host= "."+code.name; | const host= "."+code.name; | ||||||
| styles.css` | styles.css` | ||||||
| /* Code block styling */ | /* Code block styling */ | ||||||
| @@ -177,6 +177,9 @@ ${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 | ||||||
| @@ -184,15 +187,17 @@ 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 {"js"|"ts"|"html"|"css"} [attrs.language="js"] Language of the code |  * @param {Language} [attrs.language="-s"] Language of the code | ||||||
|  * @param {string} [attrs.page_id] ID of the page, if setted it registers shiki |  | ||||||
|  * */ |  * */ | ||||||
| export function code({ id, src, content, language= "js", className= host.slice(1), page_id }){ | export function code({ id, src, content, language= "-", className= host.slice(1) }){ | ||||||
| 	if(src) content= s.cat(src); | 	if(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(page_id){ | 	if(language!== "-"){ | ||||||
| 		registerClientPart(page_id); | 		registerClientPart(); | ||||||
| 		dataJS= "todo"; | 		dataJS= "todo"; | ||||||
| 	} | 	} | ||||||
| 	return el("div", { id, className, dataJS, tabIndex: 0 }).append( | 	return el("div", { id, className, dataJS, tabIndex: 0 }).append( | ||||||
| @@ -204,8 +209,7 @@ 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= {}; | ||||||
| /** @param {string} page_id */ | function registerClientPart(){ | ||||||
| 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 | ||||||
|   | |||||||
							
								
								
									
										175
									
								
								docs/components/converter.html.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								docs/components/converter.html.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,175 @@ | |||||||
|  | import { page_id, styles } from "../ssr.js"; | ||||||
|  |  | ||||||
|  | styles.css` | ||||||
|  | #html-to-dde-converter { | ||||||
|  | 	grid-column: full-main; | ||||||
|  | 	display: flex; | ||||||
|  | 	flex-direction: column; | ||||||
|  | 	gap: 1.5rem; | ||||||
|  | 	padding: 1.5rem; | ||||||
|  | 	border-radius: var(--border-radius); | ||||||
|  | 	background-color: var(--bg-sidebar); | ||||||
|  | 	box-shadow: var(--shadow); | ||||||
|  | 	border: 1px solid var(--border); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter h3 { | ||||||
|  | 	margin-top: 0; | ||||||
|  | 	color: var(--primary); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter .description { | ||||||
|  | 	color: var(--text-light); | ||||||
|  | 	font-size: 0.95rem; | ||||||
|  | 	margin-top: -1rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter .converter-form { | ||||||
|  | 	display: flex; | ||||||
|  | 	flex-direction: column; | ||||||
|  | 	gap: 1rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter .input-group, | ||||||
|  | #html-to-dde-converter .output-group { | ||||||
|  | 	display: flex; | ||||||
|  | 	flex-direction: column; | ||||||
|  | 	gap: 0.5rem; | ||||||
|  | } | ||||||
|  | #html-to-dde-converter [type="number"]{ | ||||||
|  | 	width: 3em; | ||||||
|  | 	font-variant-numeric: tabular-nums; | ||||||
|  | 	font-size: 1rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter label { | ||||||
|  | 	font-weight: 500; | ||||||
|  | 	display: flex; | ||||||
|  | 	justify-content: space-between; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter .options { | ||||||
|  | 	display: flex; | ||||||
|  | 	flex-wrap: wrap; | ||||||
|  | 	gap: 1rem; | ||||||
|  | 	margin-bottom: 0.5rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter .option-group { | ||||||
|  | 	display: flex; | ||||||
|  | 	align-items: center; | ||||||
|  | 	gap: 0.5rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter textarea { | ||||||
|  | 	font-family: var(--font-mono); | ||||||
|  | 	font-size: 0.9rem; | ||||||
|  | 	padding: 1rem; | ||||||
|  | 	border-radius: var(--border-radius); | ||||||
|  | 	border: 1px solid var(--border); | ||||||
|  | 	background-color: var(--bg); | ||||||
|  | 	color: var(--text); | ||||||
|  | 	min-height: 200px; | ||||||
|  | 	height: 25em; | ||||||
|  | 	resize: vertical; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter textarea:focus { | ||||||
|  | 	outline: 2px solid var(--primary-light); | ||||||
|  | 	outline-offset: 1px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter .button-group { | ||||||
|  | 	display: flex; | ||||||
|  | 	gap: 0.5rem; | ||||||
|  | 	justify-content: space-between; | ||||||
|  | 	align-items: center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter button { | ||||||
|  | 	padding: 0.5rem 1rem; | ||||||
|  | 	border-radius: var(--border-radius); | ||||||
|  | 	border: none; | ||||||
|  | 	background-color: var(--primary); | ||||||
|  | 	color: var(--button-text); | ||||||
|  | 	font-weight: 500; | ||||||
|  | 	cursor: pointer; | ||||||
|  | 	transition: background-color 0.2s ease; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter button:hover { | ||||||
|  | 	background-color: var(--primary-dark); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter button.secondary { | ||||||
|  | 	background-color: transparent; | ||||||
|  | 	border: 1px solid var(--border); | ||||||
|  | 	color: var(--text); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter button.secondary:hover { | ||||||
|  | 	background-color: var(--bg); | ||||||
|  | 	border-color: var(--primary); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter .copy-button { | ||||||
|  | 	background-color: var(--secondary); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter .copy-button:hover { | ||||||
|  | 	background-color: var(--secondary-dark); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter .status { | ||||||
|  | 	font-size: 0.9rem; | ||||||
|  | 	color: var(--text-light); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter .error { | ||||||
|  | 	color: hsl(0, 100%, 60%); | ||||||
|  | 	font-size: 0.9rem; | ||||||
|  | 	margin-top: 0.5rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Sample HTML examples list */ | ||||||
|  | #html-to-dde-converter .examples-list { | ||||||
|  | 	display: flex; | ||||||
|  | 	flex-wrap: wrap; | ||||||
|  | 	gap: 0.5rem; | ||||||
|  | 	margin-top: 0.5rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #html-to-dde-converter .example-button { | ||||||
|  | 	font-size: 0.85rem; | ||||||
|  | 	padding: 0.25rem 0.5rem; | ||||||
|  | } | ||||||
|  | `; | ||||||
|  |  | ||||||
|  | import { ireland } from "./ireland.html.js"; | ||||||
|  | import { el } from "deka-dom-el"; | ||||||
|  | const fileURL= url=> new URL(url, import.meta.url); | ||||||
|  |  | ||||||
|  | export function converter(){ | ||||||
|  | 	registerClientPart(page_id); | ||||||
|  | 	return el(ireland, { | ||||||
|  | 		src: fileURL("./converter.js.js"), | ||||||
|  | 		exportName: "converter", | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | let is_registered= {}; | ||||||
|  | /** @param {string} page_id */ | ||||||
|  | function registerClientPart(page_id){ | ||||||
|  | 	if(is_registered[page_id]) return; | ||||||
|  |  | ||||||
|  | 	document.head.append( | ||||||
|  | 		el("script", { | ||||||
|  | 			// src: "https://unpkg.com/@beforesemicolon/html-parser/dist/client.js", | ||||||
|  | 			src: "https://cdn.jsdelivr.net/npm/@beforesemicolon/html-parser/dist/client.js", | ||||||
|  | 			type: "text/javascript", | ||||||
|  | 			charset: "utf-8", | ||||||
|  | 			defer: true | ||||||
|  | 		}), | ||||||
|  | 	); | ||||||
|  | 	is_registered[page_id]= true; | ||||||
|  | } | ||||||
							
								
								
									
										384
									
								
								docs/components/converter.js.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										384
									
								
								docs/components/converter.js.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,384 @@ | |||||||
|  | import { el, on } from "deka-dom-el"; | ||||||
|  | import { S } from "deka-dom-el/signals"; | ||||||
|  | const { parse }= globalThis.BFS || { parse(){ return { children: [ "not implemented" ] } } }; | ||||||
|  | // Example HTML snippets | ||||||
|  | const examples = [ | ||||||
|  | { | ||||||
|  | 	name: "Simple Component", | ||||||
|  | 	html: `<div class="card"> | ||||||
|  | 	<img src="image.jpg" alt="Card Image" class="card-image"> | ||||||
|  | 	<h2 class="card-title">Card Title</h2> | ||||||
|  | 	<p class="card-text">This is a simple card component</p> | ||||||
|  | 	<button aria-pressed="mixed" type="button" class="card-button">Click Me</button> | ||||||
|  | </div>` | ||||||
|  | }, | ||||||
|  | { | ||||||
|  | 	name: "Navigation", | ||||||
|  | 	html: `<nav class="main-nav"> | ||||||
|  | 	<ul> | ||||||
|  | 		<li><a href="/" class="active">Home</a></li> | ||||||
|  | 		<li><a href="/about">About</a></li> | ||||||
|  | 		<li><a href="/services">Services</a></li> | ||||||
|  | 		<li><a href="/contact">Contact</a></li> | ||||||
|  | 	</ul> | ||||||
|  | </nav>` | ||||||
|  | }, | ||||||
|  | { | ||||||
|  | 	name: "Form", | ||||||
|  | 	html: `<form class="contact-form" onsubmit="submitForm(event)"> | ||||||
|  | 	<div class="form-group"> | ||||||
|  | 		<label for="name">Name:</label> | ||||||
|  | 		<input type="text" id="name" name="name" required> | ||||||
|  | 	</div> | ||||||
|  | 	<div class="form-group"> | ||||||
|  | 		<label for="email">Email:</label> | ||||||
|  | 		<input type="email" id="email" name="email" required> | ||||||
|  | 	</div> | ||||||
|  | 	<div class="form-group"> | ||||||
|  | 		<label for="message">Message:</label> | ||||||
|  | 		<textarea id="message" name="message" rows="4" required></textarea> | ||||||
|  | 	</div> | ||||||
|  | 	<button type="submit" class="submit-btn">Send Message</button> | ||||||
|  | </form>` | ||||||
|  | } | ||||||
|  | ]; | ||||||
|  |  | ||||||
|  | // Convert HTML to dd<el> code | ||||||
|  | function convertHTMLtoDDE(html, options = {}) { | ||||||
|  |  | ||||||
|  | 	try { | ||||||
|  | 		const parsed = parse(html); | ||||||
|  | 		const content = parsed.children[0] || parsed.childNodes[0]; | ||||||
|  | 		return !content ? "" : nodeToDDE(content, options); | ||||||
|  | 	} catch (error) { | ||||||
|  | 		console.error("Parsing error:", error); | ||||||
|  | 		return `// Error parsing HTML: ${error.message}`; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Node types based on standard DOM nodeType values | ||||||
|  | const NODE_TYPE = { | ||||||
|  | 	ELEMENT: 1,      // Standard element node (equivalent to node.type === "element") | ||||||
|  | 	TEXT: 3,         // Text node (equivalent to node.type === "text") | ||||||
|  | 	COMMENT: 8       // Comment node (equivalent to node.type === "comment") | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Convert a parsed node to dd<el> code | ||||||
|  | function nodeToDDE(node, options = {}, level = 0) { | ||||||
|  | 	const tab= options.indent === "-1" ? "\t" : " ".repeat(options.indent); | ||||||
|  | 	const indent = tab.repeat(level); | ||||||
|  | 	const nextIndent = tab.repeat(level + 1); | ||||||
|  |  | ||||||
|  | 	const { nodeType } = node; | ||||||
|  | 	// Handle text nodes | ||||||
|  | 	if (nodeType === NODE_TYPE.TEXT) { | ||||||
|  | 		const text = el("i", { innerText: node.nodeValue }).textContent; | ||||||
|  | 		if (!text.trim()) return null; | ||||||
|  |  | ||||||
|  | 		// Return as plain text or template string for longer text | ||||||
|  | 		return text.includes("\n") || text.includes('"') | ||||||
|  | 			? `\`${text}\`` | ||||||
|  | 			: `"${text}"`; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Handle comment nodes | ||||||
|  | 	if (nodeType === NODE_TYPE.COMMENT) { | ||||||
|  | 		const text = node.nodeValue; | ||||||
|  | 		if (!text.trim()) return null; | ||||||
|  | 		return text.includes("\n") | ||||||
|  | 			? [ "/*", ...text.trim().split("\n").map(l=> tab+l), "*/" ] | ||||||
|  | 			: [ `// ${text}` ]; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// For element nodes | ||||||
|  | 	if (nodeType === NODE_TYPE.ELEMENT) { | ||||||
|  | 		// Special case for SVG elements | ||||||
|  | 		const isNS = node.tagName === "svg"; | ||||||
|  | 		const elFunction = isNS ? "elNS" : "el"; | ||||||
|  |  | ||||||
|  | 		// Get tag name | ||||||
|  | 		let tagStr = `"${node.tagName}"`; | ||||||
|  |  | ||||||
|  | 		// Process attributes | ||||||
|  | 		const attrs = []; | ||||||
|  | 		const sets = { | ||||||
|  | 			aria: {}, | ||||||
|  | 			data: {}, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for (const { name: key, value } of node.attributes) { | ||||||
|  | 			// Handle class attribute | ||||||
|  | 			if (key === "class") { | ||||||
|  | 				attrs.push(`className: "${value}"`); | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Handle style attribute | ||||||
|  | 			if (key === "style") { | ||||||
|  | 				if (options.styleAsObject) { | ||||||
|  | 				// Convert inline style to object | ||||||
|  | 				const styleObj = {}; | ||||||
|  | 				value.split(";").forEach(part => { | ||||||
|  | 					const [propRaw, valueRaw] = part.split(":"); | ||||||
|  | 					if (propRaw && valueRaw) { | ||||||
|  | 						const prop = propRaw.trim(); | ||||||
|  | 						const propCamel = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase()); | ||||||
|  | 						styleObj[propCamel] = valueRaw.trim(); | ||||||
|  | 					} | ||||||
|  | 				}); | ||||||
|  |  | ||||||
|  | 				if (Object.keys(styleObj).length > 0) { | ||||||
|  | 					const styleStr = JSON.stringify(styleObj).replace(/"([^"]+)":/g, "$1:"); | ||||||
|  | 					attrs.push(`style: ${styleStr}`); | ||||||
|  | 				} | ||||||
|  | 				} else { | ||||||
|  | 					// Keep as string | ||||||
|  | 					attrs.push(`style: "${value}"`); | ||||||
|  | 				} | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Handle boolean attributes | ||||||
|  | 			if (value === "" || value === key) { | ||||||
|  | 				attrs.push(`${key}: true`); | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Handle data/aria attributes | ||||||
|  | 			if (key.startsWith("data-") || key.startsWith("aria-")) { | ||||||
|  | 				const keyName = key.startsWith("aria-") ? "aria" : "data"; | ||||||
|  | 				const keyCamel = key.slice(keyName.length + 1).replace(/-([a-z])/g, (_, c) => c.toUpperCase()); | ||||||
|  | 				sets[keyName][keyCamel] = value; | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Regular attributes | ||||||
|  | 			const keyRegular = key==="for" | ||||||
|  | 				? "htmlFor" | ||||||
|  | 				: key.startsWith("on") | ||||||
|  | 				? `"=${key}"` | ||||||
|  | 				: key.replace(/-([a-z])/g, (_, c) => c.toUpperCase()); | ||||||
|  | 			attrs.push(`${keyRegular}: "${value}"`); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Process sets | ||||||
|  | 		for (const [name, set] of Object.entries(sets)) { | ||||||
|  | 		if(options.dataAttrsAsCamel) | ||||||
|  | 			for (const [key, value] of Object.entries(set)) | ||||||
|  | 			attrs.push(`${name}${key[0].toUpperCase() + key.substring(1)}: "${value}"`); | ||||||
|  | 		else { | ||||||
|  | 			const setStr= Object.entries(set).map(([key, value]) => `${key}: "${value}"`).join(","); | ||||||
|  | 			if (setStr !== "") | ||||||
|  | 			attrs.push(`${name}set: { ${setStr} }`); | ||||||
|  | 		} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Process children | ||||||
|  | 		const children = []; | ||||||
|  | 		for (const child of node.childNodes) { | ||||||
|  | 			const childCode = nodeToDDE(child, options, level + 1); | ||||||
|  | 			if (!childCode) continue; | ||||||
|  |  | ||||||
|  | 			children.push(childCode); | ||||||
|  | 		} | ||||||
|  | 		if(node.childNodes.length===1 && node.childNodes[0].nodeType===NODE_TYPE.TEXT){ | ||||||
|  | 			const textContent= children.pop().slice(1, -1); | ||||||
|  | 			attrs.unshift(`textContent: "${textContent}"`); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Build the element creation code | ||||||
|  | 		let result = `${elFunction}("${node.tagName.toLowerCase()}"`; | ||||||
|  |  | ||||||
|  | 		// Add attributes if any | ||||||
|  | 		if (attrs.length > 0) { | ||||||
|  | 		const tooLong= attrs.join(``).length+result.length > 55; | ||||||
|  | 		if(options.expaned || tooLong || attrs.length > 3) | ||||||
|  | 			result += `, {\n${nextIndent}${attrs.join(`,\n${nextIndent}`)},\n${indent}}`; | ||||||
|  | 		else | ||||||
|  | 			result += `, { ${attrs.join(", ")} }`; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Add children if any | ||||||
|  | 		if (children.length > 0) { | ||||||
|  | 			const chs= children.map(ch=> | ||||||
|  | 				Array.isArray(ch) ? ch.map(l=> nextIndent + l).join("\n") : | ||||||
|  | 				nextIndent + ch + ","); | ||||||
|  | 			result += `).append(\n${chs.join("\n")}\n${indent})`; | ||||||
|  | 		} else { | ||||||
|  | 			result += ")"; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return result; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return null; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function converter() { | ||||||
|  | 	// State for the converter | ||||||
|  | 	const htmlInput = S(examples[0].html); | ||||||
|  | 	const error = S(""); | ||||||
|  |  | ||||||
|  | 	const status = S(""); | ||||||
|  | 	const showStatus= msg => { | ||||||
|  | 		status.set(msg); | ||||||
|  | 		// Clear status after 3 seconds | ||||||
|  | 		setTimeout(() => status.set(""), 3000); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	// Options state | ||||||
|  | 	const options = { | ||||||
|  | 		styleAsObject: { | ||||||
|  | 			title: "Convert style to object", | ||||||
|  | 			value: S(true), | ||||||
|  | 		}, | ||||||
|  | 		dataAttrsAsCamel: { | ||||||
|  | 			title: "dataKey/ariaKey (or dataset/ariaset)", | ||||||
|  | 			value: S(true), | ||||||
|  | 		}, | ||||||
|  | 		indent: { | ||||||
|  | 			title: "Indentation (-1 for tabs)", | ||||||
|  | 			value: S("-1"), | ||||||
|  | 			type: "number", | ||||||
|  | 		}, | ||||||
|  | 		expaned: { | ||||||
|  | 			title: "Force multiline", | ||||||
|  | 			value: S(false), | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 	const getOptions = ()=> Object.fromEntries(Object.entries(options) | ||||||
|  | 		.map(([key, option]) => ([ | ||||||
|  | 			key, | ||||||
|  | 			option.value.get() | ||||||
|  | 		])) | ||||||
|  | 	); | ||||||
|  |  | ||||||
|  | 	// Update the dd<el> output when input or options change | ||||||
|  | 	const ddeOutput = S(() => { | ||||||
|  | 		try { | ||||||
|  | 			const result = convertHTMLtoDDE(htmlInput.get(), getOptions()); | ||||||
|  | 			error.set(""); | ||||||
|  | 			return result; | ||||||
|  | 		} catch (err) { | ||||||
|  | 			error.set(`Error: ${err.message}`); | ||||||
|  | 			return ""; | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	// Event handlers | ||||||
|  | 	const onConvert = on("submit", e => { | ||||||
|  | 		e.preventDefault(); | ||||||
|  | 		htmlInput.set(htmlInput.get(), true); | ||||||
|  | 		showStatus("Converted!"); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	const onCopy = on("click", async () => { | ||||||
|  | 		if (!ddeOutput.get()) return; | ||||||
|  |  | ||||||
|  | 		try { | ||||||
|  | 			await navigator.clipboard.writeText(ddeOutput.get()); | ||||||
|  | 			showStatus("Copied to clipboard!"); | ||||||
|  | 		} catch (err) { | ||||||
|  | 			error.set(`Could not copy: ${err.message}`); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 	const onClear = on("click", () => { | ||||||
|  | 		htmlInput.set(""); | ||||||
|  | 		showStatus("Input cleared"); | ||||||
|  | 	}); | ||||||
|  | 	const onExampleLoad = (example) => on("click", () => { | ||||||
|  | 		htmlInput.set(example.html); | ||||||
|  | 		showStatus(`Loaded "${example.name}" example`); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	const optionsElements = () => Object.entries(options) | ||||||
|  | 		.map(([key, option]) => | ||||||
|  | 			el("label", { className: "option-group" }).append( | ||||||
|  | 				option.type==="number" | ||||||
|  | 					? el("input", { | ||||||
|  | 						type: option.type || "checkbox", | ||||||
|  | 						name: key, | ||||||
|  | 						value: option.value.get(), | ||||||
|  | 						max: 10, | ||||||
|  | 					}, on("change", e => option.value.set(e.target.value))) | ||||||
|  | 					: el("input", { | ||||||
|  | 						type: option.type || "checkbox", | ||||||
|  | 						name: key, | ||||||
|  | 						checked: option.value.get(), | ||||||
|  | 					}, on("change", e => option.value.set(e.target.checked))), | ||||||
|  | 				option.title, | ||||||
|  | 			) | ||||||
|  | 	); | ||||||
|  | 	const exampleButtons = examples.map(example => | ||||||
|  | 		el("button", { | ||||||
|  | 			type: "button", | ||||||
|  | 			className: "secondary example-button" | ||||||
|  | 		}, onExampleLoad(example)).append(example.name) | ||||||
|  | 	); | ||||||
|  |  | ||||||
|  | 	return el("div", { id: "html-to-dde-converter" }).append( | ||||||
|  | 		el("h3", "HTML to dd<el> Converter"), | ||||||
|  | 		el("p", { className: "description" }).append( | ||||||
|  | 			"Convert HTML markup to dd<el> JavaScript code. Paste your HTML below or choose from an example." | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		el("form", { className: "converter-form" }, onConvert).append( | ||||||
|  | 			el("div", { className: "options" }).append(...optionsElements()), | ||||||
|  |  | ||||||
|  | 			el("div", { className: "examples-list" }).append( | ||||||
|  | 				el("label", "Examples: "), | ||||||
|  | 				...exampleButtons | ||||||
|  | 			), | ||||||
|  |  | ||||||
|  | 			el("div", { className: "editor-container" }).append( | ||||||
|  | 				el("div", { className: "input-group" }).append( | ||||||
|  | 					el("label", { htmlFor: "html-input" }).append( | ||||||
|  | 						"HTML Input", | ||||||
|  | 						el("div", { className: "button-group" }).append( | ||||||
|  | 							el("button", { | ||||||
|  | 								type: "button", | ||||||
|  | 								className: "secondary", | ||||||
|  | 								title: "Clear input" | ||||||
|  | 							}, onClear).append("Clear") | ||||||
|  | 						) | ||||||
|  | 					), | ||||||
|  | 					el("textarea", { | ||||||
|  | 						id: "html-input", | ||||||
|  | 						spellcheck: false, | ||||||
|  | 						value: htmlInput, | ||||||
|  | 						placeholder: "Paste your HTML here or choose an example", | ||||||
|  | 						oninput: e => htmlInput.set(e.target.value) | ||||||
|  | 					}) | ||||||
|  | 				), | ||||||
|  |  | ||||||
|  | 				el("div", { className: "output-group" }).append( | ||||||
|  | 					el("label", { htmlFor: "dde-output" }).append( | ||||||
|  | 						"dd<el> Output", | ||||||
|  | 						el("div", { className: "button-group" }).append( | ||||||
|  | 							el("button", { | ||||||
|  | 								textContent: "Copy", | ||||||
|  | 								type: "button", | ||||||
|  | 								className: "copy-button", | ||||||
|  | 								title: "Copy to clipboard", | ||||||
|  | 								disabled: S(() => !ddeOutput.get()) | ||||||
|  | 							}, onCopy) | ||||||
|  | 						) | ||||||
|  | 					), | ||||||
|  | 					el("textarea", { | ||||||
|  | 						id: "dde-output", | ||||||
|  | 						readonly: true, | ||||||
|  | 						spellcheck: false, | ||||||
|  | 						placeholder: "The converted dd<el> code will appear here", | ||||||
|  | 						value: S(() => ddeOutput.get() || "// Convert HTML to see results here") | ||||||
|  | 					}) | ||||||
|  | 				) | ||||||
|  | 			), | ||||||
|  |  | ||||||
|  | 			el("div", { className: "button-group" }).append( | ||||||
|  | 				S.el(error, error => !error ? el() : el("div", { className: "error" }).append(error)), | ||||||
|  | 				el("div", { className: "status", textContent: status }), | ||||||
|  | 				el("button", { type: "submit" }).append("Convert") | ||||||
|  | 			) | ||||||
|  | 		) | ||||||
|  | 	); | ||||||
|  | } | ||||||
| @@ -1,9 +1,8 @@ | |||||||
| import { styles } from "../ssr.js"; | import { page_id, styles } from "../ssr.js"; | ||||||
| const host= "."+example.name; | const host= "."+example.name; | ||||||
| styles.css` | styles.css` | ||||||
| ${host} { | ${host} { | ||||||
| 	grid-column: full-main; | 	grid-column: full-main; | ||||||
| 	width: calc(100% - .75em); |  | ||||||
| 	height: calc(4/6 * var(--body-max-width)); | 	height: calc(4/6 * var(--body-max-width)); | ||||||
| 	border-radius: var(--border-radius); | 	border-radius: var(--border-radius); | ||||||
| 	box-shadow: var(--shadow); | 	box-shadow: var(--shadow); | ||||||
| @@ -84,7 +83,6 @@ html[data-theme="light"] .cm-s-material .cm-error { color: #f44336 !important; } | |||||||
| @media (max-width: 767px) { | @media (max-width: 767px) { | ||||||
| 	${host} { | 	${host} { | ||||||
| 		height: 50vh; | 		height: 50vh; | ||||||
| 		max-width: 100%; |  | ||||||
| 	} | 	} | ||||||
| 	${host} main { | 	${host} main { | ||||||
| 		flex-grow: 1; | 		flex-grow: 1; | ||||||
| @@ -97,7 +95,7 @@ html[data-theme="light"] .cm-s-material .cm-error { color: #f44336 !important; } | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| ${host}[data-variant=big]{ | ${host}[data-variant=big]{ | ||||||
| 	height: 100vh; | 	height: 150vh; | ||||||
|  |  | ||||||
| 	main { | 	main { | ||||||
| 		flex-flow: column nowrap; | 		flex-flow: column nowrap; | ||||||
| @@ -121,9 +119,8 @@ 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", page_id }){ | export function example({ src, language= "js", variant= "normal" }){ | ||||||
| 	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";'); | ||||||
|   | |||||||
							
								
								
									
										375
									
								
								docs/components/examples/case-studies/data-dashboard.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										375
									
								
								docs/components/examples/case-studies/data-dashboard.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,375 @@ | |||||||
|  | /** | ||||||
|  |  * Case Study: Data Dashboard with Charts | ||||||
|  |  * | ||||||
|  |  * This example demonstrates: | ||||||
|  |  * - Integration with a third-party charting library | ||||||
|  |  * - Data fetching and state management | ||||||
|  |  * - Responsive layout design | ||||||
|  |  * - Multiple interactive components working together | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | import { el, on } from "deka-dom-el"; | ||||||
|  | import { S } from "deka-dom-el/signals"; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Data Dashboard Component with Chart Integration | ||||||
|  |  * @returns {HTMLElement} Dashboard element | ||||||
|  |  */ | ||||||
|  | export function DataDashboard() { | ||||||
|  | 	// Mock data for demonstration | ||||||
|  | 	const DATA = { | ||||||
|  | 		sales: [42, 58, 65, 49, 72, 85, 63, 70, 78, 89, 95, 86], | ||||||
|  | 		visitors: [1420, 1620, 1750, 1850, 2100, 2400, 2250, 2500, 2750, 2900, 3100, 3200], | ||||||
|  | 		conversion: [2.9, 3.5, 3.7, 2.6, 3.4, 3.5, 2.8, 2.8, 2.8, 3.1, 3.0, 2.7], | ||||||
|  | 		months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] | ||||||
|  | 	}; | ||||||
|  | 	const years = [2022, 2023, 2024]; | ||||||
|  | 	const dataTypes = [ | ||||||
|  | 		{ id: 'sales', label: 'Sales', unit: 'K' }, | ||||||
|  | 		{ id: 'visitors', label: 'Visitors', unit: '' }, | ||||||
|  | 		{ id: 'conversion', label: 'Conversion Rate', unit: '%' } | ||||||
|  | 	]; | ||||||
|  |  | ||||||
|  | 	// Filter options | ||||||
|  | 	const selectedYear = S(2024); | ||||||
|  | 	const onYearChange = on("change", e => { | ||||||
|  | 		selectedYear.set(parseInt(/** @type {HTMLSelectElement} */(e.target).value)); | ||||||
|  | 		loadData(); | ||||||
|  | 	}); | ||||||
|  | 	const selectedDataType = S(/** @type {'sales' | 'visitors' | 'conversion'} */ ('sales')); | ||||||
|  | 	const onDataTypeChange = on("click", e => { | ||||||
|  | 		const type = /** @type {'sales' | 'visitors' | 'conversion'} */( | ||||||
|  | 			/** @type {HTMLButtonElement} */(e.currentTarget).dataset.type); | ||||||
|  | 		selectedDataType.set(type); | ||||||
|  | 	}); | ||||||
|  | 	const currentDataType = S(() => dataTypes.find(type => type.id === selectedDataType.get())); | ||||||
|  | 	const selectedData = S(() => DATA[selectedDataType.get()]); | ||||||
|  |  | ||||||
|  | 	// Values based on filters | ||||||
|  | 	const totalValue = S(() => selectedData.get().reduce((sum, value) => sum + value, 0)); | ||||||
|  | 	const averageValue = S(() => { | ||||||
|  | 		const data = selectedData.get(); | ||||||
|  | 		return data.reduce((sum, value) => sum + value, 0) / data.length; | ||||||
|  | 	}); | ||||||
|  | 	const highestValue = S(() => Math.max(...selectedData.get())); | ||||||
|  |  | ||||||
|  | 	// Simulate data loading | ||||||
|  | 	const isLoading = S(false); | ||||||
|  | 	const error = S(null); | ||||||
|  | 	function loadData() { | ||||||
|  | 		isLoading.set(true); | ||||||
|  | 		error.set(null); | ||||||
|  |  | ||||||
|  | 		// Simulate API call | ||||||
|  | 		setTimeout(() => { | ||||||
|  | 			if (Math.random() > 0.9) { | ||||||
|  | 				// Simulate occasional error | ||||||
|  | 				error.set('Failed to load data. Please try again.'); | ||||||
|  | 			} | ||||||
|  | 			isLoading.set(false); | ||||||
|  | 		}, 800); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Reactive chart rendering | ||||||
|  | 	const chart = S(()=> { | ||||||
|  | 		const chart= el("canvas", { id: "chart-canvas", width: 800, height: 400 }); | ||||||
|  | 		const ctx = chart.getContext('2d'); | ||||||
|  | 		const data = selectedData.get(); | ||||||
|  | 		const months = DATA.months; | ||||||
|  | 		const width = chart.width; | ||||||
|  | 		const height = chart.height; | ||||||
|  | 		const maxValue = Math.max(...data) * 1.1; | ||||||
|  | 		const barWidth = width / data.length - 10; | ||||||
|  |  | ||||||
|  | 		// Clear canvas | ||||||
|  | 		ctx.clearRect(0, 0, width, height); | ||||||
|  |  | ||||||
|  | 		// Draw background grid | ||||||
|  | 		ctx.beginPath(); | ||||||
|  | 		ctx.strokeStyle = '#f0f0f0'; | ||||||
|  | 		ctx.lineWidth = 1; | ||||||
|  | 		for(let i = 0; i < 5; i++) { | ||||||
|  | 			const y = height - (height * (i / 5)) - 30; | ||||||
|  | 			ctx.moveTo(50, y); | ||||||
|  | 			ctx.lineTo(width - 20, y); | ||||||
|  |  | ||||||
|  | 			// Draw grid labels | ||||||
|  | 			ctx.fillStyle = '#999'; | ||||||
|  | 			ctx.font = '12px Arial'; | ||||||
|  | 			ctx.fillText(Math.round(maxValue * (i / 5)).toString(), 20, y + 5); | ||||||
|  | 		} | ||||||
|  | 		ctx.stroke(); | ||||||
|  |  | ||||||
|  | 		// Draw bars | ||||||
|  | 		data.forEach((value, index) => { | ||||||
|  | 			const x = index * (barWidth + 10) + 60; | ||||||
|  | 			const barHeight = (value / maxValue) * (height - 60); | ||||||
|  |  | ||||||
|  | 			// Bar | ||||||
|  | 			ctx.fillStyle = '#4a90e2'; | ||||||
|  | 			ctx.fillRect(x, height - barHeight - 30, barWidth, barHeight); | ||||||
|  |  | ||||||
|  | 			// Month label | ||||||
|  | 			ctx.fillStyle = '#666'; | ||||||
|  | 			ctx.font = '12px Arial'; | ||||||
|  | 			ctx.fillText(months[index], x + barWidth/2 - 10, height - 10); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		// Chart title | ||||||
|  | 		ctx.fillStyle = '#333'; | ||||||
|  | 		ctx.font = 'bold 14px Arial'; | ||||||
|  | 		ctx.fillText(`${currentDataType.get().label} (${selectedYear.get()})`, width/2 - 80, 20); | ||||||
|  | 		return chart; | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	return el("div", { className: "dashboard" }).append( | ||||||
|  | 		el("header", { className: "dashboard-header" }).append( | ||||||
|  | 			el("h1", "Sales Performance Dashboard"), | ||||||
|  | 			el("div", { className: "year-filter" }).append( | ||||||
|  | 				el("label", { htmlFor: "yearSelect", textContent: "Select Year:" }), | ||||||
|  | 				el("select", { id: "yearSelect" }, | ||||||
|  | 					on.defer(el=> el.value = selectedYear.get().toString()), | ||||||
|  | 					onYearChange | ||||||
|  | 				).append( | ||||||
|  | 					...years.map(year => el("option", { value: year, textContent: year })) | ||||||
|  | 				) | ||||||
|  | 			) | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		S.el(error, errorMsg => !errorMsg | ||||||
|  | 			? el() | ||||||
|  | 			: el("div", { className: "error-message" }).append( | ||||||
|  | 					el("p", errorMsg), | ||||||
|  | 					el("button", { textContent: "Retry", type: "button" }, on("click", loadData)), | ||||||
|  | 				), | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		S.el(isLoading, loading => !loading | ||||||
|  | 			? el() | ||||||
|  | 			: el("div", { className: "loading-spinner" }) | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		// Main dashboard content | ||||||
|  | 		el("div", { className: "dashboard-content" }).append( | ||||||
|  | 			// Metrics cards | ||||||
|  | 			el("div", { className: "metrics-container" }).append( | ||||||
|  | 				el("div", { className: "metric-card" }).append( | ||||||
|  | 					el("h3", "Total"), | ||||||
|  | 					el("#text", S(() => `${totalValue.get().toLocaleString()}${currentDataType.get().unit}`)), | ||||||
|  | 				), | ||||||
|  | 				el("div", { className: "metric-card" }).append( | ||||||
|  | 					el("h3", "Average"), | ||||||
|  | 					el("#text", S(() => `${averageValue.get().toFixed(1)}${currentDataType.get().unit}`)), | ||||||
|  | 				), | ||||||
|  | 				el("div", { className: "metric-card" }).append( | ||||||
|  | 					el("h3", "Highest"), | ||||||
|  | 					el("#text", S(() => `${highestValue.get()}${currentDataType.get().unit}`)), | ||||||
|  | 				), | ||||||
|  | 			), | ||||||
|  |  | ||||||
|  | 			// Data type selection tabs | ||||||
|  | 			el("div", { className: "data-type-tabs" }).append( | ||||||
|  | 				...dataTypes.map(type => | ||||||
|  | 					el("button", { | ||||||
|  | 						type: "button", | ||||||
|  | 						className: S(() => selectedDataType.get() === type.id ? 'active' : ''), | ||||||
|  | 						dataType: type.id, | ||||||
|  | 						textContent: type.label | ||||||
|  | 					}, onDataTypeChange) | ||||||
|  | 				) | ||||||
|  | 			), | ||||||
|  |  | ||||||
|  | 			// Chart container | ||||||
|  | 			el("div", { className: "chart-container" }).append( | ||||||
|  | 				S.el(chart, chart => chart) | ||||||
|  | 			) | ||||||
|  | 		), | ||||||
|  | 	); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Render the component | ||||||
|  | document.body.append( | ||||||
|  | 	el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append( | ||||||
|  | 		el(DataDashboard) | ||||||
|  | 	), | ||||||
|  | 	el("style", ` | ||||||
|  | 			.dashboard { | ||||||
|  | 				font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | ||||||
|  | 				max-width: 1000px; | ||||||
|  | 				margin: 0 auto; | ||||||
|  | 				padding: 1rem; | ||||||
|  | 				background: #fff; | ||||||
|  | 				box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); | ||||||
|  | 				border-radius: 8px; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.dashboard-header { | ||||||
|  | 				display: flex; | ||||||
|  | 				justify-content: space-between; | ||||||
|  | 				align-items: center; | ||||||
|  | 				margin-bottom: 1.5rem; | ||||||
|  | 				padding-bottom: 1rem; | ||||||
|  | 				border-bottom: 1px solid #eee; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.dashboard-header h1 { | ||||||
|  | 				font-size: 1.5rem; | ||||||
|  | 				margin: 0; | ||||||
|  | 				color: #333; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.year-filter { | ||||||
|  | 				display: flex; | ||||||
|  | 				align-items: center; | ||||||
|  | 				gap: 0.5rem; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.year-filter select { | ||||||
|  | 				padding: 0.5rem; | ||||||
|  | 				border: 1px solid #ddd; | ||||||
|  | 				border-radius: 4px; | ||||||
|  | 				font-size: 1rem; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.metrics-container { | ||||||
|  | 				display: grid; | ||||||
|  | 				grid-template-columns: repeat(3, 1fr); | ||||||
|  | 				gap: 1rem; | ||||||
|  | 				margin-bottom: 1.5rem; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.metric-card { | ||||||
|  | 				background: #f9f9f9; | ||||||
|  | 				border-radius: 8px; | ||||||
|  | 				padding: 1rem; | ||||||
|  | 				text-align: center; | ||||||
|  | 				transition: transform 0.2s ease; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.metric-card:hover { | ||||||
|  | 				transform: translateY(-5px); | ||||||
|  | 				box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.metric-card h3 { | ||||||
|  | 				margin-top: 0; | ||||||
|  | 				color: #666; | ||||||
|  | 				font-size: 0.9rem; | ||||||
|  | 				margin-bottom: 0.5rem; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.metric-card p { | ||||||
|  | 				font-size: 1.5rem; | ||||||
|  | 				font-weight: bold; | ||||||
|  | 				color: #333; | ||||||
|  | 				margin: 0; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.data-type-tabs { | ||||||
|  | 				display: flex; | ||||||
|  | 				border-bottom: 1px solid #eee; | ||||||
|  | 				margin-bottom: 1.5rem; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.data-type-tabs button { | ||||||
|  | 				background: none; | ||||||
|  | 				border: none; | ||||||
|  | 				padding: 0.75rem 1.5rem; | ||||||
|  | 				font-size: 1rem; | ||||||
|  | 				cursor: pointer; | ||||||
|  | 				color: #666; | ||||||
|  | 				position: relative; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.data-type-tabs button.active { | ||||||
|  | 				color: #4a90e2; | ||||||
|  | 				font-weight: 500; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.data-type-tabs button.active::after { | ||||||
|  | 				content: ''; | ||||||
|  | 				position: absolute; | ||||||
|  | 				bottom: -1px; | ||||||
|  | 				left: 0; | ||||||
|  | 				width: 100%; | ||||||
|  | 				height: 3px; | ||||||
|  | 				background: #4a90e2; | ||||||
|  | 				border-radius: 3px 3px 0 0; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.chart-container { | ||||||
|  | 				background: #fff; | ||||||
|  | 				border-radius: 8px; | ||||||
|  | 				padding: 1rem; | ||||||
|  | 				margin-bottom: 1.5rem; | ||||||
|  | 				box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); | ||||||
|  | 				overflow: auto; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.loading-spinner { | ||||||
|  | 				display: flex; | ||||||
|  | 				justify-content: center; | ||||||
|  | 				align-items: center; | ||||||
|  | 				height: 100px; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.loading-spinner::before { | ||||||
|  | 				content: ''; | ||||||
|  | 				width: 40px; | ||||||
|  | 				height: 40px; | ||||||
|  | 				border: 4px solid #f3f3f3; | ||||||
|  | 				border-top: 4px solid #4a90e2; | ||||||
|  | 				border-radius: 50%; | ||||||
|  | 				animation: spin 1s linear infinite; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			@keyframes spin { | ||||||
|  | 				0% { transform: rotate(0deg); } | ||||||
|  | 				100% { transform: rotate(360deg); } | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.error-message { | ||||||
|  | 				background: #ffecec; | ||||||
|  | 				color: #e74c3c; | ||||||
|  | 				padding: 1rem; | ||||||
|  | 				border-radius: 4px; | ||||||
|  | 				margin-bottom: 1.5rem; | ||||||
|  | 				display: flex; | ||||||
|  | 				justify-content: space-between; | ||||||
|  | 				align-items: center; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.error-message p { | ||||||
|  | 				margin: 0; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.error-message button { | ||||||
|  | 				background: #e74c3c; | ||||||
|  | 				color: white; | ||||||
|  | 				border: none; | ||||||
|  | 				border-radius: 4px; | ||||||
|  | 				padding: 0.5rem 1rem; | ||||||
|  | 				cursor: pointer; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			@media (max-width: 768px) { | ||||||
|  | 				.metrics-container { | ||||||
|  | 					grid-template-columns: 1fr; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				.dashboard-header { | ||||||
|  | 					flex-direction: column; | ||||||
|  | 					align-items: flex-start; | ||||||
|  | 					gap: 1rem; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				.year-filter { | ||||||
|  | 					width: 100%; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				.year-filter select { | ||||||
|  | 					flex-grow: 1; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		`) | ||||||
|  | ); | ||||||
							
								
								
									
										412
									
								
								docs/components/examples/case-studies/image-gallery.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										412
									
								
								docs/components/examples/case-studies/image-gallery.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,412 @@ | |||||||
|  | /** | ||||||
|  |  * Case Study: Interactive Image Gallery | ||||||
|  |  * | ||||||
|  |  * This example demonstrates: | ||||||
|  |  * - Dynamic loading of content | ||||||
|  |  * - Lightbox functionality | ||||||
|  |  * - Animation handling | ||||||
|  |  * - Keyboard and gesture navigation | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | import { el, memo, on } from "deka-dom-el"; | ||||||
|  | import { S } from "deka-dom-el/signals"; | ||||||
|  |  | ||||||
|  | // Sample image data | ||||||
|  | const imagesSample = (url=> [ | ||||||
|  | 	{ id: 1, src: url+'nature', alt: 'Nature', title: 'Beautiful Landscape' }, | ||||||
|  | 	{ id: 2, src: url+'places', alt: 'City', title: 'Urban Architecture' }, | ||||||
|  | 	{ id: 3, src: url+'people', alt: 'People', title: 'Street Photography' }, | ||||||
|  | 	{ id: 4, src: url+'food', alt: 'Food', title: 'Culinary Delights' }, | ||||||
|  | 	{ id: 5, src: url+'animals', alt: 'Animals', title: 'Wildlife' }, | ||||||
|  | 	{ id: 6, src: url+'travel', alt: 'Travel', title: 'Adventure Awaits' }, | ||||||
|  | 	{ id: 7, src: url+'computer', alt: 'Technology', title: 'Modern Tech' }, | ||||||
|  | 	{ id: 8, src: url+'music', alt: 'Art', title: 'Creative Expression' }, | ||||||
|  | ])('https://api.algobook.info/v1/randomimage?category='); | ||||||
|  | /** | ||||||
|  |  * Interactive Image Gallery Component | ||||||
|  |  * @returns {HTMLElement} Gallery element | ||||||
|  |  */ | ||||||
|  | export function ImageGallery(images= imagesSample) { | ||||||
|  | 	const filterTag = S('all'); | ||||||
|  | 	const imagesToDisplay = S(() => { | ||||||
|  | 		const tag = filterTag.get(); | ||||||
|  | 		if (tag === 'all') return images; | ||||||
|  | 		else return images.filter(img => img.alt.toLowerCase() === tag); | ||||||
|  | 	}) | ||||||
|  | 	const onFilterChange = tag => on("click", () => { | ||||||
|  | 		filterTag.set(tag); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	// Lightbox | ||||||
|  | 	const selectedImageId = S(null); | ||||||
|  | 	const selectedImage = S(() => { | ||||||
|  | 		const id = selectedImageId.get(); | ||||||
|  | 		return id ? images.find(img => img.id === id) : null; | ||||||
|  | 	}); | ||||||
|  | 	const isLightboxOpen = S(() => selectedImage.get() !== null); | ||||||
|  | 	const onImageClick = id => on("click", () => { | ||||||
|  | 		selectedImageId.set(id); | ||||||
|  | 		document.body.style.overflow = 'hidden'; // Prevent scrolling when lightbox is open | ||||||
|  |  | ||||||
|  | 		// Add keyboard event listeners when lightbox opens | ||||||
|  | 		document.addEventListener('keydown', handleKeyDown); | ||||||
|  | 	}); | ||||||
|  | 	const closeLightbox = () => { | ||||||
|  | 		selectedImageId.set(null); | ||||||
|  | 		document.body.style.overflow = ''; // Restore scrolling | ||||||
|  |  | ||||||
|  | 		// Remove keyboard event listeners when lightbox closes | ||||||
|  | 		document.removeEventListener('keydown', handleKeyDown); | ||||||
|  | 	}; | ||||||
|  | 	const onPrevImage = e => { | ||||||
|  | 		e.stopPropagation(); // Prevent closing the lightbox | ||||||
|  | 		const images = imagesToDisplay.get(); | ||||||
|  | 		const currentId = selectedImageId.get(); | ||||||
|  | 		const currentIndex = images.findIndex(img => img.id === currentId); | ||||||
|  | 		const prevIndex = (currentIndex - 1 + images.length) % images.length; | ||||||
|  | 		selectedImageId.set(images[prevIndex].id); | ||||||
|  | 	}; | ||||||
|  | 	const onNextImage = e => { | ||||||
|  | 		e.stopPropagation(); // Prevent closing the lightbox | ||||||
|  | 		const images = imagesToDisplay.get(); | ||||||
|  | 		const currentId = selectedImageId.get(); | ||||||
|  | 		const currentIndex = images.findIndex(img => img.id === currentId); | ||||||
|  | 		const nextIndex = (currentIndex + 1) % images.length; | ||||||
|  | 		selectedImageId.set(images[nextIndex].id); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	// Keyboard navigation handler | ||||||
|  | 	function handleKeyDown(e) { | ||||||
|  | 		switch(e.key) { | ||||||
|  | 			case 'Escape': | ||||||
|  | 				closeLightbox(); | ||||||
|  | 				break; | ||||||
|  | 			case 'ArrowLeft': | ||||||
|  | 				onPrevImage(e); | ||||||
|  | 				break; | ||||||
|  | 			case 'ArrowRight': | ||||||
|  | 				onNextImage(e); | ||||||
|  | 				break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Build the gallery UI | ||||||
|  | 	return el("div", { className: "gallery-container" }).append( | ||||||
|  | 		// Gallery header | ||||||
|  | 		el("header", { className: "gallery-header" }).append( | ||||||
|  | 			el("h1", "Interactive Image Gallery"), | ||||||
|  | 			el("p", "Click on any image to view it in the lightbox. Use arrow keys for navigation.") | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		// Filter options | ||||||
|  | 		el("div", { className: "gallery-filters" }).append( | ||||||
|  | 			el("button", { | ||||||
|  | 				classList: { active: S(() => filterTag.get() === 'all') }, | ||||||
|  | 				textContent: "All" | ||||||
|  | 			}, onFilterChange('all')), | ||||||
|  | 			el("button", { | ||||||
|  | 				classList: { active: S(() => filterTag.get() === 'nature') }, | ||||||
|  | 				textContent: "Nature" | ||||||
|  | 			}, onFilterChange('nature')), | ||||||
|  | 			el("button", { | ||||||
|  | 				classList: { active: S(() => filterTag.get() === 'urban') }, | ||||||
|  | 				textContent: "Urban" | ||||||
|  | 			}, onFilterChange('urban')), | ||||||
|  | 			el("button", { | ||||||
|  | 				classList: { active: S(() => filterTag.get() === 'people') }, | ||||||
|  | 				textContent: "People" | ||||||
|  | 			}, onFilterChange('people')) | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		// Image grid | ||||||
|  | 		el("div", { className: "gallery-grid" }).append( | ||||||
|  | 			S.el(imagesToDisplay, images => | ||||||
|  | 				images.map(image => | ||||||
|  | 					memo(image.id, ()=> | ||||||
|  | 						el("div", { | ||||||
|  | 							className: "gallery-item", | ||||||
|  | 							dataTag: image.alt.toLowerCase() | ||||||
|  | 						}, onImageClick(image.id)).append( | ||||||
|  | 							el("img", { | ||||||
|  | 								src: image.src, | ||||||
|  | 								alt: image.alt, | ||||||
|  | 								loading: "lazy" | ||||||
|  | 							}), | ||||||
|  | 							el("div", { className: "gallery-item-caption" }).append( | ||||||
|  | 								el("h3", image.title), | ||||||
|  | 								el("p", image.alt) | ||||||
|  | 							) | ||||||
|  | 						) | ||||||
|  | 					) | ||||||
|  | 				) | ||||||
|  | 			) | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		// Lightbox (only shown when an image is selected) | ||||||
|  | 		S.el(isLightboxOpen, open => !open | ||||||
|  | 			? el() | ||||||
|  | 			: el("div", { className: "lightbox-overlay" }, on("click", closeLightbox)).append( | ||||||
|  | 				el("div", { | ||||||
|  | 					className: "lightbox-content", | ||||||
|  | 					onClick: e => e.stopPropagation() // Prevent closing when clicking inside | ||||||
|  | 				}).append( | ||||||
|  | 					el("button", { | ||||||
|  | 						className: "lightbox-close-btn", | ||||||
|  | 						ariaLabel: "Close lightbox", | ||||||
|  | 					}, on("click", closeLightbox)).append("×"), | ||||||
|  |  | ||||||
|  | 					el("button", { | ||||||
|  | 						className: "lightbox-prev-btn", | ||||||
|  | 						ariaLabel: "Previous image", | ||||||
|  | 					}, on("click", onPrevImage)).append("❮"), | ||||||
|  |  | ||||||
|  | 					el("button", { | ||||||
|  | 						className: "lightbox-next-btn", | ||||||
|  | 						ariaLabel: "Next image", | ||||||
|  | 					}, on("click", onNextImage)).append("❯"), | ||||||
|  |  | ||||||
|  | 					S.el(selectedImage, img => !img | ||||||
|  | 						? el() | ||||||
|  | 						: el("div", { className: "lightbox-image-container" }).append( | ||||||
|  | 							el("img", { | ||||||
|  | 								src: img.src, | ||||||
|  | 								alt: img.alt, | ||||||
|  | 								className: "lightbox-image", | ||||||
|  | 							}), | ||||||
|  | 							el("div", { className: "lightbox-caption" }).append( | ||||||
|  | 								el("h2", img.title), | ||||||
|  | 								el("p", img.alt), | ||||||
|  | 							), | ||||||
|  | 						) | ||||||
|  | 					), | ||||||
|  | 				), | ||||||
|  | 			), | ||||||
|  | 		), | ||||||
|  | 	); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Render the component | ||||||
|  | document.body.append( | ||||||
|  | 	el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append( | ||||||
|  | 		el(ImageGallery) | ||||||
|  | 	), | ||||||
|  | 	el("style", ` | ||||||
|  | 		.gallery-container { | ||||||
|  | 			font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | ||||||
|  | 			max-width: 1200px; | ||||||
|  | 			margin: 0 auto; | ||||||
|  | 			padding: 2rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.gallery-header { | ||||||
|  | 			text-align: center; | ||||||
|  | 			margin-bottom: 2rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.gallery-header h1 { | ||||||
|  | 			margin-bottom: 0.5rem; | ||||||
|  | 			color: #333; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.gallery-header p { | ||||||
|  | 			color: #666; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.gallery-filters { | ||||||
|  | 			display: flex; | ||||||
|  | 			justify-content: center; | ||||||
|  | 			margin-bottom: 2rem; | ||||||
|  | 			flex-wrap: wrap; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.gallery-filters button { | ||||||
|  | 			background: none; | ||||||
|  | 			border: none; | ||||||
|  | 			padding: 0.5rem 1.5rem; | ||||||
|  | 			margin: 0 0.5rem; | ||||||
|  | 			font-size: 1rem; | ||||||
|  | 			cursor: pointer; | ||||||
|  | 			border-radius: 30px; | ||||||
|  | 			transition: all 0.3s ease; | ||||||
|  | 			color: #555; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.gallery-filters button:hover { | ||||||
|  | 			background: #f0f0f0; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.gallery-filters button.active { | ||||||
|  | 			background: #4a90e2; | ||||||
|  | 			color: white; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.gallery-grid { | ||||||
|  | 			display: grid; | ||||||
|  | 			grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); | ||||||
|  | 			gap: 1.5rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.gallery-item { | ||||||
|  | 			position: relative; | ||||||
|  | 			border-radius: 8px; | ||||||
|  | 			overflow: hidden; | ||||||
|  | 			box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); | ||||||
|  | 			transition: transform 0.3s ease, box-shadow 0.3s ease; | ||||||
|  | 			cursor: pointer; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.gallery-item:hover { | ||||||
|  | 			transform: translateY(-5px); | ||||||
|  | 			box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.gallery-item img { | ||||||
|  | 			width: 100%; | ||||||
|  | 			height: 200px; | ||||||
|  | 			object-fit: cover; | ||||||
|  | 			display: block; | ||||||
|  | 			transition: transform 0.5s ease; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.gallery-item:hover img { | ||||||
|  | 			transform: scale(1.05); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.gallery-item-caption { | ||||||
|  | 			position: absolute; | ||||||
|  | 			bottom: 0; | ||||||
|  | 			left: 0; | ||||||
|  | 			right: 0; | ||||||
|  | 			background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent); | ||||||
|  | 			color: white; | ||||||
|  | 			padding: 1rem; | ||||||
|  | 			transform: translateY(100%); | ||||||
|  | 			transition: transform 0.3s ease; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.gallery-item:hover .gallery-item-caption { | ||||||
|  | 			transform: translateY(0); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.gallery-item-caption h3 { | ||||||
|  | 			margin: 0 0 0.5rem; | ||||||
|  | 			font-size: 1.2rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.gallery-item-caption p { | ||||||
|  | 			margin: 0; | ||||||
|  | 			font-size: 0.9rem; | ||||||
|  | 			opacity: 0.8; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/* Lightbox styles */ | ||||||
|  | 		.lightbox-overlay { | ||||||
|  | 			position: fixed; | ||||||
|  | 			top: 0; | ||||||
|  | 			left: 0; | ||||||
|  | 			right: 0; | ||||||
|  | 			bottom: 0; | ||||||
|  | 			background: rgba(0, 0, 0, 0.9); | ||||||
|  | 			display: flex; | ||||||
|  | 			justify-content: center; | ||||||
|  | 			align-items: center; | ||||||
|  | 			z-index: 1000; | ||||||
|  | 			padding: 2rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.lightbox-content { | ||||||
|  | 			position: relative; | ||||||
|  | 			max-width: 90%; | ||||||
|  | 			max-height: 90%; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.lightbox-image-container { | ||||||
|  | 			overflow: hidden; | ||||||
|  | 			border-radius: 4px; | ||||||
|  | 			box-shadow: 0 0 30px rgba(0, 0, 0, 0.5); | ||||||
|  | 			background: #000; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.lightbox-image { | ||||||
|  | 			max-width: 100%; | ||||||
|  | 			max-height: 80vh; | ||||||
|  | 			display: block; | ||||||
|  | 			margin: 0 auto; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.lightbox-caption { | ||||||
|  | 			background: #222; | ||||||
|  | 			color: white; | ||||||
|  | 			padding: 1rem; | ||||||
|  | 			text-align: center; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.lightbox-caption h2 { | ||||||
|  | 			margin: 0 0 0.5rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.lightbox-caption p { | ||||||
|  | 			margin: 0; | ||||||
|  | 			opacity: 0.8; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.lightbox-close-btn, | ||||||
|  | 		.lightbox-prev-btn, | ||||||
|  | 		.lightbox-next-btn { | ||||||
|  | 			background: rgba(0, 0, 0, 0.5); | ||||||
|  | 			color: white; | ||||||
|  | 			border: none; | ||||||
|  | 			font-size: 1.5rem; | ||||||
|  | 			width: 50px; | ||||||
|  | 			height: 50px; | ||||||
|  | 			border-radius: 50%; | ||||||
|  | 			display: flex; | ||||||
|  | 			justify-content: center; | ||||||
|  | 			align-items: center; | ||||||
|  | 			cursor: pointer; | ||||||
|  | 			transition: background 0.3s ease; | ||||||
|  | 			position: absolute; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.lightbox-close-btn:hover, | ||||||
|  | 		.lightbox-prev-btn:hover, | ||||||
|  | 		.lightbox-next-btn:hover { | ||||||
|  | 			background: rgba(0, 0, 0, 0.8); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.lightbox-close-btn { | ||||||
|  | 			top: -25px; | ||||||
|  | 			right: -25px; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.lightbox-prev-btn { | ||||||
|  | 			left: -25px; | ||||||
|  | 			top: 50%; | ||||||
|  | 			transform: translateY(-50%); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.lightbox-next-btn { | ||||||
|  | 			right: -25px; | ||||||
|  | 			top: 50%; | ||||||
|  | 			transform: translateY(-50%); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		@media (max-width: 768px) { | ||||||
|  | 			.gallery-container { | ||||||
|  | 				padding: 1rem; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.gallery-grid { | ||||||
|  | 				grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); | ||||||
|  | 				gap: 1rem; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.lightbox-prev-btn, | ||||||
|  | 			.lightbox-next-btn { | ||||||
|  | 				width: 40px; | ||||||
|  | 				height: 40px; | ||||||
|  | 				font-size: 1.2rem; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	`) | ||||||
|  | ); | ||||||
							
								
								
									
										339
									
								
								docs/components/examples/case-studies/interactive-form.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										339
									
								
								docs/components/examples/case-studies/interactive-form.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,339 @@ | |||||||
|  | /** | ||||||
|  |  * Case Study: Interactive Form with Validation | ||||||
|  |  * | ||||||
|  |  * This example demonstrates: | ||||||
|  |  * - Form handling with real-time validation | ||||||
|  |  * - Reactive UI updates based on input state | ||||||
|  |  * - Complex form state management | ||||||
|  |  * - Clean separation of concerns (data, validation, UI) | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | import { dispatchEvent, el, on, scope } from "deka-dom-el"; | ||||||
|  | import { S } from "deka-dom-el/signals"; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @typedef {Object} FormState | ||||||
|  |  * @property {string} name | ||||||
|  |  * @property {string} email | ||||||
|  |  * @property {string} password | ||||||
|  |  * @property {string} confirmPassword | ||||||
|  |  * @property {boolean} agreedToTerms | ||||||
|  |  * */ | ||||||
|  | /** | ||||||
|  |  * Interactive Form with Validation Component | ||||||
|  |  * @returns {HTMLElement} Form element | ||||||
|  |  */ | ||||||
|  | export function InteractiveForm() { | ||||||
|  | 	const submitted = S(false); | ||||||
|  | 	/** @type {FormState|null} */ | ||||||
|  | 	let formState = null; | ||||||
|  | 	/** @param {CustomEvent<FormState>} event */ | ||||||
|  | 	const onSubmit = ({ detail }) => { | ||||||
|  | 		submitted.set(true); | ||||||
|  | 		formState = detail; | ||||||
|  | 	}; | ||||||
|  | 	const onAnotherAccount = () => { | ||||||
|  | 		submitted.set(false) | ||||||
|  | 		formState = null; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	return el("div", { className: "form-container" }).append( | ||||||
|  | 		S.el(submitted, s => s | ||||||
|  | 			? el("div", { className: "success-message" }).append( | ||||||
|  | 				el("h3", "Thank you for registering!"), | ||||||
|  | 				el("p", `Welcome, ${formState.name}! Your account has been created successfully.`), | ||||||
|  | 				el("button", { textContent: "Register another account", type: "button" }, | ||||||
|  | 					on("click", onAnotherAccount) | ||||||
|  | 				), | ||||||
|  | 			) | ||||||
|  | 			: el(Form, { initial: formState }, on("form:submit", onSubmit)) | ||||||
|  | 		) | ||||||
|  | 	); | ||||||
|  | } | ||||||
|  | /** | ||||||
|  |  * Form Component | ||||||
|  |  * @type {(props: { initial: FormState | null }) => HTMLElement} | ||||||
|  |  * */ | ||||||
|  | export function Form({ initial }) { | ||||||
|  | 	const { host }= scope; | ||||||
|  | 	// Form state management | ||||||
|  | 	const formState = S(initial || { | ||||||
|  | 		name: '', | ||||||
|  | 		email: '', | ||||||
|  | 		password: '', | ||||||
|  | 		confirmPassword: '', | ||||||
|  | 		agreedToTerms: false | ||||||
|  | 	}, { | ||||||
|  | 		/** | ||||||
|  | 		 * @template {keyof FormState} K | ||||||
|  | 		 * @param {K} key | ||||||
|  | 		 * @param {FormState[K]} value | ||||||
|  | 		 * */ | ||||||
|  | 		update(key, value) { | ||||||
|  | 			this.value[key] = value; | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 	/** | ||||||
|  | 	 * Event handler for input events | ||||||
|  | 	 * @param {"value"|"checked"} prop | ||||||
|  | 	 * @returns {(ev: Event) => void} | ||||||
|  | 	 * */ | ||||||
|  | 	const onChange= prop => ev => { | ||||||
|  | 		const input = /** @type {HTMLInputElement} */(ev.target); | ||||||
|  | 		S.action(formState, "update", /** @type {keyof FormState} */(input.id), input[prop]); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	// Form validate state | ||||||
|  | 	const nameValid = S(() => formState.get().name.length >= 3); | ||||||
|  | 	const emailValid = S(() => { | ||||||
|  | 		const email = formState.get().email; | ||||||
|  | 		return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); | ||||||
|  | 	}); | ||||||
|  | 	const passwordValid = S(() => { | ||||||
|  | 		const password = formState.get().password; | ||||||
|  | 		return password.length >= 8 && /[A-Z]/.test(password) && /[0-9]/.test(password); | ||||||
|  | 	}); | ||||||
|  | 	const passwordsMatch = S(() => { | ||||||
|  | 		const { password, confirmPassword } = formState.get(); | ||||||
|  | 		return password === confirmPassword && confirmPassword !== ''; | ||||||
|  | 	}); | ||||||
|  | 	const termsAgreed = S(() => formState.get().agreedToTerms); | ||||||
|  | 	const formValid = S(() => | ||||||
|  | 		nameValid.get() && | ||||||
|  | 		emailValid.get() && | ||||||
|  | 		passwordValid.get() && | ||||||
|  | 		passwordsMatch.get() && | ||||||
|  | 		termsAgreed.get() | ||||||
|  | 	); | ||||||
|  |  | ||||||
|  | 	const dispatcSubmit = dispatchEvent("form:submit", host); | ||||||
|  | 	const onSubmit = on("submit", e => { | ||||||
|  | 		e.preventDefault(); | ||||||
|  | 		if (!formValid.get()) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		dispatcSubmit(formState.get()); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	// Component UI | ||||||
|  | 	return el("form", { className: "registration-form" }, onSubmit).append( | ||||||
|  | 		el("h2", "Create an Account"), | ||||||
|  |  | ||||||
|  | 		// Name field | ||||||
|  | 		el("div", { classList: { | ||||||
|  | 			"form-group": true, | ||||||
|  | 			valid: nameValid, | ||||||
|  | 			invalid: S(()=> !nameValid.get() && formState.get().name) | ||||||
|  | 		}}).append( | ||||||
|  | 			el("label", { htmlFor: "name", textContent: "Full Name" }), | ||||||
|  | 			el("input", { | ||||||
|  | 				id: "name", | ||||||
|  | 				type: "text", | ||||||
|  | 				value: formState.get().name, | ||||||
|  | 				placeholder: "Enter your full name" | ||||||
|  | 			}, on("input", onChange("value"))), | ||||||
|  | 			el("div", { className: "validation-message", textContent: "Name must be at least 3 characters long" }), | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		// Email field | ||||||
|  | 		el("div", { classList: { | ||||||
|  | 			"form-group": true, | ||||||
|  | 			valid: emailValid, | ||||||
|  | 			invalid: S(()=> !emailValid.get() && formState.get().email) | ||||||
|  | 		}}).append( | ||||||
|  | 			el("label", { htmlFor: "email", textContent: "Email Address" }), | ||||||
|  | 			el("input", { | ||||||
|  | 				id: "email", | ||||||
|  | 				type: "email", | ||||||
|  | 				value: formState.get().email, | ||||||
|  | 				placeholder: "Enter your email address" | ||||||
|  | 			}, on("input", onChange("value"))), | ||||||
|  | 			el("div", { className: "validation-message", textContent: "Please enter a valid email address" }) | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		// Password field | ||||||
|  | 		el("div", { classList: { | ||||||
|  | 			"form-group": true, | ||||||
|  | 			valid: passwordValid, | ||||||
|  | 			invalid: S(()=> !passwordValid.get() && formState.get().password) | ||||||
|  | 		}}).append( | ||||||
|  | 			el("label", { htmlFor: "password", textContent: "Password" }), | ||||||
|  | 			el("input", { | ||||||
|  | 				id: "password", | ||||||
|  | 				type: "password", | ||||||
|  | 				value: formState.get().password, | ||||||
|  | 				placeholder: "Create a password" | ||||||
|  | 			}, on("input", onChange("value"))), | ||||||
|  | 			el("div", { | ||||||
|  | 				className: "validation-message", | ||||||
|  | 				textContent: "Password must be at least 8 characters with at least one uppercase letter and one number", | ||||||
|  | 			}), | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		// Confirm password field | ||||||
|  | 		el("div", { classList: { | ||||||
|  | 			"form-group": true, | ||||||
|  | 			valid: passwordsMatch, | ||||||
|  | 			invalid: S(()=> !passwordsMatch.get() && formState.get().confirmPassword) | ||||||
|  | 		}}).append( | ||||||
|  | 			el("label", { htmlFor: "confirmPassword", textContent: "Confirm Password" }), | ||||||
|  | 			el("input", { | ||||||
|  | 				id: "confirmPassword", | ||||||
|  | 				type: "password", | ||||||
|  | 				value: formState.get().confirmPassword, | ||||||
|  | 				placeholder: "Confirm your password" | ||||||
|  | 			}, on("input", onChange("value"))), | ||||||
|  | 			el("div", { className: "validation-message", textContent: "Passwords must match" }), | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		// Terms agreement | ||||||
|  | 		el("div", { className: "form-group checkbox-group" }).append( | ||||||
|  | 			el("input", { | ||||||
|  | 				id: "agreedToTerms", | ||||||
|  | 				type: "checkbox", | ||||||
|  | 				checked: formState.get().agreedToTerms | ||||||
|  | 			}, on("change", onChange("checked"))), | ||||||
|  | 			el("label", { htmlFor: "agreedToTerms", textContent: "I agree to the Terms and Conditions" }), | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		// Submit button | ||||||
|  | 		el("button", { | ||||||
|  | 			textContent: "Create Account", | ||||||
|  | 			type: "submit", | ||||||
|  | 			className: "submit-button", | ||||||
|  | 			disabled: S(() => !formValid.get()) | ||||||
|  | 		}), | ||||||
|  | 	); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Render the component | ||||||
|  | document.body.append( | ||||||
|  | 	el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append( | ||||||
|  | 		el(InteractiveForm) | ||||||
|  | 	), | ||||||
|  | 	el("style", ` | ||||||
|  | 		.form-container { | ||||||
|  | 			font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | ||||||
|  | 			max-width: 500px; | ||||||
|  | 			margin: 0 auto; | ||||||
|  | 			padding: 2rem; | ||||||
|  | 			border-radius: 8px; | ||||||
|  | 			box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); | ||||||
|  | 			background: #fff; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		h2 { | ||||||
|  | 			margin-top: 0; | ||||||
|  | 			color: #333; | ||||||
|  | 			margin-bottom: 1.5rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.form-group { | ||||||
|  | 			margin-bottom: 1.5rem; | ||||||
|  | 			position: relative; | ||||||
|  | 			transition: all 0.3s ease; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		label { | ||||||
|  | 			display: block; | ||||||
|  | 			margin-bottom: 0.5rem; | ||||||
|  | 			color: #555; | ||||||
|  | 			font-weight: 500; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		input[type="text"], | ||||||
|  | 		input[type="email"], | ||||||
|  | 		input[type="password"] { | ||||||
|  | 			width: 100%; | ||||||
|  | 			padding: 0.75rem; | ||||||
|  | 			border: 1px solid #ddd; | ||||||
|  | 			border-radius: 4px; | ||||||
|  | 			font-size: 1rem; | ||||||
|  | 			transition: border-color 0.3s ease; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		input:focus { | ||||||
|  | 			outline: none; | ||||||
|  | 			border-color: #4a90e2; | ||||||
|  | 			box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.checkbox-group { | ||||||
|  | 			display: flex; | ||||||
|  | 			align-items: center; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.checkbox-group label { | ||||||
|  | 			margin: 0 0 0 0.5rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.validation-message { | ||||||
|  | 			font-size: 0.85rem; | ||||||
|  | 			color: #e74c3c; | ||||||
|  | 			margin-top: 0.5rem; | ||||||
|  | 			height: 0; | ||||||
|  | 			overflow: hidden; | ||||||
|  | 			opacity: 0; | ||||||
|  | 			transition: all 0.3s ease; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.form-group.invalid .validation-message { | ||||||
|  | 			height: auto; | ||||||
|  | 			opacity: 1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.form-group.valid input { | ||||||
|  | 			border-color: #2ecc71; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.form-group.invalid input { | ||||||
|  | 			border-color: #e74c3c; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.submit-button { | ||||||
|  | 			background-color: #4a90e2; | ||||||
|  | 			color: white; | ||||||
|  | 			border: none; | ||||||
|  | 			border-radius: 4px; | ||||||
|  | 			padding: 0.75rem 1.5rem; | ||||||
|  | 			font-size: 1rem; | ||||||
|  | 			cursor: pointer; | ||||||
|  | 			transition: background-color 0.3s ease; | ||||||
|  | 			width: 100%; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.submit-button:hover:not(:disabled) { | ||||||
|  | 			background-color: #3a7bc8; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.submit-button:disabled { | ||||||
|  | 			background-color: #b5b5b5; | ||||||
|  | 			cursor: not-allowed; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.success-message { | ||||||
|  | 			text-align: center; | ||||||
|  | 			color: #2ecc71; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.success-message h3 { | ||||||
|  | 			margin-top: 0; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.success-message button { | ||||||
|  | 			background-color: #2ecc71; | ||||||
|  | 			color: white; | ||||||
|  | 			border: none; | ||||||
|  | 			border-radius: 4px; | ||||||
|  | 			padding: 0.75rem 1.5rem; | ||||||
|  | 			font-size: 1rem; | ||||||
|  | 			cursor: pointer; | ||||||
|  | 			margin-top: 1rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.success-message button:hover { | ||||||
|  | 			background-color: #27ae60; | ||||||
|  | 		} | ||||||
|  | 	`), | ||||||
|  | ); | ||||||
							
								
								
									
										509
									
								
								docs/components/examples/case-studies/products.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										509
									
								
								docs/components/examples/case-studies/products.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,509 @@ | |||||||
|  | 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)); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	`), | ||||||
|  | ); | ||||||
							
								
								
									
										715
									
								
								docs/components/examples/case-studies/task-manager.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										715
									
								
								docs/components/examples/case-studies/task-manager.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,715 @@ | |||||||
|  | /** | ||||||
|  |  * Case Study: Task Manager Application | ||||||
|  |  * | ||||||
|  |  * This example demonstrates: | ||||||
|  |  * - Complex state management with signals | ||||||
|  |  * - Drag and drop functionality | ||||||
|  |  * - Local storage persistence | ||||||
|  |  * - Responsive design for different devices | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | import { el, on, dispatchEvent, scope } from "deka-dom-el"; | ||||||
|  | import { S } from "deka-dom-el/signals"; | ||||||
|  |  | ||||||
|  | /** @typedef {{ id: number, title: string, description: string, priority: string, status: string }} Task */ | ||||||
|  | /** | ||||||
|  |  * Task Manager Component | ||||||
|  |  * @returns {HTMLElement} Task manager UI | ||||||
|  |  */ | ||||||
|  | export function TaskManager() { | ||||||
|  | 	// <Tasks store> | ||||||
|  | 	const STORAGE_KEY = 'dde-task-manager'; | ||||||
|  | 	const STATUSES = { | ||||||
|  | 		TODO: 'todo', | ||||||
|  | 		IN_PROGRESS: 'in-progress', | ||||||
|  | 		DONE: 'done' | ||||||
|  | 	}; | ||||||
|  | 	/** @type {Task[]} */ | ||||||
|  | 	let initialTasks = []; | ||||||
|  | 	try { | ||||||
|  | 		const saved = localStorage.getItem(STORAGE_KEY); | ||||||
|  | 		if (saved) { | ||||||
|  | 			initialTasks = JSON.parse(saved); | ||||||
|  | 		} | ||||||
|  | 	} catch (e) { | ||||||
|  | 		console.error('Failed to load tasks from localStorage', e); | ||||||
|  | 	} | ||||||
|  | 	if (!initialTasks.length) { | ||||||
|  | 		initialTasks = [ | ||||||
|  | 			{ id: 1, title: 'Create project structure', description: 'Set up folders and initial files', | ||||||
|  | 				status: STATUSES.DONE, priority: 'high' }, | ||||||
|  | 			{ id: 2, title: 'Design UI components', description: 'Create mockups for main views', | ||||||
|  | 				status: STATUSES.IN_PROGRESS, priority: 'medium' }, | ||||||
|  | 			{ id: 3, title: 'Implement authentication', description: 'Set up user login and registration', | ||||||
|  | 				status: STATUSES.TODO, priority: 'high' }, | ||||||
|  | 			{ id: 4, title: 'Write documentation', description: 'Document API endpoints and usage examples', | ||||||
|  | 				status: STATUSES.TODO, priority: 'low' }, | ||||||
|  | 		]; | ||||||
|  | 	} | ||||||
|  | 	const tasks = S(initialTasks, { | ||||||
|  | 		add(task) { this.value.push(task); }, | ||||||
|  | 		remove(id) { this.value = this.value.filter(task => task.id !== id); }, | ||||||
|  | 		update(id, task) { | ||||||
|  | 			const current= this.value.find(t => t.id === id); | ||||||
|  | 			if (current) Object.assign(current, task); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 	S.on(tasks, value => { | ||||||
|  | 		try { | ||||||
|  | 			localStorage.setItem(STORAGE_KEY, JSON.stringify(value)); | ||||||
|  | 		} catch (e) { | ||||||
|  | 			console.error('Failed to save tasks to localStorage', e); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 	// </Tasks store> | ||||||
|  |  | ||||||
|  | 	const filterPriority = S('all'); | ||||||
|  | 	const searchQuery = S(''); | ||||||
|  | 	// Filtered tasks based on priority and search query | ||||||
|  | 	const filteredTasks = S(() => { | ||||||
|  | 		let filtered = tasks.get(); | ||||||
|  |  | ||||||
|  | 		// Filter by priority | ||||||
|  | 		if (filterPriority.get() !== 'all') { | ||||||
|  | 			filtered = filtered.filter(task => task.priority === filterPriority.get()); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Filter by search query | ||||||
|  | 		const query = searchQuery.get().toLowerCase(); | ||||||
|  | 		if (query) { | ||||||
|  | 			filtered = filtered.filter(task => | ||||||
|  | 				task.title.toLowerCase().includes(query) || | ||||||
|  | 				task.description.toLowerCase().includes(query) | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return filtered; | ||||||
|  | 	}); | ||||||
|  | 	/** Tasks grouped by status for display in columns */ | ||||||
|  | 	const tasksByStatus = S(() => { | ||||||
|  | 		const filtered = filteredTasks.get(); | ||||||
|  | 		return { | ||||||
|  | 			[STATUSES.TODO]: filtered.filter(t => t.status === STATUSES.TODO), | ||||||
|  | 			[STATUSES.IN_PROGRESS]: filtered.filter(t => t.status === STATUSES.IN_PROGRESS), | ||||||
|  | 			[STATUSES.DONE]: filtered.filter(t => t.status === STATUSES.DONE) | ||||||
|  | 		}; | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	// <Add> signals and handlers for adding new tasks | ||||||
|  | 	const newTask = { title: '', description: '', priority: 'medium' }; | ||||||
|  | 	const onAddTask = e => { | ||||||
|  | 		e.preventDefault(); | ||||||
|  | 		if (!newTask.title) return; | ||||||
|  |  | ||||||
|  | 		S.action(tasks, "add", { | ||||||
|  | 			id: Date.now(), | ||||||
|  | 			status: STATUSES.TODO, | ||||||
|  | 			...newTask | ||||||
|  | 		}); | ||||||
|  | 		e.target.reset(); | ||||||
|  | 	}; | ||||||
|  | 	// </Add> | ||||||
|  | 	const onCardEdit= on("card:edit", /** @param {CardEditEvent} ev */({ detail: [ id, task ] })=> | ||||||
|  | 		S.action(tasks, "update", id, task)); | ||||||
|  | 	const onCardDelete= on("card:delete", /** @param {CardDeleteEvent} ev */({ detail: id })=> | ||||||
|  | 		S.action(tasks, "remove", id)); | ||||||
|  |  | ||||||
|  | 	const { onDragable, onDragArea }= moveElementAddon( | ||||||
|  | 		(id, status) => S.action(tasks, "update", id, { status }) | ||||||
|  | 	); | ||||||
|  |  | ||||||
|  | 	// Build the task manager UI | ||||||
|  | 	return el("div", { className: "task-manager" }).append( | ||||||
|  | 		el("header", { className: "app-header" }).append( | ||||||
|  | 			el("h1", "DDE Task Manager"), | ||||||
|  | 			el("div", { className: "app-controls" }).append( | ||||||
|  | 				el("input", { | ||||||
|  | 					type: "text", | ||||||
|  | 					placeholder: "Search tasks...", | ||||||
|  | 					value: searchQuery.get() | ||||||
|  | 				}, on("input", e => searchQuery.set(e.target.value))), | ||||||
|  | 				el("select", null, | ||||||
|  | 					on.defer(el=> el.value= filterPriority.get()), | ||||||
|  | 					on("change", e => filterPriority.set(e.target.value)) | ||||||
|  | 				).append( | ||||||
|  | 					el("option", { value: "all", textContent: "All Priorities" }), | ||||||
|  | 					el("option", { value: "low", textContent: "Low Priority" }), | ||||||
|  | 					el("option", { value: "medium", textContent: "Medium Priority" }), | ||||||
|  | 					el("option", { value: "high", textContent: "High Priority" }) | ||||||
|  | 				) | ||||||
|  | 			) | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		// Add new task form | ||||||
|  | 		el("form", { className: "new-task-form" }, on("submit", onAddTask)).append( | ||||||
|  | 			el("div", { className: "form-row" }).append( | ||||||
|  | 				el("input", { | ||||||
|  | 					type: "text", | ||||||
|  | 					placeholder: "New task title", | ||||||
|  | 					value: newTask.title, | ||||||
|  | 					required: true | ||||||
|  | 				}, on("input", e => newTask.title= e.target.value.trim())), | ||||||
|  | 				el("select", null, | ||||||
|  | 					on.defer(el=> el.value= newTask.priority), | ||||||
|  | 					on("change", e => newTask.priority= e.target.value) | ||||||
|  | 				).append( | ||||||
|  | 					el("option", { value: "low", textContent: "Low" }), | ||||||
|  | 					el("option", { value: "medium", textContent: "Medium" }), | ||||||
|  | 					el("option", { value: "high", textContent: "High" }) | ||||||
|  | 				), | ||||||
|  | 				el("button", { type: "submit", className: "add-btn" }).append("Add Task") | ||||||
|  | 			), | ||||||
|  | 			el("textarea", { | ||||||
|  | 				placeholder: "Task description (optional)", | ||||||
|  | 				value: newTask.description | ||||||
|  | 			}, on("input", e => newTask.description= e.target.value.trim())) | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		// Task board with columns | ||||||
|  | 		el("div", { className: "task-board" }).append( | ||||||
|  | 			// Todo column | ||||||
|  | 			el("div", { | ||||||
|  | 				id: `column-${STATUSES.TODO}`, | ||||||
|  | 				className: "task-column" | ||||||
|  | 			}, onDragArea(STATUSES.TODO)).append( | ||||||
|  | 				el("h2", { className: "column-header" }).append( | ||||||
|  | 					"To Do ", | ||||||
|  | 					el("span", { | ||||||
|  | 						textContent: S(() => tasksByStatus.get()[STATUSES.TODO].length), | ||||||
|  | 						className: "task-count" | ||||||
|  | 					}), | ||||||
|  | 				), | ||||||
|  | 				S.el(S(() => tasksByStatus.get()[STATUSES.TODO]), tasks => | ||||||
|  | 					el("div", { className: "column-tasks" }).append( | ||||||
|  | 						...tasks.map(task=> el(TaskCard, { task, onDragable }, onCardEdit, onCardDelete)) | ||||||
|  | 					) | ||||||
|  | 				) | ||||||
|  | 			), | ||||||
|  |  | ||||||
|  | 			// In Progress column | ||||||
|  | 			el("div", { | ||||||
|  | 				id: `column-${STATUSES.IN_PROGRESS}`, | ||||||
|  | 				className: "task-column" | ||||||
|  | 			}, onDragArea(STATUSES.IN_PROGRESS)).append( | ||||||
|  | 				el("h2", { className: "column-header" }).append( | ||||||
|  | 					"In Progress ", | ||||||
|  | 					el("span", { | ||||||
|  | 						textContent: S(() => tasksByStatus.get()[STATUSES.IN_PROGRESS].length), | ||||||
|  | 						className: "task-count", | ||||||
|  | 					}), | ||||||
|  | 				), | ||||||
|  | 				S.el(S(() => tasksByStatus.get()[STATUSES.IN_PROGRESS]), tasks => | ||||||
|  | 					el("div", { className: "column-tasks" }).append( | ||||||
|  | 						...tasks.map(task=> el(TaskCard, { task, onDragable }, onCardEdit, onCardDelete)) | ||||||
|  | 					) | ||||||
|  | 				) | ||||||
|  | 			), | ||||||
|  |  | ||||||
|  | 			// Done column | ||||||
|  | 			el("div", { | ||||||
|  | 				id: `column-${STATUSES.DONE}`, | ||||||
|  | 				className: "task-column" | ||||||
|  | 			}, onDragArea(STATUSES.DONE)).append( | ||||||
|  | 				el("h2", { className: "column-header" }).append( | ||||||
|  | 					"Done ", | ||||||
|  | 					el("span", { | ||||||
|  | 						textContent: S(() => tasksByStatus.get()[STATUSES.DONE].length), | ||||||
|  | 						className: "task-count", | ||||||
|  | 					}), | ||||||
|  | 				), | ||||||
|  | 				S.el(S(() => tasksByStatus.get()[STATUSES.DONE]), tasks => | ||||||
|  | 					el("div", { className: "column-tasks" }).append( | ||||||
|  | 						...tasks.map(task=> el(TaskCard, { task, onDragable }, onCardEdit, onCardDelete)) | ||||||
|  | 					) | ||||||
|  | 				) | ||||||
|  | 			) | ||||||
|  | 		), | ||||||
|  | 	); | ||||||
|  | } | ||||||
|  | /** @typedef {CustomEvent<[ string, Task ]>} CardEditEvent */ | ||||||
|  | /** @typedef {CustomEvent<string>} CardDeleteEvent */ | ||||||
|  | /** | ||||||
|  |  * Task Card Component | ||||||
|  |  * @type {(props: { task: Task, onDragable: (id: number) => ddeElementAddon<HTMLDivElement> }) => HTMLElement} | ||||||
|  |  * @fires {CardEditEvent} card:edit | ||||||
|  |  * @fires {CardDeleteEvent} card:delete | ||||||
|  |  * */ | ||||||
|  | function TaskCard({ task, onDragable }){ | ||||||
|  | 	const { host }= scope; | ||||||
|  | 	const isEditing = S(false); | ||||||
|  | 	const onEditStart = () => isEditing.set(true); | ||||||
|  |  | ||||||
|  | 	const dispatchEdit= dispatchEvent("card:edit", host); | ||||||
|  | 	const dispatchDelete= dispatchEvent("card:delete", host).bind(null, task.id); | ||||||
|  |  | ||||||
|  | 	return el("div", { | ||||||
|  | 		id: `task-${task.id}`, | ||||||
|  | 		className: `task-card priority-${task.priority}`, | ||||||
|  | 		draggable: true | ||||||
|  | 	}, onDragable(task.id)).append( | ||||||
|  | 		S.el(isEditing, editing => editing | ||||||
|  | 			? el(EditMode) | ||||||
|  | 			: el().append( | ||||||
|  | 				el("div", { className: "task-header" }).append( | ||||||
|  | 					el("h3", { className: "task-title", textContent: task.title }), | ||||||
|  | 					el("div", { className: "task-actions" }).append( | ||||||
|  | 						el("button", { | ||||||
|  | 							textContent: "✎", | ||||||
|  | 							className: "edit-btn", | ||||||
|  | 							ariaLabel: "Edit task" | ||||||
|  | 						}, on("click", onEditStart)), | ||||||
|  | 						el("button", { | ||||||
|  | 							textContent: "✕", | ||||||
|  | 							className: "delete-btn", | ||||||
|  | 							ariaLabel: "Delete task" | ||||||
|  | 						}, on("click", dispatchDelete)) | ||||||
|  | 					) | ||||||
|  | 				), | ||||||
|  | 				!task.description | ||||||
|  | 				? el() | ||||||
|  | 				: el("p", { className: "task-description", textContent: task.description }), | ||||||
|  | 				el("div", { className: "task-meta" }).append( | ||||||
|  | 					el("span", { | ||||||
|  | 						className: `priority-badge priority-${task.priority}`, | ||||||
|  | 						textContent: task.priority.charAt(0).toUpperCase() + task.priority.slice(1) | ||||||
|  | 					}) | ||||||
|  | 				) | ||||||
|  | 			) | ||||||
|  | 		) | ||||||
|  | 	); | ||||||
|  | 	function EditMode(){ | ||||||
|  | 		const onSubmit = on("submit", e => { | ||||||
|  | 			e.preventDefault(); | ||||||
|  | 			const formData = new FormData(/** @type {HTMLFormElement} */(e.target)); | ||||||
|  | 			const title = formData.get("title"); | ||||||
|  | 			const description = formData.get("description"); | ||||||
|  | 			const priority = formData.get("priority"); | ||||||
|  | 			isEditing.set(false); | ||||||
|  | 			dispatchEdit([ task.id, { title, description, priority } ]); | ||||||
|  | 		}) | ||||||
|  | 		const onEditCancel = () => isEditing.set(false); | ||||||
|  |  | ||||||
|  | 		return el("form", { className: "task-edit-form" }, onSubmit).append( | ||||||
|  | 			el("input", { | ||||||
|  | 				name: "title", | ||||||
|  | 				className: "task-title-input", | ||||||
|  | 				defaultValue: task.title, | ||||||
|  | 				placeholder: "Task title", | ||||||
|  | 				required: true, | ||||||
|  | 				autoFocus: true | ||||||
|  | 			}), | ||||||
|  | 			el("textarea", { | ||||||
|  | 				name: "description", | ||||||
|  | 				className: "task-desc-input", | ||||||
|  | 				defaultValue: task.description, | ||||||
|  | 				placeholder: "Description (optional)" | ||||||
|  | 			}), | ||||||
|  | 			el("select", { | ||||||
|  | 				name: "priority", | ||||||
|  | 			}, on.defer(el=> el.value = task.priority)).append( | ||||||
|  | 				el("option", { value: "low", textContent: "Low Priority" }), | ||||||
|  | 				el("option", { value: "medium", textContent: "Medium Priority" }), | ||||||
|  | 				el("option", { value: "high", textContent: "High Priority" }) | ||||||
|  | 			), | ||||||
|  | 			el("div", { className: "task-edit-actions" }).append( | ||||||
|  | 				el("button", { | ||||||
|  | 					textContent: "Cancel", | ||||||
|  | 					type: "button", | ||||||
|  | 					className: "cancel-btn" | ||||||
|  | 				}, on("click", onEditCancel)), | ||||||
|  | 				el("button", { | ||||||
|  | 					textContent: "Save", | ||||||
|  | 					type: "submit", | ||||||
|  | 					className: "save-btn" | ||||||
|  | 				}) | ||||||
|  | 			) | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Helper function to handle move an element | ||||||
|  |  * @param {(id: string, status: string) => void} onMoved | ||||||
|  |  * */ | ||||||
|  | function moveElementAddon(onMoved){ | ||||||
|  | 	let draggedTaskId = null; | ||||||
|  | 	function onDragable(id) { | ||||||
|  | 		return element => { | ||||||
|  | 			on("dragstart", e => { | ||||||
|  | 				draggedTaskId= id; | ||||||
|  | 				e.dataTransfer.effectAllowed = 'move'; | ||||||
|  |  | ||||||
|  | 				// Add some styling to the element being dragged | ||||||
|  | 				setTimeout(() => { | ||||||
|  | 					const el = document.getElementById(`task-${id}`); | ||||||
|  | 					if (el) el.classList.add('dragging'); | ||||||
|  | 				}, 0); | ||||||
|  | 			})(element); | ||||||
|  |  | ||||||
|  | 			on("dragend", () => { | ||||||
|  | 				draggedTaskId= null; | ||||||
|  |  | ||||||
|  | 				// Remove the styling | ||||||
|  | 				const el = document.getElementById(`task-${id}`); | ||||||
|  | 				if (el) el.classList.remove('dragging'); | ||||||
|  | 			})(element); | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 	function onDragArea(status) { | ||||||
|  | 		return element => { | ||||||
|  | 			on("dragover", e => { | ||||||
|  | 				e.preventDefault(); | ||||||
|  | 				e.dataTransfer.dropEffect = 'move'; | ||||||
|  |  | ||||||
|  | 				// Add a visual indicator for the drop target | ||||||
|  | 				const column = document.getElementById(`column-${status}`); | ||||||
|  | 				if (column) column.classList.add('drag-over'); | ||||||
|  | 			})(element); | ||||||
|  |  | ||||||
|  | 			on("dragleave", () => { | ||||||
|  | 				// Remove the visual indicator | ||||||
|  | 				const column = document.getElementById(`column-${status}`); | ||||||
|  | 				if (column) column.classList.remove('drag-over'); | ||||||
|  | 			})(element); | ||||||
|  |  | ||||||
|  | 			on("drop", e => { | ||||||
|  | 				e.preventDefault(); | ||||||
|  | 				const id = draggedTaskId; | ||||||
|  | 				if (id) onMoved(id, status); | ||||||
|  | 				// Remove the visual indicator | ||||||
|  | 				const column = document.getElementById(`column-${status}`); | ||||||
|  | 				if (column) column.classList.remove('drag-over'); | ||||||
|  | 			})(element); | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 	return { onDragable, onDragArea }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Render the component | ||||||
|  | document.body.append( | ||||||
|  | 	el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append( | ||||||
|  | 		el(TaskManager) | ||||||
|  | 	), | ||||||
|  | 	el("style", ` | ||||||
|  | 		.task-manager { | ||||||
|  | 			font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | ||||||
|  | 			max-width: 1200px; | ||||||
|  | 			margin: 0 auto; | ||||||
|  | 			padding: 1rem; | ||||||
|  | 			color: #333; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.app-header { | ||||||
|  | 			display: flex; | ||||||
|  | 			justify-content: space-between; | ||||||
|  | 			align-items: center; | ||||||
|  | 			margin-bottom: 1.5rem; | ||||||
|  | 			flex-wrap: wrap; | ||||||
|  | 			gap: 1rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.app-header h1 { | ||||||
|  | 			margin: 0; | ||||||
|  | 			color: #2d3748; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.app-controls { | ||||||
|  | 			display: flex; | ||||||
|  | 			gap: 1rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.app-controls input, | ||||||
|  | 		.app-controls select { | ||||||
|  | 			padding: 0.5rem; | ||||||
|  | 			border: 1px solid #e2e8f0; | ||||||
|  | 			border-radius: 4px; | ||||||
|  | 			font-size: 0.9rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.new-task-form { | ||||||
|  | 			background: white; | ||||||
|  | 			padding: 1.5rem; | ||||||
|  | 			border-radius: 8px; | ||||||
|  | 			box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); | ||||||
|  | 			margin-bottom: 2rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.form-row { | ||||||
|  | 			display: flex; | ||||||
|  | 			gap: 1rem; | ||||||
|  | 			margin-bottom: 1rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.form-row input { | ||||||
|  | 			flex-grow: 1; | ||||||
|  | 			padding: 0.75rem; | ||||||
|  | 			border: 1px solid #e2e8f0; | ||||||
|  | 			border-radius: 4px; | ||||||
|  | 			font-size: 1rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.form-row select { | ||||||
|  | 			width: 100px; | ||||||
|  | 			padding: 0.75rem; | ||||||
|  | 			border: 1px solid #e2e8f0; | ||||||
|  | 			border-radius: 4px; | ||||||
|  | 			font-size: 1rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.add-btn { | ||||||
|  | 			background: #4a90e2; | ||||||
|  | 			color: white; | ||||||
|  | 			border: none; | ||||||
|  | 			border-radius: 4px; | ||||||
|  | 			padding: 0.75rem 1.5rem; | ||||||
|  | 			font-size: 1rem; | ||||||
|  | 			cursor: pointer; | ||||||
|  | 			transition: background 0.2s ease; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.add-btn:hover { | ||||||
|  | 			background: #3a7bc8; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.new-task-form textarea { | ||||||
|  | 			width: 100%; | ||||||
|  | 			padding: 0.75rem; | ||||||
|  | 			border: 1px solid #e2e8f0; | ||||||
|  | 			border-radius: 4px; | ||||||
|  | 			font-size: 1rem; | ||||||
|  | 			resize: vertical; | ||||||
|  | 			min-height: 80px; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-board { | ||||||
|  | 			display: grid; | ||||||
|  | 			grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | ||||||
|  | 			gap: 1.5rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-column { | ||||||
|  | 			background: #f7fafc; | ||||||
|  | 			border-radius: 8px; | ||||||
|  | 			padding: 1rem; | ||||||
|  | 			min-height: 400px; | ||||||
|  | 			transition: background 0.2s ease; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.column-header { | ||||||
|  | 			margin-top: 0; | ||||||
|  | 			padding-bottom: 0.75rem; | ||||||
|  | 			border-bottom: 2px solid #e2e8f0; | ||||||
|  | 			font-size: 1.25rem; | ||||||
|  | 			color: #2d3748; | ||||||
|  | 			display: flex; | ||||||
|  | 			align-items: center; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-count { | ||||||
|  | 			display: inline-flex; | ||||||
|  | 			justify-content: center; | ||||||
|  | 			align-items: center; | ||||||
|  | 			background: #e2e8f0; | ||||||
|  | 			color: #4a5568; | ||||||
|  | 			border-radius: 50%; | ||||||
|  | 			width: 25px; | ||||||
|  | 			height: 25px; | ||||||
|  | 			font-size: 0.875rem; | ||||||
|  | 			margin-left: 0.5rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.column-tasks { | ||||||
|  | 			margin-top: 1rem; | ||||||
|  | 			display: flex; | ||||||
|  | 			flex-direction: column; | ||||||
|  | 			gap: 1rem; | ||||||
|  | 			min-height: 200px; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-card { | ||||||
|  | 			background: white; | ||||||
|  | 			border-radius: 6px; | ||||||
|  | 			padding: 1rem; | ||||||
|  | 			box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); | ||||||
|  | 			cursor: grab; | ||||||
|  | 			transition: transform 0.2s ease, box-shadow 0.2s ease; | ||||||
|  | 			position: relative; | ||||||
|  | 			border-left: 4px solid #ccc; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-card:hover { | ||||||
|  | 			transform: translateY(-3px); | ||||||
|  | 			box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-card.dragging { | ||||||
|  | 			opacity: 0.5; | ||||||
|  | 			cursor: grabbing; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-card.priority-low { | ||||||
|  | 			border-left-color: #38b2ac; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-card.priority-medium { | ||||||
|  | 			border-left-color: #ecc94b; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-card.priority-high { | ||||||
|  | 			border-left-color: #e53e3e; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-header { | ||||||
|  | 			display: flex; | ||||||
|  | 			justify-content: space-between; | ||||||
|  | 			align-items: flex-start; | ||||||
|  | 			margin-bottom: 0.5rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-title { | ||||||
|  | 			margin: 0; | ||||||
|  | 			font-size: 1.1rem; | ||||||
|  | 			color: #2d3748; | ||||||
|  | 			word-break: break-word; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-description { | ||||||
|  | 			margin: 0.5rem 0; | ||||||
|  | 			font-size: 0.9rem; | ||||||
|  | 			color: #4a5568; | ||||||
|  | 			word-break: break-word; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-actions { | ||||||
|  | 			display: flex; | ||||||
|  | 			gap: 0.5rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.edit-btn, | ||||||
|  | 		.delete-btn { | ||||||
|  | 			background: none; | ||||||
|  | 			border: none; | ||||||
|  | 			font-size: 1rem; | ||||||
|  | 			cursor: pointer; | ||||||
|  | 			width: 24px; | ||||||
|  | 			height: 24px; | ||||||
|  | 			display: inline-flex; | ||||||
|  | 			justify-content: center; | ||||||
|  | 			align-items: center; | ||||||
|  | 			border-radius: 50%; | ||||||
|  | 			color: #718096; | ||||||
|  | 			transition: background 0.2s ease, color 0.2s ease; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.edit-btn:hover { | ||||||
|  | 			background: #edf2f7; | ||||||
|  | 			color: #4a5568; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.delete-btn:hover { | ||||||
|  | 			background: #fed7d7; | ||||||
|  | 			color: #e53e3e; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-meta { | ||||||
|  | 			display: flex; | ||||||
|  | 			justify-content: space-between; | ||||||
|  | 			align-items: center; | ||||||
|  | 			margin-top: 0.75rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.priority-badge { | ||||||
|  | 			font-size: 0.75rem; | ||||||
|  | 			padding: 0.2rem 0.5rem; | ||||||
|  | 			border-radius: 4px; | ||||||
|  | 			font-weight: 500; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.priority-badge.priority-low { | ||||||
|  | 			background: #e6fffa; | ||||||
|  | 			color: #2c7a7b; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.priority-badge.priority-medium { | ||||||
|  | 			background: #fefcbf; | ||||||
|  | 			color: #975a16; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.priority-badge.priority-high { | ||||||
|  | 			background: #fed7d7; | ||||||
|  | 			color: #c53030; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.drag-over { | ||||||
|  | 			background: #f0f9ff; | ||||||
|  | 			border: 2px dashed #4a90e2; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-edit-form { | ||||||
|  | 			display: flex; | ||||||
|  | 			flex-direction: column; | ||||||
|  | 			gap: 0.75rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-title-input, | ||||||
|  | 		.task-desc-input { | ||||||
|  | 			width: 100%; | ||||||
|  | 			padding: 0.5rem; | ||||||
|  | 			border: 1px solid #e2e8f0; | ||||||
|  | 			border-radius: 4px; | ||||||
|  | 			font-size: 0.9rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-desc-input { | ||||||
|  | 			min-height: 60px; | ||||||
|  | 			resize: vertical; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.task-edit-actions { | ||||||
|  | 			display: flex; | ||||||
|  | 			justify-content: flex-end; | ||||||
|  | 			gap: 0.5rem; | ||||||
|  | 			margin-top: 0.5rem; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.cancel-btn, | ||||||
|  | 		.save-btn { | ||||||
|  | 			padding: 0.4rem 0.75rem; | ||||||
|  | 			border-radius: 4px; | ||||||
|  | 			font-size: 0.9rem; | ||||||
|  | 			cursor: pointer; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.cancel-btn { | ||||||
|  | 			background: #edf2f7; | ||||||
|  | 			color: #4a5568; | ||||||
|  | 			border: 1px solid #e2e8f0; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		.save-btn { | ||||||
|  | 			background: #4a90e2; | ||||||
|  | 			color: white; | ||||||
|  | 			border: none; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		@media (max-width: 768px) { | ||||||
|  | 			.app-header { | ||||||
|  | 				flex-direction: column; | ||||||
|  | 				align-items: flex-start; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.app-controls { | ||||||
|  | 				width: 100%; | ||||||
|  | 				flex-direction: column; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.form-row { | ||||||
|  | 				flex-direction: column; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			.task-board { | ||||||
|  | 				grid-template-columns: 1fr; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	`) | ||||||
|  | ); | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| import { S } from "deka-dom-el/signals"; | import { S } from "deka-dom-el/signals"; | ||||||
| // Debouncing signal updates |  | ||||||
|  | // ===== Approach 1: Traditional debouncing with utility function ===== | ||||||
| function debounce(func, wait) { | function debounce(func, wait) { | ||||||
| 	let timeout; | 	let timeout; | ||||||
| 	return (...args)=> { | 	return (...args)=> { | ||||||
| @@ -8,8 +9,59 @@ function debounce(func, wait) { | |||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
|  |  | ||||||
| const inputSignal= S(""); | const inputSignal = S(""); | ||||||
| const debouncedSet= debounce(value => inputSignal.set(value), 300); | const debouncedSet = debounce(value => inputSignal.set(value), 300); | ||||||
|  |  | ||||||
| // In your input handler | // In your input handler | ||||||
| inputElement.addEventListener("input", e=> debouncedSet(e.target.value)); | inputElement.addEventListener("input", e => debouncedSet(e.target.value)); | ||||||
|  |  | ||||||
|  | // ===== Approach 2: Signal debouncing utility ===== | ||||||
|  | /** | ||||||
|  |  * Creates a debounced signal that only updates after delay | ||||||
|  |  * @param {any} initialValue Initial signal value | ||||||
|  |  * @param {number} delay Debounce delay in ms | ||||||
|  |  */ | ||||||
|  | function createDebouncedSignal(initialValue, delay = 300) { | ||||||
|  | 	// Create two signals: one for immediate updates, one for debounced values | ||||||
|  | 	const immediateSignal = S(initialValue); | ||||||
|  | 	const debouncedSignal = S(initialValue); | ||||||
|  |  | ||||||
|  | 	// Keep track of the timeout | ||||||
|  | 	let timeout = null; | ||||||
|  |  | ||||||
|  | 	// Set up a listener on the immediate signal | ||||||
|  | 	S.on(immediateSignal, value => { | ||||||
|  | 		// Clear any existing timeout | ||||||
|  | 		if (timeout) clearTimeout(timeout); | ||||||
|  |  | ||||||
|  | 		// Set a new timeout to update the debounced signal | ||||||
|  | 		timeout = setTimeout(() => { | ||||||
|  | 			debouncedSignal.set(value); | ||||||
|  | 		}, delay); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	// Return an object with both signals and a setter function | ||||||
|  | 	return { | ||||||
|  | 		// The raw signal that updates immediately | ||||||
|  | 		raw: immediateSignal, | ||||||
|  | 		// The debounced signal that only updates after delay | ||||||
|  | 		debounced: debouncedSignal, | ||||||
|  | 		// Setter function to update the immediate signal | ||||||
|  | 		set: value => immediateSignal.set(value) | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Usage example | ||||||
|  | const searchInput = createDebouncedSignal("", 300); | ||||||
|  |  | ||||||
|  | // Log immediate changes for demonstration | ||||||
|  | S.on(searchInput.raw, value => console.log("Input changed to:", value)); | ||||||
|  |  | ||||||
|  | // Only perform expensive operations on the debounced value | ||||||
|  | S.on(searchInput.debounced, value => { | ||||||
|  | 	console.log("Performing search with:", value); | ||||||
|  | 	// Expensive operation would go here | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | // In your input handler | ||||||
|  | searchElement.addEventListener("input", e => searchInput.set(e.target.value)); | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| // Example of reactive element marker | // Example of reactive element marker | ||||||
| <!--<dde:mark type="reactive" source="...">--> | <!--<dde:mark type="reactive" component="<component-name>">--> | ||||||
| <!-- content that updates when signal changes --> | <!-- content that updates when signal changes --> | ||||||
| <!--</dde:mark>--> | <!--</dde:mark>--> | ||||||
|   | |||||||
| @@ -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"), | ||||||
| 	) | 	), | ||||||
| ); | ); | ||||||
|   | |||||||
| @@ -1,15 +1,28 @@ | |||||||
| import { el, on } from "deka-dom-el"; | import { el, on } from "deka-dom-el"; | ||||||
| const paragraph= el("p", "See lifecycle events in console.", | function allLifecycleEvents(){ | ||||||
|  | 	return el("form", null, | ||||||
| 		el=> log({ type: "dde:created", detail: el }), | 		el=> log({ type: "dde:created", detail: el }), | ||||||
| 		on.connected(log), | 		on.connected(log), | ||||||
| 		on.disconnected(log), | 		on.disconnected(log), | ||||||
| ); | 	).append( | ||||||
|  | 		el("select", { id: "country" }, on.defer(select => { | ||||||
|  | 			// This runs when the select is ready with all its options | ||||||
|  | 			select.value = "cz"; // Pre-select Czechia | ||||||
|  | 			log({ type: "dde:on.defer", detail: select }); | ||||||
|  | 		})).append( | ||||||
|  | 			el("option", { value: "au", textContent: "Australia" }), | ||||||
|  | 			el("option", { value: "ca", textContent: "Canada" }), | ||||||
|  | 			el("option", { value: "cz", textContent: "Czechia" }), | ||||||
|  | 		), | ||||||
|  | 		el("p", "See lifecycle events in console."), | ||||||
|  | 	); | ||||||
|  | } | ||||||
|  |  | ||||||
| document.body.append( | document.body.append( | ||||||
| 	paragraph, | 	el(allLifecycleEvents), | ||||||
| 	el("button", "Update attribute", on("click", ()=> paragraph.setAttribute("test", Math.random().toString()))), | 	el("button", "Remove Element", on("click", function(){ | ||||||
| 	" ", | 		this.previousSibling.remove(); | ||||||
| 	el("button", "Remove", on("click", ()=> paragraph.remove())) | 	})) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| /** @param {Partial<CustomEvent>} event */ | /** @param {Partial<CustomEvent>} event */ | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ document.body.append( | |||||||
| 	padding: 1em; | 	padding: 1em; | ||||||
| 	margin: 1em; | 	margin: 1em; | ||||||
| } | } | ||||||
| 		`.trim()) | `.trim()) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| export function CounterStandard() { | export function CounterStandard() { | ||||||
|   | |||||||
| @@ -13,41 +13,38 @@ import { S } from "deka-dom-el/signals"; | |||||||
|  * @returns {HTMLElement} The root TodoMVC application element |  * @returns {HTMLElement} The root TodoMVC application element | ||||||
|  */ |  */ | ||||||
| function Todos(){ | function Todos(){ | ||||||
| 	const pageS = routerSignal(S); | 	const { signal } = scope; | ||||||
|  | 	const pageS = routerSignal(S, signal); | ||||||
| 	const todosS = todosSignal(); | 	const todosS = todosSignal(); | ||||||
| 	/** Derived signal that filters todos based on current route */ | 	/** Derived signal that filters todos based on current route */ | ||||||
| 	const filteredTodosS = S(()=> { | 	const todosFilteredS = S(()=> { | ||||||
| 		const todos = todosS.get(); | 		const todos = todosS.get(); | ||||||
| 		const filter = pageS.get(); | 		const filter = pageS.get(); | ||||||
|  | 		if (filter === "all") return todos; | ||||||
| 		return todos.filter(todo => { | 		return todos.filter(todo => { | ||||||
| 			if (filter === "active") return !todo.completed; | 			if (filter === "active") return !todo.completed; | ||||||
| 			if (filter === "completed") return todo.completed; | 			if (filter === "completed") return todo.completed; | ||||||
| 			return true; // "all" |  | ||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  | 	const todosRemainingS = S(()=> todosS.get().filter(todo => !todo.completed).length); | ||||||
| 	// Setup hash change listener |  | ||||||
| 	window.addEventListener("hashchange", () => { |  | ||||||
| 		const hash = location.hash.replace("#", "") || "all"; |  | ||||||
| 		S.action(pageS, "set", /** @type {"all"|"active"|"completed"} */(hash)); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	/** @type {ddeElementAddon<HTMLInputElement>} */ | 	/** @type {ddeElementAddon<HTMLInputElement>} */ | ||||||
| 	const onToggleAll = on("change", event => { | 	const onToggleAll = on("change", event => { | ||||||
| 		const checked = /** @type {HTMLInputElement} */ (event.target).checked; | 		const checked = /** @type {HTMLInputElement} */ (event.target).checked; | ||||||
| 		S.action(todosS, "completeAll", checked); | 		S.action(todosS, "completeAll", checked); | ||||||
| 	}); | 	}); | ||||||
|  | 	const formNewTodo = "newTodo"; | ||||||
| 	/** @type {ddeElementAddon<HTMLFormElement>} */ | 	/** @type {ddeElementAddon<HTMLFormElement>} */ | ||||||
| 	const onSubmitNewTodo = on("submit", event => { | 	const onSubmitNewTodo = on("submit", event => { | ||||||
| 		event.preventDefault(); | 		event.preventDefault(); | ||||||
| 		const input = /** @type {HTMLInputElement} */( | 		const input = /** @type {HTMLInputElement} */( | ||||||
| 			/** @type {HTMLFormElement} */(event.target).elements.namedItem("newTodo") | 			/** @type {HTMLFormElement} */(event.target).elements.namedItem(formNewTodo) | ||||||
| 		); | 		); | ||||||
| 		const title = input.value.trim(); | 		const title = input.value.trim(); | ||||||
| 		if (title) { | 		if (!title) return; | ||||||
|  |  | ||||||
| 		S.action(todosS, "add", title); | 		S.action(todosS, "add", title); | ||||||
| 		input.value = ""; | 		input.value = ""; | ||||||
| 		} |  | ||||||
| 	}); | 	}); | ||||||
| 	const onClearCompleted = on("click", () => S.action(todosS, "clearCompleted")); | 	const onClearCompleted = on("click", () => S.action(todosS, "clearCompleted")); | ||||||
| 	const onDelete = on("todo:delete", ev => | 	const onDelete = on("todo:delete", ev => | ||||||
| @@ -61,15 +58,16 @@ function Todos(){ | |||||||
| 			el("form", null, onSubmitNewTodo).append( | 			el("form", null, onSubmitNewTodo).append( | ||||||
| 				el("input", { | 				el("input", { | ||||||
| 					className: "new-todo", | 					className: "new-todo", | ||||||
| 					name: "newTodo", | 					name: formNewTodo, | ||||||
| 					placeholder: "What needs to be done?", | 					placeholder: "What needs to be done?", | ||||||
| 					autocomplete: "off", | 					autocomplete: "off", | ||||||
| 					autofocus: true | 					autofocus: true | ||||||
| 				}) | 				}) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		S.el(todosS, todos => todos.length | 		S.el(todosS, todos => !todos.length | ||||||
| 			? el("main", { className: "main" }).append( | 			? el() | ||||||
|  | 			: el("main", { className: "main" }).append( | ||||||
| 				el("input", { | 				el("input", { | ||||||
| 					id: "toggle-all", | 					id: "toggle-all", | ||||||
| 					className: "toggle-all", | 					className: "toggle-all", | ||||||
| @@ -77,54 +75,40 @@ function Todos(){ | |||||||
| 				}, onToggleAll), | 				}, onToggleAll), | ||||||
| 				el("label", { htmlFor: "toggle-all", title: "Mark all as complete" }), | 				el("label", { htmlFor: "toggle-all", title: "Mark all as complete" }), | ||||||
| 				el("ul", { className: "todo-list" }).append( | 				el("ul", { className: "todo-list" }).append( | ||||||
| 					S.el(filteredTodosS, filteredTodos => filteredTodos.map(todo => | 					S.el(todosFilteredS, filteredTodos => filteredTodos.map(todo => | ||||||
| 						memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) | 						memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) | ||||||
| 					) | 					) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 			: el() |  | ||||||
| 		), | 		), | ||||||
| 		S.el(todosS, todos => memo(todos.length, length=> length | 		S.el(todosS, ({ length }) => !length | ||||||
| 			? el("footer", { className: "footer" }).append( | 			? el() | ||||||
|  | 			: el("footer", { className: "footer" }).append( | ||||||
| 				el("span", { className: "todo-count" }).append( | 				el("span", { className: "todo-count" }).append( | ||||||
| 					S.el(S(() => todosS.get().filter(todo => !todo.completed).length), | 					el("strong", length + " " + (length === 1 ? "item" : "items")), | ||||||
| 						length=> el("strong").append( |  | ||||||
| 							length + " ", |  | ||||||
| 							length === 1 ? "item left" : "items left" |  | ||||||
| 						) |  | ||||||
| 					) |  | ||||||
| 				), | 				), | ||||||
|  | 				memo("filters", ()=> | ||||||
| 					el("ul", { className: "filters" }).append( | 					el("ul", { className: "filters" }).append( | ||||||
|  | 						...[ "All", "Active", "Completed" ].map(textContent => | ||||||
| 							el("li").append( | 							el("li").append( | ||||||
| 								el("a", { | 								el("a", { | ||||||
| 							textContent: "All", | 									textContent, | ||||||
| 							className: S(()=> pageS.get() === "all" ? "selected" : ""), | 									classList: { selected: S(()=> pageS.get() === textContent.toLowerCase()) }, | ||||||
| 							href: "#" | 									href: `#${textContent.toLowerCase()}` | ||||||
| 						}), | 								}) | ||||||
| 					), | 							) | ||||||
| 					el("li").append( | 						) | ||||||
| 						el("a", { | 					), | ||||||
| 							textContent: "Active", | 				), | ||||||
| 							className: S(()=> pageS.get() === "active" ? "selected" : ""), | 				length - todosRemainingS.get() === 0 | ||||||
| 							href: "#active" | 					? el() | ||||||
| 						}), | 					: memo("delete", () => | ||||||
| 					), | 						el("button", | ||||||
| 					el("li").append( | 							{ textContent: "Clear completed", className: "clear-completed" }, | ||||||
| 						el("a", { | 							onClearCompleted) | ||||||
| 							textContent: "Completed", | 					) | ||||||
| 							className: S(()=> pageS.get() === "completed" ? "selected" : ""), |  | ||||||
| 							href: "#completed" |  | ||||||
| 						}), |  | ||||||
| 					) |  | ||||||
| 				), |  | ||||||
| 				S.el(S(() => todosS.get().some(todo => todo.completed)), |  | ||||||
| 					hasTodosCompleted=> hasTodosCompleted |  | ||||||
| 					? el("button", { textContent: "Clear completed", className: "clear-completed" }, onClearCompleted) |  | ||||||
| 					: el() |  | ||||||
| 			) | 			) | ||||||
| 		) | 		) | ||||||
| 			: el() |  | ||||||
| 		)) |  | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -177,10 +161,11 @@ function TodoItem({ id, title, completed }) { | |||||||
| 		} | 		} | ||||||
| 		isEditing.set(false); | 		isEditing.set(false); | ||||||
| 	}); | 	}); | ||||||
|  | 	const formEdit = "edit"; | ||||||
| 	/** @type {ddeElementAddon<HTMLFormElement>} */ | 	/** @type {ddeElementAddon<HTMLFormElement>} */ | ||||||
| 	const onSubmitEdit = on("submit", event => { | 	const onSubmitEdit = on("submit", event => { | ||||||
| 		event.preventDefault(); | 		event.preventDefault(); | ||||||
| 		const input = /** @type {HTMLFormElement} */(event.target).elements.namedItem("edit"); | 		const input = /** @type {HTMLFormElement} */(event.target).elements.namedItem(formEdit); | ||||||
| 		const value = /** @type {HTMLInputElement} */(input).value.trim(); | 		const value = /** @type {HTMLInputElement} */(input).value.trim(); | ||||||
| 		if (value) { | 		if (value) { | ||||||
| 			dispatchEdit({ id, title: value }); | 			dispatchEdit({ id, title: value }); | ||||||
| @@ -207,18 +192,17 @@ function TodoItem({ id, title, completed }) { | |||||||
| 				checked: completed | 				checked: completed | ||||||
| 			}, onToggleCompleted), | 			}, onToggleCompleted), | ||||||
| 			el("label", { textContent: title }, onStartEdit), | 			el("label", { textContent: title }, onStartEdit), | ||||||
| 			el("button", { className: "destroy" }, onDelete) | 			el("button", { ariaLabel: "Delete todo", className: "destroy" }, onDelete) | ||||||
| 		), | 		), | ||||||
| 		S.el(isEditing, editing => editing | 		S.el(isEditing, editing => !editing | ||||||
| 			? el("form", null, onSubmitEdit).append( | 			? el() | ||||||
|  | 			: el("form", null, onSubmitEdit).append( | ||||||
| 				el("input", { | 				el("input", { | ||||||
| 					className: "edit", | 					className: "edit", | ||||||
| 					name: "edit", | 					name: formEdit, | ||||||
| 					value: title, | 					value: title, | ||||||
| 					"data-id": id |  | ||||||
| 				}, onBlurEdit, onKeyDown, addFocus) | 				}, onBlurEdit, onKeyDown, addFocus) | ||||||
| 			) | 			) | ||||||
| 			: el() |  | ||||||
| 		) | 		) | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
| @@ -342,6 +326,7 @@ function todosSignal(){ | |||||||
| 			localStorage.setItem(store_key, JSON.stringify(value)); | 			localStorage.setItem(store_key, JSON.stringify(value)); | ||||||
| 		} catch (e) { | 		} catch (e) { | ||||||
| 			console.error("Failed to save todos to localStorage", e); | 			console.error("Failed to save todos to localStorage", e); | ||||||
|  | 			// Optionally, provide user feedback | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
| 	return out; | 	return out; | ||||||
| @@ -350,20 +335,30 @@ function todosSignal(){ | |||||||
| /** | /** | ||||||
|  * Creates a signal for managing route state |  * Creates a signal for managing route state | ||||||
|  * |  * | ||||||
|  * @param {typeof S} signal - The signal constructor |  * @param {typeof S} signal - The signal constructor from a library | ||||||
|  |  * @param {AbortSignal} abortSignal | ||||||
|  */ |  */ | ||||||
| function routerSignal(signal){ | function routerSignal(signal, abortSignal){ | ||||||
| 	const initial = location.hash.replace("#", "") || "all"; | 	const initial = location.hash.replace("#", "") || "all"; | ||||||
| 	return signal(initial, { | 	const out = signal(initial, { | ||||||
| 		/** | 		/** | ||||||
| 		 * Set the current route | 		 * Set the current route | ||||||
| 		 * @param {"all"|"active"|"completed"} hash - The route to set | 		 * @param {"all"|"active"|"completed"} hash - The route to set | ||||||
| 		 */ | 		 */ | ||||||
| 		set(hash){ | 		set(hash){ | ||||||
| 			location.hash = hash; | 			location.hash = hash; | ||||||
| 			this.value = hash; | 			//this.value = hash; | ||||||
| 		} | 		}, | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
|  | 	// Setup hash change listener | ||||||
|  | 	window.addEventListener("hashchange", () => { | ||||||
|  | 		const hash = location.hash.replace("#", "") || "all"; | ||||||
|  | 		//S.action(out, "set", /** @type {"all"|"active"|"completed"} */(hash)); | ||||||
|  | 		out.set(hash); | ||||||
|  | 	}, { signal: abortSignal }); | ||||||
|  |  | ||||||
|  | 	return out; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ const todos= S([], { | |||||||
| 		const removed= this.value.pop(); | 		const removed= this.value.pop(); | ||||||
| 		if(removed) S.clear(removed); | 		if(removed) S.clear(removed); | ||||||
| 	}, | 	}, | ||||||
| 	[S.symbols.onclear](){ // this covers `O.clear(todos)` | 	[S.symbols.onclear](){ // this covers `S.clear(todos)` | ||||||
| 		S.clear(...this.value); | 		S.clear(...this.value); | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
|   | |||||||
							
								
								
									
										83
									
								
								docs/components/getLibraryUrl.html.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								docs/components/getLibraryUrl.html.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | |||||||
|  | import { styles, page_id } from "../ssr.js"; | ||||||
|  |  | ||||||
|  | styles.css` | ||||||
|  | #library-url-form { | ||||||
|  | 	display: flex; | ||||||
|  | 	flex-flow: column nowrap; | ||||||
|  | 	gap: 1rem; | ||||||
|  | 	padding: 1.5rem; | ||||||
|  | 	border-radius: var(--border-radius); | ||||||
|  | 	background-color: var(--bg-sidebar); | ||||||
|  | 	box-shadow: var(--shadow); | ||||||
|  | 	border: 1px solid var(--border); | ||||||
|  | 	margin: 1.5rem 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #library-url-form .selectors { | ||||||
|  | 	display: flex; | ||||||
|  | 	flex-flow: row wrap; | ||||||
|  | 	gap: 0.75rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #library-url-form output { | ||||||
|  | 	display: flex; | ||||||
|  | 	flex-flow: column nowrap; | ||||||
|  | 	gap: 0.75rem; | ||||||
|  | 	margin-top: 0.5rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #library-url-form output p { | ||||||
|  | 	font-weight: 500; | ||||||
|  | 	margin: 0.25rem 0; | ||||||
|  | 	color: var(--text-light); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #library-url-form .url-title { | ||||||
|  | 	display: flex; | ||||||
|  | 	align-items: center; | ||||||
|  | 	gap: 0.5rem; | ||||||
|  | 	margin-bottom: -0.25rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #library-url-form .url-title strong { | ||||||
|  | 	font-family: var(--font-mono); | ||||||
|  | 	font-size: 0.95rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #library-url-form .url-title span { | ||||||
|  | 	color: var(--text-light); | ||||||
|  | 	font-size: 0.9rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #library-url-form .code { | ||||||
|  | 	margin-bottom: 1rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #library-url-form .info-text { | ||||||
|  | 	font-size: 0.9rem; | ||||||
|  | 	font-style: italic; | ||||||
|  | 	margin-top: 1rem; | ||||||
|  | 	color: var(--text-light); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @media (max-width: 768px) { | ||||||
|  | 	#library-url-form .selectors { | ||||||
|  | 		flex-direction: column; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	#library-url-form select { | ||||||
|  | 		width: 100%; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | `; | ||||||
|  |  | ||||||
|  | import { el } from "deka-dom-el"; | ||||||
|  | import { ireland } from "./ireland.html.js"; | ||||||
|  |  | ||||||
|  | export function getLibraryUrl(){ | ||||||
|  | 	return el(ireland, { | ||||||
|  | 		src: new URL("./getLibraryUrl.js.js", import.meta.url), | ||||||
|  | 		exportName: "getLibraryUrl", | ||||||
|  | 		page_id, | ||||||
|  | 	}); | ||||||
|  | } | ||||||
							
								
								
									
										92
									
								
								docs/components/getLibraryUrl.js.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								docs/components/getLibraryUrl.js.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | |||||||
|  | import { el, on } from "deka-dom-el"; | ||||||
|  | import { S } from "deka-dom-el/signals"; | ||||||
|  |  | ||||||
|  | const url_base= { | ||||||
|  | 	jsdeka: "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/", | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export function getLibraryUrl(){ | ||||||
|  | 	const lib= S([ "esm", "-with-signals", ".min" ]); | ||||||
|  | 	const url= S(()=> url_base.jsdeka+lib.get().join("")); | ||||||
|  | 	const urlLabel= S(() => { | ||||||
|  | 		const [format, signalsPart, minified] = lib.get(); | ||||||
|  | 		const formatText = format === "esm" ? "ES Module" : "IIFE"; | ||||||
|  | 		const signalsText = signalsPart ? " with signals" : ""; | ||||||
|  | 		const minText = minified ? " (minified)" : ""; | ||||||
|  | 		return `${formatText}${signalsText}${minText}`; | ||||||
|  | 	}) | ||||||
|  | 	const onSubmit= on("submit", ev => { | ||||||
|  | 		ev.preventDefault(); | ||||||
|  | 		const form= new FormData(/** @type {HTMLFormElement} */ (ev.target)); | ||||||
|  | 		lib.set([ | ||||||
|  | 			"module", | ||||||
|  | 			"what", | ||||||
|  | 			"minified", | ||||||
|  | 		].map(name => /** @type {string} */(form.get(name)))); | ||||||
|  | 	}); | ||||||
|  | 	const onChangeSubmit= on("change", | ||||||
|  | 		ev=> /** @type {HTMLSelectElement} */(ev.target).form.requestSubmit() | ||||||
|  | 	); | ||||||
|  |  | ||||||
|  | 	return el("form", { id: "library-url-form" }, onSubmit).append( | ||||||
|  | 		el("h4", "Select your preferred library format:"), | ||||||
|  | 		el("div", { className: "selectors" }).append( | ||||||
|  | 			el("select", { name: "module" }, onChangeSubmit, | ||||||
|  | 				on.defer(select => select.value = lib.get()[0]), | ||||||
|  | 			).append( | ||||||
|  | 				el("option", { value: "esm", textContent: "ESM — modern JavaScript module" }), | ||||||
|  | 				el("option", { value: "iife", textContent: "IIFE — legacy JavaScript with DDE global variable" }), | ||||||
|  | 			), | ||||||
|  | 			el("select", { name: "what" }, onChangeSubmit, | ||||||
|  | 				on.defer(select => select.value = lib.get()[1]), | ||||||
|  | 			).append( | ||||||
|  | 				el("option", { value: "", textContent: "DOM part only" }), | ||||||
|  | 				el("option", { value: "-with-signals", textContent: "DOM + signals" }), | ||||||
|  | 			), | ||||||
|  | 			el("select", { name: "minified" }, onChangeSubmit, | ||||||
|  | 				on.defer(select => select.value = lib.get()[2]), | ||||||
|  | 			).append( | ||||||
|  | 				el("option", { value: "", textContent: "Unminified" }), | ||||||
|  | 				el("option", { value: ".min", textContent: "Minified" }), | ||||||
|  | 			), | ||||||
|  | 		), | ||||||
|  | 		el("output").append( | ||||||
|  | 			el("div", { className: "url-title" }).append( | ||||||
|  | 				el("strong", "JavaScript:"), | ||||||
|  | 				el("span", urlLabel), | ||||||
|  | 			), | ||||||
|  | 			el(code, { value: S(()=> url.get()+".js") }), | ||||||
|  | 			el("div", { className: "url-title" }).append( | ||||||
|  | 				el("strong", "TypeScript definition:") | ||||||
|  | 			), | ||||||
|  | 			el(code, { value: S(()=> url.get()+".d.ts") }), | ||||||
|  | 			el("p", { className: "info-text", | ||||||
|  | 				textContent: "Use the CDN URL in your HTML or import it in your JavaScript files." | ||||||
|  | 			}) | ||||||
|  | 		) | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | /** @param {{ value: ddeSignal<string> }} props */ | ||||||
|  | function code({ value }){ | ||||||
|  | 	/** @type {ddeSignal<"Copy"|"Copied!">} */ | ||||||
|  | 	const textContent= S("Copy"); | ||||||
|  | 	const onCopy= on("click", () => { | ||||||
|  | 		navigator.clipboard.writeText(value.get()); | ||||||
|  |  | ||||||
|  | 		textContent.set("Copied!"); | ||||||
|  | 		setTimeout(() => { | ||||||
|  | 			textContent.set("Copy"); | ||||||
|  | 		}, 1500); | ||||||
|  | 	}); | ||||||
|  | 	return el("div", { className: "code", dataJs: "done", tabIndex: 0 }).append( | ||||||
|  | 		el("code").append( | ||||||
|  | 			el("pre", value), | ||||||
|  | 		), | ||||||
|  | 		el("button", { | ||||||
|  | 			className: "copy-button", | ||||||
|  | 			textContent, | ||||||
|  | 			ariaLabel: "Copy code to clipboard", | ||||||
|  | 		}, onCopy) | ||||||
|  | 	) | ||||||
|  | 	; | ||||||
|  | } | ||||||
| @@ -1,3 +1,33 @@ | |||||||
|  | import { styles } from "../ssr.js"; | ||||||
|  | styles.css` | ||||||
|  | [data-dde-mark] { | ||||||
|  | 	opacity: .5; | ||||||
|  | 	filter: grayscale(); | ||||||
|  |  | ||||||
|  | 	@media (prefers-reduced-motion: no-preference) { | ||||||
|  | 		animation: fadein 2s infinite ease forwards;; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	position: relative; | ||||||
|  | 	&::after { | ||||||
|  | 		content: "Loading Ireland…"; | ||||||
|  | 		background-color: rgba(0, 0, 0, .5); | ||||||
|  | 		color: white; | ||||||
|  | 		font-weight: bold; | ||||||
|  | 		border-radius: 5px; | ||||||
|  | 		padding: 5px 10px; | ||||||
|  | 		position: absolute; | ||||||
|  | 		top: 3%; | ||||||
|  | 		left: 50%; | ||||||
|  | 		transform: translateX(-50%); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @keyframes fadein { | ||||||
|  | 	from { opacity: .5; } | ||||||
|  | 	to { opacity: .85; } | ||||||
|  | } | ||||||
|  | `; | ||||||
|  |  | ||||||
| import { el, queue } from "deka-dom-el"; | import { el, queue } from "deka-dom-el"; | ||||||
| import { addEventListener, registerClientFile } from "../ssr.js"; | import { addEventListener, registerClientFile } from "../ssr.js"; | ||||||
| import { relative } from "node:path"; | import { relative } from "node:path"; | ||||||
| @@ -13,7 +43,6 @@ 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 = {} }) { | ||||||
| @@ -21,10 +50,17 @@ export function ireland({ src, exportName = "default", props = {} }) { | |||||||
| 	const path= "./"+relative(dir, src.pathname); | 	const path= "./"+relative(dir, src.pathname); | ||||||
| 	const id = "ireland-" + generateComponentId(src); | 	const id = "ireland-" + generateComponentId(src); | ||||||
| 	const element = el.mark({ type: "later", name: ireland.name }); | 	const element = el.mark({ type: "later", name: ireland.name }); | ||||||
| 	queue(import(path).then(module => { | 	queue( | ||||||
|  | 		import(path) | ||||||
|  | 		.then(module => { | ||||||
| 			const component = module[exportName]; | 			const component = module[exportName]; | ||||||
| 		element.replaceWith(el(component, props, mark(id))); | 			const content= el(component, props, mark(id)); | ||||||
| 	})); | 			element.replaceWith(content); | ||||||
|  | 			content.querySelectorAll("input, textarea, button") | ||||||
|  | 				.forEach(el=> el.disabled= true); | ||||||
|  | 		}) | ||||||
|  | 		.catch(console.error) | ||||||
|  | 	); | ||||||
|  |  | ||||||
| 	if(!componentsRegistry.size) | 	if(!componentsRegistry.size) | ||||||
| 		addEventListener("oneachrender", registerClientPart); | 		addEventListener("oneachrender", registerClientPart); | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ export function mnemonic(){ | |||||||
| 		), | 		), | ||||||
| 		el("li").append( | 		el("li").append( | ||||||
| 			el("code", "el(<tag-name>, <primitive>)[.append(...)]: <element-from-tag-name>"), | 			el("code", "el(<tag-name>, <primitive>)[.append(...)]: <element-from-tag-name>"), | ||||||
| 			" — simple element containing only text", | 			" — simple element containing only text (accepts string, number or signal)", | ||||||
| 		), | 		), | ||||||
| 		el("li").append( | 		el("li").append( | ||||||
| 			el("code", "el(<tag-name>, <object>)[.append(...)]: <element-from-tag-name>"), | 			el("code", "el(<tag-name>, <object>)[.append(...)]: <element-from-tag-name>"), | ||||||
| @@ -26,6 +26,6 @@ export function mnemonic(){ | |||||||
| 		el("li").append( | 		el("li").append( | ||||||
| 			el("code", "elNS(<namespace>)(<as-el-see-above>)[.append(...)]: <element-based-on-arguments>"), | 			el("code", "elNS(<namespace>)(<as-el-see-above>)[.append(...)]: <element-based-on-arguments>"), | ||||||
| 			" — typically SVG elements", | 			" — typically SVG elements", | ||||||
| 		) | 		), | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,6 +12,10 @@ 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>]))") | ||||||
|   | |||||||
| @@ -14,10 +14,6 @@ export function mnemonic(){ | |||||||
| 			el("code", "S.on(<signal>, <listener>[, <options>])"), | 			el("code", "S.on(<signal>, <listener>[, <options>])"), | ||||||
| 			" — listen to the signal value changes", | 			" — listen to the signal value changes", | ||||||
| 		), | 		), | ||||||
| 		el("li").append( |  | ||||||
| 			el("code", "S.clear(...<signals>)"), |  | ||||||
| 			" — off and clear signals", |  | ||||||
| 		), |  | ||||||
| 		el("li").append( | 		el("li").append( | ||||||
| 			el("code", "S(<value>, <actions>)"), | 			el("code", "S(<value>, <actions>)"), | ||||||
| 			" — signal: pattern to create complex reactive objects/arrays", | 			" — signal: pattern to create complex reactive objects/arrays", | ||||||
| @@ -29,6 +25,11 @@ export function mnemonic(){ | |||||||
| 		el("li").append( | 		el("li").append( | ||||||
| 			el("code", "S.el(<signal>, <function-returning-dom>)"), | 			el("code", "S.el(<signal>, <function-returning-dom>)"), | ||||||
| 			" — render partial dom structure (template) based on the current signal value", | 			" — render partial dom structure (template) based on the current signal value", | ||||||
| 		) | 		), | ||||||
|  | 		el("li").append( | ||||||
|  | 			el("code", "S.clear(...<signals>)"), | ||||||
|  | 			" — off and clear signals (most of the time it is not needed as reactive ", | ||||||
|  | 			"attributes and elements are handled automatically)", | ||||||
|  | 		), | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -76,11 +76,9 @@ export function h3({ textContent, id }){ | |||||||
| 				el("a", { | 				el("a", { | ||||||
| 					className: "heading-anchor", | 					className: "heading-anchor", | ||||||
| 					href: "#"+id, | 					href: "#"+id, | ||||||
| 						textContent: "#", |  | ||||||
| 					title: `Link to this section: ${textContent}`, | 					title: `Link to this section: ${textContent}`, | ||||||
| 						"aria-label": `Link to section ${textContent}` |  | ||||||
| 				}), | 				}), | ||||||
| 				" ", | 				"# ", | ||||||
| 				textContent, | 				textContent, | ||||||
| 		); | 		); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -37,3 +37,14 @@ styles.css` | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| `;
 | `;
 | ||||||
|  | 
 | ||||||
|  | import { el } from "deka-dom-el"; | ||||||
|  | import { ireland } from "./ireland.html.js"; | ||||||
|  | 
 | ||||||
|  | export function scrollTop(){ | ||||||
|  | 	return el(ireland, { | ||||||
|  | 		src: new URL("./scrollTop.js.js", import.meta.url), | ||||||
|  | 		exportName: "scrollTop", | ||||||
|  | 		page_id: "*", | ||||||
|  | 	}); | ||||||
|  | } | ||||||
| @@ -86,7 +86,7 @@ html { | |||||||
| } | } | ||||||
|  |  | ||||||
| :focus-visible { | :focus-visible { | ||||||
| 	outline: 3px solid hsl(231, 48%, 70%); | 	outline: 3px solid var(--primary-light); | ||||||
| 	outline-offset: 2px; | 	outline-offset: 2px; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -193,6 +193,34 @@ pre code { | |||||||
| 	background-color: transparent; | 	background-color: transparent; | ||||||
| 	padding: 0; | 	padding: 0; | ||||||
| } | } | ||||||
|  | figure { | ||||||
|  | 	width: 100%; | ||||||
|  | 	text-align: center; | ||||||
|  | 	color: var(--text-light); | ||||||
|  | 	border: 1px dashed var(--border); | ||||||
|  | 	border-radius: var(--border-radius); | ||||||
|  |  | ||||||
|  | 	img { | ||||||
|  | 		object-fit: contain; | ||||||
|  | 		border-radius: var(--border-radius); | ||||||
|  | 		box-shadow: var(--shadow); | ||||||
|  | 		max-width: 100%; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | select { | ||||||
|  | 	padding: 0.5rem 0.75rem; | ||||||
|  | 	border-radius: var(--border-radius); | ||||||
|  | 	border: 1px solid var(--border); | ||||||
|  | 	background-color: var(--bg); | ||||||
|  | 	color: var(--text); | ||||||
|  | 	cursor: pointer; | ||||||
|  | 	font-size: 0.95rem; | ||||||
|  | 	font-family: var(--font-main); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | select:hover { | ||||||
|  | 	border-color: var(--primary); | ||||||
|  | } | ||||||
|  |  | ||||||
| /* Layout */ | /* Layout */ | ||||||
| body { | body { | ||||||
| @@ -234,7 +262,7 @@ body > main { | |||||||
| } | } | ||||||
| body > main > *, body > main slot > * { | body > main > *, body > main slot > * { | ||||||
| 	width: 100%; | 	width: 100%; | ||||||
| 	max-width: 100%; | 	max-width: calc(var(--body-max-width) * 5/3); | ||||||
| 	margin-inline: auto; | 	margin-inline: auto; | ||||||
| 	grid-column: main; | 	grid-column: main; | ||||||
| } | } | ||||||
| @@ -267,9 +295,8 @@ body > main h3, body > main h4 { | |||||||
| /* Boxes */ | /* Boxes */ | ||||||
| .illustration{ | .illustration{ | ||||||
| 	grid-column: full-main; | 	grid-column: full-main; | ||||||
| 	width: calc(100% - .75em); |  | ||||||
| } | } | ||||||
| .illustration:not(:has( .comparison)){ | .illustration:not(:has( .comparison)):not(:has( .tabs)) { | ||||||
| 	grid-column: main; | 	grid-column: main; | ||||||
|  |  | ||||||
| 	pre { | 	pre { | ||||||
|   | |||||||
| @@ -1,9 +1,10 @@ | |||||||
|  | import "./components/getLibraryUrl.html.js"; | ||||||
| import { t, T } from "./utils/index.js"; | 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`A lightweight, reactive DOM library for creating dynamic UIs with a declarative syntax`, | 	description: t`Reactive DOM library for creating dynamic UIs with a declarative syntax`, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| import { el } from "deka-dom-el"; | import { el } from "deka-dom-el"; | ||||||
| @@ -11,10 +12,15 @@ import { simplePage } from "./layout/simplePage.html.js"; | |||||||
| import { h3 } from "./components/pageUtils.html.js"; | import { h3 } from "./components/pageUtils.html.js"; | ||||||
| import { example } from "./components/example.html.js"; | import { example } from "./components/example.html.js"; | ||||||
| import { code } from "./components/code.html.js"; | import { code } from "./components/code.html.js"; | ||||||
|  | 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= { | ||||||
| 	w_mvv:{ | 	npm: { | ||||||
|  | 		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", | ||||||
| 	}, | 	}, | ||||||
| @@ -25,27 +31,31 @@ 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 lightweight library for building dynamic UIs with | 			Welcome to Deka DOM Elements (dd<el> or DDE) — a 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. | ||||||
| 		`), | 		`), | ||||||
| 		el("div", { className: "callout" }).append( | 		el("div", { className: "callout" }).append( | ||||||
| 			el("h4", t`What Makes dd<el> Special`), | 			el("h4", t`Key Benefits of dd<el>`), | ||||||
| 			el("ul").append( | 			el("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`Lightweight core (~10–15kB minified) without unnecessary dependencies (0 at now 😇)`), | 				el("li", t`Minimalized footprint:`), | ||||||
|  | 				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 reactivity with simplified but powerful signals system`), | 				el("li", t`Built-in (but optional) 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"), page_id }), | 		el(example, { src: fileURL("./components/examples/introducing/helloWorld.js") }), | ||||||
|  |  | ||||||
| 		el(h3, { textContent: t`The 3PS Pattern: A Better Way to Build UIs`, id: "h-3ps" }), | 		el(h3, { textContent: t`The 3PS Pattern: Simplified architecture pattern`, id: "h-3ps" }), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			At the heart of dd<el> is the 3PS (3-Part Separation) pattern. This simple yet powerful approach helps you | 			At the heart of dd<el> is the 3PS (3-Part Separation) pattern. This simple yet powerful approach helps you | ||||||
| 			organize your UI code into three distinct areas, making your applications more maintainable and easier | 			organize your UI code into three distinct areas, making your applications more maintainable and easier | ||||||
| 			to reason about. | 			to reason about. | ||||||
| @@ -54,75 +64,114 @@ 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"), page_id }), | 					el(code, { src: fileURL("./components/examples/introducing/3ps-before.js") }), | ||||||
| 				), | 				), | ||||||
| 				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"), page_id }), | 					el(code, { src: fileURL("./components/examples/introducing/3ps.js") }), | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The 3PS pattern separates your code into three clear parts: | 			The 3PS pattern separates your code into three clear parts: | ||||||
| 		`), | 		`), | ||||||
| 		el("ol").append( | 		el("ol").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Create State")}: Define your application’s reactive data using signals | 				${el("strong", "Create State")}: Define your application’s reactive data using signals | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Bind to Elements")}: Define how UI elements react to state changes | 				${el("strong", "React to Changes")}: Define how UI elements and other parts of your app react to state | ||||||
|  | 				changes | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Update State")}: Modify state in response to user events or other triggers | 				${el("strong", "Update State")}: Modify state in response to user events or other triggers | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			By separating these concerns, your code becomes more modular, testable, and easier to maintain. This | 			By separating these concerns, your code becomes more modular, testable, and easier to maintain. This | ||||||
| 			approach shares principles with more formal patterns like ${el("a", { textContent: "MVVM", | 			approach ${el("strong", "is not something new and/or special to dd<el>")}. It’s based on ${el("a", { | ||||||
| 				...references.w_mvv })} and ${el("a", { textContent: "MVC", ...references.w_mvc })}, but with less | 				textContent: "MVC", ...references.w_mvc })} (${el("a", { textContent: "MVVM", ...references.w_mvv })}), | ||||||
| 			overhead and complexity. | 			but is there presented in simpler form. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				The 3PS pattern becomes especially powerful when combined with components, allowing you to create | 				The 3PS pattern becomes especially powerful when combined with components, allowing you to create | ||||||
| 				reusable pieces of UI with encapsulated state and behavior. You’ll learn more about this in the | 				reusable pieces of UI with encapsulated state and behavior. You’ll learn more about this in the | ||||||
| 				following sections. | 				following sections. | ||||||
|  | 			`), | ||||||
|  | 			el("p").append(T` | ||||||
|  | 				The 3PS pattern isn’t required to use with dd<el> but it is good practice to follow it or some similar | ||||||
|  | 				software architecture. | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
|  | 		el(h3, t`Getting Started`), | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			There are multiple ways to include dd<el> in your project. You can use npm for a full development setup, | ||||||
|  | 			or directly include it from a CDN for quick prototyping. | ||||||
|  | 		`), | ||||||
|  | 		el("h4", "npm installation"), | ||||||
|  | 		el(code, { content: "npm install deka-dom-el --save", language: "shell" }), | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			…see ${el("a", { textContent: "package page", ...references.npm, target: "_blank" })}. | ||||||
|  | 		`), | ||||||
|  |  | ||||||
|  | 		el("h4", "CDN / Direct Script Usage"), | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			Use the interactive selector below to choose your preferred format: | ||||||
|  | 		`), | ||||||
|  | 		el(getLibraryUrl), | ||||||
|  | 		el("div", { className: "note" }).append( | ||||||
|  | 			el("p").append(T` | ||||||
|  | 				Based on your selection, you can use dd<el> in your project like this: | ||||||
|  | 			`), | ||||||
|  | 			el(code, { content: ` | ||||||
|  | 				// ESM format (modern JavaScript with import/export) | ||||||
|  | 				import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.min.js"; | ||||||
|  |  | ||||||
|  | 				// Or with IIFE format (creates a global DDE object) | ||||||
|  | 				// <script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/iife-with-signals.min.js"></script> | ||||||
|  | 				const { el, on } = DDE; | ||||||
|  | 			`, language: "js" }), | ||||||
|  | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`How to Use This Documentation`), | 		el(h3, t`How to Use This Documentation`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			This guide will take you through dd<el>’s features step by step: | 			This guide will take you through dd<el>’s features step by step: | ||||||
| 		`), | 		`), | ||||||
| 		el("ol", { start: 2 }).append( | 		el("ol", { start: 2 }).append( | ||||||
| 			el("li").append(...T`${el("a", { href: "p02-elements.html" }).append(el("strong", "Elements"))} — Creating | 			el("li").append(T`${el("a", { href: "p02-elements.html" }).append(el("strong", "Elements"))} — Creating | ||||||
| 				and manipulating DOM elements`), | 				and manipulating DOM elements`), | ||||||
| 			el("li").append(...T`${el("a", { href: "p03-events.html" }).append(el("strong", "Events and Addons"))} — | 			el("li").append(T`${el("a", { href: "p03-events.html" }).append(el("strong", "Events and Addons"))} — | ||||||
| 				Handling user interactions and lifecycle events`), | 				Handling user interactions and lifecycle events`), | ||||||
| 			el("li").append(...T`${el("a", { href: "p04-signals.html" }).append(el("strong", "Signals"))} — Adding | 			el("li").append(T`${el("a", { href: "p04-signals.html" }).append(el("strong", "Signals"))} — Adding | ||||||
| 				reactivity to your UI`), | 				reactivity to your UI`), | ||||||
| 			el("li").append(...T`${el("a", { href: "p05-scopes.html" }).append(el("strong", "Scopes"))} — Managing | 			el("li").append(T`${el("a", { href: "p05-scopes.html" }).append(el("strong", "Scopes"))} — Managing | ||||||
| 				component lifecycles`), | 				component lifecycles`), | ||||||
| 			el("li").append(...T`${el("a", { href: "p06-customElement.html" }).append(el("strong", "Web Components"))} — | 			el("li").append(T`${el("a", { href: "p06-customElement.html" }).append(el("strong", "Web Components"))} — | ||||||
| 				Building native custom elements`), | 				Building native custom elements`), | ||||||
| 			el("li").append(...T`${el("a", { href: "p07-debugging.html" }).append(el("strong", "Debugging"))} — Tools to | 			el("li").append(T`${el("a", { href: "p07-debugging.html" }).append(el("strong", "Debugging"))} — Tools to | ||||||
| 				help you build and fix your apps`), | 				help you build and fix your apps`), | ||||||
| 			el("li").append(...T`${el("a", { href: "p08-extensions.html" }).append(el("strong", "Extensions"))} — | 			el("li").append(T`${el("a", { href: "p08-extensions.html" }).append(el("strong", "Extensions"))} — | ||||||
| 				Integrating third-party functionalities`), | 				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>`), | ||||||
| 			el("li").append(...T`${el("a", { href: "p12-ireland.html" }).append(el("strong", "Ireland Components"))} — | 			el("li").append(T`${el("a", { href: "p12-ireland.html" }).append(el("strong", "Ireland Components"))} — | ||||||
| 				Interactive demos with server-side pre-rendering`), | 				Interactive demos with server-side pre-rendering`), | ||||||
| 			el("li").append(...T`${el("a", { href: "p13-appendix.html" }).append(el("strong", "Appendix & Summary"))} — | 			el("li").append(T`${el("a", { href: "p13-appendix.html" }).append(el("strong", "Appendix & Summary"))} — | ||||||
| 				Comprehensive reference and best practices`), | 				Comprehensive reference and best practices`), | ||||||
|  | 			el("li").append(T`${el("a", { href: "p14-converter.html" }).append(el("strong", "HTML Converter"))} — | ||||||
|  | 				Convert HTML to dd<el> JavaScript code`), | ||||||
|  | 			el("li").append(T`${el("a", { href: "p15-examples.html" }).append(el("strong", "Examples Gallery"))} — | ||||||
|  | 				Real-world application examples and case studies`), | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Each section builds on the previous ones, so we recommend following them in order. | 			Each section builds on the previous ones, so we recommend following them in order. | ||||||
| 			Let’s get started with the basics of creating elements! | 			Let’s get started with the basics of creating elements! | ||||||
| 		`), | 		`), | ||||||
|   | |||||||
| @@ -151,7 +151,7 @@ export function header({ info: { href, title, description }, pkg }){ | |||||||
| 				), | 				), | ||||||
| 				el("span", { | 				el("span", { | ||||||
| 					className: "version-badge", | 					className: "version-badge", | ||||||
| 					"aria-label": "Version", | 					ariaLabel: "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", | ||||||
| 		"aria-label": "Main navigation", | 		ariaLabel: "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", | ||||||
| 			"aria-label": "View on GitHub", | 			ariaLabel: "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}`, | ||||||
| 				"aria-current": isCurrent ? "page" : null, | 				ariaCurrent: isCurrent ? "page" : null, | ||||||
| 			}).append( | 			}).append( | ||||||
| 				el("span", { | 				el("span", { | ||||||
| 					className: "nav-number", | 					className: "nav-number", | ||||||
| 					"aria-hidden": "true", | 					ariaHidden: "true", | ||||||
| 					textContent: `${i+1}. ` | 					textContent: `${i+1}. ` | ||||||
| 				}), | 				}), | ||||||
| 				p.title | 				p.title | ||||||
|   | |||||||
| @@ -3,10 +3,7 @@ import { el, simulateSlots } from "deka-dom-el"; | |||||||
|  |  | ||||||
| import { header } from "./head.html.js"; | import { header } from "./head.html.js"; | ||||||
| import { prevNext } from "../components/pageUtils.html.js"; | import { prevNext } from "../components/pageUtils.html.js"; | ||||||
| import { ireland } from "../components/ireland.html.js"; | import { scrollTop } from "../components/scrollTop.html.js"; | ||||||
| import "../components/scrollTop.css.js"; |  | ||||||
| /** @param {string} url */ |  | ||||||
| const fileURL= url=> new URL(url, import.meta.url); |  | ||||||
|  |  | ||||||
| /** @param {Pick<import("../types.d.ts").PageAttrs, "pkg" | "info">} attrs */ | /** @param {Pick<import("../types.d.ts").PageAttrs, "pkg" | "info">} attrs */ | ||||||
| export function simplePage({ pkg, info }){ | export function simplePage({ pkg, info }){ | ||||||
| @@ -33,6 +30,6 @@ export function simplePage({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		// Scroll to top button | 		// Scroll to top button | ||||||
| 		el(ireland, { src: fileURL("../components/scrollTop.js.js"), exportName: "scrollTop" }) | 		el(scrollTop), | ||||||
| 	)); | 	)); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -47,12 +47,11 @@ 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`), | ||||||
| @@ -65,10 +64,10 @@ export function page({ pkg, info }){ | |||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(code, { src: fileURL("./components/examples/elements/intro.js"), page_id }), | 		el(code, { src: fileURL("./components/examples/elements/intro.js") }), | ||||||
|  |  | ||||||
| 		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` | ||||||
| 			In standard JavaScript, you create DOM elements using the | 			In standard JavaScript, you create DOM elements using the | ||||||
| 			${el("a", references.mdn_create).append(el("code", "document.createElement()"))} method | 			${el("a", references.mdn_create).append(el("code", "document.createElement()"))} method | ||||||
| 			and then set properties individually or with ${el("code", "Object.assign()")}: | 			and then set properties individually or with ${el("code", "Object.assign()")}: | ||||||
| @@ -77,22 +76,22 @@ 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"), page_id }) | 					el(code, { src: fileURL("./components/examples/elements/native-dom-create.js") }) | ||||||
| 				), | 				), | ||||||
| 				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"), page_id }) | 					el(code, { src: fileURL("./components/examples/elements/dde-dom-create.js") }) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			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"), page_id }), | 		el(example, { src: fileURL("./components/examples/elements/dekaCreateElement.js") }), | ||||||
|  |  | ||||||
| 		el(h3, t`Advanced Property Assignment`), | 		el(h3, t`Advanced Property Assignment`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The ${el("code", "assign")} function is the heart of dd<el>’s element property handling. It is internally | 			The ${el("code", "assign")} function is the heart of dd<el>’s element property handling. It is internally | ||||||
| 			used to assign properties using the ${el("code", "el")} function. ${el("code", "assign")} provides | 			used to assign properties using the ${el("code", "el")} function. ${el("code", "assign")} provides | ||||||
| 			intelligent assignment of both ${el("a", { textContent: "properties (IDL)", ...references.mdn_idl })} | 			intelligent assignment of both ${el("a", { textContent: "properties (IDL)", ...references.mdn_idl })} | ||||||
| @@ -104,28 +103,28 @@ export function page({ pkg, info }){ | |||||||
| 				el("dd", t`Prefers IDL properties, falls back to setAttribute() when no writable property exists`), | 				el("dd", t`Prefers IDL properties, falls back to setAttribute() when no writable property exists`), | ||||||
|  |  | ||||||
| 				el("dt", t`Data and ARIA Attributes`), | 				el("dt", t`Data and ARIA Attributes`), | ||||||
| 				el("dd").append(...T`Both ${el("code", "dataset")}.* and ${el("code", "data-")}* syntaxes supported | 				el("dd").append(T`Both ${el("code", "dataset.keyName")} and ${el("code", "dataKeyName")} syntaxes are | ||||||
| 					(same for ${el("em", "ARIA")})`), | 					supported (same for ${el("code", "aria")}/${el("code", "ariaset")})`), | ||||||
|  |  | ||||||
| 				el("dt", t`Style Handling`), | 				el("dt", t`Style Handling`), | ||||||
| 				el("dd").append(...T`Accepts string or object notation for ${el("code", "style")} property`), | 				el("dd").append(T`Accepts string or object notation for ${el("code", "style")} property`), | ||||||
|  |  | ||||||
| 				el("dt", t`Class Management`), | 				el("dt", t`Class Management`), | ||||||
| 				el("dd").append(...T`Works with ${el("code", "className")}, ${el("code", "class")}, or ${el("code", | 				el("dd").append(T`Works with ${el("code", "className")} (${el("code", "class")}) and ${el("code", | ||||||
| 					"classList")} object for toggling classes`), | 					"classList")} object for toggling classes`), | ||||||
|  |  | ||||||
| 				el("dt", t`Force Modes`), | 				el("dt", t`Force Modes`), | ||||||
| 				el("dd").append(...T`Use ${el("code", "=")} prefix to force attribute mode, ${el("code", ".")} prefix to | 				el("dd").append(T`Use ${el("code", "=")} prefix to force attribute mode, ${el("code", ".")} prefix to | ||||||
| 					force property mode`), | 					force property mode`), | ||||||
|  |  | ||||||
| 				el("dt", t`Attribute Removal`), | 				el("dt", t`Attribute Removal`), | ||||||
| 				el("dd").append(...T`Pass ${el("code", "undefined")} to remove a property or attribute`) | 				el("dd").append(T`Pass ${el("code", "undefined")} to remove a property or attribute`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el(example, { src: fileURL("./components/examples/elements/dekaAssign.js"), page_id }), | 		el(example, { src: fileURL("./components/examples/elements/dekaAssign.js") }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				You can explore standard HTML element properties in the MDN documentation for | 				You can explore standard HTML element properties in the MDN documentation for | ||||||
| 				${el("a", { textContent: "HTMLElement", ...references.mdn_el })} (base class) | 				${el("a", { textContent: "HTMLElement", ...references.mdn_el })} (base class) | ||||||
| 				and specific element interfaces like ${el("a", { textContent: "HTMLParagraphElement", ...references.mdn_p })}. | 				and specific element interfaces like ${el("a", { textContent: "HTMLParagraphElement", ...references.mdn_p })}. | ||||||
| @@ -133,7 +132,7 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Building DOM Trees with Chainable Methods`), | 		el(h3, t`Building DOM Trees with Chainable Methods`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			One of the most powerful features of dd<el> is its approach to building element trees. | 			One of the most powerful features of dd<el> is its approach to building element trees. | ||||||
| 			Unlike the native DOM API which doesn’t return the parent after ${el("code", "append()")}, dd<el>’s | 			Unlike the native DOM API which doesn’t return the parent after ${el("code", "append()")}, dd<el>’s | ||||||
| 			${el("code", "append()")} always returns the parent element: | 			${el("code", "append()")} always returns the parent element: | ||||||
| @@ -142,36 +141,36 @@ 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"), page_id }) | 					el(code, { src: fileURL("./components/examples/elements/native-dom-tree.js") }) | ||||||
| 				), | 				), | ||||||
| 				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"), page_id }) | 					el(code, { src: fileURL("./components/examples/elements/dde-dom-tree.js") }) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			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"), page_id }), | 		el(example, { src: fileURL("./components/examples/elements/dekaAppend.js") }), | ||||||
|  |  | ||||||
| 		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"), page_id }), | 		el(example, { src: fileURL("./components/examples/elements/dekaBasicComponent.js") }), | ||||||
| 		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. | ||||||
| 		`), | 		`), | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				It’s helpful to use naming conventions similar to native DOM elements for your components. | 				It’s helpful to use naming conventions similar to native DOM elements for your components. | ||||||
| 				This allows you to keeps your code consistent with the DOM API. | 				This allows you to keeps your code consistent with the DOM API. | ||||||
| 			`), | 			`), | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				Use ${el("a", { textContent: "destructuring assignment", ...references.mdn_destruct })} | 				Use ${el("a", { textContent: "destructuring assignment", ...references.mdn_destruct })} | ||||||
| 				to extract the properties from the ${el("code", "props")} and pass them to the component element: | 				to extract the properties from the ${el("code", "props")} and pass them to the component element: | ||||||
| 				${el("code", "function component({ className }){ return el(\"p\", { className }); }")} for make | 				${el("code", "function component({ className }){ return el(\"p\", { className }); }")} for make | ||||||
| @@ -180,34 +179,34 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Working with SVG and Other Namespaces`), | 		el(h3, t`Working with SVG and Other Namespaces`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			For non-HTML elements like SVG, MathML, or custom namespaces, dd<el> provides the ${el("code", "elNS")} | 			For non-HTML elements like SVG, MathML, or custom namespaces, dd<el> provides the ${el("code", "elNS")} | ||||||
| 			function which corresponds to the native ${el("a", references.mdn_ns).append(el("code", | 			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"), page_id }), | 		el(example, { src: fileURL("./components/examples/elements/dekaElNS.js") }), | ||||||
| 		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. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el(h3, t`Best Practices for Declarative DOM Creation`), | 		el(h3, t`Best Practices for Declarative DOM Creation`), | ||||||
| 		el("ol").append( | 		el("ol").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Use component functions for reusable UI fragments:")} Extract common UI patterns | 				${el("strong", "Use component functions for reusable UI fragments:")} Extract common UI patterns | ||||||
| 				into reusable functions that return elements. | 				into reusable functions that return elements. | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Leverage destructuring for cleaner component code:")} Use | 				${el("strong", "Leverage destructuring for cleaner component code:")} Use | ||||||
| 				${el("a", { textContent: "destructuring", ...references.mdn_destruct })} to extract properties | 				${el("a", { textContent: "destructuring", ...references.mdn_destruct })} to extract properties | ||||||
| 				from the props object for cleaner component code. | 				from the props object for cleaner component code. | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Leverage chainable methods for better performance:")} Use chainable methods | 				${el("strong", "Leverage chainable methods for better performance:")} Use chainable methods | ||||||
| 				${el("code", ".append()")} to build complex DOM trees for better performance and cleaner code. | 				${el("code", ".append()")} to build complex DOM trees for better performance and cleaner code. | ||||||
| 			`), | 			`), | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(mnemonic) | 		el(mnemonic), | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -39,9 +39,8 @@ 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 | ||||||
| 			handling DOM events and extends this pattern with a powerful Addon system to incorporate additional | 			handling DOM events and extends this pattern with a powerful Addon system to incorporate additional | ||||||
| 			functionalities into your UI templates. | 			functionalities into your UI templates. | ||||||
| @@ -57,10 +56,10 @@ export function page({ pkg, info }){ | |||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(code, { src: fileURL("./components/examples/events/intro.js"), page_id }), | 		el(code, { src: fileURL("./components/examples/events/intro.js") }), | ||||||
|  |  | ||||||
| 		el(h3, t`Events and Listeners: Two Approaches`), | 		el(h3, t`Events and Listeners: Two Approaches`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			In JavaScript you can listen to native DOM events using | 			In JavaScript you can listen to native DOM events using | ||||||
| 			${el("a", references.mdn_listen).append(el("code", "element.addEventListener(type, listener, options)"))}. | 			${el("a", references.mdn_listen).append(el("code", "element.addEventListener(type, listener, options)"))}. | ||||||
| 			dd<el> provides an alternative approach with arguments ordered differently to better fit its declarative | 			dd<el> provides an alternative approach with arguments ordered differently to better fit its declarative | ||||||
| @@ -70,62 +69,62 @@ 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);`, page_id }) | 					el(code, { content: `element.addEventListener("click", callback, options);`, language: "js" }) | ||||||
| 				), | 				), | ||||||
| 				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);`, page_id }) | 					el(code, { content: `on("click", callback, options)(element);`, language: "js" }) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The main benefit of dd<el>’s approach is that it works as an Addon (see below), making it easy to integrate | 			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"), page_id }), | 		el(example, { src: fileURL("./components/examples/events/compare.js") }), | ||||||
|  |  | ||||||
| 		el(h3, t`Removing Event Listeners`), | 		el(h3, t`Removing Event Listeners`), | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				Unlike the native addEventListener/removeEventListener pattern, dd<el> uses the ${el("a", { | 				Unlike the native addEventListener/removeEventListener pattern, dd<el> uses ${el("strong", "only")} | ||||||
| 				textContent: "AbortSignal", ...references.mdn_abortListener })} for declarative approach for removal: | 				${el("a", { textContent: "AbortSignal", ...references.mdn_abortListener })} for declarative removal: | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
| 		el(example, { src: fileURL("./components/examples/events/abortSignal.js"), page_id }), | 		el(example, { src: fileURL("./components/examples/events/abortSignal.js") }), | ||||||
| 		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). | 			see scopes and extensions section — mainly ${el("code", "scope.signal")}). | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el(h3, t`Three Ways to Handle Events`), | 		el(h3, t`Three Ways to Handle Events`), | ||||||
| 		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"), page_id }), | 				el(code, { src: fileURL("./components/examples/events/attribute-event.js") }), | ||||||
| 				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"), page_id }), | 				el(code, { src: fileURL("./components/examples/events/property-event.js") }), | ||||||
| 				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"), page_id }), | 				el(code, { src: fileURL("./components/examples/events/chain-event.js") }), | ||||||
| 				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 })}. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el(h3, t`Understanding Addons`), | 		el(h3, t`Understanding Addons`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Addons are a powerful pattern in dd<el> that extends beyond just event handling. | 			Addons are a powerful pattern in dd<el> that extends beyond just event handling. | ||||||
| 			An Addon is any function that accepts an HTML element as its first parameter. | 			An Addon is any function that accepts an HTML element as its first parameter. | ||||||
| 		`), | 		`), | ||||||
| @@ -136,27 +135,27 @@ export function page({ pkg, info }){ | |||||||
| 				el("li", t`Set up lifecycle behaviors`), | 				el("li", t`Set up lifecycle behaviors`), | ||||||
| 				el("li", t`Integrate third-party libraries`), | 				el("li", t`Integrate third-party libraries`), | ||||||
| 				el("li", t`Create reusable element behaviors`), | 				el("li", t`Create reusable element behaviors`), | ||||||
| 				el("li", t`Capture element references`) | 				el("li", t`Capture element references`), // TODO: add example? | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			You can use Addons as ≥3rd argument of the ${el("code", "el")} function, making it possible to | 			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"), page_id }), | 		el(example, { src: fileURL("./components/examples/events/templateWithListeners.js") }), | ||||||
| 		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. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el(h3, t`Lifecycle Events`), | 		el(h3, t`Lifecycle Events`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Addons are called immediately when an element is created, even before it’s connected to the live DOM. | 			Addons are called immediately when an element is created, even before it’s connected to the live DOM. | ||||||
| 			You can think of an Addon as an "oncreate" event handler. | 			You can think of an Addon as an “oncreate” event handler. | ||||||
| 		`), | 		`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			dd<el> provides two additional lifecycle events that correspond to ${el("a", { textContent: | 			dd<el> provides two additional lifecycle events that correspond to ${el("a", { textContent: | ||||||
| 				"custom element", ...references.mdn_customElements })} lifecycle callbacks: | 				"custom element", ...references.mdn_customElements })} lifecycle callbacks and component patterns: | ||||||
| 		`), | 		`), | ||||||
| 		el("div", { className: "function-table" }).append( | 		el("div", { className: "function-table" }).append( | ||||||
| 			el("dl").append( | 			el("dl").append( | ||||||
| @@ -167,10 +166,10 @@ 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"), page_id }), | 		el(example, { src: fileURL("./components/examples/events/live-cycle.js") }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				For regular elements (non-custom elements), dd<el> uses ${el("a", | 				For regular elements (non-custom elements), dd<el> uses ${el("a", | ||||||
| 					references.mdn_mutation).append(el("code", "MutationObserver"), " | MDN")} internally to track | 					references.mdn_mutation).append(el("code", "MutationObserver"), " | MDN")} internally to track | ||||||
| 				lifecycle events. | 				lifecycle events. | ||||||
| @@ -179,47 +178,60 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 		el("div", { className: "warning" }).append( | 		el("div", { className: "warning" }).append( | ||||||
| 			el("ul").append( | 			el("ul").append( | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					Always use ${el("code", "on.*")} functions as library must ensure proper (MutationObserver) | 					Always use ${el("code", "on.*")} functions as library must ensure proper (MutationObserver) | ||||||
| 					registration, not ${el("code", "on('dde:*', ...)")}, even the native event system is used with event | 					registration, not ${el("code", "on('dde:*', ...)")}, even the native event system is used with event | ||||||
| 					names prefixed with ${el("code", "dde:")}. | 					names prefixed with ${el("code", "dde:")}. | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					Use lifecycle events sparingly, as they require internal tracking | 					Use lifecycle events sparingly, as they require internal tracking | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					Leverage parent-child relationships: when a parent is removed, all children are also removed | 					Leverage parent-child relationships: when a parent is removed, all children are also removed | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					…see section later in documentation regarding hosts elements | 					…see section later in documentation regarding hosts elements | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					dd<el> ensures that connected/disconnected events fire only once for better predictability | 					dd<el> ensures that connected/disconnected events fire only once for better predictability | ||||||
| 				`) | 				`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
|  | 		el(h3, t`Utility Helpers`), | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			You can use the ${el("code", "on.defer")} helper to defer execution to the next event loop. | ||||||
|  | 			This is useful for example when you wan to set some element properties based on the current element | ||||||
|  | 			body (typically the ${el("code", "<select value=\"...\">")}). | ||||||
|  | 		`), | ||||||
|  | 		el("div", { className: "function-table" }).append( | ||||||
|  | 			el("dl").append( | ||||||
|  | 				el("dt", t`on.defer(callback)`), | ||||||
|  | 				el("dd", t`Helper that defers function execution to the next event loop (using setTimeout)`), | ||||||
|  | 			) | ||||||
|  | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Dispatching Custom Events`), | 		el(h3, t`Dispatching Custom Events`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			This makes it easy to implement component communication through events, following standard web platform | 			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"), page_id }), | 		el(example, { src: fileURL("./components/examples/events/compareDispatch.js") }), | ||||||
| 		el(code, { src: fileURL("./components/examples/events/dispatch.js"), page_id }), | 		el(code, { src: fileURL("./components/examples/events/dispatch.js") }), | ||||||
|  |  | ||||||
| 		el(h3, t`Best Practices`), | 		el(h3, t`Best Practices`), | ||||||
| 		el("ol").append( | 		el("ol").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Clean up listeners")}: Use AbortSignal to prevent memory leaks | 				${el("strong", "Clean up listeners")}: Use AbortSignal to prevent memory leaks | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Leverage lifecycle events")}: For component setup and teardown | 				${el("strong", "Leverage lifecycle events")}: For component setup and teardown | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Delegate when possible")}: Add listeners to container elements when handling many | 				${el("strong", "Delegate when possible")}: Add listeners to container elements when handling many | ||||||
| 				similar elements | 				similar elements | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Maintain consistency")}: Choose one event binding approach and stick with it | 				${el("strong", "Maintain consistency")}: Choose one event binding approach and stick with it | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
| @@ -238,6 +250,6 @@ export function page({ pkg, info }){ | |||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(mnemonic) | 		el(mnemonic), | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -42,9 +42,8 @@ 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 | ||||||
| 			fundamental challenge of keeping your UI in sync with changing data in a declarative, efficient way. | 			fundamental challenge of keeping your UI in sync with changing data in a declarative, efficient way. | ||||||
| 		`), | 		`), | ||||||
| @@ -55,37 +54,37 @@ export function page({ pkg, info }){ | |||||||
| 				el("li", t`Automatic UI updates when data changes`), | 				el("li", t`Automatic UI updates when data changes`), | ||||||
| 				el("li", t`Clean separation between data, logic, and UI`), | 				el("li", t`Clean separation between data, logic, and UI`), | ||||||
| 				el("li", t`Small runtime with minimal overhead`), | 				el("li", t`Small runtime with minimal overhead`), | ||||||
| 				el("li").append(...T`${el("strong", "In future")} no dependencies or framework lock-in`) | 				el("li").append(T`${el("strong", "In future")} no dependencies or framework lock-in`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }), | 		el(code, { src: fileURL("./components/examples/signals/intro.js") }), | ||||||
|  |  | ||||||
| 		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` | ||||||
| 			Signals organize your code into three distinct parts, following the | 			Signals organize your code into three distinct parts, following the | ||||||
| 			${el("a", { textContent: t`3PS principle`, href: "./#h-3ps" })}: | 			${el("a", { textContent: t`3PS principle`, href: "./#h-3ps" })}: | ||||||
| 		`), | 		`), | ||||||
| 		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);", page_id }), | 				el(code, { content: "const count = S(0);", language: "js" }), | ||||||
| 				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));", page_id }), | 				el(code, { content: "S.on(count, value => updateUI(value));", language: "js" }), | ||||||
| 				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);", page_id }), | 				el(code, { content: "count.set(count.get() + 1);", language: "js" }), | ||||||
| 				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"), page_id }), | 		el(example, { src: fileURL("./components/examples/signals/signals.js") }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				Signals implement the ${el("a", { textContent: t`Publish–subscribe pattern`, ...references.wiki_pubsub | 				Signals implement the ${el("a", { textContent: t`Publish–subscribe pattern`, ...references.wiki_pubsub | ||||||
| 				})}, a form of ${el("a", { textContent: t`Event-driven programming`, ...references.wiki_event_driven | 				})}, a form of ${el("a", { textContent: t`Event-driven programming`, ...references.wiki_event_driven | ||||||
| 				})}.  This architecture allows different parts of your application to stay synchronized through | 				})}.  This architecture allows different parts of your application to stay synchronized through | ||||||
| @@ -110,30 +109,30 @@ export function page({ pkg, info }){ | |||||||
| 				el("dd", t`S.on(signal, callback) → runs callback whenever signal changes`), | 				el("dd", t`S.on(signal, callback) → runs callback whenever signal changes`), | ||||||
|  |  | ||||||
| 				el("dt", t`Unsubscribing`), | 				el("dt", t`Unsubscribing`), | ||||||
| 				el("dd").append(...T`S.on(signal, callback, { signal: abortController.signal }) → Similarly to the | 				el("dd").append(T`S.on(signal, callback, { signal: abortController.signal }) → Similarly to the | ||||||
| 					${el("code", "on")} function to register DOM events listener.`) | 					${el("code", "on")} function to register DOM events listener.`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Signals can be created with any type of value, but they work best with ${el("a", { textContent: | 			Signals can be created with any type of value, but they work best with ${el("a", { textContent: | ||||||
| 				t`primitive types`, ...references.mdn_primitive })} like strings, numbers, and booleans.  For complex | 				t`primitive types`, ...references.mdn_primitive })} like strings, numbers, and booleans.  For complex | ||||||
| 			data types like objects and arrays, you’ll want to use Actions (covered below). | 			data types like objects and arrays, you’ll want to use Actions (covered below). | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el(h3, t`Derived Signals: Computed Values`), | 		el(h3, t`Derived Signals: Computed Values`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Computed values (also called derived signals) automatically update when their dependencies change. | 			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"), page_id }), | 		el(example, { src: fileURL("./components/examples/signals/derived.js") }), | ||||||
| 		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"), page_id }), | 		el(example, { src: fileURL("./components/examples/signals/computations-abort.js") }), | ||||||
|  |  | ||||||
| 		el(h3, t`Signal Actions: For Complex State`), | 		el(h3, t`Signal Actions: For Complex State`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			When working with objects, arrays, or other complex data structures. Signal Actions provide | 			When working with objects, arrays, or other complex data structures. Signal Actions provide | ||||||
| 			a structured way to modify state while maintaining reactivity. | 			a structured way to modify state while maintaining reactivity. | ||||||
| 		`), | 		`), | ||||||
| @@ -151,7 +150,7 @@ export function page({ pkg, info }){ | |||||||
| 						}); | 						}); | ||||||
| 						// Use the action | 						// Use the action | ||||||
| 						S.action(todos, "add", "New todo"); | 						S.action(todos, "add", "New todo"); | ||||||
| 					`, page_id }) | 					`, language: "js" }) | ||||||
| 				), | 				), | ||||||
| 				el("div", { className: "bad-practice" }).append( | 				el("div", { className: "bad-practice" }).append( | ||||||
| 					el("h5", t`❌ Without Actions`), | 					el("h5", t`❌ Without Actions`), | ||||||
| @@ -161,10 +160,10 @@ 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! | ||||||
| 					`, page_id })) | 					`, language: "js" })) | ||||||
| 				), | 				), | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			In some way, you can compare it with ${el("a", { textContent: "useReducer", ...references.mdn_use_reducer })} | 			In some way, you can compare it with ${el("a", { textContent: "useReducer", ...references.mdn_use_reducer })} | ||||||
| 			hook from React. So, the ${el("code", "S(<data>, <actions>)")} pattern creates a store “machine”. We can | 			hook from React. So, the ${el("code", "S(<data>, <actions>)")} pattern creates a store “machine”. We can | ||||||
| 			then invoke (dispatch) registered action by calling ${el("code", "S.action(<signal>, <name>, ...<args>)")} | 			then invoke (dispatch) registered action by calling ${el("code", "S.action(<signal>, <name>, ...<args>)")} | ||||||
| @@ -172,9 +171,9 @@ 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"), page_id }), | 		el(example, { src: fileURL("./components/examples/signals/actions-demo.js") }), | ||||||
|  |  | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Actions provide these benefits: | 			Actions provide these benefits: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| @@ -183,17 +182,17 @@ export function page({ pkg, info }){ | |||||||
| 			el("li", t`Prevent accidental direct mutations`), | 			el("li", t`Prevent accidental direct mutations`), | ||||||
| 			el("li", t`Act similar to reducers in other state management libraries`) | 			el("li", t`Act similar to reducers in other state management libraries`) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Here’s a more complete example of a todo list using signal actions: | 			Here’s a more complete example of a todo list using signal actions: | ||||||
| 		`), | 		`), | ||||||
| 		el(example, { src: fileURL("./components/examples/signals/actions-todos.js"), page_id }), | 		el(example, { src: fileURL("./components/examples/signals/actions-todos.js") }), | ||||||
|  |  | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				${el("strong", "Special Action Methods")}: Signal actions can implement special lifecycle hooks: | 				${el("strong", "Special Action Methods")}: Signal actions can implement special lifecycle hooks: | ||||||
| 			`), | 			`), | ||||||
| 			el("ul").append( | 			el("ul").append( | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("code", "[S.symbols.onclear]()")} - Called when the signal is cleared. Use it to clean up | 					${el("code", "[S.symbols.onclear]()")} - Called when the signal is cleared. Use it to clean up | ||||||
| 					resources. | 					resources. | ||||||
| 				`), | 				`), | ||||||
| @@ -201,7 +200,7 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Connecting Signals to the DOM`), | 		el(h3, t`Connecting Signals to the DOM`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Signals really shine when connected to your UI. dd<el> provides several ways to bind signals to DOM elements: | 			Signals really shine when connected to your UI. dd<el> provides several ways to bind signals to DOM elements: | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| @@ -223,7 +222,7 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 					// Later: | 					// Later: | ||||||
| 					color.set("red"); // UI updates automatically | 					color.set("red"); // UI updates automatically | ||||||
| 				`, page_id }), | 				`, language: "js" }), | ||||||
| 			), | 			), | ||||||
| 			el("div", { className: "tab", dataTab: "elements" }).append( | 			el("div", { className: "tab", dataTab: "elements" }).append( | ||||||
| 				el("h4", t`Reactive Elements`), | 				el("h4", t`Reactive Elements`), | ||||||
| @@ -241,43 +240,108 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 					// Later: | 					// Later: | ||||||
| 					S.action(items, "push", "Dragonfruit"); // List updates automatically | 					S.action(items, "push", "Dragonfruit"); // List updates automatically | ||||||
| 				`, page_id }), | 				`, language: "js" }), | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The ${el("code", "assign")} and ${el("code", "el")} functions detect signals automatically and handle binding. | 			The ${el("code", "assign")} and ${el("code", "el")} functions detect signals automatically and handle binding. | ||||||
| 			You can use special properties like ${el("code", "dataset")}, ${el("code", "ariaset")}, and | 			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"), page_id }), | 		el(example, { src: fileURL("./components/examples/signals/dom-attrs.js") }), | ||||||
|  |  | ||||||
| 		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"), page_id }), | 		el(example, { src: fileURL("./components/examples/signals/dom-el.js") }), | ||||||
|  |  | ||||||
| 		el(h3, t`Best Practices for Signals`), | 		el(h3, t`Best Practices for Signals`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Follow these guidelines to get the most out of signals: | 			Follow these guidelines to get the most out of signals: | ||||||
| 		`), | 		`), | ||||||
| 		el("ol").append( | 		el("ol").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Keep signals small and focused")}: Use many small signals rather than a few large ones | 				${el("strong", "Keep signals small and focused")}: Use many small signals rather than a few large ones | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Use derived signals for computations")}: Don’t recompute values in multiple places | 				${el("strong", "Use derived signals for computations")}: Don’t recompute values in multiple places | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Clean up signal subscriptions")}: Use AbortController (scope.host()) to prevent memory | 				${el("strong", "Clean up signal subscriptions")}: Use AbortController (scope.host()) to prevent memory | ||||||
| 				leaks | 				leaks | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Use actions for complex state")}: Don’t directly mutate objects or arrays in signals | 				${el("strong", "Use actions for complex state")}: Don’t directly mutate objects or arrays in signals | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Avoid infinite loops")}: Be careful when one signal updates another in a subscription | 				${el("strong", "Avoid infinite loops")}: Be careful when one signal updates another in a subscription | ||||||
| 			`) | 			`), | ||||||
|  | 		), | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			While signals provide powerful reactivity for complex UI interactions, they’re not always necessary. | ||||||
|  | 			A good approach is to started with variables/constants and when necessary, convert them to signals. | ||||||
|  | 		`), | ||||||
|  |  | ||||||
|  | 		el("div", { className: "tabs" }).append( | ||||||
|  | 			el("div", { className: "tab", dataTab: "events" }).append( | ||||||
|  | 				el("h4", t`We can process form events without signals`), | ||||||
|  | 				el("p", t`This can be used when the form data doesn’t need to be reactive and we just waiting for | ||||||
|  | 					results.`), | ||||||
|  | 				el(code, { content: ` | ||||||
|  | 					const onFormSubmit = on("submit", e => { | ||||||
|  | 						e.preventDefault(); | ||||||
|  | 						const formData = new FormData(e.currentTarget); | ||||||
|  | 						// this can be sent to a server | ||||||
|  | 						// or processed locally | ||||||
|  | 						// e.g.: console.log(Object.fromEntries(formData)) | ||||||
|  | 					}); | ||||||
|  | 					// … | ||||||
|  | 					return el("form", null, onFormSubmit).append( | ||||||
|  | 						// … | ||||||
|  | 					); | ||||||
|  | 				`, language: "js" }) | ||||||
|  | 			), | ||||||
|  |  | ||||||
|  | 			el("div", { className: "tab", dataTab: "variables" }).append( | ||||||
|  | 				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(code, { content: ` | ||||||
|  | 					let canSubmit = false; | ||||||
|  |  | ||||||
|  | 					const onFormSubmit = on("submit", e => { | ||||||
|  | 						e.preventDefault(); | ||||||
|  | 						if(!canSubmit) return; // some message | ||||||
|  | 						// … | ||||||
|  | 					}); | ||||||
|  | 					const onAllowSubmit = on("click", e => { | ||||||
|  | 						canSubmit = true; | ||||||
|  | 					}); | ||||||
|  | 				`, language: "js" }), | ||||||
|  | 			), | ||||||
|  |  | ||||||
|  | 			el("div", { className: "tab", dataTab: "state" }).append( | ||||||
|  | 				el("h4", t`Using signals`), | ||||||
|  | 				el("p", t`We use this when we need to reflect changes for example in the UI (e.g. enable/disable | ||||||
|  | 					buttons).`), | ||||||
|  | 				el(code, { content: ` | ||||||
|  | 					const canSubmit = S(false); | ||||||
|  |  | ||||||
|  | 					const onFormSubmit = on("submit", e => { | ||||||
|  | 						e.preventDefault(); | ||||||
|  | 						// … | ||||||
|  | 					}); | ||||||
|  | 					const onAllowSubmit = on("click", e => { | ||||||
|  | 						canSubmit.set(true); | ||||||
|  | 					}); | ||||||
|  |  | ||||||
|  | 					return el("form", null, onFormSubmit).append( | ||||||
|  | 						// ... | ||||||
|  | 						el("button", { textContent: "Allow Submit", type: "button" }, onAllowSubmit), | ||||||
|  | 						el("button", { disabled: S(()=> !canSubmit), textContent: "Submit" }) | ||||||
|  | 					); | ||||||
|  | 				`, language: "js" }), | ||||||
|  | 			), | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("div", { className: "troubleshooting" }).append( | 		el("div", { className: "troubleshooting" }).append( | ||||||
| @@ -286,17 +350,18 @@ export function page({ pkg, info }){ | |||||||
| 				el("dt", t`UI not updating when array/object changes`), | 				el("dt", t`UI not updating when array/object changes`), | ||||||
| 				el("dd", t`Use signal actions instead of direct mutation`), | 				el("dd", t`Use signal actions instead of direct mutation`), | ||||||
|  |  | ||||||
|  | 				el("dt", t`UI not updating`), | ||||||
|  | 				el("dd").append(T`Ensure you passing the (correct) signal not its value (${el("code", "signal")} vs | ||||||
|  | 					${el("code", "signal.get()")})`), | ||||||
|  |  | ||||||
| 				el("dt", t`Infinite update loops`), | 				el("dt", t`Infinite update loops`), | ||||||
| 				el("dd", t`Check for circular dependencies between signals`), | 				el("dd", t`Check for circular dependencies between signals`), | ||||||
|  |  | ||||||
| 				el("dt", t`Memory leaks`), |  | ||||||
| 				el("dd", t`Use AbortController or scope.host() to clean up subscriptions`), |  | ||||||
|  |  | ||||||
| 				el("dt", t`Multiple elements updating unnecessarily`), | 				el("dt", t`Multiple elements updating unnecessarily`), | ||||||
| 				el("dd", t`Split large signals into smaller, more focused ones`) | 				el("dd", t`Split large signals into smaller, more focused ones`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(mnemonic) | 		el(mnemonic), | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -27,23 +27,22 @@ 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"), page_id }), | 		el(code, { src: fileURL("./components/examples/scopes/intro.js") }), | ||||||
| 		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`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The ${el("strong", "host")} is the name for the element representing the component. This is typically the | 			The ${el("strong", "host")} is the name for the element representing the component. This is typically the | ||||||
| 			element returned by a function. To get a reference, you can use ${el("code", "scope.host()")}. To apply addons, | 			element returned by a function. To get a reference, you can use ${el("code", "scope.host()")}. To apply addons, | ||||||
| 			just use ${el("code", "scope.host(...<addons>)")}. | 			just use ${el("code", "scope.host(...<addons>)")}. | ||||||
| 		`), | 		`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Scopes are primarily needed when signals are used in DOM templates (with ${el("code", "el")}, ${el("code", | 			Scopes are primarily needed when signals are used in DOM templates (with ${el("code", "el")}, ${el("code", | ||||||
| 				"assign")}, or ${el("code", "S.el")}). They provide a way for automatically removing signal listeners | 				"assign")}, or ${el("code", "S.el")}). They provide a way for automatically removing signal listeners | ||||||
| 			and cleaning up unused signals when components are removed from the DOM. | 			and cleaning up unused signals when components are removed from the DOM. | ||||||
| @@ -55,7 +54,7 @@ export function page({ pkg, info }){ | |||||||
| 				el(MyComponent); | 				el(MyComponent); | ||||||
|  |  | ||||||
| 				function MyComponent() { | 				function MyComponent() { | ||||||
| 					// 2. access the host element | 					// 2. access the host element (or other scope related values) | ||||||
| 					const { host } = scope; | 					const { host } = scope; | ||||||
|  |  | ||||||
| 					// 3. Add behavior to host | 					// 3. Add behavior to host | ||||||
| @@ -68,7 +67,7 @@ export function page({ pkg, info }){ | |||||||
| 						className: "my-component" | 						className: "my-component" | ||||||
| 					}).append( | 					}).append( | ||||||
| 						el("h2", "Title"), | 						el("h2", "Title"), | ||||||
| 						el("p", "Content") | 						el("p", "Content"), | ||||||
| 					); | 					); | ||||||
| 				} | 				} | ||||||
| 			` }) | 			` }) | ||||||
| @@ -83,30 +82,30 @@ 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"), page_id }), | 		el(example, { src: fileURL("./components/examples/scopes/scopes-and-hosts.js") }), | ||||||
|  |  | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				${el("strong", "Best Practice:")} Always capture the host reference (or other scope related values) at | 				${el("strong", "Best Practice:")} Always capture the host reference (or other scope related values) at | ||||||
| 				the beginning of your component function using ${el("code", "const { host } = scope")} to avoid | 				the beginning of your component function using ${el("code", "const { host } = scope")} to avoid | ||||||
| 				scope-related issues, especially with ${el("em", "asynchronous code")}. | 				scope-related issues, especially with ${el("em", "asynchronous code")}. | ||||||
| 			`), | 			`), | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				If you are interested in the implementation details, see Class-Based Components section. | 				If you are interested in the implementation details, see Class-Based Components section. | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
| 		el(code, { src: fileURL("./components/examples/scopes/good-practise.js"), page_id }), | 		el(code, { src: fileURL("./components/examples/scopes/good-practise.js") }), | ||||||
|  |  | ||||||
| 		el(h3, t`Class-Based Components`), | 		el(h3, t`Class-Based Components`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			While functional components are the primary pattern in dd<el>, you can also create class-based components. | 			While functional components are the primary pattern in dd<el>, you can also create class-based components. | ||||||
| 			For this, we implement function ${el("code", "elClass")} and use it to demonstrate implementation details | 			For 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"), page_id }), | 		el(example, { src: fileURL("./components/examples/scopes/class-component.js") }), | ||||||
|  |  | ||||||
| 		el(h3, t`Automatic Cleanup with Scopes`), | 		el(h3, t`Automatic Cleanup with Scopes`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			One of the most powerful features of scopes is automatic cleanup when components are removed from the DOM. | 			One of the most powerful features of scopes is automatic cleanup when components are removed from the DOM. | ||||||
| 			This prevents memory leaks and ensures resources are properly released. | 			This prevents memory leaks and ensures resources are properly released. | ||||||
| 		`), | 		`), | ||||||
| @@ -123,10 +122,10 @@ 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"), page_id }), | 		el(example, { src: fileURL("./components/examples/scopes/cleaning.js") }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				In this example, when you click "Remove", the component is removed from the DOM, and all its associated | 				In this example, when you click "Remove", the component is removed from the DOM, and all its associated | ||||||
| 				resources are automatically cleaned up, including ${el("em", | 				resources are automatically cleaned up, including ${el("em", | ||||||
| 					"the signal subscription that updates the text content")}. This happens because the library | 					"the signal subscription that updates the text content")}. This happens because the library | ||||||
| @@ -135,12 +134,12 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Declarative vs Imperative Components`), | 		el(h3, t`Declarative vs Imperative Components`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The library DOM API and signals work best when used declaratively. It means you split your app’s logic | 			The library DOM API and signals work best when used declaratively. It means you split your app’s logic | ||||||
| 			into three parts as introduced in ${el("a", { textContent: "Signals (3PS)", ...references.signals })}. | 			into three parts as introduced in ${el("a", { textContent: "Signals (3PS)", ...references.signals })}. | ||||||
| 		`), | 		`), | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				Strictly speaking, the imperative way of using the library is not prohibited. Just be careful to avoid | 				Strictly speaking, the imperative way of using the library is not prohibited. Just be careful to avoid | ||||||
| 				mixing the declarative approach (using signals) with imperative manipulation of elements. | 				mixing the declarative approach (using signals) with imperative manipulation of elements. | ||||||
| 			`) | 			`) | ||||||
| @@ -149,36 +148,36 @@ 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"), page_id }) | 				el(code, { src: fileURL("./components/examples/scopes/declarative.js") }) | ||||||
| 			), | 			), | ||||||
| 			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"), page_id }) | 				el(code, { src: fileURL("./components/examples/scopes/imperative.js") }) | ||||||
| 			), | 			), | ||||||
| 			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"), page_id }) | 				el(code, { src: fileURL("./components/examples/scopes/mixed.js") }) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Best Practices for Scopes and Components`), | 		el(h3, t`Best Practices for Scopes and Components`), | ||||||
| 		el("ol").append( | 		el("ol").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Capture host early:")} Use ${el("code", "const { host } = scope")} at component start | 				${el("strong", "Capture host early:")} Use ${el("code", "const { host } = scope")} at component start | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Define signals as constants:")} ${el("code", "const counter = S(0);")} | 				${el("strong", "Define signals as constants:")} ${el("code", "const counter = S(0);")} | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Prefer declarative patterns:")} Use signals to drive UI updates rather than manual DOM | 				${el("strong", "Prefer declarative patterns:")} Use signals to drive UI updates rather than manual DOM | ||||||
| 				manipulation | 				manipulation | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Keep components focused:")} Each component should do one thing well | 				${el("strong", "Keep components focused:")} Each component should do one thing well | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Add explicit cleanup:")} For resources not managed by dd<el>, use ${el("code", | 				${el("strong", "Add explicit cleanup:")} For resources not managed by dd<el>, use ${el("code", | ||||||
| 					"on.disconnected")} | 					"on.disconnected")} | ||||||
| 			`) | 			`) | ||||||
|   | |||||||
| @@ -57,9 +57,8 @@ 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 | ||||||
| 				Components`))} to create reusable, encapsulated custom elements with all the benefits of dd<el>’s | 				Components`))} to create reusable, encapsulated custom elements with all the benefits of dd<el>’s | ||||||
| 				declarative DOM construction and reactivity system. | 				declarative DOM construction and reactivity system. | ||||||
| @@ -73,32 +72,32 @@ 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"), page_id }), | 		el(code, { src: fileURL("./components/examples/customElement/intro.js") }), | ||||||
|  |  | ||||||
| 		el(h3, t`Getting Started: Web Components Basics`), | 		el(h3, t`Getting Started: Web Components Basics`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Web Components are a set of standard browser APIs that let you create custom HTML elements with | 			Web Components are a set of standard browser APIs that let you create custom HTML elements with | ||||||
| 			encapsulated functionality. They consist of three main technologies: | 			encapsulated functionality. They consist of three main technologies: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Custom Elements:")} Create your own HTML tags with JS-defined behavior | 				${el("strong", "Custom Elements:")} Create your own HTML tags with JS-defined behavior | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Shadow DOM:")} Encapsulate styles and markup within a component | 				${el("strong", "Shadow DOM:")} Encapsulate styles and markup within a component | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "HTML Templates:")} Define reusable markup structures (${el("em", | 				${el("strong", "HTML Templates:")} Define reusable markup structures (${el("em", | ||||||
| 					"the dd<el> replaces this part")}) | 					"the dd<el> replaces this part")}) | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Let’s start with a basic Custom Element example without dd<el> to establish the foundation: | 			Let’s start with a basic Custom Element example without dd<el> to establish the foundation: | ||||||
| 		`), | 		`), | ||||||
| 		el(code, { src: fileURL("./components/examples/customElement/native-basic.js"), page_id }), | 		el(code, { src: fileURL("./components/examples/customElement/native-basic.js") }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				For complete information on Web Components, see the | 				For complete information on Web Components, see the | ||||||
| 				${el("a", references.mdn_custom_elements).append(el("strong", t`MDN documentation`))}. | 				${el("a", references.mdn_custom_elements).append(el("strong", t`MDN documentation`))}. | ||||||
| 				Also, ${el("a", references.custom_elements_tips).append(el("strong", t`Handy Custom Elements Patterns`))} | 				Also, ${el("a", references.custom_elements_tips).append(el("strong", t`Handy Custom Elements Patterns`))} | ||||||
| @@ -107,10 +106,10 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`dd<el> Integration: Step 1 - Event Handling`), | 		el(h3, t`dd<el> Integration: Step 1 - Event Handling`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The first step in integrating dd<el> with Web Components is enabling dd<el>’s event system to work with your | 			The first step in integrating dd<el> with Web Components is enabling dd<el>’s event system to work with your | ||||||
| 			Custom Elements. This is done with ${el("code", "customElementWithDDE")}, which makes your Custom Element | 			Custom Elements. This is done with ${el("code", "customElementWithDDE")}, which makes your Custom Element | ||||||
| 			compatible with dd<el>’s event handling. (${el("em").append(...T`Notice that customElementWithDDE is | 			compatible with dd<el>’s event handling. (${el("em").append(T`Notice that customElementWithDDE is | ||||||
| 			actually`)} ${el("a", { textContent: "decorator", ...references.decorators })}) | 			actually`)} ${el("a", { textContent: "decorator", ...references.decorators })}) | ||||||
| 		`), | 		`), | ||||||
| 		el("div", { className: "function-table" }).append( | 		el("div", { className: "function-table" }).append( | ||||||
| @@ -124,17 +123,17 @@ 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"), page_id }), | 		el(example, { src: fileURL("./components/examples/customElement/customElementWithDDE.js") }), | ||||||
|  |  | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				${el("strong", "Key Point:")} The ${el("code", "customElementWithDDE")} function adds event dispatching | 				${el("strong", "Key Point:")} The ${el("code", "customElementWithDDE")} function adds event dispatching | ||||||
| 				to your Custom Element lifecycle methods, making them work seamlessly with dd<el>’s event system. | 				to your Custom Element lifecycle methods, making them work seamlessly with dd<el>’s event system. | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`dd<el> Integration: Step 2 - Rendering Components`), | 		el(h3, t`dd<el> Integration: Step 2 - Rendering Components`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The next step is to use dd<el>’s component rendering within your Custom Element. This is done with | 			The next step is to use dd<el>’s component rendering within your Custom Element. This is done with | ||||||
| 			${el("code", "customElementRender")}, which connects your dd<el> component function to the Custom Element. | 			${el("code", "customElementRender")}, which connects your dd<el> component function to the Custom Element. | ||||||
| 		`), | 		`), | ||||||
| @@ -156,39 +155,39 @@ 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"), page_id }), | 		el(example, { src: fileURL("./components/examples/customElement/dde.js") }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				In this example, we’re using Shadow DOM (${el("code", "this.attachShadow()")}) for encapsulation, | 				In this example, we’re using Shadow DOM (${el("code", "this.attachShadow()")}) for encapsulation, | ||||||
| 				but you can also render directly to the element with ${el("code", "customElementRender(this, ...)")}. | 				but you can also render directly to the element with ${el("code", "customElementRender(this, ...)")}. | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Reactive Web Components with Signals`), | 		el(h3, t`Reactive Web Components with Signals`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			One of the most powerful features of integrating dd<el> with Web Components is connecting HTML attributes | 			One of the most powerful features of integrating dd<el> with Web Components is connecting HTML attributes | ||||||
| 			to dd<el>’s reactive signals system. This creates truly reactive custom elements. | 			to dd<el>’s reactive signals system. This creates truly reactive custom elements. | ||||||
| 		`), | 		`), | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				${el("strong", "Two Ways to Handle Attributes:")} | 				${el("strong", "Two Ways to Handle Attributes:")} | ||||||
| 			`), | 			`), | ||||||
| 			el("ol").append( | 			el("ol").append( | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					Using standard attribute access (${el("code", "this.getAttribute(<name>)")}) - Passes attributes as | 					Using standard attribute access (${el("code", "this.getAttribute(<name>)")}) - Passes attributes as | ||||||
| 					regular values (static) | 					regular values (static) | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("code", "S.observedAttributes")} - Transforms attributes into signals (reactive) | 					${el("code", "S.observedAttributes")} - Transforms attributes into signals (reactive) | ||||||
| 				`) | 				`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Using the ${el("code", "S.observedAttributes")} creates a reactive connection between your element’s | 			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"), page_id }), | 		el(example, { src: fileURL("./components/examples/customElement/observedAttributes.js") }), | ||||||
|  |  | ||||||
| 		el("div", { className: "callout" }).append( | 		el("div", { className: "callout" }).append( | ||||||
| 			el("h4", t`How S.observedAttributes Works`), | 			el("h4", t`How S.observedAttributes Works`), | ||||||
| @@ -203,7 +202,7 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Working with Shadow DOM`), | 		el(h3, t`Working with Shadow DOM`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Shadow DOM provides encapsulation for your component’s styles and markup. When using dd<el> with Shadow DOM, | 			Shadow DOM provides encapsulation for your component’s styles and markup. When using dd<el> with Shadow DOM, | ||||||
| 			you get the best of both worlds: encapsulation plus declarative DOM creation. | 			you get the best of both worlds: encapsulation plus declarative DOM creation. | ||||||
| 		`), | 		`), | ||||||
| @@ -221,20 +220,20 @@ export function page({ pkg, info }){ | |||||||
| 								<p>Content</p> | 								<p>Content</p> | ||||||
| 			` }) | 			` }) | ||||||
| 		), | 		), | ||||||
| 		el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js"), page_id }), | 		el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js") }), | ||||||
|  |  | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			For more information on Shadow DOM, see | 			For more information on Shadow DOM, see | ||||||
| 			${el("a", { textContent: t`Using Shadow DOM`, ...references.mdn_shadow_dom_depth })}, or the comprehensive | 			${el("a", { textContent: t`Using Shadow DOM`, ...references.mdn_shadow_dom_depth })}, or the comprehensive | ||||||
| 			${el("a", { textContent: t`Shadow DOM in Depth`, ...references.shadow_dom_depth })}. | 			${el("a", { textContent: t`Shadow DOM in Depth`, ...references.shadow_dom_depth })}. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el(h3, t`Working with Slots`), | 		el(h3, t`Working with Slots`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Besides the encapsulation, the Shadow DOM allows for using the ${el("a", references.mdn_shadow_dom_slot).append( | 			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"), page_id }), | 		el(example, { src: fileURL("./components/examples/customElement/simulateSlots.js") }), | ||||||
| 		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( | ||||||
| @@ -246,23 +245,23 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Best Practices for Web Components with dd<el>`), | 		el(h3, t`Best Practices for Web Components with dd<el>`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			When combining dd<el> with Web Components, follow these recommendations: | 			When combining dd<el> with Web Components, follow these recommendations: | ||||||
| 		`), | 		`), | ||||||
| 		el("ol").append( | 		el("ol").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Always use customElementWithDDE")} to enable event integration | 				${el("strong", "Always use customElementWithDDE")} to enable event integration | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Prefer S.observedAttributes")} for reactive attribute connections | 				${el("strong", "Prefer S.observedAttributes")} for reactive attribute connections | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Create reusable component functions")} that your custom elements render | 				${el("strong", "Create reusable component functions")} that your custom elements render | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Use scope.host()")} to clean up event listeners and subscriptions | 				${el("strong", "Use scope.host()")} to clean up event listeners and subscriptions | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Add setters and getters")} for better property access to your element | 				${el("strong", "Add setters and getters")} for better property access to your element | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
|   | |||||||
| @@ -15,39 +15,51 @@ 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 | ||||||
| 			and best practices for debugging applications built with dd<el>, with a focus on signals. | 			and best practices for debugging applications built with dd<el>, with a focus on signals. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el(h3, t`Debugging signals`), | 		el(h3, t`Debugging signals`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Signals are reactive primitives that update the UI when their values change. When debugging signals, | 			Signals are reactive primitives that update the UI when their values change. When debugging signals, | ||||||
| 			you need to track their values, understand their dependencies, and identify why updates are or aren't happening. | 			you need to track their values, understand their dependencies, and identify why updates are or aren’t | ||||||
|  | 			happening. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el("h4", t`Inspecting signal values`), | 		el("h4", t`Inspecting signal values`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The simplest way to debug a signal is to log its current value by calling the get method: | 			The simplest way to debug a signal is to log its current value by calling the get or valueOf method: | ||||||
| 		`), | 		`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			const signal = S(0); | 			const signal = S(0); | ||||||
| 			console.log('Current value:', signal.get()); |  | ||||||
| 			// without triggering updates |  | ||||||
| 			console.log('Current value:', signal.valueOf()); | 			console.log('Current value:', signal.valueOf()); | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
| 		el("p").append(...T` | 		el("div", { className: "warning" }).append( | ||||||
|  | 			el("p").append(T` | ||||||
|  | 				${el("code", "signal.get")} is OK, but in some situations may lead to unexpected results: | ||||||
|  | 			`), | ||||||
|  | 			el(code, { content: ` | ||||||
|  | 				const signal = S(0); | ||||||
|  | 				const derived = S(()=> { | ||||||
|  | 					console.log('Current value:', signal.get()); | ||||||
|  | 					// ↑ in rare cases this will register unwanted dependency | ||||||
|  | 					// but typically this is fine ↓ | ||||||
|  | 					return signal.get() + 1; | ||||||
|  | 				}); | ||||||
|  | 			`, language: "js" }) | ||||||
|  | 		), | ||||||
|  | 		el("p").append(T` | ||||||
| 			You can also monitor signal changes by adding a listener: | 			You can also monitor signal changes by adding a listener: | ||||||
| 		`), | 		`), | ||||||
| 		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)); | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el("h4", t`Debugging derived signals`), | 		el("h4", t`Debugging derived signals`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			With derived signals (created with ${el("code", "S(() => computation))")}), debugging is a bit more complex | 			With derived signals (created with ${el("code", "S(() => computation))")}), debugging is a bit more complex | ||||||
| 			because the value depends on other signals. To understand why a derived signal isn’t updating correctly: | 			because the value depends on other signals. To understand why a derived signal isn’t updating correctly: | ||||||
| 		`), | 		`), | ||||||
| @@ -56,7 +68,43 @@ 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"), page_id }), | 		el(example, { src: fileURL("./components/examples/debugging/consoleLog.js") }), | ||||||
|  |  | ||||||
|  | 		el("h4", t`Examining signal via DevTools`), | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			${el("code", "<signal>.__dde_signal")} - A Symbol property used to identify and store the internal state of | ||||||
|  | 			signal objects. It contains the following information: | ||||||
|  | 		`), | ||||||
|  | 		el("ul").append( | ||||||
|  | 			// TODO: value? | ||||||
|  | 			el("li", t`listeners: A Set of functions called when the signal value changes`), | ||||||
|  | 			el("li", t`actions: Custom actions that can be performed on the signal`), | ||||||
|  | 			el("li", t`onclear: Functions to run when the signal is cleared`), | ||||||
|  | 			el("li", t`host: Reference to the host element/scope in which the signal was created`), | ||||||
|  | 		), | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			…to determine the current value of the signal, call ${el("code", "signal.valueOf()")}. Don’t hesitate to | ||||||
|  | 			use the debugger to inspect the signal object. | ||||||
|  | 		`), | ||||||
|  |  | ||||||
|  | 		el("h4", t`Debugging with breakpoints`), | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			Effective use of breakpoints can help track signal flow: | ||||||
|  | 		`), | ||||||
|  | 		el("ul").append( | ||||||
|  | 			el("li").append(T` | ||||||
|  | 				Set breakpoints in signal update methods to track when values change | ||||||
|  | 			`), | ||||||
|  | 			el("li").append(T` | ||||||
|  | 				Use conditional breakpoints to only break when specific signals change to certain values | ||||||
|  | 			`), | ||||||
|  | 			el("li").append(T` | ||||||
|  | 				Set breakpoints in your signal computation functions to see when derived signals recalculate | ||||||
|  | 			`), | ||||||
|  | 			el("li").append(T` | ||||||
|  | 				Use performance profiling to identify bottlenecks in signal updates | ||||||
|  | 			`) | ||||||
|  | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Common signal debugging issues`), | 		el(h3, t`Common signal debugging issues`), | ||||||
| 		el("h4", t`Signal updates not triggering UI changes`), | 		el("h4", t`Signal updates not triggering UI changes`), | ||||||
| @@ -64,14 +112,21 @@ export function page({ pkg, info }){ | |||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| 			el("li", t`That you’re using signal.set() to update the value, not modifying objects/arrays directly`), | 			el("li", t`That you’re using signal.set() to update the value, not modifying objects/arrays directly`), | ||||||
| 			el("li", t`For mutable objects, ensure you’re using actions or making proper copies before updating`), | 			el("li", t`For mutable objects, ensure you’re using actions or making proper copies before updating`), | ||||||
| 			el("li", t`That the signal is actually connected to the DOM element (check your S.el or attribute binding code)`) | 			el("li", t`That the signal is actually connected to the DOM element (check your S.el or attribute binding | ||||||
|  | 				code)`), | ||||||
|  | 			el("li").append(T` | ||||||
|  | 				That you’re passing signal corecctly (without using ${el("code", "*.get()")}) and for ${el("code", | ||||||
|  | 				"S.el")} that you passing (derived) signals not a function (use ${el("code", | ||||||
|  | 				"S.el(S(()=> count.get() % 2), odd=> …)")}). | ||||||
|  | 			`), | ||||||
| 		), | 		), | ||||||
| 		el(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }), | 		el(code, { src: fileURL("./components/examples/debugging/mutations.js") }), | ||||||
|  |  | ||||||
| 		el("h4", t`Memory leaks with signal listeners`), | 		el("h4", t`Memory leaks with signal listeners`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Signal listeners can cause memory leaks if not properly cleaned up. Always use AbortSignal | 			Signal listeners can cause memory leaks if not properly cleaned up. Always use AbortSignal | ||||||
| 			to cancel listeners. | 			to cancel listeners when they are used ouside the dd<el> knowledge (el, assign, S.el, … auto cleanup | ||||||
|  | 			unnecessarily signals automatically). | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el("h4", t`Performance issues with frequently updating signals`), | 		el("h4", t`Performance issues with frequently updating signals`), | ||||||
| @@ -81,76 +136,57 @@ 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"), page_id }), | 		el(code, { src: fileURL("./components/examples/debugging/debouncing.js") }), | ||||||
|  |  | ||||||
| 		el(h3, t`Browser DevTools tips for dd<el>`), | 		el(h3, t`Browser DevTools tips for components and reactivity`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			When debugging in the browser, dd<el> provides several helpful DevTools-friendly features: | 			When debugging in the browser, dd<el> provides several helpful DevTools-friendly features: | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el("h4", t`Identifying components in the DOM`), |  | ||||||
| 		el("p").append(...T` |  | ||||||
| 			dd<el> marks components in the DOM with special comment nodes to help you identify component boundaries. |  | ||||||
| 			Components created with ${el("code", "el(ComponentFunction)")} are marked with comment nodes |  | ||||||
| 			${el("code", `<!--<dde:mark type="component" name="MyComponent" host="parentElement"/>-->`)} and |  | ||||||
| 			includes: |  | ||||||
| 		`), |  | ||||||
| 		el("ul").append( |  | ||||||
| 			el("li", t`type - Identifies the type of marker ("component", "reactive", or "later")`), |  | ||||||
| 			el("li", t`name - The name of the component function`), |  | ||||||
| 			el("li", t`host - Indicates whether the host is "this" (for DocumentFragments) or "parentElement"`), |  | ||||||
| 		), |  | ||||||
|  |  | ||||||
| 		el("h4", t`Finding reactive elements in the DOM`), | 		el("h4", t`Finding reactive elements in the DOM`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			When using ${el("code", "S.el()")}, dd<el> creates reactive elements in the DOM | 			When using ${el("code", "S.el()")}, dd<el> creates reactive elements in the DOM | ||||||
| 			that are automatically updated when signal values change. These elements are wrapped in special | 			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"), page_id }), | 		el(code, { src: fileURL("./components/examples/debugging/dom-reactive-mark.html") }), | ||||||
| 		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 | ||||||
| 			signal connections through \`__dde_reactive\` of the host element. | 			signal connections through \`__dde_reactive\` of the host element. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el("h4", t`DOM inspection properties`), | 		el("h4", t`Identifying components in the DOM`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Elements created with the dd<el> library have special properties to aid in debugging: | 			dd<el> marks components in the DOM with special comment nodes to help you identify component boundaries. | ||||||
| 		`), | 			Components created with ${el("code", "el(MyComponent)")} are marked with comment nodes | ||||||
| 		el("p").append(...T` | 			${el("code", `<!--<dde:mark type="component" name="MyComponent" host="parentElement"/>-->`)} and | ||||||
| 			${el("code", "<element>.__dde_reactive")} - An array property on DOM elements that tracks signal-to-element | 			includes: | ||||||
| 			relationships.  This allows you to quickly identify which elements are reactive and what signals they’re |  | ||||||
| 			bound to. Each entry in the array contains: |  | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| 			el("li", t`A pair of signal and listener function: [signal, listener]`), | 			el("li", t`type - Identifies the type of marker ("component", "reactive", …)`), | ||||||
| 			el("li", t`Additional context information about the element or attribute`), | 			el("li", t`name - The name of the component function`), | ||||||
| 			el("li", t`Automatically managed by signal.el(), signal.observedAttributes(), and processReactiveAttribute()`) | 			el("li", t`host - Indicates whether the host is "this" (for DocumentFragments) or "parentElement"`), | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("div", { className: "warning" }).append( | ||||||
| 			These properties make it easier to understand the reactive structure of your application when inspecting | 			el("p").append(T` | ||||||
| 			elements. | 				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(example, { src: fileURL("./components/examples/signals/debugging-dom.js"), page_id }), | 			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`Examining signal connections`), | 		el("h4", t`Identifying reactive elements in the DOM`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			${el("code", "<signal>.__dde_signal")} - A Symbol property used to identify and store the internal state of |  | ||||||
| 			signal objects. It contains the following information: |  | ||||||
| 		`), |  | ||||||
| 		el("ul").append( |  | ||||||
| 			el("li", t`listeners: A Set of functions called when the signal value changes`), |  | ||||||
| 			el("li", t`actions: Custom actions that can be performed on the signal`), |  | ||||||
| 			el("li", t`onclear: Functions to run when the signal is cleared`), |  | ||||||
| 			el("li", t`host: Reference to the host element/scope`), |  | ||||||
| 			el("li", t`defined: Stack trace information for debugging`), |  | ||||||
| 			el("li", t`readonly: Boolean flag indicating if the signal is read-only`) |  | ||||||
| 		), |  | ||||||
| 		el("p").append(...T` |  | ||||||
| 			…to determine the current value of the signal, call ${el("code", "signal.valueOf()")}. |  | ||||||
| 		`), |  | ||||||
| 		el("p").append(...T` |  | ||||||
| 			You can inspect (host) element relationships and bindings with signals in the DevTools console using | 			You can inspect (host) element relationships and bindings with signals in the DevTools console using | ||||||
| 			${el("code", "$0.__dde_reactive")} (for currently selected element). In the console you will see a list of | 			${el("code", "$0.__dde_reactive")} (for currently selected element). In the console you will see a list of | ||||||
| 			${el("code", `[ [ signal, listener ], element, property ]`)}, where: | 			${el("code", `[ [ signal, listener ], element, property ]`)}, where: | ||||||
| @@ -161,29 +197,41 @@ export function page({ pkg, info }){ | |||||||
| 			el("li", t`element — the DOM element that is bound to the signal`), | 			el("li", t`element — the DOM element that is bound to the signal`), | ||||||
| 			el("li", t`property — the attribute or property name which is changing based on the signal`), | 			el("li", t`property — the attribute or property name which is changing based on the signal`), | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			…the structure of \`__dde_reactive\` utilizes the browser’s behavior of packing the first field, | 			…the structure of \`__dde_reactive\` utilizes the browser’s behavior of packing the first field, | ||||||
| 			so you can see the element and property that changes in the console right away. | 			so you can see the element and property that changes in the console right away. These properties make it | ||||||
|  | 			easier to understand the reactive structure of your application when inspecting elements. | ||||||
|  | 		`), | ||||||
|  | 		el(example, { src: fileURL("./components/examples/signals/debugging-dom.js") }), | ||||||
|  |  | ||||||
|  | 		el("p", { className: "note" }).append(T` | ||||||
|  | 			${el("code", "<element>.__dde_reactive")} - An array property on DOM elements that tracks signal-to-element | ||||||
|  | 			relationships.  This allows you to quickly identify which elements are reactive and what signals they’re | ||||||
|  | 			bound to. Each entry in the array contains: | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el("h4", t`Debugging with breakpoints`), | 		el("h4", t`Inspecting events and listeners in DevTools`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Effective use of breakpoints can help track signal flow: | 			Modern browser DevTools provide built-in tools for inspecting event listeners attached to DOM elements. | ||||||
|  | 			For example, in Firefox and Chrome, you can: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ol").append( | ||||||
| 			el("li").append(...T` | 			el("li", t`Select an element in the Elements/Inspector panel`), | ||||||
| 				Set breakpoints in signal update methods to track when values change | 			el("li", t`Look for the "Event Listeners" tab or section`), | ||||||
| 			`), | 			el("li", t`See all event listeners attached to the element, including those added by dd<el>`) | ||||||
| 			el("li").append(...T` | 		), | ||||||
| 				Use conditional breakpoints to only break when specific signals change to certain values | 		el("p").append(T` | ||||||
| 			`), | 			Additionally, dd<el> provides special markers in the DOM that help identify debug information. | ||||||
| 			el("li").append(...T` | 			Look for comments with ${el("code", "dde:mark")}, ${el("code", "dde:disconnected")} and ${el("code", | ||||||
| 				Set breakpoints in your signal computation functions to see when derived signals recalculate | 				"__dde_reactive")} which indicate components, reactive regions, and other internal relationships: | ||||||
| 			`), | 		`), | ||||||
| 			el("li").append(...T` | 		el("figure").append( | ||||||
| 				Use performance profiling to identify bottlenecks in signal updates | 			el("img", { | ||||||
| 			`) | 				src: "./assets/devtools.png", | ||||||
|  | 				alt: "Screenshot of DevTools showing usage of “event” button to inspect event listeners", | ||||||
|  | 			}), | ||||||
|  | 			el("figcaption", t`Firefox DevTools showing dd<el> debugging information with event listeners and reactive | ||||||
|  | 				markers`) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -14,23 +14,22 @@ 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 | ||||||
| 			third-party functionalities and integrate them seamlessly with the library, focusing on | 			third-party functionalities and integrate them seamlessly with the library, focusing on | ||||||
| 			proper resource cleanup and interoperability. | 			proper resource cleanup and interoperability. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el(h3, t`DOM Element Extensions with Addons`), | 		el(h3, t`DOM Element Extensions with Addons`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The primary method for extending DOM elements in dd<el> is through the Addon pattern. | 			The primary method for extending DOM elements in dd<el> is through the Addon pattern. | ||||||
| 			Addons are functions that take an element and applying some functionality to it. This pattern enables | 			Addons are functions that take an element and applying some functionality to it. This pattern enables | ||||||
| 			a clean, functional approach to element enhancement. | 			a clean, functional approach to element enhancement. | ||||||
| 		`), | 		`), | ||||||
| 		el("div", { className: "callout" }).append( | 		el("div", { className: "callout" }).append( | ||||||
| 			el("h4", t`What are Addons?`), | 			el("h4", t`What are Addons?`), | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				Addons are simply functions with the signature: (element) => void. They: | 				Addons are simply functions with the signature: (element) => void. They: | ||||||
| 			`), | 			`), | ||||||
| 			el("ul").append( | 			el("ul").append( | ||||||
| @@ -49,16 +48,16 @@ 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" })); | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el(h3, t`Resource Cleanup with Abort Signals`), | 		el(h3, t`Resource Cleanup with Abort Signals`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			When extending elements with functionality that uses resources like event listeners, timers, | 			When extending elements with functionality that uses resources like event listeners, timers, | ||||||
| 			or external subscriptions, it’s critical to clean up these resources when the element is removed | 			or external subscriptions, it’s critical to clean up these resources when the element is removed | ||||||
| 			from the DOM. dd<el> provides utilities for this through AbortSignal integration. | 			from the DOM. dd<el> provides utilities for this through AbortSignal integration. | ||||||
| 		`), | 		`), | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				The ${el("code", "scope.signal")} property creates an AbortSignal that automatically | 				The ${el("code", "scope.signal")} property creates an AbortSignal that automatically | ||||||
| 				triggers when an element is disconnected from the DOM, making cleanup much easier to manage. | 				triggers when an element is disconnected from the DOM, making cleanup much easier to manage. | ||||||
| 			`) | 			`) | ||||||
| @@ -83,15 +82,14 @@ 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)); | ||||||
| 			} | 			} | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el(h3, t`Building Library-Independent Extensions`), | 		el(h3, t`Building Library-Independent Extensions`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			When creating extensions, it’s a good practice to make them as library-independent as possible. | 			When creating extensions, it’s a good practice to make them as library-independent as possible. | ||||||
| 			This approach enables better interoperability and future-proofing. | 			This approach enables better interoperability and future-proofing. | ||||||
| 		`), | 		`), | ||||||
| 		el("div", { className: "illustration" }).append( | 		el("div", { className: "illustration" }).append( | ||||||
| 			el("h4", t`Library-Independent vs. Library-Dependent Extension`), |  | ||||||
| 			el("div", { className: "tabs" }).append( | 			el("div", { className: "tabs" }).append( | ||||||
| 				el("div", { className: "tab" }).append( | 				el("div", { className: "tab" }).append( | ||||||
| 					el("h5", t`✅ Library-Independent`), | 					el("h5", t`✅ Library-Independent`), | ||||||
| @@ -105,7 +103,7 @@ export function page({ pkg, info }){ | |||||||
| 								}); | 								}); | ||||||
| 							}; | 							}; | ||||||
| 						} | 						} | ||||||
| 					`, page_id }) | 					`, language: "js" }) | ||||||
| 				), | 				), | ||||||
| 				el("div", { className: "tab" }).append( | 				el("div", { className: "tab" }).append( | ||||||
| 					el("h5", t`⚠️ Library-Dependent`), | 					el("h5", t`⚠️ Library-Dependent`), | ||||||
| @@ -119,19 +117,19 @@ export function page({ pkg, info }){ | |||||||
| 								})(element); | 								})(element); | ||||||
| 							}; | 							}; | ||||||
| 						} | 						} | ||||||
| 					`, page_id }) | 					`, language: "js" }) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Signal Extensions and Factory Patterns`), | 		el(h3, t`Signal Extensions and Factory Patterns`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Unlike DOM elements, signal functionality in dd<el> currently lacks a standardized | 			Unlike DOM elements, signal functionality in dd<el> currently lacks a standardized | ||||||
| 			way to create library-independent extensions. This is because signals are implemented | 			way to create library-independent extensions. This is because signals are implemented | ||||||
| 			differently across libraries. | 			differently across libraries. | ||||||
| 		`), | 		`), | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				In the future, JavaScript may include built-in signals through the | 				In the future, JavaScript may include built-in signals through the | ||||||
| 				${el("a", { href: "https://github.com/tc39/proposal-signals", textContent: "TC39 Signals Proposal" })}. | 				${el("a", { href: "https://github.com/tc39/proposal-signals", textContent: "TC39 Signals Proposal" })}. | ||||||
| 				dd<el> is designed with future compatibility in mind and will hopefully support these | 				dd<el> is designed with future compatibility in mind and will hopefully support these | ||||||
| @@ -140,7 +138,7 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("h4", t`The Signal Factory Pattern`), | 		el("h4", t`The Signal Factory Pattern`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			A powerful approach for extending signal functionality is the "Signal Factory" pattern. | 			A powerful approach for extending signal functionality is the "Signal Factory" pattern. | ||||||
| 			This approach encapsulates specific behavior in a function that creates and configures a signal. | 			This approach encapsulates specific behavior in a function that creates and configures a signal. | ||||||
| 		`), | 		`), | ||||||
| @@ -178,7 +176,7 @@ export function page({ pkg, info }){ | |||||||
| 					textContent: "All" | 					textContent: "All" | ||||||
| 				}) | 				}) | ||||||
| 			); | 			); | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el("div", { className: "callout" }).append( | 		el("div", { className: "callout" }).append( | ||||||
| 			el("h4", t`Benefits of Signal Factories`), | 			el("h4", t`Benefits of Signal Factories`), | ||||||
| @@ -191,13 +189,13 @@ export function page({ pkg, info }){ | |||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Note how the factory accepts the signal constructor as a parameter, making it easier to test | 			Note how the factory accepts the signal constructor as a parameter, making it easier to test | ||||||
| 			and potentially migrate to different signal implementations in the future. | 			and potentially migrate to different signal implementations in the future. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el("h4", t`Other Signal Extension Approaches`), | 		el("h4", t`Other Signal Extension Approaches`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			For simpler cases, you can also extend signals with clear interfaces and isolation to make | 			For simpler cases, you can also extend signals with clear interfaces and isolation to make | ||||||
| 			future migration easier. | 			future migration easier. | ||||||
| 		`), | 		`), | ||||||
| @@ -218,10 +216,10 @@ 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}\`); | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				When designing signal extensions, consider creating specialized signals for common patterns like: | 				When designing signal extensions, consider creating specialized signals for common patterns like: | ||||||
| 				forms, API requests, persistence, animations, or routing. These can significantly reduce | 				forms, API requests, persistence, animations, or routing. These can significantly reduce | ||||||
| 				boilerplate code in your applications. | 				boilerplate code in your applications. | ||||||
| @@ -229,19 +227,19 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Using Signals Independently`), | 		el(h3, t`Using Signals Independently`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			While signals are tightly integrated with DDE’s DOM elements, you can also use them independently. | 			While signals are tightly integrated with DDE’s DOM elements, you can also use them independently. | ||||||
| 			This can be useful when you need reactivity in non-UI code or want to integrate with other libraries. | 			This can be useful when you need reactivity in non-UI code or want to integrate with other libraries. | ||||||
| 		`), | 		`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			There are two ways to import signals: | 			There are two ways to import signals: | ||||||
| 		`), | 		`), | ||||||
| 		el("ol").append( | 		el("ol").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Standard import")}: ${el("code", `import { S } from "deka-dom-el/signals";`)} | 				${el("strong", "Standard import")}: ${el("code", `import { S } from "deka-dom-el/signals";`)} | ||||||
| 				— This automatically registers signals with DDE’s DOM reactivity system | 				— This automatically registers signals with DDE’s DOM reactivity system | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Independent import")}: ${el("code", `import { S } from "deka-dom-el/src/signals-lib";`)} | 				${el("strong", "Independent import")}: ${el("code", `import { S } from "deka-dom-el/src/signals-lib";`)} | ||||||
| 				— This gives you just the signal system without DOM integration | 				— This gives you just the signal system without DOM integration | ||||||
| 			`) | 			`) | ||||||
| @@ -260,8 +258,8 @@ 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 | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
| 		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()")}). | ||||||
| 		`), | 		`), | ||||||
| @@ -276,27 +274,27 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 		el(h3, t`Best Practices for Extensions`), | 		el(h3, t`Best Practices for Extensions`), | ||||||
| 		el("ol").append( | 		el("ol").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Use AbortSignals for cleanup:")} Always implement proper resource cleanup with | 				${el("strong", "Use AbortSignals for cleanup:")} Always implement proper resource cleanup with | ||||||
| 				${el("code", "scope.signal")} or similar mechanisms | 				${el("code", "scope.signal")} or similar mechanisms | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Separate core logic from library adaptation:")} Make your core functionality work | 				${el("strong", "Separate core logic from library adaptation:")} Make your core functionality work | ||||||
| 				with standard DOM APIs when possible | 				with standard DOM APIs when possible | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Use signal factories for common patterns:")} Create reusable signal factories that encapsulate | 				${el("strong", "Use signal factories for common patterns:")} Create reusable signal factories that encapsulate | ||||||
| 				domain-specific behavior and state logic | 				domain-specific behavior and state logic | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Document clearly:")} Provide clear documentation on how your extension works | 				${el("strong", "Document clearly:")} Provide clear documentation on how your extension works | ||||||
| 				and what resources it uses | 				and what resources it uses | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Follow the Addon pattern:")} Keep to the (element) => element signature for | 				${el("strong", "Follow the Addon pattern:")} Keep to the (element) => element signature for | ||||||
| 				DOM element extensions | 				DOM element extensions | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Avoid modifying global state:")} Extensions should be self-contained and not | 				${el("strong", "Avoid modifying global state:")} Extensions should be self-contained and not | ||||||
| 				affect other parts of the application | 				affect other parts of the application | ||||||
| 			`) | 			`) | ||||||
|   | |||||||
| @@ -43,9 +43,8 @@ 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 | ||||||
| 			techniques to optimize rendering performance, especially when dealing with large lists or frequently | 			techniques to optimize rendering performance, especially when dealing with large lists or frequently | ||||||
| 			updating components. This guide focuses on memoization and other optimization strategies. | 			updating components. This guide focuses on memoization and other optimization strategies. | ||||||
| @@ -60,10 +59,10 @@ 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"), page_id }), | 		el(code, { src: fileURL("./components/examples/optimization/intro.js") }), | ||||||
|  |  | ||||||
| 		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` | ||||||
| 			In standard JavaScript applications, optimizing list rendering often involves manual caching | 			In standard JavaScript applications, optimizing list rendering often involves manual caching | ||||||
| 			or relying on complex virtual DOM diffing algorithms. dd<el>'s ${el("code", "memo")} function | 			or relying on complex virtual DOM diffing algorithms. dd<el>'s ${el("code", "memo")} function | ||||||
| 			provides a simpler, more direct approach: | 			provides a simpler, more direct approach: | ||||||
| @@ -84,7 +83,7 @@ export function page({ pkg, info }){ | |||||||
| 								)) | 								)) | ||||||
| 							); | 							); | ||||||
| 						} | 						} | ||||||
| 					`, page_id }) | 					`, language: "js" }) | ||||||
| 				), | 				), | ||||||
| 				el("div").append( | 				el("div").append( | ||||||
| 					el("h5", t`With dd<el>'s memo`), | 					el("h5", t`With dd<el>'s memo`), | ||||||
| @@ -102,17 +101,17 @@ export function page({ pkg, info }){ | |||||||
| 								))) | 								))) | ||||||
| 							); | 							); | ||||||
| 						} | 						} | ||||||
| 					`, page_id }) | 					`, language: "js" }) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The ${el("a", references.memo_docs).append(el("code", "memo"))} function in dd<el> allows you to | 			The ${el("a", references.memo_docs).append(el("code", "memo"))} function in dd<el> allows you to | ||||||
| 			cache and reuse DOM elements instead of recreating them on every render, which can | 			cache and reuse DOM elements instead of recreating them on every render, which can | ||||||
| 			significantly improve performance for components that render frequently or contain heavy computations. | 			significantly improve performance for components that render frequently or contain heavy computations. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The memo system is particularly useful for: | 			The memo system is particularly useful for: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| @@ -122,7 +121,7 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Using memo with Signal Rendering`), | 		el(h3, t`Using memo with Signal Rendering`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The most common use case for memoization is within ${el("code", "S.el()")} when rendering lists with | 			The most common use case for memoization is within ${el("code", "S.el()")} when rendering lists with | ||||||
| 			${el("code", "map()")}: | 			${el("code", "map()")}: | ||||||
| 		`), | 		`), | ||||||
| @@ -134,9 +133,9 @@ export function page({ pkg, info }){ | |||||||
| 						memo(todo.id, () => | 						memo(todo.id, () => | ||||||
| 							el(TodoItem, todo) | 							el(TodoItem, todo) | ||||||
| 			)))) | 			)))) | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The ${el("code", "memo")} function in this context: | 			The ${el("code", "memo")} function in this context: | ||||||
| 		`), | 		`), | ||||||
| 		el("ol").append( | 		el("ol").append( | ||||||
| @@ -146,10 +145,10 @@ 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"), page_id }), | 		el(example, { src: fileURL("./components/examples/optimization/memo.js") }), | ||||||
|  |  | ||||||
| 		el(h3, t`Creating Memoization Scopes`), | 		el(h3, t`Creating Memoization Scopes`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The ${el("code", "memo()")} uses cache store defined via the ${el("code", "memo.scope")} function. | 			The ${el("code", "memo()")} uses cache store defined via the ${el("code", "memo.scope")} function. | ||||||
| 			That is actually what the ${el("code", "S.el")} is doing under the hood: | 			That is actually what the ${el("code", "S.el")} is doing under the hood: | ||||||
| 		`), | 		`), | ||||||
| @@ -171,9 +170,9 @@ 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)) | ||||||
| 			); | 			); | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		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: | ||||||
| 		`), | 		`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| @@ -188,17 +187,21 @@ export function page({ pkg, info }){ | |||||||
| 				// Clear cache when signal is aborted | 				// Clear cache when signal is aborted | ||||||
| 				signal: controller.signal | 				signal: controller.signal | ||||||
| 			}); | 			}); | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			You can use custom memo scope as function in (e. g. ${el("code", "S.el(signal, renderList)")}) and as | ||||||
|  | 			(Abort) signal use ${el("code", "scope.signal")}. | ||||||
|  | 		`), | ||||||
|  |  | ||||||
| 		el("div", { className: "function-table" }).append( | 		el("div", { className: "function-table" }).append( | ||||||
| 			el("dl").append( | 			el("dl").append( | ||||||
| 				el("dt", t`onlyLast Option`), | 				el("dt", t`onlyLast Option`), | ||||||
| 				el("dd").append(...T`Only keeps the cache from the most recent function call, | 				el("dd").append(T`Only keeps the cache from the most recent function call, | ||||||
| 					which is useful when the entire collection is replaced. ${el("strong", "This is default behavior of ") | 					which is useful when the entire collection is replaced. ${el("strong", "This is default behavior of ") | ||||||
| 					.append(el("code", "S.el"))}!`), | 					.append(el("code", "S.el"))}!`), | ||||||
|  |  | ||||||
| 				el("dt", t`signal Option`), | 				el("dt", t`signal Option`), | ||||||
| 				el("dd").append(...T`An ${el("a", references.mdn_abort).append(el("code", "AbortSignal"))} | 				el("dd").append(T`An ${el("a", references.mdn_abort).append(el("code", "AbortSignal"))} | ||||||
| 					that will clear the cache when aborted, helping with memory management`) | 					that will clear the cache when aborted, helping with memory management`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| @@ -206,18 +209,16 @@ export function page({ pkg, info }){ | |||||||
| 		el(h3, t`Additional Optimization Techniques`), | 		el(h3, t`Additional Optimization Techniques`), | ||||||
|  |  | ||||||
| 		el("h4", t`Minimizing Signal Updates`), | 		el("h4", t`Minimizing Signal Updates`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Signals are efficient, but unnecessary updates can impact performance: | 			Signals are efficient, but unnecessary updates can impact performance: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| 			el("li", t`Avoid setting signal values that haven't actually changed`), |  | ||||||
| 			el("li", t`For frequently updating values (like scroll position), consider debouncing`), | 			el("li", t`For frequently updating values (like scroll position), consider debouncing`), | ||||||
| 			el("li", t`Keep signal computations small and focused`), | 			el("li", t`Keep signal computations small and focused`), | ||||||
| 			el("li", t`Use derived signals to compute values only when dependencies change`) |  | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("h4", t`Optimizing List Rendering`), | 		el("h4", t`Optimizing List Rendering`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Beyond memoization, consider these approaches for optimizing list rendering: | 			Beyond memoization, consider these approaches for optimizing list rendering: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| @@ -228,17 +229,17 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				Memoization works best when your keys are stable and unique. Use IDs or other persistent | 				Memoization works best when your keys are stable and unique. Use IDs or other persistent | ||||||
| 				identifiers rather than array indices, which can change when items are reordered. | 				identifiers rather than array indices, which can change when items are reordered. | ||||||
| 				`), | 				`), | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				Alternatively you can use any “jsonable” value as key, when the primitive values aren’t enough. | 				Alternatively you can use any “jsonable” value as key, when the primitive values aren’t enough. | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("h4", t`Memory Management`), | 		el("h4", t`Memory Management`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			To prevent memory leaks and reduce memory consumption: | 			To prevent memory leaks and reduce memory consumption: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| @@ -249,43 +250,55 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("h4", t`Choosing the Right Optimization Approach`), | 		el("h4", t`Choosing the Right Optimization Approach`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			While memo is powerful, it's not always the best solution: | 			While ${el("code", "memo")} is powerful, different scenarios call for different optimization techniques: | ||||||
| 		`), | 		`), | ||||||
| 		el("table").append( | 		el("div", { className: "function-table" }).append( | ||||||
| 			el("thead").append( | 			el("dl").append( | ||||||
| 				el("tr").append( | 				el("dt", t`memo`), | ||||||
| 					el("th", "Approach"), | 				el("dd").append(T` | ||||||
| 					el("th", "When to use") | 					Best for list rendering where items rarely change or only their properties update. | ||||||
| 				) | 					${el("code", "todos.map(todo => memo(todo.id, () => el(TodoItem, todo)))")} | ||||||
| 			), | 					Use when you need to cache and reuse DOM elements to avoid recreating them on every render. | ||||||
| 			el("tbody").append( | 				`), | ||||||
| 				el("tr").append( |  | ||||||
| 					el("td", "memo"), | 				el("dt", t`Signal computations`), | ||||||
| 					el("td", "Lists with stable items that infrequently change position") | 				el("dd").append(T` | ||||||
| 				), | 					Ideal for derived values that depend on other signals and need to auto-update. | ||||||
| 				el("tr").append( | 					${el("code", "const totalPrice = S(() => items.get().reduce((t, i) => t + i.price, 0))")} | ||||||
| 					el("td", "Signal computations"), | 					Use when calculated values need to stay in sync with changing source data. | ||||||
| 					el("td", "Derived values that depend on other signals") | 				`), | ||||||
| 				), |  | ||||||
| 				el("tr").append( | 				el("dt", t`Debouncing/Throttling`), | ||||||
| 					el("td", "Debouncing"), | 				el("dd").append(T` | ||||||
| 					el("td", "High-frequency events like scroll or resize") | 					Essential for high-frequency events (scroll, resize) or rapidly changing input values. | ||||||
| 				), | 					${el("code", "debounce(e => searchQuery.set(e.target.value), 300)")} | ||||||
| 				el("tr").append( | 					Use to limit the rate at which expensive operations execute when triggered by fast events. | ||||||
| 					el("td", "Stateful components"), | 				`), | ||||||
| 					el("td", "Complex components with internal state") |  | ||||||
| 				) | 				el("dt", t`memo.scope`), | ||||||
|  | 				el("dd").append(T` | ||||||
|  | 					Useful for using memoization inside any function: ${el("code", | ||||||
|  | 						"const renderList = memo.scope(items => items.map(...))")}. Use to create isolated memoization | ||||||
|  | 					contexts that can be cleared or managed independently. | ||||||
|  | 				`), | ||||||
|  |  | ||||||
|  | 				el("dt", t`Stateful components`), | ||||||
|  | 				el("dd").append(T` | ||||||
|  | 					For complex UI components with internal state management. | ||||||
|  | 					${el("code", "el(ComplexComponent, { initialState, onChange })")} | ||||||
|  | 					Use when a component needs to encapsulate and manage its own state and lifecycle. | ||||||
|  | 				`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Known Issues and Limitations`), | 		el(h3, t`Known Issues and Limitations`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			While memoization is a powerful optimization technique, there are some limitations and edge cases to be aware of: | 			While memoization is a powerful optimization technique, there are some limitations and edge cases to be aware of: | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el("h4", t`Document Fragments and Memoization`), | 		el("h4", t`Document Fragments and Memoization`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			One important limitation to understand is how memoization interacts with | 			One important limitation to understand is how memoization interacts with | ||||||
| 			${el("a", references.mdn_fragment).append("DocumentFragment")} objects. | 			${el("a", references.mdn_fragment).append("DocumentFragment")} objects. | ||||||
| 			Functions like ${el("code", "S.el")} internally use DocumentFragment to efficiently handle multiple elements, | 			Functions like ${el("code", "S.el")} internally use DocumentFragment to efficiently handle multiple elements, | ||||||
| @@ -303,9 +316,9 @@ 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 | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		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 | ||||||
| 			is cached by memo and reused, it's already empty. | 			is cached by memo and reused, it's already empty. | ||||||
| 		`), | 		`), | ||||||
| @@ -324,10 +337,10 @@ 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))) | ||||||
| 					) | 					) | ||||||
| 				); | 				); | ||||||
| 			`, page_id }) | 			`, language: "js" }) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Generally, you should either: | 			Generally, you should either: | ||||||
| 		`), | 		`), | ||||||
| 		el("ol").append( | 		el("ol").append( | ||||||
| @@ -337,7 +350,7 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				This limitation isn't specific to dd<el> but is related to how DocumentFragment works in the DOM. | 				This limitation isn't specific to dd<el> but is related to how DocumentFragment works in the DOM. | ||||||
| 				Once a fragment is appended to the DOM, its child nodes are moved from the fragment to the target element, | 				Once a fragment is appended to the DOM, its child nodes are moved from the fragment to the target element, | ||||||
| 				leaving the original fragment empty. | 				leaving the original fragment empty. | ||||||
| @@ -345,11 +358,11 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Performance Debugging`), | 		el(h3, t`Performance Debugging`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			To identify performance bottlenecks in your dd<el> applications: | 			To identify performance bottlenecks in your dd<el> applications: | ||||||
| 		`), | 		`), | ||||||
| 		el("ol").append( | 		el("ol").append( | ||||||
| 			el("li").append(...T`Use ${el("a", references.mdn_perf).append("browser performance tools")} to profile | 			el("li").append(T`Use ${el("a", references.mdn_perf).append("browser performance tools")} to profile | ||||||
| 				rendering times`), | 				rendering times`), | ||||||
| 			el("li", t`Check for excessive signal updates using S.on() listeners with console.log`), | 			el("li", t`Check for excessive signal updates using S.on() listeners with console.log`), | ||||||
| 			el("li", t`Verify memo usage by inspecting cache hit rates`), | 			el("li", t`Verify memo usage by inspecting cache hit rates`), | ||||||
| @@ -357,26 +370,26 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				For more details on debugging, see the ${el("a", { href: "p07-debugging.html", textContent: "Debugging" })} page. | 				For more details on debugging, see the ${el("a", { href: "p07-debugging.html", textContent: "Debugging" })} page. | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Best Practices for Optimized Rendering`), | 		el(h3, t`Best Practices for Optimized Rendering`), | ||||||
| 		el("ol").append( | 		el("ol").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Use memo for list items:")} Memoize items in lists, especially when they contain complex components. | 				${el("strong", "Use memo for list items:")} Memoize items in lists, especially when they contain complex components. | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Clean up with AbortSignals:")} Connect memo caches to component lifecycles using AbortSignals. | 				${el("strong", "Clean up with AbortSignals:")} Connect memo caches to component lifecycles using AbortSignals. | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Profile before optimizing:")} Identify actual bottlenecks before adding optimization. | 				${el("strong", "Profile before optimizing:")} Identify actual bottlenecks before adding optimization. | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Use derived signals:")} Compute derived values efficiently with signal computations. | 				${el("strong", "Use derived signals:")} Compute derived values efficiently with signal computations. | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Avoid memoizing fragments:")} Memoize individual elements or use container elements | 				${el("strong", "Avoid memoizing fragments:")} Memoize individual elements or use container elements | ||||||
| 				instead of DocumentFragments. | 				instead of DocumentFragments. | ||||||
| 			`) | 			`) | ||||||
|   | |||||||
| @@ -43,9 +43,8 @@ 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 | ||||||
| 			frameworks by implementing the same todo application. This implementation showcases how dd<el> | 			frameworks by implementing the same todo application. This implementation showcases how dd<el> | ||||||
| 			can be used to build a complete, real-world application with all the expected features of a modern | 			can be used to build a complete, real-world application with all the expected features of a modern | ||||||
| @@ -63,16 +62,16 @@ export function page({ pkg, info }){ | |||||||
| 				el("li", t`Component scopes for proper encapsulation`) | 				el("li", t`Component scopes for proper encapsulation`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Below is a fully working TodoMVC implementation. You can interact with it directly in this | 			Below is a fully working TodoMVC implementation. You can interact with it directly in this | ||||||
| 			documentation page. The example demonstrates how dd<el> handles common app development | 			documentation page. The example demonstrates how dd<el> handles common app development | ||||||
| 			challenges in a clean, maintainable way. | 			challenges in a clean, maintainable way. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el(example, { src: fileURL("./components/examples/reallife/todomvc.js"), variant: "big", page_id }), | 		el(example, { src: fileURL("./components/examples/reallife/todomvc.js"), variant: "big" }), | ||||||
|  |  | ||||||
| 		el(h3, t`Application Architecture Overview`), | 		el(h3, t`Application Architecture Overview`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The TodoMVC implementation is structured around several key components: | 			The TodoMVC implementation is structured around several key components: | ||||||
| 		`), | 		`), | ||||||
| 		el("div", { className: "function-table" }).append( | 		el("div", { className: "function-table" }).append( | ||||||
| @@ -96,29 +95,31 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Reactive State Management with Signals`), | 		el(h3, t`Reactive State Management with Signals`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The application uses three primary signals to manage state: | 			The application uses three primary signals to manage state: | ||||||
| 		`), | 		`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			// Signal for current route (all/active/completed) | 			// Signal for current route (all/active/completed) | ||||||
| 			const pageS = routerSignal(S); | 			const { signal } = scope; | ||||||
|  | 			const pageS = routerSignal(S, signal); | ||||||
|  |  | ||||||
| 			// Signal for the todos collection with custom actions | 			// Signal for the todos collection with custom actions | ||||||
| 			const todosS = todosSignal(); | 			const todosS = todosSignal(); | ||||||
|  |  | ||||||
| 			// Derived signal that filters todos based on current route | 			// Derived signal that filters todos based on current route | ||||||
| 			const filteredTodosS = S(()=> { | 			const todosFilteredS = S(()=> { | ||||||
| 				const todos = todosS.get(); | 				const todos = todosS.get(); | ||||||
| 				const filter = pageS.get(); | 				const filter = pageS.get(); | ||||||
|  | 				if (filter === "all") return todos; | ||||||
| 				return todos.filter(todo => { | 				return todos.filter(todo => { | ||||||
| 					if (filter === "active") return !todo.completed; | 					if (filter === "active") return !todo.completed; | ||||||
| 					if (filter === "completed") return todo.completed; | 					if (filter === "completed") return todo.completed; | ||||||
| 					return true; // "all" |  | ||||||
| 				}); | 				}); | ||||||
| 			}); | 			}); | ||||||
| 		`, page_id }), | 			const todosRemainingS = S(()=> todosS.get().filter(todo => !todo.completed).length); | ||||||
|  | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		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: | ||||||
| 		`), | 		`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| @@ -177,6 +178,13 @@ export function page({ pkg, info }){ | |||||||
| 					clearCompleted() { | 					clearCompleted() { | ||||||
| 						this.value = this.value.filter(todo => !todo.completed); | 						this.value = this.value.filter(todo => !todo.completed); | ||||||
| 					}, | 					}, | ||||||
|  | 					/** | ||||||
|  | 					 * Mark all todos as completed or active | ||||||
|  | 					 * @param {boolean} state - Whether to mark todos as completed or active | ||||||
|  | 					 */ | ||||||
|  | 					completeAll(state = true) { | ||||||
|  | 						this.value.forEach(todo => todo.completed = state); | ||||||
|  | 					}, | ||||||
| 					/** | 					/** | ||||||
| 					 * Handle cleanup when signal is cleared | 					 * Handle cleanup when signal is cleared | ||||||
| 					 */ | 					 */ | ||||||
| @@ -193,14 +201,15 @@ export function page({ pkg, info }){ | |||||||
| 						localStorage.setItem(store_key, JSON.stringify(value)); | 						localStorage.setItem(store_key, JSON.stringify(value)); | ||||||
| 					} catch (e) { | 					} catch (e) { | ||||||
| 						console.error("Failed to save todos to localStorage", e); | 						console.error("Failed to save todos to localStorage", e); | ||||||
|  | 						// Optionally, provide user feedback | ||||||
| 					} | 					} | ||||||
| 				}); | 				}); | ||||||
| 				return out; | 				return out; | ||||||
| 			} | 			} | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				Using ${el("a", references.mdn_storage).append("localStorage")} allows the application to persist todos | 				Using ${el("a", references.mdn_storage).append("localStorage")} allows the application to persist todos | ||||||
| 				even when the page is refreshed. The ${el("code", "S.on")} listener ensures todos are saved | 				even when the page is refreshed. The ${el("code", "S.on")} listener ensures todos are saved | ||||||
| 				after every state change, providing automatic persistence without explicit calls. | 				after every state change, providing automatic persistence without explicit calls. | ||||||
| @@ -208,37 +217,61 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Integration of Signals and Reactive UI`), | 		el(h3, t`Integration of Signals and Reactive UI`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The implementation demonstrates a clean integration between signal state and reactive UI: | 			The implementation demonstrates a clean integration between signal state and reactive UI: | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el("h4", t`1. Derived Signals for Filtering`), | 		el("h4", t`1. Derived Signals for Filtering`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			/** Derived signal that filters todos based on current route */ | 			/** Derived signal that filters todos based on current route */ | ||||||
| 			const filteredTodosS = S(()=> { | 			const todosFilteredS = S(()=> { | ||||||
| 				const todos = todosS.get(); | 				const todos = todosS.get(); | ||||||
| 				const filter = pageS.get(); | 				const filter = pageS.get(); | ||||||
|  | 				if (filter === "all") return todos; | ||||||
| 				return todos.filter(todo => { | 				return todos.filter(todo => { | ||||||
| 					if (filter === "active") return !todo.completed; | 					if (filter === "active") return !todo.completed; | ||||||
| 					if (filter === "completed") return todo.completed; | 					if (filter === "completed") return todo.completed; | ||||||
| 					return true; // "all" |  | ||||||
| 				}); | 				}); | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| 			// Using the derived signal in the UI | 			// Using the derived signal in the UI | ||||||
| 			el("ul", { className: "todo-list" }).append( | 			el("ul", { className: "todo-list" }).append( | ||||||
| 				S.el(filteredTodosS, filteredTodos => filteredTodos.map(todo => | 				S.el(todosFilteredS, filteredTodos => filteredTodos.map(todo => | ||||||
| 					memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) | 					memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		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, | ||||||
| 			ensuring the UI always shows the correct filtered todos. | 			ensuring the UI always shows the correct filtered todos. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el("h4", t`2. Local Component State`), | 		el("h4", t`2. Toggle All Functionality`), | ||||||
|  | 		el(code, { content: ` | ||||||
|  | 			/** @type {ddeElementAddon<HTMLInputElement>} */ | ||||||
|  | 			const onToggleAll = on("change", event => { | ||||||
|  | 				const checked = /** @type {HTMLInputElement} */ (event.target).checked; | ||||||
|  | 				S.action(todosS, "completeAll", checked); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			// Using the toggle-all functionality in the UI | ||||||
|  | 			el("input", { | ||||||
|  | 				id: "toggle-all", | ||||||
|  | 				className: "toggle-all", | ||||||
|  | 				type: "checkbox" | ||||||
|  | 			}, onToggleAll), | ||||||
|  | 			el("label", { htmlFor: "toggle-all", title: "Mark all as complete" }), | ||||||
|  | 		`, language: "js" }), | ||||||
|  |  | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			The "toggle all" checkbox allows users to mark all todos as completed or active. When the checkbox | ||||||
|  | 			is toggled, it calls the ${el("code", "completeAll")} action on the todos signal, passing the current | ||||||
|  | 			checked state. This is a good example of how signals and actions can be used to manage application | ||||||
|  | 			state in a clean, declarative way. | ||||||
|  | 		`), | ||||||
|  |  | ||||||
|  | 		el("h4", t`3. Local Component State`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			function TodoItem({ id, title, completed }) { | 			function TodoItem({ id, title, completed }) { | ||||||
| 				const { host }= scope; | 				const { host }= scope; | ||||||
| @@ -266,50 +299,50 @@ export function page({ pkg, info }){ | |||||||
| 					// Component content... | 					// Component content... | ||||||
| 				); | 				); | ||||||
| 			} | 			} | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		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 | ||||||
| 			UI feedback while still communicating changes to the parent via events. | 			UI feedback while still communicating changes to the parent via events. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el("h4", t`3. Reactive Properties`), | 		el("h4", t`4. Reactive Properties`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			// Dynamic class attributes | 			// Dynamic class attributes | ||||||
| 			el("a", { | 			el("a", { | ||||||
| 				textContent: "All", | 				textContent, | ||||||
| 				className: S(()=> pageS.get() === "all" ? "selected" : ""), | 				classList: { selected: S(()=> pageS.get() === textContent.toLowerCase()) }, | ||||||
| 				href: "#" | 				href: \`#\${textContent.toLowerCase()}\` | ||||||
| 			}) | 			}) | ||||||
|  |  | ||||||
| 			// Reactive classList | 			// Reactive classList | ||||||
| 			el("li", { | 			el("li", { | ||||||
| 				classList: { completed: isCompleted, editing: isEditing } | 				classList: { completed: isCompleted, editing: isEditing } | ||||||
| 			}) | 			}) | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				Binding signals directly to element properties creates a reactive UI that automatically updates | 				Binding signals directly to element properties creates a reactive UI that automatically updates | ||||||
| 				when state changes, without the need for explicit DOM manipulation or virtual DOM diffing. | 				when state changes, without the need for explicit DOM manipulation or virtual DOM diffing. | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Performance Optimization with Memoization`), | 		el(h3, t`Performance Optimization with Memoization`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The implementation uses ${el("code", "memo")} to optimize performance in several key areas: | 			The implementation uses ${el("code", "memo")} to optimize performance in several key areas: | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el("h4", t`Memoizing Todo Items`), | 		el("h4", t`Memoizing Todo Items`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			el("ul", { className: "todo-list" }).append( | 			el("ul", { className: "todo-list" }).append( | ||||||
| 				S.el(filteredTodosS, filteredTodos => filteredTodos.map(todo => | 				S.el(todosFilteredS, filteredTodos => filteredTodos.map(todo => | ||||||
| 					memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) | 					memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			This approach ensures that: | 			This approach ensures that: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| @@ -321,22 +354,29 @@ 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, todos => memo(todos.length, length=> length | 			S.el(todosS, ({ length }) => !length | ||||||
| 				? el("footer", { className: "footer" }).append( | 				? el() | ||||||
| 					// Footer content... | 				: el("footer", { className: "footer" }).append( | ||||||
|  | 					// … | ||||||
|  | 					memo("filters", ()=> | ||||||
|  | 						// … | ||||||
|  | 								el("a", { | ||||||
|  | 									textContent, | ||||||
|  | 									classList: { selected: S(()=> pageS.get() === textContent.toLowerCase()) }, | ||||||
|  | 									href: \`#\${textContent.toLowerCase()}\` | ||||||
|  | 								}) | ||||||
|  | 					// … | ||||||
| 				) | 				) | ||||||
| 				: el() |  | ||||||
| 			)) | 			)) | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			By memoizing based on the todos length, the entire footer component is only re-rendered | 			We memoize the UI section and uses derived signal for the classList. Re-rendering this part is therefore | ||||||
| 			when todos are added or removed, not when their properties change. This improves performance | 			unnecessary when the number of todos changes. | ||||||
| 			by avoiding unnecessary DOM operations. |  | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				Memoization is especially important for UI elements that are expensive to render or that contain | 				Memoization is especially important for UI elements that are expensive to render or that contain | ||||||
| 				many child elements. The ${el("code", "memo")} function allows precise control over when components | 				many child elements. The ${el("code", "memo")} function allows precise control over when components | ||||||
| 				should re-render, avoiding the overhead of virtual DOM diffing algorithms. | 				should re-render, avoiding the overhead of virtual DOM diffing algorithms. | ||||||
| @@ -344,23 +384,25 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Component-Based Architecture with Events`), | 		el(h3, t`Component-Based Architecture with Events`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The TodoMVC implementation demonstrates a clean component architecture with custom events | 			The TodoMVC implementation demonstrates a clean component architecture with custom events | ||||||
| 			for communication between components: | 			for communication between components: | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el("h4", t`1. Main Component Event Handling`), | 		el("h4", t`1. Main Component Event Handling`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The main Todos component sets up event listeners to handle actions from child components: | 			The main Todos component sets up event listeners to handle actions from child components: | ||||||
| 		`), | 		`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			// Event handlers in the main component | 			// Event handlers in the main component | ||||||
| 			const onDelete = on("todo:delete", ev => S.action(todosS, "delete", ev.detail)); | 			const onDelete = on("todo:delete", ev => | ||||||
| 			const onEdit = on("todo:edit", ev => S.action(todosS, "edit", ev.detail)); | 				S.action(todosS, "delete", /** @type {{ detail: Todo["id"] }} */(ev).detail)); | ||||||
| 		`, page_id }), | 			const onEdit = on("todo:edit", ev => | ||||||
|  | 				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` | ||||||
| 			Each todo item is rendered by the TodoItem component that uses scopes, local signals, and custom events: | 			Each todo item is rendered by the TodoItem component that uses scopes, local signals, and custom events: | ||||||
| 		`), | 		`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| @@ -400,10 +442,10 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 				// Component implementation... | 				// Component implementation... | ||||||
| 			} | 			} | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				Using ${el("code", "scope")} and ${el("a", references.mdn_events).append("custom events")} | 				Using ${el("code", "scope")} and ${el("a", references.mdn_events).append("custom events")} | ||||||
| 				creates a clean separation of concerns. Each TodoItem component dispatches events up to the parent | 				creates a clean separation of concerns. Each TodoItem component dispatches events up to the parent | ||||||
| 				without directly manipulating the application state, following a unidirectional data flow pattern. | 				without directly manipulating the application state, following a unidirectional data flow pattern. | ||||||
| @@ -411,7 +453,7 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Improved DOM Updates with classList`), | 		el(h3, t`Improved DOM Updates with classList`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The implementation uses the reactive ${el("code", "classList")} property for efficient class updates: | 			The implementation uses the reactive ${el("code", "classList")} property for efficient class updates: | ||||||
| 		`), | 		`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| @@ -421,9 +463,9 @@ export function page({ pkg, info }){ | |||||||
| 			}).append( | 			}).append( | ||||||
| 				// Component content... | 				// Component content... | ||||||
| 			); | 			); | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Benefits of using ${el("code", "classList")}: | 			Benefits of using ${el("code", "classList")}: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| @@ -434,7 +476,7 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Improved Focus Management`), | 		el(h3, t`Improved Focus Management`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The implementation uses a dedicated function for managing focus in edit inputs: | 			The implementation uses a dedicated function for managing focus in edit inputs: | ||||||
| 		`), | 		`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| @@ -460,9 +502,9 @@ export function page({ pkg, info }){ | |||||||
| 				value: title, | 				value: title, | ||||||
| 				"data-id": id | 				"data-id": id | ||||||
| 			}, onBlurEdit, onKeyDown, addFocus) | 			}, onBlurEdit, onKeyDown, addFocus) | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			This approach offers several advantages: | 			This approach offers several advantages: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| @@ -473,7 +515,7 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				Using ${el("a", references.mdn_raf).append("requestAnimationFrame")} ensures that the focus operation | 				Using ${el("a", references.mdn_raf).append("requestAnimationFrame")} ensures that the focus operation | ||||||
| 				happens after the browser has finished rendering the DOM changes, which is more reliable than | 				happens after the browser has finished rendering the DOM changes, which is more reliable than | ||||||
| 				using setTimeout. | 				using setTimeout. | ||||||
| @@ -481,46 +523,47 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Efficient Conditional Rendering`), | 		el(h3, t`Efficient Conditional Rendering`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The implementation uses signals for efficient conditional rendering: | 			The implementation uses signals for efficient conditional rendering: | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		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("main", { className: "main" }).append( | 				? el() | ||||||
|  | 				: el("main", { className: "main" }).append( | ||||||
| 					// Main content with toggle all and todo list | 					// Main content with toggle all and todo list | ||||||
| 				) | 				) | ||||||
| 				: el() |  | ||||||
| 			) | 			) | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		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("form", null, onSubmitEdit).append( | 				? el() | ||||||
|  | 				: el("form", null, onSubmitEdit).append( | ||||||
| 					el("input", { | 					el("input", { | ||||||
| 						className: "edit", | 						className: "edit", | ||||||
| 						name: "edit", | 						name: formEdit, | ||||||
| 						value: title, | 						value: title, | ||||||
| 						"data-id": id |  | ||||||
| 					}, onBlurEdit, onKeyDown, addFocus) | 					}, onBlurEdit, onKeyDown, addFocus) | ||||||
| 				) | 				) | ||||||
| 				: el() |  | ||||||
| 			) | 			) | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el("h4", t`Conditional Clear Completed Button`), | 		el("h4", t`Conditional Clear Completed Button`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			S.el(S(() => todosS.get().some(todo => todo.completed)), | 			todos.length - todosRemainingS.get() === 0 | ||||||
| 				hasTodosCompleted=> hasTodosCompleted | 				? el() | ||||||
| 				? el("button", { textContent: "Clear completed", className: "clear-completed" }, onClearCompleted) | 				: memo("delete", () => | ||||||
| 				: el() | 					el("button", | ||||||
|  | 						{ textContent: "Clear completed", className: "clear-completed" }, | ||||||
|  | 						onClearCompleted) | ||||||
| 				) | 				) | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				Unlike frameworks that use a virtual DOM, dd<el> directly updates only the specific DOM elements | 				Unlike frameworks that use a virtual DOM, dd<el> directly updates only the specific DOM elements | ||||||
| 				that need to change. This approach is often more efficient for small to medium-sized applications, | 				that need to change. This approach is often more efficient for small to medium-sized applications, | ||||||
| 				especially when combined with strategic memoization. | 				especially when combined with strategic memoization. | ||||||
| @@ -528,7 +571,7 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Type Safety with JSDoc Comments`), | 		el(h3, t`Type Safety with JSDoc Comments`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The implementation uses comprehensive JSDoc comments to provide type safety without requiring TypeScript: | 			The implementation uses comprehensive JSDoc comments to provide type safety without requiring TypeScript: | ||||||
| 		`), | 		`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| @@ -563,10 +606,10 @@ export function page({ pkg, info }){ | |||||||
| 				if (event.key !== "Escape") return; | 				if (event.key !== "Escape") return; | ||||||
| 				isEditing.set(false); | 				isEditing.set(false); | ||||||
| 			}); | 			}); | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el("div", { className: "tip" }).append( | 		el("div", { className: "tip" }).append( | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				Using JSDoc comments provides many of the benefits of TypeScript (autocomplete, type checking, | 				Using JSDoc comments provides many of the benefits of TypeScript (autocomplete, type checking, | ||||||
| 				documentation) while maintaining pure JavaScript code. This approach works well with modern | 				documentation) while maintaining pure JavaScript code. This approach works well with modern | ||||||
| 				IDEs that support JSDoc type inference. | 				IDEs that support JSDoc type inference. | ||||||
| @@ -575,41 +618,41 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 		el(h3, t`Best Practices Demonstrated`), | 		el(h3, t`Best Practices Demonstrated`), | ||||||
| 		el("ol").append( | 		el("ol").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Component Composition:")} Breaking the UI into focused, reusable components | 				${el("strong", "Component Composition:")} Breaking the UI into focused, reusable components | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Performance Optimization:")} Strategic memoization to minimize DOM operations | 				${el("strong", "Performance Optimization:")} Strategic memoization to minimize DOM operations | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Reactive State Management:")} Using signals with derived computations | 				${el("strong", "Reactive State Management:")} Using signals with derived computations | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Event-Based Communication:")} Using custom events for component communication | 				${el("strong", "Event-Based Communication:")} Using custom events for component communication | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Local Component State:")} Maintaining UI state within components for better encapsulation | 				${el("strong", "Local Component State:")} Maintaining UI state within components for better encapsulation | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Declarative Class Management:")} Using the classList property for cleaner class handling | 				${el("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 requestAnimationFrame | ||||||
| 			`), | 			`), | ||||||
| 			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 | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Type Safety:")} Using comprehensive JSDoc comments for type checking and documentation | 				${el("strong", "Type Safety:")} Using comprehensive JSDoc comments for type checking and documentation | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Composable Event Handlers:")} Attaching multiple event handlers to elements | 				${el("strong", "Composable Event Handlers:")} Attaching multiple event handlers to elements | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("div", { className: "callout" }).append( | 		el("div", { className: "callout" }).append( | ||||||
| 			el("h4", t`Key Takeaways`), | 			el("h4", t`Key Takeaways`), | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				This TodoMVC implementation showcases the strengths of dd<el> for building real-world applications: | 				This TodoMVC implementation showcases the strengths of dd<el> for building real-world applications: | ||||||
| 			`), | 			`), | ||||||
| 			el("ul").append( | 			el("ul").append( | ||||||
| @@ -622,7 +665,7 @@ export function page({ pkg, info }){ | |||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			You can find the ${el("a", references.github_example).append("complete source code")} for this example on GitHub. | 			You can find the ${el("a", references.github_example).append("complete source code")} for this example on GitHub. | ||||||
| 			Feel free to use it as a reference for your own projects or as a starting point for more complex applications. | 			Feel free to use it as a reference for your own projects or as a starting point for more complex applications. | ||||||
| 		`), | 		`), | ||||||
|   | |||||||
| @@ -14,10 +14,9 @@ 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` | ||||||
| 				This part of the documentation is primarily intended for technical enthusiasts and authors of | 				This part of the documentation is primarily intended for technical enthusiasts and authors of | ||||||
| 				3rd-party libraries. It describes an advanced feature, not a core part of the library. Most users will | 				3rd-party libraries. It describes an advanced feature, not a core part of the library. Most users will | ||||||
| 				not need to implement this functionality directly in their applications. This capability will hopefully | 				not need to implement this functionality directly in their applications. This capability will hopefully | ||||||
| @@ -25,30 +24,30 @@ export function page({ pkg, info }){ | |||||||
| 				dd<el>. | 				dd<el>. | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			dd<el> isn’t limited to browser environments. Thanks to its flexible architecture, | 			dd<el> isn’t limited to browser environments. Thanks to its flexible architecture, | ||||||
| 			it can be used for server-side rendering (SSR) to generate static HTML files. | 			it can be used for server-side rendering (SSR) to generate static HTML files. | ||||||
| 			This is achieved through integration with for example ${el("a", { href: "https://github.com/tmpvar/jsdom", | 			This is achieved through integration with for example ${el("a", { href: "https://github.com/tmpvar/jsdom", | ||||||
| 			textContent: "jsdom" })}, a JavaScript implementation of web standards for Node.js. | 			textContent: "jsdom" })}, a JavaScript implementation of web standards for Node.js. | ||||||
| 		`), | 		`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Additionally, you might consider using these alternative solutions: | 			Additionally, you might consider using these alternative solutions: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("a", { href: "https://github.com/capricorn86/happy-dom", textContent: "happy-dom" })} — | 				${el("a", { href: "https://github.com/capricorn86/happy-dom", textContent: "happy-dom" })} — | ||||||
| 				A JavaScript implementation of a web browser without its graphical user interface that’s faster than jsdom | 				A JavaScript implementation of a web browser without its graphical user interface that’s faster than jsdom | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("a", { href: "https://github.com/WebReflection/linkedom", textContent: "linkedom" })} — | 				${el("a", { href: "https://github.com/WebReflection/linkedom", textContent: "linkedom" })} — | ||||||
| 				A lightweight DOM implementation specifically designed for SSR with significantly better performance | 				A lightweight DOM implementation specifically designed for SSR with significantly better performance | ||||||
| 				than jsdom | 				than jsdom | ||||||
| 			`), | 			`), | ||||||
| 		), | 		), | ||||||
| 		el(code, { src: fileURL("./components/examples/ssr/intro.js"), page_id }), | 		el(code, { src: fileURL("./components/examples/ssr/intro.js") }), | ||||||
|  |  | ||||||
| 		el(h3, t`Why Server-Side Rendering?`), | 		el(h3, t`Why Server-Side Rendering?`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			SSR offers several benefits: | 			SSR offers several benefits: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| @@ -60,7 +59,7 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`How jsdom Integration Works`), | 		el(h3, t`How jsdom Integration Works`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The jsdom export in dd<el> provides the necessary tools to use the library in Node.js | 			The jsdom export in dd<el> provides the necessary tools to use the library in Node.js | ||||||
| 			by integrating with jsdom. Here’s what it does: | 			by integrating with jsdom. Here’s what it does: | ||||||
| 		`), | 		`), | ||||||
| @@ -71,58 +70,58 @@ 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"), page_id }), | 		el(code, { src: fileURL("./components/examples/ssr/start.js") }), | ||||||
|  |  | ||||||
| 		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"), page_id }), | 		el(code, { src: fileURL("./components/examples/ssr/basic-example.js") }), | ||||||
|  |  | ||||||
| 		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"), page_id }), | 		el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js") }), | ||||||
|  |  | ||||||
| 		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"), page_id }), | 		el(code, { src: fileURL("./components/examples/ssr/async-data.js") }), | ||||||
|  |  | ||||||
| 		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` | ||||||
| 			When structuring server-side rendering code, a crucial pattern to follow is using dynamic imports | 			When structuring server-side rendering code, a crucial pattern to follow is using dynamic imports | ||||||
| 			for both the deka-dom-el/jsdom module and your page components. | 			for both the deka-dom-el/jsdom module and your page components. | ||||||
| 		`), | 		`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Why is this important? | 			Why is this important? | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Static imports are hoisted:")} JavaScript hoists import statements to the top of the file, | 				${el("strong", "Static imports are hoisted:")} JavaScript hoists import statements to the top of the file, | ||||||
| 				executing them before any other code | 				executing them before any other code | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Environment registration timing:")} The jsdom module auto-registers the DOM environment | 				${el("strong", "Environment registration timing:")} The jsdom module auto-registers the DOM environment | ||||||
| 				when imported, which must happen ${el("em", "after")} you’ve created your JSDOM instance and | 				when imported, which must happen ${el("em", "after")} you’ve created your JSDOM instance and | ||||||
| 				${el("em", "before")} you import your components using ${el("code", "import { el } from \"deka-dom-el\";")}. | 				${el("em", "before")} you import your components using ${el("code", "import { el } from \"deka-dom-el\";")}. | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Correct initialization order:")} You need to control the exact sequence of: | 				${el("strong", "Correct initialization order:")} You need to control the exact sequence of: | ||||||
| 				create JSDOM → register environment → import components | 				create JSDOM → register environment → import components | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
| 		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"), page_id }), | 		el(code, { src: fileURL("./components/examples/ssr/pages.js") }), | ||||||
|  |  | ||||||
| 		el(h3, t`SSR Considerations and Limitations`), | 		el(h3, t`SSR Considerations and Limitations`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			When using dd<el> for SSR, keep these considerations in mind: | 			When using dd<el> for SSR, keep these considerations in mind: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| @@ -131,19 +130,19 @@ export function page({ pkg, info }){ | |||||||
| 			el("li", t`Some DOM features may behave differently in jsdom compared to real browsers`), | 			el("li", t`Some DOM features may behave differently in jsdom compared to real browsers`), | ||||||
| 			el("li", t`For large sites, you may need to optimize memory usage by creating a new jsdom instance for each page`) | 			el("li", t`For large sites, you may need to optimize memory usage by creating a new jsdom instance for each page`) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			For advanced SSR applications, consider implementing hydration on the client-side to restore | 			For advanced SSR applications, consider implementing hydration on the client-side to restore | ||||||
| 			interactivity after the initial render. | 			interactivity after the initial render. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el(h3, t`Real Example: How This Documentation is Built`), | 		el(h3, t`Real Example: How This Documentation is Built`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			This documentation site itself is built using dd<el>’s SSR capabilities. | 			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"), page_id }), | 		el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js") }), | ||||||
|  |  | ||||||
| 		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, | ||||||
| 			providing fast loading times and excellent SEO without the need for client-side JavaScript | 			providing fast loading times and excellent SEO without the need for client-side JavaScript | ||||||
| 			to render the initial content. | 			to render the initial content. | ||||||
|   | |||||||
| @@ -1,9 +1,8 @@ | |||||||
| 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`Interactive Demo Components with Server-Side Pre-Rendering`, | 	fullTitle: t`Server-Side Pre-Rendering and Client-Side Rehydration`, | ||||||
| 	description: t`Creating live, interactive component examples in documentation with server-side | 	description: t`Using Ireland components for server-side pre-rendering and client-side rehydration`, | ||||||
| 		rendering and client-side hydration.`, |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| import { el } from "deka-dom-el"; | import { el } from "deka-dom-el"; | ||||||
| @@ -16,10 +15,9 @@ 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` | ||||||
| 				This part of the documentation is primarily intended for technical enthusiasts and authors of | 				This part of the documentation is primarily intended for technical enthusiasts and authors of | ||||||
| 				3rd-party libraries. It describes an advanced feature, not a core part of the library. Most users will | 				3rd-party libraries. It describes an advanced feature, not a core part of the library. Most users will | ||||||
| 				not need to implement this functionality directly in their applications. This capability will hopefully | 				not need to implement this functionality directly in their applications. This capability will hopefully | ||||||
| @@ -29,7 +27,7 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`What Are Ireland Components?`), | 		el(h3, t`What Are Ireland Components?`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Ireland components are a special type of component that: | 			Ireland components are a special type of component that: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| @@ -38,24 +36,24 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`How Ireland Components Work`), | 		el(h3, t`How Ireland Components Work`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The Ireland component system consists of several parts working together: | 			The Ireland component system consists of several parts working together: | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el("ol").append( | 		el("ol").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Server-side rendering:")} Components are pre-rendered during the documentation build process | 				${el("strong", "Server-side rendering:")} Components are pre-rendered during the documentation build process | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Component registration:")} Source files are copied to the documentation output directory | 				${el("strong", "Component registration:")} Source files are copied to the documentation output directory | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Client-side scripting:")} JavaScript code is generated to load and render components | 				${el("strong", "Client-side scripting:")} JavaScript code is generated to load and render components | ||||||
| 			`), | 			`), | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Implementation Architecture`), | 		el(h3, t`Implementation Architecture`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The core of the Ireland system is implemented in ${el("code", "docs/components/ireland.html.js")}. | 			The core of the Ireland system is implemented in ${el("code", "docs/components/ireland.html.js")}. | ||||||
| 			It integrates with the SSR build process using the ${el("code", "registerClientFile")} function | 			It integrates with the SSR build process using the ${el("code", "registerClientFile")} function | ||||||
| 			from ${el("code", "docs/ssr.js")}. | 			from ${el("code", "docs/ssr.js")}. | ||||||
| @@ -67,9 +65,9 @@ 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", | ||||||
| 			}) | 			}) | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		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: | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| @@ -81,7 +79,7 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Core Implementation Details`), | 		el(h3, t`Core Implementation Details`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Let's look at the key parts of the ireland component implementation: | 			Let's look at the key parts of the ireland component implementation: | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| @@ -119,7 +117,7 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 			// Final build step - trigger SSR end event | 			// Final build step - trigger SSR end event | ||||||
| 			dispatchEvent("onssrend"); | 			dispatchEvent("onssrend"); | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
| 		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 | ||||||
| @@ -145,7 +143,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); | ||||||
| 			} | 			} | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
| 		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 | ||||||
| @@ -226,7 +224,7 @@ export function page({ pkg, info }){ | |||||||
| 					\`.trim()) | 					\`.trim()) | ||||||
| 				); | 				); | ||||||
| 			} | 			} | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
| 		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 | ||||||
| @@ -250,40 +248,36 @@ export function page({ pkg, info }){ | |||||||
| 				}); | 				}); | ||||||
| 				}); | 				}); | ||||||
| 			} | 			} | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el(h3, t`Live Example`), | 		el(h3, t`Live Example`), | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				Here’s a live example of an Ireland component showing a standard counter. | 				Here’s a live example of an Ireland component showing a standard counter. | ||||||
| 				The component is defined in ${el("code", "docs/components/examples/ireland-test/counter.js")} and | 				The component is defined in ${el("code", "docs/components/examples/ireland-test/counter.js")} and | ||||||
| 				rendered with the Ireland component system: | 				rendered with the Ireland component system: | ||||||
| 			`), | 			`), | ||||||
|  |  | ||||||
| 			el(code, { | 			el(code, { src: fileURL("./components/examples/ireland-test/counter.js") }), | ||||||
| 				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` | ||||||
| 				When the page is loaded, the component is also loaded and rendered. The counter state is maintained | 				When the page is loaded, the component is also loaded and rendered. The counter state is maintained | ||||||
| 				using signals, allowing for reactive updates as you click the buttons to increment and decrement the | 				using signals, allowing for reactive updates as you click the buttons to increment and decrement the | ||||||
| 				value. | 				value. | ||||||
| 			`), | 			`), | ||||||
|  |  | ||||||
| 		el(h3, t`Practical Considerations and Limitations`), | 		el(h3, t`Practical Considerations and Limitations`), | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				When implementing Ireland components in real documentation, there are several important | 				When implementing Ireland components in real documentation, there are several important | ||||||
| 				considerations to keep in mind: | 				considerations to keep in mind: | ||||||
| 			`), | 			`), | ||||||
|  |  | ||||||
| 			el("div", { className: "warning" }).append( | 			el("div", { className: "warning" }).append( | ||||||
| 				el("h4", t`Module Resolution and Bundling`), | 				el("h4", t`Module Resolution and Bundling`), | ||||||
| 				el("p").append(...T` | 				el("p").append(T` | ||||||
| 					The examples shown here use bare module specifiers like ${el("code", | 					The examples shown here use bare module specifiers like ${el("code", | ||||||
| 						`import { el } from "deka-dom-el"`)} which aren’t supported in all browsers without importmaps. | 						`import { el } from "deka-dom-el"`)} which aren’t supported in all browsers without importmaps. | ||||||
| 					In a production implementation, you would need to: `), | 					In a production implementation, you would need to: `), | ||||||
| @@ -292,7 +286,7 @@ export function page({ pkg, info }){ | |||||||
| 					el("li", t`Bundle component dependencies to avoid multiple requests`), | 					el("li", t`Bundle component dependencies to avoid multiple requests`), | ||||||
| 					el("li", t`Ensure all module dependencies are properly resolved and copied to the output directory`) | 					el("li", t`Ensure all module dependencies are properly resolved and copied to the output directory`) | ||||||
| 				), | 				), | ||||||
| 				el("p").append(...T` | 				el("p").append(T` | ||||||
| 					In this documentation, we replace the paths with ${el("code", "./esm-with-signals.js")} and provide | 					In this documentation, we replace the paths with ${el("code", "./esm-with-signals.js")} and provide | ||||||
| 					a bundled version of the library, but more complex components might require a dedicated bundling step. | 					a bundled version of the library, but more complex components might require a dedicated bundling step. | ||||||
| 				`) | 				`) | ||||||
| @@ -300,7 +294,7 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 			el("div", { className: "note" }).append( | 			el("div", { className: "note" }).append( | ||||||
| 				el("h4", t`Component Dependencies`), | 				el("h4", t`Component Dependencies`), | ||||||
| 				el("p").append(...T` | 				el("p").append(T` | ||||||
| 					Real-world components typically depend on multiple modules and assets. The Ireland system would need | 					Real-world components typically depend on multiple modules and assets. The Ireland system would need | ||||||
| 					to be extended to: | 					to be extended to: | ||||||
| 				`), | 				`), | ||||||
| @@ -312,7 +306,7 @@ export function page({ pkg, info }){ | |||||||
| 			), | 			), | ||||||
|  |  | ||||||
| 			el(h3, t`Advanced Usage`), | 			el(h3, t`Advanced Usage`), | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				The Ireland system can be extended in several ways to address these limitations: | 				The Ireland system can be extended in several ways to address these limitations: | ||||||
| 			`), | 			`), | ||||||
|  |  | ||||||
| @@ -325,7 +319,7 @@ export function page({ pkg, info }){ | |||||||
| 				el("li", t`Implement state persistence between runs`) | 				el("li", t`Implement state persistence between runs`) | ||||||
| 			), | 			), | ||||||
|  |  | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				This documentation site itself is built using the techniques described here, | 				This documentation site itself is built using the techniques described here, | ||||||
| 				showcasing how dd<el> can be used to create both the documentation and | 				showcasing how dd<el> can be used to create both the documentation and | ||||||
| 				the interactive examples within it. The implementation here is simplified for clarity, | 				the interactive examples within it. The implementation here is simplified for clarity, | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ import { T, t } from "./utils/index.js"; | |||||||
| export const info= { | export const info= { | ||||||
| 	title: t`Appendix & Summary`, | 	title: t`Appendix & Summary`, | ||||||
| 	fullTitle: t`dd<el> Comprehensive Reference`, | 	fullTitle: t`dd<el> Comprehensive Reference`, | ||||||
| 	description: t`A final overview, case studies, key concepts, and best practices for working with deka-dom-el.`, | 	description: t`A final overview, case studies, key concepts, and best practices for working with deka-dom-el.`, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| import { el } from "deka-dom-el"; | import { el } from "deka-dom-el"; | ||||||
| @@ -25,63 +25,80 @@ const references= { | |||||||
| 	performance: { | 	performance: { | ||||||
| 		title: t`Performance Optimization Guide`, | 		title: t`Performance Optimization Guide`, | ||||||
| 		href: "p09-optimization.html", | 		href: "p09-optimization.html", | ||||||
|  | 	}, | ||||||
|  | 	/** Examples gallery */ | ||||||
|  | 	examples: { | ||||||
|  | 		title: t`Examples Gallery`, | ||||||
|  | 		href: "p15-examples.html", | ||||||
|  | 	}, | ||||||
|  | 	/** Converter */ | ||||||
|  | 	converter: { | ||||||
|  | 		title: t`HTML to dd<el> Converter`, | ||||||
|  | 		href: "p14-converter.html", | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** @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, | ||||||
| 			case studies, and advanced techniques. Use it as a quick reference when working with the library | 			case studies, and advanced techniques. Use it as a quick reference when working with the library | ||||||
| 			or to deepen your understanding of its design principles and patterns. | 			or to deepen your understanding of its design principles and patterns. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| 		el(h3, t`Core Principles of dd<el>`), | 		el(h3, t`Core Principles of dd<el>`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			At its foundation, dd<el> is built on several core principles that shape its design and usage: | 			At its foundation, dd<el> is built on several core principles that shape its design and usage: | ||||||
| 		`), | 		`), | ||||||
| 		el("div", { className: "callout" }).append( | 		el("div", { className: "callout" }).append( | ||||||
| 			el("h4", t`Guiding Principles`), | 			el("h4", t`Guiding Principles`), | ||||||
| 			el("ul").append( | 			el("ul").append( | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "DOM-First Approach:")} Working directly with the real DOM instead of virtual DOM | 					${el("strong", "DOM-First Approach:")} Working directly with the real DOM instead of virtual DOM | ||||||
| 					abstractions | 					abstractions | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "Declarative Syntax:")} Creating UIs by describing what they should look like, not | 					${el("strong", "Declarative Syntax:")} Creating UIs by describing what they should look like, not | ||||||
| 					how to create them | 					how to create them | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "Minimal Overhead:")} Staying close to standard Web APIs without unnecessary | 					${el("strong", "Minimal Overhead:")} Staying close to standard Web APIs without unnecessary | ||||||
| 					abstractions | 					abstractions | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "Progressive Enhancement:")} Starting simple and adding complexity only when needed | 					${el("strong", "Progressive Enhancement:")} Starting simple and adding complexity only when needed | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "Functional Composition:")} Building UIs through function composition rather than | 					${el("strong", "Flexibility:")} Using what you need, whether that’s plain DOM elements, event | ||||||
| 					inheritance | 					handling, or signals for reactivity | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
|  | 					${el("strong", "Functional Composition:")} Building UIs through function composition | ||||||
|  | 				`), | ||||||
|  | 				el("li").append(T` | ||||||
| 					${el("strong", "Clear Patterns:")} Promoting maintainable code organization with the 3PS pattern | 					${el("strong", "Clear Patterns:")} Promoting maintainable code organization with the 3PS pattern | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "Targeted Reactivity:")} Using signals for efficient, fine-grained updates | 					${el("strong", "Targeted Reactivity:")} Using signals for efficient, fine-grained updates only when | ||||||
|  | 					needed | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "Unix Philosophy:")} Doing one thing well and allowing composability with other tools | 					${el("strong", "Unix Philosophy:")} Doing one thing well and allowing composability with other tools | ||||||
| 				`) | 				`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Case Studies & Real-World Applications`), | 		el(h3, t`Case Studies & Real-World Applications`), | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			Explore our ${el("a", references.examples).append("Examples Gallery")} to see how dd<el> can be used to build | ||||||
|  | 			various real-world applications, from simple components to complex interactive UIs. | ||||||
|  | 		`), | ||||||
|  |  | ||||||
| 		el("h4", t`TodoMVC Implementation`), | 		el("h4", t`TodoMVC Implementation`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The ${el("a", references.todomvc).append("TodoMVC implementation")} showcases how dd<el> handles a complete, | 			The ${el("a", references.todomvc).append("TodoMVC implementation")} showcases how dd<el> handles a complete, | ||||||
| 			real-world application with all standard features of a modern web app: | 			real-world application with all standard features of a modern web app: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| 			el("li", t`Persistent storage with localStorage`), | 			el("li", t`Persistent storage with localStorage`), | ||||||
| @@ -92,41 +109,43 @@ export function page({ pkg, info }){ | |||||||
| 			el("li", t`Custom event system for component communication`), | 			el("li", t`Custom event system for component communication`), | ||||||
| 			el("li", t`Proper focus management and accessibility`) | 			el("li", t`Proper focus management and accessibility`) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			Key takeaways from the TodoMVC example: | 			Key takeaways from the TodoMVC example: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				Signal factories like ${el("code", "routerSignal")} and ${el("code", "todosSignal")} | 				Signal factories like ${el("code", "routerSignal")} and ${el("code", "todosSignal")} | ||||||
| 				encapsulate related functionality | 				encapsulate related functionality | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				Custom events provide clean communication between components | 				Custom events provide clean communication between components | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				Targeted memoization improves rendering performance dramatically | 				Targeted memoization improves rendering performance dramatically | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				Derived signals simplify complex UI logic like filtering | 				Derived signals simplify complex UI logic like filtering | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("h4", t`Migrating from Traditional Approaches`), | 		el("h4", t`Migrating from Traditional Approaches`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			When migrating from traditional DOM manipulation or other frameworks to dd<el>: | 			When migrating from traditional DOM manipulation or other frameworks to dd<el>: | ||||||
| 		`), | 		`), | ||||||
| 		el("ol").append( | 		el("ol").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Start with state:")}: Convert global variables or ad-hoc state to signals | 				${el("strong", "Start with state")}: Convert global variables or ad-hoc state to signals | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Replace query selectors:")}: Replace getElementById/querySelector with direct references to elements | 				${el("strong", "Replace query selectors")}: Replace getElementById/querySelector with direct references | ||||||
|  | 				to elements | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Convert imperative updates:")}: Replace manual DOM updates with declarative signal bindings | 				${el("strong", "Convert imperative updates")}: Replace manual DOM updates with declarative signal | ||||||
|  | 				bindings | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Refactor into components:")}: Organize related UI elements into component functions | 				${el("strong", "Refactor into components")}: Organize related UI elements into component functions | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| @@ -146,7 +165,7 @@ export function page({ pkg, info }){ | |||||||
| 					className: S(() => countS.get() > 10 ? 'warning' : '') | 					className: S(() => countS.get() > 10 ? 'warning' : '') | ||||||
| 				}) | 				}) | ||||||
| 			); | 			); | ||||||
| 		`, page_id }), | 		`, language: "js" }), | ||||||
|  |  | ||||||
| 		el(h3, t`Key Concepts Reference`), | 		el(h3, t`Key Concepts Reference`), | ||||||
|  |  | ||||||
| @@ -157,7 +176,7 @@ export function page({ pkg, info }){ | |||||||
| 				el("dd", t`Core function for creating DOM elements with declarative properties`), | 				el("dd", t`Core function for creating DOM elements with declarative properties`), | ||||||
|  |  | ||||||
| 				el("dt", t`el().append(...children)`), | 				el("dt", t`el().append(...children)`), | ||||||
| 				el("dd", t`Add child elements to a parent element`), | 				el("dd", t`Add child elements to a parent element`), | ||||||
|  |  | ||||||
| 				el("dt", t`memo(key, () => element)`), | 				el("dt", t`memo(key, () => element)`), | ||||||
| 				el("dd", t`Cache and reuse DOM elements for performance optimization`), | 				el("dd", t`Cache and reuse DOM elements for performance optimization`), | ||||||
| @@ -171,22 +190,22 @@ export function page({ pkg, info }){ | |||||||
| 			el("h4", t`Signals & Reactivity`), | 			el("h4", t`Signals & Reactivity`), | ||||||
| 			el("dl").append( | 			el("dl").append( | ||||||
| 				el("dt", t`S(initialValue)`), | 				el("dt", t`S(initialValue)`), | ||||||
| 				el("dd", t`Create a signal with an initial value`), | 				el("dd", t`Create a signal with an initial value`), | ||||||
|  |  | ||||||
| 				el("dt", t`S(() => computation)`), | 				el("dt", t`S(() => computation)`), | ||||||
| 				el("dd", t`Create a derived signal that updates when dependencies change`), | 				el("dd", t`Create a derived signal that updates when dependencies change`), | ||||||
|  |  | ||||||
| 				el("dt", t`S.el(signal, data => element)`), | 				el("dt", t`S.el(signal, data => element)`), | ||||||
| 				el("dd", t`Create reactive elements that update when a signal changes`), | 				el("dd", t`Create reactive elements that update when a signal changes`), | ||||||
|  |  | ||||||
| 				el("dt", t`S.action(signal, "method", ...args)`), | 				el("dt", t`S.action(signal, "method", ...args)`), | ||||||
| 				el("dd", t`Call custom methods defined on a signal`), | 				el("dd", t`Call custom methods defined on a signal`), | ||||||
|  |  | ||||||
| 				el("dt", t`signal.get()`), | 				el("dt", t`signal.get()`), | ||||||
| 				el("dd", t`Get the current value of a signal`), | 				el("dd", t`Get the current value of a signal`), | ||||||
|  |  | ||||||
| 				el("dt", t`signal.set(newValue)`), | 				el("dt", t`signal.set(newValue)`), | ||||||
| 				el("dd", t`Update a signal's value and trigger reactive updates`) | 				el("dd", t`Update a signal’s value and trigger reactive updates`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| @@ -200,7 +219,7 @@ export function page({ pkg, info }){ | |||||||
| 				el("dd", t`Provides access to component context, signal, host element`), | 				el("dd", t`Provides access to component context, signal, host element`), | ||||||
|  |  | ||||||
| 				el("dt", t`dispatchEvent(type, element)`), | 				el("dt", t`dispatchEvent(type, element)`), | ||||||
| 				el("dd", t`Creates a function for dispatching custom events`), | 				el("dd", t`Creates a function for dispatching custom events`), | ||||||
|  |  | ||||||
| 				el("dt", t`Signal Factories`), | 				el("dt", t`Signal Factories`), | ||||||
| 				el("dd", t`Functions that create and configure signals with domain-specific behavior`) | 				el("dd", t`Functions that create and configure signals with domain-specific behavior`) | ||||||
| @@ -211,20 +230,46 @@ export function page({ pkg, info }){ | |||||||
| 		el("div").append( | 		el("div").append( | ||||||
| 			el("h4", t`Code Organization`), | 			el("h4", t`Code Organization`), | ||||||
| 			el("ul").append( | 			el("ul").append( | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "Follow the 3PS pattern:")}: Separate state creation, binding to elements, and state updates | 					${el("strong", "Follow the 3PS pattern")}: Separate state creation, binding to elements, and state updates | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "Use component functions:")}: Create reusable UI components as functions | 					${el("strong", "Use component functions")}: Create reusable UI components as functions | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "Create signal factories:")}: Extract reusable signal patterns into factory functions | 					${el("strong", "Create signal factories")}: Extract reusable signal patterns into factory functions | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "Leverage scopes:")}: Use scope for component context and clean resource management | 					${el("strong", "Leverage scopes")}: Use scope for component context and clean resource management | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "Event delegation:")}: Prefer component-level event handlers over many individual handlers | 					${el("strong", "Event delegation")}: Prefer component-level event handlers over many individual handlers | ||||||
|  | 				`) | ||||||
|  | 			) | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		el("div").append( | ||||||
|  | 			el("h4", t`When to Use Signals vs. Plain DOM`), | ||||||
|  | 			el("ul").append( | ||||||
|  | 				el("li").append(T` | ||||||
|  | 					${el("strong", "Use signals for")}: Data that changes frequently, multiple elements that need to | ||||||
|  | 					stay in sync, computed values dependent on other state | ||||||
|  | 				`), | ||||||
|  | 				el("li").append(T` | ||||||
|  | 					${el("strong", "Use plain DOM for")}: Static content, one-time DOM operations, simple toggling of | ||||||
|  | 					elements, single-element updates | ||||||
|  | 				`), | ||||||
|  | 				el("li").append(T` | ||||||
|  | 					${el("strong", "Mixed approach")}: Start with plain DOM and events, then add signals only where | ||||||
|  | 					needed for reactivity | ||||||
|  | 				`), | ||||||
|  | 				el("li").append(T` | ||||||
|  | 					${el("strong", "Consider derived signals")}: For complex transformations of data rather than manual | ||||||
|  | 					updates | ||||||
|  | 				`), | ||||||
|  | 				el("li").append(T` | ||||||
|  | 					${el("strong", "Use event delegation")}: For handling multiple similar interactions without | ||||||
|  | 					individual signal bindings | ||||||
| 				`) | 				`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| @@ -232,23 +277,23 @@ export function page({ pkg, info }){ | |||||||
| 		el("div").append( | 		el("div").append( | ||||||
| 			el("h4", t`Performance Optimization`), | 			el("h4", t`Performance Optimization`), | ||||||
| 			el("ul").append( | 			el("ul").append( | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "Memoize list items:")}: Use ${el("code", "memo")} for items in frequently-updated lists | 					${el("strong", "Memoize list items")}: Use ${el("code", "memo")} for items in frequently-updated lists | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "Avoid unnecessary signal updates:")}: Only update signals when values actually change | 					${el("strong", "Avoid unnecessary signal updates")}: Only update signals when values actually change | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "Use AbortSignals:")}: Clean up resources when components are removed | 					${el("strong", "Use AbortSignals")}: Clean up resources when components are removed | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "Prefer derived signals:")}: Use computed values instead of manual updates | 					${el("strong", "Prefer derived signals")}: Use computed values instead of manual updates | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(...T` | 				el("li").append(T` | ||||||
| 					${el("strong", "Avoid memoizing fragments:")}: Never memoize DocumentFragments, only individual elements | 					${el("strong", "Avoid memoizing fragments")}: Never memoize DocumentFragments, only individual elements | ||||||
| 				`) | 				`) | ||||||
| 			), | 			), | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				See the ${el("a", references.performance).append("Performance Optimization Guide")} for detailed strategies. | 				See the ${el("a", references.performance).append("Performance Optimization Guide")} for detailed strategies. | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
| @@ -263,7 +308,7 @@ export function page({ pkg, info }){ | |||||||
| 				el("dd", t`Use scope.signal or AbortSignals to handle resource cleanup when elements are removed`), | 				el("dd", t`Use scope.signal or AbortSignals to handle resource cleanup when elements are removed`), | ||||||
|  |  | ||||||
| 				el("dt", t`Circular Signal Dependencies`), | 				el("dt", t`Circular Signal Dependencies`), | ||||||
| 				el("dd", t`Avoid signals that depend on each other in a circular way, which can cause infinite update loops`), | 				el("dd", t`Avoid signals that depend on each other in a circular way, which can cause infinite update loops`), | ||||||
|  |  | ||||||
| 				el("dt", t`Memoizing with Unstable Keys`), | 				el("dt", t`Memoizing with Unstable Keys`), | ||||||
| 				el("dd", t`Always use stable, unique identifiers as memo keys, not array indices or objects`), | 				el("dd", t`Always use stable, unique identifiers as memo keys, not array indices or objects`), | ||||||
| @@ -279,104 +324,121 @@ export function page({ pkg, info }){ | |||||||
| 				el("tr").append( | 				el("tr").append( | ||||||
| 					el("th", "Feature"), | 					el("th", "Feature"), | ||||||
| 					el("th", "dd<el>"), | 					el("th", "dd<el>"), | ||||||
| 					el("th", "React"), | 					el("th", "VanJS"), | ||||||
| 					el("th", "Vue"), | 					el("th", "Solid"), | ||||||
| 					el("th", "Svelte") | 					el("th", "Alpine") | ||||||
| 				) | 				) | ||||||
| 			), | 			), | ||||||
| 			el("tbody").append( | 			el("tbody").append( | ||||||
| 				el("tr").append( | 				el("tr").append( | ||||||
| 					el("td", "No Build Step Required"), | 					el("td", "No Build Step Required"), | ||||||
| 					el("td", "✅"), | 					el("td", "✅"), | ||||||
|  | 					el("td", "✅"), | ||||||
| 					el("td", "⚠️ JSX needs transpilation"), | 					el("td", "⚠️ JSX needs transpilation"), | ||||||
| 					el("td", "⚠️ SFC needs compilation"), | 					el("td", "✅") | ||||||
| 					el("td", "❌ Requires compilation") |  | ||||||
| 				), | 				), | ||||||
| 				el("tr").append( | 				el("tr").append( | ||||||
| 					el("td", "Bundle Size (minimal)"), | 					el("td", "Bundle Size (minified)"), | ||||||
| 					el("td", "~10-15kb"), | 					el("td", "~14kb"), | ||||||
| 					el("td", "~40kb+"), | 					el("td", "~3kb"), | ||||||
| 					el("td", "~33kb+"), | 					el("td", "~20kb"), | ||||||
| 					el("td", "Minimal runtime") | 					el("td", "~43kb") | ||||||
| 				), | 				), | ||||||
| 				el("tr").append( | 				el("tr").append( | ||||||
| 					el("td", "Reactivity Model"), | 					el("td", "Reactivity Model"), | ||||||
| 					el("td", "Signal-based"), | 					el("td", "Signal-based"), | ||||||
| 					el("td", "Virtual DOM diffing"), | 					el("td", "Signal-based (basics only)"), | ||||||
| 					el("td", "Proxy-based"), | 					el("td", "Signal-based"), | ||||||
| 					el("td", "Compile-time reactivity") | 					el("td", "MVVM + Proxy") | ||||||
| 				), | 				), | ||||||
| 				el("tr").append( | 				el("tr").append( | ||||||
| 					el("td", "DOM Interface"), | 					el("td", "DOM Interface"), | ||||||
| 					el("td", "Direct DOM API"), | 					el("td", "Direct DOM API"), | ||||||
| 					el("td", "Virtual DOM"), | 					el("td", "Direct DOM API"), | ||||||
| 					el("td", "Virtual DOM"), | 					el("td", "Compiled DOM updates"), | ||||||
| 					el("td", "Compiled DOM updates") | 					el("td", "Directive-based") | ||||||
| 				), | 				), | ||||||
| 				el("tr").append( | 				el("tr").append( | ||||||
| 					el("td", "Server-Side Rendering"), | 					el("td", "Server-Side Rendering"), | ||||||
| 					el("td", "✅ Basic Support"), | 					el("td", "✅ Basic Support"), | ||||||
|  | 					el("td", "✅ Basic Support"), | ||||||
| 					el("td", "✅ Advanced"), | 					el("td", "✅ Advanced"), | ||||||
| 					el("td", "✅ Advanced"), | 					el("td", "❌") | ||||||
| 					el("td", "✅ Advanced") |  | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Looking Ahead: Future Directions`), | 		el(h3, t`Looking Ahead: Future Directions`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			The dd<el> library continues to evolve, with several areas of focus for future development: | 			The dd<el> library continues to evolve, with several areas of focus for future development: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Future Compatibility:")} Alignment with the TC39 Signals proposal for native browser support | 				${el("strong", "Future Compatibility:")} Alignment with the TC39 Signals proposal for native browser support | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "SSR Improvements:")} Enhanced server-side rendering capabilities | 				${el("strong", "SSR Improvements:")} Enhanced server-side rendering capabilities | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Ecosystem Growth:")} More utilities, patterns, and integrations with other libraries | 				${el("strong", "Ecosystem Growth:")} More utilities, patterns, and integrations with other libraries | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Documentation Expansion:")} Additional examples, tutorials, and best practices | 				${el("strong", "Documentation Expansion:")} Additional examples, tutorials, and best practices | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("strong", "TypeScript Enhancements:")} Improved type definitions and inference | 				${el("strong", "TypeScript Enhancements:")} Improved type definitions and inference | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(h3, t`Contribution and Community`), | 		el(h3, t`Contribution and Community`), | ||||||
| 		el("p").append(...T` | 		el("p").append(T` | ||||||
| 			dd<el> is an open-source project that welcomes contributions from the community: | 			dd<el> is an open-source project that welcomes contributions from the community: | ||||||
| 		`), | 		`), | ||||||
| 		el("ul").append( | 		el("ul").append( | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				${el("a", references.github).append("GitHub Repository")}: Star, fork, and contribute to the project | 				${el("a", references.github).append("GitHub Repository")}: Star, fork, and contribute to the project | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				Bug reports and feature requests: Open issues on GitHub | 				Bug reports and feature requests: Open issues on GitHub | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				Documentation improvements: Help expand and clarify these guides | 				Documentation improvements: Help expand and clarify these guides | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(...T` | 			el("li").append(T` | ||||||
| 				Examples and case studies: Share your implementations and solutions | 				Examples and case studies: Share your implementations and solutions | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("div", { className: "callout" }).append( | 		el("div", { className: "callout" }).append( | ||||||
| 			el("h4", t`Final Thoughts`), | 			el("h4", t`Final Thoughts`), | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				dd<el> provides a lightweight yet powerful approach to building modern web interfaces | 				dd<el> provides a lightweight yet powerful approach to building modern web interfaces | ||||||
| 				with minimal overhead and maximal flexibility. By embracing standard web technologies | 				with minimal overhead and maximal flexibility. By embracing standard web technologies | ||||||
| 				rather than abstracting them away, it offers a development experience that scales | 				rather than abstracting them away, it offers a development experience that scales | ||||||
| 				from simple interactive elements to complex applications while remaining close | 				from simple interactive elements to complex applications while remaining close | ||||||
| 				to what makes the web platform powerful. | 				to what makes the web platform powerful. | ||||||
| 			`), | 			`), | ||||||
| 			el("p").append(...T` | 			el("p").append(T` | ||||||
| 				Whether you're building a small interactive component or a full-featured application, | 				Whether you’re building a small interactive component or a full-featured application, | ||||||
| 				dd<el>'s combination of declarative syntax, targeted reactivity, and pragmatic design | 				dd<el>’s combination of declarative syntax, targeted reactivity, and pragmatic design | ||||||
| 				provides the tools you need without the complexity you don't. | 				provides the tools you need without the complexity you don’t. | ||||||
|  | 			`) | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		el(h3, t`Tools and Resources`), | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			To help you get started with dd<el>, we provide several tools and resources: | ||||||
|  | 		`), | ||||||
|  | 		el("ul").append( | ||||||
|  | 			el("li").append(T` | ||||||
|  | 				${el("a", references.converter).append("HTML to dd<el> Converter")}: Easily convert existing HTML markup | ||||||
|  | 				to dd<el> JavaScript code | ||||||
|  | 			`), | ||||||
|  | 			el("li").append(T` | ||||||
|  | 				${el("a", references.examples).append("Examples Gallery")}: Browse real-world examples and code snippets | ||||||
|  | 			`), | ||||||
|  | 			el("li").append(T` | ||||||
|  | 				${el("a", references.github).append("Documentation")}: Comprehensive guides and API reference | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
| 	); | 	); | ||||||
|   | |||||||
							
								
								
									
										52
									
								
								docs/p14-converter.html.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								docs/p14-converter.html.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | import "./components/converter.html.js"; | ||||||
|  | import { T, t } from "./utils/index.js"; | ||||||
|  | export const info= { | ||||||
|  | 	title: t`Convert to dd<el>`, | ||||||
|  | 	fullTitle: t`HTML to dd<el> Converter`, | ||||||
|  | 	description: t`Convert your HTML markup to dd<el> JavaScript code with our interactive tool`, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | import { el } from "deka-dom-el"; | ||||||
|  | import { simplePage } from "./layout/simplePage.html.js"; | ||||||
|  | import { h3 } from "./components/pageUtils.html.js"; | ||||||
|  | import { converter } from "./components/converter.html.js"; | ||||||
|  |  | ||||||
|  | /** @param {import("./types.d.ts").PageAttrs} attrs */ | ||||||
|  | export function page({ pkg, info }){ | ||||||
|  | 	return el(simplePage, { info, pkg }).append( | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			Transitioning from HTML to dd<el> is simple with our interactive converter. This tool helps you quickly | ||||||
|  | 			transform existing HTML markup into dd<el> JavaScript code, making it easier to adopt dd<el> in your projects. | ||||||
|  | 		`), | ||||||
|  |  | ||||||
|  | 		el("div", { className: "warning" }).append( | ||||||
|  | 			el("p").append(T` | ||||||
|  | 				While the converter handles most basic HTML patterns, complex attributes or specialized elements might | ||||||
|  | 				need manual adjustments. Always review the generated code before using it in production. | ||||||
|  | 			`) | ||||||
|  | 		), | ||||||
|  |  | ||||||
|  | 		// The actual converter component | ||||||
|  | 		el(converter), | ||||||
|  |  | ||||||
|  | 		el(h3, t`Next Steps`), | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			After converting your HTML to dd<el>, you might want to: | ||||||
|  | 		`), | ||||||
|  | 		el("ul").append( | ||||||
|  | 			el("li").append(T` | ||||||
|  | 				Add signal bindings for dynamic content (see ${el("a", { href: "p04-signals.html", | ||||||
|  | 					textContent: "Signals section" })}) | ||||||
|  | 			`), | ||||||
|  | 			el("li").append(T` | ||||||
|  | 				Add event handlers for interactivity (see ${el("a", { href: "p03-events.html", | ||||||
|  | 					textContent: "Events section" })}) | ||||||
|  | 			`), | ||||||
|  | 			el("li").append(T` | ||||||
|  | 				Organize your components with components (see ${el("a", { href: | ||||||
|  | 					"p02-elements.html#h-using-components-to-build-ui-fragments", textContent: "Components section" })} | ||||||
|  | 					and ${el("a", { href: "p05-scopes.html", textContent: "Scopes section" })}) | ||||||
|  | 			`), | ||||||
|  | 		) | ||||||
|  | 	); | ||||||
|  | } | ||||||
							
								
								
									
										81
									
								
								docs/p15-examples.html.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								docs/p15-examples.html.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | |||||||
|  | import { T, t } from "./utils/index.js"; | ||||||
|  | export const info= { | ||||||
|  | 	title: t`Examples Gallery`, | ||||||
|  | 	fullTitle: t`DDE Examples & Code Snippets`, | ||||||
|  | 	description: t`A comprehensive collection of examples and code snippets for working with Deka DOM Elements.`, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | import { el } from "deka-dom-el"; | ||||||
|  | import { simplePage } from "./layout/simplePage.html.js"; | ||||||
|  | import { h3 } from "./components/pageUtils.html.js"; | ||||||
|  | import { example } from "./components/example.html.js"; | ||||||
|  | /** @param {string} url */ | ||||||
|  | const fileURL= url=> new URL(url, import.meta.url); | ||||||
|  |  | ||||||
|  | /** @param {import("./types.d.ts").PageAttrs} attrs */ | ||||||
|  | export function page({ pkg, info }){ | ||||||
|  | 	return el(simplePage, { info, pkg }).append( | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			Real-world application examples showcasing how to build complete, production-ready interfaces with dd<el>: | ||||||
|  | 		`), | ||||||
|  | 		el(h3, t`Data Dashboard`), | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			Data visualization dashboard with charts, filters, and responsive layout.  Integration with a | ||||||
|  | 			third-party charting library, data fetching and state management, responsive layout design, and multiple | ||||||
|  | 			interactive components working together. | ||||||
|  | 		`), | ||||||
|  | 		el(example, { src: fileURL("./components/examples/case-studies/data-dashboard.js"), variant: "big" }), | ||||||
|  |  | ||||||
|  | 		el(h3, t`Interactive Form`), | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			Complete form with real-time validation, conditional rendering, and responsive design. Form handling with | ||||||
|  | 			real-time validation, reactive UI updates, complex form state management, and clean separation of concerns. | ||||||
|  | 		`), | ||||||
|  | 		el(example, { src: fileURL("./components/examples/case-studies/interactive-form.js"), variant: "big" }), | ||||||
|  |  | ||||||
|  | 		el(h3, t`Interactive Image Gallery`), | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			Responsive image gallery with lightbox, keyboard navigation, and filtering. Dynamic loading of content, | ||||||
|  | 			lightbox functionality, animation handling, and keyboard and gesture navigation support. | ||||||
|  | 		`), | ||||||
|  | 		el(example, { src: fileURL("./components/examples/case-studies/image-gallery.js"), variant: "big" }), | ||||||
|  |  | ||||||
|  | 		el(h3, t`Task Manager`), | ||||||
|  | 		el("p").append(T` | ||||||
|  | 			Kanban-style task management app with drag-and-drop and localStorage persistence. Complex state management | ||||||
|  | 			with signals, drag and drop functionality, local storage persistence, and responsive design for different | ||||||
|  | 			devices. | ||||||
|  | 		`), | ||||||
|  | 		el(example, { src: fileURL("./components/examples/case-studies/task-manager.js"), variant: "big" }), | ||||||
|  |  | ||||||
|  | 		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("p").append(T` | ||||||
|  | 			Complete TodoMVC implementation with local storage and routing. TodoMVC implementation showing routing, | ||||||
|  | 			local storage persistence, filtering, and component architecture patterns. For commented code, see the | ||||||
|  | 			dedicated page ${el("a", { href: "./p10-todomvc.html" }).append(T`TodoMVC`)}. | ||||||
|  | 		`), | ||||||
|  |  | ||||||
|  | 	); | ||||||
|  | } | ||||||
| @@ -1,4 +1,8 @@ | |||||||
| 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/", | ||||||
|   | |||||||
| @@ -17,12 +17,14 @@ | |||||||
|  * |  * | ||||||
|  * @param {TemplateStringsArray} strings |  * @param {TemplateStringsArray} strings | ||||||
|  * @param {...(string|Node)} values |  * @param {...(string|Node)} values | ||||||
|  * @returns {(string|Node)[]} |  * @returns {DocumentFragment} | ||||||
|  * */ |  * */ | ||||||
| export function T(strings, ...values){ | export function T(strings, ...values){ | ||||||
| 	const out= []; | 	const out= []; | ||||||
| 	tT(s=> out.push(s), strings, ...values); | 	tT(s=> out.push(s), strings, ...values); | ||||||
| 	return out; | 	const fragment= document.createDocumentFragment(); | ||||||
|  | 	fragment.append(...out); | ||||||
|  | 	return fragment; | ||||||
| } | } | ||||||
| /** | /** | ||||||
|  * Similarly to {@link T}, but for only strings. |  * Similarly to {@link T}, but for only strings. | ||||||
|   | |||||||
| @@ -1,51 +0,0 @@ | |||||||
| import { style, el, S, isSignal } from '../exports.js'; |  | ||||||
| const className= style.host(thirdParty).css` |  | ||||||
| 	:host { |  | ||||||
| 		color: green; |  | ||||||
| 	} |  | ||||||
| `; |  | ||||||
|  |  | ||||||
| const store_adapter= { |  | ||||||
| 	read(){ return (new URL(location)).searchParams; }, |  | ||||||
| 	write(data){ console.log(data); history.replaceState("", "", "?"+(new URLSearchParams(data)).toString()); } |  | ||||||
| }; |  | ||||||
| export function thirdParty(){ |  | ||||||
| 	const store= S({ |  | ||||||
| 		value: S("initial") |  | ||||||
| 	}, { |  | ||||||
| 		set(key, value){ |  | ||||||
| 			const p=  this.value[key] || S(); |  | ||||||
| 			p.set(value); |  | ||||||
| 			this.value[key]= p; |  | ||||||
| 		} |  | ||||||
| 	}); |  | ||||||
| 	// Array.from((new URL(location)).searchParams.entries()) |  | ||||||
| 	// 	.forEach(([ key, value ])=> S.action(store, "set", key, value)); |  | ||||||
| 	// S.on(store, data=> history.replaceState("", "", |  | ||||||
| 	// "?"+(new URLSearchParams(JSON.parse(JSON.stringify(data)))).toString())); |  | ||||||
| 	useStore(store_adapter, { |  | ||||||
| 		onread(data){ |  | ||||||
| 			Array.from(data.entries()) |  | ||||||
| 				.forEach(([ key, value ])=> S.action(store, "set", key, value)); |  | ||||||
| 			return store; |  | ||||||
| 		} |  | ||||||
| 	})(); |  | ||||||
| 	return el("input", { |  | ||||||
| 		className, |  | ||||||
| 		value: store.get().value.get(), |  | ||||||
| 		type: "text", |  | ||||||
| 		onchange: ev=> S.action(store, "set", "value", ev.target.value) |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function useStore(adapter_in, { onread, onbeforewrite }= {}){ |  | ||||||
| 	const adapter= typeof adapter_in === "function" ? { read: adapter_in } : adapter_in; |  | ||||||
| 	if(!onread) onread= S; |  | ||||||
| 	if(!onbeforewrite) onbeforewrite= data=> JSON.parse(JSON.stringify(data)); |  | ||||||
| 	return function useStoreInner(data_read){ |  | ||||||
| 		const signal= onread(adapter.read(data_read)); //TODO OK as synchronous |  | ||||||
| 		if(adapter.write && isSignal(signal)) |  | ||||||
| 			S.on(signal, data=> adapter.write(onbeforewrite(data))); |  | ||||||
| 		return signal; |  | ||||||
| 	}; |  | ||||||
| } |  | ||||||
| @@ -1,42 +0,0 @@ | |||||||
| import { style, el, elNS, on, S, scope } from '../exports.js'; |  | ||||||
| const className= style.host(fullNameComponent).css` |  | ||||||
| 	:host form{ |  | ||||||
| 		display: flex; |  | ||||||
| 		flex-flow: column nowrap; |  | ||||||
| 	} |  | ||||||
| `; |  | ||||||
| export function fullNameComponent(){ |  | ||||||
| 	const labels= [ "Name", "Surname" ]; |  | ||||||
| 	const name= labels.map(_=> S("")); |  | ||||||
| 	const full_name= S(()=> |  | ||||||
| 		name.map(l=> l.get()).filter(Boolean).join(" ") || "-"); |  | ||||||
| 	scope.host( |  | ||||||
| 		on.connected(()=> console.log(fullNameComponent)), |  | ||||||
| 		on.disconnected(()=> console.log(fullNameComponent)) |  | ||||||
| 	); |  | ||||||
|  |  | ||||||
| 	const count= S(0); |  | ||||||
| 	setInterval(()=> count.set(count.get()+1), 5000); |  | ||||||
| 	const style= { height: "80px", display: "block", fill: "currentColor" }; |  | ||||||
| 	const elSVG= elNS("http://www.w3.org/2000/svg"); |  | ||||||
| 	return el("div", { className }).append( |  | ||||||
| 		el("h2", "Simple form:"), |  | ||||||
| 		el("p", { textContent: S(()=> "Count: "+count.get()), |  | ||||||
| 			dataset: { count }, classList: { count: S(() => count.get()%2 === 0) } }), |  | ||||||
| 		el("form", { onsubmit: ev=> ev.preventDefault() }).append( |  | ||||||
| 			...name.map((v, i)=> |  | ||||||
| 				el("label", labels[i]).append( |  | ||||||
| 					el("input", { type: "text", name: labels[i], value: v.get() }, on("change", ev=> v.set(ev.target.value))) |  | ||||||
| 				)) |  | ||||||
| 		), |  | ||||||
| 		el("p").append( |  | ||||||
| 			el("strong", "Full name"), |  | ||||||
| 			": ", |  | ||||||
| 			el("#text", full_name) |  | ||||||
| 		), |  | ||||||
| 		elSVG("svg", { viewBox: "0 0 240 80", style }).append( |  | ||||||
| 			//elSVG("style", {  }) |  | ||||||
| 			elSVG("text", { x: 20, y: 35, textContent: "Text" }), |  | ||||||
| 		) |  | ||||||
| 	); |  | ||||||
| } |  | ||||||
| @@ -1,93 +0,0 @@ | |||||||
| import { style, el, dispatchEvent, on, S, scope } from '../exports.js'; |  | ||||||
| const className= style.host(todosComponent).css` |  | ||||||
| 	:host{ |  | ||||||
| 		display: flex; |  | ||||||
| 		flex-flow: column nowrap; |  | ||||||
| 	} |  | ||||||
| 	:host input{ |  | ||||||
| 		margin-inline-start: .5em; |  | ||||||
| 	} |  | ||||||
| 	:host button{ |  | ||||||
| 		margin-inline-start: 1em; |  | ||||||
| 	} |  | ||||||
| 	:host output{ |  | ||||||
| 		white-space: pre; |  | ||||||
| 	} |  | ||||||
| `; |  | ||||||
| /** @param {{ todos: string[] }} */ |  | ||||||
| export function todosComponent({ todos= [ "Task A" ] }= {}){ |  | ||||||
| 	let key= 0; |  | ||||||
| 	const todosO= S( /** @type {Map<number, ddeSignal<string>>} */ (new Map()), { |  | ||||||
| 		/** @param {string} v */ |  | ||||||
| 		add(v){ this.value.set(key++, S(v)); }, |  | ||||||
| 		/** @param {number} key */ |  | ||||||
| 		remove(key){ S.clear(this.value.get(key)); this.value.delete(key); } |  | ||||||
| 	}); |  | ||||||
| 	todos.forEach(text=> S.action(todosO, "add", text)); |  | ||||||
|  |  | ||||||
| 	const name= "todoName"; |  | ||||||
| 	const onsubmitAdd= on("submit", event=> { |  | ||||||
| 		const el_form= /** @type {HTMLFormElement} */ (event.target); |  | ||||||
| 		const el= /** @type {HTMLInputElement} */ (el_form.elements[name]); |  | ||||||
| 		event.preventDefault(); |  | ||||||
| 		S.action(todosO, "add", el.value); |  | ||||||
| 		el.value= ""; |  | ||||||
| 	}); |  | ||||||
| 	const onremove= on("remove", /** @param {CustomEvent<number>} event */ |  | ||||||
| 		event=> S.action(todosO, "remove", event.detail)); |  | ||||||
|  |  | ||||||
| 	return el("div", { className }).append( |  | ||||||
| 		el("div").append( |  | ||||||
| 			el("h2", "Todos:"), |  | ||||||
| 			el("h3", "List of todos:"), |  | ||||||
| 			S.el(todosO, (ts, memo)=> !ts.size |  | ||||||
| 				? el("p", "No todos yet") |  | ||||||
| 				: el("ul").append( |  | ||||||
| 					...Array.from(ts).map(([ value, textContent ])=> |  | ||||||
| 						memo(value, ()=> el(todoComponent, { textContent, value, className }, onremove))) |  | ||||||
| 				), |  | ||||||
| 			), |  | ||||||
| 			el("p", "Click to the text to edit it.") |  | ||||||
| 		), |  | ||||||
| 		el("form", null, onsubmitAdd).append( |  | ||||||
| 			el("h3", "Add a todo:"), |  | ||||||
| 			el("label", "New todo: ").append( |  | ||||||
| 				el("input", { name, type: "text", required: true }), |  | ||||||
| 			), |  | ||||||
| 			el("button", "+"), |  | ||||||
| 		), |  | ||||||
| 		el("div").append( |  | ||||||
| 			el("h3", "Output (JSON):"), |  | ||||||
| 			el("output", S(()=> JSON.stringify(Array.from(todosO.get()), null, "\t"))) |  | ||||||
| 		) |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| /** |  | ||||||
|  * @param {{ textContent: ddeSignal<string>, value: ddeSignal<number> }} attrs |  | ||||||
|  * @dispatchs {number} remove |  | ||||||
|  * */ |  | ||||||
| function todoComponent({ textContent, value }){ |  | ||||||
| 	const { host }= scope; |  | ||||||
| 	const dispatchRemove= /** @type {(data: number) => void} */ |  | ||||||
| 		(dispatchEvent("remove", null, host)); |  | ||||||
| 	const onclick= on("click", event=> { |  | ||||||
| 		const el= /** @type {HTMLButtonElement} */ (event.target); |  | ||||||
| 		const value= Number(el.value); |  | ||||||
| 		event.preventDefault(); |  | ||||||
| 		event.stopPropagation(); |  | ||||||
| 		dispatchRemove(value); |  | ||||||
| 	}); |  | ||||||
| 	const is_editable= S(false); |  | ||||||
| 	const onedited= on("change", ev=> { |  | ||||||
| 		const el= /** @type {HTMLInputElement} */ (ev.target); |  | ||||||
| 		textContent.set(el.value); |  | ||||||
| 		is_editable.set(false); |  | ||||||
| 	}); |  | ||||||
| 	return el("li").append( |  | ||||||
| 		S.el(is_editable, is=> is |  | ||||||
| 			? el("input", { value: textContent.get(), type: "text" }, onedited) |  | ||||||
| 			: el("span", { textContent, onclick: ()=> is_editable.set(true) }) |  | ||||||
| 		), |  | ||||||
| 		el("button", { type: "button", value, textContent: "-" }, onclick) |  | ||||||
| 	); |  | ||||||
| } |  | ||||||
| @@ -1,70 +0,0 @@ | |||||||
| import { el, on, customElementRender, customElementWithDDE, scope, simulateSlots } from "../../index.js"; |  | ||||||
| import { S } from "../../signals.js"; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Compatible with `npx -y web-component-analyzer examples/components/webComponent.js` |  | ||||||
|  * @element custom-test |  | ||||||
|  * */ |  | ||||||
| export class CustomHTMLTestElement extends HTMLElement{ |  | ||||||
| 	static tagName= "custom-test"; |  | ||||||
| 	static get observedAttributes(){ |  | ||||||
| 		return [ "name", "pre-name" ]; |  | ||||||
| 	} |  | ||||||
| 	connectedCallback(){ |  | ||||||
| 		if(!this.hasAttribute("pre-name")) this.setAttribute("pre-name", "default"); |  | ||||||
| 		customElementRender(this.attachShadow({ mode: "open" }), this.render, this.attributes) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	attributes(element){ |  | ||||||
| 		const observed= S.observedAttributes(element); |  | ||||||
| 		return Object.assign({ test: element.test }, observed); |  | ||||||
| 	} |  | ||||||
| 	render({ name, preName, test }){ |  | ||||||
| 		console.log(scope.state); |  | ||||||
| 		scope.host( |  | ||||||
| 			on.connected(console.log), |  | ||||||
| 			on.attributeChanged(e=> console.log(e)), |  | ||||||
| 			on.disconnected(()=> console.log(CustomHTMLTestElement)) |  | ||||||
| 		); |  | ||||||
| 		const text= text=> el().append( |  | ||||||
| 			el("#text", text), |  | ||||||
| 			" | " |  | ||||||
| 		); |  | ||||||
| 		return el("p").append( |  | ||||||
| 			text(test), |  | ||||||
| 			text(name), |  | ||||||
| 			text(preName), |  | ||||||
| 			el("button", { type: "button", textContent: "pre-name", onclick: ()=> preName.set("Ahoj") }), |  | ||||||
| 			" | ", |  | ||||||
| 			el("slot", { className: "test", name: "test" }), |  | ||||||
| 		); |  | ||||||
| 	} |  | ||||||
| 	test= "A"; |  | ||||||
|  |  | ||||||
| 	get name(){ return this.getAttribute("name"); } |  | ||||||
| 	set name(value){ this.setAttribute("name", value); } |  | ||||||
| 	/** @attr pre-name */ |  | ||||||
| 	get preName(){ return this.getAttribute("pre-name"); } |  | ||||||
| 	set preName(value){ this.setAttribute("pre-name", value); } |  | ||||||
| } |  | ||||||
| customElementWithDDE(CustomHTMLTestElement); |  | ||||||
| customElements.define(CustomHTMLTestElement.tagName, CustomHTMLTestElement); |  | ||||||
|  |  | ||||||
| export class CustomSlottingHTMLElement extends HTMLElement{ |  | ||||||
| 	static tagName= "custom-slotting"; |  | ||||||
| 	render(){ |  | ||||||
| 		return simulateSlots(this, el().append( |  | ||||||
| 			el("p").append( |  | ||||||
| 				"Ahoj ", el("slot", { name: "name", className: "name", textContent: "World" }) |  | ||||||
| 			), |  | ||||||
| 			el("p").append( |  | ||||||
| 				"BTW ", el("slot") |  | ||||||
| 			) |  | ||||||
| 		)); |  | ||||||
| 	} |  | ||||||
| 	connectedCallback(){ |  | ||||||
| 		customElementRender(this, this.render); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| customElementWithDDE(CustomSlottingHTMLElement); |  | ||||||
| customElements.define(CustomSlottingHTMLElement.tagName, CustomSlottingHTMLElement); |  | ||||||
| @@ -1,30 +0,0 @@ | |||||||
| import * as dde_dom from "../index.js"; |  | ||||||
| export * from "../index.js"; |  | ||||||
| import * as dde_s from "../signals.js"; |  | ||||||
| export * from "../signals.js"; |  | ||||||
| Object.assign(globalThis, dde_dom, dde_s); |  | ||||||
| //import * as dde_dom from "../dist/esm-with-signals.js"; |  | ||||||
| //export * from "../dist/esm-with-signals.js"; |  | ||||||
| //Object.assign(globalThis, dde_dom); |  | ||||||
| export const style= createStyle(); |  | ||||||
|  |  | ||||||
| function createStyle(){ |  | ||||||
| 	const element= dde_dom.el("style"); |  | ||||||
| 	const store= new WeakSet(); |  | ||||||
| 	let host; |  | ||||||
| 	return { |  | ||||||
| 		element, |  | ||||||
| 		host(k, h= k.name){ |  | ||||||
| 			if(store.has(k)) return { css: ()=> {} }; |  | ||||||
| 			store.add(k); |  | ||||||
| 			host= h; |  | ||||||
| 			return this; |  | ||||||
| 		}, |  | ||||||
| 		css(...args){ |  | ||||||
| 			const textContent= String.raw(...args).replaceAll(":host", "."+host); |  | ||||||
| 			const className= host; |  | ||||||
| 			element.appendChild(el("#text", { textContent })); |  | ||||||
| 			return className; |  | ||||||
| 		} |  | ||||||
| 	}; |  | ||||||
| } |  | ||||||
| @@ -1,26 +0,0 @@ | |||||||
| <!DOCTYPE html> |  | ||||||
| <html class="no-js" lang="en"> |  | ||||||
| <head> |  | ||||||
| 	<meta charset="utf-8" /> |  | ||||||
| 	<meta name="viewport" content="width=device-width, initial-scale=1" /> |  | ||||||
| <!-- DEL CSP https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP | https://github.com/Prinzhorn/minimal-csp --> |  | ||||||
| <!-- DEL https://github.com/jensimmons/cssremedy --> |  | ||||||
| <!-- <link rel="stylesheet" href="https://github.com/jensimmons/cssremedy/raw/master/css/remedy.css"> --> |  | ||||||
|  |  | ||||||
| 	<title>Small DOM element creation enhancements</title> |  | ||||||
| 	<meta name="description" content="Making creatig elements easier"> |  | ||||||
| 	<script type="module"> |  | ||||||
| 		import { el } from "../index.js"; |  | ||||||
| 		document.body.append( |  | ||||||
| 			el("input", { type: "checkbox", name: "name", checked: true, id: undefined }), |  | ||||||
| 			el("p", { textContent: "Ahoj", style: { color: "green" } }), |  | ||||||
| 			el("select", { value: "a" }).append( |  | ||||||
| 				el("option", { value: "a", textContent: "A" }), |  | ||||||
| 				el("option", { value: "b", textContent: "B" }) |  | ||||||
| 			) |  | ||||||
| 		); |  | ||||||
| 	</script> |  | ||||||
| </head> |  | ||||||
| <body> |  | ||||||
| </body> |  | ||||||
| </html> |  | ||||||
| @@ -1,16 +0,0 @@ | |||||||
| <!DOCTYPE html> |  | ||||||
| <html class="no-js" lang="en"> |  | ||||||
| <head> |  | ||||||
| 	<meta charset="utf-8" /> |  | ||||||
| 	<meta name="viewport" content="width=device-width, initial-scale=1" /> |  | ||||||
| <!-- DEL CSP https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP | https://github.com/Prinzhorn/minimal-csp --> |  | ||||||
| <!-- DEL https://github.com/jensimmons/cssremedy --> |  | ||||||
| <!-- <link rel="stylesheet" href="https://github.com/jensimmons/cssremedy/raw/master/css/remedy.css"> --> |  | ||||||
|  |  | ||||||
| 	<title>Small DOM element creation enhancements</title> |  | ||||||
| 	<meta name="description" content="Making creatig elements easier"> |  | ||||||
| 	<script src="index.js" type="module"></script> |  | ||||||
| </head> |  | ||||||
| <body> |  | ||||||
| </body> |  | ||||||
| </html> |  | ||||||
| @@ -1,30 +0,0 @@ | |||||||
| import { style, el, S } from './exports.js'; |  | ||||||
| style.css` |  | ||||||
| :root{ |  | ||||||
| 	color-scheme: dark light; |  | ||||||
| } |  | ||||||
| `; |  | ||||||
| document.head.append(style.element); |  | ||||||
| import { fullNameComponent } from './components/fullNameComponent.js'; |  | ||||||
| import { todosComponent } from './components/todosComponent.js'; |  | ||||||
| import { CustomHTMLTestElement, CustomSlottingHTMLElement } from "./components/webComponent.js"; |  | ||||||
| import { thirdParty } from "./components/3rd-party.js"; |  | ||||||
|  |  | ||||||
| const toggle= S(false); |  | ||||||
| document.body.append( |  | ||||||
| 	el("h1", "Experiments:"), |  | ||||||
| 	el(fullNameComponent), |  | ||||||
| 	el(todosComponent), |  | ||||||
| 	el(CustomHTMLTestElement.tagName, { name: "attr" }).append( |  | ||||||
| 		el("span", { textContent: "test", slot: "test" }), |  | ||||||
| 		el("span", { textContent: "test", slot: "test" }), |  | ||||||
| 	), |  | ||||||
| 	el(thirdParty), |  | ||||||
| 	el(CustomSlottingHTMLElement.tagName, { onclick: ()=> toggle.set(!toggle.get()) }).append( |  | ||||||
| 		el("strong", { slot: "name", textContent: "Honzo" }), |  | ||||||
| 		S.el(toggle, is=> is |  | ||||||
| 			? el("span", "…default slot") |  | ||||||
| 			: el("span", "…custom slot") |  | ||||||
| 		) |  | ||||||
| 	) |  | ||||||
| ); |  | ||||||
							
								
								
									
										46
									
								
								index.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										46
									
								
								index.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -184,12 +184,12 @@ interface On{ | |||||||
| 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | 	/** Listens to the DOM event. See {@link Document.addEventListener} */ | ||||||
| 	< | 	< | ||||||
| 		Event extends keyof DocumentEventMap, | 		Event extends keyof DocumentEventMap, | ||||||
| 		EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>, | 		EL extends SupportedElement | ||||||
| 		>( | 		>( | ||||||
| 			type: Event, | 			type: Event, | ||||||
| 			listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, | 			listener: (this: EL, ev: DocumentEventMap[Event] & { target: EL }) => any, | ||||||
| 			options?: AddEventListenerOptions | 			options?: AddEventListenerOptions | ||||||
| 		) : EE; | 		) : ddeElementAddon<EL>; | ||||||
| 	< | 	< | ||||||
| 		EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>, | 		EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>, | ||||||
| 		>( | 		>( | ||||||
| @@ -199,28 +199,38 @@ interface On{ | |||||||
| 		) : EE; | 		) : EE; | ||||||
| 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line | 	/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line | ||||||
| 	connected< | 	connected< | ||||||
| 		EE extends ddeElementAddon<SupportedElement>, | 		EL extends SupportedElement | ||||||
| 		El extends ( EE extends ddeElementAddon<infer El> ? El : never ) |  | ||||||
| 		>( | 		>( | ||||||
| 			listener: (this: El, event: CustomEvent<El>) => any, | 			listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, | ||||||
| 			options?: AddEventListenerOptions | 			options?: AddEventListenerOptions | ||||||
| 		) : EE; | 		) : ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line | 	/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line | ||||||
| 	disconnected< | 	disconnected< | ||||||
| 		EE extends ddeElementAddon<SupportedElement>, | 		EL extends SupportedElement | ||||||
| 		El extends ( EE extends ddeElementAddon<infer El> ? El : never ) |  | ||||||
| 		>( | 		>( | ||||||
| 			listener: (this: El, event: CustomEvent<void>) => any, | 			listener: (this: EL, event: CustomEvent<void>) => any, | ||||||
| 			options?: AddEventListenerOptions | 			options?: AddEventListenerOptions | ||||||
| 		) : EE; | 		) : ddeElementAddon<EL>; | ||||||
| 	/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line | 	/** | ||||||
| 	attributeChanged< | 	 * Fires after the next tick of the Javascript event loop. | ||||||
| 		EE extends ddeElementAddon<SupportedElement>, | 	 * This is handy for example to apply some property depending on the element content: | ||||||
| 		El extends ( EE extends ddeElementAddon<infer El> ? El : never ) | 	 * ```js | ||||||
|  | 	 * const selected= "Z"; | ||||||
|  | 	 * //... | ||||||
|  | 	 * return el("form").append( | ||||||
|  | 	 *		el("select", null, on.defer(e=> e.value=selected)).append( | ||||||
|  | 	 *			el("option", { value: "A", textContent: "A" }), | ||||||
|  | 	 *			//... | ||||||
|  | 	 *			el("option", { value: "Z", textContent: "Z" }), | ||||||
|  | 	 *		), | ||||||
|  | 	 * ); | ||||||
|  | 	 * ``` | ||||||
|  | 	 * */ | ||||||
|  | 	defer< | ||||||
|  | 		EL extends SupportedElement | ||||||
| 	>( | 	>( | ||||||
| 			listener: (this: El, event: CustomEvent<[ string, string ]>) => any, | 		listener: (element: EL) => any, | ||||||
| 			options?: AddEventListenerOptions | 	) : ddeElementAddon<EL>; | ||||||
| 		) : EE; |  | ||||||
| } | } | ||||||
| export const on: On; | export const on: On; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								index.js
									
									
									
									
									
								
							| @@ -1,5 +1,3 @@ | |||||||
| export * from "./src/dom.js"; | export * from "./src/dom-lib/index.js"; | ||||||
| export * from "./src/customElement.js"; |  | ||||||
| export * from "./src/events.js"; |  | ||||||
| export { registerReactivity } from "./src/signals-lib/common.js"; |  | ||||||
| export { memo } from "./src/memo.js"; | export { memo } from "./src/memo.js"; | ||||||
|  | export { registerReactivity } from "./src/signals-lib/common.js"; | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								jsdom.js
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								jsdom.js
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| import { enviroment as env } from './src/dom-common.js'; | import { enviroment as env } from './src/dom-lib/common.js'; | ||||||
| env.ssr= " ssr"; | env.ssr= " ssr"; | ||||||
|  |  | ||||||
| const q_store= new Set(); | const q_store= new Set(); | ||||||
|   | |||||||
							
								
								
									
										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.1-alpha", | 	"version": "0.9.4-alpha", | ||||||
| 	"lockfileVersion": 3, | 	"lockfileVersion": 3, | ||||||
| 	"requires": true, | 	"requires": true, | ||||||
| 	"packages": { | 	"packages": { | ||||||
| 		"": { | 		"": { | ||||||
| 			"name": "deka-dom-el", | 			"name": "deka-dom-el", | ||||||
| 			"version": "0.9.1-alpha", | 			"version": "0.9.4-alpha", | ||||||
| 			"license": "MIT", | 			"license": "MIT", | ||||||
| 			"devDependencies": { | 			"devDependencies": { | ||||||
| 				"@size-limit/preset-small-lib": "~11.2", | 				"@size-limit/preset-small-lib": "~11.2", | ||||||
| @@ -15,7 +15,8 @@ | |||||||
| 				"esbuild": "~0.25", | 				"esbuild": "~0.25", | ||||||
| 				"jsdom": "~26.0", | 				"jsdom": "~26.0", | ||||||
| 				"jshint": "~2.13", | 				"jshint": "~2.13", | ||||||
| 				"nodejsscript": "^1.0.2", | 				"nodejsscript": "^1.0", | ||||||
|  | 				"publint": "^0.3", | ||||||
| 				"size-limit-node-esbuild": "~0.3" | 				"size-limit-node-esbuild": "~0.3" | ||||||
| 			}, | 			}, | ||||||
| 			"engines": { | 			"engines": { | ||||||
| @@ -614,6 +615,19 @@ | |||||||
| 				"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", | ||||||
| @@ -2229,6 +2243,16 @@ | |||||||
| 				"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", | ||||||
| @@ -2306,6 +2330,28 @@ | |||||||
| 				"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", | ||||||
| @@ -2316,6 +2362,23 @@ | |||||||
| 				"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", | ||||||
|   | |||||||
							
								
								
									
										39
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,13 +1,13 @@ | |||||||
| { | { | ||||||
| 	"name": "deka-dom-el", | 	"name": "deka-dom-el", | ||||||
| 	"version": "0.9.1-alpha", | 	"version": "0.9.5-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@github.com:jaandrle/deka-dom-el.git" | 		"url": "git+ssh://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": { | ||||||
| 		".": { | 		".": { | ||||||
| 			"import": "./index.js", | 			"types": "./index.d.ts", | ||||||
| 			"types": "./index.d.ts" | 			"import": "./index.js" | ||||||
| 		}, | 		}, | ||||||
| 		"./signals": { | 		"./signals": { | ||||||
| 			"import": "./signals.js", | 			"types": "./signals.d.ts", | ||||||
| 			"types": "./signals.d.ts" | 			"import": "./signals.js" | ||||||
| 		}, | 		}, | ||||||
| 		"./jsdom": { | 		"./jsdom": { | ||||||
| 			"import": "./jsdom.js", | 			"types": "./jsdom.d.ts", | ||||||
| 			"types": "./jsdom.d.ts" | 			"import": "./jsdom.js" | ||||||
| 		}, | 		}, | ||||||
| 		"./src/signals-lib": { | 		"./src/signals-lib": { | ||||||
| 			"import": "./src/signals-lib/signals-lib.js", | 			"types": "./src/signals-lib/signals-lib.d.ts", | ||||||
| 			"types": "./src/signals-lib/signals-lib.d.ts" | 			"import": "./src/signals-lib/signals-lib.js" | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 	"files": [ | 	"files": [ | ||||||
| @@ -52,7 +52,6 @@ | |||||||
| 		"maxdepth": 3, | 		"maxdepth": 3, | ||||||
| 		"maxcomplexity": 14, | 		"maxcomplexity": 14, | ||||||
| 		"globals": { | 		"globals": { | ||||||
| 			"requestIdleCallback": false, |  | ||||||
| 			"AbortController": false, | 			"AbortController": false, | ||||||
| 			"AbortSignal": false, | 			"AbortSignal": false, | ||||||
| 			"FinalizationRegistry": false | 			"FinalizationRegistry": false | ||||||
| @@ -61,31 +60,36 @@ | |||||||
| 	"size-limit": [ | 	"size-limit": [ | ||||||
| 		{ | 		{ | ||||||
| 			"path": "./index.js", | 			"path": "./index.js", | ||||||
| 			"limit": "10.5 kB", | 			"limit": "10 kB", | ||||||
| 			"gzip": false, | 			"gzip": false, | ||||||
| 			"brotli": false | 			"brotli": false | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"path": "./signals.js", | 			"path": "./signals.js", | ||||||
| 			"limit": "12.5 kB", | 			"limit": "12.2 kB", | ||||||
| 			"gzip": false, | 			"gzip": false, | ||||||
| 			"brotli": false | 			"brotli": false | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"path": "./index-with-signals.js", | 			"path": "./index-with-signals.js", | ||||||
| 			"limit": "15 kB", | 			"limit": "14.75 kB", | ||||||
| 			"gzip": false, | 			"gzip": false, | ||||||
| 			"brotli": false | 			"brotli": false | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"path": "./index-with-signals.js", | 			"path": "./index-with-signals.js", | ||||||
| 			"limit": "5.5 kB" | 			"limit": "5.25 kB" | ||||||
| 		} | 		} | ||||||
| 	], | 	], | ||||||
| 	"modifyEsbuildConfig": { | 	"modifyEsbuildConfig": { | ||||||
| 		"platform": "browser" | 		"platform": "browser" | ||||||
| 	}, | 	}, | ||||||
| 	"scripts": {}, | 	"scripts": { | ||||||
|  | 		"test": "echo \"Error: no tests yet\"", | ||||||
|  | 		"build": "bs/build.js", | ||||||
|  | 		"lint": "bs/lint.sh", | ||||||
|  | 		"docs": "bs/docs.js" | ||||||
|  | 	}, | ||||||
| 	"keywords": [ | 	"keywords": [ | ||||||
| 		"dom", | 		"dom", | ||||||
| 		"javascript", | 		"javascript", | ||||||
| @@ -99,7 +103,8 @@ | |||||||
| 		"esbuild": "~0.25", | 		"esbuild": "~0.25", | ||||||
| 		"jsdom": "~26.0", | 		"jsdom": "~26.0", | ||||||
| 		"jshint": "~2.13", | 		"jshint": "~2.13", | ||||||
| 		"nodejsscript": "^1.0.2", | 		"nodejsscript": "^1.0", | ||||||
|  | 		"publint": "^0.3", | ||||||
| 		"size-limit-node-esbuild": "~0.3" | 		"size-limit-node-esbuild": "~0.3" | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								signals.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								signals.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -2,7 +2,7 @@ export interface Signal<V, A> { | |||||||
| 	/** The current value of the signal */ | 	/** The current value of the signal */ | ||||||
| 	get(): V; | 	get(): V; | ||||||
| 	/** Set new value of the signal */ | 	/** Set new value of the signal */ | ||||||
| 	set(value: V): V; | 	set(value: V, force?: boolean): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ export const enviroment= { | |||||||
| 	M: globalThis.MutationObserver, | 	M: globalThis.MutationObserver, | ||||||
| 	q: p=> p || Promise.resolve(), | 	q: p=> p || Promise.resolve(), | ||||||
| }; | }; | ||||||
| import { isInstance, isUndef } from './helpers.js'; | import { isInstance, isUndef } from '../helpers.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handles attribute setting with special undefined handling |  * Handles attribute setting with special undefined handling | ||||||
| @@ -1,6 +1,47 @@ | |||||||
| import { keyLTE, evc, evd, eva } from "./dom-common.js"; | import { keyLTE, evc, evd, eva } from "./common.js"; | ||||||
| import { scope } from "./dom.js"; | import { scope } from "./scopes.js"; | ||||||
| import { c_ch_o } from "./events-observer.js"; | import { c_ch_o } from "./events-observer.js"; | ||||||
|  | import { elementAttribute } from "./helpers.js"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Simulates slot functionality for elements | ||||||
|  |  * | ||||||
|  |  * @param {HTMLElement} element - Parent element | ||||||
|  |  * @param {HTMLElement} [root=element] - Root element containing slots | ||||||
|  |  * @returns {HTMLElement} The root element | ||||||
|  |  */ | ||||||
|  | export function simulateSlots(element, root= element){ | ||||||
|  | 	const mark_e= "¹⁰", mark_s= "✓"; //NOTE: Markers to identify slots processed by this function. Also “prevents” native behavior as it is unlikely to use these in names. // editorconfig-checker-disable-line
 | ||||||
|  | 	const slots= Object.fromEntries( | ||||||
|  | 		Array.from(root.querySelectorAll("slot")) | ||||||
|  | 			.filter(s => !s.name.endsWith(mark_e)) | ||||||
|  | 			.map(s => [(s.name += mark_e), s])); | ||||||
|  | 	element.append= new Proxy(element.append, { | ||||||
|  | 		apply(orig, _, els){ | ||||||
|  | 			if(els[0]===root) return orig.apply(element, els); | ||||||
|  | 			for(const el of els){ | ||||||
|  | 				const name= (el.slot||"")+mark_e; | ||||||
|  | 				try{ elementAttribute(el, "remove", "slot"); } catch(_error){} | ||||||
|  | 				const slot= slots[name]; | ||||||
|  | 				if(!slot) return; | ||||||
|  | 				if(!slot.name.startsWith(mark_s)){ | ||||||
|  | 					slot.childNodes.forEach(c=> c.remove()); | ||||||
|  | 					slot.name= mark_s+name; | ||||||
|  | 				} | ||||||
|  | 				slot.append(el); | ||||||
|  | 				//TODO?: el.dispatchEvent(new CustomEvent("dde:slotchange", { detail: slot }));
 | ||||||
|  | 			} | ||||||
|  | 			element.append= orig; //TODO?: better memory management, but non-native behavior!
 | ||||||
|  | 			return element; | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 	if(element!==root){ | ||||||
|  | 		const els= Array.from(element.childNodes); | ||||||
|  | 		//TODO?: els.forEach(el=> el.remove());
 | ||||||
|  | 		element.append(...els); | ||||||
|  | 	} | ||||||
|  | 	return root; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Renders content into a custom element or shadow root |  * Renders content into a custom element or shadow root | ||||||
| @@ -1,7 +1,6 @@ | |||||||
| import { signals } from "./signals-lib/common.js"; | import { signals } from "../signals-lib/common.js"; | ||||||
| import { enviroment as env } from './dom-common.js'; | import { enviroment as env } from './common.js'; | ||||||
| import { isInstance, isUndef, oAssign } from "./helpers.js"; | import { isInstance, isUndef, oAssign } from "../helpers.js"; | ||||||
| import { on } from "./events.js"; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Queues a promise, this is helpful for crossplatform components (on server side we can wait for all registered |  * Queues a promise, this is helpful for crossplatform components (on server side we can wait for all registered | ||||||
| @@ -11,84 +10,6 @@ import { on } from "./events.js"; | |||||||
|  */ |  */ | ||||||
| export function queue(promise){ return env.q(promise); } | export function queue(promise){ return env.q(promise); } | ||||||
| 
 | 
 | ||||||
| /** |  | ||||||
|  * Array of scope contexts for tracking component hierarchies |  | ||||||
|  * @type {{ scope: object, prevent: boolean, host: function }[]} |  | ||||||
|  */ |  | ||||||
| const scopes= [ { |  | ||||||
| 	get scope(){ return  env.D.body; }, |  | ||||||
| 	host: c=> c ? c(env.D.body) : env.D.body, |  | ||||||
| 	prevent: true, |  | ||||||
| } ]; |  | ||||||
| /** Store for disconnect abort controllers */ |  | ||||||
| const store_abort= new WeakMap(); |  | ||||||
| /** |  | ||||||
|  * Scope management utility for tracking component hierarchies |  | ||||||
|  */ |  | ||||||
| export const scope= { |  | ||||||
| 	/** |  | ||||||
| 	 * Gets the current scope |  | ||||||
| 	 * @returns {Object} Current scope context |  | ||||||
| 	 */ |  | ||||||
| 	get current(){ return scopes[scopes.length-1]; }, |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Gets the host element of the current scope |  | ||||||
| 	 * @returns {Function} Host accessor function |  | ||||||
| 	 */ |  | ||||||
| 	get host(){ return this.current.host; }, |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Creates/gets an AbortController that triggers when the element disconnects |  | ||||||
| 	 * */ |  | ||||||
| 	get signal(){ |  | ||||||
| 		const { host }= this; |  | ||||||
| 		if(store_abort.has(host)) return store_abort.get(host); |  | ||||||
| 
 |  | ||||||
| 		const a= new AbortController(); |  | ||||||
| 		store_abort.set(host, a); |  | ||||||
| 		host(on.disconnected(()=> a.abort())); |  | ||||||
| 		return a.signal; |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Prevents default behavior in the current scope |  | ||||||
| 	 * @returns {Object} Current scope context |  | ||||||
| 	 */ |  | ||||||
| 	preventDefault(){ |  | ||||||
| 		const { current }= this; |  | ||||||
| 		current.prevent= true; |  | ||||||
| 		return current; |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Gets a copy of the current scope stack |  | ||||||
| 	 * @returns {Array} Copy of scope stack |  | ||||||
| 	 */ |  | ||||||
| 	get state(){ return [ ...scopes ]; }, |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Pushes a new scope to the stack |  | ||||||
| 	 * @param {Object} [s={}] - Scope object to push |  | ||||||
| 	 * @returns {number} New length of the scope stack |  | ||||||
| 	 */ |  | ||||||
| 	push(s= {}){ return scopes.push(oAssign({}, this.current, { prevent: false }, s)); }, |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Pushes the root scope to the stack |  | ||||||
| 	 * @returns {number} New length of the scope stack |  | ||||||
| 	 */ |  | ||||||
| 	pushRoot(){ return scopes.push(scopes[0]); }, |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Pops the current scope from the stack |  | ||||||
| 	 * @returns {Object|undefined} Popped scope or undefined if only one scope remains |  | ||||||
| 	 */ |  | ||||||
| 	pop(){ |  | ||||||
| 		if(scopes.length===1) return; |  | ||||||
| 		return scopes.pop(); |  | ||||||
| 	}, |  | ||||||
| }; |  | ||||||
| /** | /** | ||||||
|  * Chainable append function for elements |  * Chainable append function for elements | ||||||
|  * @private |  * @private | ||||||
| @@ -107,6 +28,7 @@ export function chainableAppend(el){ | |||||||
| /** Current namespace for element creation */ | /** Current namespace for element creation */ | ||||||
| let namespace; | let namespace; | ||||||
| 
 | 
 | ||||||
|  | import { scope } from "./scopes.js"; | ||||||
| /** | /** | ||||||
|  * Creates a DOM element with specified tag, attributes and addons |  * Creates a DOM element with specified tag, attributes and addons | ||||||
|  * |  * | ||||||
| @@ -116,11 +38,12 @@ let namespace; | |||||||
|  * @returns {Element|DocumentFragment} Created element |  * @returns {Element|DocumentFragment} Created element | ||||||
|  */ |  */ | ||||||
| export function createElement(tag, attributes, ...addons){ | export function createElement(tag, attributes, ...addons){ | ||||||
| 	/* jshint maxcomplexity: 15 */ | 	/* jshint maxcomplexity: 16 */ | ||||||
| 	const s= signals(this); | 	const s= signals(this); | ||||||
| 	let scoped= 0; | 	let scoped= 0; | ||||||
| 	let el, el_host; | 	let el, el_host; | ||||||
| 	if(Object(attributes)!==attributes || s.isSignal(attributes)) | 	const att_type= typeof attributes; | ||||||
|  | 	if(att_type==="string" || att_type==="number" || s.isSignal(attributes)) | ||||||
| 		attributes= { textContent: attributes }; | 		attributes= { textContent: attributes }; | ||||||
| 	switch(true){ | 	switch(true){ | ||||||
| 		case typeof tag==="function": { | 		case typeof tag==="function": { | ||||||
| @@ -128,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= tag(attributes || undefined); | 			el= /** @type {Element} */(tag(attributes || undefined)); | ||||||
| 			const is_fragment= isInstance(el, env.F); |  | ||||||
| 			if(el.nodeName==="#comment") break; | 			if(el.nodeName==="#comment") break; | ||||||
|  | 			const is_fragment= isInstance(el, env.F); | ||||||
| 			const el_mark= createElement.mark({ | 			const el_mark= createElement.mark({ | ||||||
| 				type: "component", | 				type: "component", | ||||||
| 				name: tag.name, | 				name: tag.name, | ||||||
| @@ -191,46 +114,6 @@ export function createElementNS(ns){ | |||||||
| /** Alias for createElementNS */ | /** Alias for createElementNS */ | ||||||
| export { createElementNS as elNS }; | export { createElementNS as elNS }; | ||||||
| 
 | 
 | ||||||
| /** |  | ||||||
|  * Simulates slot functionality for elements |  | ||||||
|  * |  | ||||||
|  * @param {HTMLElement} element - Parent element |  | ||||||
|  * @param {HTMLElement} [root=element] - Root element containing slots |  | ||||||
|  * @returns {HTMLElement} The root element |  | ||||||
|  */ |  | ||||||
| export function simulateSlots(element, root= element){ |  | ||||||
| 	const mark_e= "¹⁰", mark_s= "✓"; //NOTE: Markers to identify slots processed by this function. Also “prevents” native behavior as it is unlikely to use these in names. // editorconfig-checker-disable-line
 |  | ||||||
| 	const slots= Object.fromEntries( |  | ||||||
| 		Array.from(root.querySelectorAll("slot")) |  | ||||||
| 			.filter(s => !s.name.endsWith(mark_e)) |  | ||||||
| 			.map(s => [(s.name += mark_e), s])); |  | ||||||
| 	element.append= new Proxy(element.append, { |  | ||||||
| 		apply(orig, _, els){ |  | ||||||
| 			if(els[0]===root) return orig.apply(element, els); |  | ||||||
| 			for(const el of els){ |  | ||||||
| 				const name= (el.slot||"")+mark_e; |  | ||||||
| 				try{ elementAttribute(el, "remove", "slot"); } catch(_error){} |  | ||||||
| 				const slot= slots[name]; |  | ||||||
| 				if(!slot) return; |  | ||||||
| 				if(!slot.name.startsWith(mark_s)){ |  | ||||||
| 					slot.childNodes.forEach(c=> c.remove()); |  | ||||||
| 					slot.name= mark_s+name; |  | ||||||
| 				} |  | ||||||
| 				slot.append(el); |  | ||||||
| 				//TODO?: el.dispatchEvent(new CustomEvent("dde:slotchange", { detail: slot }));
 |  | ||||||
| 			} |  | ||||||
| 			element.append= orig; //TODO?: better memory management, but non-native behavior!
 |  | ||||||
| 			return element; |  | ||||||
| 		} |  | ||||||
| 	}); |  | ||||||
| 	if(element!==root){ |  | ||||||
| 		const els= Array.from(element.childNodes); |  | ||||||
| 		//TODO?: els.forEach(el=> el.remove());
 |  | ||||||
| 		element.append(...els); |  | ||||||
| 	} |  | ||||||
| 	return root; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** Store for element assignment contexts */ | /** Store for element assignment contexts */ | ||||||
| const assign_context= new WeakMap(); | const assign_context= new WeakMap(); | ||||||
| const { setDeleteAttr }= env; | const { setDeleteAttr }= env; | ||||||
| @@ -251,6 +134,7 @@ export function assign(element, ...attributes){ | |||||||
| 	assign_context.delete(element); | 	assign_context.delete(element); | ||||||
| 	return element; | 	return element; | ||||||
| } | } | ||||||
|  | import { setDelete } from "./helpers.js"; | ||||||
| /** | /** | ||||||
|  * Assigns a single attribute to an element |  * Assigns a single attribute to an element | ||||||
|  * |  * | ||||||
| @@ -290,6 +174,7 @@ export function assignAttribute(element, key, value){ | |||||||
| 	} | 	} | ||||||
| 	return isPropSetter(element, key) ? setDeleteAttr(element, key, value) : setRemoveAttr(key, value); | 	return isPropSetter(element, key) ? setDeleteAttr(element, key, value) : setRemoveAttr(key, value); | ||||||
| } | } | ||||||
|  | import { setRemove, setRemoveNS } from "./helpers.js"; | ||||||
| /** | /** | ||||||
|  * Gets or creates assignment context for an element |  * Gets or creates assignment context for an element | ||||||
|  * |  * | ||||||
| @@ -320,21 +205,6 @@ export function classListDeclarative(element, toggle){ | |||||||
| 	return element; | 	return element; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** |  | ||||||
|  * Generic element attribute manipulation |  | ||||||
|  * |  | ||||||
|  * @param {Element} element - Element to manipulate |  | ||||||
|  * @param {string} op - Operation ("set" or "remove") |  | ||||||
|  * @param {string} key - Attribute name |  | ||||||
|  * @param {any} [value] - Attribute value |  | ||||||
|  * @returns {void} |  | ||||||
|  */ |  | ||||||
| export function elementAttribute(element, op, key, value){ |  | ||||||
| 	if(isInstance(element, env.H)) |  | ||||||
| 		return element[op+"Attribute"](key, value); |  | ||||||
| 	return element[op+"AttributeNS"](null, key, value); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| //TODO: add cache? `(Map/Set)<el.tagName+key,isUndef>`
 | //TODO: add cache? `(Map/Set)<el.tagName+key,isUndef>`
 | ||||||
| /** | /** | ||||||
|  * Checks if a property can be set on an element |  * Checks if a property can be set on an element | ||||||
| @@ -385,45 +255,3 @@ function forEachEntries(s, target, element, obj, cb){ | |||||||
| 		cb(key, val); | 		cb(key, val); | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Sets or removes an attribute based on value |  | ||||||
|  * |  | ||||||
|  * @param {Element} obj - Element to modify |  | ||||||
|  * @param {string} prop - Property suffix ("Attribute") |  | ||||||
|  * @param {string} key - Attribute name |  | ||||||
|  * @param {any} val - Attribute value |  | ||||||
|  * @returns {void} |  | ||||||
|  * @private |  | ||||||
|  */ |  | ||||||
| function setRemove(obj, prop, key, val){ |  | ||||||
| 	return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, val); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Sets or removes a namespaced attribute based on value |  | ||||||
|  * |  | ||||||
|  * @param {Element} obj - Element to modify |  | ||||||
|  * @param {string} prop - Property suffix ("Attribute") |  | ||||||
|  * @param {string} key - Attribute name |  | ||||||
|  * @param {any} val - Attribute value |  | ||||||
|  * @param {string|null} [ns=null] - Namespace URI |  | ||||||
|  * @returns {void} |  | ||||||
|  * @private |  | ||||||
|  */ |  | ||||||
| function setRemoveNS(obj, prop, key, val, ns= null){ |  | ||||||
| 	return obj[ (isUndef(val) ? "remove" : "set") + prop + "NS" ](ns, key, val); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Sets or deletes a property based on value |  | ||||||
|  * |  | ||||||
|  * @param {Object} obj - Object to modify |  | ||||||
|  * @param {string} key - Property name |  | ||||||
|  * @param {any} val - Property value |  | ||||||
|  * @returns {void} |  | ||||||
|  * @private |  | ||||||
|  */ |  | ||||||
| function setDelete(obj, key, val){ |  | ||||||
| 	Reflect.set(obj, key, val); if(!isUndef(val)) return; return Reflect.deleteProperty(obj, key); |  | ||||||
| } |  | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { enviroment as env, evc, evd } from './dom-common.js'; | import { enviroment as env, evc, evd } from './common.js'; | ||||||
| import { isInstance } from "./helpers.js"; | import { isInstance, requestIdle } from "../helpers.js"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Connection changes observer for tracking element connection/disconnection |  * Connection changes observer for tracking element connection/disconnection | ||||||
| @@ -149,15 +149,6 @@ 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 | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { keyLTE, evc, evd } from './dom-common.js'; | import { keyLTE, evc, evd } from './common.js'; | ||||||
| import { oAssign, onAbort } from './helpers.js'; | import { oAssign, onAbort } from '../helpers.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Creates a function to dispatch events on elements |  * Creates a function to dispatch events on elements | ||||||
| @@ -38,6 +38,8 @@ export function on(event, listener, options){ | |||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | on.defer= fn=> setTimeout.bind(null, fn, 0); | ||||||
|  | 
 | ||||||
| import { c_ch_o } from "./events-observer.js"; | import { c_ch_o } from "./events-observer.js"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
							
								
								
									
										57
									
								
								src/dom-lib/helpers.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/dom-lib/helpers.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | import { enviroment as env } from './common.js'; | ||||||
|  | import { isInstance, isUndef } from "../helpers.js"; | ||||||
|  | /** | ||||||
|  |  * Sets or removes an attribute based on value | ||||||
|  |  * | ||||||
|  |  * @param {Element} obj - Element to modify | ||||||
|  |  * @param {string} prop - Property suffix ("Attribute") | ||||||
|  |  * @param {string} key - Attribute name | ||||||
|  |  * @param {any} val - Attribute value | ||||||
|  |  * @returns {void} | ||||||
|  |  * @private | ||||||
|  |  */ | ||||||
|  | export function setRemove(obj, prop, key, val){ | ||||||
|  | 	return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, val); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Sets or removes a namespaced attribute based on value | ||||||
|  |  * | ||||||
|  |  * @param {Element} obj - Element to modify | ||||||
|  |  * @param {string} prop - Property suffix ("Attribute") | ||||||
|  |  * @param {string} key - Attribute name | ||||||
|  |  * @param {any} val - Attribute value | ||||||
|  |  * @param {string|null} [ns=null] - Namespace URI | ||||||
|  |  * @returns {void} | ||||||
|  |  * @private | ||||||
|  |  */ | ||||||
|  | export function setRemoveNS(obj, prop, key, val, ns= null){ | ||||||
|  | 	return obj[ (isUndef(val) ? "remove" : "set") + prop + "NS" ](ns, key, val); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Sets or deletes a property based on value | ||||||
|  |  * | ||||||
|  |  * @param {Object} obj - Object to modify | ||||||
|  |  * @param {string} key - Property name | ||||||
|  |  * @param {any} val - Property value | ||||||
|  |  * @returns {void} | ||||||
|  |  * @private | ||||||
|  |  */ | ||||||
|  | export function setDelete(obj, key, val){ | ||||||
|  | 	Reflect.set(obj, key, val); if(!isUndef(val)) return; return Reflect.deleteProperty(obj, key); | ||||||
|  | } | ||||||
|  | /** | ||||||
|  |  * Generic element attribute manipulation | ||||||
|  |  * | ||||||
|  |  * @param {Element} element - Element to manipulate | ||||||
|  |  * @param {string} op - Operation ("set" or "remove") | ||||||
|  |  * @param {string} key - Attribute name | ||||||
|  |  * @param {any} [value] - Attribute value | ||||||
|  |  * @returns {void} | ||||||
|  |  */ | ||||||
|  | export function elementAttribute(element, op, key, value){ | ||||||
|  | 	if(isInstance(element, env.H)) | ||||||
|  | 		return element[op+"Attribute"](key, value); | ||||||
|  | 	return element[op+"AttributeNS"](null, key, value); | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								src/dom-lib/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/dom-lib/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | export * from "./scopes.js"; | ||||||
|  | export * from "./el.js"; | ||||||
|  | export * from "./events.js"; | ||||||
|  | export * from "./customElement.js"; | ||||||
							
								
								
									
										82
									
								
								src/dom-lib/scopes.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/dom-lib/scopes.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | |||||||
|  | import { enviroment as env } from './common.js'; | ||||||
|  | import { oAssign } from "../helpers.js"; | ||||||
|  | import { on } from "./events.js"; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Array of scope contexts for tracking component hierarchies | ||||||
|  |  * @type {{ scope: object, prevent: boolean, host: function }[]} | ||||||
|  |  */ | ||||||
|  | const scopes= [ { | ||||||
|  | 	get scope(){ return  env.D.body; }, | ||||||
|  | 	host: c=> c ? c(env.D.body) : env.D.body, | ||||||
|  | 	prevent: true, | ||||||
|  | } ]; | ||||||
|  | /** Store for disconnect abort controllers */ | ||||||
|  | const store_abort= new WeakMap(); | ||||||
|  | /** | ||||||
|  |  * Scope management utility for tracking component hierarchies | ||||||
|  |  */ | ||||||
|  | export const scope= { | ||||||
|  | 	/** | ||||||
|  | 	 * Gets the current scope | ||||||
|  | 	 * @returns {typeof scopes[number]} Current scope context | ||||||
|  | 	 */ | ||||||
|  | 	get current(){ return scopes[scopes.length-1]; }, | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Gets the host element of the current scope | ||||||
|  | 	 * @returns {Function} Host accessor function | ||||||
|  | 	 */ | ||||||
|  | 	get host(){ return this.current.host; }, | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Creates/gets an AbortController that triggers when the element disconnects | ||||||
|  | 	 * */ | ||||||
|  | 	get signal(){ | ||||||
|  | 		const { host }= this; | ||||||
|  | 		if(store_abort.has(host)) return store_abort.get(host); | ||||||
|  |  | ||||||
|  | 		const a= new AbortController(); | ||||||
|  | 		store_abort.set(host, a); | ||||||
|  | 		host(on.disconnected(()=> a.abort())); | ||||||
|  | 		return a.signal; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Prevents default behavior in the current scope | ||||||
|  | 	 * @returns {Object} Current scope context | ||||||
|  | 	 */ | ||||||
|  | 	preventDefault(){ | ||||||
|  | 		const { current }= this; | ||||||
|  | 		current.prevent= true; | ||||||
|  | 		return current; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Gets a copy of the current scope stack | ||||||
|  | 	 * @returns {Array} Copy of scope stack | ||||||
|  | 	 */ | ||||||
|  | 	get state(){ return [ ...scopes ]; }, | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Pushes a new scope to the stack | ||||||
|  | 	 * @param {Object} [s={}] - Scope object to push | ||||||
|  | 	 * @returns {number} New length of the scope stack | ||||||
|  | 	 */ | ||||||
|  | 	push(s= {}){ return scopes.push(oAssign({}, this.current, { prevent: false }, s)); }, | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Pushes the root scope to the stack | ||||||
|  | 	 * @returns {number} New length of the scope stack | ||||||
|  | 	 */ | ||||||
|  | 	pushRoot(){ return scopes.push(scopes[0]); }, | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Pops the current scope from the stack | ||||||
|  | 	 * @returns {Object|undefined} Popped scope or undefined if only one scope remains | ||||||
|  | 	 */ | ||||||
|  | 	pop(){ | ||||||
|  | 		if(scopes.length===1) return; | ||||||
|  | 		return scopes.pop(); | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user