mirror of
				https://github.com/jaandrle/deka-dom-el
				synced 2025-10-31 21:59:15 +01:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
			v0.9.3-alp
			...
			93b905e677
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 93b905e677 | |||
| 2fdd8ebea9 | |||
| 7cee9a4a14 | |||
| 3c6cad5648 | |||
| 9251e70015 | |||
| 8756dabc55 | |||
| 0a2d17ac6f | 
							
								
								
									
										40
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							| @@ -1,40 +0,0 @@ | |||||||
| --- |  | ||||||
| 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
									
									
								
							
							
						
						
									
										22
									
								
								.github/ISSUE_TEMPLATE/documentation.md
									
									
									
									
										vendored
									
									
								
							| @@ -1,22 +0,0 @@ | |||||||
| --- |  | ||||||
| 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
									
									
								
							
							
						
						
									
										29
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
								
							| @@ -1,29 +0,0 @@ | |||||||
| --- |  | ||||||
| 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
									
									
								
							
							
						
						
									
										39
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
								
							| @@ -1,39 +0,0 @@ | |||||||
| <!--  |  | ||||||
| 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
									
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/npm-publish.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,18 +0,0 @@ | |||||||
| name: Publish Package to npmjs |  | ||||||
| on: |  | ||||||
|   workflow_dispatch: |  | ||||||
|   release: |  | ||||||
|     types: [created] |  | ||||||
| jobs: |  | ||||||
|   build: |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 |  | ||||||
|       - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 |  | ||||||
|         with: |  | ||||||
|           node-version: '20.16' |  | ||||||
|           registry-url: 'https://registry.npmjs.org' |  | ||||||
|       - run: npm ci |  | ||||||
|       - run: npm publish |  | ||||||
|         env: |  | ||||||
|           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} |  | ||||||
							
								
								
									
										3
									
								
								.npmrc
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								.npmrc
									
									
									
									
									
								
							| @@ -1,3 +0,0 @@ | |||||||
