mirror of
				https://github.com/jaandrle/deka-dom-el
				synced 2025-10-31 05:49:15 +01:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			330f702409
			...
			v0.9.3-alp
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5076771410 | 
							
								
								
									
										40
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| --- | ||||
| name: Bug report | ||||
| about: Create a report to help us improve | ||||
| title: ":bug: " | ||||
| labels: bug | ||||
| assignees: '' | ||||
| --- | ||||
|  | ||||
| ## Bug Description | ||||
| <!-- A clear and concise description of what the bug is --> | ||||
|  | ||||
| ## Steps to Reproduce | ||||
| <!-- Steps to reproduce the behavior --> | ||||
| 1.  | ||||
| 2.  | ||||
| 3.  | ||||
|  | ||||
| ## Expected Behavior | ||||
| <!-- A clear and concise description of what you expected to happen --> | ||||
|  | ||||
| ## Actual Behavior | ||||
| <!-- A clear and concise description of what actually happened --> | ||||
|  | ||||
| ## Code Sample | ||||
| <!-- If applicable, add minimal code sample to reproduce the issue --> | ||||
| ```js | ||||
| // Your code here | ||||
| ``` | ||||
|  | ||||
| ## Environment | ||||
| - Browser and version: <!-- e.g. Chrome 120, Firefox 120, Safari 17 --> | ||||
| - OS: <!-- e.g. Windows 11, macOS Sonoma, Ubuntu 22.04 --> | ||||
| - dd<el> version: <!-- e.g. 0.9.2 --> | ||||
| - Other relevant details: | ||||
|  | ||||
| ## Screenshots | ||||
| <!-- If applicable, add screenshots to help explain your problem --> | ||||
|  | ||||
| ## Additional Context | ||||
| <!-- Add any other context about the problem here --> | ||||
							
								
								
									
										22
									
								
								.github/ISSUE_TEMPLATE/documentation.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.github/ISSUE_TEMPLATE/documentation.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| --- | ||||
| name: Documentation improvement | ||||
| about: Suggest improvements to the documentation | ||||
| title: ":abc: " | ||||
| labels: documentation | ||||
| assignees: '' | ||||
| --- | ||||
|  | ||||
| ## Documentation Area | ||||
| <!-- Which part of the documentation needs improvement? Provide links if applicable --> | ||||
|  | ||||
| ## Current Issue | ||||
| <!-- What's currently unclear, missing, or incorrect in the documentation? --> | ||||
|  | ||||
| ## Suggested Improvement | ||||
| <!-- Describe the improvement or addition you'd like to see --> | ||||
|  | ||||
| ## Example Content | ||||
| <!-- If applicable, provide example content or wording --> | ||||
|  | ||||
| ## Additional Context | ||||
| <!-- Any other context or screenshots about the documentation request --> | ||||
							
								
								
									
										29
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| --- | ||||
| name: Feature request | ||||
| about: Suggest an idea for this project | ||||
| title: ":zap: " | ||||
| labels: enhancement | ||||
| assignees: '' | ||||
| --- | ||||
| <!-- Consider open discussion: https://github.com/jaandrle/deka-dom-el/discussions first --> | ||||
|  | ||||
| ## Problem Statement | ||||
| <!-- A clear and concise description of the problem this feature would solve --> | ||||
|  | ||||
| ## Proposed Solution | ||||
| <!-- A detailed description of the feature you're suggesting --> | ||||
|  | ||||
| ## Use Cases | ||||
| <!-- Describe specific use cases where this feature would be beneficial --> | ||||
|  | ||||
| ## Example Implementation | ||||
| <!-- If possible, provide example code or pseudocode for how this feature might work --> | ||||
| ```js | ||||
| // Example code | ||||
| ``` | ||||
|  | ||||
| ## Alternatives Considered | ||||
| <!-- A description of any alternative solutions or features you've considered --> | ||||
|  | ||||
| ## Additional Context | ||||
| <!-- Any other context, screenshots, or examples that might be helpful --> | ||||
							
								
								
									
										39
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| <!--  | ||||
| Please use an appropriate git3moji in your PR title: https://robinpokorny.github.io/git3moji/ | ||||
| Examples: | ||||
| - :bug: Fix signal update not triggering on nested properties | ||||
| - :zap: Improve event delegation performance | ||||
| - :abc: Add documentation for custom elements | ||||
| --> | ||||
|  | ||||
| ## Description | ||||
| <!-- Describe the changes introduced by this PR --> | ||||
|  | ||||
| ## Related Issues | ||||
| <!-- Link any related issues using the format #ISSUE_NUMBER --> | ||||
|  | ||||
| ## Type of Change | ||||
| - [ ] Bug fix (non-breaking change that fixes an issue) | ||||
| - [ ] New feature (non-breaking change that adds functionality) | ||||
| - [ ] Breaking change (fix or feature that would cause existing functionality to change) | ||||
| - [ ] Documentation update | ||||
| - [ ] Code refactoring | ||||
| - [ ] Performance improvement | ||||
| - [ ] Test update | ||||
|  | ||||
| ## Testing Performed | ||||
| <!-- Describe the tests you've done to verify your changes --> | ||||
|  | ||||
| ## Screenshots | ||||
| <!-- If applicable, add screenshots to help explain your changes --> | ||||
|  | ||||
| ## Checklist | ||||
| - [ ] My code follows the code style of this project | ||||
| - [ ] I have performed a self-review of my own code | ||||
| - [ ] I have added tests that prove my fix is effective or that my feature works | ||||
| - [ ] I have updated the documentation accordingly | ||||
| - [ ] My changes generate no new warnings | ||||
| - [ ] All existing tests are passing | ||||
|  | ||||
| ## Additional Notes | ||||
| <!-- Any additional information that might be helpful for reviewers --> | ||||
							
								
								
									
										18
									
								
								.github/workflows/npm-publish.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/npm-publish.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| name: Publish Package to npmjs | ||||
| on: | ||||
|   workflow_dispatch: | ||||
|   release: | ||||
|     types: [created] | ||||
| jobs: | ||||
|   build: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||||
|       - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 | ||||
|         with: | ||||
|           node-version: '20.16' | ||||
|           registry-url: 'https://registry.npmjs.org' | ||||
|       - run: npm ci | ||||
|       - run: npm publish | ||||
|         env: | ||||
|           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | ||||
							
								
								
									
										3
									
								
								.npmrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.npmrc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} | ||||
| registry=https://registry.npmjs.org/ | ||||
| always-auth=true | ||||
							
								
								
									
										134
									
								
								CODE_OF_CONDUCT.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								CODE_OF_CONDUCT.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | ||||
|  | ||||
| # Contributor Covenant Code of Conduct | ||||
|  | ||||
| ## Our Pledge | ||||
|  | ||||
| We as members, contributors, and leaders pledge to make participation in our | ||||
| community a harassment-free experience for everyone, regardless of age, body | ||||
| size, visible or invisible disability, ethnicity, sex characteristics, gender | ||||
| identity and expression, level of experience, education, socio-economic status, | ||||
| nationality, personal appearance, race, caste, color, religion, or sexual | ||||
| identity and orientation. | ||||
|  | ||||
| We pledge to act and interact in ways that contribute to an open, welcoming, | ||||
| diverse, inclusive, and healthy community. | ||||
|  | ||||
| ## Our Standards | ||||
|  | ||||
| Examples of behavior that contributes to a positive environment for our | ||||
| community include: | ||||
|  | ||||
| * Demonstrating empathy and kindness toward other people | ||||
| * Being respectful of differing opinions, viewpoints, and experiences | ||||
| * Giving and gracefully accepting constructive feedback | ||||
| * Accepting responsibility and apologizing to those affected by our mistakes, | ||||
| 	and learning from the experience | ||||
| * Focusing on what is best not just for us as individuals, but for the overall | ||||
| 	community | ||||
|  | ||||
| Examples of unacceptable behavior include: | ||||
|  | ||||
| * The use of sexualized language or imagery, and sexual attention or advances of | ||||
| 	any kind | ||||
| * Trolling, insulting or derogatory comments, and personal or political attacks | ||||
| * Public or private harassment | ||||
| * Publishing others' private information, such as a physical or email address, | ||||
| 	without their explicit permission | ||||
| * Other conduct which could reasonably be considered inappropriate in a | ||||
| 	professional setting | ||||
|  | ||||
| ## Enforcement Responsibilities | ||||
|  | ||||
| Community leaders are responsible for clarifying and enforcing our standards of | ||||
| acceptable behavior and will take appropriate and fair corrective action in | ||||
| response to any behavior that they deem inappropriate, threatening, offensive, | ||||
| or harmful. | ||||
|  | ||||
| Community leaders have the right and responsibility to remove, edit, or reject | ||||
| comments, commits, code, wiki edits, issues, and other contributions that are | ||||
| not aligned to this Code of Conduct, and will communicate reasons for moderation | ||||
| decisions when appropriate. | ||||
|  | ||||
| ## Scope | ||||
|  | ||||
| This Code of Conduct applies within all community spaces, and also applies when | ||||
| an individual is officially representing the community in public spaces. | ||||
| Examples of representing our community include using an official e-mail address, | ||||
| posting via an official social media account, or acting as an appointed | ||||
| representative at an online or offline event. | ||||
|  | ||||
| ## Enforcement | ||||
|  | ||||
| Instances of abusive, harassing, or otherwise unacceptable behavior may be | ||||
| reported to the community leaders responsible for enforcement at | ||||
| andrle.jan@centrum.cz. | ||||
| All complaints will be reviewed and investigated promptly and fairly. | ||||
|  | ||||
| All community leaders are obligated to respect the privacy and security of the | ||||
| reporter of any incident. | ||||
|  | ||||
| ## Enforcement Guidelines | ||||
|  | ||||
| Community leaders will follow these Community Impact Guidelines in determining | ||||
| the consequences for any action they deem in violation of this Code of Conduct: | ||||
|  | ||||
| ### 1. Correction | ||||
|  | ||||
| **Community Impact**: Use of inappropriate language or other behavior deemed | ||||
| unprofessional or unwelcome in the community. | ||||
|  | ||||
| **Consequence**: A private, written warning from community leaders, providing | ||||
| clarity around the nature of the violation and an explanation of why the | ||||
| behavior was inappropriate. A public apology may be requested. | ||||
|  | ||||
| ### 2. Warning | ||||
|  | ||||
| **Community Impact**: A violation through a single incident or series of | ||||
| actions. | ||||
|  | ||||
| **Consequence**: A warning with consequences for continued behavior. No | ||||
| interaction with the people involved, including unsolicited interaction with | ||||
| those enforcing the Code of Conduct, for a specified period of time. This | ||||
| includes avoiding interactions in community spaces as well as external channels | ||||
| like social media. Violating these terms may lead to a temporary or permanent | ||||
| ban. | ||||
|  | ||||
| ### 3. Temporary Ban | ||||
|  | ||||
| **Community Impact**: A serious violation of community standards, including | ||||
| sustained inappropriate behavior. | ||||
|  | ||||
| **Consequence**: A temporary ban from any sort of interaction or public | ||||
| communication with the community for a specified period of time. No public or | ||||
| private interaction with the people involved, including unsolicited interaction | ||||
| with those enforcing the Code of Conduct, is allowed during this period. | ||||
| Violating these terms may lead to a permanent ban. | ||||
|  | ||||
| ### 4. Permanent Ban | ||||
|  | ||||
| **Community Impact**: Demonstrating a pattern of violation of community | ||||
| standards, including sustained inappropriate behavior, harassment of an | ||||
| individual, or aggression toward or disparagement of classes of individuals. | ||||
|  | ||||
| **Consequence**: A permanent ban from any sort of public interaction within the | ||||
| community. | ||||
|  | ||||
| ## Attribution | ||||
|  | ||||
| This Code of Conduct is adapted from the [Contributor Covenant][homepage], | ||||
| version 2.1, available at | ||||
| [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. | ||||
|  | ||||
| Community Impact Guidelines were inspired by | ||||
| [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. | ||||
|  | ||||
| For answers to common questions about this code of conduct, see the FAQ at | ||||
| [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at | ||||
| [https://www.contributor-covenant.org/translations][translations]. | ||||
|  | ||||
| [homepage]: https://www.contributor-covenant.org | ||||
| [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html | ||||
| [Mozilla CoC]: https://github.com/mozilla/diversity | ||||
| [FAQ]: https://www.contributor-covenant.org/faq | ||||
| [translations]: https://www.contributor-covenant.org/translations | ||||
|  | ||||
							
								
								
									
										174
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,174 @@ | ||||
| # 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. | ||||
							
								
								
									
										56
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,6 +1,7 @@ | ||||
| **WIP** (the experimentation phase) | ||||
| **Alpha** | ||||
| | [source code on GitHub](https://github.com/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"> | ||||
| 	<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180"> | ||||
| @@ -39,7 +40,7 @@ function EmojiCounter({ initial }) { | ||||
| 			on("click", () => count.set(count.get() + 1)) | ||||
| 		), | ||||
|  | ||||
| 		el("select", null, on.host(el=> el.value= initial), | ||||
| 		el("select", null, on.defer(el=> el.value= initial), | ||||
| 			on("change", e => emoji.set(e.target.value)) | ||||
| 		).append( | ||||
| 			el(Option, "🎉"), | ||||
| @@ -60,40 +61,30 @@ Creating reactive elements, components, and Web Components using the native | ||||
| ## Features at a Glance | ||||
|  | ||||
| - ✅ **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 | ||||
| - ✅ **Signals and events** for reactive UI | ||||
| - ✅ **Memoization for performance** — optimize rendering with intelligent caching | ||||
| - ✅ **Optional build-in signals** with support for custom reactive implementations | ||||
| - ✅ **Optional build-in signals** with support for custom reactive implementations (#39) | ||||
| - ✅ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom) | ||||
| - ✅ **TypeScript support** (work in progress) | ||||
| - ✅ **TypeScript support** | ||||
| - ☑️ **Support for debugging with browser DevTools** without extensions | ||||
| - ☑️ **Enhanced Web Components** support (work in progress) | ||||
|  | ||||
| ## Why Another Library? | ||||
|  | ||||
| This library bridges the gap between minimal solutions like van/hyperscript and more comprehensive frameworks like | ||||
| [solid-js](https://github.com/solidjs/solid), offering a balanced trade-off between size, complexity, and usability. | ||||
|  | ||||
| Following functional programming principles, dd\<el\> starts with pure JavaScript (DOM API) and gradually adds | ||||
| auxiliary functions. These range from minor improvements to advanced features for building complete declarative | ||||
| reactive UI templates. | ||||
|  | ||||
| A key advantage: any internal function (`assign`, `classListDeclarative`, `on`, `dispatchEvent`, `S`, etc.) can be used | ||||
| independently while also working seamlessly together. This modular approach makes it easier to integrate the library | ||||
| into existing projects. | ||||
| - ☑️ **Enhanced Web Components** support | ||||
|  | ||||
| ## 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 | ||||
|  | ||||
| #### npm | ||||
| ```bash | ||||
| # TBD | ||||
| # npm install deka-dom-el | ||||
| npm install deka-dom-el --save | ||||
| ``` | ||||
|  | ||||
| #### CDN / Direct Script | ||||
| …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. | ||||
| @@ -113,10 +104,18 @@ format selector](https://jaandrle.github.io/deka-dom-el/) on the documentation s | ||||
| </script> | ||||
| ``` | ||||
|  | ||||
| ### Documentation | ||||
| ## Why Another Library? | ||||
|  | ||||
| - [**Interactive Guide**](https://jaandrle.github.io/deka-dom-el) | ||||
| - [**Examples**](https://jaandrle.github.io/deka-dom-el/p15-examples.html) | ||||
| This library bridges the gap between minimal solutions like van/hyperscript and more comprehensive frameworks like | ||||
| [solid-js](https://github.com/solidjs/solid), offering a balanced trade-off between size, complexity, and usability. | ||||
|  | ||||
| Following functional programming principles, dd\<el\> starts with pure JavaScript (DOM API) and gradually adds | ||||
| auxiliary functions. These range from minor improvements to advanced features for building complete declarative | ||||
| reactive UI templates. | ||||
|  | ||||
| A key advantage: any internal function (`assign`, `classListDeclarative`, `on`, `dispatchEvent`, `S`, etc.) can be used | ||||
| independently while also working seamlessly together. This modular approach makes it easier to integrate the library | ||||
| into existing projects. | ||||
|  | ||||
| ## Understanding Signals | ||||
|  | ||||
| @@ -127,6 +126,11 @@ Signals are the reactive backbone of Deka DOM Elements: | ||||
| - [TC39 Signals Proposal](https://github.com/tc39/proposal-signals) (future standard) | ||||
| - [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 | ||||
|  | ||||
| - [vanjs-org/van](https://github.com/vanjs-org/van) — World's smallest reactive UI framework | ||||
|   | ||||
| @@ -2,15 +2,18 @@ | ||||
| This project uses [jaandrle/bs: The simplest possible build system using executable/bash scripts]( | ||||
| https://github.com/jaandrle/bs). | ||||
|  | ||||
| #### bs/build.js [--minify|--help] | ||||
| #### bs/build.js [main|signals] [--no-types|--help] | ||||
| Generates alternative versions of the project (other than native ESM code). | ||||
| Also generates typescript definitions. | ||||
|  | ||||
| #### bs/docs.js | ||||
| Generates documentation, from `docs/`. Uses “SSR” technique, using deka-dom-el itself. | ||||
|  | ||||
| For running use `npx serve dist/docs`. | ||||
|  | ||||
| #### bs/lint.sh | ||||
| Lints size of the project, jshint. See configs: | ||||
|  | ||||
| - `package.json`: key `size-limit` | ||||
| - `package.json`: key `jshintConfig` | ||||
| - `.editorconfig` | ||||
|   | ||||
							
								
								
									
										43
									
								
								dist/esm-with-signals.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										43
									
								
								dist/esm-with-signals.js
									
									
									
									
										vendored
									
									
								
							| @@ -41,19 +41,6 @@ function observedAttributes(instance, observedAttribute2) { | ||||
| function kebabToCamel(name) { | ||||
| 	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 | ||||
| var enviroment = { | ||||
| @@ -303,7 +290,7 @@ var store_abort = /* @__PURE__ */ new WeakMap(); | ||||
| var scope = { | ||||
| 	/** | ||||
| 	* Gets the current scope | ||||
| 	* @returns {Object} Current scope context | ||||
| 	* @returns {typeof scopes[number]} Current scope context | ||||
| 	*/ | ||||
| 	get current() { | ||||
| 		return scopes[scopes.length - 1]; | ||||
| @@ -663,9 +650,9 @@ function memo(key, generator) { | ||||
| memo.isScope = function(obj) { | ||||
| 	return obj[memoMark]; | ||||
| }; | ||||
| memo.scope = function memoScope(fun, { signal: signal2, onlyLast } = {}) { | ||||
| memo.scope = function memoScopeCreate(fun, { signal: signal2, onlyLast } = {}) { | ||||
| 	let cache = oCreate(); | ||||
| 	function memoScope2(...args) { | ||||
| 	function memoScope(...args) { | ||||
| 		if (signal2 && signal2.aborted) | ||||
| 			return fun.apply(this, args); | ||||
| 		let cache_local = onlyLast ? cache : oCreate(); | ||||
| @@ -680,10 +667,10 @@ memo.scope = function memoScope(fun, { signal: signal2, onlyLast } = {}) { | ||||
| 		cache = cache_local; | ||||
| 		return out; | ||||
| 	} | ||||
| 	memoScope2[memoMark] = true; | ||||
| 	memoScope2.clear = () => cache = oCreate(); | ||||
| 	if (signal2) signal2.addEventListener("abort", memoScope2.clear); | ||||
| 	return memoScope2; | ||||
| 	memoScope[memoMark] = true; | ||||
| 	memoScope.clear = () => cache = oCreate(); | ||||
| 	if (signal2) signal2.addEventListener("abort", memoScope.clear); | ||||
| 	return memoScope; | ||||
| }; | ||||
|  | ||||
| // src/signals-lib/helpers.js | ||||
| @@ -800,17 +787,17 @@ signal.clear = function(...signals2) { | ||||
| }; | ||||
| var key_reactive = "__dde_reactive"; | ||||
| signal.el = function(s, map) { | ||||
| 	map = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | ||||
| 	const mark_start = createElement.mark({ type: "reactive", source: new Defined().compact }, true); | ||||
| 	const mapScoped = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | ||||
| 	const { current } = scope, { scope: sc } = current; | ||||
| 	const mark_start = createElement.mark({ type: "reactive", component: sc && sc.name || "" }, true); | ||||
| 	const mark_end = mark_start.end; | ||||
| 	const out = enviroment.D.createDocumentFragment(); | ||||
| 	out.append(mark_start, mark_end); | ||||
| 	const { current } = scope; | ||||
| 	const reRenderReactiveElement = (v) => { | ||||
| 		if (!mark_start.parentNode || !mark_end.parentNode) | ||||
| 			return removeSignalListener(s, reRenderReactiveElement); | ||||
| 		scope.push(current); | ||||
| 		let els = map(v); | ||||
| 		let els = mapScoped(v); | ||||
| 		scope.pop(); | ||||
| 		if (!Array.isArray(els)) | ||||
| 			els = [els]; | ||||
| @@ -830,7 +817,7 @@ signal.el = function(s, map) { | ||||
| 	current.host(on.disconnected( | ||||
| 		() => ( | ||||
| 			/*! Clears cached elements for reactive element `S.el` */ | ||||
| 			map.clear() | ||||
| 			mapScoped.clear() | ||||
| 		) | ||||
| 	)); | ||||
| 	return out; | ||||
| @@ -902,9 +889,8 @@ var signals_config = { | ||||
| function removeSignalsFromElements(s, listener, ...notes) { | ||||
| 	const { current } = scope; | ||||
| 	current.host(function(element) { | ||||
| 		if (element[key_reactive]) | ||||
| 			return element[key_reactive].push([[s, listener], ...notes]); | ||||
| 		element[key_reactive] = []; | ||||
| 		if (!element[key_reactive]) element[key_reactive] = []; | ||||
| 		element[key_reactive].push([[s, listener], ...notes]); | ||||
| 		if (current.prevent) return; | ||||
| 		on.disconnected( | ||||
| 			() => ( | ||||
| @@ -949,7 +935,6 @@ function toSignal(s, value, actions, readonly = false) { | ||||
| 			onclear, | ||||
| 			host, | ||||
| 			listeners: /* @__PURE__ */ new Set(), | ||||
| 			defined: new Defined().stack, | ||||
| 			readonly | ||||
| 		}), | ||||
| 		enumerable: false, | ||||
|   | ||||
							
								
								
									
										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
											
										
									
								
							
							
								
								
									
										14
									
								
								dist/esm.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								dist/esm.js
									
									
									
									
										vendored
									
									
								
							| @@ -274,7 +274,7 @@ var store_abort = /* @__PURE__ */ new WeakMap(); | ||||
| var scope = { | ||||
| 	/** | ||||
| 	* Gets the current scope | ||||
| 	* @returns {Object} Current scope context | ||||
| 	* @returns {typeof scopes[number]} Current scope context | ||||
| 	*/ | ||||
| 	get current() { | ||||
| 		return scopes[scopes.length - 1]; | ||||
| @@ -634,9 +634,9 @@ function memo(key, generator) { | ||||
| memo.isScope = function(obj) { | ||||
| 	return obj[memoMark]; | ||||
| }; | ||||
| memo.scope = function memoScope(fun, { signal, onlyLast } = {}) { | ||||
| memo.scope = function memoScopeCreate(fun, { signal, onlyLast } = {}) { | ||||
| 	let cache = oCreate(); | ||||
| 	function memoScope2(...args) { | ||||
| 	function memoScope(...args) { | ||||
| 		if (signal && signal.aborted) | ||||
| 			return fun.apply(this, args); | ||||
| 		let cache_local = onlyLast ? cache : oCreate(); | ||||
| @@ -651,10 +651,10 @@ memo.scope = function memoScope(fun, { signal, onlyLast } = {}) { | ||||
| 		cache = cache_local; | ||||
| 		return out; | ||||
| 	} | ||||
| 	memoScope2[memoMark] = true; | ||||
| 	memoScope2.clear = () => cache = oCreate(); | ||||
| 	if (signal) signal.addEventListener("abort", memoScope2.clear); | ||||
| 	return memoScope2; | ||||
| 	memoScope[memoMark] = true; | ||||
| 	memoScope.clear = () => cache = oCreate(); | ||||
| 	if (signal) signal.addEventListener("abort", memoScope.clear); | ||||
| 	return memoScope; | ||||
| }; | ||||
| export { | ||||
| 	assign, | ||||
|   | ||||
							
								
								
									
										2
									
								
								dist/esm.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/esm.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										43
									
								
								dist/iife-with-signals.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										43
									
								
								dist/iife-with-signals.js
									
									
									
									
										vendored
									
									
								
							| @@ -86,19 +86,6 @@ var DDE = (() => { | ||||
| 	function kebabToCamel(name) { | ||||
| 		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 | ||||
| 	var enviroment = { | ||||
| @@ -348,7 +335,7 @@ var DDE = (() => { | ||||
| 	var scope = { | ||||
| 		/** | ||||
| 		* Gets the current scope | ||||
| 		* @returns {Object} Current scope context | ||||
| 		* @returns {typeof scopes[number]} Current scope context | ||||
| 		*/ | ||||
| 		get current() { | ||||
| 			return scopes[scopes.length - 1]; | ||||
| @@ -708,9 +695,9 @@ var DDE = (() => { | ||||
| 	memo.isScope = function(obj) { | ||||
| 		return obj[memoMark]; | ||||
| 	}; | ||||
| 	memo.scope = function memoScope(fun, { signal: signal2, onlyLast } = {}) { | ||||
| 	memo.scope = function memoScopeCreate(fun, { signal: signal2, onlyLast } = {}) { | ||||
| 		let cache = oCreate(); | ||||
| 		function memoScope2(...args) { | ||||
| 		function memoScope(...args) { | ||||
| 			if (signal2 && signal2.aborted) | ||||
| 				return fun.apply(this, args); | ||||
| 			let cache_local = onlyLast ? cache : oCreate(); | ||||
| @@ -725,10 +712,10 @@ var DDE = (() => { | ||||
| 			cache = cache_local; | ||||
| 			return out; | ||||
| 		} | ||||
| 		memoScope2[memoMark] = true; | ||||
| 		memoScope2.clear = () => cache = oCreate(); | ||||
| 		if (signal2) signal2.addEventListener("abort", memoScope2.clear); | ||||
| 		return memoScope2; | ||||
| 		memoScope[memoMark] = true; | ||||
| 		memoScope.clear = () => cache = oCreate(); | ||||
| 		if (signal2) signal2.addEventListener("abort", memoScope.clear); | ||||
| 		return memoScope; | ||||
| 	}; | ||||
|  | ||||
| 	// src/signals-lib/helpers.js | ||||
| @@ -845,17 +832,17 @@ var DDE = (() => { | ||||
| 	}; | ||||
| 	var key_reactive = "__dde_reactive"; | ||||
| 	signal.el = function(s, map) { | ||||
| 		map = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | ||||
| 		const mark_start = createElement.mark({ type: "reactive", source: new Defined().compact }, true); | ||||
| 		const mapScoped = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | ||||
| 		const { current } = scope, { scope: sc } = current; | ||||
| 		const mark_start = createElement.mark({ type: "reactive", component: sc && sc.name || "" }, true); | ||||
| 		const mark_end = mark_start.end; | ||||
| 		const out = enviroment.D.createDocumentFragment(); | ||||
| 		out.append(mark_start, mark_end); | ||||
| 		const { current } = scope; | ||||
| 		const reRenderReactiveElement = (v) => { | ||||
| 			if (!mark_start.parentNode || !mark_end.parentNode) | ||||
| 				return removeSignalListener(s, reRenderReactiveElement); | ||||
| 			scope.push(current); | ||||
| 			let els = map(v); | ||||
| 			let els = mapScoped(v); | ||||
| 			scope.pop(); | ||||
| 			if (!Array.isArray(els)) | ||||
| 				els = [els]; | ||||
| @@ -875,7 +862,7 @@ var DDE = (() => { | ||||
| 		current.host(on.disconnected( | ||||
| 			() => ( | ||||
| 				/*! Clears cached elements for reactive element `S.el` */ | ||||
| 				map.clear() | ||||
| 				mapScoped.clear() | ||||
| 			) | ||||
| 		)); | ||||
| 		return out; | ||||
| @@ -947,9 +934,8 @@ var DDE = (() => { | ||||
| 	function removeSignalsFromElements(s, listener, ...notes) { | ||||
| 		const { current } = scope; | ||||
| 		current.host(function(element) { | ||||
| 			if (element[key_reactive]) | ||||
| 				return element[key_reactive].push([[s, listener], ...notes]); | ||||
| 			element[key_reactive] = []; | ||||
| 			if (!element[key_reactive]) element[key_reactive] = []; | ||||
| 			element[key_reactive].push([[s, listener], ...notes]); | ||||
| 			if (current.prevent) return; | ||||
| 			on.disconnected( | ||||
| 				() => ( | ||||
| @@ -994,7 +980,6 @@ var DDE = (() => { | ||||
| 				onclear, | ||||
| 				host, | ||||
| 				listeners: /* @__PURE__ */ new Set(), | ||||
| 				defined: new Defined().stack, | ||||
| 				readonly | ||||
| 			}), | ||||
| 			enumerable: false, | ||||
|   | ||||
							
								
								
									
										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
											
										
									
								
							
							
								
								
									
										14
									
								
								dist/iife.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								dist/iife.js
									
									
									
									
										vendored
									
									
								
							| @@ -316,7 +316,7 @@ var DDE = (() => { | ||||
| 	var scope = { | ||||
| 		/** | ||||
| 		* Gets the current scope | ||||
| 		* @returns {Object} Current scope context | ||||
| 		* @returns {typeof scopes[number]} Current scope context | ||||
| 		*/ | ||||
| 		get current() { | ||||
| 			return scopes[scopes.length - 1]; | ||||
| @@ -676,9 +676,9 @@ var DDE = (() => { | ||||
| 	memo.isScope = function(obj) { | ||||
| 		return obj[memoMark]; | ||||
| 	}; | ||||
| 	memo.scope = function memoScope(fun, { signal, onlyLast } = {}) { | ||||
| 	memo.scope = function memoScopeCreate(fun, { signal, onlyLast } = {}) { | ||||
| 		let cache = oCreate(); | ||||
| 		function memoScope2(...args) { | ||||
| 		function memoScope(...args) { | ||||
| 			if (signal && signal.aborted) | ||||
| 				return fun.apply(this, args); | ||||
| 			let cache_local = onlyLast ? cache : oCreate(); | ||||
| @@ -693,10 +693,10 @@ var DDE = (() => { | ||||
| 			cache = cache_local; | ||||
| 			return out; | ||||
| 		} | ||||
| 		memoScope2[memoMark] = true; | ||||
| 		memoScope2.clear = () => cache = oCreate(); | ||||
| 		if (signal) signal.addEventListener("abort", memoScope2.clear); | ||||
| 		return memoScope2; | ||||
| 		memoScope[memoMark] = true; | ||||
| 		memoScope.clear = () => cache = oCreate(); | ||||
| 		if (signal) signal.addEventListener("abort", memoScope.clear); | ||||
| 		return memoScope; | ||||
| 	}; | ||||
| 	return __toCommonJS(index_exports); | ||||
| })(); | ||||
|   | ||||
							
								
								
									
										2
									
								
								dist/iife.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/iife.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -23,14 +23,6 @@ export function DataDashboard() { | ||||
| 		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'] | ||||
| 	}; | ||||
|  | ||||
| 	// Application state | ||||
| 	const selectedYear = S(2024); | ||||
| 	const selectedDataType = S(/** @type {'sales' | 'visitors' | 'conversion'} */ ('sales')); | ||||
| 	const isLoading = S(false); | ||||
| 	const error = S(null); | ||||
|  | ||||
| 	// Filter options | ||||
| 	const years = [2022, 2023, 2024]; | ||||
| 	const dataTypes = [ | ||||
| 		{ id: 'sales', label: 'Sales', unit: 'K' }, | ||||
| @@ -38,42 +30,32 @@ export function DataDashboard() { | ||||
| 		{ id: 'conversion', label: 'Conversion Rate', unit: '%' } | ||||
| 	]; | ||||
|  | ||||
| 	// Computed values | ||||
| 	const selectedData = S(() => { | ||||
| 		return DATA[selectedDataType.get()]; | ||||
| 	}); | ||||
|  | ||||
| 	const currentDataType = S(() => { | ||||
| 		return dataTypes.find(type => type.id === selectedDataType.get()); | ||||
| 	}); | ||||
|  | ||||
| 	const totalValue = S(() => { | ||||
| 		const data = selectedData.get(); | ||||
| 		return data.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(() => { | ||||
| 		return Math.max(...selectedData.get()); | ||||
| 	}); | ||||
|  | ||||
| 	// Event handlers | ||||
| 	// 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); | ||||
| @@ -114,7 +96,7 @@ export function DataDashboard() { | ||||
| 			// Draw grid labels | ||||
| 			ctx.fillStyle = '#999'; | ||||
| 			ctx.font = '12px Arial'; | ||||
| 			ctx.fillText(Math.round(maxValue * (i / 5)), 20, y + 5); | ||||
| 			ctx.fillText(Math.round(maxValue * (i / 5)).toString(), 20, y + 5); | ||||
| 		} | ||||
| 		ctx.stroke(); | ||||
|  | ||||
| @@ -154,7 +136,6 @@ export function DataDashboard() { | ||||
| 			) | ||||
| 		), | ||||
|  | ||||
| 		// Error message (only shown when there's an error) | ||||
| 		S.el(error, errorMsg => !errorMsg | ||||
| 			? el() | ||||
| 			: el("div", { className: "error-message" }).append( | ||||
| @@ -163,7 +144,6 @@ export function DataDashboard() { | ||||
| 				), | ||||
| 		), | ||||
|  | ||||
| 		// Loading indicator | ||||
| 		S.el(isLoading, loading => !loading | ||||
| 			? el() | ||||
| 			: el("div", { className: "loading-spinner" }) | ||||
|   | ||||
| @@ -27,25 +27,23 @@ const imagesSample = (url=> [ | ||||
|  * @returns {HTMLElement} Gallery element | ||||
|  */ | ||||
| export function ImageGallery(images= imagesSample) { | ||||
|  | ||||
| 	// Application state | ||||
| 	const selectedImageId = S(null); | ||||
| 	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); | ||||
| 	}); | ||||
|  | ||||
| 	// Derived state | ||||
| 	// 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); | ||||
|  | ||||
| 	// Event handlers | ||||
| 	const onImageClick = id => on("click", () => { | ||||
| 		selectedImageId.set(id); | ||||
| 		document.body.style.overflow = 'hidden'; // Prevent scrolling when lightbox is open | ||||
| @@ -76,9 +74,6 @@ export function ImageGallery(images= imagesSample) { | ||||
| 		const nextIndex = (currentIndex + 1) % images.length; | ||||
| 		selectedImageId.set(images[nextIndex].id); | ||||
| 	}; | ||||
| 	const onFilterChange = tag => on("click", () => { | ||||
| 		filterTag.set(tag); | ||||
| 	}); | ||||
|  | ||||
| 	// Keyboard navigation handler | ||||
| 	function handleKeyDown(e) { | ||||
|   | ||||
| @@ -73,8 +73,17 @@ export function Form({ initial }) { | ||||
| 			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]); | ||||
| 	}; | ||||
|  | ||||
| 	// Derived signals for validation | ||||
| 	// Form validate state | ||||
| 	const nameValid = S(() => formState.get().name.length >= 3); | ||||
| 	const emailValid = S(() => { | ||||
| 		const email = formState.get().email; | ||||
| @@ -89,8 +98,6 @@ export function Form({ initial }) { | ||||
| 		return password === confirmPassword && confirmPassword !== ''; | ||||
| 	}); | ||||
| 	const termsAgreed = S(() => formState.get().agreedToTerms); | ||||
|  | ||||
| 	// Overall form validity | ||||
| 	const formValid = S(() => | ||||
| 		nameValid.get() && | ||||
| 		emailValid.get() && | ||||
| @@ -99,16 +106,6 @@ export function Form({ initial }) { | ||||
| 		termsAgreed.get() | ||||
| 	); | ||||
|  | ||||
| 	// Event handlers | ||||
| 	/** | ||||
| 	 * 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]); | ||||
| 	}; | ||||
| 	const dispatcSubmit = dispatchEvent("form:submit", host); | ||||
| 	const onSubmit = on("submit", e => { | ||||
| 		e.preventDefault(); | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| // Example of reactive element marker | ||||
| <!--<dde:mark type="reactive" source="...">--> | ||||
| <!--<dde:mark type="reactive" component="<component-name>">--> | ||||
| <!-- content that updates when signal changes --> | ||||
| <!--</dde:mark>--> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ export function mnemonic(){ | ||||
| 		), | ||||
| 		el("li").append( | ||||
| 			el("code", "el(<tag-name>, <primitive>)[.append(...)]: <element-from-tag-name>"), | ||||
| 			" — simple element containing only text", | ||||
| 			" — simple element containing only text (accepts string, number or signal)", | ||||
| 		), | ||||
| 		el("li").append( | ||||
| 			el("code", "el(<tag-name>, <object>)[.append(...)]: <element-from-tag-name>"), | ||||
| @@ -26,6 +26,6 @@ export function mnemonic(){ | ||||
| 		el("li").append( | ||||
| 			el("code", "elNS(<namespace>)(<as-el-see-above>)[.append(...)]: <element-based-on-arguments>"), | ||||
| 			" — typically SVG elements", | ||||
| 		) | ||||
| 		), | ||||
| 	); | ||||
| } | ||||
|   | ||||
| @@ -29,7 +29,7 @@ export function mnemonic(){ | ||||
| 		el("li").append( | ||||
| 			el("code", "S.clear(...<signals>)"), | ||||
| 			" — off and clear signals (most of the time it is not needed as reactive ", | ||||
| 			"attributes and elements are cleared automatically)", | ||||
| 			"attributes and elements are handled automatically)", | ||||
| 		), | ||||
| 	); | ||||
| } | ||||
|   | ||||
| @@ -82,7 +82,7 @@ export function page({ pkg, info }){ | ||||
|  | ||||
| 		el("p").append(T` | ||||
| 			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 })}), | ||||
| 			but is there presented in simpler form. | ||||
| 		`), | ||||
| @@ -105,7 +105,7 @@ export function page({ pkg, info }){ | ||||
| 			or directly include it from a CDN for quick prototyping. | ||||
| 		`), | ||||
| 		el("h4", "npm installation"), | ||||
| 		el(code, { content: "npm install deka-dom-el # Coming soon", language: "shell", page_id }), | ||||
| 		el(code, { content: "npm install deka-dom-el --save", language: "shell", page_id }), | ||||
| 		el("h4", "CDN / Direct Script Usage"), | ||||
| 		el("p").append(T` | ||||
| 			Use the interactive selector below to choose your preferred format: | ||||
| @@ -154,6 +154,10 @@ export function page({ pkg, info }){ | ||||
| 				Interactive demos with server-side pre-rendering`), | ||||
| 			el("li").append(T`${el("a", { href: "p13-appendix.html" }).append(el("strong", "Appendix & Summary"))} — | ||||
| 				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` | ||||
| 			Each section builds on the previous ones, so we recommend following them in order. | ||||
|   | ||||
| @@ -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`Integrate third-party libraries`), | ||||
| 				el("li", t`Create reusable element behaviors`), | ||||
| 				el("li", t`Capture element references`) | ||||
| 				el("li", t`Capture element references`), // TODO: add example? | ||||
| 			) | ||||
| 		), | ||||
| 		el("p").append(T` | ||||
|   | ||||
| @@ -277,7 +277,72 @@ export function page({ pkg, info }){ | ||||
| 			`), | ||||
| 			el("li").append(T` | ||||
| 				${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( | ||||
| @@ -298,6 +363,6 @@ export function page({ pkg, info }){ | ||||
| 			) | ||||
| 		), | ||||
|  | ||||
| 		el(mnemonic) | ||||
| 		el(mnemonic), | ||||
| 	); | ||||
| } | ||||
|   | ||||
| @@ -55,7 +55,7 @@ export function page({ pkg, info }){ | ||||
| 				el(MyComponent); | ||||
|  | ||||
| 				function MyComponent() { | ||||
| 					// 2. access the host element | ||||
| 					// 2. access the host element (or other scope related values) | ||||
| 					const { host } = scope; | ||||
|  | ||||
| 					// 3. Add behavior to host | ||||
|   | ||||
| @@ -80,8 +80,7 @@ export function page({ pkg, info }){ | ||||
| 			el("li", t`listeners: A Set of functions called when the signal value changes`), | ||||
| 			el("li", t`actions: Custom actions that can be performed on the signal`), | ||||
| 			el("li", t`onclear: Functions to run when the signal is cleared`), | ||||
| 			el("li", t`host: Reference to the host element/scope`), | ||||
| 			el("li", t`defined: Stack trace information for debugging`), | ||||
| 			el("li", t`host: Reference to the host element/scope in which the signal was created`), | ||||
| 			el("li", t`readonly: Boolean flag indicating if the signal is read-only`) | ||||
| 		), | ||||
| 		el("p").append(T` | ||||
| @@ -114,7 +113,13 @@ export function page({ pkg, info }){ | ||||
| 		el("ul").append( | ||||
| 			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`That the signal is actually connected to the DOM element (check your S.el or attribute binding code)`) | ||||
| 			el("li", t`That the signal is actually connected to the DOM element (check your S.el or attribute binding | ||||
| 				code)`), | ||||
| 			el("li").append(T` | ||||
| 				That you’re passing signal corecctly (without using ${el("code", "*.get()")}) and for ${el("code", | ||||
| 				"S.el")} that you passing (derived) signals not a function (use ${el("code", | ||||
| 				"S.el(S(()=> count.get() % 2), odd=> …)")}). | ||||
| 			`), | ||||
| 		), | ||||
| 		el(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }), | ||||
|  | ||||
|   | ||||
| @@ -129,75 +129,6 @@ export function page({ pkg, info }){ | ||||
| 			`) | ||||
| 		), | ||||
|  | ||||
| 		el("h4", t`Using Signals Appropriately`), | ||||
| 		el("p").append(T` | ||||
| 			While signals provide powerful reactivity for complex UI interactions, they’re not always necessary. | ||||
| 		`), | ||||
|  | ||||
| 		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("p").append(T` | ||||
| 				A good approach is to started with variables/constants and when necessary, convert them to signals. | ||||
| 			`), | ||||
| 		), | ||||
|  | ||||
| 		el("h4", t`Migrating from Traditional Approaches`), | ||||
| 		el("p").append(T` | ||||
| 			When migrating from traditional DOM manipulation or other frameworks to dd<el>: | ||||
| @@ -394,46 +325,46 @@ export function page({ pkg, info }){ | ||||
| 				el("tr").append( | ||||
| 					el("th", "Feature"), | ||||
| 					el("th", "dd<el>"), | ||||
| 					el("th", "React"), | ||||
| 					el("th", "Vue"), | ||||
| 					el("th", "Svelte") | ||||
| 					el("th", "VanJS"), | ||||
| 					el("th", "Solid"), | ||||
| 					el("th", "Alpine") | ||||
| 				) | ||||
| 			), | ||||
| 			el("tbody").append( | ||||
| 				el("tr").append( | ||||
| 					el("td", "No Build Step Required"), | ||||
| 					el("td", "✅"), | ||||
| 					el("td", "✅"), | ||||
| 					el("td", "⚠️ JSX needs transpilation"), | ||||
| 					el("td", "⚠️ SFC needs compilation"), | ||||
| 					el("td", "❌ Requires compilation") | ||||
| 					el("td", "✅") | ||||
| 				), | ||||
| 				el("tr").append( | ||||
| 					el("td", "Bundle Size (minimal)"), | ||||
| 					el("td", "~10-15kb"), | ||||
| 					el("td", "~40kb+"), | ||||
| 					el("td", "~33kb+"), | ||||
| 					el("td", "Minimal runtime") | ||||
| 					el("td", "Bundle Size (minified)"), | ||||
| 					el("td", "~14kb"), | ||||
| 					el("td", "~3kb"), | ||||
| 					el("td", "~20kb"), | ||||
| 					el("td", "~43kb") | ||||
| 				), | ||||
| 				el("tr").append( | ||||
| 					el("td", "Reactivity Model"), | ||||
| 					el("td", "Signal-based"), | ||||
| 					el("td", "Virtual DOM diffing"), | ||||
| 					el("td", "Proxy-based"), | ||||
| 					el("td", "Compile-time reactivity") | ||||
| 					el("td", "Signal-based (basics only)"), | ||||
| 					el("td", "Signal-based"), | ||||
| 					el("td", "MVVM + Proxy") | ||||
| 				), | ||||
| 				el("tr").append( | ||||
| 					el("td", "DOM Interface"), | ||||
| 					el("td", "Direct DOM API"), | ||||
| 					el("td", "Virtual DOM"), | ||||
| 					el("td", "Virtual DOM"), | ||||
| 					el("td", "Compiled DOM updates") | ||||
| 					el("td", "Direct DOM API"), | ||||
| 					el("td", "Compiled DOM updates"), | ||||
| 					el("td", "Directive-based") | ||||
| 				), | ||||
| 				el("tr").append( | ||||
| 					el("td", "Server-Side Rendering"), | ||||
| 					el("td", "✅ Basic Support"), | ||||
| 					el("td", "✅ Basic Support"), | ||||
| 					el("td", "✅ Advanced"), | ||||
| 					el("td", "✅ Advanced"), | ||||
| 					el("td", "✅ Advanced") | ||||
| 					el("td", "❌") | ||||
| 				) | ||||
| 			) | ||||
| 		), | ||||
|   | ||||
							
								
								
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +1,12 @@ | ||||
| { | ||||
| 	"name": "deka-dom-el", | ||||
| 	"version": "0.9.2-alpha", | ||||
| 	"version": "0.9.3-alpha", | ||||
| 	"lockfileVersion": 3, | ||||
| 	"requires": true, | ||||
| 	"packages": { | ||||
| 		"": { | ||||
| 			"name": "deka-dom-el", | ||||
| 			"version": "0.9.2-alpha", | ||||
| 			"version": "0.9.3-alpha", | ||||
| 			"license": "MIT", | ||||
| 			"devDependencies": { | ||||
| 				"@size-limit/preset-small-lib": "~11.2", | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
| 	"name": "deka-dom-el", | ||||
| 	"version": "0.9.2-alpha", | ||||
| 	"version": "0.9.3-alpha", | ||||
| 	"description": "A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks.", | ||||
| 	"author": "Jan Andrle <andrle.jan@centrum.cz>", | ||||
| 	"license": "MIT", | ||||
| @@ -85,7 +85,12 @@ | ||||
| 	"modifyEsbuildConfig": { | ||||
| 		"platform": "browser" | ||||
| 	}, | ||||
| 	"scripts": {}, | ||||
| 	"scripts": { | ||||
| 		"test": "echo \"Error: no tests yet\"", | ||||
| 		"build": "bs/build.js", | ||||
| 		"lint": "bs/lint.sh", | ||||
| 		"docs": "bs/docs.js" | ||||
| 	}, | ||||
| 	"keywords": [ | ||||
| 		"dom", | ||||
| 		"javascript", | ||||
|   | ||||
| @@ -19,7 +19,7 @@ const store_abort= new WeakMap(); | ||||
| export const scope= { | ||||
| 	/** | ||||
| 	 * Gets the current scope | ||||
| 	 * @returns {Object} Current scope context | ||||
| 	 * @returns {typeof scopes[number]} Current scope context | ||||
| 	 */ | ||||
| 	get current(){ return scopes[scopes.length-1]; }, | ||||
|  | ||||
|   | ||||
| @@ -68,21 +68,3 @@ export function observedAttributes(instance, observedAttribute){ | ||||
|  * @returns {string} The camelCase string | ||||
|  */ | ||||
| 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 {boolean} [options.onlyLast=false] | ||||
|  * */ | ||||
| memo.scope= function memoScope(fun, { signal, onlyLast }= {}){ | ||||
| memo.scope= function memoScopeCreate(fun, { signal, onlyLast }= {}){ | ||||
| 	let cache= oCreate(); | ||||
| 	function memoScope(...args){ | ||||
| 		if(signal && signal.aborted) | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { queueSignalWrite, mark } from "./helpers.js"; | ||||
| export { mark }; | ||||
| import { hasOwn, Defined, oCreate, oAssign } from "../helpers.js"; | ||||
| import { hasOwn, oCreate, oAssign } from "../helpers.js"; | ||||
|  | ||||
| const Signal = oCreate(null, { | ||||
| 	get: { value(){ return read(this); } }, | ||||
| @@ -169,21 +169,21 @@ import { memo } from "../memo.js"; | ||||
|  * Creates a reactive DOM element that re-renders when signal changes | ||||
|  * | ||||
|  * @param {Object} s - Signal object to watch | ||||
|  * @param {Function} map - Function mapping signal value to DOM elements | ||||
|  * @param {Function} mapScoped - Function mapping signal value to DOM elements | ||||
|  * @returns {DocumentFragment} Fragment containing reactive elements | ||||
|  */ | ||||
| signal.el= function(s, map){ | ||||
| 	map= memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | ||||
| 	const mark_start= el.mark({ type: "reactive", source: new Defined().compact }, true); | ||||
| 	const mapScoped= memo.isScope(map) ? map : memo.scope(map, { onlyLast: true }); | ||||
| 	const { current }= scope, { scope: sc }= current; | ||||
| 	const mark_start= el.mark({ type: "reactive", component: sc && sc.name || "" }, true); | ||||
| 	const mark_end= mark_start.end; | ||||
| 	const out= env.D.createDocumentFragment(); | ||||
| 	out.append(mark_start, mark_end); | ||||
| 	const { current }= scope; | ||||
| 	const reRenderReactiveElement= v=> { | ||||
| 		if(!mark_start.parentNode || !mark_end.parentNode) // === `isConnected` or wasn’t yet rendered | ||||
| 			return removeSignalListener(s, reRenderReactiveElement); | ||||
| 		scope.push(current); | ||||
| 		let els= map(v); | ||||
| 		let els= mapScoped(v); | ||||
| 		scope.pop(); | ||||
| 		if(!Array.isArray(els)) | ||||
| 			els= [ els ]; | ||||
| @@ -202,7 +202,7 @@ signal.el= function(s, map){ | ||||
| 	reRenderReactiveElement(s.get()); | ||||
| 	current.host(on.disconnected(()=> | ||||
| 		/*! Clears cached elements for reactive element `S.el` */ | ||||
| 		map.clear() | ||||
| 		mapScoped.clear() | ||||
| 	)); | ||||
| 	return out; | ||||
| }; | ||||
| @@ -314,9 +314,8 @@ export const signals_config= { | ||||
| function removeSignalsFromElements(s, listener, ...notes){ | ||||
| 	const { current }= scope; | ||||
| 	current.host(function(element){ | ||||
| 		if(element[key_reactive]) | ||||
| 			return element[key_reactive].push([ [ s, listener ], ...notes ]); | ||||
| 		element[key_reactive]= []; | ||||
| 		if(!element[key_reactive]) element[key_reactive]= []; | ||||
| 		element[key_reactive].push([ [ s, listener ], ...notes ]); | ||||
| 		if(current.prevent) return; // typically document.body, doenst need auto-remove as it should happen on page leave | ||||
| 		on.disconnected(()=> | ||||
| 			/*! Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, …?). | ||||
| @@ -386,7 +385,6 @@ function toSignal(s, value, actions, readonly= false){ | ||||
| 		value: oAssign(oCreate(protoSigal), { | ||||
| 			value, actions, onclear, host, | ||||
| 			listeners: new Set(), | ||||
| 			defined: new Defined().stack, | ||||
| 			readonly | ||||
| 		}), | ||||
| 		enumerable: false, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user