| //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} |  | ||||||
| registry=https://registry.npmjs.org/ |  | ||||||
| always-auth=true |  | ||||||
| @@ -1,134 +0,0 @@ | |||||||
|  |  | ||||||
| # Contributor Covenant Code of Conduct |  | ||||||
|  |  | ||||||
| ## Our Pledge |  | ||||||
|  |  | ||||||
| We as members, contributors, and leaders pledge to make participation in our |  | ||||||
| community a harassment-free experience for everyone, regardless of age, body |  | ||||||
| size, visible or invisible disability, ethnicity, sex characteristics, gender |  | ||||||
| identity and expression, level of experience, education, socio-economic status, |  | ||||||
| nationality, personal appearance, race, caste, color, religion, or sexual |  | ||||||
| identity and orientation. |  | ||||||
|  |  | ||||||
| We pledge to act and interact in ways that contribute to an open, welcoming, |  | ||||||
| diverse, inclusive, and healthy community. |  | ||||||
|  |  | ||||||
| ## Our Standards |  | ||||||
|  |  | ||||||
| Examples of behavior that contributes to a positive environment for our |  | ||||||
| community include: |  | ||||||
|  |  | ||||||
| * Demonstrating empathy and kindness toward other people |  | ||||||
| * Being respectful of differing opinions, viewpoints, and experiences |  | ||||||
| * Giving and gracefully accepting constructive feedback |  | ||||||
| * Accepting responsibility and apologizing to those affected by our mistakes, |  | ||||||
| 	and learning from the experience |  | ||||||
| * Focusing on what is best not just for us as individuals, but for the overall |  | ||||||
| 	community |  | ||||||
|  |  | ||||||
| Examples of unacceptable behavior include: |  | ||||||
|  |  | ||||||
| * The use of sexualized language or imagery, and sexual attention or advances of |  | ||||||
| 	any kind |  | ||||||
| * Trolling, insulting or derogatory comments, and personal or political attacks |  | ||||||
| * Public or private harassment |  | ||||||
| * Publishing others' private information, such as a physical or email address, |  | ||||||
| 	without their explicit permission |  | ||||||
| * Other conduct which could reasonably be considered inappropriate in a |  | ||||||
| 	professional setting |  | ||||||
|  |  | ||||||
| ## Enforcement Responsibilities |  | ||||||
|  |  | ||||||
| Community leaders are responsible for clarifying and enforcing our standards of |  | ||||||
| acceptable behavior and will take appropriate and fair corrective action in |  | ||||||
| response to any behavior that they deem inappropriate, threatening, offensive, |  | ||||||
| or harmful. |  | ||||||
|  |  | ||||||
| Community leaders have the right and responsibility to remove, edit, or reject |  | ||||||
| comments, commits, code, wiki edits, issues, and other contributions that are |  | ||||||
| not aligned to this Code of Conduct, and will communicate reasons for moderation |  | ||||||
| decisions when appropriate. |  | ||||||
|  |  | ||||||
| ## Scope |  | ||||||
|  |  | ||||||
| This Code of Conduct applies within all community spaces, and also applies when |  | ||||||
| an individual is officially representing the community in public spaces. |  | ||||||
| Examples of representing our community include using an official e-mail address, |  | ||||||
| posting via an official social media account, or acting as an appointed |  | ||||||
| representative at an online or offline event. |  | ||||||
|  |  | ||||||
| ## Enforcement |  | ||||||
|  |  | ||||||
| Instances of abusive, harassing, or otherwise unacceptable behavior may be |  | ||||||
| reported to the community leaders responsible for enforcement at |  | ||||||
| andrle.jan@centrum.cz. |  | ||||||
| All complaints will be reviewed and investigated promptly and fairly. |  | ||||||
|  |  | ||||||
| All community leaders are obligated to respect the privacy and security of the |  | ||||||
| reporter of any incident. |  | ||||||
|  |  | ||||||
| ## Enforcement Guidelines |  | ||||||
|  |  | ||||||
| Community leaders will follow these Community Impact Guidelines in determining |  | ||||||
| the consequences for any action they deem in violation of this Code of Conduct: |  | ||||||
|  |  | ||||||
| ### 1. Correction |  | ||||||
|  |  | ||||||
| **Community Impact**: Use of inappropriate language or other behavior deemed |  | ||||||
| unprofessional or unwelcome in the community. |  | ||||||
|  |  | ||||||
| **Consequence**: A private, written warning from community leaders, providing |  | ||||||
| clarity around the nature of the violation and an explanation of why the |  | ||||||
| behavior was inappropriate. A public apology may be requested. |  | ||||||
|  |  | ||||||
| ### 2. Warning |  | ||||||
|  |  | ||||||
| **Community Impact**: A violation through a single incident or series of |  | ||||||
| actions. |  | ||||||
|  |  | ||||||
| **Consequence**: A warning with consequences for continued behavior. No |  | ||||||
| interaction with the people involved, including unsolicited interaction with |  | ||||||
| those enforcing the Code of Conduct, for a specified period of time. This |  | ||||||
| includes avoiding interactions in community spaces as well as external channels |  | ||||||
| like social media. Violating these terms may lead to a temporary or permanent |  | ||||||
| ban. |  | ||||||
|  |  | ||||||
| ### 3. Temporary Ban |  | ||||||
|  |  | ||||||
| **Community Impact**: A serious violation of community standards, including |  | ||||||
| sustained inappropriate behavior. |  | ||||||
|  |  | ||||||
| **Consequence**: A temporary ban from any sort of interaction or public |  | ||||||
| communication with the community for a specified period of time. No public or |  | ||||||
| private interaction with the people involved, including unsolicited interaction |  | ||||||
| with those enforcing the Code of Conduct, is allowed during this period. |  | ||||||
| Violating these terms may lead to a permanent ban. |  | ||||||
|  |  | ||||||
| ### 4. Permanent Ban |  | ||||||
|  |  | ||||||
| **Community Impact**: Demonstrating a pattern of violation of community |  | ||||||
| standards, including sustained inappropriate behavior, harassment of an |  | ||||||
| individual, or aggression toward or disparagement of classes of individuals. |  | ||||||
|  |  | ||||||
| **Consequence**: A permanent ban from any sort of public interaction within the |  | ||||||
| community. |  | ||||||
|  |  | ||||||
| ## Attribution |  | ||||||
|  |  | ||||||
| This Code of Conduct is adapted from the [Contributor Covenant][homepage], |  | ||||||
| version 2.1, available at |  | ||||||
| [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. |  | ||||||
|  |  | ||||||
| Community Impact Guidelines were inspired by |  | ||||||
| [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. |  | ||||||
|  |  | ||||||
| For answers to common questions about this code of conduct, see the FAQ at |  | ||||||
| [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at |  | ||||||
| [https://www.contributor-covenant.org/translations][translations]. |  | ||||||
|  |  | ||||||
| [homepage]: https://www.contributor-covenant.org |  | ||||||
| [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html |  | ||||||
| [Mozilla CoC]: https://github.com/mozilla/diversity |  | ||||||
| [FAQ]: https://www.contributor-covenant.org/faq |  | ||||||
| [translations]: https://www.contributor-covenant.org/translations |  | ||||||
|  |  | ||||||
							
								
								
									
										174
									
								
								CONTRIBUTING.md
									
									
									
									
									
								
							
							
						
						
									
										174
									
								
								CONTRIBUTING.md
									
									
									
									
									
								
							| @@ -1,174 +0,0 @@ | |||||||
| # 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. |  | ||||||
|  |  | ||||||
| ## Commit Guidelines |  | ||||||
|  |  | ||||||
| We use |  | ||||||
| [](https://robinpokorny.github.io/git3moji/) <!-- editorconfig-checker-disable-line --> |  | ||||||
| for commit messages. This helps keep the commit history clear and consistent. |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| :emoji: Short summary of the change |  | ||||||
| ``` |  | ||||||
| …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. |  | ||||||
							
								
								
									
										101
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,7 +1,6 @@ | |||||||
| **Alpha** | **WIP** (the experimentation phase) | ||||||
| | [source code on GitHub](https://github.com/jaandrle/deka-dom-el) | | [source code on GitHub](https://github.com/jaandrle/deka-dom-el) | ||||||
| | [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el) | | [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el) | ||||||
| | [](https://robinpokorny.github.io/git3moji/) <!-- editorconfig-checker-disable-line --> |  | ||||||
|  |  | ||||||
| <p align="center"> | <p align="center"> | ||||||
| 	<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180"> | 	<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180"> | ||||||
| @@ -32,7 +31,7 @@ function EmojiCounter({ initial }) { | |||||||
| 		el("p", { | 		el("p", { | ||||||
| 			className: "output", | 			className: "output", | ||||||
| 			textContent: S(() => | 			textContent: S(() => | ||||||
| 				`Hello World ${emoji.get().repeat(count.get())}`), | 				`Hello World ${emoji.get().repeat(clicks.get())}`), | ||||||
| 		}), | 		}), | ||||||
|  |  | ||||||
| 		// 🎮 Controls - Update state on events | 		// 🎮 Controls - Update state on events | ||||||
| @@ -40,12 +39,12 @@ function EmojiCounter({ initial }) { | |||||||
| 			on("click", () => count.set(count.get() + 1)) | 			on("click", () => count.set(count.get() + 1)) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("select", null, on.defer(el=> el.value= initial), | 		el("select", null, | ||||||
| 			on("change", e => emoji.set(e.target.value)) | 			on("change", e => emoji.set(e.target.value)) | ||||||
| 		).append( | 		).append( | ||||||
| 			el(Option, "🎉"), | 			el(Option, "🎉", isSelected), | ||||||
| 			el(Option, "🚀"), | 			el(Option, "🚀", isSelected), | ||||||
| 			el(Option, "💖"), | 			el(Option, "💖", isSelected), | ||||||
| 		) | 		) | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
| @@ -61,48 +60,15 @@ 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 | - ☑️ **Lightweight** — ~10-15kB minified (original goal 10kB) with zero/minimal dependencies | ||||||
| - ✅ **Declarative & functional approach** for clean, maintainable code | - ✅ **Declarative & functional approach** for clean, maintainable code | ||||||
| - ✅ **Signals and events** for reactive UI | - ✅ **Signals and events** for reactive UI | ||||||
| - ✅ **Memoization for performance** — optimize rendering with intelligent caching | - ✅ **Memoization for performance** — optimize rendering with intelligent caching | ||||||
| - ✅ **Optional build-in signals** with support for custom reactive implementations (#39) | - ✅ **Optional build-in signals** with support for custom reactive implementations | ||||||
| - ✅ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom) | - ✅ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom) | ||||||
| - ✅ **TypeScript support** | - ✅ **TypeScript support** (work in progress) | ||||||
| - ☑️ **Support for debugging with browser DevTools** without extensions | - ☑️ **Support for debugging with browser DevTools** without extensions | ||||||
| - ☑️ **Enhanced Web Components** support | - ☑️ **Enhanced Web Components** support (work in progress) | ||||||
|  |  | ||||||
| ## Getting Started |  | ||||||
|  |  | ||||||
| ### Documentation |  | ||||||
|  |  | ||||||
| - [**Documentation and Guide**](https://jaandrle.github.io/deka-dom-el) |  | ||||||
| - [**Examples**](https://jaandrle.github.io/deka-dom-el/p15-examples.html) |  | ||||||
|  |  | ||||||
| ### 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/) 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? | ||||||
|  |  | ||||||
| @@ -117,6 +83,29 @@ 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: | ||||||
| @@ -126,24 +115,12 @@ 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 | ||||||
| - [AseasRoa/paintor](https://github.com/AseasRoa/paintor) - JavaScript library for building reactive client-side user | - [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) - | ||||||
| 	interfaces or HTML code. |  | ||||||
| - [pota](https://pota.quack.uy/) — small and pluggable Reactive Web Renderer. It's compiler-less, includes an html |  | ||||||
| 	function, and a optimized babel preset in case you fancy JSX. |  | ||||||
| - [jaandrle/dollar_dom_component](https://github.com/jaandrle/dollar_dom_component) — |  | ||||||
| 	Functional DOM components without JSX/virtual DOM | 	Functional DOM components without JSX/virtual DOM | ||||||
| - [TarekRaafat/eleva](https://github.com/TarekRaafat/eleva) — A minimalist, lightweight, pure vanilla JavaScript | - [mxjp/rvx: A signal based frontend framework](https://github.com/mxjp/rvx) | ||||||
| 	frontend runtime framework. |  | ||||||
| - [didi/mpx](https://github.com/didi/mpx) — Mpx,一款具有优秀开发体验和深度性能优化的增强型跨端小程序框架 |  | ||||||
| - [mxjp/rvx](https://github.com/mxjp/rvx) — A signal based frontend framework |  | ||||||
|   | |||||||
| @@ -2,18 +2,15 @@ | |||||||
| 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 [main|signals] [--no-types|--help] | #### bs/build.js [--minify|--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` |  | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| #!/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; } | ||||||
| `; | `; | ||||||
| @@ -9,7 +8,8 @@ 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); | ||||||
| 		esbuild({ file, out, minify }); | 		const esbuild_output= buildEsbuild({ 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,13 +31,14 @@ 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", | ||||||
| 			globalName: name | 			"--global-name="+name, | ||||||
| 		}; | 		]; | ||||||
| 		esbuild({ file, out, minify, params }); | 		const dde_output= buildEsbuild({ file, out, minify, params }); | ||||||
|  | 		echoVariant(`${out} (${name})`) | ||||||
|  |  | ||||||
| 		if(!types) return; | 		if(!types) return dde_output; | ||||||
| 		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); | ||||||
| @@ -47,6 +48,8 @@ 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 }){ | ||||||
| @@ -61,39 +64,46 @@ 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; | ||||||
| } | } | ||||||
| export function esbuild({ file, out, minify= "partial", params= {} }){ | class ErrorEsbuild extends Error{ | ||||||
| 	const esbuild_output= esbuildSync({ | 	constructor({ code, stderr }){ | ||||||
| 		entryPoints: [file], | 		super(stderr); | ||||||
| 		outfile: out, | 		this.code= code; | ||||||
| 		platform: "neutral", | 		this.stderr= stderr; | ||||||
| 		bundle: true, | 	} | ||||||
| 		legalComments: "inline", | } | ||||||
| 		packages: "external", | function buildEsbuild({ file, out, minify= "partial", params= [] }){ | ||||||
| 		metafile: true, | 	try { | ||||||
| 		...minifyOption(minify), | 		return esbuild({ file, out, minify, params }); | ||||||
| 		...params | 	} catch(e){ | ||||||
| 	}); | 		if(e instanceof ErrorEsbuild) | ||||||
|  | 			return $.exit(e.code, echo(e.stderr)); | ||||||
|  | 		throw e; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | export function esbuild({ file, out, minify= "partial", params= [] }){ | ||||||
|  | 	const esbuild_output= s.$().run([ | ||||||
|  | 		"npx esbuild '::file::'", | ||||||
|  | 		"--platform=neutral", | ||||||
|  | 		"--bundle", | ||||||
|  | 		minifyOption(minify), | ||||||
|  | 		"--legal-comments=inline", | ||||||
|  | 		"--packages=external", | ||||||
|  | 		...params, | ||||||
|  | 		"--outfile='::out::'" | ||||||
|  | 	].filter(Boolean).join(" "), { file, out }); | ||||||
|  | 	if(esbuild_output.code) | ||||||
|  | 		throw new ErrorEsbuild(esbuild_output); | ||||||
| 	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 { minify: false }; | 	if("no"===level) return undefined; | ||||||
| 	if("full"===level) return { minify: true }; | 	if("full"===level) return "--minify"; | ||||||
| 	return { minifySyntax: true, minifyIdentifiers: true }; | 	return "--minify-syntax --minify-identifiers"; | ||||||
| } |  | ||||||
| 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); | ||||||
|   | |||||||
							
								
								
									
										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, force?: boolean): V; | 	set(value: V): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
| @@ -173,30 +173,17 @@ 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, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & { | 	<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; | ||||||
| 		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<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | 	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 | 	/** 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<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | 	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 | ||||||
| 	 * Fires after the next tick of the Javascript event loop. | 	attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[ | ||||||
| 	 * This is handy for example to apply some property depending on the element content: | 		string, | ||||||
| 	 * ```js | 		string | ||||||
| 	 * const selected= "Z"; | 	]>) => any, options?: AddEventListenerOptions): EE; | ||||||
| 	 * //... |  | ||||||
| 	 * 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 = { | ||||||
|   | |||||||
							
								
								
									
										246
									
								
								dist/esm-with-signals.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										246
									
								
								dist/esm-with-signals.js
									
									
									
									
										vendored
									
									
								
							| @@ -41,8 +41,52 @@ 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 { | ||||||
|  | 	constructor() { | ||||||
|  | 		super(); | ||||||
|  | 		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/dom-lib/common.js | // 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 | ||||||
| var enviroment = { | var enviroment = { | ||||||
| 	setDeleteAttr, | 	setDeleteAttr, | ||||||
| 	ssr: "", | 	ssr: "", | ||||||
| @@ -68,7 +112,7 @@ var evc = "dde:connected"; | |||||||
| var evd = "dde:disconnected"; | var evd = "dde:disconnected"; | ||||||
| var eva = "dde:attributeChanged"; | var eva = "dde:attributeChanged"; | ||||||
|  |  | ||||||
| // src/dom-lib/events-observer.js | // src/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 () => { | ||||||
| @@ -232,7 +276,7 @@ function connectionsChangesObserverConstructor() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // src/dom-lib/events.js | // src/events.js | ||||||
| function dispatchEvent(name, options, host) { | function dispatchEvent(name, options, host) { | ||||||
| 	if (typeof options === "function") { | 	if (typeof options === "function") { | ||||||
| 		host = options; | 		host = options; | ||||||
| @@ -254,7 +298,6 @@ 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); | ||||||
| @@ -278,7 +321,10 @@ on.disconnected = function(listener, options) { | |||||||
| 	}; | 	}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // src/dom-lib/scopes.js | // src/dom.js | ||||||
|  | function queue(promise) { | ||||||
|  | 	return enviroment.q(promise); | ||||||
|  | } | ||||||
| var scopes = [{ | var scopes = [{ | ||||||
| 	get scope() { | 	get scope() { | ||||||
| 		return enviroment.D.body; | 		return enviroment.D.body; | ||||||
| @@ -290,7 +336,7 @@ var store_abort = /* @__PURE__ */ new WeakMap(); | |||||||
| var scope = { | var scope = { | ||||||
| 	/** | 	/** | ||||||
| 	* Gets the current scope | 	* Gets the current scope | ||||||
| 	* @returns {typeof scopes[number]} Current scope context | 	* @returns {Object} Current scope context | ||||||
| 	*/ | 	*/ | ||||||
| 	get current() { | 	get current() { | ||||||
| 		return scopes[scopes.length - 1]; | 		return scopes[scopes.length - 1]; | ||||||
| @@ -353,60 +399,6 @@ 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; | ||||||
| @@ -422,8 +414,7 @@ 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; | ||||||
| 	const att_type = typeof attributes; | 	if (Object(attributes) !== attributes || s.isSignal(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": { | ||||||
| @@ -477,6 +468,38 @@ 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) { | ||||||
| @@ -539,6 +562,11 @@ 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); | ||||||
| @@ -562,40 +590,19 @@ function forEachEntries(s, target, element, obj, cb) { | |||||||
| 		cb(key, val); | 		cb(key, val); | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  | function setRemove(obj, prop, key, val) { | ||||||
| // src/dom-lib/customElement.js | 	return obj[(isUndef(val) ? "remove" : "set") + prop](key, val); | ||||||
| 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 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 | ||||||
| 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({ | ||||||
| @@ -650,9 +657,9 @@ function memo(key, generator) { | |||||||
| memo.isScope = function(obj) { | memo.isScope = function(obj) { | ||||||
| 	return obj[memoMark]; | 	return obj[memoMark]; | ||||||
| }; | }; | ||||||
| memo.scope = function memoScopeCreate(fun, { signal: signal2, onlyLast } = {}) { | memo.scope = function memoScope(fun, { signal: signal2, onlyLast } = {}) { | ||||||
| 	let cache = oCreate(); | 	let cache = oCreate(); | ||||||
| 	function memoScope(...args) { | 	function memoScope2(...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(); | ||||||
| @@ -667,28 +674,28 @@ memo.scope = function memoScopeCreate(fun, { signal: signal2, onlyLast } = {}) { | |||||||
| 		cache = cache_local; | 		cache = cache_local; | ||||||
| 		return out; | 		return out; | ||||||
| 	} | 	} | ||||||
| 	memoScope[memoMark] = true; | 	memoScope2[memoMark] = true; | ||||||
| 	memoScope.clear = () => cache = oCreate(); | 	memoScope2.clear = () => cache = oCreate(); | ||||||
| 	if (signal2) signal2.addEventListener("abort", memoScope.clear); | 	if (signal2) signal2.addEventListener("abort", memoScope2.clear); | ||||||
| 	return memoScope; | 	return memoScope2; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // 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 Map(); | 	let pendingSignals = /* @__PURE__ */ new Set(); | ||||||
| 	let scheduled = false; | 	let scheduled = false; | ||||||
| 	function flushSignals() { | 	function flushSignals() { | ||||||
| 		scheduled = false; | 		scheduled = false; | ||||||
| 		const todo = pendingSignals; | 		const todo = pendingSignals; | ||||||
| 		pendingSignals = /* @__PURE__ */ new Map(); | 		pendingSignals = /* @__PURE__ */ new Set(); | ||||||
| 		for (const [signal2, force] of todo) { | 		for (const signal2 of todo) { | ||||||
| 			const M = signal2[mark]; | 			const M = signal2[mark]; | ||||||
| 			if (M) M.listeners.forEach((l) => l(M.value, force)); | 			if (M) M.listeners.forEach((l) => l(M.value)); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return function(s, force = false) { | 	return function(s) { | ||||||
| 		pendingSignals.set(s, pendingSignals.get(s) || force); | 		pendingSignals.add(s); | ||||||
| 		if (scheduled) return; | 		if (scheduled) return; | ||||||
| 		scheduled = true; | 		scheduled = true; | ||||||
| 		queueMicrotask(flushSignals); | 		queueMicrotask(flushSignals); | ||||||
| @@ -725,11 +732,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(_, force) { | 	function contextReWatch() { | ||||||
| 		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(), force); | 		write(out, value()); | ||||||
| 		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); | ||||||
| @@ -751,7 +758,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, true); | 	queueSignalWrite(s); | ||||||
| }; | }; | ||||||
| signal.on = function on2(s, listener, options = {}) { | signal.on = function on2(s, listener, options = {}) { | ||||||
| 	const { signal: as } = options; | 	const { signal: as } = options; | ||||||
| @@ -787,17 +794,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) { | ||||||
| 	const mapScoped = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | 	map = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | ||||||
| 	const { current } = scope, { scope: sc } = current; | 	const mark_start = createElement.mark({ type: "reactive", source: new Defined().compact }, true); | ||||||
| 	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 = mapScoped(v); | 		let els = map(v); | ||||||
| 		scope.pop(); | 		scope.pop(); | ||||||
| 		if (!Array.isArray(els)) | 		if (!Array.isArray(els)) | ||||||
| 			els = [els]; | 			els = [els]; | ||||||
| @@ -817,7 +824,7 @@ 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` */ | ||||||
| 			mapScoped.clear() | 			map.clear() | ||||||
| 		) | 		) | ||||||
| 	)); | 	)); | ||||||
| 	return out; | 	return out; | ||||||
| @@ -889,8 +896,9 @@ var signals_config = { | |||||||
| function removeSignalsFromElements(s, listener, ...notes) { | function removeSignalsFromElements(s, listener, ...notes) { | ||||||
| 	const { current } = scope; | 	const { current } = scope; | ||||||
| 	current.host(function(element) { | 	current.host(function(element) { | ||||||
| 		if (!element[key_reactive]) element[key_reactive] = []; | 		if (element[key_reactive]) | ||||||
| 		element[key_reactive].push([[s, listener], ...notes]); | 			return element[key_reactive].push([[s, listener], ...notes]); | ||||||
|  | 		element[key_reactive] = []; | ||||||
| 		if (current.prevent) return; | 		if (current.prevent) return; | ||||||
| 		on.disconnected( | 		on.disconnected( | ||||||
| 			() => ( | 			() => ( | ||||||
| @@ -935,6 +943,7 @@ function toSignal(s, value, actions, readonly = false) { | |||||||
| 			onclear, | 			onclear, | ||||||
| 			host, | 			host, | ||||||
| 			listeners: /* @__PURE__ */ new Set(), | 			listeners: /* @__PURE__ */ new Set(), | ||||||
|  | 			defined: new Defined().stack, | ||||||
| 			readonly | 			readonly | ||||||
| 		}), | 		}), | ||||||
| 		enumerable: false, | 		enumerable: false, | ||||||
| @@ -958,7 +967,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, force); | 	queueSignalWrite(s); | ||||||
| 	return value; | 	return value; | ||||||
| } | } | ||||||
| function addSignalListener(s, listener) { | function addSignalListener(s, listener) { | ||||||
| @@ -995,6 +1004,7 @@ 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, force?: boolean): V; | 	set(value: V): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
| @@ -173,30 +173,17 @@ 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, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & { | 	<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; | ||||||
| 		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<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | 	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 | 	/** 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<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | 	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 | ||||||
| 	 * Fires after the next tick of the Javascript event loop. | 	attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[ | ||||||
| 	 * This is handy for example to apply some property depending on the element content: | 		string, | ||||||
| 	 * ```js | 		string | ||||||
| 	 * const selected= "Z"; | 	]>) => any, options?: AddEventListenerOptions): EE; | ||||||
| 	 * //... |  | ||||||
| 	 * 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
											
										
									
								
							
							
								
								
									
										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, force?: boolean): V; | 	set(value: V): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
| @@ -172,30 +172,17 @@ 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, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & { | 	<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; | ||||||
| 		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<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | 	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 | 	/** 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<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | 	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 | ||||||
| 	 * Fires after the next tick of the Javascript event loop. | 	attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[ | ||||||
| 	 * This is handy for example to apply some property depending on the element content: | 		string, | ||||||
| 	 * ```js | 		string | ||||||
| 	 * const selected= "Z"; | 	]>) => any, options?: AddEventListenerOptions): EE; | ||||||
| 	 * //... |  | ||||||
| 	 * 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 = { | ||||||
|   | |||||||
							
								
								
									
										197
									
								
								dist/esm.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										197
									
								
								dist/esm.js
									
									
									
									
										vendored
									
									
								
							| @@ -26,7 +26,38 @@ function onAbort(signal, listener) { | |||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
|  |  | ||||||
| // src/dom-lib/common.js | // 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 | ||||||
| var enviroment = { | var enviroment = { | ||||||
| 	setDeleteAttr, | 	setDeleteAttr, | ||||||
| 	ssr: "", | 	ssr: "", | ||||||
| @@ -52,7 +83,7 @@ var evc = "dde:connected"; | |||||||
| var evd = "dde:disconnected"; | var evd = "dde:disconnected"; | ||||||
| var eva = "dde:attributeChanged"; | var eva = "dde:attributeChanged"; | ||||||
|  |  | ||||||
| // src/dom-lib/events-observer.js | // src/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 () => { | ||||||
| @@ -216,7 +247,7 @@ function connectionsChangesObserverConstructor() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // src/dom-lib/events.js | // src/events.js | ||||||
| function dispatchEvent(name, options, host) { | function dispatchEvent(name, options, host) { | ||||||
| 	if (typeof options === "function") { | 	if (typeof options === "function") { | ||||||
| 		host = options; | 		host = options; | ||||||
| @@ -238,7 +269,6 @@ 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); | ||||||
| @@ -262,7 +292,10 @@ on.disconnected = function(listener, options) { | |||||||
| 	}; | 	}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // src/dom-lib/scopes.js | // src/dom.js | ||||||
|  | function queue(promise) { | ||||||
|  | 	return enviroment.q(promise); | ||||||
|  | } | ||||||
| var scopes = [{ | var scopes = [{ | ||||||
| 	get scope() { | 	get scope() { | ||||||
| 		return enviroment.D.body; | 		return enviroment.D.body; | ||||||
| @@ -274,7 +307,7 @@ var store_abort = /* @__PURE__ */ new WeakMap(); | |||||||
| var scope = { | var scope = { | ||||||
| 	/** | 	/** | ||||||
| 	* Gets the current scope | 	* Gets the current scope | ||||||
| 	* @returns {typeof scopes[number]} Current scope context | 	* @returns {Object} Current scope context | ||||||
| 	*/ | 	*/ | ||||||
| 	get current() { | 	get current() { | ||||||
| 		return scopes[scopes.length - 1]; | 		return scopes[scopes.length - 1]; | ||||||
| @@ -337,60 +370,6 @@ 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; | ||||||
| @@ -406,8 +385,7 @@ 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; | ||||||
| 	const att_type = typeof attributes; | 	if (Object(attributes) !== attributes || s.isSignal(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": { | ||||||
| @@ -461,6 +439,38 @@ 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) { | ||||||
| @@ -523,6 +533,11 @@ 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); | ||||||
| @@ -546,40 +561,19 @@ function forEachEntries(s, target, element, obj, cb) { | |||||||
| 		cb(key, val); | 		cb(key, val); | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  | function setRemove(obj, prop, key, val) { | ||||||
| // src/dom-lib/customElement.js | 	return obj[(isUndef(val) ? "remove" : "set") + prop](key, val); | ||||||
| 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 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 | ||||||
| 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({ | ||||||
| @@ -634,9 +628,9 @@ function memo(key, generator) { | |||||||
| memo.isScope = function(obj) { | memo.isScope = function(obj) { | ||||||
| 	return obj[memoMark]; | 	return obj[memoMark]; | ||||||
| }; | }; | ||||||
| memo.scope = function memoScopeCreate(fun, { signal, onlyLast } = {}) { | memo.scope = function memoScope(fun, { signal, onlyLast } = {}) { | ||||||
| 	let cache = oCreate(); | 	let cache = oCreate(); | ||||||
| 	function memoScope(...args) { | 	function memoScope2(...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(); | ||||||
| @@ -651,10 +645,10 @@ memo.scope = function memoScopeCreate(fun, { signal, onlyLast } = {}) { | |||||||
| 		cache = cache_local; | 		cache = cache_local; | ||||||
| 		return out; | 		return out; | ||||||
| 	} | 	} | ||||||
| 	memoScope[memoMark] = true; | 	memoScope2[memoMark] = true; | ||||||
| 	memoScope.clear = () => cache = oCreate(); | 	memoScope2.clear = () => cache = oCreate(); | ||||||
| 	if (signal) signal.addEventListener("abort", memoScope.clear); | 	if (signal) signal.addEventListener("abort", memoScope2.clear); | ||||||
| 	return memoScope; | 	return memoScope2; | ||||||
| }; | }; | ||||||
| export { | export { | ||||||
| 	assign, | 	assign, | ||||||
| @@ -668,6 +662,7 @@ 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, force?: boolean): V; | 	set(value: V): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
| @@ -172,30 +172,17 @@ 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, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & { | 	<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; | ||||||
| 		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<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | 	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 | 	/** 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<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | 	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 | ||||||
| 	 * Fires after the next tick of the Javascript event loop. | 	attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[ | ||||||
| 	 * This is handy for example to apply some property depending on the element content: | 		string, | ||||||
| 	 * ```js | 		string | ||||||
| 	 * const selected= "Z"; | 	]>) => any, options?: AddEventListenerOptions): EE; | ||||||
| 	 * //... |  | ||||||
| 	 * 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, force?: boolean): V; | 	set(value: V): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
| @@ -173,30 +173,17 @@ 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, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & { | 	<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; | ||||||
| 		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<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | 	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 | 	/** 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<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | 	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 | ||||||
| 	 * Fires after the next tick of the Javascript event loop. | 	attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[ | ||||||
| 	 * This is handy for example to apply some property depending on the element content: | 		string, | ||||||
| 	 * ```js | 		string | ||||||
| 	 * const selected= "Z"; | 	]>) => any, options?: AddEventListenerOptions): EE; | ||||||
| 	 * //... |  | ||||||
| 	 * 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 = { | ||||||
|   | |||||||
							
								
								
									
										246
									
								
								dist/iife-with-signals.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										246
									
								
								dist/iife-with-signals.js
									
									
									
									
										vendored
									
									
								
							| @@ -32,6 +32,7 @@ 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, | ||||||
| @@ -86,8 +87,52 @@ 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 { | ||||||
|  | 		constructor() { | ||||||
|  | 			super(); | ||||||
|  | 			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/dom-lib/common.js | 	// 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 | ||||||
| 	var enviroment = { | 	var enviroment = { | ||||||
| 		setDeleteAttr, | 		setDeleteAttr, | ||||||
| 		ssr: "", | 		ssr: "", | ||||||
| @@ -113,7 +158,7 @@ var DDE = (() => { | |||||||
| 	var evd = "dde:disconnected"; | 	var evd = "dde:disconnected"; | ||||||
| 	var eva = "dde:attributeChanged"; | 	var eva = "dde:attributeChanged"; | ||||||
|  |  | ||||||
| 	// src/dom-lib/events-observer.js | 	// src/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 () => { | ||||||
| @@ -277,7 +322,7 @@ var DDE = (() => { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// src/dom-lib/events.js | 	// src/events.js | ||||||
| 	function dispatchEvent(name, options, host) { | 	function dispatchEvent(name, options, host) { | ||||||
| 		if (typeof options === "function") { | 		if (typeof options === "function") { | ||||||
| 			host = options; | 			host = options; | ||||||
| @@ -299,7 +344,6 @@ 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); | ||||||
| @@ -323,7 +367,10 @@ var DDE = (() => { | |||||||
| 		}; | 		}; | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	// src/dom-lib/scopes.js | 	// src/dom.js | ||||||
|  | 	function queue(promise) { | ||||||
|  | 		return enviroment.q(promise); | ||||||
|  | 	} | ||||||
| 	var scopes = [{ | 	var scopes = [{ | ||||||
| 		get scope() { | 		get scope() { | ||||||
| 			return enviroment.D.body; | 			return enviroment.D.body; | ||||||
| @@ -335,7 +382,7 @@ var DDE = (() => { | |||||||
| 	var scope = { | 	var scope = { | ||||||
| 		/** | 		/** | ||||||
| 		* Gets the current scope | 		* Gets the current scope | ||||||
| 		* @returns {typeof scopes[number]} Current scope context | 		* @returns {Object} Current scope context | ||||||
| 		*/ | 		*/ | ||||||
| 		get current() { | 		get current() { | ||||||
| 			return scopes[scopes.length - 1]; | 			return scopes[scopes.length - 1]; | ||||||
| @@ -398,60 +445,6 @@ 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; | ||||||
| @@ -467,8 +460,7 @@ var DDE = (() => { | |||||||
| 		const s = signals(this); | 		const s = signals(this); | ||||||
| 		let scoped = 0; | 		let scoped = 0; | ||||||
| 		let el, el_host; | 		let el, el_host; | ||||||
| 		const att_type = typeof attributes; | 		if (Object(attributes) !== attributes || s.isSignal(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": { | ||||||
| @@ -522,6 +514,38 @@ 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) { | ||||||
| @@ -584,6 +608,11 @@ 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); | ||||||
| @@ -607,40 +636,19 @@ var DDE = (() => { | |||||||
| 			cb(key, val); | 			cb(key, val); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  | 	function setRemove(obj, prop, key, val) { | ||||||
| 	// src/dom-lib/customElement.js | 		return obj[(isUndef(val) ? "remove" : "set") + prop](key, val); | ||||||
| 	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 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 | ||||||
| 	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({ | ||||||
| @@ -695,9 +703,9 @@ var DDE = (() => { | |||||||
| 	memo.isScope = function(obj) { | 	memo.isScope = function(obj) { | ||||||
| 		return obj[memoMark]; | 		return obj[memoMark]; | ||||||
| 	}; | 	}; | ||||||
| 	memo.scope = function memoScopeCreate(fun, { signal: signal2, onlyLast } = {}) { | 	memo.scope = function memoScope(fun, { signal: signal2, onlyLast } = {}) { | ||||||
| 		let cache = oCreate(); | 		let cache = oCreate(); | ||||||
| 		function memoScope(...args) { | 		function memoScope2(...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(); | ||||||
| @@ -712,28 +720,28 @@ var DDE = (() => { | |||||||
| 			cache = cache_local; | 			cache = cache_local; | ||||||
| 			return out; | 			return out; | ||||||
| 		} | 		} | ||||||
| 		memoScope[memoMark] = true; | 		memoScope2[memoMark] = true; | ||||||
| 		memoScope.clear = () => cache = oCreate(); | 		memoScope2.clear = () => cache = oCreate(); | ||||||
| 		if (signal2) signal2.addEventListener("abort", memoScope.clear); | 		if (signal2) signal2.addEventListener("abort", memoScope2.clear); | ||||||
| 		return memoScope; | 		return memoScope2; | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	// 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 Map(); | 		let pendingSignals = /* @__PURE__ */ new Set(); | ||||||
| 		let scheduled = false; | 		let scheduled = false; | ||||||
| 		function flushSignals() { | 		function flushSignals() { | ||||||
| 			scheduled = false; | 			scheduled = false; | ||||||
| 			const todo = pendingSignals; | 			const todo = pendingSignals; | ||||||
| 			pendingSignals = /* @__PURE__ */ new Map(); | 			pendingSignals = /* @__PURE__ */ new Set(); | ||||||
| 			for (const [signal2, force] of todo) { | 			for (const signal2 of todo) { | ||||||
| 				const M = signal2[mark]; | 				const M = signal2[mark]; | ||||||
| 				if (M) M.listeners.forEach((l) => l(M.value, force)); | 				if (M) M.listeners.forEach((l) => l(M.value)); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		return function(s, force = false) { | 		return function(s) { | ||||||
| 			pendingSignals.set(s, pendingSignals.get(s) || force); | 			pendingSignals.add(s); | ||||||
| 			if (scheduled) return; | 			if (scheduled) return; | ||||||
| 			scheduled = true; | 			scheduled = true; | ||||||
| 			queueMicrotask(flushSignals); | 			queueMicrotask(flushSignals); | ||||||
| @@ -770,11 +778,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(_, force) { | 		function contextReWatch() { | ||||||
| 			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(), force); | 			write(out, value()); | ||||||
| 			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); | ||||||
| @@ -796,7 +804,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, true); | 		queueSignalWrite(s); | ||||||
| 	}; | 	}; | ||||||
| 	signal.on = function on2(s, listener, options = {}) { | 	signal.on = function on2(s, listener, options = {}) { | ||||||
| 		const { signal: as } = options; | 		const { signal: as } = options; | ||||||
| @@ -832,17 +840,17 @@ var DDE = (() => { | |||||||
| 	}; | 	}; | ||||||
| 	var key_reactive = "__dde_reactive"; | 	var key_reactive = "__dde_reactive"; | ||||||
| 	signal.el = function(s, map) { | 	signal.el = function(s, map) { | ||||||
| 		const mapScoped = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | 		map = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | ||||||
| 		const { current } = scope, { scope: sc } = current; | 		const mark_start = createElement.mark({ type: "reactive", source: new Defined().compact }, true); | ||||||
| 		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 = mapScoped(v); | 			let els = map(v); | ||||||
| 			scope.pop(); | 			scope.pop(); | ||||||
| 			if (!Array.isArray(els)) | 			if (!Array.isArray(els)) | ||||||
| 				els = [els]; | 				els = [els]; | ||||||
| @@ -862,7 +870,7 @@ 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` */ | ||||||
| 				mapScoped.clear() | 				map.clear() | ||||||
| 			) | 			) | ||||||
| 		)); | 		)); | ||||||
| 		return out; | 		return out; | ||||||
| @@ -934,8 +942,9 @@ var DDE = (() => { | |||||||
| 	function removeSignalsFromElements(s, listener, ...notes) { | 	function removeSignalsFromElements(s, listener, ...notes) { | ||||||
| 		const { current } = scope; | 		const { current } = scope; | ||||||
| 		current.host(function(element) { | 		current.host(function(element) { | ||||||
| 			if (!element[key_reactive]) element[key_reactive] = []; | 			if (element[key_reactive]) | ||||||
| 			element[key_reactive].push([[s, listener], ...notes]); | 				return element[key_reactive].push([[s, listener], ...notes]); | ||||||
|  | 			element[key_reactive] = []; | ||||||
| 			if (current.prevent) return; | 			if (current.prevent) return; | ||||||
| 			on.disconnected( | 			on.disconnected( | ||||||
| 				() => ( | 				() => ( | ||||||
| @@ -980,6 +989,7 @@ var DDE = (() => { | |||||||
| 				onclear, | 				onclear, | ||||||
| 				host, | 				host, | ||||||
| 				listeners: /* @__PURE__ */ new Set(), | 				listeners: /* @__PURE__ */ new Set(), | ||||||
|  | 				defined: new Defined().stack, | ||||||
| 				readonly | 				readonly | ||||||
| 			}), | 			}), | ||||||
| 			enumerable: false, | 			enumerable: false, | ||||||
| @@ -1003,7 +1013,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, force); | 		queueSignalWrite(s); | ||||||
| 		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, force?: boolean): V; | 	set(value: V): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
| @@ -173,30 +173,17 @@ 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, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & { | 	<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; | ||||||
| 		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<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | 	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 | 	/** 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<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | 	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 | ||||||
| 	 * Fires after the next tick of the Javascript event loop. | 	attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[ | ||||||
| 	 * This is handy for example to apply some property depending on the element content: | 		string, | ||||||
| 	 * ```js | 		string | ||||||
| 	 * const selected= "Z"; | 	]>) => any, options?: AddEventListenerOptions): EE; | ||||||
| 	 * //... |  | ||||||
| 	 * 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, force?: boolean): V; | 	set(value: V): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
| @@ -172,30 +172,17 @@ 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, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & { | 	<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; | ||||||
| 		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<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | 	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 | 	/** 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<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | 	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 | ||||||
| 	 * Fires after the next tick of the Javascript event loop. | 	attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[ | ||||||
| 	 * This is handy for example to apply some property depending on the element content: | 		string, | ||||||
| 	 * ```js | 		string | ||||||
| 	 * const selected= "Z"; | 	]>) => any, options?: AddEventListenerOptions): EE; | ||||||
| 	 * //... |  | ||||||
| 	 * 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 = { | ||||||
|   | |||||||
							
								
								
									
										197
									
								
								dist/iife.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										197
									
								
								dist/iife.js
									
									
									
									
										vendored
									
									
								
							| @@ -31,6 +31,7 @@ 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,7 +69,38 @@ var DDE = (() => { | |||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// src/dom-lib/common.js | 	// 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 | ||||||
| 	var enviroment = { | 	var enviroment = { | ||||||
| 		setDeleteAttr, | 		setDeleteAttr, | ||||||
| 		ssr: "", | 		ssr: "", | ||||||
| @@ -94,7 +126,7 @@ var DDE = (() => { | |||||||
| 	var evd = "dde:disconnected"; | 	var evd = "dde:disconnected"; | ||||||
| 	var eva = "dde:attributeChanged"; | 	var eva = "dde:attributeChanged"; | ||||||
|  |  | ||||||
| 	// src/dom-lib/events-observer.js | 	// src/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 () => { | ||||||
| @@ -258,7 +290,7 @@ var DDE = (() => { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// src/dom-lib/events.js | 	// src/events.js | ||||||
| 	function dispatchEvent(name, options, host) { | 	function dispatchEvent(name, options, host) { | ||||||
| 		if (typeof options === "function") { | 		if (typeof options === "function") { | ||||||
| 			host = options; | 			host = options; | ||||||
| @@ -280,7 +312,6 @@ 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); | ||||||
| @@ -304,7 +335,10 @@ var DDE = (() => { | |||||||
| 		}; | 		}; | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	// src/dom-lib/scopes.js | 	// src/dom.js | ||||||
|  | 	function queue(promise) { | ||||||
|  | 		return enviroment.q(promise); | ||||||
|  | 	} | ||||||
| 	var scopes = [{ | 	var scopes = [{ | ||||||
| 		get scope() { | 		get scope() { | ||||||
| 			return enviroment.D.body; | 			return enviroment.D.body; | ||||||
| @@ -316,7 +350,7 @@ var DDE = (() => { | |||||||
| 	var scope = { | 	var scope = { | ||||||
| 		/** | 		/** | ||||||
| 		* Gets the current scope | 		* Gets the current scope | ||||||
| 		* @returns {typeof scopes[number]} Current scope context | 		* @returns {Object} Current scope context | ||||||
| 		*/ | 		*/ | ||||||
| 		get current() { | 		get current() { | ||||||
| 			return scopes[scopes.length - 1]; | 			return scopes[scopes.length - 1]; | ||||||
| @@ -379,60 +413,6 @@ 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; | ||||||
| @@ -448,8 +428,7 @@ var DDE = (() => { | |||||||
| 		const s = signals(this); | 		const s = signals(this); | ||||||
| 		let scoped = 0; | 		let scoped = 0; | ||||||
| 		let el, el_host; | 		let el, el_host; | ||||||
| 		const att_type = typeof attributes; | 		if (Object(attributes) !== attributes || s.isSignal(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": { | ||||||
| @@ -503,6 +482,38 @@ 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) { | ||||||
| @@ -565,6 +576,11 @@ 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); | ||||||
| @@ -588,40 +604,19 @@ var DDE = (() => { | |||||||
| 			cb(key, val); | 			cb(key, val); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  | 	function setRemove(obj, prop, key, val) { | ||||||
| 	// src/dom-lib/customElement.js | 		return obj[(isUndef(val) ? "remove" : "set") + prop](key, val); | ||||||
| 	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 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 | ||||||
| 	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({ | ||||||
| @@ -676,9 +671,9 @@ var DDE = (() => { | |||||||
| 	memo.isScope = function(obj) { | 	memo.isScope = function(obj) { | ||||||
| 		return obj[memoMark]; | 		return obj[memoMark]; | ||||||
| 	}; | 	}; | ||||||
| 	memo.scope = function memoScopeCreate(fun, { signal, onlyLast } = {}) { | 	memo.scope = function memoScope(fun, { signal, onlyLast } = {}) { | ||||||
| 		let cache = oCreate(); | 		let cache = oCreate(); | ||||||
| 		function memoScope(...args) { | 		function memoScope2(...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(); | ||||||
| @@ -693,10 +688,10 @@ var DDE = (() => { | |||||||
| 			cache = cache_local; | 			cache = cache_local; | ||||||
| 			return out; | 			return out; | ||||||
| 		} | 		} | ||||||
| 		memoScope[memoMark] = true; | 		memoScope2[memoMark] = true; | ||||||
| 		memoScope.clear = () => cache = oCreate(); | 		memoScope2.clear = () => cache = oCreate(); | ||||||
| 		if (signal) signal.addEventListener("abort", memoScope.clear); | 		if (signal) signal.addEventListener("abort", memoScope2.clear); | ||||||
| 		return memoScope; | 		return memoScope2; | ||||||
| 	}; | 	}; | ||||||
| 	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, force?: boolean): V; | 	set(value: V): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
| @@ -172,30 +172,17 @@ 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, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event] & { | 	<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; | ||||||
| 		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<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | 	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 | 	/** 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<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>; | 	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 | ||||||
| 	 * Fires after the next tick of the Javascript event loop. | 	attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[ | ||||||
| 	 * This is handy for example to apply some property depending on the element content: | 		string, | ||||||
| 	 * ```js | 		string | ||||||
| 	 * const selected= "Z"; | 	]>) => any, options?: AddEventListenerOptions): EE; | ||||||
| 	 * //... |  | ||||||
| 	 * 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
											
										
									
								
							| @@ -184,7 +184,7 @@ 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"|"shell"} [attrs.language="js"] Language of the code |  * @param {"js"|"ts"|"html"|"css"} [attrs.language="js"] Language of the code | ||||||
|  * @param {string} [attrs.page_id] ID of the page, if setted it registers shiki |  * @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= "js", className= host.slice(1), page_id }){ | ||||||
|   | |||||||
| @@ -1,176 +0,0 @@ | |||||||
| import { styles } from "../ssr.js"; |  | ||||||
|  |  | ||||||
| styles.css` |  | ||||||
| #html-to-dde-converter { |  | ||||||
| 	grid-column: full-main; |  | ||||||
| 	display: flex; |  | ||||||
| 	flex-direction: column; |  | ||||||
| 	gap: 1.5rem; |  | ||||||
| 	padding: 1.5rem; |  | ||||||
| 	border-radius: var(--border-radius); |  | ||||||
| 	background-color: var(--bg-sidebar); |  | ||||||
| 	box-shadow: var(--shadow); |  | ||||||
| 	border: 1px solid var(--border); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter h3 { |  | ||||||
| 	margin-top: 0; |  | ||||||
| 	color: var(--primary); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter .description { |  | ||||||
| 	color: var(--text-light); |  | ||||||
| 	font-size: 0.95rem; |  | ||||||
| 	margin-top: -1rem; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter .converter-form { |  | ||||||
| 	display: flex; |  | ||||||
| 	flex-direction: column; |  | ||||||
| 	gap: 1rem; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter .input-group, |  | ||||||
| #html-to-dde-converter .output-group { |  | ||||||
| 	display: flex; |  | ||||||
| 	flex-direction: column; |  | ||||||
| 	gap: 0.5rem; |  | ||||||
| } |  | ||||||
| #html-to-dde-converter [type="number"]{ |  | ||||||
| 	width: 3em; |  | ||||||
| 	font-variant-numeric: tabular-nums; |  | ||||||
| 	font-size: 1rem; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter label { |  | ||||||
| 	font-weight: 500; |  | ||||||
| 	display: flex; |  | ||||||
| 	justify-content: space-between; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter .options { |  | ||||||
| 	display: flex; |  | ||||||
| 	flex-wrap: wrap; |  | ||||||
| 	gap: 1rem; |  | ||||||
| 	margin-bottom: 0.5rem; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter .option-group { |  | ||||||
| 	display: flex; |  | ||||||
| 	align-items: center; |  | ||||||
| 	gap: 0.5rem; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter textarea { |  | ||||||
| 	font-family: var(--font-mono); |  | ||||||
| 	font-size: 0.9rem; |  | ||||||
| 	padding: 1rem; |  | ||||||
| 	border-radius: var(--border-radius); |  | ||||||
| 	border: 1px solid var(--border); |  | ||||||
| 	background-color: var(--bg); |  | ||||||
| 	color: var(--text); |  | ||||||
| 	min-height: 200px; |  | ||||||
| 	height: 25em; |  | ||||||
| 	resize: vertical; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter textarea:focus { |  | ||||||
| 	outline: 2px solid var(--primary-light); |  | ||||||
| 	outline-offset: 1px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter .button-group { |  | ||||||
| 	display: flex; |  | ||||||
| 	gap: 0.5rem; |  | ||||||
| 	justify-content: space-between; |  | ||||||
| 	align-items: center; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter button { |  | ||||||
| 	padding: 0.5rem 1rem; |  | ||||||
| 	border-radius: var(--border-radius); |  | ||||||
| 	border: none; |  | ||||||
| 	background-color: var(--primary); |  | ||||||
| 	color: var(--button-text); |  | ||||||
| 	font-weight: 500; |  | ||||||
| 	cursor: pointer; |  | ||||||
| 	transition: background-color 0.2s ease; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter button:hover { |  | ||||||
| 	background-color: var(--primary-dark); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter button.secondary { |  | ||||||
| 	background-color: transparent; |  | ||||||
| 	border: 1px solid var(--border); |  | ||||||
| 	color: var(--text); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter button.secondary:hover { |  | ||||||
| 	background-color: var(--bg); |  | ||||||
| 	border-color: var(--primary); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter .copy-button { |  | ||||||
| 	background-color: var(--secondary); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter .copy-button:hover { |  | ||||||
| 	background-color: var(--secondary-dark); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter .status { |  | ||||||
| 	font-size: 0.9rem; |  | ||||||
| 	color: var(--text-light); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter .error { |  | ||||||
| 	color: hsl(0, 100%, 60%); |  | ||||||
| 	font-size: 0.9rem; |  | ||||||
| 	margin-top: 0.5rem; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /* Sample HTML examples list */ |  | ||||||
| #html-to-dde-converter .examples-list { |  | ||||||
| 	display: flex; |  | ||||||
| 	flex-wrap: wrap; |  | ||||||
| 	gap: 0.5rem; |  | ||||||
| 	margin-top: 0.5rem; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #html-to-dde-converter .example-button { |  | ||||||
| 	font-size: 0.85rem; |  | ||||||
| 	padding: 0.25rem 0.5rem; |  | ||||||
| } |  | ||||||
| `; |  | ||||||
|  |  | ||||||
| import { ireland } from "./ireland.html.js"; |  | ||||||
| import { el } from "deka-dom-el"; |  | ||||||
| const fileURL= url=> new URL(url, import.meta.url); |  | ||||||
|  |  | ||||||
| export function converter({ page_id }){ |  | ||||||
| 	registerClientPart(page_id); |  | ||||||
| 	return el(ireland, { |  | ||||||
| 		src: fileURL("./converter.js.js"), |  | ||||||
| 		exportName: "converter", |  | ||||||
| 		page_id, |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| let is_registered= {}; |  | ||||||
| /** @param {string} page_id */ |  | ||||||
| function registerClientPart(page_id){ |  | ||||||
| 	if(is_registered[page_id]) return; |  | ||||||
|  |  | ||||||
| 	document.head.append( |  | ||||||
| 		el("script", { |  | ||||||
| 			// src: "https://unpkg.com/@beforesemicolon/html-parser/dist/client.js", |  | ||||||
| 			src: "https://cdn.jsdelivr.net/npm/@beforesemicolon/html-parser/dist/client.js", |  | ||||||
| 			type: "text/javascript", |  | ||||||
| 			charset: "utf-8", |  | ||||||
| 			defer: true |  | ||||||
| 		}), |  | ||||||
| 	); |  | ||||||
| 	is_registered[page_id]= true; |  | ||||||
| } |  | ||||||
| @@ -1,384 +0,0 @@ | |||||||
| 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,374 +0,0 @@ | |||||||
| /** |  | ||||||
|  * 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); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			.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; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		`) |  | ||||||
| ); |  | ||||||
| @@ -1,412 +0,0 @@ | |||||||
| /** |  | ||||||
|  * 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': |  | ||||||
| 				document.querySelector('.lightbox-prev-btn').click(); |  | ||||||
| 				break; |  | ||||||
| 			case 'ArrowRight': |  | ||||||
| 				document.querySelector('.lightbox-next-btn').click(); |  | ||||||
| 				break; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Build the gallery UI |  | ||||||
| 	return el("div", { className: "gallery-container" }).append( |  | ||||||
| 		// Gallery header |  | ||||||
| 		el("header", { className: "gallery-header" }).append( |  | ||||||
| 			el("h1", "Interactive Image Gallery"), |  | ||||||
| 			el("p", "Click on any image to view it in the lightbox. Use arrow keys for navigation.") |  | ||||||
| 		), |  | ||||||
|  |  | ||||||
| 		// Filter options |  | ||||||
| 		el("div", { className: "gallery-filters" }).append( |  | ||||||
| 			el("button", { |  | ||||||
| 				classList: { active: S(() => filterTag.get() === 'all') }, |  | ||||||
| 				textContent: "All" |  | ||||||
| 			}, onFilterChange('all')), |  | ||||||
| 			el("button", { |  | ||||||
| 				classList: { active: S(() => filterTag.get() === 'nature') }, |  | ||||||
| 				textContent: "Nature" |  | ||||||
| 			}, onFilterChange('nature')), |  | ||||||
| 			el("button", { |  | ||||||
| 				classList: { active: S(() => filterTag.get() === 'urban') }, |  | ||||||
| 				textContent: "Urban" |  | ||||||
| 			}, onFilterChange('urban')), |  | ||||||
| 			el("button", { |  | ||||||
| 				classList: { active: S(() => filterTag.get() === 'people') }, |  | ||||||
| 				textContent: "People" |  | ||||||
| 			}, onFilterChange('people')) |  | ||||||
| 		), |  | ||||||
|  |  | ||||||
| 		// Image grid |  | ||||||
| 		el("div", { className: "gallery-grid" }).append( |  | ||||||
| 			S.el(imagesToDisplay, images => |  | ||||||
| 				images.map(image => |  | ||||||
| 					memo(image.id, ()=> |  | ||||||
| 						el("div", { |  | ||||||
| 							className: "gallery-item", |  | ||||||
| 							dataTag: image.alt.toLowerCase() |  | ||||||
| 						}).append( |  | ||||||
| 							el("img", { |  | ||||||
| 								src: image.src, |  | ||||||
| 								alt: image.alt, |  | ||||||
| 								loading: "lazy" |  | ||||||
| 							}, onImageClick(image.id)), |  | ||||||
| 							el("div", { className: "gallery-item-caption" }).append( |  | ||||||
| 								el("h3", image.title), |  | ||||||
| 								el("p", image.alt) |  | ||||||
| 							) |  | ||||||
| 						) |  | ||||||
| 					) |  | ||||||
| 				) |  | ||||||
| 			) |  | ||||||
| 		), |  | ||||||
|  |  | ||||||
| 		// Lightbox (only shown when an image is selected) |  | ||||||
| 		S.el(isLightboxOpen, open => !open |  | ||||||
| 			? el() |  | ||||||
| 			: el("div", { className: "lightbox-overlay" }, on("click", closeLightbox)).append( |  | ||||||
| 					el("div", { |  | ||||||
| 						className: "lightbox-content", |  | ||||||
| 						onClick: e => e.stopPropagation() // Prevent closing when clicking inside |  | ||||||
| 					}).append( |  | ||||||
| 						el("button", { |  | ||||||
| 							className: "lightbox-close-btn", |  | ||||||
| 							"aria-label": "Close lightbox" |  | ||||||
| 						}, on("click", closeLightbox)).append("×"), |  | ||||||
|  |  | ||||||
| 						el("button", { |  | ||||||
| 							className: "lightbox-prev-btn", |  | ||||||
| 							"aria-label": "Previous image" |  | ||||||
| 						}, on("click", onPrevImage)).append("❮"), |  | ||||||
|  |  | ||||||
| 						el("button", { |  | ||||||
| 							className: "lightbox-next-btn", |  | ||||||
| 							"aria-label": "Next image" |  | ||||||
| 						}, on("click", onNextImage)).append("❯"), |  | ||||||
|  |  | ||||||
| 						S.el(selectedImage, img => !img |  | ||||||
| 							? el() |  | ||||||
| 							: el("div", { className: "lightbox-image-container" }).append( |  | ||||||
| 								el("img", { |  | ||||||
| 									src: img.src, |  | ||||||
| 									alt: img.alt, |  | ||||||
| 									className: "lightbox-image" |  | ||||||
| 								}), |  | ||||||
| 								el("div", { className: "lightbox-caption" }).append( |  | ||||||
| 									el("h2", img.title), |  | ||||||
| 									el("p", img.alt) |  | ||||||
| 								) |  | ||||||
| 							) |  | ||||||
| 						) |  | ||||||
| 					) |  | ||||||
| 				) |  | ||||||
| 		), |  | ||||||
| 	); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Render the component |  | ||||||
| document.body.append( |  | ||||||
| 	el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append( |  | ||||||
| 		el(ImageGallery) |  | ||||||
| 	), |  | ||||||
| 	el("style", ` |  | ||||||
| 		.gallery-container { |  | ||||||
| 			font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; |  | ||||||
| 			max-width: 1200px; |  | ||||||
| 			margin: 0 auto; |  | ||||||
| 			padding: 2rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.gallery-header { |  | ||||||
| 			text-align: center; |  | ||||||
| 			margin-bottom: 2rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.gallery-header h1 { |  | ||||||
| 			margin-bottom: 0.5rem; |  | ||||||
| 			color: #333; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.gallery-header p { |  | ||||||
| 			color: #666; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.gallery-filters { |  | ||||||
| 			display: flex; |  | ||||||
| 			justify-content: center; |  | ||||||
| 			margin-bottom: 2rem; |  | ||||||
| 			flex-wrap: wrap; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.gallery-filters button { |  | ||||||
| 			background: none; |  | ||||||
| 			border: none; |  | ||||||
| 			padding: 0.5rem 1.5rem; |  | ||||||
| 			margin: 0 0.5rem; |  | ||||||
| 			font-size: 1rem; |  | ||||||
| 			cursor: pointer; |  | ||||||
| 			border-radius: 30px; |  | ||||||
| 			transition: all 0.3s ease; |  | ||||||
| 			color: #555; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.gallery-filters button:hover { |  | ||||||
| 			background: #f0f0f0; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.gallery-filters button.active { |  | ||||||
| 			background: #4a90e2; |  | ||||||
| 			color: white; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.gallery-grid { |  | ||||||
| 			display: grid; |  | ||||||
| 			grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); |  | ||||||
| 			gap: 1.5rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.gallery-item { |  | ||||||
| 			position: relative; |  | ||||||
| 			border-radius: 8px; |  | ||||||
| 			overflow: hidden; |  | ||||||
| 			box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); |  | ||||||
| 			transition: transform 0.3s ease, box-shadow 0.3s ease; |  | ||||||
| 			cursor: pointer; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.gallery-item:hover { |  | ||||||
| 			transform: translateY(-5px); |  | ||||||
| 			box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.gallery-item img { |  | ||||||
| 			width: 100%; |  | ||||||
| 			height: 200px; |  | ||||||
| 			object-fit: cover; |  | ||||||
| 			display: block; |  | ||||||
| 			transition: transform 0.5s ease; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.gallery-item:hover img { |  | ||||||
| 			transform: scale(1.05); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.gallery-item-caption { |  | ||||||
| 			position: absolute; |  | ||||||
| 			bottom: 0; |  | ||||||
| 			left: 0; |  | ||||||
| 			right: 0; |  | ||||||
| 			background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent); |  | ||||||
| 			color: white; |  | ||||||
| 			padding: 1rem; |  | ||||||
| 			transform: translateY(100%); |  | ||||||
| 			transition: transform 0.3s ease; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.gallery-item:hover .gallery-item-caption { |  | ||||||
| 			transform: translateY(0); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.gallery-item-caption h3 { |  | ||||||
| 			margin: 0 0 0.5rem; |  | ||||||
| 			font-size: 1.2rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.gallery-item-caption p { |  | ||||||
| 			margin: 0; |  | ||||||
| 			font-size: 0.9rem; |  | ||||||
| 			opacity: 0.8; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		/* Lightbox styles */ |  | ||||||
| 		.lightbox-overlay { |  | ||||||
| 			position: fixed; |  | ||||||
| 			top: 0; |  | ||||||
| 			left: 0; |  | ||||||
| 			right: 0; |  | ||||||
| 			bottom: 0; |  | ||||||
| 			background: rgba(0, 0, 0, 0.9); |  | ||||||
| 			display: flex; |  | ||||||
| 			justify-content: center; |  | ||||||
| 			align-items: center; |  | ||||||
| 			z-index: 1000; |  | ||||||
| 			padding: 2rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.lightbox-content { |  | ||||||
| 			position: relative; |  | ||||||
| 			max-width: 90%; |  | ||||||
| 			max-height: 90%; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.lightbox-image-container { |  | ||||||
| 			overflow: hidden; |  | ||||||
| 			border-radius: 4px; |  | ||||||
| 			box-shadow: 0 0 30px rgba(0, 0, 0, 0.5); |  | ||||||
| 			background: #000; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.lightbox-image { |  | ||||||
| 			max-width: 100%; |  | ||||||
| 			max-height: 80vh; |  | ||||||
| 			display: block; |  | ||||||
| 			margin: 0 auto; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.lightbox-caption { |  | ||||||
| 			background: #222; |  | ||||||
| 			color: white; |  | ||||||
| 			padding: 1rem; |  | ||||||
| 			text-align: center; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.lightbox-caption h2 { |  | ||||||
| 			margin: 0 0 0.5rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.lightbox-caption p { |  | ||||||
| 			margin: 0; |  | ||||||
| 			opacity: 0.8; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.lightbox-close-btn, |  | ||||||
| 		.lightbox-prev-btn, |  | ||||||
| 		.lightbox-next-btn { |  | ||||||
| 			background: rgba(0, 0, 0, 0.5); |  | ||||||
| 			color: white; |  | ||||||
| 			border: none; |  | ||||||
| 			font-size: 1.5rem; |  | ||||||
| 			width: 50px; |  | ||||||
| 			height: 50px; |  | ||||||
| 			border-radius: 50%; |  | ||||||
| 			display: flex; |  | ||||||
| 			justify-content: center; |  | ||||||
| 			align-items: center; |  | ||||||
| 			cursor: pointer; |  | ||||||
| 			transition: background 0.3s ease; |  | ||||||
| 			position: absolute; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.lightbox-close-btn:hover, |  | ||||||
| 		.lightbox-prev-btn:hover, |  | ||||||
| 		.lightbox-next-btn:hover { |  | ||||||
| 			background: rgba(0, 0, 0, 0.8); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.lightbox-close-btn { |  | ||||||
| 			top: -25px; |  | ||||||
| 			right: -25px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.lightbox-prev-btn { |  | ||||||
| 			left: -25px; |  | ||||||
| 			top: 50%; |  | ||||||
| 			transform: translateY(-50%); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.lightbox-next-btn { |  | ||||||
| 			right: -25px; |  | ||||||
| 			top: 50%; |  | ||||||
| 			transform: translateY(-50%); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		@media (max-width: 768px) { |  | ||||||
| 			.gallery-container { |  | ||||||
| 				padding: 1rem; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			.gallery-grid { |  | ||||||
| 				grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); |  | ||||||
| 				gap: 1rem; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			.lightbox-prev-btn, |  | ||||||
| 			.lightbox-next-btn { |  | ||||||
| 				width: 40px; |  | ||||||
| 				height: 40px; |  | ||||||
| 				font-size: 1.2rem; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	`) |  | ||||||
| ); |  | ||||||
| @@ -1,339 +0,0 @@ | |||||||
| /** |  | ||||||
|  * 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; |  | ||||||
| 		} |  | ||||||
| 	`), |  | ||||||
| ); |  | ||||||
| @@ -1,715 +0,0 @@ | |||||||
| /** |  | ||||||
|  * Case Study: Task Manager Application |  | ||||||
|  * |  | ||||||
|  * This example demonstrates: |  | ||||||
|  * - Complex state management with signals |  | ||||||
|  * - Drag and drop functionality |  | ||||||
|  * - Local storage persistence |  | ||||||
|  * - Responsive design for different devices |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| import { el, on, dispatchEvent, scope } from "deka-dom-el"; |  | ||||||
| import { S } from "deka-dom-el/signals"; |  | ||||||
|  |  | ||||||
| /** @typedef {{ id: number, title: string, description: string, priority: string, status: string }} Task */ |  | ||||||
| /** |  | ||||||
|  * Task Manager Component |  | ||||||
|  * @returns {HTMLElement} Task manager UI |  | ||||||
|  */ |  | ||||||
| export function TaskManager() { |  | ||||||
| 	// <Tasks store> |  | ||||||
| 	const STORAGE_KEY = 'dde-task-manager'; |  | ||||||
| 	const STATUSES = { |  | ||||||
| 		TODO: 'todo', |  | ||||||
| 		IN_PROGRESS: 'in-progress', |  | ||||||
| 		DONE: 'done' |  | ||||||
| 	}; |  | ||||||
| 	/** @type {Task[]} */ |  | ||||||
| 	let initialTasks = []; |  | ||||||
| 	try { |  | ||||||
| 		const saved = localStorage.getItem(STORAGE_KEY); |  | ||||||
| 		if (saved) { |  | ||||||
| 			initialTasks = JSON.parse(saved); |  | ||||||
| 		} |  | ||||||
| 	} catch (e) { |  | ||||||
| 		console.error('Failed to load tasks from localStorage', e); |  | ||||||
| 	} |  | ||||||
| 	if (!initialTasks.length) { |  | ||||||
| 		initialTasks = [ |  | ||||||
| 			{ id: 1, title: 'Create project structure', description: 'Set up folders and initial files', |  | ||||||
| 				status: STATUSES.DONE, priority: 'high' }, |  | ||||||
| 			{ id: 2, title: 'Design UI components', description: 'Create mockups for main views', |  | ||||||
| 				status: STATUSES.IN_PROGRESS, priority: 'medium' }, |  | ||||||
| 			{ id: 3, title: 'Implement authentication', description: 'Set up user login and registration', |  | ||||||
| 				status: STATUSES.TODO, priority: 'high' }, |  | ||||||
| 			{ id: 4, title: 'Write documentation', description: 'Document API endpoints and usage examples', |  | ||||||
| 				status: STATUSES.TODO, priority: 'low' }, |  | ||||||
| 		]; |  | ||||||
| 	} |  | ||||||
| 	const tasks = S(initialTasks, { |  | ||||||
| 		add(task) { this.value.push(task); }, |  | ||||||
| 		remove(id) { this.value = this.value.filter(task => task.id !== id); }, |  | ||||||
| 		update(id, task) { |  | ||||||
| 			const current= this.value.find(t => t.id === id); |  | ||||||
| 			if (current) Object.assign(current, task); |  | ||||||
| 		} |  | ||||||
| 	}); |  | ||||||
| 	S.on(tasks, value => { |  | ||||||
| 		try { |  | ||||||
| 			localStorage.setItem(STORAGE_KEY, JSON.stringify(value)); |  | ||||||
| 		} catch (e) { |  | ||||||
| 			console.error('Failed to save tasks to localStorage', e); |  | ||||||
| 		} |  | ||||||
| 	}); |  | ||||||
| 	// </Tasks store> |  | ||||||
|  |  | ||||||
| 	const filterPriority = S('all'); |  | ||||||
| 	const searchQuery = S(''); |  | ||||||
| 	// Filtered tasks based on priority and search query |  | ||||||
| 	const filteredTasks = S(() => { |  | ||||||
| 		let filtered = tasks.get(); |  | ||||||
|  |  | ||||||
| 		// Filter by priority |  | ||||||
| 		if (filterPriority.get() !== 'all') { |  | ||||||
| 			filtered = filtered.filter(task => task.priority === filterPriority.get()); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Filter by search query |  | ||||||
| 		const query = searchQuery.get().toLowerCase(); |  | ||||||
| 		if (query) { |  | ||||||
| 			filtered = filtered.filter(task => |  | ||||||
| 				task.title.toLowerCase().includes(query) || |  | ||||||
| 				task.description.toLowerCase().includes(query) |  | ||||||
| 			); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return filtered; |  | ||||||
| 	}); |  | ||||||
| 	/** Tasks grouped by status for display in columns */ |  | ||||||
| 	const tasksByStatus = S(() => { |  | ||||||
| 		const filtered = filteredTasks.get(); |  | ||||||
| 		return { |  | ||||||
| 			[STATUSES.TODO]: filtered.filter(t => t.status === STATUSES.TODO), |  | ||||||
| 			[STATUSES.IN_PROGRESS]: filtered.filter(t => t.status === STATUSES.IN_PROGRESS), |  | ||||||
| 			[STATUSES.DONE]: filtered.filter(t => t.status === STATUSES.DONE) |  | ||||||
| 		}; |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	// <Add> signals and handlers for adding new tasks |  | ||||||
| 	const newTask = { title: '', description: '', priority: 'medium' }; |  | ||||||
| 	const onAddTask = e => { |  | ||||||
| 		e.preventDefault(); |  | ||||||
| 		if (!newTask.title) return; |  | ||||||
|  |  | ||||||
| 		S.action(tasks, "add", { |  | ||||||
| 			id: Date.now(), |  | ||||||
| 			status: STATUSES.TODO, |  | ||||||
| 			...newTask |  | ||||||
| 		}); |  | ||||||
| 		e.target.reset(); |  | ||||||
| 	}; |  | ||||||
| 	// </Add> |  | ||||||
| 	const onCardEdit= on("card:edit", /** @param {CardEditEvent} ev */({ detail: [ id, task ] })=> |  | ||||||
| 		S.action(tasks, "update", id, task)); |  | ||||||
| 	const onCardDelete= on("card:delete", /** @param {CardDeleteEvent} ev */({ detail: id })=> |  | ||||||
| 		S.action(tasks, "remove", id)); |  | ||||||
|  |  | ||||||
| 	const { onDragable, onDragArea }= moveElementAddon( |  | ||||||
| 		(id, status) => S.action(tasks, "update", id, { status }) |  | ||||||
| 	); |  | ||||||
|  |  | ||||||
| 	// Build the task manager UI |  | ||||||
| 	return el("div", { className: "task-manager" }).append( |  | ||||||
| 		el("header", { className: "app-header" }).append( |  | ||||||
| 			el("h1", "DDE Task Manager"), |  | ||||||
| 			el("div", { className: "app-controls" }).append( |  | ||||||
| 				el("input", { |  | ||||||
| 					type: "text", |  | ||||||
| 					placeholder: "Search tasks...", |  | ||||||
| 					value: searchQuery.get() |  | ||||||
| 				}, on("input", e => searchQuery.set(e.target.value))), |  | ||||||
| 				el("select", null, |  | ||||||
| 					on.defer(el=> el.value= filterPriority.get()), |  | ||||||
| 					on("change", e => filterPriority.set(e.target.value)) |  | ||||||
| 				).append( |  | ||||||
| 					el("option", { value: "all", textContent: "All Priorities" }), |  | ||||||
| 					el("option", { value: "low", textContent: "Low Priority" }), |  | ||||||
| 					el("option", { value: "medium", textContent: "Medium Priority" }), |  | ||||||
| 					el("option", { value: "high", textContent: "High Priority" }) |  | ||||||
| 				) |  | ||||||
| 			) |  | ||||||
| 		), |  | ||||||
|  |  | ||||||
| 		// Add new task form |  | ||||||
| 		el("form", { className: "new-task-form" }, on("submit", onAddTask)).append( |  | ||||||
| 			el("div", { className: "form-row" }).append( |  | ||||||
| 				el("input", { |  | ||||||
| 					type: "text", |  | ||||||
| 					placeholder: "New task title", |  | ||||||
| 					value: newTask.title, |  | ||||||
| 					required: true |  | ||||||
| 				}, on("input", e => newTask.title= e.target.value.trim())), |  | ||||||
| 				el("select", null, |  | ||||||
| 					on.defer(el=> el.value= newTask.priority), |  | ||||||
| 					on("change", e => newTask.priority= e.target.value) |  | ||||||
| 				).append( |  | ||||||
| 					el("option", { value: "low", textContent: "Low" }), |  | ||||||
| 					el("option", { value: "medium", textContent: "Medium" }), |  | ||||||
| 					el("option", { value: "high", textContent: "High" }) |  | ||||||
| 				), |  | ||||||
| 				el("button", { type: "submit", className: "add-btn" }).append("Add Task") |  | ||||||
| 			), |  | ||||||
| 			el("textarea", { |  | ||||||
| 				placeholder: "Task description (optional)", |  | ||||||
| 				value: newTask.description |  | ||||||
| 			}, on("input", e => newTask.description= e.target.value.trim())) |  | ||||||
| 		), |  | ||||||
|  |  | ||||||
| 		// Task board with columns |  | ||||||
| 		el("div", { className: "task-board" }).append( |  | ||||||
| 			// Todo column |  | ||||||
| 			el("div", { |  | ||||||
| 				id: `column-${STATUSES.TODO}`, |  | ||||||
| 				className: "task-column" |  | ||||||
| 			}, onDragArea(STATUSES.TODO)).append( |  | ||||||
| 				el("h2", { className: "column-header" }).append( |  | ||||||
| 					"To Do ", |  | ||||||
| 					el("span", { |  | ||||||
| 						textContent: S(() => tasksByStatus.get()[STATUSES.TODO].length), |  | ||||||
| 						className: "task-count" |  | ||||||
| 					}), |  | ||||||
| 				), |  | ||||||
| 				S.el(S(() => tasksByStatus.get()[STATUSES.TODO]), tasks => |  | ||||||
| 					el("div", { className: "column-tasks" }).append( |  | ||||||
| 						...tasks.map(task=> el(TaskCard, { task, onDragable }, onCardEdit, onCardDelete)) |  | ||||||
| 					) |  | ||||||
| 				) |  | ||||||
| 			), |  | ||||||
|  |  | ||||||
| 			// In Progress column |  | ||||||
| 			el("div", { |  | ||||||
| 				id: `column-${STATUSES.IN_PROGRESS}`, |  | ||||||
| 				className: "task-column" |  | ||||||
| 			}, onDragArea(STATUSES.IN_PROGRESS)).append( |  | ||||||
| 				el("h2", { className: "column-header" }).append( |  | ||||||
| 					"In Progress ", |  | ||||||
| 					el("span", { |  | ||||||
| 						textContent: S(() => tasksByStatus.get()[STATUSES.IN_PROGRESS].length), |  | ||||||
| 						className: "task-count", |  | ||||||
| 					}), |  | ||||||
| 				), |  | ||||||
| 				S.el(S(() => tasksByStatus.get()[STATUSES.IN_PROGRESS]), tasks => |  | ||||||
| 					el("div", { className: "column-tasks" }).append( |  | ||||||
| 						...tasks.map(task=> el(TaskCard, { task, onDragable }, onCardEdit, onCardDelete)) |  | ||||||
| 					) |  | ||||||
| 				) |  | ||||||
| 			), |  | ||||||
|  |  | ||||||
| 			// Done column |  | ||||||
| 			el("div", { |  | ||||||
| 				id: `column-${STATUSES.DONE}`, |  | ||||||
| 				className: "task-column" |  | ||||||
| 			}, onDragArea(STATUSES.DONE)).append( |  | ||||||
| 				el("h2", { className: "column-header" }).append( |  | ||||||
| 					"Done ", |  | ||||||
| 					el("span", { |  | ||||||
| 						textContent: S(() => tasksByStatus.get()[STATUSES.DONE].length), |  | ||||||
| 						className: "task-count", |  | ||||||
| 					}), |  | ||||||
| 				), |  | ||||||
| 				S.el(S(() => tasksByStatus.get()[STATUSES.DONE]), tasks => |  | ||||||
| 					el("div", { className: "column-tasks" }).append( |  | ||||||
| 						...tasks.map(task=> el(TaskCard, { task, onDragable }, onCardEdit, onCardDelete)) |  | ||||||
| 					) |  | ||||||
| 				) |  | ||||||
| 			) |  | ||||||
| 		), |  | ||||||
| 	); |  | ||||||
| } |  | ||||||
| /** @typedef {CustomEvent<[ string, Task ]>} CardEditEvent */ |  | ||||||
| /** @typedef {CustomEvent<string>} CardDeleteEvent */ |  | ||||||
| /** |  | ||||||
|  * Task Card Component |  | ||||||
|  * @type {(props: { task: Task, onDragable: (id: number) => ddeElementAddon<HTMLDivElement> }) => HTMLElement} |  | ||||||
|  * @fires {CardEditEvent} card:edit |  | ||||||
|  * @fires {CardDeleteEvent} card:delete |  | ||||||
|  * */ |  | ||||||
| function TaskCard({ task, onDragable }){ |  | ||||||
| 	const { host }= scope; |  | ||||||
| 	const isEditing = S(false); |  | ||||||
| 	const onEditStart = () => isEditing.set(true); |  | ||||||
|  |  | ||||||
| 	const dispatchEdit= dispatchEvent("card:edit", host); |  | ||||||
| 	const dispatchDelete= dispatchEvent("card:delete", host).bind(null, task.id); |  | ||||||
|  |  | ||||||
| 	return el("div", { |  | ||||||
| 		id: `task-${task.id}`, |  | ||||||
| 		className: `task-card priority-${task.priority}`, |  | ||||||
| 		draggable: true |  | ||||||
| 	}, onDragable(task.id)).append( |  | ||||||
| 		S.el(isEditing, editing => editing |  | ||||||
| 			? el(EditMode) |  | ||||||
| 			: el().append( |  | ||||||
| 				el("div", { className: "task-header" }).append( |  | ||||||
| 					el("h3", { className: "task-title", textContent: task.title }), |  | ||||||
| 					el("div", { className: "task-actions" }).append( |  | ||||||
| 						el("button", { |  | ||||||
| 							textContent: "✎", |  | ||||||
| 							className: "edit-btn", |  | ||||||
| 							ariaLabel: "Edit task" |  | ||||||
| 						}, on("click", onEditStart)), |  | ||||||
| 						el("button", { |  | ||||||
| 							textContent: "✕", |  | ||||||
| 							className: "delete-btn", |  | ||||||
| 							ariaLabel: "Delete task" |  | ||||||
| 						}, on("click", dispatchDelete)) |  | ||||||
| 					) |  | ||||||
| 				), |  | ||||||
| 				!task.description |  | ||||||
| 				? el() |  | ||||||
| 				: el("p", { className: "task-description", textContent: task.description }), |  | ||||||
| 				el("div", { className: "task-meta" }).append( |  | ||||||
| 					el("span", { |  | ||||||
| 						className: `priority-badge priority-${task.priority}`, |  | ||||||
| 						textContent: task.priority.charAt(0).toUpperCase() + task.priority.slice(1) |  | ||||||
| 					}) |  | ||||||
| 				) |  | ||||||
| 			) |  | ||||||
| 		) |  | ||||||
| 	); |  | ||||||
| 	function EditMode(){ |  | ||||||
| 		const onSubmit = on("submit", e => { |  | ||||||
| 			e.preventDefault(); |  | ||||||
| 			const formData = new FormData(/** @type {HTMLFormElement} */(e.target)); |  | ||||||
| 			const title = formData.get("title"); |  | ||||||
| 			const description = formData.get("description"); |  | ||||||
| 			const priority = formData.get("priority"); |  | ||||||
| 			isEditing.set(false); |  | ||||||
| 			dispatchEdit([ task.id, { title, description, priority } ]); |  | ||||||
| 		}) |  | ||||||
| 		const onEditCancel = () => isEditing.set(false); |  | ||||||
|  |  | ||||||
| 		return el("form", { className: "task-edit-form" }, onSubmit).append( |  | ||||||
| 			el("input", { |  | ||||||
| 				name: "title", |  | ||||||
| 				className: "task-title-input", |  | ||||||
| 				defaultValue: task.title, |  | ||||||
| 				placeholder: "Task title", |  | ||||||
| 				required: true, |  | ||||||
| 				autoFocus: true |  | ||||||
| 			}), |  | ||||||
| 			el("textarea", { |  | ||||||
| 				name: "description", |  | ||||||
| 				className: "task-desc-input", |  | ||||||
| 				defaultValue: task.description, |  | ||||||
| 				placeholder: "Description (optional)" |  | ||||||
| 			}), |  | ||||||
| 			el("select", { |  | ||||||
| 				name: "priority", |  | ||||||
| 			}, on.defer(el=> el.value = task.priority)).append( |  | ||||||
| 				el("option", { value: "low", textContent: "Low Priority" }), |  | ||||||
| 				el("option", { value: "medium", textContent: "Medium Priority" }), |  | ||||||
| 				el("option", { value: "high", textContent: "High Priority" }) |  | ||||||
| 			), |  | ||||||
| 			el("div", { className: "task-edit-actions" }).append( |  | ||||||
| 				el("button", { |  | ||||||
| 					textContent: "Cancel", |  | ||||||
| 					type: "button", |  | ||||||
| 					className: "cancel-btn" |  | ||||||
| 				}, on("click", onEditCancel)), |  | ||||||
| 				el("button", { |  | ||||||
| 					textContent: "Save", |  | ||||||
| 					type: "submit", |  | ||||||
| 					className: "save-btn" |  | ||||||
| 				}) |  | ||||||
| 			) |  | ||||||
| 		); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Helper function to handle move an element |  | ||||||
|  * @param {(id: string, status: string) => void} onMoved |  | ||||||
|  * */ |  | ||||||
| function moveElementAddon(onMoved){ |  | ||||||
| 	let draggedTaskId = null; |  | ||||||
| 	function onDragable(id) { |  | ||||||
| 		return element => { |  | ||||||
| 			on("dragstart", e => { |  | ||||||
| 				draggedTaskId= id; |  | ||||||
| 				e.dataTransfer.effectAllowed = 'move'; |  | ||||||
|  |  | ||||||
| 				// Add some styling to the element being dragged |  | ||||||
| 				setTimeout(() => { |  | ||||||
| 					const el = document.getElementById(`task-${id}`); |  | ||||||
| 					if (el) el.classList.add('dragging'); |  | ||||||
| 				}, 0); |  | ||||||
| 			})(element); |  | ||||||
|  |  | ||||||
| 			on("dragend", () => { |  | ||||||
| 				draggedTaskId= null; |  | ||||||
|  |  | ||||||
| 				// Remove the styling |  | ||||||
| 				const el = document.getElementById(`task-${id}`); |  | ||||||
| 				if (el) el.classList.remove('dragging'); |  | ||||||
| 			})(element); |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| 	function onDragArea(status) { |  | ||||||
| 		return element => { |  | ||||||
| 			on("dragover", e => { |  | ||||||
| 				e.preventDefault(); |  | ||||||
| 				e.dataTransfer.dropEffect = 'move'; |  | ||||||
|  |  | ||||||
| 				// Add a visual indicator for the drop target |  | ||||||
| 				const column = document.getElementById(`column-${status}`); |  | ||||||
| 				if (column) column.classList.add('drag-over'); |  | ||||||
| 			})(element); |  | ||||||
|  |  | ||||||
| 			on("dragleave", () => { |  | ||||||
| 				// Remove the visual indicator |  | ||||||
| 				const column = document.getElementById(`column-${status}`); |  | ||||||
| 				if (column) column.classList.remove('drag-over'); |  | ||||||
| 			})(element); |  | ||||||
|  |  | ||||||
| 			on("drop", e => { |  | ||||||
| 				e.preventDefault(); |  | ||||||
| 				const id = draggedTaskId; |  | ||||||
| 				if (id) onMoved(id, status); |  | ||||||
| 				// Remove the visual indicator |  | ||||||
| 				const column = document.getElementById(`column-${status}`); |  | ||||||
| 				if (column) column.classList.remove('drag-over'); |  | ||||||
| 			})(element); |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| 	return { onDragable, onDragArea }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Render the component |  | ||||||
| document.body.append( |  | ||||||
| 	el("div", { style: "padding: 20px; background: #f5f5f5; min-height: 100vh;" }).append( |  | ||||||
| 		el(TaskManager) |  | ||||||
| 	), |  | ||||||
| 	el("style", ` |  | ||||||
| 		.task-manager { |  | ||||||
| 			font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; |  | ||||||
| 			max-width: 1200px; |  | ||||||
| 			margin: 0 auto; |  | ||||||
| 			padding: 1rem; |  | ||||||
| 			color: #333; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.app-header { |  | ||||||
| 			display: flex; |  | ||||||
| 			justify-content: space-between; |  | ||||||
| 			align-items: center; |  | ||||||
| 			margin-bottom: 1.5rem; |  | ||||||
| 			flex-wrap: wrap; |  | ||||||
| 			gap: 1rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.app-header h1 { |  | ||||||
| 			margin: 0; |  | ||||||
| 			color: #2d3748; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.app-controls { |  | ||||||
| 			display: flex; |  | ||||||
| 			gap: 1rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.app-controls input, |  | ||||||
| 		.app-controls select { |  | ||||||
| 			padding: 0.5rem; |  | ||||||
| 			border: 1px solid #e2e8f0; |  | ||||||
| 			border-radius: 4px; |  | ||||||
| 			font-size: 0.9rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.new-task-form { |  | ||||||
| 			background: white; |  | ||||||
| 			padding: 1.5rem; |  | ||||||
| 			border-radius: 8px; |  | ||||||
| 			box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); |  | ||||||
| 			margin-bottom: 2rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.form-row { |  | ||||||
| 			display: flex; |  | ||||||
| 			gap: 1rem; |  | ||||||
| 			margin-bottom: 1rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.form-row input { |  | ||||||
| 			flex-grow: 1; |  | ||||||
| 			padding: 0.75rem; |  | ||||||
| 			border: 1px solid #e2e8f0; |  | ||||||
| 			border-radius: 4px; |  | ||||||
| 			font-size: 1rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.form-row select { |  | ||||||
| 			width: 100px; |  | ||||||
| 			padding: 0.75rem; |  | ||||||
| 			border: 1px solid #e2e8f0; |  | ||||||
| 			border-radius: 4px; |  | ||||||
| 			font-size: 1rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.add-btn { |  | ||||||
| 			background: #4a90e2; |  | ||||||
| 			color: white; |  | ||||||
| 			border: none; |  | ||||||
| 			border-radius: 4px; |  | ||||||
| 			padding: 0.75rem 1.5rem; |  | ||||||
| 			font-size: 1rem; |  | ||||||
| 			cursor: pointer; |  | ||||||
| 			transition: background 0.2s ease; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.add-btn:hover { |  | ||||||
| 			background: #3a7bc8; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.new-task-form textarea { |  | ||||||
| 			width: 100%; |  | ||||||
| 			padding: 0.75rem; |  | ||||||
| 			border: 1px solid #e2e8f0; |  | ||||||
| 			border-radius: 4px; |  | ||||||
| 			font-size: 1rem; |  | ||||||
| 			resize: vertical; |  | ||||||
| 			min-height: 80px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-board { |  | ||||||
| 			display: grid; |  | ||||||
| 			grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); |  | ||||||
| 			gap: 1.5rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-column { |  | ||||||
| 			background: #f7fafc; |  | ||||||
| 			border-radius: 8px; |  | ||||||
| 			padding: 1rem; |  | ||||||
| 			min-height: 400px; |  | ||||||
| 			transition: background 0.2s ease; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.column-header { |  | ||||||
| 			margin-top: 0; |  | ||||||
| 			padding-bottom: 0.75rem; |  | ||||||
| 			border-bottom: 2px solid #e2e8f0; |  | ||||||
| 			font-size: 1.25rem; |  | ||||||
| 			color: #2d3748; |  | ||||||
| 			display: flex; |  | ||||||
| 			align-items: center; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-count { |  | ||||||
| 			display: inline-flex; |  | ||||||
| 			justify-content: center; |  | ||||||
| 			align-items: center; |  | ||||||
| 			background: #e2e8f0; |  | ||||||
| 			color: #4a5568; |  | ||||||
| 			border-radius: 50%; |  | ||||||
| 			width: 25px; |  | ||||||
| 			height: 25px; |  | ||||||
| 			font-size: 0.875rem; |  | ||||||
| 			margin-left: 0.5rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.column-tasks { |  | ||||||
| 			margin-top: 1rem; |  | ||||||
| 			display: flex; |  | ||||||
| 			flex-direction: column; |  | ||||||
| 			gap: 1rem; |  | ||||||
| 			min-height: 200px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-card { |  | ||||||
| 			background: white; |  | ||||||
| 			border-radius: 6px; |  | ||||||
| 			padding: 1rem; |  | ||||||
| 			box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); |  | ||||||
| 			cursor: grab; |  | ||||||
| 			transition: transform 0.2s ease, box-shadow 0.2s ease; |  | ||||||
| 			position: relative; |  | ||||||
| 			border-left: 4px solid #ccc; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-card:hover { |  | ||||||
| 			transform: translateY(-3px); |  | ||||||
| 			box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-card.dragging { |  | ||||||
| 			opacity: 0.5; |  | ||||||
| 			cursor: grabbing; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-card.priority-low { |  | ||||||
| 			border-left-color: #38b2ac; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-card.priority-medium { |  | ||||||
| 			border-left-color: #ecc94b; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-card.priority-high { |  | ||||||
| 			border-left-color: #e53e3e; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-header { |  | ||||||
| 			display: flex; |  | ||||||
| 			justify-content: space-between; |  | ||||||
| 			align-items: flex-start; |  | ||||||
| 			margin-bottom: 0.5rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-title { |  | ||||||
| 			margin: 0; |  | ||||||
| 			font-size: 1.1rem; |  | ||||||
| 			color: #2d3748; |  | ||||||
| 			word-break: break-word; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-description { |  | ||||||
| 			margin: 0.5rem 0; |  | ||||||
| 			font-size: 0.9rem; |  | ||||||
| 			color: #4a5568; |  | ||||||
| 			word-break: break-word; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-actions { |  | ||||||
| 			display: flex; |  | ||||||
| 			gap: 0.5rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.edit-btn, |  | ||||||
| 		.delete-btn { |  | ||||||
| 			background: none; |  | ||||||
| 			border: none; |  | ||||||
| 			font-size: 1rem; |  | ||||||
| 			cursor: pointer; |  | ||||||
| 			width: 24px; |  | ||||||
| 			height: 24px; |  | ||||||
| 			display: inline-flex; |  | ||||||
| 			justify-content: center; |  | ||||||
| 			align-items: center; |  | ||||||
| 			border-radius: 50%; |  | ||||||
| 			color: #718096; |  | ||||||
| 			transition: background 0.2s ease, color 0.2s ease; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.edit-btn:hover { |  | ||||||
| 			background: #edf2f7; |  | ||||||
| 			color: #4a5568; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.delete-btn:hover { |  | ||||||
| 			background: #fed7d7; |  | ||||||
| 			color: #e53e3e; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-meta { |  | ||||||
| 			display: flex; |  | ||||||
| 			justify-content: space-between; |  | ||||||
| 			align-items: center; |  | ||||||
| 			margin-top: 0.75rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.priority-badge { |  | ||||||
| 			font-size: 0.75rem; |  | ||||||
| 			padding: 0.2rem 0.5rem; |  | ||||||
| 			border-radius: 4px; |  | ||||||
| 			font-weight: 500; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.priority-badge.priority-low { |  | ||||||
| 			background: #e6fffa; |  | ||||||
| 			color: #2c7a7b; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.priority-badge.priority-medium { |  | ||||||
| 			background: #fefcbf; |  | ||||||
| 			color: #975a16; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.priority-badge.priority-high { |  | ||||||
| 			background: #fed7d7; |  | ||||||
| 			color: #c53030; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.drag-over { |  | ||||||
| 			background: #f0f9ff; |  | ||||||
| 			border: 2px dashed #4a90e2; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-edit-form { |  | ||||||
| 			display: flex; |  | ||||||
| 			flex-direction: column; |  | ||||||
| 			gap: 0.75rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-title-input, |  | ||||||
| 		.task-desc-input { |  | ||||||
| 			width: 100%; |  | ||||||
| 			padding: 0.5rem; |  | ||||||
| 			border: 1px solid #e2e8f0; |  | ||||||
| 			border-radius: 4px; |  | ||||||
| 			font-size: 0.9rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-desc-input { |  | ||||||
| 			min-height: 60px; |  | ||||||
| 			resize: vertical; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.task-edit-actions { |  | ||||||
| 			display: flex; |  | ||||||
| 			justify-content: flex-end; |  | ||||||
| 			gap: 0.5rem; |  | ||||||
| 			margin-top: 0.5rem; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.cancel-btn, |  | ||||||
| 		.save-btn { |  | ||||||
| 			padding: 0.4rem 0.75rem; |  | ||||||
| 			border-radius: 4px; |  | ||||||
| 			font-size: 0.9rem; |  | ||||||
| 			cursor: pointer; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.cancel-btn { |  | ||||||
| 			background: #edf2f7; |  | ||||||
| 			color: #4a5568; |  | ||||||
| 			border: 1px solid #e2e8f0; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		.save-btn { |  | ||||||
| 			background: #4a90e2; |  | ||||||
| 			color: white; |  | ||||||
| 			border: none; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		@media (max-width: 768px) { |  | ||||||
| 			.app-header { |  | ||||||
| 				flex-direction: column; |  | ||||||
| 				align-items: flex-start; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			.app-controls { |  | ||||||
| 				width: 100%; |  | ||||||
| 				flex-direction: column; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			.form-row { |  | ||||||
| 				flex-direction: column; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			.task-board { |  | ||||||
| 				grid-template-columns: 1fr; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	`) |  | ||||||
| ); |  | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| // Example of reactive element marker | // Example of reactive element marker | ||||||
| <!--<dde:mark type="reactive" component="<component-name>">--> | <!--<dde:mark type="reactive" source="...">--> | ||||||
| <!-- content that updates when signal changes --> | <!-- content that updates when signal changes --> | ||||||
| <!--</dde:mark>--> | <!--</dde:mark>--> | ||||||
|   | |||||||
| @@ -1,28 +1,13 @@ | |||||||
| import { el, on } from "deka-dom-el"; | import { el, on } from "deka-dom-el"; | ||||||
| function allLifecycleEvents(){ | const paragraph= el("p", "See lifecycle events in console.", | ||||||
| 	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( | ||||||
| 	el(allLifecycleEvents), | 	paragraph, | ||||||
| 	el("button", "Remove Element", on("click", function(){ | 	el("button", "Remove", on("click", ()=> paragraph.remove())) | ||||||
| 		this.previousSibling.remove(); |  | ||||||
| 	})) |  | ||||||
| ); | ); | ||||||
|  |  | ||||||
| /** @param {Partial<CustomEvent>} event */ | /** @param {Partial<CustomEvent>} event */ | ||||||
|   | |||||||
| @@ -7,20 +7,20 @@ function HelloWorld({ emoji = "🚀" }) { | |||||||
| 	const clicks = S(0); | 	const clicks = S(0); | ||||||
|  |  | ||||||
| 	return el().append( | 	return el().append( | ||||||
| 		// PART 2: Bind state to UI elements | 	// PART 2: Bind state to UI elements | ||||||
| 		el("p", { | 	el("p", { | ||||||
| 			className: "greeting", | 		className: "greeting", | ||||||
| 			// This paragraph automatically updates when clicks changes | 		// This paragraph automatically updates when clicks changes | ||||||
| 			textContent: S(() => `Hello World ${emoji.repeat(clicks.get())}`) | 		textContent: S(() => `Hello World ${emoji.repeat(clicks.get())}`) | ||||||
| 		}), | 	}), | ||||||
|  |  | ||||||
| 		// PART 3: Update state in response to events | 	// PART 3: Update state in response to events | ||||||
| 		el("button", { | 	el("button", { | ||||||
| 			type: "button", | 		type: "button", | ||||||
| 			textContent: "Add emoji", | 		textContent: "Add emoji", | ||||||
| 			// When clicked, update the state | 		// When clicked, update the state | ||||||
| 			onclick: () => clicks.set(clicks.get() + 1) | 		onclick: () => clicks.set(clicks.get() + 1) | ||||||
| 		}) | 	}) | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ document.body.append( | |||||||
| 	padding: 1em; | 	padding: 1em; | ||||||
| 	margin: 1em; | 	margin: 1em; | ||||||
| } | } | ||||||
| `.trim()) | 		`.trim()) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| export function CounterStandard() { | export function CounterStandard() { | ||||||
|   | |||||||
| @@ -13,20 +13,18 @@ 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 { signal } = scope; | 	const pageS = routerSignal(S); | ||||||
| 	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 todosFilteredS = S(()=> { | 	const filteredTodosS = 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); |  | ||||||
|  |  | ||||||
| 	/** @type {ddeElementAddon<HTMLInputElement>} */ | 	/** @type {ddeElementAddon<HTMLInputElement>} */ | ||||||
| 	const onToggleAll = on("change", event => { | 	const onToggleAll = on("change", event => { | ||||||
| @@ -75,7 +73,7 @@ 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(todosFilteredS, filteredTodos => filteredTodos.map(todo => | 					S.el(filteredTodosS, filteredTodos => filteredTodos.map(todo => | ||||||
| 						memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) | 						memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) | ||||||
| 					) | 					) | ||||||
| 				) | 				) | ||||||
| @@ -85,7 +83,7 @@ function Todos(){ | |||||||
| 			? el() | 			? el() | ||||||
| 			: el("footer", { className: "footer" }).append( | 			: el("footer", { className: "footer" }).append( | ||||||
| 				el("span", { className: "todo-count" }).append( | 				el("span", { className: "todo-count" }).append( | ||||||
| 					noOfLeft() | 					noOfLeft(todos) | ||||||
| 				), | 				), | ||||||
| 				memo("filters", ()=> | 				memo("filters", ()=> | ||||||
| 					el("ul", { className: "filters" }).append( | 					el("ul", { className: "filters" }).append( | ||||||
| @@ -100,18 +98,15 @@ function Todos(){ | |||||||
| 						) | 						) | ||||||
| 					), | 					), | ||||||
| 				), | 				), | ||||||
| 				todos.length - todosRemainingS.get() === 0 | 				!todos.some(todo => todo.completed) | ||||||
| 					? el() | 					? el() | ||||||
| 					: memo("delete", () => | 					: el("button", { textContent: "Clear completed", className: "clear-completed" }, onClearCompleted) | ||||||
| 						el("button", |  | ||||||
| 							{ textContent: "Clear completed", className: "clear-completed" }, |  | ||||||
| 							onClearCompleted) |  | ||||||
| 					) |  | ||||||
| 			) | 			) | ||||||
| 		) | 		) | ||||||
| 	); | 	); | ||||||
| 	function noOfLeft(){ | 	/** @param {Todo[]} todos */ | ||||||
| 		const length = todosRemainingS.get(); | 	function noOfLeft(todos){ | ||||||
|  | 		const { length }= todos.filter(todo => !todo.completed); | ||||||
| 		return el("strong").append( | 		return el("strong").append( | ||||||
| 			length + " ", | 			length + " ", | ||||||
| 			length === 1 ? "item left" : "items left" | 			length === 1 ? "item left" : "items left" | ||||||
| @@ -199,7 +194,7 @@ function TodoItem({ id, title, completed }) { | |||||||
| 				checked: completed | 				checked: completed | ||||||
| 			}, onToggleCompleted), | 			}, onToggleCompleted), | ||||||
| 			el("label", { textContent: title }, onStartEdit), | 			el("label", { textContent: title }, onStartEdit), | ||||||
| 			el("button", { ariaLabel: "Delete todo", className: "destroy" }, onDelete) | 			el("button", { className: "destroy" }, onDelete) | ||||||
| 		), | 		), | ||||||
| 		S.el(isEditing, editing => !editing | 		S.el(isEditing, editing => !editing | ||||||
| 			? el() | 			? el() | ||||||
| @@ -333,7 +328,6 @@ 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; | ||||||
| @@ -342,10 +336,9 @@ function todosSignal(){ | |||||||
| /** | /** | ||||||
|  * Creates a signal for managing route state |  * Creates a signal for managing route state | ||||||
|  * |  * | ||||||
|  * @param {typeof S} signal - The signal constructor from a library |  * @param {typeof S} signal - The signal constructor | ||||||
|  * @param {AbortSignal} abortSignal |  | ||||||
|  */ |  */ | ||||||
| function routerSignal(signal, abortSignal){ | function routerSignal(signal){ | ||||||
| 	const initial = location.hash.replace("#", "") || "all"; | 	const initial = location.hash.replace("#", "") || "all"; | ||||||
| 	const out = signal(initial, { | 	const out = signal(initial, { | ||||||
| 		/** | 		/** | ||||||
| @@ -354,16 +347,15 @@ function routerSignal(signal, abortSignal){ | |||||||
| 		 */ | 		 */ | ||||||
| 		set(hash){ | 		set(hash){ | ||||||
| 			location.hash = hash; | 			location.hash = hash; | ||||||
| 			//this.value = hash; | 			this.value = hash; | ||||||
| 		}, | 		} | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	// Setup hash change listener | 	// Setup hash change listener | ||||||
| 	window.addEventListener("hashchange", () => { | 	window.addEventListener("hashchange", () => { | ||||||
| 		const hash = location.hash.replace("#", "") || "all"; | 		const hash = location.hash.replace("#", "") || "all"; | ||||||
| 		//S.action(out, "set", /** @type {"all"|"active"|"completed"} */(hash)); | 		S.action(out, "set", /** @type {"all"|"active"|"completed"} */(hash)); | ||||||
| 		out.set(hash); | 	}); | ||||||
| 	}, { signal: abortSignal }); |  | ||||||
|  |  | ||||||
| 	return out; | 	return out; | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								docs/components/getLibraryUrl.css.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/components/getLibraryUrl.css.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | import { styles } from "../ssr.js"; | ||||||
|  |  | ||||||
|  | styles.css` | ||||||
|  | #library-url-form { | ||||||
|  | 	display: flex; | ||||||
|  | 	flex-flow: column nowrap; | ||||||
|  | } | ||||||
|  | `; | ||||||
| @@ -1,83 +0,0 @@ | |||||||
| import { styles } from "../ssr.js"; |  | ||||||
|  |  | ||||||
| styles.css` |  | ||||||
| #library-url-form { |  | ||||||
| 	display: flex; |  | ||||||
| 	flex-flow: column nowrap; |  | ||||||
| 	gap: 1rem; |  | ||||||
| 	padding: 1.5rem; |  | ||||||
| 	border-radius: var(--border-radius); |  | ||||||
| 	background-color: var(--bg-sidebar); |  | ||||||
| 	box-shadow: var(--shadow); |  | ||||||
| 	border: 1px solid var(--border); |  | ||||||
| 	margin: 1.5rem 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #library-url-form .selectors { |  | ||||||
| 	display: flex; |  | ||||||
| 	flex-flow: row wrap; |  | ||||||
| 	gap: 0.75rem; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #library-url-form output { |  | ||||||
| 	display: flex; |  | ||||||
| 	flex-flow: column nowrap; |  | ||||||
| 	gap: 0.75rem; |  | ||||||
| 	margin-top: 0.5rem; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #library-url-form output p { |  | ||||||
| 	font-weight: 500; |  | ||||||
| 	margin: 0.25rem 0; |  | ||||||
| 	color: var(--text-light); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #library-url-form .url-title { |  | ||||||
| 	display: flex; |  | ||||||
| 	align-items: center; |  | ||||||
| 	gap: 0.5rem; |  | ||||||
| 	margin-bottom: -0.25rem; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #library-url-form .url-title strong { |  | ||||||
| 	font-family: var(--font-mono); |  | ||||||
| 	font-size: 0.95rem; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #library-url-form .url-title span { |  | ||||||
| 	color: var(--text-light); |  | ||||||
| 	font-size: 0.9rem; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #library-url-form .code { |  | ||||||
| 	margin-bottom: 1rem; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #library-url-form .info-text { |  | ||||||
| 	font-size: 0.9rem; |  | ||||||
| 	font-style: italic; |  | ||||||
| 	margin-top: 1rem; |  | ||||||
| 	color: var(--text-light); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @media (max-width: 768px) { |  | ||||||
| 	#library-url-form .selectors { |  | ||||||
| 		flex-direction: column; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	#library-url-form select { |  | ||||||
| 		width: 100%; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| `; |  | ||||||
|  |  | ||||||
| import { el } from "deka-dom-el"; |  | ||||||
| import { ireland } from "./ireland.html.js"; |  | ||||||
|  |  | ||||||
| export function getLibraryUrl({ page_id }){ |  | ||||||
| 	return el(ireland, { |  | ||||||
| 		src: new URL("./getLibraryUrl.js.js", import.meta.url), |  | ||||||
| 		exportName: "getLibraryUrl", |  | ||||||
| 		page_id, |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
| @@ -8,13 +8,6 @@ const url_base= { | |||||||
| export function getLibraryUrl(){ | export function getLibraryUrl(){ | ||||||
| 	const lib= S([ "esm", "-with-signals", ".min" ]); | 	const lib= S([ "esm", "-with-signals", ".min" ]); | ||||||
| 	const url= S(()=> url_base.jsdeka+lib.get().join("")); | 	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 => { | 	const onSubmit= on("submit", ev => { | ||||||
| 		ev.preventDefault(); | 		ev.preventDefault(); | ||||||
| 		const form= new FormData(/** @type {HTMLFormElement} */ (ev.target)); | 		const form= new FormData(/** @type {HTMLFormElement} */ (ev.target)); | ||||||
| @@ -27,66 +20,56 @@ export function getLibraryUrl(){ | |||||||
| 	const onChangeSubmit= on("change", | 	const onChangeSubmit= on("change", | ||||||
| 		ev=> /** @type {HTMLSelectElement} */(ev.target).form.requestSubmit() | 		ev=> /** @type {HTMLSelectElement} */(ev.target).form.requestSubmit() | ||||||
| 	); | 	); | ||||||
|  | 	const onCopy= on("click", ev => { | ||||||
|  | 		const code= /** @type {HTMLDivElement} */ (ev.target).previousElementSibling; | ||||||
|  | 		navigator.clipboard.writeText(code.textContent); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
| 	return el("form", { id: "library-url-form" }, onSubmit).append( | 	return el("form", { id: "library-url-form" }, onSubmit).append( | ||||||
| 		el("h4", "Select your preferred library format:"), | 		el("select", { name: "module" }, onChangeSubmit).append( | ||||||
| 		el("div", { className: "selectors" }).append( | 			el("option", { value: "esm", textContent: "ESM — modern JavaScript module" }, | ||||||
| 			el("select", { name: "module" }, onChangeSubmit, | 				isSelected(lib.get()[0])), | ||||||
| 				on.defer(select => select.value = lib.get()[0]), | 			el("option", { value: "iife", textContent: "IIFE — legacy JavaScript with DDE global variable" }, | ||||||
| 			).append( | 				isSelected(lib.get()[0])), | ||||||
| 				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).append( | ||||||
| 			), | 			el("option", { value: "", textContent: "only DOM part" }, | ||||||
| 			el("select", { name: "what" }, onChangeSubmit, | 				isSelected(lib.get()[1])), | ||||||
| 				on.defer(select => select.value = lib.get()[1]), | 			el("option", { value: "-with-signals", textContent: "DOM part and signals library" }, | ||||||
| 			).append( | 				isSelected(lib.get()[1])), | ||||||
| 				el("option", { value: "", textContent: "DOM part only" }), | 		), | ||||||
| 				el("option", { value: "-with-signals", textContent: "DOM + signals" }), | 		el("select", { name: "minified" }, onChangeSubmit).append( | ||||||
| 			), | 			el("option", { value: "", textContent: "unminified" }, | ||||||
| 			el("select", { name: "minified" }, onChangeSubmit, | 				isSelected(lib.get()[2])), | ||||||
| 				on.defer(select => select.value = lib.get()[2]), | 			el("option", { value: ".min", textContent: "minified" }, | ||||||
| 			).append( | 				isSelected(lib.get()[2])), | ||||||
| 				el("option", { value: "", textContent: "Unminified" }), |  | ||||||
| 				el("option", { value: ".min", textContent: "Minified" }), |  | ||||||
| 			), |  | ||||||
| 		), | 		), | ||||||
| 		el("output").append( | 		el("output").append( | ||||||
| 			el("div", { className: "url-title" }).append( | 			el("p", "Library URL:"), | ||||||
| 				el("strong", "JavaScript:"), | 			el("div", { className: "code", dataJs: "done", tabIndex: 0 }).append( | ||||||
| 				el("span", urlLabel), | 				el("code").append( | ||||||
|  | 					el("pre", S(()=> url.get()+".js")), | ||||||
|  | 				), | ||||||
|  | 				el("button", { | ||||||
|  | 					className: "copy-button", | ||||||
|  | 					textContent: "Copy", | ||||||
|  | 					ariaLabel: "Copy code to clipboard", | ||||||
|  | 				}, onCopy), | ||||||
| 			), | 			), | ||||||
| 			el(code, { value: S(()=> url.get()+".js") }), | 			el("p", "Library type definition:"), | ||||||
| 			el("div", { className: "url-title" }).append( | 			el("div", { className: "code", dataJs: "done", tabIndex: 0 }).append( | ||||||
| 				el("strong", "TypeScript definition:") | 				el("code").append( | ||||||
|  | 					el("pre", S(()=> url.get()+".d.ts")), | ||||||
|  | 				), | ||||||
|  | 				el("button", { | ||||||
|  | 					className: "copy-button", | ||||||
|  | 					textContent: "Copy", | ||||||
|  | 					ariaLabel: "Copy code to clipboard", | ||||||
|  | 				}, onCopy), | ||||||
| 			), | 			), | ||||||
| 			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 isSelected(value){ | ||||||
| function code({ value }){ | 	return element=> element.selected= element.value===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,33 +1,3 @@ | |||||||
| 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"; | ||||||
| @@ -51,17 +21,10 @@ 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( | 	queue(import(path).then(module => { | ||||||
| 		import(path) | 		const component = module[exportName]; | ||||||
| 		.then(module => { | 		element.replaceWith(el(component, props, mark(id))); | ||||||
| 			const component = module[exportName]; | 	})); | ||||||
| 			const content= el(component, props, mark(id)); |  | ||||||
| 			element.replaceWith(content); |  | ||||||
| 			content.querySelectorAll("input, textarea, button") |  | ||||||
| 				.forEach(el=> el.disabled= true); |  | ||||||
| 		}) |  | ||||||
| 		.catch(console.error) |  | ||||||
| 	); |  | ||||||
|  |  | ||||||
| 	if(!componentsRegistry.size) | 	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 (accepts string, number or signal)", | 			" — simple element containing only text", | ||||||
| 		), | 		), | ||||||
| 		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", | ||||||
| 		), | 		) | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ export function mnemonic(){ | |||||||
| 		el("li").append( | 		el("li").append( | ||||||
| 			el("code", "S.clear(...<signals>)"), | 			el("code", "S.clear(...<signals>)"), | ||||||
| 			" — off and clear signals (most of the time it is not needed as reactive ", | 			" — off and clear signals (most of the time it is not needed as reactive ", | ||||||
| 			"attributes and elements are handled automatically)", | 			"attributes and elements are cleared automatically)", | ||||||
| 		), | 		), | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -37,14 +37,3 @@ 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 var(--primary-light); | 	outline: 3px solid hsl(231, 48%, 70%); | ||||||
| 	outline-offset: 2px; | 	outline-offset: 2px; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -207,20 +207,6 @@ figure { | |||||||
| 		max-width: 100%; | 		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 { | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import "./components/getLibraryUrl.html.js"; | import "./components/getLibraryUrl.css.js"; | ||||||
| import { t, T } from "./utils/index.js"; | import { t, T } from "./utils/index.js"; | ||||||
| export const info= { | export const info= { | ||||||
| 	href: "./", | 	href: "./", | ||||||
| @@ -12,7 +12,7 @@ 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"; | import { ireland } from "./components/ireland.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= { | ||||||
| @@ -82,7 +82,7 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 		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 ${el("strong", "is not something new and/or special to dd<el>")}. It’s based on ${el("a", { | 			approach ${el("strong", "is not")} something new and/or special to dd<el>. It’s based on ${el("a", { | ||||||
| 				textContent: "MVC", ...references.w_mvc })} (${el("a", { textContent: "MVVM", ...references.w_mvv })}), | 				textContent: "MVC", ...references.w_mvc })} (${el("a", { textContent: "MVVM", ...references.w_mvv })}), | ||||||
| 			but is there presented in simpler form. | 			but is there presented in simpler form. | ||||||
| 		`), | 		`), | ||||||
| @@ -101,29 +101,13 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 		el(h3, t`Getting Started`), | 		el(h3, t`Getting Started`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| 			There are multiple ways to include dd<el> in your project. You can use npm for a full development setup, | 			To get builded dd<el> to be used immediately in your browser, you can download the latest version from: | ||||||
| 			or directly include it from a CDN for quick prototyping. |  | ||||||
| 		`), | 		`), | ||||||
| 		el("h4", "npm installation"), | 		el(ireland, { | ||||||
| 		el(code, { content: "npm install deka-dom-el --save", language: "shell", page_id }), | 			src: fileURL("./components/getLibraryUrl.js.js"), | ||||||
| 		el("h4", "CDN / Direct Script Usage"), | 			exportName: "getLibraryUrl", | ||||||
| 		el("p").append(T` | 			page_id, | ||||||
| 			Use the interactive selector below to choose your preferred format: | 		}), | ||||||
| 		`), |  | ||||||
| 		el(getLibraryUrl, { page_id }), |  | ||||||
| 		el("div", { className: "note" }).append( |  | ||||||
| 			el("p").append(T` |  | ||||||
| 				Based on your selection, you can use dd<el> in your project like this: |  | ||||||
| 			`), |  | ||||||
| 			el(code, { content: ` |  | ||||||
| 				// ESM format (modern JavaScript with import/export) |  | ||||||
| 				import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.min.js"; |  | ||||||
|  |  | ||||||
| 				// Or with IIFE format (creates a global DDE object) |  | ||||||
| 				// <script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/iife-with-signals.min.js"></script> |  | ||||||
| 				const { el, on } = DDE; |  | ||||||
| 			`, language: "js", page_id }), |  | ||||||
| 		), |  | ||||||
|  |  | ||||||
| 		el(h3, t`How to Use This Documentation`), | 		el(h3, t`How to Use This Documentation`), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -154,10 +138,6 @@ export function page({ pkg, info }){ | |||||||
| 				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. | ||||||
|   | |||||||
| @@ -3,7 +3,10 @@ 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 { scrollTop } from "../components/scrollTop.html.js"; | import { ireland } from "../components/ireland.html.js"; | ||||||
|  | import "../components/scrollTop.css.js"; | ||||||
|  | /** @param {string} url */ | ||||||
|  | const fileURL= url=> new URL(url, import.meta.url); | ||||||
|  |  | ||||||
| /** @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 }){ | ||||||
| @@ -30,6 +33,6 @@ export function simplePage({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		// Scroll to top button | 		// Scroll to top button | ||||||
| 		el(scrollTop), | 		el(ireland, { src: fileURL("../components/scrollTop.js.js"), exportName: "scrollTop" }) | ||||||
| 	)); | 	)); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -208,6 +208,6 @@ export function page({ pkg, info }){ | |||||||
| 			`), | 			`), | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(mnemonic), | 		el(mnemonic) | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -136,7 +136,7 @@ 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`), // TODO: add example? | 				el("li", t`Capture element references`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -156,7 +156,7 @@ export function page({ pkg, info }){ | |||||||
| 		`), | 		`), | ||||||
| 		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 and component patterns: | 				"custom element", ...references.mdn_customElements })} lifecycle callbacks: | ||||||
| 		`), | 		`), | ||||||
| 		el("div", { className: "function-table" }).append( | 		el("div", { className: "function-table" }).append( | ||||||
| 			el("dl").append( | 			el("dl").append( | ||||||
| @@ -199,19 +199,6 @@ export function page({ pkg, info }){ | |||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		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 | ||||||
|   | |||||||
| @@ -277,72 +277,7 @@ export function page({ pkg, info }){ | |||||||
| 			`), | 			`), | ||||||
| 			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, { page_id, content: ` |  | ||||||
| 					const onFormSubmit = on("submit", e => { |  | ||||||
| 						e.preventDefault(); |  | ||||||
| 						const formData = new FormData(e.currentTarget); |  | ||||||
| 						// this can be sent to a server |  | ||||||
| 						// or processed locally |  | ||||||
| 						// e.g.: console.log(Object.fromEntries(formData)) |  | ||||||
| 					}); |  | ||||||
| 					// … |  | ||||||
| 					return el("form", null, onFormSubmit).append( |  | ||||||
| 						// … |  | ||||||
| 					); |  | ||||||
| 				` }) |  | ||||||
| 			), |  | ||||||
|  |  | ||||||
| 			el("div", { className: "tab", dataTab: "variables" }).append( |  | ||||||
| 				el("h4", t`We can use variables without signals`), |  | ||||||
| 				el("p", t`We use this when we dont’t need to reflect changes in the elsewhere (UI).`), |  | ||||||
| 				el(code, { page_id, content: ` |  | ||||||
| 					let canSubmit = false; |  | ||||||
|  |  | ||||||
| 					const onFormSubmit = on("submit", e => { |  | ||||||
| 						e.preventDefault(); |  | ||||||
| 						if(!canSubmit) return; // some message |  | ||||||
| 						// … |  | ||||||
| 					}); |  | ||||||
| 					const onAllowSubmit = on("click", e => { |  | ||||||
| 						canSubmit = true; |  | ||||||
| 					}); |  | ||||||
| 				`}), |  | ||||||
| 			), |  | ||||||
|  |  | ||||||
| 			el("div", { className: "tab", dataTab: "state" }).append( |  | ||||||
| 				el("h4", t`Using signals`), |  | ||||||
| 				el("p", t`We use this when we need to reflect changes for example in the UI (e.g. enable/disable |  | ||||||
| 					buttons).`), |  | ||||||
| 				el(code, { page_id, content: ` |  | ||||||
| 					const canSubmit = S(false); |  | ||||||
|  |  | ||||||
| 					const onFormSubmit = on("submit", e => { |  | ||||||
| 						e.preventDefault(); |  | ||||||
| 						// … |  | ||||||
| 					}); |  | ||||||
| 					const onAllowSubmit = on("click", e => { |  | ||||||
| 						canSubmit.set(true); |  | ||||||
| 					}); |  | ||||||
|  |  | ||||||
| 					return el("form", null, onFormSubmit).append( |  | ||||||
| 						// ... |  | ||||||
| 						el("button", { textContent: "Allow Submit", type: "button" }, onAllowSubmit), |  | ||||||
| 						el("button", { disabled: S(()=> !canSubmit), textContent: "Submit" }) |  | ||||||
| 					); |  | ||||||
| 				`}), |  | ||||||
| 			), |  | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el("div", { className: "troubleshooting" }).append( | 		el("div", { className: "troubleshooting" }).append( | ||||||
| @@ -363,6 +298,6 @@ export function page({ pkg, info }){ | |||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		el(mnemonic), | 		el(mnemonic) | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -55,7 +55,7 @@ export function page({ pkg, info }){ | |||||||
| 				el(MyComponent); | 				el(MyComponent); | ||||||
|  |  | ||||||
| 				function MyComponent() { | 				function MyComponent() { | ||||||
| 					// 2. access the host element (or other scope related values) | 					// 2. access the host element | ||||||
| 					const { host } = scope; | 					const { host } = scope; | ||||||
|  |  | ||||||
| 					// 3. Add behavior to host | 					// 3. Add behavior to host | ||||||
|   | |||||||
| @@ -80,7 +80,8 @@ export function page({ pkg, info }){ | |||||||
| 			el("li", t`listeners: A Set of functions called when the signal value changes`), | 			el("li", t`listeners: A Set of functions called when the signal value changes`), | ||||||
| 			el("li", t`actions: Custom actions that can be performed on the signal`), | 			el("li", t`actions: Custom actions that can be performed on the signal`), | ||||||
| 			el("li", t`onclear: Functions to run when the signal is cleared`), | 			el("li", t`onclear: Functions to run when the signal is cleared`), | ||||||
| 			el("li", t`host: Reference to the host element/scope in which the signal was created`), | 			el("li", t`host: Reference to the host element/scope`), | ||||||
|  | 			el("li", t`defined: Stack trace information for debugging`), | ||||||
| 			el("li", t`readonly: Boolean flag indicating if the signal is read-only`) | 			el("li", t`readonly: Boolean flag indicating if the signal is read-only`) | ||||||
| 		), | 		), | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -113,13 +114,7 @@ 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 | 			el("li", t`That the signal is actually connected to the DOM element (check your S.el or attribute binding code)`) | ||||||
| 				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"), page_id }), | ||||||
|  |  | ||||||
|   | |||||||
| @@ -101,23 +101,21 @@ export function page({ pkg, info }){ | |||||||
| 		`), | 		`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			// Signal for current route (all/active/completed) | 			// Signal for current route (all/active/completed) | ||||||
| 			const { signal } = scope; | 			const pageS = routerSignal(S); | ||||||
| 			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 todosFilteredS = S(()=> { | 			const filteredTodosS = 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); |  | ||||||
| 		`, page_id }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("p").append(T` | 		el("p").append(T` | ||||||
| @@ -202,7 +200,6 @@ 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; | ||||||
| @@ -225,19 +222,19 @@ export function page({ pkg, info }){ | |||||||
| 		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 todosFilteredS = S(()=> { | 			const filteredTodosS = 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(todosFilteredS, filteredTodos => filteredTodos.map(todo => | 				S.el(filteredTodosS, filteredTodos => filteredTodos.map(todo => | ||||||
| 					memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) | 					memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| @@ -337,7 +334,7 @@ export function page({ pkg, info }){ | |||||||
| 		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(todosFilteredS, filteredTodos => filteredTodos.map(todo => | 				S.el(filteredTodosS, filteredTodos => filteredTodos.map(todo => | ||||||
| 					memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) | 					memo(todo.id, ()=> el(TodoItem, todo, onDelete, onEdit))) | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| @@ -546,13 +543,11 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 		el("h4", t`Conditional Clear Completed Button`), | 		el("h4", t`Conditional Clear Completed Button`), | ||||||
| 		el(code, { content: ` | 		el(code, { content: ` | ||||||
| 			todos.length - todosRemainingS.get() === 0 | 			S.el(S(() => todosS.get().some(todo => todo.completed)), | ||||||
| 				? el() | 				hasTodosCompleted=> hasTodosCompleted | ||||||
| 				: memo("delete", () => | 				? el("button", { textContent: "Clear completed", className: "clear-completed" }, onClearCompleted) | ||||||
| 					el("button", | 				: el() | ||||||
| 						{ textContent: "Clear completed", className: "clear-completed" }, | 			) | ||||||
| 						onClearCompleted) |  | ||||||
| 				) |  | ||||||
| 		`, page_id }), | 		`, page_id }), | ||||||
|  |  | ||||||
| 		el("div", { className: "note" }).append( | 		el("div", { className: "note" }).append( | ||||||
| @@ -630,7 +625,7 @@ export function page({ pkg, info }){ | |||||||
| 				${el("strong", "Declarative Class Management:")} Using the classList property for cleaner class handling | 				${el("strong", "Declarative Class Management:")} Using the classList property for cleaner class handling | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Focus Management:")} Reliable input focus with setTimeout | 				${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 | ||||||
|   | |||||||
| @@ -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,16 +25,6 @@ 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", |  | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -43,8 +33,8 @@ export function page({ pkg, info }){ | |||||||
| 	const page_id= info.id; | 	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. | ||||||
| 		`), | 		`), | ||||||
|  |  | ||||||
| @@ -71,18 +61,14 @@ export function page({ pkg, info }){ | |||||||
| 					${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", "Flexibility:")} Using what you need, whether that’s plain DOM elements, event | 					${el("strong", "Functional Composition:")} Building UIs through function composition rather than | ||||||
| 					handling, or signals for reactivity | 					inheritance | ||||||
| 				`), |  | ||||||
| 				el("li").append(T` |  | ||||||
| 					${el("strong", "Functional Composition:")} Building UIs through function composition |  | ||||||
| 				`), | 				`), | ||||||
| 				el("li").append(T` | 				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 only when | 					${el("strong", "Targeted Reactivity:")} Using signals for efficient, fine-grained updates | ||||||
| 					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 | ||||||
| @@ -91,15 +77,11 @@ export function page({ pkg, info }){ | |||||||
| 		), | 		), | ||||||
|  |  | ||||||
| 		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`), | ||||||
| @@ -135,18 +117,16 @@ export function page({ pkg, info }){ | |||||||
| 		`), | 		`), | ||||||
| 		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 | 				${el("strong", "Replace query selectors:")}: Replace getElementById/querySelector with direct references to elements | ||||||
| 				to elements |  | ||||||
| 			`), | 			`), | ||||||
| 			el("li").append(T` | 			el("li").append(T` | ||||||
| 				${el("strong", "Convert imperative updates")}: Replace manual DOM updates with declarative signal | 				${el("strong", "Convert imperative updates:")}: Replace manual DOM updates with declarative signal bindings | ||||||
| 				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: ` | ||||||
| @@ -177,7 +157,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`), | ||||||
| @@ -191,22 +171,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`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
|  |  | ||||||
| @@ -220,7 +200,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`) | ||||||
| @@ -232,45 +212,19 @@ export function page({ pkg, info }){ | |||||||
| 			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 |  | ||||||
| 				`) | 				`) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| @@ -279,19 +233,19 @@ export function page({ pkg, info }){ | |||||||
| 			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` | ||||||
| @@ -309,7 +263,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`), | ||||||
| @@ -325,46 +279,46 @@ 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", "VanJS"), | 					el("th", "React"), | ||||||
| 					el("th", "Solid"), | 					el("th", "Vue"), | ||||||
| 					el("th", "Alpine") | 					el("th", "Svelte") | ||||||
| 				) | 				) | ||||||
| 			), | 			), | ||||||
| 			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", "✅") | 					el("td", "⚠️ SFC needs compilation"), | ||||||
|  | 					el("td", "❌ Requires compilation") | ||||||
| 				), | 				), | ||||||
| 				el("tr").append( | 				el("tr").append( | ||||||
| 					el("td", "Bundle Size (minified)"), | 					el("td", "Bundle Size (minimal)"), | ||||||
| 					el("td", "~14kb"), | 					el("td", "~10-15kb"), | ||||||
| 					el("td", "~3kb"), | 					el("td", "~40kb+"), | ||||||
| 					el("td", "~20kb"), | 					el("td", "~33kb+"), | ||||||
| 					el("td", "~43kb") | 					el("td", "Minimal runtime") | ||||||
| 				), | 				), | ||||||
| 				el("tr").append( | 				el("tr").append( | ||||||
| 					el("td", "Reactivity Model"), | 					el("td", "Reactivity Model"), | ||||||
| 					el("td", "Signal-based"), | 					el("td", "Signal-based"), | ||||||
| 					el("td", "Signal-based (basics only)"), | 					el("td", "Virtual DOM diffing"), | ||||||
| 					el("td", "Signal-based"), | 					el("td", "Proxy-based"), | ||||||
| 					el("td", "MVVM + Proxy") | 					el("td", "Compile-time reactivity") | ||||||
| 				), | 				), | ||||||
| 				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", "Direct DOM API"), | 					el("td", "Virtual DOM"), | ||||||
| 					el("td", "Compiled DOM updates"), | 					el("td", "Virtual DOM"), | ||||||
| 					el("td", "Directive-based") | 					el("td", "Compiled DOM updates") | ||||||
| 				), | 				), | ||||||
| 				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", "❌") | 					el("td", "✅ Advanced"), | ||||||
|  | 					el("td", "✅ Advanced") | ||||||
| 				) | 				) | ||||||
| 			) | 			) | ||||||
| 		), | 		), | ||||||
| @@ -393,7 +347,7 @@ export function page({ pkg, info }){ | |||||||
|  |  | ||||||
| 		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` | ||||||
| @@ -413,33 +367,16 @@ export function page({ pkg, info }){ | |||||||
| 		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 |  | ||||||
| 			`) | 			`) | ||||||
| 		), | 		), | ||||||
| 	); | 	); | ||||||
|   | |||||||
| @@ -1,53 +0,0 @@ | |||||||
| import "./components/converter.html.js"; |  | ||||||
| import { T, t } from "./utils/index.js"; |  | ||||||
| export const info= { |  | ||||||
| 	title: t`Convert to dd<el>`, |  | ||||||
| 	fullTitle: t`HTML to dd<el> Converter`, |  | ||||||
| 	description: t`Convert your HTML markup to dd<el> JavaScript code with our interactive tool`, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| import { el } from "deka-dom-el"; |  | ||||||
| import { simplePage } from "./layout/simplePage.html.js"; |  | ||||||
| import { h3 } from "./components/pageUtils.html.js"; |  | ||||||
| import { converter } from "./components/converter.html.js"; |  | ||||||
|  |  | ||||||
| /** @param {import("./types.d.ts").PageAttrs} attrs */ |  | ||||||
| export function page({ pkg, info }){ |  | ||||||
| 	const page_id= info.id; |  | ||||||
| 	return el(simplePage, { info, pkg }).append( |  | ||||||
| 		el("p").append(T` |  | ||||||
| 			Transitioning from HTML to dd<el> is simple with our interactive converter. This tool helps you quickly |  | ||||||
| 			transform existing HTML markup into dd<el> JavaScript code, making it easier to adopt dd<el> in your projects. |  | ||||||
| 		`), |  | ||||||
|  |  | ||||||
| 		el("div", { className: "warning" }).append( |  | ||||||
| 			el("p").append(T` |  | ||||||
| 				While the converter handles most basic HTML patterns, complex attributes or specialized elements might |  | ||||||
| 				need manual adjustments. Always review the generated code before using it in production. |  | ||||||
| 			`) |  | ||||||
| 		), |  | ||||||
|  |  | ||||||
| 		// The actual converter component |  | ||||||
| 		el(converter, { page_id }), |  | ||||||
|  |  | ||||||
| 		el(h3, t`Next Steps`), |  | ||||||
| 		el("p").append(T` |  | ||||||
| 			After converting your HTML to dd<el>, you might want to: |  | ||||||
| 		`), |  | ||||||
| 		el("ul").append( |  | ||||||
| 			el("li").append(T` |  | ||||||
| 				Add signal bindings for dynamic content (see ${el("a", { href: "p04-signals.html", |  | ||||||
| 					textContent: "Signals section" })}) |  | ||||||
| 			`), |  | ||||||
| 			el("li").append(T` |  | ||||||
| 				Add event handlers for interactivity (see ${el("a", { href: "p03-events.html", |  | ||||||
| 					textContent: "Events section" })}) |  | ||||||
| 			`), |  | ||||||
| 			el("li").append(T` |  | ||||||
| 				Organize your components with components (see ${el("a", { href: |  | ||||||
| 					"p02-elements.html#h-using-components-to-build-ui-fragments", textContent: "Components section" })} |  | ||||||
| 					and ${el("a", { href: "p05-scopes.html", textContent: "Scopes section" })}) |  | ||||||
| 			`), |  | ||||||
| 		) |  | ||||||
| 	); |  | ||||||
| } |  | ||||||
| @@ -1,63 +0,0 @@ | |||||||
| import { T, t } from "./utils/index.js"; |  | ||||||
| export const info= { |  | ||||||
| 	title: t`Examples Gallery`, |  | ||||||
| 	fullTitle: t`DDE Examples & Code Snippets`, |  | ||||||
| 	description: t`A comprehensive collection of examples and code snippets for working with Deka DOM Elements.`, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| import { el } from "deka-dom-el"; |  | ||||||
| import { simplePage } from "./layout/simplePage.html.js"; |  | ||||||
| import { h3 } from "./components/pageUtils.html.js"; |  | ||||||
| import { example } from "./components/example.html.js"; |  | ||||||
| /** @param {string} url */ |  | ||||||
| const fileURL= url=> new URL(url, import.meta.url); |  | ||||||
|  |  | ||||||
| /** @param {import("./types.d.ts").PageAttrs} attrs */ |  | ||||||
| export function page({ pkg, info }){ |  | ||||||
| 	const page_id= info.id; |  | ||||||
| 	return el(simplePage, { info, pkg }).append( |  | ||||||
| 		el("p").append(T` |  | ||||||
| 			Real-world application examples showcasing how to build complete, production-ready interfaces with dd<el>: |  | ||||||
| 		`), |  | ||||||
| 		el(h3, t`Data Dashboard`), |  | ||||||
| 		el("p").append(T` |  | ||||||
| 			Data visualization dashboard with charts, filters, and responsive layout.  Integration with a |  | ||||||
| 			third-party charting library, data fetching and state management, responsive layout design, and multiple |  | ||||||
| 			interactive components working together. |  | ||||||
| 		`), |  | ||||||
| 		el(example, { src: fileURL("./components/examples/case-studies/data-dashboard.js"), variant: "big", page_id }), |  | ||||||
|  |  | ||||||
| 		el(h3, t`Interactive Form`), |  | ||||||
| 		el("p").append(T` |  | ||||||
| 			Complete form with real-time validation, conditional rendering, and responsive design. Form handling with |  | ||||||
| 			real-time validation, reactive UI updates, complex form state management, and clean separation of concerns. |  | ||||||
| 		`), |  | ||||||
| 		el(example, { src: fileURL("./components/examples/case-studies/interactive-form.js"), variant: "big", page_id }), |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 		el(h3, t`Interactive Image Gallery`), |  | ||||||
| 		el("p").append(T` |  | ||||||
| 			Responsive image gallery with lightbox, keyboard navigation, and filtering. Dynamic loading of content, |  | ||||||
| 			lightbox functionality, animation handling, and keyboard and gesture navigation support. |  | ||||||
| 		`), |  | ||||||
| 		el(example, { src: fileURL("./components/examples/case-studies/image-gallery.js"), variant: "big", page_id }), |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 		el(h3, t`Task Manager`), |  | ||||||
| 		el("p").append(T` |  | ||||||
| 			Kanban-style task management app with drag-and-drop and localStorage persistence. Complex state management |  | ||||||
| 			with signals, drag and drop functionality, local storage persistence, and responsive design for different |  | ||||||
| 			devices. |  | ||||||
| 		`), |  | ||||||
| 		el(example, { src: fileURL("./components/examples/case-studies/task-manager.js"), variant: "big", page_id }), |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 		el(h3, t`TodoMVC`), |  | ||||||
| 		el("p").append(T` |  | ||||||
| 			Complete TodoMVC implementation with local storage and routing. TodoMVC implementation showing routing, |  | ||||||
| 			local storage persistence, filtering, and component architecture patterns. For commented code, see the |  | ||||||
| 			dedicated page ${el("a", { href: "./p10-todomvc.html" }).append(T`TodoMVC`)}. |  | ||||||
| 		`), |  | ||||||
|  |  | ||||||
| 	); |  | ||||||
| } |  | ||||||
							
								
								
									
										48
									
								
								index.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										48
									
								
								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, | ||||||
| 		EL extends SupportedElement | 		EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>, | ||||||
| 		>( | 		>( | ||||||
| 			type: Event, | 			type: Event, | ||||||
| 			listener: (this: EL, ev: DocumentEventMap[Event] & { target: EL }) => any, | 			listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, | ||||||
| 			options?: AddEventListenerOptions | 			options?: AddEventListenerOptions | ||||||
| 		) : ddeElementAddon<EL>; | 		) : EE; | ||||||
| 	< | 	< | ||||||
| 		EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>, | 		EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>, | ||||||
| 		>( | 		>( | ||||||
| @@ -199,38 +199,28 @@ 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< | ||||||
| 		EL extends SupportedElement | 		EE extends ddeElementAddon<SupportedElement>, | ||||||
|  | 		El extends ( EE extends ddeElementAddon<infer El> ? El : never ) | ||||||
| 		>( | 		>( | ||||||
| 			listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, | 			listener: (this: El, event: CustomEvent<El>) => any, | ||||||
| 			options?: AddEventListenerOptions | 			options?: AddEventListenerOptions | ||||||
| 		) : ddeElementAddon<EL>; | 		) : 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 | 	/** 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< | ||||||
| 		EL extends SupportedElement | 		EE extends ddeElementAddon<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 | ||||||
| 		) : ddeElementAddon<EL>; | 		) : 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 | ||||||
| 	 * Fires after the next tick of the Javascript event loop. | 	attributeChanged< | ||||||
| 	 * This is handy for example to apply some property depending on the element content: | 		EE extends ddeElementAddon<SupportedElement>, | ||||||
| 	 * ```js | 		El extends ( EE extends ddeElementAddon<infer El> ? El : never ) | ||||||
| 	 * const selected= "Z"; | 		>( | ||||||
| 	 * //... | 			listener: (this: El, event: CustomEvent<[ string, string ]>) => any, | ||||||
| 	 * return el("form").append( | 			options?: AddEventListenerOptions | ||||||
| 	 *		el("select", null, on.defer(e=> e.value=selected)).append( | 		) : EE; | ||||||
| 	 *			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; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								index.js
									
									
									
									
									
								
							| @@ -1,3 +1,5 @@ | |||||||
| export * from "./src/dom-lib/index.js"; | export * from "./src/dom.js"; | ||||||
| export { memo } from "./src/memo.js"; | export * from "./src/customElement.js"; | ||||||
|  | export * from "./src/events.js"; | ||||||
| export { registerReactivity } from "./src/signals-lib/common.js"; | export { registerReactivity } from "./src/signals-lib/common.js"; | ||||||
|  | export { memo } from "./src/memo.js"; | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								jsdom.js
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								jsdom.js
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| import { enviroment as env } from './src/dom-lib/common.js'; | import { enviroment as env } from './src/dom-common.js'; | ||||||
| env.ssr= " ssr"; | env.ssr= " ssr"; | ||||||
|  |  | ||||||
| const q_store= new Set(); | const q_store= new Set(); | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +1,12 @@ | |||||||
| { | { | ||||||
| 	"name": "deka-dom-el", | 	"name": "deka-dom-el", | ||||||
| 	"version": "0.9.3-alpha", | 	"version": "0.9.1-alpha", | ||||||
| 	"lockfileVersion": 3, | 	"lockfileVersion": 3, | ||||||
| 	"requires": true, | 	"requires": true, | ||||||
| 	"packages": { | 	"packages": { | ||||||
| 		"": { | 		"": { | ||||||
| 			"name": "deka-dom-el", | 			"name": "deka-dom-el", | ||||||
| 			"version": "0.9.3-alpha", | 			"version": "0.9.1-alpha", | ||||||
| 			"license": "MIT", | 			"license": "MIT", | ||||||
| 			"devDependencies": { | 			"devDependencies": { | ||||||
| 				"@size-limit/preset-small-lib": "~11.2", | 				"@size-limit/preset-small-lib": "~11.2", | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
| 	"name": "deka-dom-el", | 	"name": "deka-dom-el", | ||||||
| 	"version": "0.9.3-alpha", | 	"version": "0.9.1-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", | ||||||
| @@ -85,12 +85,7 @@ | |||||||
| 	"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", | ||||||
|   | |||||||
							
								
								
									
										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, force?: boolean): V; | 	set(value: V): V; | ||||||
| 	toJSON(): V; | 	toJSON(): V; | ||||||
| 	valueOf(): V; | 	valueOf(): V; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,47 +1,6 @@ | |||||||
| import { keyLTE, evc, evd, eva } from "./common.js"; | import { keyLTE, evc, evd, eva } from "./dom-common.js"; | ||||||
| import { scope } from "./scopes.js"; | import { scope } from "./dom.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 | ||||||
| @@ -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,57 +0,0 @@ | |||||||
| 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); |  | ||||||
| } |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| export * from "./scopes.js"; |  | ||||||
| export * from "./el.js"; |  | ||||||
| export * from "./events.js"; |  | ||||||
| export * from "./customElement.js"; |  | ||||||
| @@ -1,82 +0,0 @@ | |||||||
| 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(); |  | ||||||
| 	}, |  | ||||||
| }; |  | ||||||
| @@ -1,6 +1,7 @@ | |||||||
| import { signals } from "../signals-lib/common.js"; | import { signals } from "./signals-lib/common.js"; | ||||||
| import { enviroment as env } from './common.js'; | import { enviroment as env } from './dom-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 | ||||||
| @@ -10,6 +11,84 @@ import { isInstance, isUndef, oAssign } from "../helpers.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 | ||||||
| @@ -28,7 +107,6 @@ 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 | ||||||
|  * |  * | ||||||
| @@ -38,12 +116,11 @@ import { scope } from "./scopes.js"; | |||||||
|  * @returns {Element|DocumentFragment} Created element |  * @returns {Element|DocumentFragment} Created element | ||||||
|  */ |  */ | ||||||
| export function createElement(tag, attributes, ...addons){ | export function createElement(tag, attributes, ...addons){ | ||||||
| 	/* jshint maxcomplexity: 16 */ | 	/* jshint maxcomplexity: 15 */ | ||||||
| 	const s= signals(this); | 	const s= signals(this); | ||||||
| 	let scoped= 0; | 	let scoped= 0; | ||||||
| 	let el, el_host; | 	let el, el_host; | ||||||
| 	const att_type= typeof attributes; | 	if(Object(attributes)!==attributes || s.isSignal(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": { | ||||||
| @@ -114,6 +191,46 @@ 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; | ||||||
| @@ -134,7 +251,6 @@ 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 | ||||||
|  * |  * | ||||||
| @@ -174,7 +290,6 @@ 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 | ||||||
|  * |  * | ||||||
| @@ -205,6 +320,21 @@ 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 | ||||||
| @@ -255,3 +385,45 @@ 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 './common.js'; | import { enviroment as env, evc, evd } from './dom-common.js'; | ||||||
| import { isInstance } from "../helpers.js"; | import { isInstance } from "./helpers.js"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Connection changes observer for tracking element connection/disconnection |  * Connection changes observer for tracking element connection/disconnection | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { keyLTE, evc, evd } from './common.js'; | import { keyLTE, evc, evd } from './dom-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,8 +38,6 @@ 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"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @@ -68,3 +68,21 @@ export function observedAttributes(instance, observedAttribute){ | |||||||
|  * @returns {string} The camelCase string |  * @returns {string} The camelCase string | ||||||
|  */ |  */ | ||||||
| function kebabToCamel(name){ return name.replace(/-./g, x=> x[1].toUpperCase()); } | function kebabToCamel(name){ return name.replace(/-./g, x=> x[1].toUpperCase()); } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Error class for definition tracking | ||||||
|  |  * Shows the correct stack trace for debugging (signal) creation | ||||||
|  |  */ | ||||||
|  | export class Defined extends Error{ | ||||||
|  | 	constructor(){ | ||||||
|  | 		super(); | ||||||
|  | 		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)+"…"+stack.slice(stack.lastIndexOf("/")); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ memo.isScope= function(obj){ return obj[memoMark]; }; | |||||||
|  * @param {AbortSignal} options.signal |  * @param {AbortSignal} options.signal | ||||||
|  * @param {boolean} [options.onlyLast=false] |  * @param {boolean} [options.onlyLast=false] | ||||||
|  * */ |  * */ | ||||||
| memo.scope= function memoScopeCreate(fun, { signal, onlyLast }= {}){ | memo.scope= function memoScope(fun, { signal, onlyLast }= {}){ | ||||||
| 	let cache= oCreate(); | 	let cache= oCreate(); | ||||||
| 	function memoScope(...args){ | 	function memoScope(...args){ | ||||||
| 		if(signal && signal.aborted) | 		if(signal && signal.aborted) | ||||||
|   | |||||||
| @@ -9,8 +9,7 @@ export const mark= "__dde_signal"; | |||||||
|  * @type {Function} |  * @type {Function} | ||||||
|  */ |  */ | ||||||
| export const queueSignalWrite= (()=> { | export const queueSignalWrite= (()=> { | ||||||
| 	/** @type {Map<ddeSignal, boolean>} */ | 	let pendingSignals= new Set(); | ||||||
| 	let pendingSignals= new Map(); |  | ||||||
| 	let scheduled= false; | 	let scheduled= false; | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| @@ -20,20 +19,19 @@ export const queueSignalWrite= (()=> { | |||||||
| 	function flushSignals() { | 	function flushSignals() { | ||||||
| 		scheduled = false; | 		scheduled = false; | ||||||
| 		const todo= pendingSignals; | 		const todo= pendingSignals; | ||||||
| 		pendingSignals= new Map(); | 		pendingSignals= new Set(); | ||||||
| 		for(const [ signal, force ] of todo){ | 		for(const signal of todo){ | ||||||
| 			const M = signal[mark]; | 			const M = signal[mark]; | ||||||
| 			if(M) M.listeners.forEach(l => l(M.value, force)); | 			if(M) M.listeners.forEach(l => l(M.value)); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 * Queues a signal for update | 	 * Queues a signal for update | ||||||
| 	 * @param {ddeSignal} s - Signal to queue | 	 * @param {Object} s - Signal to queue | ||||||
| 	 * @param {boolean} force - Forced update |  | ||||||
| 	 */ | 	 */ | ||||||
| 	return function(s, force= false){ | 	return function(s){ | ||||||
| 		pendingSignals.set(s, pendingSignals.get(s) || force); | 		pendingSignals.add(s); | ||||||
| 		if(scheduled) return; | 		if(scheduled) return; | ||||||
| 		scheduled = true; | 		scheduled = true; | ||||||
| 		queueMicrotask(flushSignals); | 		queueMicrotask(flushSignals); | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { queueSignalWrite, mark } from "./helpers.js"; | import { queueSignalWrite, mark } from "./helpers.js"; | ||||||
| export { mark }; | export { mark }; | ||||||
| import { hasOwn, oCreate, oAssign } from "../helpers.js"; | import { hasOwn, Defined, oCreate, oAssign } from "../helpers.js"; | ||||||
|  |  | ||||||
| const Signal = oCreate(null, { | const Signal = oCreate(null, { | ||||||
| 	get: { value(){ return read(this); } }, | 	get: { value(){ return read(this); } }, | ||||||
| @@ -57,12 +57,12 @@ export function signal(value, actions){ | |||||||
| 	 * Updates the derived signal when dependencies change | 	 * Updates the derived signal when dependencies change | ||||||
| 	 * @private | 	 * @private | ||||||
| 	 */ | 	 */ | ||||||
| 	function contextReWatch(_, force){ | 	function contextReWatch(){ | ||||||
| 		const [ origin, ...deps_old ]= deps.get(contextReWatch); | 		const [ origin, ...deps_old ]= deps.get(contextReWatch); | ||||||
| 		deps.set(contextReWatch, new Set([ origin ])); | 		deps.set(contextReWatch, new Set([ origin ])); | ||||||
|  |  | ||||||
| 		stack_watch.push(contextReWatch); | 		stack_watch.push(contextReWatch); | ||||||
| 		write(out, value(), force); | 		write(out, value()); | ||||||
| 		stack_watch.pop(); | 		stack_watch.pop(); | ||||||
|  |  | ||||||
| 		if(!deps_old.length) return; | 		if(!deps_old.length) return; | ||||||
| @@ -95,7 +95,7 @@ signal.action= function(s, name, ...a){ | |||||||
| 		throw new Error(`Action "${name}" not defined. See ${mark}.actions.`); | 		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, true); | 	queueSignalWrite(s); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -159,31 +159,31 @@ signal.clear= function(...signals){ | |||||||
| }; | }; | ||||||
| /** Property key for tracking reactive elements */ | /** Property key for tracking reactive elements */ | ||||||
| const key_reactive= "__dde_reactive"; | const key_reactive= "__dde_reactive"; | ||||||
| import { enviroment as env, eva } from "../dom-lib/common.js"; | import { enviroment as env, eva } from "../dom-common.js"; | ||||||
| import { el } from "../dom-lib/index.js"; | import { el } from "../dom.js"; | ||||||
| import { scope } from "../dom-lib/scopes.js"; | import { scope } from "../dom.js"; | ||||||
| import { on } from "../dom-lib/events.js"; | import { on } from "../events.js"; | ||||||
| import { memo } from "../memo.js"; | import { memo } from "../memo.js"; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Creates a reactive DOM element that re-renders when signal changes |  * Creates a reactive DOM element that re-renders when signal changes | ||||||
|  * |  * | ||||||
|  * @param {Object} s - Signal object to watch |  * @param {Object} s - Signal object to watch | ||||||
|  * @param {Function} mapScoped - Function mapping signal value to DOM elements |  * @param {Function} map - Function mapping signal value to DOM elements | ||||||
|  * @returns {DocumentFragment} Fragment containing reactive elements |  * @returns {DocumentFragment} Fragment containing reactive elements | ||||||
|  */ |  */ | ||||||
| signal.el= function(s, map){ | signal.el= function(s, map){ | ||||||
| 	const mapScoped= memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | 	map= memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | ||||||
| 	const { current }= scope, { scope: sc }= current; | 	const mark_start= el.mark({ type: "reactive", source: new Defined().compact }, true); | ||||||
| 	const mark_start= el.mark({ type: "reactive", component: sc && sc.name || "" }, true); |  | ||||||
| 	const mark_end= mark_start.end; | 	const mark_end= mark_start.end; | ||||||
| 	const out= env.D.createDocumentFragment(); | 	const out= env.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) // === `isConnected` or wasn’t yet rendered | 		if(!mark_start.parentNode || !mark_end.parentNode) // === `isConnected` or wasn’t yet rendered | ||||||
| 			return removeSignalListener(s, reRenderReactiveElement); | 			return removeSignalListener(s, reRenderReactiveElement); | ||||||
| 		scope.push(current); | 		scope.push(current); | ||||||
| 		let els= mapScoped(v); | 		let els= map(v); | ||||||
| 		scope.pop(); | 		scope.pop(); | ||||||
| 		if(!Array.isArray(els)) | 		if(!Array.isArray(els)) | ||||||
| 			els= [ els ]; | 			els= [ els ]; | ||||||
| @@ -202,7 +202,7 @@ signal.el= function(s, map){ | |||||||
| 	reRenderReactiveElement(s.get()); | 	reRenderReactiveElement(s.get()); | ||||||
| 	current.host(on.disconnected(()=> | 	current.host(on.disconnected(()=> | ||||||
| 		/*! Clears cached elements for reactive element `S.el` */ | 		/*! Clears cached elements for reactive element `S.el` */ | ||||||
| 		mapScoped.clear() | 		map.clear() | ||||||
| 	)); | 	)); | ||||||
| 	return out; | 	return out; | ||||||
| }; | }; | ||||||
| @@ -314,8 +314,9 @@ export const signals_config= { | |||||||
| function removeSignalsFromElements(s, listener, ...notes){ | function removeSignalsFromElements(s, listener, ...notes){ | ||||||
| 	const { current }= scope; | 	const { current }= scope; | ||||||
| 	current.host(function(element){ | 	current.host(function(element){ | ||||||
| 		if(!element[key_reactive]) element[key_reactive]= []; | 		if(element[key_reactive]) | ||||||
| 		element[key_reactive].push([ [ s, listener ], ...notes ]); | 			return element[key_reactive].push([ [ s, listener ], ...notes ]); | ||||||
|  | 		element[key_reactive]= []; | ||||||
| 		if(current.prevent) return; // typically document.body, doenst need auto-remove as it should happen on page leave | 		if(current.prevent) return; // typically document.body, doenst need auto-remove as it should happen on page leave | ||||||
| 		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`, …?). | ||||||
| @@ -385,6 +386,7 @@ function toSignal(s, value, actions, readonly= false){ | |||||||
| 		value: oAssign(oCreate(protoSigal), { | 		value: oAssign(oCreate(protoSigal), { | ||||||
| 			value, actions, onclear, host, | 			value, actions, onclear, host, | ||||||
| 			listeners: new Set(), | 			listeners: new Set(), | ||||||
|  | 			defined: new Defined().stack, | ||||||
| 			readonly | 			readonly | ||||||
| 		}), | 		}), | ||||||
| 		enumerable: false, | 		enumerable: false, | ||||||
| @@ -432,7 +434,7 @@ function write(s, value, force){ | |||||||
| 	if(!M || (!force && M.value===value)) return; | 	if(!M || (!force && M.value===value)) return; | ||||||
|  |  | ||||||
| 	M.value= value; | 	M.value= value; | ||||||
| 	queueSignalWrite(s, force); | 	queueSignalWrite(s); | ||||||
| 	return value; | 	return value; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user